diff options
Diffstat (limited to 'Custom keyboard V2/pcb/VRMLLoader.js')
| -rw-r--r-- | Custom keyboard V2/pcb/VRMLLoader.js | 3537 |
1 files changed, 3537 insertions, 0 deletions
diff --git a/Custom keyboard V2/pcb/VRMLLoader.js b/Custom keyboard V2/pcb/VRMLLoader.js new file mode 100644 index 0000000..c04a318 --- /dev/null +++ b/Custom keyboard V2/pcb/VRMLLoader.js @@ -0,0 +1,3537 @@ +import { + BackSide, + BoxGeometry, + BufferAttribute, + BufferGeometry, + ClampToEdgeWrapping, + Color, + ConeGeometry, + CylinderGeometry, + DataTexture, + DoubleSide, + FileLoader, + Float32BufferAttribute, + FrontSide, + Group, + LineBasicMaterial, + LineSegments, + Loader, + LoaderUtils, + Mesh, + MeshBasicMaterial, + MeshPhongMaterial, + Object3D, + Points, + PointsMaterial, + Quaternion, + RepeatWrapping, + Scene, + ShapeUtils, + SphereGeometry, + SRGBColorSpace, + TextureLoader, + Vector2, + Vector3 +} from 'three'; +import chevrotain from './chevrotain.module.min.js'; + + +class VRMLLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( scope.path === '' ) ? LoaderUtils.extractUrlBase( url ) : scope.path; + + const loader = new 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; + + // 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\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][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])*"/ } ); + 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 + } ); + + // 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 + } ); + + 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 + + class VRMLToASTVisitor extends BaseVRMLVisitor { + + constructor() { + + super(); + + this.validateVisitor(); + + } + + vrml( 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( ctx ) { + + return ctx.Version[ 0 ].image; + + } + + node( 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( 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( ctx ) { + + return ( ctx.Identifier || ctx.NodeName )[ 0 ].image; + + } + + use( ctx ) { + + return { USE: ( ctx.Identifier || ctx.NodeName )[ 0 ].image }; + + } + + singleFieldValue( ctx ) { + + return processField( this, ctx ); + + } + + multiFieldValue( ctx ) { + + return processField( this, ctx ); + + } + + route( 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 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 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 'Anchor': + 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 '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 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 'description': + // field not supported + break; + + case 'collide': + // field not supported + break; + + case 'parameter': + // field not supported + break; + + case 'rotation': + const axis = new Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ).normalize(); + 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; + + case 'url': + // field not supported + break; + + default: + console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName ); + break; + + } + + } + + return object; + + } + + function buildBackgroundNode( node ) { + + const group = new 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 SphereGeometry( radius, 32, 16 ); + const skyMaterial = new MeshBasicMaterial( { fog: false, side: 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 ] ); + skyMaterial.color.convertSRGBToLinear(); + + } + + const sky = new Mesh( skyGeometry, skyMaterial ); + group.add( sky ); + + } + + // ground + + if ( groundColor ) { + + if ( groundColor.length > 0 ) { + + const groundGeometry = new SphereGeometry( radius, 32, 16, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI ); + const groundMaterial = new MeshBasicMaterial( { fog: false, side: BackSide, vertexColors: true, depthWrite: false, depthTest: false } ); + + paintFaces( groundGeometry, radius, groundAngle, toColorArray( groundColor ), false ); + + const ground = new 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 MeshBasicMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + 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 PointsMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0xffffff, + opacity: material.opacity, + transparent: material.transparent + } ); + + 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 Points( geometry, pointsMaterial ); + + } else if ( type === 'line' ) { // lines + + const lineMaterial = new LineBasicMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0xffffff, + opacity: material.opacity, + transparent: material.transparent + } ); + + 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 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 ) ? FrontSide : DoubleSide; + + } + + // check for vertex colors + + if ( geometry.attributes.color !== undefined ) { + + material.vertexColors = true; + + } + + object = new Mesh( geometry, material ); + + } + + } else { + + object = new 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 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 MeshBasicMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + 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 Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ); + materialData.diffuseColor.convertSRGBToLinear(); + break; + + case 'emissiveColor': + materialData.emissiveColor = new Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ); + materialData.emissiveColor.convertSRGBToLinear(); + break; + + case 'shininess': + materialData.shininess = fieldValues[ 0 ]; + break; + + case 'specularColor': + materialData.specularColor = new Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ); + materialData.specularColor.convertSRGBToLinear(); + 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; + color.a = 1; + 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 ) ); + color.a = 1; + 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 = RepeatWrapping; + let wrapT = 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 textureType = getTextureType( num_components ); + + const data = new Uint8Array( 4 * width * height ); + + 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 ); + + const stride = k * 4; + + data[ stride + 0 ] = color.r; + data[ stride + 1 ] = color.g; + data[ stride + 2 ] = color.b; + data[ stride + 3 ] = color.a; + + } + + texture = new DataTexture( data, width, height ); + texture.colorSpace = SRGBColorSpace; + texture.needsUpdate = true; + texture.__type = textureType; // needed for material modifications + break; + + case 'repeatS': + if ( fieldValues[ 0 ] === false ) wrapS = ClampToEdgeWrapping; + break; + + case 'repeatT': + if ( fieldValues[ 0 ] === false ) wrapT = 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 = RepeatWrapping; + let wrapT = 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 = ClampToEdgeWrapping; + break; + + case 'repeatT': + if ( fieldValues[ 0 ] === false ) wrapT = ClampToEdgeWrapping; + break; + + default: + console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName ); + break; + + } + + } + + if ( texture ) { + + texture.wrapS = wrapS; + texture.wrapT = wrapT; + texture.colorSpace = SRGBColorSpace; + + } + + return texture; + + } + + function buildTextureTransformNode( node ) { + + const transformData = { + center: new Vector2(), + rotation: new Vector2(), + scale: new Vector2(), + translation: new 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 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 Color node + + colorAttribute = toNonIndexedAttribute( triangulatedCoordIndex, new 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 ); + + + } + + } + + convertColorsToLinearSRGB( colorAttribute ); + + } + + 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 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 Float32BufferAttribute( texCoord, 2 ) ); + + } + + } + + const geometry = new BufferGeometry(); + const positionAttribute = toNonIndexedAttribute( triangulatedCoordIndex, new 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 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 Color node + + const expandedLineColors = expandLineData( color, coordIndex ); // compute colors for each line segment (rendering primitve) + colorAttribute = computeAttributeFromLineData( expandedLineIndex, expandedLineColors ); // compute data on vertex level + + } + + } + + convertColorsToLinearSRGB( colorAttribute ); + + } + + // + + const geometry = new BufferGeometry(); + + const positionAttribute = toNonIndexedAttribute( expandedLineIndex, new 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 BufferGeometry(); + + geometry.setAttribute( 'position', new Float32BufferAttribute( coord, 3 ) ); + + if ( color ) { + + const colorAttribute = new Float32BufferAttribute( color, 3 ); + convertColorsToLinearSRGB( colorAttribute ); + + geometry.setAttribute( 'color', colorAttribute ); + + } + + geometry._type = 'points'; + + return geometry; + + } + + function buildBoxNode( node ) { + + const size = new 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 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 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 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 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 Float32BufferAttribute( vertices, 3 ) ); + const uvAttribute = toNonIndexedAttribute( indices, new 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 Float32BufferAttribute( colors, 3 ); + + } else { + + colorAttribute = toNonIndexedAttribute( indices, new Float32BufferAttribute( colors, 3 ) ); + + } + + convertColorsToLinearSRGB( colorAttribute ); + + } + + // 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 Float32BufferAttribute( normals, 3 ); + + } else { + + normalAttribute = toNonIndexedAttribute( indices, new Float32BufferAttribute( normals, 3 ) ); + + } + + } else { + + normalAttribute = computeNormalAttribute( indices, vertices, creaseAngle ); + + } + + // build geometry + + const geometry = new 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 Vector3(); + const scaling = new Vector3(); + + const axis = new Vector3(); + const vertex = new Vector3(); + const quaternion = new 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 Vector2( crossSection[ i ], crossSection[ i + 1 ] ) ); + + } + + const faces = 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 Float32BufferAttribute( vertices, 3 ) ); + const normalAttribute = computeNormalAttribute( indices, vertices, creaseAngle ); + + const geometry = new 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 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 Vector3(); + const vB = new Vector3(); + const vC = new Vector3(); + + const uvA = new Vector2(); + const uvB = new Vector2(); + const uvC = new 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 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 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 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 Float32BufferAttribute( array2, itemSize ); + + } + + const ab = new Vector3(); + const cb = new 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 Float32BufferAttribute( normals, 3 ); + + } + + function weightedNormal( normals, vector, creaseAngle ) { + + const normal = new 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 Color( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ) ); + + } + + return array; + + } + + function convertColorsToLinearSRGB( attribute ) { + + const color = new Color(); + + for ( let i = 0; i < attribute.count; i ++ ) { + + color.fromBufferAttribute( attribute, i ); + color.convertSRGBToLinear(); + + attribute.setXYZ( i, color.r, color.g, color.b ); + + } + + } + + /** + * 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 Vector3(); + point.setFromSphericalCoords( radius, angle, 0 ); + + thresholds.push( point ); + + } + + // generate vertex colors + + const indices = geometry.index; + const positionAttribute = geometry.attributes.position; + const colorAttribute = new BufferAttribute( new Float32Array( geometry.attributes.position.count * 3 ), 3 ); + + const position = new Vector3(); + const color = new 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 ).convertSRGBToLinear(); + + colorAttribute.setXYZ( index, color.r, color.g, color.b ); + + } + + geometry.setAttribute( 'color', colorAttribute ); + + } + + // + + const textureLoader = new 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 ); + + } + + 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; + +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 Vector3(); + + } + +} + +const TEXTURE_TYPE = { + INTENSITY: 1, + INTENSITY_ALPHA: 2, + RGB: 3, + RGBA: 4 +}; + +export { VRMLLoader }; |
