2019-08-03 16:16:07 +08:00

2597 lines
55 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @author Mugen87 / https://github.com/Mugen87
*/
/* global chevrotain */
THREE.VRMLLoader = ( function () {
// dependency check
if ( typeof chevrotain === 'undefined' ) {
throw Error( 'THREE.VRMLLoader: External library chevrotain.min.js required.' );
}
// class definitions
function VRMLLoader( manager ) {
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
}
VRMLLoader.prototype = {
constructor: VRMLLoader,
crossOrigin: 'anonymous',
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var path = ( scope.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path;
var loader = new THREE.FileLoader( this.manager );
loader.setPath( scope.path );
loader.load( url, function ( text ) {
onLoad( scope.parse( text, path ) );
}, onProgress, onError );
},
setPath: function ( value ) {
this.path = value;
return this;
},
setResourcePath: function ( value ) {
this.resourcePath = value;
return this;
},
setCrossOrigin: function ( value ) {
this.crossOrigin = value;
return this;
},
parse: function ( data, path ) {
var nodeMap = {};
function generateVRMLTree( data ) {
// create lexer, parser and visitor
var tokenData = createTokens();
var lexer = new VRMLLexer( tokenData.tokens );
var parser = new VRMLParser( tokenData.tokenVocabulary );
var visitor = createVisitor( parser.getBaseCstVisitorConstructor() );
// lexing
var lexingResult = lexer.lex( data );
parser.input = lexingResult.tokens;
// parsing
var cstOutput = parser.vrml();
if ( parser.errors.length > 0 ) {
console.error( parser.errors );
throw Error( 'THREE.VRMLLoader: Parsing errors detected.' );
}
// actions
var ast = visitor.visit( cstOutput );
return ast;
}
function createTokens() {
var createToken = chevrotain.createToken;
// from http://gun.teipir.gr/VRML-amgem/spec/part1/concepts.html#SyntaxBasics
var RouteIdentifier = createToken( { name: 'RouteIdentifier', pattern: /[^\x30-\x39\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d][^\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d]*[\.][^\x30-\x39\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d][^\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d]*/ } );
var Identifier = createToken( { name: 'Identifier', pattern: /[^\x30-\x39\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d][^\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d]*/, longer_alt: RouteIdentifier } );
// from http://gun.teipir.gr/VRML-amgem/spec/part1/nodesRef.html
var nodeTypes = [
'Anchor', 'Billboard', 'Collision', 'Group', 'Transform', // grouping nodes
'Inline', 'LOD', 'Switch', // special groups
'AudioClip', 'DirectionalLight', 'PointLight', 'Script', 'Shape', 'Sound', 'SpotLight', 'WorldInfo', // common nodes
'CylinderSensor', 'PlaneSensor', 'ProximitySensor', 'SphereSensor', 'TimeSensor', 'TouchSensor', 'VisibilitySensor', // sensors
'Box', 'Cone', 'Cylinder', 'ElevationGrid', 'Extrusion', 'IndexedFaceSet', 'IndexedLineSet', 'PointSet', 'Sphere', // geometries
'Color', 'Coordinate', 'Normal', 'TextureCoordinate', // geometric properties
'Appearance', 'FontStyle', 'ImageTexture', 'Material', 'MovieTexture', 'PixelTexture', 'TextureTransform', // appearance
'ColorInterpolator', 'CoordinateInterpolator', 'NormalInterpolator', 'OrientationInterpolator', 'PositionInterpolator', 'ScalarInterpolator', // interpolators
'Background', 'Fog', 'NavigationInfo', 'Viewpoint', // bindable nodes
'Text' // Text must be placed at the end of the regex so there are no matches for TextureTransform and TextureCoordinate
];
//
var Version = createToken( {
name: 'Version',
pattern: /#VRML.*/,
longer_alt: Identifier
} );
var NodeName = createToken( {
name: 'NodeName',
pattern: new RegExp( nodeTypes.join( '|' ) ),
longer_alt: Identifier
} );
var DEF = createToken( {
name: 'DEF',
pattern: /DEF/,
longer_alt: Identifier
} );
var USE = createToken( {
name: 'USE',
pattern: /USE/,
longer_alt: Identifier
} );
var ROUTE = createToken( {
name: 'ROUTE',
pattern: /ROUTE/,
longer_alt: Identifier
} );
var TO = createToken( {
name: 'TO',
pattern: /TO/,
longer_alt: Identifier
} );
//
var StringLiteral = createToken( { name: "StringLiteral", pattern: /"(:?[^\\"\n\r]+|\\(:?[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*"/ } );
var NumberLiteral = createToken( { name: 'NumberLiteral', pattern: /[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/ } );
var TrueLiteral = createToken( { name: 'TrueLiteral', pattern: /TRUE/ } );
var FalseLiteral = createToken( { name: 'FalseLiteral', pattern: /FALSE/ } );
var NullLiteral = createToken( { name: 'NullLiteral', pattern: /NULL/ } );
var LSquare = createToken( { name: 'LSquare', pattern: /\[/ } );
var RSquare = createToken( { name: 'RSquare', pattern: /]/ } );
var LCurly = createToken( { name: 'LCurly', pattern: /{/ } );
var RCurly = createToken( { name: 'RCurly', pattern: /}/ } );
var Comment = createToken( {
name: 'Comment',
pattern: /#.*/,
group: chevrotain.Lexer.SKIPPED
} );
// commas, blanks, tabs, newlines and carriage returns are whitespace characters wherever they appear outside of string fields
var WhiteSpace = createToken( {
name: 'WhiteSpace',
pattern: /[ ,\s]/,
group: chevrotain.Lexer.SKIPPED
} );
var tokens = [
WhiteSpace,
// keywords appear before the Identifier
NodeName,
DEF,
USE,
ROUTE,
TO,
TrueLiteral,
FalseLiteral,
NullLiteral,
// the Identifier must appear after the keywords because all keywords are valid identifiers
Version,
Identifier,
RouteIdentifier,
StringLiteral,
NumberLiteral,
LSquare,
RSquare,
LCurly,
RCurly,
Comment
];
var tokenVocabulary = {};
for ( var i = 0, l = tokens.length; i < l; i ++ ) {
var token = tokens[ i ];
tokenVocabulary[ token.name ] = token;
}
return { tokens: tokens, tokenVocabulary: tokenVocabulary };
}
function createVisitor( BaseVRMLVisitor ) {
// the visitor is created dynmaically based on the given base class
function VRMLToASTVisitor() {
BaseVRMLVisitor.call( this );
this.validateVisitor();
}
VRMLToASTVisitor.prototype = Object.assign( Object.create( BaseVRMLVisitor.prototype ), {
constructor: VRMLToASTVisitor,
vrml: function ( ctx ) {
var data = {
version: this.visit( ctx.version ),
nodes: [],
routes: []
};
for ( var i = 0, l = ctx.node.length; i < l; i ++ ) {
var node = ctx.node[ i ];
data.nodes.push( this.visit( node ) );
}
if ( ctx.route ) {
for ( var i = 0, l = ctx.route.length; i < l; i ++ ) {
var route = ctx.route[ i ];
data.routes.push( this.visit( route ) );
}
}
return data;
},
version: function ( ctx ) {
return ctx.Version[ 0 ].image;
},
node: function ( ctx ) {
var data = {
name: ctx.NodeName[ 0 ].image,
fields: []
};
if ( ctx.field ) {
for ( var i = 0, l = ctx.field.length; i < l; i ++ ) {
var field = ctx.field[ i ];
data.fields.push( this.visit( field ) );
}
}
// DEF
if ( ctx.def ) {
data.DEF = this.visit( ctx.def[ 0 ] );
}
return data;
},
field: function ( ctx ) {
var data = {
name: ctx.Identifier[ 0 ].image,
type: null,
values: null
};
var result;
// SFValue
if ( ctx.singleFieldValue ) {
result = this.visit( ctx.singleFieldValue[ 0 ] );
}
// MFValue
if ( ctx.multiFieldValue ) {
result = this.visit( ctx.multiFieldValue[ 0 ] );
}
data.type = result.type;
data.values = result.values;
return data;
},
def: function ( ctx ) {
return ctx.Identifier[ 0 ].image;
},
use: function ( ctx ) {
return { USE: ctx.Identifier[ 0 ].image };
},
singleFieldValue: function ( ctx ) {
return processField( this, ctx );
},
multiFieldValue: function ( ctx ) {
return processField( this, ctx );
},
route: function ( ctx ) {
var data = {
FROM: ctx.RouteIdentifier[ 0 ].image,
TO: ctx.RouteIdentifier[ 1 ].image
};
return data;
}
} );
function processField( scope, ctx ) {
var field = {
type: null,
values: []
};
if ( ctx.node ) {
field.type = 'node';
for ( var i = 0, l = ctx.node.length; i < l; i ++ ) {
var node = ctx.node[ i ];
field.values.push( scope.visit( node ) );
}
}
if ( ctx.use ) {
field.type = 'use';
for ( var i = 0, l = ctx.use.length; i < l; i ++ ) {
var use = ctx.use[ i ];
field.values.push( scope.visit( use ) );
}
}
if ( ctx.StringLiteral ) {
field.type = 'string';
for ( var i = 0, l = ctx.StringLiteral.length; i < l; i ++ ) {
var stringLiteral = ctx.StringLiteral[ i ];
field.values.push( stringLiteral.image.replace( /'|"/g, '' ) );
}
}
if ( ctx.NumberLiteral ) {
field.type = 'number';
for ( var i = 0, l = ctx.NumberLiteral.length; i < l; i ++ ) {
var numberLiteral = ctx.NumberLiteral[ i ];
field.values.push( parseFloat( numberLiteral.image ) );
}
}
if ( ctx.TrueLiteral ) {
field.type = 'boolean';
for ( var i = 0, l = ctx.TrueLiteral.length; i < l; i ++ ) {
var trueLiteral = ctx.TrueLiteral[ i ];
if ( trueLiteral.image === 'TRUE' ) field.values.push( true );
}
}
if ( ctx.FalseLiteral ) {
field.type = 'boolean';
for ( var i = 0, l = ctx.FalseLiteral.length; i < l; i ++ ) {
var falseLiteral = ctx.FalseLiteral[ i ];
if ( falseLiteral.image === 'FALSE' ) field.values.push( false );
}
}
if ( ctx.NullLiteral ) {
field.type = 'null';
ctx.NullLiteral.forEach( function () {
field.values.push( null );
} );
}
return field;
}
return new VRMLToASTVisitor();
}
function parseTree( tree ) {
// console.log( JSON.stringify( tree, null, 2 ) );
var nodes = tree.nodes;
var scene = new THREE.Scene();
// first iteration: build nodemap based on DEF statements
for ( var i = 0, l = nodes.length; i < l; i ++ ) {
var node = nodes[ i ];
buildNodeMap( node );
}
// second iteration: build nodes
for ( var i = 0, l = nodes.length; i < l; i ++ ) {
var node = nodes[ i ];
var object = getNode( node );
if ( object instanceof THREE.Object3D ) scene.add( object );
}
return scene;
}
function buildNodeMap( node ) {
if ( node.DEF ) {
nodeMap[ node.DEF ] = node;
}
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
if ( field.type === 'node' ) {
var fieldValues = field.values;
for ( var j = 0, jl = fieldValues.length; j < jl; j ++ ) {
buildNodeMap( fieldValues[ j ] );
}
}
}
}
function getNode( node ) {
// handle case where a node refers to a different one
if ( node.USE ) {
return resolveUSE( node.USE );
}
if ( node.build !== undefined ) return node.build;
node.build = buildNode( node );
return node.build;
}
// node builder
function buildNode( node ) {
var nodeName = node.name;
var build;
switch ( nodeName ) {
case 'Group':
case 'Transform':
build = buildGroupingNode( node );
break;
case 'Background':
build = buildBackgroundNode( node );
break;
case 'Shape':
build = buildShapeNode( node );
break;
case 'Appearance':
build = buildApperanceNode( node );
break;
case 'Material':
build = buildMaterialNode( node );
break;
case 'ImageTexture':
build = buildImageTextureNode( node );
break;
case 'TextureTransform':
build = buildTextureTransformNode( node );
break;
case 'IndexedFaceSet':
build = buildIndexedFaceSetNode( node );
break;
case 'IndexedLineSet':
build = buildIndexedLineSetNode( node );
break;
case 'PointSet':
build = buildPointSetNode( node );
break;
case 'Box':
build = buildBoxNode( node );
break;
case 'Cone':
build = buildConeNode( node );
break;
case 'Cylinder':
build = buildCylinderNode( node );
break;
case 'Sphere':
build = buildSphereNode( node );
break;
case 'Color':
case 'Coordinate':
case 'Normal':
case 'TextureCoordinate':
build = buildGeometricNode( node );
break;
case 'Anchor':
case 'Billboard':
case 'Collision':
case 'Inline':
case 'LOD':
case 'Switch':
case 'AudioClip':
case 'DirectionalLight':
case 'PointLight':
case 'Script':
case 'Sound':
case 'SpotLight':
case 'WorldInfo':
case 'CylinderSensor':
case 'PlaneSensor':
case 'ProximitySensor':
case 'SphereSensor':
case 'TimeSensor':
case 'TouchSensor':
case 'VisibilitySensor':
case 'ElevationGrid':
case 'Extrusion':
case 'Text':
case 'FontStyle':
case 'MovieTexture':
case 'PixelTexture':
case 'ColorInterpolator':
case 'CoordinateInterpolator':
case 'NormalInterpolator':
case 'OrientationInterpolator':
case 'PositionInterpolator':
case 'ScalarInterpolator':
case 'Fog':
case 'NavigationInfo':
case 'Viewpoint':
// node not supported yet
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown node:', nodeName );
break;
}
return build;
}
function buildGroupingNode( node ) {
var object = new THREE.Group();
//
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'center':
// field not supported
break;
case 'children':
parseFieldChildren( fieldValues, object );
break;
case 'rotation':
var axis = new THREE.Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
var angle = fieldValues[ 3 ];
object.quaternion.setFromAxisAngle( axis, angle );
break;
case 'scale':
object.scale.set( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
break;
case 'scaleOrientation':
// field not supported
break;
case 'translation':
object.position.set( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
break;
case 'bboxCenter':
// field not supported
break;
case 'bboxSize':
// field not supported
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
return object;
}
function buildBackgroundNode( node ) {
var group = new THREE.Group();
var groundAngle, groundColor;
var skyAngle, skyColor;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'groundAngle':
groundAngle = fieldValues;
break;
case 'groundColor':
groundColor = fieldValues;
break;
case 'backUrl':
// field not supported
break;
case 'bottomUrl':
// field not supported
break;
case 'frontUrl':
// field not supported
break;
case 'leftUrl':
// field not supported
break;
case 'rightUrl':
// field not supported
break;
case 'topUrl':
// field not supported
break;
case 'skyAngle':
skyAngle = fieldValues;
break;
case 'skyColor':
skyColor = fieldValues;
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
// sky
if ( skyColor ) {
var radius = 10000;
var skyGeometry = new THREE.SphereBufferGeometry( radius, 32, 16 );
var skyMaterial = new THREE.MeshBasicMaterial( { fog: false, side: THREE.BackSide, depthWrite: false, depthTest: false } );
if ( skyColor.length > 3 ) {
paintFaces( skyGeometry, radius, skyAngle, toColorArray( skyColor ), true );
skyMaterial.vertexColors = THREE.VertexColors;
} else {
skyMaterial.color.setRGB( skyColor[ 0 ], skyColor[ 1 ], skyColor[ 2 ] );
}
var sky = new THREE.Mesh( skyGeometry, skyMaterial );
group.add( sky );
}
// ground
if ( groundColor ) {
if ( groundColor.length > 0 ) {
var groundGeometry = new THREE.SphereBufferGeometry( radius, 32, 16, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI );
var groundMaterial = new THREE.MeshBasicMaterial( { fog: false, side: THREE.BackSide, vertexColors: THREE.VertexColors, depthWrite: false, depthTest: false } );
paintFaces( groundGeometry, radius, groundAngle, toColorArray( groundColor ), false );
var ground = new THREE.Mesh( groundGeometry, groundMaterial );
group.add( ground );
}
}
// render background group first
group.renderOrder = - Infinity;
return group;
}
function buildShapeNode( node ) {
var fields = node.fields;
// if the appearance field is NULL or unspecified, lighting is off and the unlit object color is (0, 0, 0)
var material = new THREE.MeshBasicMaterial( { color: 0x000000 } );
var geometry;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'appearance':
if ( fieldValues[ 0 ] !== null ) {
material = getNode( fieldValues[ 0 ] );
}
break;
case 'geometry':
if ( fieldValues[ 0 ] !== null ) {
geometry = getNode( fieldValues[ 0 ] );
}
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
// build 3D object
var object;
if ( geometry && geometry.attributes.position ) {
var type = geometry._type;
if ( type === 'points' ) { // points
var pointsMaterial = new THREE.PointsMaterial( { color: 0xffffff } );
if ( geometry.attributes.color !== undefined ) {
pointsMaterial.vertexColors = THREE.VertexColors;
} else {
// if the color field is NULL and there is a material defined for the appearance affecting this PointSet, then use the emissiveColor of the material to draw the points
if ( material.isMeshPhongMaterial ) {
pointsMaterial.color.copy( material.emissive );
}
}
object = new THREE.Points( geometry, pointsMaterial );
} else if ( type === 'line' ) { // lines
var lineMaterial = new THREE.LineBasicMaterial( { color: 0xffffff } );
if ( geometry.attributes.color !== undefined ) {
lineMaterial.vertexColors = THREE.VertexColors;
} else {
// if the color field is NULL and there is a material defined for the appearance affecting this IndexedLineSet, then use the emissiveColor of the material to draw the lines
if ( material.isMeshPhongMaterial ) {
lineMaterial.color.copy( material.emissive );
}
}
object = new THREE.LineSegments( geometry, lineMaterial );
} else { // consider meshes
// check "solid" hint (it's placed in the geometry but affects the material)
if ( geometry._solid !== undefined ) {
material.side = ( geometry._solid ) ? THREE.FrontSide : THREE.DoubleSide;
}
// check for vertex colors
if ( geometry.attributes.color !== undefined ) {
material.vertexColors = THREE.VertexColors;
}
object = new THREE.Mesh( geometry, material );
}
} else {
object = new THREE.Object3D();
// if the geometry field is NULL or no vertices are defined the object is not drawn
object.visible = false;
}
return object;
}
function buildApperanceNode( node ) {
var material = new THREE.MeshPhongMaterial();
var transformData;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'material':
if ( fieldValues[ 0 ] !== null ) {
var materialData = getNode( fieldValues[ 0 ] );
if ( materialData.diffuseColor ) material.color.copy( materialData.diffuseColor );
if ( materialData.emissiveColor ) material.emissive.copy( materialData.emissiveColor );
if ( materialData.shininess ) material.shininess = materialData.shininess;
if ( materialData.specularColor ) material.specular.copy( materialData.specularColor );
if ( materialData.transparency ) material.opacity = 1 - materialData.transparency;
if ( materialData.transparency > 0 ) material.transparent = true;
} else {
// if the material field is NULL or unspecified, lighting is off and the unlit object color is (0, 0, 0)
material = new THREE.MeshBasicMaterial( { color: 0x000000 } );
}
break;
case 'texture':
var textureNode = fieldValues[ 0 ];
if ( textureNode !== null ) {
if ( textureNode.name === 'ImageTexture' ) {
material.map = getNode( textureNode );
} else {
// MovieTexture and PixelTexture not supported yet
}
}
break;
case 'textureTransform':
if ( fieldValues[ 0 ] !== null ) {
transformData = getNode( fieldValues[ 0 ] );
}
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
// only apply texture transform data if a texture was defined
if ( material.map && transformData ) {
material.map.center.copy( transformData.center );
material.map.rotation = transformData.rotation;
material.map.repeat.copy( transformData.scale );
material.map.offset.copy( transformData.translation );
}
return material;
}
function buildMaterialNode( node ) {
var materialData = {};
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'ambientIntensity':
// field not supported
break;
case 'diffuseColor':
materialData.diffuseColor = new THREE.Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
break;
case 'emissiveColor':
materialData.emissiveColor = new THREE.Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
break;
case 'shininess':
materialData.shininess = fieldValues[ 0 ];
break;
case 'specularColor':
materialData.emissiveColor = new THREE.Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
break;
case 'transparency':
materialData.transparency = fieldValues[ 0 ];
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
return materialData;
}
function buildImageTextureNode( node ) {
var texture;
var wrapS = THREE.RepeatWrapping;
var wrapT = THREE.RepeatWrapping;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'url':
var url = fieldValues[ 0 ];
if ( url ) texture = textureLoader.load( url );
break;
case 'repeatS':
if ( fieldValues[ 0 ] === false ) wrapS = THREE.ClampToEdgeWrapping;
break;
case 'repeatT':
if ( fieldValues[ 0 ] === false ) wrapT = THREE.ClampToEdgeWrapping;
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
if ( texture ) {
texture.wrapS = wrapS;
texture.wrapT = wrapT;
}
return texture;
}
function buildTextureTransformNode( node ) {
var transformData = {
center: new THREE.Vector2(),
rotation: new THREE.Vector2(),
scale: new THREE.Vector2(),
translation: new THREE.Vector2()
};
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'center':
transformData.center.set( fieldValues[ 0 ], fieldValues[ 1 ] );
break;
case 'rotation':
transformData.rotation = fieldValues[ 0 ];
break;
case 'scale':
transformData.scale.set( fieldValues[ 0 ], fieldValues[ 1 ] );
break;
case 'translation':
transformData.translation.set( fieldValues[ 0 ], fieldValues[ 1 ] );
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
return transformData;
}
function buildGeometricNode( node ) {
return node.fields[ 0 ].values;
}
function buildIndexedFaceSetNode( node ) {
var color, coord, normal, texCoord;
var ccw = true, solid = true, creaseAngle;
var colorIndex, coordIndex, normalIndex, texCoordIndex;
var colorPerVertex = true, normalPerVertex = true;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'color':
var colorNode = fieldValues[ 0 ];
if ( colorNode !== null ) {
color = getNode( colorNode );
}
break;
case 'coord':
var coordNode = fieldValues[ 0 ];
if ( coordNode !== null ) {
coord = getNode( coordNode );
}
break;
case 'normal':
var normalNode = fieldValues[ 0 ];
if ( normalNode !== null ) {
normal = getNode( normalNode );
}
break;
case 'texCoord':
var texCoordNode = fieldValues[ 0 ];
if ( texCoordNode !== null ) {
texCoord = getNode( texCoordNode );
}
break;
case 'ccw':
ccw = fieldValues[ 0 ];
break;
case 'colorIndex':
colorIndex = fieldValues;
break;
case 'colorPerVertex':
colorPerVertex = fieldValues[ 0 ];
break;
case 'convex':
// field not supported
break;
case 'coordIndex':
coordIndex = fieldValues;
break;
case 'creaseAngle':
creaseAngle = fieldValues[ 0 ];
break;
case 'normalIndex':
normalIndex = fieldValues;
break;
case 'normalPerVertex':
normalPerVertex = fieldValues[ 0 ];
break;
case 'solid':
solid = fieldValues[ 0 ];
break;
case 'texCoordIndex':
texCoordIndex = fieldValues;
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
if ( coordIndex === undefined ) {
console.warn( 'THREE.VRMLLoader: Missing coordIndex.' );
return new THREE.BufferGeometry(); // handle VRML files with incomplete geometry definition
}
var triangulatedCoordIndex = triangulateFaceIndex( coordIndex, ccw );
var positionAttribute;
var colorAttribute;
var normalAttribute;
var uvAttribute;
if ( color ) {
if ( colorPerVertex === true ) {
if ( colorIndex && colorIndex.length > 0 ) {
// if the colorIndex field is not empty, then it is used to choose colors for each vertex of the IndexedFaceSet.
var triangulatedColorIndex = triangulateFaceIndex( colorIndex, ccw );
colorAttribute = computeAttributeFromIndexedData( triangulatedCoordIndex, triangulatedColorIndex, color, 3 );
} else {
// if the colorIndex field is empty, then the coordIndex field is used to choose colors from the Color node
colorAttribute = toNonIndexedAttribute( triangulatedCoordIndex, new THREE.Float32BufferAttribute( color, 3 ) );
}
} else {
if ( colorIndex && colorIndex.length > 0 ) {
// if the colorIndex field is not empty, then they are used to choose one color for each face of the IndexedFaceSet
var flattenFaceColors = flattenData( color, colorIndex );
var triangulatedFaceColors = triangulateFaceData( flattenFaceColors, coordIndex );
colorAttribute = computeAttributeFromFaceData( triangulatedCoordIndex, triangulatedFaceColors );
} else {
// if the colorIndex field is empty, then the color are applied to each face of the IndexedFaceSet in order
var triangulatedFaceColors = triangulateFaceData( color, coordIndex );
colorAttribute = computeAttributeFromFaceData( triangulatedCoordIndex, triangulatedFaceColors );
}
}
}
if ( normal ) {
if ( normalPerVertex === true ) {
// consider vertex normals
if ( normalIndex && normalIndex.length > 0 ) {
// if the normalIndex field is not empty, then it is used to choose normals for each vertex of the IndexedFaceSet.
var triangulatedNormalIndex = triangulateFaceIndex( normalIndex, ccw );
normalAttribute = computeAttributeFromIndexedData( triangulatedCoordIndex, triangulatedNormalIndex, normal, 3 );
} else {
// if the normalIndex field is empty, then the coordIndex field is used to choose normals from the Normal node
normalAttribute = toNonIndexedAttribute( triangulatedCoordIndex, new THREE.Float32BufferAttribute( normal, 3 ) );
}
} else {
// consider face normals
if ( normalIndex && normalIndex.length > 0 ) {
// if the normalIndex field is not empty, then they are used to choose one normal for each face of the IndexedFaceSet
var flattenFaceNormals = flattenData( normal, normalIndex );
var triangulatedFaceNormals = triangulateFaceData( flattenFaceNormals, coordIndex );
normalAttribute = computeAttributeFromFaceData( triangulatedCoordIndex, triangulatedFaceNormals );
} else {
// if the normalIndex field is empty, then the normals are applied to each face of the IndexedFaceSet in order
var triangulatedFaceNormals = triangulateFaceData( normal, coordIndex );
normalAttribute = computeAttributeFromFaceData( triangulatedCoordIndex, triangulatedFaceNormals );
}
}
} else {
// if the normal field is NULL, then the loader should automatically generate normals, using creaseAngle to determine if and how normals are smoothed across shared vertices
normalAttribute = computeNormalAttribute( triangulatedCoordIndex, coord, creaseAngle );
}
if ( texCoord ) {
// texture coordinates are always defined on vertex level
if ( texCoordIndex && texCoordIndex.length > 0 ) {
// if the texCoordIndex field is not empty, then it is used to choose texture coordinates for each vertex of the IndexedFaceSet.
var triangulatedTexCoordIndex = triangulateFaceIndex( texCoordIndex, ccw );
uvAttribute = computeAttributeFromIndexedData( triangulatedCoordIndex, triangulatedTexCoordIndex, texCoord, 2 );
} else {
// if the texCoordIndex field is empty, then the coordIndex array is used to choose texture coordinates from the TextureCoordinate node
uvAttribute = toNonIndexedAttribute( triangulatedCoordIndex, new THREE.Float32BufferAttribute( texCoord, 2 ) );
}
}
var geometry = new THREE.BufferGeometry();
positionAttribute = toNonIndexedAttribute( triangulatedCoordIndex, new THREE.Float32BufferAttribute( coord, 3 ) );
geometry.addAttribute( 'position', positionAttribute );
geometry.addAttribute( 'normal', normalAttribute );
// optional attributes
if ( colorAttribute ) geometry.addAttribute( 'color', colorAttribute );
if ( uvAttribute ) geometry.addAttribute( 'uv', uvAttribute );
// "solid" influences the material so let's store it for later use
geometry._solid = solid;
geometry._type = 'mesh';
return geometry;
}
function buildIndexedLineSetNode( node ) {
var color, coord;
var colorIndex, coordIndex;
var colorPerVertex = true;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'color':
var colorNode = fieldValues[ 0 ];
if ( colorNode !== null ) {
color = getNode( colorNode );
}
break;
case 'coord':
var coordNode = fieldValues[ 0 ];
if ( coordNode !== null ) {
coord = getNode( coordNode );
}
break;
case 'colorIndex':
colorIndex = fieldValues;
break;
case 'colorPerVertex':
colorPerVertex = fieldValues[ 0 ];
break;
case 'coordIndex':
coordIndex = fieldValues;
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
// build lines
var colorAttribute;
var expandedLineIndex = expandLineIndex( coordIndex ); // create an index for three.js's linesegment primitive
if ( color ) {
if ( colorPerVertex === true ) {
if ( colorIndex.length > 0 ) {
// if the colorIndex field is not empty, then one color is used for each polyline of the IndexedLineSet.
var expandedColorIndex = expandLineIndex( colorIndex ); // compute colors for each line segment (rendering primitve)
colorAttribute = computeAttributeFromIndexedData( expandedLineIndex, expandedColorIndex, color, 3 ); // compute data on vertex level
} else {
// if the colorIndex field is empty, then the colors are applied to each polyline of the IndexedLineSet in order.
colorAttribute = toNonIndexedAttribute( expandedLineIndex, new THREE.Float32BufferAttribute( color, 3 ) );
}
} else {
if ( colorIndex.length > 0 ) {
// if the colorIndex field is not empty, then colors are applied to each vertex of the IndexedLineSet
var flattenLineColors = flattenData( color, colorIndex ); // compute colors for each VRML primitve
var expandedLineColors = expandLineData( flattenLineColors, coordIndex ); // compute colors for each line segment (rendering primitve)
colorAttribute = computeAttributeFromLineData( expandedLineIndex, expandedLineColors ); // compute data on vertex level
} else {
// if the colorIndex field is empty, then the coordIndex field is used to choose colors from the Color node
var expandedLineColors = expandLineData( color, coordIndex ); // compute colors for each line segment (rendering primitve)
colorAttribute = computeAttributeFromLineData( expandedLineIndex, expandedLineColors ); // compute data on vertex level
}
}
}
//
var geometry = new THREE.BufferGeometry();
var positionAttribute = toNonIndexedAttribute( expandedLineIndex, new THREE.Float32BufferAttribute( coord, 3 ) );
geometry.addAttribute( 'position', positionAttribute );
if ( colorAttribute ) geometry.addAttribute( 'color', colorAttribute );
geometry._type = 'line';
return geometry;
}
function buildPointSetNode( node ) {
var geometry;
var color, coord;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'color':
var colorNode = fieldValues[ 0 ];
if ( colorNode !== null ) {
color = getNode( colorNode );
}
break;
case 'coord':
var coordNode = fieldValues[ 0 ];
if ( coordNode !== null ) {
coord = getNode( coordNode );
}
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( coord, 3 ) );
if ( color ) geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( color, 3 ) );
geometry._type = 'points';
return geometry;
}
function buildBoxNode( node ) {
var size = new THREE.Vector3( 2, 2, 2 );
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'size':
size.x = fieldValues[ 0 ];
size.y = fieldValues[ 1 ];
size.z = fieldValues[ 2 ];
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
var geometry = new THREE.BoxBufferGeometry( size.x, size.y, size.z );
return geometry;
}
function buildConeNode( node ) {
var radius = 1, height = 2, openEnded = false;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'bottom':
openEnded = ! fieldValues[ 0 ];
break;
case 'bottomRadius':
radius = fieldValues[ 0 ];
break;
case 'height':
height = fieldValues[ 0 ];
break;
case 'side':
// field not supported
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
var geometry = new THREE.ConeBufferGeometry( radius, height, 16, 1, openEnded );
return geometry;
}
function buildCylinderNode( node ) {
var radius = 1, height = 2;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'bottom':
// field not supported
break;
case 'radius':
radius = fieldValues[ 0 ];
break;
case 'height':
height = fieldValues[ 0 ];
break;
case 'side':
// field not supported
break;
case 'top':
// field not supported
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
var geometry = new THREE.CylinderBufferGeometry( radius, radius, height, 16, 1 );
return geometry;
}
function buildSphereNode( node ) {
var radius = 1;
var fields = node.fields;
for ( var i = 0, l = fields.length; i < l; i ++ ) {
var field = fields[ i ];
var fieldName = field.name;
var fieldValues = field.values;
switch ( fieldName ) {
case 'radius':
radius = fieldValues[ 0 ];
break;
default:
console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
break;
}
}
var geometry = new THREE.SphereBufferGeometry( radius, 16, 16 );
return geometry;
}
// helper functions
function resolveUSE( identifier ) {
var node = nodeMap[ identifier ];
var build = getNode( node );
// because the same 3D objects can have different transformations, it's necessary to clone them.
// materials can be influenced by the geometry (e.g. vertex normals). cloning is necessary to avoid
// any side effects
return ( build.isObject3D || build.isMaterial ) ? build.clone() : build;
}
function parseFieldChildren( children, owner ) {
for ( var i = 0, l = children.length; i < l; i ++ ) {
var object = getNode( children[ i ] );
if ( object instanceof THREE.Object3D ) owner.add( object );
}
}
function triangulateFaceIndex( index, ccw ) {
var indices = [];
// since face defintions can have more than three vertices, it's necessary to
// perform a simple triangulation
var start = 0;
for ( var i = 0, l = index.length; i < l; i ++ ) {
var i1 = index[ start ];
var i2 = index[ i + ( ccw ? 1 : 2 ) ];
var i3 = index[ i + ( ccw ? 2 : 1 ) ];
indices.push( i1, i2, i3 );
// an index of -1 indicates that the current face has ended and the next one begins
if ( index[ i + 3 ] === - 1 || i + 3 >= l ) {
i += 3;
start = i + 1;
}
}
return indices;
}
function triangulateFaceData( data, index ) {
var triangulatedData = [];
var start = 0;
for ( var i = 0, l = index.length; i < l; i ++ ) {
var stride = start * 3;
var x = data[ stride ];
var y = data[ stride + 1 ];
var z = data[ stride + 2 ];
triangulatedData.push( x, y, z );
// an index of -1 indicates that the current face has ended and the next one begins
if ( index[ i + 3 ] === - 1 || i + 3 >= l ) {
i += 3;
start ++;
}
}
return triangulatedData;
}
function flattenData( data, index ) {
var flattenData = [];
for ( var i = 0, l = index.length; i < l; i ++ ) {
var i1 = index[ i ];
var stride = i1 * 3;
var x = data[ stride ];
var y = data[ stride + 1 ];
var z = data[ stride + 2 ];
flattenData.push( x, y, z );
}
return flattenData;
}
function expandLineIndex( index ) {
var indices = [];
for ( var i = 0, l = index.length; i < l; i ++ ) {
var i1 = index[ i ];
var i2 = index[ i + 1 ];
indices.push( i1, i2 );
// an index of -1 indicates that the current line has ended and the next one begins
if ( index[ i + 2 ] === - 1 || i + 2 >= l ) {
i += 2;
}
}
return indices;
}
function expandLineData( data, index ) {
var triangulatedData = [];
var start = 0;
for ( var i = 0, l = index.length; i < l; i ++ ) {
var stride = start * 3;
var x = data[ stride ];
var y = data[ stride + 1 ];
var z = data[ stride + 2 ];
triangulatedData.push( x, y, z );
// an index of -1 indicates that the current line has ended and the next one begins
if ( index[ i + 2 ] === - 1 || i + 2 >= l ) {
i += 2;
start ++;
}
}
return triangulatedData;
}
var vA = new THREE.Vector3();
var vB = new THREE.Vector3();
var vC = new THREE.Vector3();
var uvA = new THREE.Vector2();
var uvB = new THREE.Vector2();
var uvC = new THREE.Vector2();
function computeAttributeFromIndexedData( coordIndex, index, data, itemSize ) {
var array = [];
// we use the coordIndex.length as delimiter since normalIndex must contain at least as many indices
for ( var i = 0, l = coordIndex.length; i < l; i += 3 ) {
var a = index[ i ];
var b = index[ i + 1 ];
var c = index[ i + 2 ];
if ( itemSize === 2 ) {
uvA.fromArray( data, a * itemSize );
uvB.fromArray( data, b * itemSize );
uvC.fromArray( data, c * itemSize );
array.push( uvA.x, uvA.y );
array.push( uvB.x, uvB.y );
array.push( uvC.x, uvC.y );
} else {
vA.fromArray( data, a * itemSize );
vB.fromArray( data, b * itemSize );
vC.fromArray( data, c * itemSize );
array.push( vA.x, vA.y, vA.z );
array.push( vB.x, vB.y, vB.z );
array.push( vC.x, vC.y, vC.z );
}
}
return new THREE.Float32BufferAttribute( array, itemSize );
}
function computeAttributeFromFaceData( index, faceData ) {
var array = [];
for ( var i = 0, j = 0, l = index.length; i < l; i += 3, j ++ ) {
vA.fromArray( faceData, j * 3 );
array.push( vA.x, vA.y, vA.z );
array.push( vA.x, vA.y, vA.z );
array.push( vA.x, vA.y, vA.z );
}
return new THREE.Float32BufferAttribute( array, 3 );
}
function computeAttributeFromLineData( index, lineData ) {
var array = [];
for ( var i = 0, j = 0, l = index.length; i < l; i += 2, j ++ ) {
vA.fromArray( lineData, j * 3 );
array.push( vA.x, vA.y, vA.z );
array.push( vA.x, vA.y, vA.z );
}
return new THREE.Float32BufferAttribute( array, 3 );
}
function toNonIndexedAttribute( indices, attribute ) {
var array = attribute.array;
var itemSize = attribute.itemSize;
var array2 = new array.constructor( indices.length * itemSize );
var index = 0, index2 = 0;
for ( var i = 0, l = indices.length; i < l; i ++ ) {
index = indices[ i ] * itemSize;
for ( var j = 0; j < itemSize; j ++ ) {
array2[ index2 ++ ] = array[ index ++ ];
}
}
return new THREE.Float32BufferAttribute( array2, itemSize );
}
var ab = new THREE.Vector3();
var cb = new THREE.Vector3();
function computeNormalAttribute( index, coord, creaseAngle ) {
var faces = [];
var vertexNormals = {};
// prepare face and raw vertex normals
for ( var i = 0, l = index.length; i < l; i += 3 ) {
var a = index[ i ];
var b = index[ i + 1 ];
var c = index[ i + 2 ];
var face = new Face( a, b, c );
vA.fromArray( coord, a * 3 );
vB.fromArray( coord, b * 3 );
vC.fromArray( coord, c * 3 );
cb.subVectors( vC, vB );
ab.subVectors( vA, vB );
cb.cross( ab );
cb.normalize();
face.normal.copy( cb );
if ( vertexNormals[ a ] === undefined ) vertexNormals[ a ] = [];
if ( vertexNormals[ b ] === undefined ) vertexNormals[ b ] = [];
if ( vertexNormals[ c ] === undefined ) vertexNormals[ c ] = [];
vertexNormals[ a ].push( face.normal );
vertexNormals[ b ].push( face.normal );
vertexNormals[ c ].push( face.normal );
faces.push( face );
}
// compute vertex normals and build final geometry
var normals = [];
for ( var i = 0, l = faces.length; i < l; i ++ ) {
var face = faces[ i ];
var nA = weightedNormal( vertexNormals[ face.a ], face.normal, creaseAngle );
var nB = weightedNormal( vertexNormals[ face.b ], face.normal, creaseAngle );
var nC = weightedNormal( vertexNormals[ face.c ], face.normal, creaseAngle );
vA.fromArray( coord, face.a * 3 );
vB.fromArray( coord, face.b * 3 );
vC.fromArray( coord, face.c * 3 );
normals.push( nA.x, nA.y, nA.z );
normals.push( nB.x, nB.y, nB.z );
normals.push( nC.x, nC.y, nC.z );
}
return new THREE.Float32BufferAttribute( normals, 3 );
}
function weightedNormal( normals, vector, creaseAngle ) {
var normal = vector.clone();
for ( var i = 0, l = normals.length; i < l; i ++ ) {
if ( normals[ i ].angleTo( vector ) < creaseAngle ) {
normal.add( normals[ i ] );
}
}
return normal.normalize();
}
function toColorArray( colors ) {
var array = [];
for ( var i = 0, l = colors.length; i < l; i += 3 ) {
array.push( new THREE.Color( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ) );
}
return array;
}
/**
* Vertically paints the faces interpolating between the
* specified colors at the specified angels. This is used for the Background
* node, but could be applied to other nodes with multiple faces as well.
*
* When used with the Background node, default is directionIsDown is true if
* interpolating the skyColor down from the Zenith. When interpolationg up from
* the Nadir i.e. interpolating the groundColor, the directionIsDown is false.
*
* The first angle is never specified, it is the Zenith (0 rad). Angles are specified
* in radians. The geometry is thought a sphere, but could be anything. The color interpolation
* is linear along the Y axis in any case.
*
* You must specify one more color than you have angles at the beginning of the colors array.
* This is the color of the Zenith (the top of the shape).
*
* @param {BufferGeometry} geometry
* @param {number} radius
* @param {array} angles
* @param {array} colors
* @param {boolean} topDown - Whether to work top down or bottom up.
*/
function paintFaces( geometry, radius, angles, colors, topDown ) {
var direction = ( topDown === true ) ? 1 : - 1;
var coord = [], A = {}, B = {}, applyColor = false;
for ( var k = 0; k < angles.length; k ++ ) {
// push the vector at which the color changes
var vec = {
x: direction * ( Math.cos( angles[ k ] ) * radius ),
y: direction * ( Math.sin( angles[ k ] ) * radius )
};
coord.push( vec );
}
var index = geometry.index;
var positionAttribute = geometry.attributes.position;
var colorAttribute = new THREE.BufferAttribute( new Float32Array( geometry.attributes.position.count * 3 ), 3 );
var position = new THREE.Vector3();
var color = new THREE.Color();
for ( var i = 0; i < index.count; i ++ ) {
var vertexIndex = index.getX( i );
position.fromBufferAttribute( positionAttribute, vertexIndex );
for ( var j = 0; j < colors.length; j ++ ) {
// linear interpolation between aColor and bColor, calculate proportion
// A is previous point (angle)
if ( j === 0 ) {
A.x = 0;
A.y = ( topDown === true ) ? radius : - 1 * radius;
} else {
A.x = coord[ j - 1 ].x;
A.y = coord[ j - 1 ].y;
}
// B is current point (angle)
B = coord[ j ];
if ( B !== undefined ) {
// p has to be between the points A and B which we interpolate
applyColor = ( topDown === true ) ? ( position.y <= A.y && position.y > B.y ) : ( position.y >= A.y && position.y < B.y );
if ( applyColor === true ) {
var aColor = colors[ j ];
var bColor = colors[ j + 1 ];
// below is simple linear interpolation
var t = Math.abs( position.y - A.y ) / ( A.y - B.y );
// to make it faster, you can only calculate this if the y coord changes, the color is the same for points with the same y
color.copy( aColor ).lerp( bColor, t );
colorAttribute.setXYZ( vertexIndex, color.r, color.g, color.b );
} else {
var colorIndex = ( topDown === true ) ? colors.length - 1 : 0;
var c = colors[ colorIndex ];
colorAttribute.setXYZ( vertexIndex, c.r, c.g, c.b );
}
}
}
}
geometry.addAttribute( 'color', colorAttribute );
}
//
var textureLoader = new THREE.TextureLoader( this.manager );
textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
// create JSON representing the tree structure of the VRML asset
var tree = generateVRMLTree( data );
// check version (only 2.0 is supported)
if ( tree.version.indexOf( 'V2.0' ) === - 1 ) {
throw Error( 'THREE.VRMLLexer: Version of VRML asset not supported.' );
}
// parse the tree structure to a three.js scene
var scene = parseTree( tree );
return scene;
}
};
function VRMLLexer( tokens ) {
this.lexer = new chevrotain.Lexer( tokens );
}
VRMLLexer.prototype = {
constructor: VRMLLexer,
lex: function ( inputText ) {
var lexingResult = this.lexer.tokenize( inputText );
if ( lexingResult.errors.length > 0 ) {
console.error( lexingResult.errors );
throw Error( 'THREE.VRMLLexer: Lexing errors detected.' );
}
return lexingResult;
}
};
function VRMLParser( tokenVocabulary ) {
chevrotain.Parser.call( this, tokenVocabulary );
var $ = this;
var Version = tokenVocabulary[ 'Version' ];
var LCurly = tokenVocabulary[ 'LCurly' ];
var RCurly = tokenVocabulary[ 'RCurly' ];
var LSquare = tokenVocabulary[ 'LSquare' ];
var RSquare = tokenVocabulary[ 'RSquare' ];
var Identifier = tokenVocabulary[ 'Identifier' ];
var RouteIdentifier = tokenVocabulary[ 'RouteIdentifier' ];
var StringLiteral = tokenVocabulary[ 'StringLiteral' ];
var NumberLiteral = tokenVocabulary[ 'NumberLiteral' ];
var TrueLiteral = tokenVocabulary[ 'TrueLiteral' ];
var FalseLiteral = tokenVocabulary[ 'FalseLiteral' ];
var NullLiteral = tokenVocabulary[ 'NullLiteral' ];
var DEF = tokenVocabulary[ 'DEF' ];
var USE = tokenVocabulary[ 'USE' ];
var ROUTE = tokenVocabulary[ 'ROUTE' ];
var TO = tokenVocabulary[ 'TO' ];
var NodeName = tokenVocabulary[ 'NodeName' ];
$.RULE( 'vrml', function () {
$.SUBRULE( $.version );
$.AT_LEAST_ONE( function () {
$.SUBRULE( $.node );
} );
$.MANY( function () {
$.SUBRULE( $.route );
} );
} );
$.RULE( 'version', function () {
$.CONSUME( Version );
} );
$.RULE( 'node', function () {
$.OPTION( function () {
$.SUBRULE( $.def );
} );
$.CONSUME( NodeName );
$.CONSUME( LCurly );
$.MANY( function () {
$.SUBRULE( $.field );
} );
$.CONSUME( RCurly );
} );
$.RULE( 'field', function () {
$.CONSUME( Identifier );
$.OR2( [
{ ALT: function () {
$.SUBRULE( $.singleFieldValue );
} },
{ ALT: function () {
$.SUBRULE( $.multiFieldValue );
} }
] );
} );
$.RULE( 'def', function () {
$.CONSUME( DEF );
$.CONSUME( Identifier );
} );
$.RULE( 'use', function () {
$.CONSUME( USE );
$.CONSUME( Identifier );
} );
$.RULE( 'singleFieldValue', function () {
$.AT_LEAST_ONE( function () {
$.OR( [
{ ALT: function () {
$.SUBRULE( $.node );
} },
{ ALT: function () {
$.SUBRULE( $.use );
} },
{ ALT: function () {
$.CONSUME( StringLiteral );
} },
{ ALT: function () {
$.CONSUME( NumberLiteral );
} },
{ ALT: function () {
$.CONSUME( TrueLiteral );
} },
{ ALT: function () {
$.CONSUME( FalseLiteral );
} },
{ ALT: function () {
$.CONSUME( NullLiteral );
} }
] );
} );
} );
$.RULE( 'multiFieldValue', function () {
$.CONSUME( LSquare );
$.MANY( function () {
$.OR( [
{ ALT: function () {
$.SUBRULE( $.node );
} },
{ ALT: function () {
$.SUBRULE( $.use );
} },
{ ALT: function () {
$.CONSUME( StringLiteral );
} },
{ ALT: function () {
$.CONSUME( NumberLiteral );
} },
{ ALT: function () {
$.CONSUME( NullLiteral );
} }
] );
} );
$.CONSUME( RSquare );
} );
$.RULE( 'route', function () {
$.CONSUME( ROUTE );
$.CONSUME( RouteIdentifier );
$.CONSUME( TO );
$.CONSUME2( RouteIdentifier );
} );
this.performSelfAnalysis();
}
VRMLParser.prototype = Object.create( chevrotain.Parser.prototype );
VRMLParser.prototype.constructor = VRMLParser;
function Face( a, b, c ) {
this.a = a;
this.b = b;
this.c = c;
this.normal = new THREE.Vector3();
}
return VRMLLoader;
} )();