diff --git a/libs/three_loaders/VRMLLoader.js b/libs/three_loaders/VRMLLoader.js new file mode 100644 index 0000000..f40a978 --- /dev/null +++ b/libs/three_loaders/VRMLLoader.js @@ -0,0 +1,3239 @@ +( function () { + + class VRMLLoader extends THREE.Loader { + + constructor( manager ) { + + super( manager ); // dependency check + + if ( typeof chevrotain === 'undefined' ) { + + // eslint-disable-line no-undef + throw Error( 'THREE.VRMLLoader: External library chevrotain.min.js required.' ); + + } + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + const path = scope.path === '' ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path; + const loader = new THREE.FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data, path ) { + + const nodeMap = {}; + + function generateVRMLTree( data ) { + + // create lexer, parser and visitor + const tokenData = createTokens(); + const lexer = new VRMLLexer( tokenData.tokens ); + const parser = new VRMLParser( tokenData.tokenVocabulary ); + const visitor = createVisitor( parser.getBaseCstVisitorConstructor() ); // lexing + + const lexingResult = lexer.lex( data ); + parser.input = lexingResult.tokens; // parsing + + const cstOutput = parser.vrml(); + + if ( parser.errors.length > 0 ) { + + console.error( parser.errors ); + throw Error( 'THREE.VRMLLoader: Parsing errors detected.' ); + + } // actions + + + const ast = visitor.visit( cstOutput ); + return ast; + + } + + function createTokens() { + + const createToken = chevrotain.createToken; // eslint-disable-line no-undef + // from http://gun.teipir.gr/VRML-amgem/spec/part1/concepts.html#SyntaxBasics + + const 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]*/ + } ); + const 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 + + const 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 + ]; // + + const Version = createToken( { + name: 'Version', + pattern: /#VRML.*/, + longer_alt: Identifier + } ); + const NodeName = createToken( { + name: 'NodeName', + pattern: new RegExp( nodeTypes.join( '|' ) ), + longer_alt: Identifier + } ); + const DEF = createToken( { + name: 'DEF', + pattern: /DEF/, + longer_alt: Identifier + } ); + const USE = createToken( { + name: 'USE', + pattern: /USE/, + longer_alt: Identifier + } ); + const ROUTE = createToken( { + name: 'ROUTE', + pattern: /ROUTE/, + longer_alt: Identifier + } ); + const TO = createToken( { + name: 'TO', + pattern: /TO/, + longer_alt: Identifier + } ); // + + const StringLiteral = createToken( { + name: 'StringLiteral', + pattern: /"(:?[^\\"\n\r]+|\\(:?[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*"/ + } ); + const HexLiteral = createToken( { + name: 'HexLiteral', + pattern: /0[xX][0-9a-fA-F]+/ + } ); + const NumberLiteral = createToken( { + name: 'NumberLiteral', + pattern: /[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/ + } ); + const TrueLiteral = createToken( { + name: 'TrueLiteral', + pattern: /TRUE/ + } ); + const FalseLiteral = createToken( { + name: 'FalseLiteral', + pattern: /FALSE/ + } ); + const NullLiteral = createToken( { + name: 'NullLiteral', + pattern: /NULL/ + } ); + const LSquare = createToken( { + name: 'LSquare', + pattern: /\[/ + } ); + const RSquare = createToken( { + name: 'RSquare', + pattern: /]/ + } ); + const LCurly = createToken( { + name: 'LCurly', + pattern: /{/ + } ); + const RCurly = createToken( { + name: 'RCurly', + pattern: /}/ + } ); + const Comment = createToken( { + name: 'Comment', + pattern: /#.*/, + group: chevrotain.Lexer.SKIPPED // eslint-disable-line no-undef + + } ); // commas, blanks, tabs, newlines and carriage returns are whitespace characters wherever they appear outside of string fields + + const WhiteSpace = createToken( { + name: 'WhiteSpace', + pattern: /[ ,\s]/, + group: chevrotain.Lexer.SKIPPED // eslint-disable-line no-undef + + } ); + const 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, HexLiteral, NumberLiteral, LSquare, RSquare, LCurly, RCurly, Comment ]; + const tokenVocabulary = {}; + + for ( let i = 0, l = tokens.length; i < l; i ++ ) { + + const 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 ) { + + const data = { + version: this.visit( ctx.version ), + nodes: [], + routes: [] + }; + + for ( let i = 0, l = ctx.node.length; i < l; i ++ ) { + + const node = ctx.node[ i ]; + data.nodes.push( this.visit( node ) ); + + } + + if ( ctx.route ) { + + for ( let i = 0, l = ctx.route.length; i < l; i ++ ) { + + const route = ctx.route[ i ]; + data.routes.push( this.visit( route ) ); + + } + + } + + return data; + + }, + version: function ( ctx ) { + + return ctx.Version[ 0 ].image; + + }, + node: function ( ctx ) { + + const data = { + name: ctx.NodeName[ 0 ].image, + fields: [] + }; + + if ( ctx.field ) { + + for ( let i = 0, l = ctx.field.length; i < l; i ++ ) { + + const 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 ) { + + const data = { + name: ctx.Identifier[ 0 ].image, + type: null, + values: null + }; + let 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 || ctx.NodeName )[ 0 ].image; + + }, + use: function ( ctx ) { + + return { + USE: ( ctx.Identifier || ctx.NodeName )[ 0 ].image + }; + + }, + singleFieldValue: function ( ctx ) { + + return processField( this, ctx ); + + }, + multiFieldValue: function ( ctx ) { + + return processField( this, ctx ); + + }, + route: function ( ctx ) { + + const data = { + FROM: ctx.RouteIdentifier[ 0 ].image, + TO: ctx.RouteIdentifier[ 1 ].image + }; + return data; + + } + } ); + + function processField( scope, ctx ) { + + const field = { + type: null, + values: [] + }; + + if ( ctx.node ) { + + field.type = 'node'; + + for ( let i = 0, l = ctx.node.length; i < l; i ++ ) { + + const node = ctx.node[ i ]; + field.values.push( scope.visit( node ) ); + + } + + } + + if ( ctx.use ) { + + field.type = 'use'; + + for ( let i = 0, l = ctx.use.length; i < l; i ++ ) { + + const use = ctx.use[ i ]; + field.values.push( scope.visit( use ) ); + + } + + } + + if ( ctx.StringLiteral ) { + + field.type = 'string'; + + for ( let i = 0, l = ctx.StringLiteral.length; i < l; i ++ ) { + + const stringLiteral = ctx.StringLiteral[ i ]; + field.values.push( stringLiteral.image.replace( /'|"/g, '' ) ); + + } + + } + + if ( ctx.NumberLiteral ) { + + field.type = 'number'; + + for ( let i = 0, l = ctx.NumberLiteral.length; i < l; i ++ ) { + + const numberLiteral = ctx.NumberLiteral[ i ]; + field.values.push( parseFloat( numberLiteral.image ) ); + + } + + } + + if ( ctx.HexLiteral ) { + + field.type = 'hex'; + + for ( let i = 0, l = ctx.HexLiteral.length; i < l; i ++ ) { + + const hexLiteral = ctx.HexLiteral[ i ]; + field.values.push( hexLiteral.image ); + + } + + } + + if ( ctx.TrueLiteral ) { + + field.type = 'boolean'; + + for ( let i = 0, l = ctx.TrueLiteral.length; i < l; i ++ ) { + + const trueLiteral = ctx.TrueLiteral[ i ]; + if ( trueLiteral.image === 'TRUE' ) field.values.push( true ); + + } + + } + + if ( ctx.FalseLiteral ) { + + field.type = 'boolean'; + + for ( let i = 0, l = ctx.FalseLiteral.length; i < l; i ++ ) { + + const 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 ) ); + const nodes = tree.nodes; + const scene = new THREE.Scene(); // first iteration: build nodemap based on DEF statements + + for ( let i = 0, l = nodes.length; i < l; i ++ ) { + + const node = nodes[ i ]; + buildNodeMap( node ); + + } // second iteration: build nodes + + + for ( let i = 0, l = nodes.length; i < l; i ++ ) { + + const node = nodes[ i ]; + const object = getNode( node ); + if ( object instanceof THREE.Object3D ) scene.add( object ); + if ( node.name === 'WorldInfo' ) scene.userData.worldInfo = object; + + } + + return scene; + + } + + function buildNodeMap( node ) { + + if ( node.DEF ) { + + nodeMap[ node.DEF ] = node; + + } + + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + + if ( field.type === 'node' ) { + + const fieldValues = field.values; + + for ( let 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 ) { + + const nodeName = node.name; + let build; + + switch ( nodeName ) { + + case 'Group': + case 'Transform': + case 'Collision': + build = buildGroupingNode( node ); + break; + + case 'Background': + build = buildBackgroundNode( node ); + break; + + case 'Shape': + build = buildShapeNode( node ); + break; + + case 'Appearance': + build = buildAppearanceNode( node ); + break; + + case 'Material': + build = buildMaterialNode( node ); + break; + + case 'ImageTexture': + build = buildImageTextureNode( node ); + break; + + case 'PixelTexture': + build = buildPixelTextureNode( 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 'ElevationGrid': + build = buildElevationGridNode( node ); + break; + + case 'Extrusion': + build = buildExtrusionNode( node ); + break; + + case 'Color': + case 'Coordinate': + case 'Normal': + case 'TextureCoordinate': + build = buildGeometricNode( node ); + break; + + case 'WorldInfo': + build = buildWorldInfoNode( node ); + break; + + case 'Anchor': + case 'Billboard': + case 'Inline': + case 'LOD': + case 'Switch': + case 'AudioClip': + case 'DirectionalLight': + case 'PointLight': + case 'Script': + case 'Sound': + case 'SpotLight': + case 'CylinderSensor': + case 'PlaneSensor': + case 'ProximitySensor': + case 'SphereSensor': + case 'TimeSensor': + case 'TouchSensor': + case 'VisibilitySensor': + case 'Text': + case 'FontStyle': + case 'MovieTexture': + 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; + + } + + if ( build !== undefined && node.DEF !== undefined && build.hasOwnProperty( 'name' ) === true ) { + + build.name = node.DEF; + + } + + return build; + + } + + function buildGroupingNode( node ) { + + const object = new THREE.Group(); // + + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'bboxCenter': + // field not supported + break; + + case 'bboxSize': + // field not supported + break; + + case 'center': + // field not supported + break; + + case 'children': + parseFieldChildren( fieldValues, object ); + break; + + case 'collide': + // field not supported + break; + + case 'rotation': + const axis = new THREE.Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ); + const 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 'proxy': + // field not supported + break; + + default: + console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName ); + break; + + } + + } + + return object; + + } + + function buildBackgroundNode( node ) { + + const group = new THREE.Group(); + let groundAngle, groundColor; + let skyAngle, skyColor; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const 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; + + } + + } + + const radius = 10000; // sky + + if ( skyColor ) { + + const skyGeometry = new THREE.SphereGeometry( radius, 32, 16 ); + const 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 = true; + + } else { + + skyMaterial.color.setRGB( skyColor[ 0 ], skyColor[ 1 ], skyColor[ 2 ] ); + + } + + const sky = new THREE.Mesh( skyGeometry, skyMaterial ); + group.add( sky ); + + } // ground + + + if ( groundColor ) { + + if ( groundColor.length > 0 ) { + + const groundGeometry = new THREE.SphereGeometry( radius, 32, 16, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI ); + const groundMaterial = new THREE.MeshBasicMaterial( { + fog: false, + side: THREE.BackSide, + vertexColors: true, + depthWrite: false, + depthTest: false + } ); + paintFaces( groundGeometry, radius, groundAngle, toColorArray( groundColor ), false ); + const ground = new THREE.Mesh( groundGeometry, groundMaterial ); + group.add( ground ); + + } + + } // render background group first + + + group.renderOrder = - Infinity; + return group; + + } + + function buildShapeNode( node ) { + + const fields = node.fields; // if the appearance field is NULL or unspecified, lighting is off and the unlit object color is (0, 0, 0) + + let material = new THREE.MeshBasicMaterial( { + color: 0x000000 + } ); + let geometry; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const 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 + + + let object; + + if ( geometry && geometry.attributes.position ) { + + const type = geometry._type; + + if ( type === 'points' ) { + + // points + const pointsMaterial = new THREE.PointsMaterial( { + color: 0xffffff + } ); + + if ( geometry.attributes.color !== undefined ) { + + pointsMaterial.vertexColors = true; + + } 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 + const lineMaterial = new THREE.LineBasicMaterial( { + color: 0xffffff + } ); + + if ( geometry.attributes.color !== undefined ) { + + lineMaterial.vertexColors = true; + + } 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 = true; + + } + + 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 buildAppearanceNode( node ) { + + let material = new THREE.MeshPhongMaterial(); + let transformData; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'material': + if ( fieldValues[ 0 ] !== null ) { + + const 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': + const textureNode = fieldValues[ 0 ]; + + if ( textureNode !== null ) { + + if ( textureNode.name === 'ImageTexture' || textureNode.name === 'PixelTexture' ) { + + material.map = getNode( textureNode ); + + } else { // MovieTexture 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 ) { + + // respect VRML lighting model + if ( material.map.__type ) { + + switch ( material.map.__type ) { + + case TEXTURE_TYPE.INTENSITY_ALPHA: + material.opacity = 1; // ignore transparency + + break; + + case TEXTURE_TYPE.RGB: + material.color.set( 0xffffff ); // ignore material color + + break; + + case TEXTURE_TYPE.RGBA: + material.color.set( 0xffffff ); // ignore material color + + material.opacity = 1; // ignore transparency + + break; + + default: + + } + + delete material.map.__type; + + } // apply texture transform + + + if ( 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 ) { + + const materialData = {}; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const 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 parseHexColor( hex, textureType, color ) { + + let value; + + switch ( textureType ) { + + case TEXTURE_TYPE.INTENSITY: + // Intensity texture: A one-component image specifies one-byte hexadecimal or integer values representing the intensity of the image + value = parseInt( hex ); + color.r = value; + color.g = value; + color.b = value; + break; + + case TEXTURE_TYPE.INTENSITY_ALPHA: + // Intensity+Alpha texture: A two-component image specifies the intensity in the first (high) byte and the alpha opacity in the second (low) byte. + value = parseInt( '0x' + hex.substring( 2, 4 ) ); + color.r = value; + color.g = value; + color.b = value; + color.a = parseInt( '0x' + hex.substring( 4, 6 ) ); + break; + + case TEXTURE_TYPE.RGB: + // RGB texture: Pixels in a three-component image specify the red component in the first (high) byte, followed by the green and blue components + color.r = parseInt( '0x' + hex.substring( 2, 4 ) ); + color.g = parseInt( '0x' + hex.substring( 4, 6 ) ); + color.b = parseInt( '0x' + hex.substring( 6, 8 ) ); + break; + + case TEXTURE_TYPE.RGBA: + // RGBA texture: Four-component images specify the alpha opacity byte after red/green/blue + color.r = parseInt( '0x' + hex.substring( 2, 4 ) ); + color.g = parseInt( '0x' + hex.substring( 4, 6 ) ); + color.b = parseInt( '0x' + hex.substring( 6, 8 ) ); + color.a = parseInt( '0x' + hex.substring( 8, 10 ) ); + break; + + default: + + } + + } + + function getTextureType( num_components ) { + + let type; + + switch ( num_components ) { + + case 1: + type = TEXTURE_TYPE.INTENSITY; + break; + + case 2: + type = TEXTURE_TYPE.INTENSITY_ALPHA; + break; + + case 3: + type = TEXTURE_TYPE.RGB; + break; + + case 4: + type = TEXTURE_TYPE.RGBA; + break; + + default: + + } + + return type; + + } + + function buildPixelTextureNode( node ) { + + let texture; + let wrapS = THREE.RepeatWrapping; + let wrapT = THREE.RepeatWrapping; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'image': + const width = fieldValues[ 0 ]; + const height = fieldValues[ 1 ]; + const num_components = fieldValues[ 2 ]; + const useAlpha = num_components === 2 || num_components === 4; + const textureType = getTextureType( num_components ); + const size = ( useAlpha === true ? 4 : 3 ) * ( width * height ); + const data = new Uint8Array( size ); + const color = { + r: 0, + g: 0, + b: 0, + a: 0 + }; + + for ( let j = 3, k = 0, jl = fieldValues.length; j < jl; j ++, k ++ ) { + + parseHexColor( fieldValues[ j ], textureType, color ); + + if ( useAlpha === true ) { + + const stride = k * 4; + data[ stride + 0 ] = color.r; + data[ stride + 1 ] = color.g; + data[ stride + 2 ] = color.b; + data[ stride + 3 ] = color.a; + + } else { + + const stride = k * 3; + data[ stride + 0 ] = color.r; + data[ stride + 1 ] = color.g; + data[ stride + 2 ] = color.b; + + } + + } + + texture = new THREE.DataTexture( data, width, height, useAlpha === true ? THREE.RGBAFormat : THREE.RGBFormat ); + texture.__type = textureType; // needed for material modifications + + 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 buildImageTextureNode( node ) { + + let texture; + let wrapS = THREE.RepeatWrapping; + let wrapT = THREE.RepeatWrapping; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'url': + const 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 ) { + + const transformData = { + center: new THREE.Vector2(), + rotation: new THREE.Vector2(), + scale: new THREE.Vector2(), + translation: new THREE.Vector2() + }; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const 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 buildWorldInfoNode( node ) { + + const worldInfo = {}; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'title': + worldInfo.title = fieldValues[ 0 ]; + break; + + case 'info': + worldInfo.info = fieldValues; + break; + + default: + console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName ); + break; + + } + + } + + return worldInfo; + + } + + function buildIndexedFaceSetNode( node ) { + + let color, coord, normal, texCoord; + let ccw = true, + solid = true, + creaseAngle = 0; + let colorIndex, coordIndex, normalIndex, texCoordIndex; + let colorPerVertex = true, + normalPerVertex = true; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'color': + const colorNode = fieldValues[ 0 ]; + + if ( colorNode !== null ) { + + color = getNode( colorNode ); + + } + + break; + + case 'coord': + const coordNode = fieldValues[ 0 ]; + + if ( coordNode !== null ) { + + coord = getNode( coordNode ); + + } + + break; + + case 'normal': + const normalNode = fieldValues[ 0 ]; + + if ( normalNode !== null ) { + + normal = getNode( normalNode ); + + } + + break; + + case 'texCoord': + const 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 + + } + + const triangulatedCoordIndex = triangulateFaceIndex( coordIndex, ccw ); + let colorAttribute; + let normalAttribute; + let 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. + const 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 THREE.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 + const flattenFaceColors = flattenData( color, colorIndex ); + const 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 + const 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. + const 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 + const flattenFaceNormals = flattenData( normal, normalIndex ); + const 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 + const 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. + const 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 ) ); + + } + + } + + const geometry = new THREE.BufferGeometry(); + const positionAttribute = toNonIndexedAttribute( triangulatedCoordIndex, new THREE.Float32BufferAttribute( coord, 3 ) ); + geometry.setAttribute( 'position', positionAttribute ); + geometry.setAttribute( 'normal', normalAttribute ); // optional attributes + + if ( colorAttribute ) geometry.setAttribute( 'color', colorAttribute ); + if ( uvAttribute ) geometry.setAttribute( '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 ) { + + let color, coord; + let colorIndex, coordIndex; + let colorPerVertex = true; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'color': + const colorNode = fieldValues[ 0 ]; + + if ( colorNode !== null ) { + + color = getNode( colorNode ); + + } + + break; + + case 'coord': + const 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 + + + let colorAttribute; + const 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. + const 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 + const flattenLineColors = flattenData( color, colorIndex ); // compute colors for each VRML primitve + + const 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 THREE.Color node + const expandedLineColors = expandLineData( color, coordIndex ); // compute colors for each line segment (rendering primitve) + + colorAttribute = computeAttributeFromLineData( expandedLineIndex, expandedLineColors ); // compute data on vertex level + + } + + } + + } // + + + const geometry = new THREE.BufferGeometry(); + const positionAttribute = toNonIndexedAttribute( expandedLineIndex, new THREE.Float32BufferAttribute( coord, 3 ) ); + geometry.setAttribute( 'position', positionAttribute ); + if ( colorAttribute ) geometry.setAttribute( 'color', colorAttribute ); + geometry._type = 'line'; + return geometry; + + } + + function buildPointSetNode( node ) { + + let color, coord; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'color': + const colorNode = fieldValues[ 0 ]; + + if ( colorNode !== null ) { + + color = getNode( colorNode ); + + } + + break; + + case 'coord': + const coordNode = fieldValues[ 0 ]; + + if ( coordNode !== null ) { + + coord = getNode( coordNode ); + + } + + break; + + default: + console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName ); + break; + + } + + } + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( coord, 3 ) ); + if ( color ) geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( color, 3 ) ); + geometry._type = 'points'; + return geometry; + + } + + function buildBoxNode( node ) { + + const size = new THREE.Vector3( 2, 2, 2 ); + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const 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; + + } + + } + + const geometry = new THREE.BoxGeometry( size.x, size.y, size.z ); + return geometry; + + } + + function buildConeNode( node ) { + + let radius = 1, + height = 2, + openEnded = false; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const 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; + + } + + } + + const geometry = new THREE.ConeGeometry( radius, height, 16, 1, openEnded ); + return geometry; + + } + + function buildCylinderNode( node ) { + + let radius = 1, + height = 2; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const 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; + + } + + } + + const geometry = new THREE.CylinderGeometry( radius, radius, height, 16, 1 ); + return geometry; + + } + + function buildSphereNode( node ) { + + let radius = 1; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'radius': + radius = fieldValues[ 0 ]; + break; + + default: + console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName ); + break; + + } + + } + + const geometry = new THREE.SphereGeometry( radius, 16, 16 ); + return geometry; + + } + + function buildElevationGridNode( node ) { + + let color; + let normal; + let texCoord; + let height; + let colorPerVertex = true; + let normalPerVertex = true; + let solid = true; + let ccw = true; + let creaseAngle = 0; + let xDimension = 2; + let zDimension = 2; + let xSpacing = 1; + let zSpacing = 1; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'color': + const colorNode = fieldValues[ 0 ]; + + if ( colorNode !== null ) { + + color = getNode( colorNode ); + + } + + break; + + case 'normal': + const normalNode = fieldValues[ 0 ]; + + if ( normalNode !== null ) { + + normal = getNode( normalNode ); + + } + + break; + + case 'texCoord': + const texCoordNode = fieldValues[ 0 ]; + + if ( texCoordNode !== null ) { + + texCoord = getNode( texCoordNode ); + + } + + break; + + case 'height': + height = fieldValues; + break; + + case 'ccw': + ccw = fieldValues[ 0 ]; + break; + + case 'colorPerVertex': + colorPerVertex = fieldValues[ 0 ]; + break; + + case 'creaseAngle': + creaseAngle = fieldValues[ 0 ]; + break; + + case 'normalPerVertex': + normalPerVertex = fieldValues[ 0 ]; + break; + + case 'solid': + solid = fieldValues[ 0 ]; + break; + + case 'xDimension': + xDimension = fieldValues[ 0 ]; + break; + + case 'xSpacing': + xSpacing = fieldValues[ 0 ]; + break; + + case 'zDimension': + zDimension = fieldValues[ 0 ]; + break; + + case 'zSpacing': + zSpacing = fieldValues[ 0 ]; + break; + + default: + console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName ); + break; + + } + + } // vertex data + + + const vertices = []; + const normals = []; + const colors = []; + const uvs = []; + + for ( let i = 0; i < zDimension; i ++ ) { + + for ( let j = 0; j < xDimension; j ++ ) { + + // compute a row major index + const index = i * xDimension + j; // vertices + + const x = xSpacing * i; + const y = height[ index ]; + const z = zSpacing * j; + vertices.push( x, y, z ); // colors + + if ( color && colorPerVertex === true ) { + + const r = color[ index * 3 + 0 ]; + const g = color[ index * 3 + 1 ]; + const b = color[ index * 3 + 2 ]; + colors.push( r, g, b ); + + } // normals + + + if ( normal && normalPerVertex === true ) { + + const xn = normal[ index * 3 + 0 ]; + const yn = normal[ index * 3 + 1 ]; + const zn = normal[ index * 3 + 2 ]; + normals.push( xn, yn, zn ); + + } // uvs + + + if ( texCoord ) { + + const s = texCoord[ index * 2 + 0 ]; + const t = texCoord[ index * 2 + 1 ]; + uvs.push( s, t ); + + } else { + + uvs.push( i / ( xDimension - 1 ), j / ( zDimension - 1 ) ); + + } + + } + + } // indices + + + const indices = []; + + for ( let i = 0; i < xDimension - 1; i ++ ) { + + for ( let j = 0; j < zDimension - 1; j ++ ) { + + // from https://tecfa.unige.ch/guides/vrml/vrml97/spec/part1/nodesRef.html#ElevationGrid + const a = i + j * xDimension; + const b = i + ( j + 1 ) * xDimension; + const c = i + 1 + ( j + 1 ) * xDimension; + const d = i + 1 + j * xDimension; // faces + + if ( ccw === true ) { + + indices.push( a, c, b ); + indices.push( c, a, d ); + + } else { + + indices.push( a, b, c ); + indices.push( c, d, a ); + + } + + } + + } // + + + const positionAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( vertices, 3 ) ); + const uvAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( uvs, 2 ) ); + let colorAttribute; + let normalAttribute; // color attribute + + if ( color ) { + + if ( colorPerVertex === false ) { + + for ( let i = 0; i < xDimension - 1; i ++ ) { + + for ( let j = 0; j < zDimension - 1; j ++ ) { + + const index = i + j * ( xDimension - 1 ); + const r = color[ index * 3 + 0 ]; + const g = color[ index * 3 + 1 ]; + const b = color[ index * 3 + 2 ]; // one color per quad + + colors.push( r, g, b ); + colors.push( r, g, b ); + colors.push( r, g, b ); + colors.push( r, g, b ); + colors.push( r, g, b ); + colors.push( r, g, b ); + + } + + } + + colorAttribute = new THREE.Float32BufferAttribute( colors, 3 ); + + } else { + + colorAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( colors, 3 ) ); + + } + + } // normal attribute + + + if ( normal ) { + + if ( normalPerVertex === false ) { + + for ( let i = 0; i < xDimension - 1; i ++ ) { + + for ( let j = 0; j < zDimension - 1; j ++ ) { + + const index = i + j * ( xDimension - 1 ); + const xn = normal[ index * 3 + 0 ]; + const yn = normal[ index * 3 + 1 ]; + const zn = normal[ index * 3 + 2 ]; // one normal per quad + + normals.push( xn, yn, zn ); + normals.push( xn, yn, zn ); + normals.push( xn, yn, zn ); + normals.push( xn, yn, zn ); + normals.push( xn, yn, zn ); + normals.push( xn, yn, zn ); + + } + + } + + normalAttribute = new THREE.Float32BufferAttribute( normals, 3 ); + + } else { + + normalAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( normals, 3 ) ); + + } + + } else { + + normalAttribute = computeNormalAttribute( indices, vertices, creaseAngle ); + + } // build geometry + + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( 'position', positionAttribute ); + geometry.setAttribute( 'normal', normalAttribute ); + geometry.setAttribute( 'uv', uvAttribute ); + if ( colorAttribute ) geometry.setAttribute( 'color', colorAttribute ); // "solid" influences the material so let's store it for later use + + geometry._solid = solid; + geometry._type = 'mesh'; + return geometry; + + } + + function buildExtrusionNode( node ) { + + let crossSection = [ 1, 1, 1, - 1, - 1, - 1, - 1, 1, 1, 1 ]; + let spine = [ 0, 0, 0, 0, 1, 0 ]; + let scale; + let orientation; + let beginCap = true; + let ccw = true; + let creaseAngle = 0; + let endCap = true; + let solid = true; + const fields = node.fields; + + for ( let i = 0, l = fields.length; i < l; i ++ ) { + + const field = fields[ i ]; + const fieldName = field.name; + const fieldValues = field.values; + + switch ( fieldName ) { + + case 'beginCap': + beginCap = fieldValues[ 0 ]; + break; + + case 'ccw': + ccw = fieldValues[ 0 ]; + break; + + case 'convex': + // field not supported + break; + + case 'creaseAngle': + creaseAngle = fieldValues[ 0 ]; + break; + + case 'crossSection': + crossSection = fieldValues; + break; + + case 'endCap': + endCap = fieldValues[ 0 ]; + break; + + case 'orientation': + orientation = fieldValues; + break; + + case 'scale': + scale = fieldValues; + break; + + case 'solid': + solid = fieldValues[ 0 ]; + break; + + case 'spine': + spine = fieldValues; // only extrusion along the Y-axis are supported so far + + break; + + default: + console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName ); + break; + + } + + } + + const crossSectionClosed = crossSection[ 0 ] === crossSection[ crossSection.length - 2 ] && crossSection[ 1 ] === crossSection[ crossSection.length - 1 ]; // vertices + + const vertices = []; + const spineVector = new THREE.Vector3(); + const scaling = new THREE.Vector3(); + const axis = new THREE.Vector3(); + const vertex = new THREE.Vector3(); + const quaternion = new THREE.Quaternion(); + + for ( let i = 0, j = 0, o = 0, il = spine.length; i < il; i += 3, j += 2, o += 4 ) { + + spineVector.fromArray( spine, i ); + scaling.x = scale ? scale[ j + 0 ] : 1; + scaling.y = 1; + scaling.z = scale ? scale[ j + 1 ] : 1; + axis.x = orientation ? orientation[ o + 0 ] : 0; + axis.y = orientation ? orientation[ o + 1 ] : 0; + axis.z = orientation ? orientation[ o + 2 ] : 1; + const angle = orientation ? orientation[ o + 3 ] : 0; + + for ( let k = 0, kl = crossSection.length; k < kl; k += 2 ) { + + vertex.x = crossSection[ k + 0 ]; + vertex.y = 0; + vertex.z = crossSection[ k + 1 ]; // scale + + vertex.multiply( scaling ); // rotate + + quaternion.setFromAxisAngle( axis, angle ); + vertex.applyQuaternion( quaternion ); // translate + + vertex.add( spineVector ); + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + } // indices + + + const indices = []; + const spineCount = spine.length / 3; + const crossSectionCount = crossSection.length / 2; + + for ( let i = 0; i < spineCount - 1; i ++ ) { + + for ( let j = 0; j < crossSectionCount - 1; j ++ ) { + + const a = j + i * crossSectionCount; + let b = j + 1 + i * crossSectionCount; + const c = j + ( i + 1 ) * crossSectionCount; + let d = j + 1 + ( i + 1 ) * crossSectionCount; + + if ( j === crossSectionCount - 2 && crossSectionClosed === true ) { + + b = i * crossSectionCount; + d = ( i + 1 ) * crossSectionCount; + + } + + if ( ccw === true ) { + + indices.push( a, b, c ); + indices.push( c, b, d ); + + } else { + + indices.push( a, c, b ); + indices.push( c, d, b ); + + } + + } + + } // triangulate cap + + + if ( beginCap === true || endCap === true ) { + + const contour = []; + + for ( let i = 0, l = crossSection.length; i < l; i += 2 ) { + + contour.push( new THREE.Vector2( crossSection[ i ], crossSection[ i + 1 ] ) ); + + } + + const faces = THREE.ShapeUtils.triangulateShape( contour, [] ); + const capIndices = []; + + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const face = faces[ i ]; + capIndices.push( face[ 0 ], face[ 1 ], face[ 2 ] ); + + } // begin cap + + + if ( beginCap === true ) { + + for ( let i = 0, l = capIndices.length; i < l; i += 3 ) { + + if ( ccw === true ) { + + indices.push( capIndices[ i + 0 ], capIndices[ i + 1 ], capIndices[ i + 2 ] ); + + } else { + + indices.push( capIndices[ i + 0 ], capIndices[ i + 2 ], capIndices[ i + 1 ] ); + + } + + } + + } // end cap + + + if ( endCap === true ) { + + const indexOffset = crossSectionCount * ( spineCount - 1 ); // references to the first vertex of the last cross section + + for ( let i = 0, l = capIndices.length; i < l; i += 3 ) { + + if ( ccw === true ) { + + indices.push( indexOffset + capIndices[ i + 0 ], indexOffset + capIndices[ i + 2 ], indexOffset + capIndices[ i + 1 ] ); + + } else { + + indices.push( indexOffset + capIndices[ i + 0 ], indexOffset + capIndices[ i + 1 ], indexOffset + capIndices[ i + 2 ] ); + + } + + } + + } + + } + + const positionAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( vertices, 3 ) ); + const normalAttribute = computeNormalAttribute( indices, vertices, creaseAngle ); + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( 'position', positionAttribute ); + geometry.setAttribute( 'normal', normalAttribute ); // no uvs yet + // "solid" influences the material so let's store it for later use + + geometry._solid = solid; + geometry._type = 'mesh'; + return geometry; + + } // helper functions + + + function resolveUSE( identifier ) { + + const node = nodeMap[ identifier ]; + const 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 ( let i = 0, l = children.length; i < l; i ++ ) { + + const object = getNode( children[ i ] ); + if ( object instanceof THREE.Object3D ) owner.add( object ); + + } + + } + + function triangulateFaceIndex( index, ccw ) { + + const indices = []; // since face defintions can have more than three vertices, it's necessary to + // perform a simple triangulation + + let start = 0; + + for ( let i = 0, l = index.length; i < l; i ++ ) { + + const i1 = index[ start ]; + const i2 = index[ i + ( ccw ? 1 : 2 ) ]; + const 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 ) { + + const triangulatedData = []; + let start = 0; + + for ( let i = 0, l = index.length; i < l; i ++ ) { + + const stride = start * 3; + const x = data[ stride ]; + const y = data[ stride + 1 ]; + const 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 ) { + + const flattenData = []; + + for ( let i = 0, l = index.length; i < l; i ++ ) { + + const i1 = index[ i ]; + const stride = i1 * 3; + const x = data[ stride ]; + const y = data[ stride + 1 ]; + const z = data[ stride + 2 ]; + flattenData.push( x, y, z ); + + } + + return flattenData; + + } + + function expandLineIndex( index ) { + + const indices = []; + + for ( let i = 0, l = index.length; i < l; i ++ ) { + + const i1 = index[ i ]; + const 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 ) { + + const triangulatedData = []; + let start = 0; + + for ( let i = 0, l = index.length; i < l; i ++ ) { + + const stride = start * 3; + const x = data[ stride ]; + const y = data[ stride + 1 ]; + const 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; + + } + + const vA = new THREE.Vector3(); + const vB = new THREE.Vector3(); + const vC = new THREE.Vector3(); + const uvA = new THREE.Vector2(); + const uvB = new THREE.Vector2(); + const uvC = new THREE.Vector2(); + + function computeAttributeFromIndexedData( coordIndex, index, data, itemSize ) { + + const array = []; // we use the coordIndex.length as delimiter since normalIndex must contain at least as many indices + + for ( let i = 0, l = coordIndex.length; i < l; i += 3 ) { + + const a = index[ i ]; + const b = index[ i + 1 ]; + const 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 ) { + + const array = []; + + for ( let 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 ) { + + const array = []; + + for ( let 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 ) { + + const array = attribute.array; + const itemSize = attribute.itemSize; + const array2 = new array.constructor( indices.length * itemSize ); + let index = 0, + index2 = 0; + + for ( let i = 0, l = indices.length; i < l; i ++ ) { + + index = indices[ i ] * itemSize; + + for ( let j = 0; j < itemSize; j ++ ) { + + array2[ index2 ++ ] = array[ index ++ ]; + + } + + } + + return new THREE.Float32BufferAttribute( array2, itemSize ); + + } + + const ab = new THREE.Vector3(); + const cb = new THREE.Vector3(); + + function computeNormalAttribute( index, coord, creaseAngle ) { + + const faces = []; + const vertexNormals = {}; // prepare face and raw vertex normals + + for ( let i = 0, l = index.length; i < l; i += 3 ) { + + const a = index[ i ]; + const b = index[ i + 1 ]; + const c = index[ i + 2 ]; + const 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 + + + const normals = []; + + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const face = faces[ i ]; + const nA = weightedNormal( vertexNormals[ face.a ], face.normal, creaseAngle ); + const nB = weightedNormal( vertexNormals[ face.b ], face.normal, creaseAngle ); + const 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 ) { + + const normal = new THREE.Vector3(); + + if ( creaseAngle === 0 ) { + + normal.copy( vector ); + + } else { + + for ( let i = 0, l = normals.length; i < l; i ++ ) { + + if ( normals[ i ].angleTo( vector ) < creaseAngle ) { + + normal.add( normals[ i ] ); + + } + + } + + } + + return normal.normalize(); + + } + + function toColorArray( colors ) { + + const array = []; + + for ( let 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 ) { + + // compute threshold values + const thresholds = []; + const startAngle = topDown === true ? 0 : Math.PI; + + for ( let i = 0, l = colors.length; i < l; i ++ ) { + + let angle = i === 0 ? 0 : angles[ i - 1 ]; + angle = topDown === true ? angle : startAngle - angle; + const point = new THREE.Vector3(); + point.setFromSphericalCoords( radius, angle, 0 ); + thresholds.push( point ); + + } // generate vertex colors + + + const indices = geometry.index; + const positionAttribute = geometry.attributes.position; + const colorAttribute = new THREE.BufferAttribute( new Float32Array( geometry.attributes.position.count * 3 ), 3 ); + const position = new THREE.Vector3(); + const color = new THREE.Color(); + + for ( let i = 0; i < indices.count; i ++ ) { + + const index = indices.getX( i ); + position.fromBufferAttribute( positionAttribute, index ); + let thresholdIndexA, thresholdIndexB; + let t = 1; + + for ( let j = 1; j < thresholds.length; j ++ ) { + + thresholdIndexA = j - 1; + thresholdIndexB = j; + const thresholdA = thresholds[ thresholdIndexA ]; + const thresholdB = thresholds[ thresholdIndexB ]; + + if ( topDown === true ) { + + // interpolation for sky color + if ( position.y <= thresholdA.y && position.y > thresholdB.y ) { + + t = Math.abs( thresholdA.y - position.y ) / Math.abs( thresholdA.y - thresholdB.y ); + break; + + } + + } else { + + // interpolation for ground color + if ( position.y >= thresholdA.y && position.y < thresholdB.y ) { + + t = Math.abs( thresholdA.y - position.y ) / Math.abs( thresholdA.y - thresholdB.y ); + break; + + } + + } + + } + + const colorA = colors[ thresholdIndexA ]; + const colorB = colors[ thresholdIndexB ]; + color.copy( colorA ).lerp( colorB, t ); + colorAttribute.setXYZ( index, color.r, color.g, color.b ); + + } + + geometry.setAttribute( 'color', colorAttribute ); + + } // + + + const textureLoader = new THREE.TextureLoader( this.manager ); + textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); // check version (only 2.0 is supported) + + if ( data.indexOf( '#VRML V2.0' ) === - 1 ) { + + throw Error( 'THREE.VRMLLexer: Version of VRML asset not supported.' ); + + } // create JSON representing the tree structure of the VRML asset + + + const tree = generateVRMLTree( data ); // parse the tree structure to a three.js scene + + const scene = parseTree( tree ); + return scene; + + } + + } + + class VRMLLexer { + + constructor( tokens ) { + + this.lexer = new chevrotain.Lexer( tokens ); // eslint-disable-line no-undef + + } + + lex( inputText ) { + + const lexingResult = this.lexer.tokenize( inputText ); + + if ( lexingResult.errors.length > 0 ) { + + console.error( lexingResult.errors ); + throw Error( 'THREE.VRMLLexer: Lexing errors detected.' ); + + } + + return lexingResult; + + } + + } + + const CstParser = chevrotain.CstParser; // eslint-disable-line no-undef + + class VRMLParser extends CstParser { + + constructor( tokenVocabulary ) { + + super( tokenVocabulary ); + const $ = this; + const Version = tokenVocabulary[ 'Version' ]; + const LCurly = tokenVocabulary[ 'LCurly' ]; + const RCurly = tokenVocabulary[ 'RCurly' ]; + const LSquare = tokenVocabulary[ 'LSquare' ]; + const RSquare = tokenVocabulary[ 'RSquare' ]; + const Identifier = tokenVocabulary[ 'Identifier' ]; + const RouteIdentifier = tokenVocabulary[ 'RouteIdentifier' ]; + const StringLiteral = tokenVocabulary[ 'StringLiteral' ]; + const HexLiteral = tokenVocabulary[ 'HexLiteral' ]; + const NumberLiteral = tokenVocabulary[ 'NumberLiteral' ]; + const TrueLiteral = tokenVocabulary[ 'TrueLiteral' ]; + const FalseLiteral = tokenVocabulary[ 'FalseLiteral' ]; + const NullLiteral = tokenVocabulary[ 'NullLiteral' ]; + const DEF = tokenVocabulary[ 'DEF' ]; + const USE = tokenVocabulary[ 'USE' ]; + const ROUTE = tokenVocabulary[ 'ROUTE' ]; + const TO = tokenVocabulary[ 'TO' ]; + const 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 ); + $.OR( [ { + ALT: function () { + + $.CONSUME( Identifier ); + + } + }, { + ALT: function () { + + $.CONSUME( NodeName ); + + } + } ] ); + + } ); + $.RULE( 'use', function () { + + $.CONSUME( USE ); + $.OR( [ { + ALT: function () { + + $.CONSUME( Identifier ); + + } + }, { + ALT: function () { + + $.CONSUME( NodeName ); + + } + } ] ); + + } ); + $.RULE( 'singleFieldValue', function () { + + $.AT_LEAST_ONE( function () { + + $.OR( [ { + ALT: function () { + + $.SUBRULE( $.node ); + + } + }, { + ALT: function () { + + $.SUBRULE( $.use ); + + } + }, { + ALT: function () { + + $.CONSUME( StringLiteral ); + + } + }, { + ALT: function () { + + $.CONSUME( HexLiteral ); + + } + }, { + 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( HexLiteral ); + + } + }, { + ALT: function () { + + $.CONSUME( NumberLiteral ); + + } + }, { + ALT: function () { + + $.CONSUME( NullLiteral ); + + } + } ] ); + + } ); + $.CONSUME( RSquare ); + + } ); + $.RULE( 'route', function () { + + $.CONSUME( ROUTE ); + $.CONSUME( RouteIdentifier ); + $.CONSUME( TO ); + $.CONSUME2( RouteIdentifier ); + + } ); + this.performSelfAnalysis(); + + } + + } + + class Face { + + constructor( a, b, c ) { + + this.a = a; + this.b = b; + this.c = c; + this.normal = new THREE.Vector3(); + + } + + } + + const TEXTURE_TYPE = { + INTENSITY: 1, + INTENSITY_ALPHA: 2, + RGB: 3, + RGBA: 4 + }; + + THREE.VRMLLoader = VRMLLoader; + +} )(); diff --git a/libs/three_loaders/chevrotain.min.js b/libs/three_loaders/chevrotain.min.js new file mode 100644 index 0000000..ea22f59 --- /dev/null +++ b/libs/three_loaders/chevrotain.min.js @@ -0,0 +1,3 @@ +/*! chevrotain - v9.0.1 */ +/*! For license information please see chevrotain.min.js.LICENSE.txt */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("chevrotain",[],e):"object"==typeof exports?exports.chevrotain=e():t.chevrotain=e()}("undefined"!=typeof self?self:this,(function(){return(()=>{var t={844:function(t,e){var n,r;"undefined"!=typeof self&&self,void 0===(r="function"==typeof(n=function(){function t(){}t.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},t.prototype.restoreState=function(t){this.idx=t.idx,this.input=t.input,this.groupIdx=t.groupIdx},t.prototype.pattern=function(t){this.idx=0,this.input=t,this.groupIdx=0,this.consumeChar("/");var e=this.disjunction();this.consumeChar("/");for(var n={type:"Flags",loc:{begin:this.idx,end:t.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":s(n,"global");break;case"i":s(n,"ignoreCase");break;case"m":s(n,"multiLine");break;case"u":s(n,"unicode");break;case"y":s(n,"sticky")}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:n,value:e,loc:this.loc(0)}},t.prototype.disjunction=function(){var t=[],e=this.idx;for(t.push(this.alternative());"|"===this.peekChar();)this.consumeChar("|"),t.push(this.alternative());return{type:"Disjunction",value:t,loc:this.loc(e)}},t.prototype.alternative=function(){for(var t=[],e=this.idx;this.isTerm();)t.push(this.term());return{type:"Alternative",value:t,loc:this.loc(e)}},t.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},t.prototype.assertion=function(){var t=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(t)};case"$":return{type:"EndAnchor",loc:this.loc(t)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(t)};case"B":return{type:"NonWordBoundary",loc:this.loc(t)}}throw Error("Invalid Assertion Escape");case"(":var e;switch(this.consumeChar("?"),this.popChar()){case"=":e="Lookahead";break;case"!":e="NegativeLookahead"}c(e);var n=this.disjunction();return this.consumeChar(")"),{type:e,value:n,loc:this.loc(t)}}!function(){throw Error("Internal Error - Should never get here!")}()},t.prototype.quantifier=function(t){var e,n=this.idx;switch(this.popChar()){case"*":e={atLeast:0,atMost:1/0};break;case"+":e={atLeast:1,atMost:1/0};break;case"?":e={atLeast:0,atMost:1};break;case"{":var r=this.integerIncludingZero();switch(this.popChar()){case"}":e={atLeast:r,atMost:r};break;case",":e=this.isDigit()?{atLeast:r,atMost:this.integerIncludingZero()}:{atLeast:r,atMost:1/0},this.consumeChar("}")}if(!0===t&&void 0===e)return;c(e)}if(!0!==t||void 0!==e)return c(e),"?"===this.peekChar(0)?(this.consumeChar("?"),e.greedy=!1):e.greedy=!0,e.type="Quantifier",e.loc=this.loc(n),e},t.prototype.atom=function(){var t,e=this.idx;switch(this.peekChar()){case".":t=this.dotAll();break;case"\\":t=this.atomEscape();break;case"[":t=this.characterClass();break;case"(":t=this.group()}return void 0===t&&this.isPatternCharacter()&&(t=this.patternCharacter()),c(t),t.loc=this.loc(e),this.isQuantifier()&&(t.quantifier=this.quantifier()),t},t.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[o("\n"),o("\r"),o("\u2028"),o("\u2029")]}},t.prototype.atomEscape=function(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}},t.prototype.decimalEscapeAtom=function(){return{type:"GroupBackReference",value:this.positiveInteger()}},t.prototype.characterClassEscape=function(){var t,e=!1;switch(this.popChar()){case"d":t=u;break;case"D":t=u,e=!0;break;case"s":t=p;break;case"S":t=p,e=!0;break;case"w":t=l;break;case"W":t=l,e=!0}return c(t),{type:"Set",value:t,complement:e}},t.prototype.controlEscapeAtom=function(){var t;switch(this.popChar()){case"f":t=o("\f");break;case"n":t=o("\n");break;case"r":t=o("\r");break;case"t":t=o("\t");break;case"v":t=o("\v")}return c(t),{type:"Character",value:t}},t.prototype.controlLetterEscapeAtom=function(){this.consumeChar("c");var t=this.popChar();if(!1===/[a-zA-Z]/.test(t))throw Error("Invalid ");return{type:"Character",value:t.toUpperCase().charCodeAt(0)-64}},t.prototype.nulCharacterAtom=function(){return this.consumeChar("0"),{type:"Character",value:o("\0")}},t.prototype.hexEscapeSequenceAtom=function(){return this.consumeChar("x"),this.parseHexDigits(2)},t.prototype.regExpUnicodeEscapeSequenceAtom=function(){return this.consumeChar("u"),this.parseHexDigits(4)},t.prototype.identityEscapeAtom=function(){return{type:"Character",value:o(this.popChar())}},t.prototype.classPatternCharacterAtom=function(){switch(this.peekChar()){case"\n":case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:return{type:"Character",value:o(this.popChar())}}},t.prototype.characterClass=function(){var t=[],e=!1;for(this.consumeChar("["),"^"===this.peekChar(0)&&(this.consumeChar("^"),e=!0);this.isClassAtom();){var n=this.classAtom();if("Character"===n.type&&this.isRangeDash()){this.consumeChar("-");var r=this.classAtom();if("Character"===r.type){if(r.value=this.input.length)throw Error("Unexpected end of input");this.idx++},t.prototype.loc=function(t){return{begin:t,end:this.idx}};var e,n=/[0-9a-fA-F]/,r=/[0-9]/,i=/[1-9]/;function o(t){return t.charCodeAt(0)}function a(t,e){void 0!==t.length?t.forEach((function(t){e.push(t)})):e.push(t)}function s(t,e){if(!0===t[e])throw"duplicate flag "+e;t[e]=!0}function c(t){if(void 0===t)throw Error("Internal Error - Should never get here!")}var u=[];for(e=o("0");e<=o("9");e++)u.push(e);var l=[o("_")].concat(u);for(e=o("a");e<=o("z");e++)l.push(e);for(e=o("A");e<=o("Z");e++)l.push(e);var p=[o(" "),o("\f"),o("\n"),o("\r"),o("\t"),o("\v"),o("\t"),o(" "),o(" "),o(" "),o(" "),o(" "),o(" "),o(" "),o(" "),o(" "),o(" "),o(" "),o(" "),o(" "),o("\u2028"),o("\u2029"),o(" "),o(" "),o(" "),o("\ufeff")];function f(){}return f.prototype.visitChildren=function(t){for(var e in t){var n=t[e];t.hasOwnProperty(e)&&(void 0!==n.type?this.visit(n):Array.isArray(n)&&n.forEach((function(t){this.visit(t)}),this))}},f.prototype.visit=function(t){switch(t.type){case"Pattern":this.visitPattern(t);break;case"Flags":this.visitFlags(t);break;case"Disjunction":this.visitDisjunction(t);break;case"Alternative":this.visitAlternative(t);break;case"StartAnchor":this.visitStartAnchor(t);break;case"EndAnchor":this.visitEndAnchor(t);break;case"WordBoundary":this.visitWordBoundary(t);break;case"NonWordBoundary":this.visitNonWordBoundary(t);break;case"Lookahead":this.visitLookahead(t);break;case"NegativeLookahead":this.visitNegativeLookahead(t);break;case"Character":this.visitCharacter(t);break;case"Set":this.visitSet(t);break;case"Group":this.visitGroup(t);break;case"GroupBackReference":this.visitGroupBackReference(t);break;case"Quantifier":this.visitQuantifier(t)}this.visitChildren(t)},f.prototype.visitPattern=function(t){},f.prototype.visitFlags=function(t){},f.prototype.visitDisjunction=function(t){},f.prototype.visitAlternative=function(t){},f.prototype.visitStartAnchor=function(t){},f.prototype.visitEndAnchor=function(t){},f.prototype.visitWordBoundary=function(t){},f.prototype.visitNonWordBoundary=function(t){},f.prototype.visitLookahead=function(t){},f.prototype.visitNegativeLookahead=function(t){},f.prototype.visitCharacter=function(t){},f.prototype.visitSet=function(t){},f.prototype.visitGroup=function(t){},f.prototype.visitGroupBackReference=function(t){},f.prototype.visitQuantifier=function(t){},{RegExpParser:t,BaseRegExpVisitor:f,VERSION:"0.5.0"}})?n.apply(e,[]):n)||(t.exports=r)},781:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createSyntaxDiagramsCode=void 0;var r=n(979);e.createSyntaxDiagramsCode=function(t,e){var n=void 0===e?{}:e,i=n.resourceBase,o=void 0===i?"https://unpkg.com/chevrotain@"+r.VERSION+"/diagrams/":i,a=n.css;return"\n\x3c!-- This is a generated file --\x3e\n\n\n\n\n\n\n\n