three-stdlib
Version:
stand-alone library of threejs examples
1 lines • 127 kB
Source Map (JSON)
{"version":3,"file":"VRMLLoader.cjs","sources":["../../src/loaders/VRMLLoader.js"],"sourcesContent":["import {\n BackSide,\n BoxGeometry,\n BufferAttribute,\n BufferGeometry,\n ClampToEdgeWrapping,\n Color,\n ConeGeometry,\n CylinderGeometry,\n DataTexture,\n DoubleSide,\n FileLoader,\n Float32BufferAttribute,\n FrontSide,\n Group,\n LineBasicMaterial,\n LineSegments,\n Loader,\n LoaderUtils,\n Mesh,\n MeshBasicMaterial,\n MeshPhongMaterial,\n Object3D,\n Points,\n PointsMaterial,\n Quaternion,\n RepeatWrapping,\n Scene,\n ShapeUtils,\n SphereGeometry,\n TextureLoader,\n Vector2,\n Vector3,\n} from 'three'\nimport { Lexer, CstParser, createToken } from '../libs/chevrotain'\n\nclass VRMLLoader extends Loader {\n constructor(manager) {\n super(manager)\n }\n\n load(url, onLoad, onProgress, onError) {\n const scope = this\n\n const path = scope.path === '' ? LoaderUtils.extractUrlBase(url) : scope.path\n\n const loader = new FileLoader(scope.manager)\n loader.setPath(scope.path)\n loader.setRequestHeader(scope.requestHeader)\n loader.setWithCredentials(scope.withCredentials)\n loader.load(\n url,\n function (text) {\n try {\n onLoad(scope.parse(text, path))\n } catch (e) {\n if (onError) {\n onError(e)\n } else {\n console.error(e)\n }\n\n scope.manager.itemError(url)\n }\n },\n onProgress,\n onError,\n )\n }\n\n parse(data, path) {\n const nodeMap = {}\n\n function generateVRMLTree(data) {\n // create lexer, parser and visitor\n\n const tokenData = createTokens()\n\n const lexer = new VRMLLexer(tokenData.tokens)\n const parser = new VRMLParser(tokenData.tokenVocabulary)\n const visitor = createVisitor(parser.getBaseCstVisitorConstructor())\n\n // lexing\n\n const lexingResult = lexer.lex(data)\n parser.input = lexingResult.tokens\n\n // parsing\n\n const cstOutput = parser.vrml()\n\n if (parser.errors.length > 0) {\n console.error(parser.errors)\n\n throw Error('THREE.VRMLLoader: Parsing errors detected.')\n }\n\n // actions\n\n const ast = visitor.visit(cstOutput)\n\n return ast\n }\n\n function createTokens() {\n // from http://gun.teipir.gr/VRML-amgem/spec/part1/concepts.html#SyntaxBasics\n\n const RouteIdentifier = createToken({\n name: 'RouteIdentifier',\n 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]*/,\n })\n const Identifier = createToken({\n name: 'Identifier',\n 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]*/,\n longer_alt: RouteIdentifier,\n })\n\n // from http://gun.teipir.gr/VRML-amgem/spec/part1/nodesRef.html\n\n const nodeTypes = [\n 'Anchor',\n 'Billboard',\n 'Collision',\n 'Group',\n 'Transform', // grouping nodes\n 'Inline',\n 'LOD',\n 'Switch', // special groups\n 'AudioClip',\n 'DirectionalLight',\n 'PointLight',\n 'Script',\n 'Shape',\n 'Sound',\n 'SpotLight',\n 'WorldInfo', // common nodes\n 'CylinderSensor',\n 'PlaneSensor',\n 'ProximitySensor',\n 'SphereSensor',\n 'TimeSensor',\n 'TouchSensor',\n 'VisibilitySensor', // sensors\n 'Box',\n 'Cone',\n 'Cylinder',\n 'ElevationGrid',\n 'Extrusion',\n 'IndexedFaceSet',\n 'IndexedLineSet',\n 'PointSet',\n 'Sphere', // geometries\n 'Color',\n 'Coordinate',\n 'Normal',\n 'TextureCoordinate', // geometric properties\n 'Appearance',\n 'FontStyle',\n 'ImageTexture',\n 'Material',\n 'MovieTexture',\n 'PixelTexture',\n 'TextureTransform', // appearance\n 'ColorInterpolator',\n 'CoordinateInterpolator',\n 'NormalInterpolator',\n 'OrientationInterpolator',\n 'PositionInterpolator',\n 'ScalarInterpolator', // interpolators\n 'Background',\n 'Fog',\n 'NavigationInfo',\n 'Viewpoint', // bindable nodes\n 'Text', // Text must be placed at the end of the regex so there are no matches for TextureTransform and TextureCoordinate\n ]\n\n //\n\n const Version = createToken({\n name: 'Version',\n pattern: /#VRML.*/,\n longer_alt: Identifier,\n })\n\n const NodeName = createToken({\n name: 'NodeName',\n pattern: new RegExp(nodeTypes.join('|')),\n longer_alt: Identifier,\n })\n\n const DEF = createToken({\n name: 'DEF',\n pattern: /DEF/,\n longer_alt: Identifier,\n })\n\n const USE = createToken({\n name: 'USE',\n pattern: /USE/,\n longer_alt: Identifier,\n })\n\n const ROUTE = createToken({\n name: 'ROUTE',\n pattern: /ROUTE/,\n longer_alt: Identifier,\n })\n\n const TO = createToken({\n name: 'TO',\n pattern: /TO/,\n longer_alt: Identifier,\n })\n\n //\n\n const StringLiteral = createToken({\n name: 'StringLiteral',\n pattern: /\"(?:[^\\\\\"\\n\\r]|\\\\[bfnrtv\"\\\\/]|\\\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])*\"/,\n })\n const HexLiteral = createToken({ name: 'HexLiteral', pattern: /0[xX][0-9a-fA-F]+/ })\n const NumberLiteral = createToken({ name: 'NumberLiteral', pattern: /[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?/ })\n const TrueLiteral = createToken({ name: 'TrueLiteral', pattern: /TRUE/ })\n const FalseLiteral = createToken({ name: 'FalseLiteral', pattern: /FALSE/ })\n const NullLiteral = createToken({ name: 'NullLiteral', pattern: /NULL/ })\n const LSquare = createToken({ name: 'LSquare', pattern: /\\[/ })\n const RSquare = createToken({ name: 'RSquare', pattern: /]/ })\n const LCurly = createToken({ name: 'LCurly', pattern: /{/ })\n const RCurly = createToken({ name: 'RCurly', pattern: /}/ })\n const Comment = createToken({\n name: 'Comment',\n pattern: /#.*/,\n group: Lexer.SKIPPED,\n })\n\n // commas, blanks, tabs, newlines and carriage returns are whitespace characters wherever they appear outside of string fields\n\n const WhiteSpace = createToken({\n name: 'WhiteSpace',\n pattern: /[ ,\\s]/,\n group: Lexer.SKIPPED,\n })\n\n const tokens = [\n WhiteSpace,\n // keywords appear before the Identifier\n NodeName,\n DEF,\n USE,\n ROUTE,\n TO,\n TrueLiteral,\n FalseLiteral,\n NullLiteral,\n // the Identifier must appear after the keywords because all keywords are valid identifiers\n Version,\n Identifier,\n RouteIdentifier,\n StringLiteral,\n HexLiteral,\n NumberLiteral,\n LSquare,\n RSquare,\n LCurly,\n RCurly,\n Comment,\n ]\n\n const tokenVocabulary = {}\n\n for (let i = 0, l = tokens.length; i < l; i++) {\n const token = tokens[i]\n\n tokenVocabulary[token.name] = token\n }\n\n return { tokens: tokens, tokenVocabulary: tokenVocabulary }\n }\n\n function createVisitor(BaseVRMLVisitor) {\n // the visitor is created dynmaically based on the given base class\n\n function VRMLToASTVisitor() {\n BaseVRMLVisitor.call(this)\n\n this.validateVisitor()\n }\n\n VRMLToASTVisitor.prototype = Object.assign(Object.create(BaseVRMLVisitor.prototype), {\n constructor: VRMLToASTVisitor,\n\n vrml: function (ctx) {\n const data = {\n version: this.visit(ctx.version),\n nodes: [],\n routes: [],\n }\n\n for (let i = 0, l = ctx.node.length; i < l; i++) {\n const node = ctx.node[i]\n\n data.nodes.push(this.visit(node))\n }\n\n if (ctx.route) {\n for (let i = 0, l = ctx.route.length; i < l; i++) {\n const route = ctx.route[i]\n\n data.routes.push(this.visit(route))\n }\n }\n\n return data\n },\n\n version: function (ctx) {\n return ctx.Version[0].image\n },\n\n node: function (ctx) {\n const data = {\n name: ctx.NodeName[0].image,\n fields: [],\n }\n\n if (ctx.field) {\n for (let i = 0, l = ctx.field.length; i < l; i++) {\n const field = ctx.field[i]\n\n data.fields.push(this.visit(field))\n }\n }\n\n // DEF\n\n if (ctx.def) {\n data.DEF = this.visit(ctx.def[0])\n }\n\n return data\n },\n\n field: function (ctx) {\n const data = {\n name: ctx.Identifier[0].image,\n type: null,\n values: null,\n }\n\n let result\n\n // SFValue\n\n if (ctx.singleFieldValue) {\n result = this.visit(ctx.singleFieldValue[0])\n }\n\n // MFValue\n\n if (ctx.multiFieldValue) {\n result = this.visit(ctx.multiFieldValue[0])\n }\n\n data.type = result.type\n data.values = result.values\n\n return data\n },\n\n def: function (ctx) {\n return (ctx.Identifier || ctx.NodeName)[0].image\n },\n\n use: function (ctx) {\n return { USE: (ctx.Identifier || ctx.NodeName)[0].image }\n },\n\n singleFieldValue: function (ctx) {\n return processField(this, ctx)\n },\n\n multiFieldValue: function (ctx) {\n return processField(this, ctx)\n },\n\n route: function (ctx) {\n const data = {\n FROM: ctx.RouteIdentifier[0].image,\n TO: ctx.RouteIdentifier[1].image,\n }\n\n return data\n },\n })\n\n function processField(scope, ctx) {\n const field = {\n type: null,\n values: [],\n }\n\n if (ctx.node) {\n field.type = 'node'\n\n for (let i = 0, l = ctx.node.length; i < l; i++) {\n const node = ctx.node[i]\n\n field.values.push(scope.visit(node))\n }\n }\n\n if (ctx.use) {\n field.type = 'use'\n\n for (let i = 0, l = ctx.use.length; i < l; i++) {\n const use = ctx.use[i]\n\n field.values.push(scope.visit(use))\n }\n }\n\n if (ctx.StringLiteral) {\n field.type = 'string'\n\n for (let i = 0, l = ctx.StringLiteral.length; i < l; i++) {\n const stringLiteral = ctx.StringLiteral[i]\n\n field.values.push(stringLiteral.image.replace(/'|\"/g, ''))\n }\n }\n\n if (ctx.NumberLiteral) {\n field.type = 'number'\n\n for (let i = 0, l = ctx.NumberLiteral.length; i < l; i++) {\n const numberLiteral = ctx.NumberLiteral[i]\n\n field.values.push(parseFloat(numberLiteral.image))\n }\n }\n\n if (ctx.HexLiteral) {\n field.type = 'hex'\n\n for (let i = 0, l = ctx.HexLiteral.length; i < l; i++) {\n const hexLiteral = ctx.HexLiteral[i]\n\n field.values.push(hexLiteral.image)\n }\n }\n\n if (ctx.TrueLiteral) {\n field.type = 'boolean'\n\n for (let i = 0, l = ctx.TrueLiteral.length; i < l; i++) {\n const trueLiteral = ctx.TrueLiteral[i]\n\n if (trueLiteral.image === 'TRUE') field.values.push(true)\n }\n }\n\n if (ctx.FalseLiteral) {\n field.type = 'boolean'\n\n for (let i = 0, l = ctx.FalseLiteral.length; i < l; i++) {\n const falseLiteral = ctx.FalseLiteral[i]\n\n if (falseLiteral.image === 'FALSE') field.values.push(false)\n }\n }\n\n if (ctx.NullLiteral) {\n field.type = 'null'\n\n ctx.NullLiteral.forEach(function () {\n field.values.push(null)\n })\n }\n\n return field\n }\n\n return new VRMLToASTVisitor()\n }\n\n function parseTree(tree) {\n // console.log( JSON.stringify( tree, null, 2 ) );\n\n const nodes = tree.nodes\n const scene = new Scene()\n\n // first iteration: build nodemap based on DEF statements\n\n for (let i = 0, l = nodes.length; i < l; i++) {\n const node = nodes[i]\n\n buildNodeMap(node)\n }\n\n // second iteration: build nodes\n\n for (let i = 0, l = nodes.length; i < l; i++) {\n const node = nodes[i]\n const object = getNode(node)\n\n if (object instanceof Object3D) scene.add(object)\n\n if (node.name === 'WorldInfo') scene.userData.worldInfo = object\n }\n\n return scene\n }\n\n function buildNodeMap(node) {\n if (node.DEF) {\n nodeMap[node.DEF] = node\n }\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n\n if (field.type === 'node') {\n const fieldValues = field.values\n\n for (let j = 0, jl = fieldValues.length; j < jl; j++) {\n buildNodeMap(fieldValues[j])\n }\n }\n }\n }\n\n function getNode(node) {\n // handle case where a node refers to a different one\n\n if (node.USE) {\n return resolveUSE(node.USE)\n }\n\n if (node.build !== undefined) return node.build\n\n node.build = buildNode(node)\n\n return node.build\n }\n\n // node builder\n\n function buildNode(node) {\n const nodeName = node.name\n let build\n\n switch (nodeName) {\n case 'Group':\n case 'Transform':\n case 'Collision':\n build = buildGroupingNode(node)\n break\n\n case 'Background':\n build = buildBackgroundNode(node)\n break\n\n case 'Shape':\n build = buildShapeNode(node)\n break\n\n case 'Appearance':\n build = buildAppearanceNode(node)\n break\n\n case 'Material':\n build = buildMaterialNode(node)\n break\n\n case 'ImageTexture':\n build = buildImageTextureNode(node)\n break\n\n case 'PixelTexture':\n build = buildPixelTextureNode(node)\n break\n\n case 'TextureTransform':\n build = buildTextureTransformNode(node)\n break\n\n case 'IndexedFaceSet':\n build = buildIndexedFaceSetNode(node)\n break\n\n case 'IndexedLineSet':\n build = buildIndexedLineSetNode(node)\n break\n\n case 'PointSet':\n build = buildPointSetNode(node)\n break\n\n case 'Box':\n build = buildBoxNode(node)\n break\n\n case 'Cone':\n build = buildConeNode(node)\n break\n\n case 'Cylinder':\n build = buildCylinderNode(node)\n break\n\n case 'Sphere':\n build = buildSphereNode(node)\n break\n\n case 'ElevationGrid':\n build = buildElevationGridNode(node)\n break\n\n case 'Extrusion':\n build = buildExtrusionNode(node)\n break\n\n case 'Color':\n case 'Coordinate':\n case 'Normal':\n case 'TextureCoordinate':\n build = buildGeometricNode(node)\n break\n\n case 'WorldInfo':\n build = buildWorldInfoNode(node)\n break\n\n case 'Anchor':\n case 'Billboard':\n\n case 'Inline':\n case 'LOD':\n case 'Switch':\n\n case 'AudioClip':\n case 'DirectionalLight':\n case 'PointLight':\n case 'Script':\n case 'Sound':\n case 'SpotLight':\n\n case 'CylinderSensor':\n case 'PlaneSensor':\n case 'ProximitySensor':\n case 'SphereSensor':\n case 'TimeSensor':\n case 'TouchSensor':\n case 'VisibilitySensor':\n\n case 'Text':\n\n case 'FontStyle':\n case 'MovieTexture':\n\n case 'ColorInterpolator':\n case 'CoordinateInterpolator':\n case 'NormalInterpolator':\n case 'OrientationInterpolator':\n case 'PositionInterpolator':\n case 'ScalarInterpolator':\n\n case 'Fog':\n case 'NavigationInfo':\n case 'Viewpoint':\n // node not supported yet\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown node:', nodeName)\n break\n }\n\n if (build !== undefined && node.DEF !== undefined && build.hasOwnProperty('name') === true) {\n build.name = node.DEF\n }\n\n return build\n }\n\n function buildGroupingNode(node) {\n const object = new Group()\n\n //\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'bboxCenter':\n // field not supported\n break\n\n case 'bboxSize':\n // field not supported\n break\n\n case 'center':\n // field not supported\n break\n\n case 'children':\n parseFieldChildren(fieldValues, object)\n break\n\n case 'collide':\n // field not supported\n break\n\n case 'rotation':\n const axis = new Vector3(fieldValues[0], fieldValues[1], fieldValues[2]).normalize()\n const angle = fieldValues[3]\n object.quaternion.setFromAxisAngle(axis, angle)\n break\n\n case 'scale':\n object.scale.set(fieldValues[0], fieldValues[1], fieldValues[2])\n break\n\n case 'scaleOrientation':\n // field not supported\n break\n\n case 'translation':\n object.position.set(fieldValues[0], fieldValues[1], fieldValues[2])\n break\n\n case 'proxy':\n // field not supported\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n return object\n }\n\n function buildBackgroundNode(node) {\n const group = new Group()\n\n let groundAngle, groundColor\n let skyAngle, skyColor\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'groundAngle':\n groundAngle = fieldValues\n break\n\n case 'groundColor':\n groundColor = fieldValues\n break\n\n case 'backUrl':\n // field not supported\n break\n\n case 'bottomUrl':\n // field not supported\n break\n\n case 'frontUrl':\n // field not supported\n break\n\n case 'leftUrl':\n // field not supported\n break\n\n case 'rightUrl':\n // field not supported\n break\n\n case 'topUrl':\n // field not supported\n break\n\n case 'skyAngle':\n skyAngle = fieldValues\n break\n\n case 'skyColor':\n skyColor = fieldValues\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n const radius = 10000\n\n // sky\n\n if (skyColor) {\n const skyGeometry = new SphereGeometry(radius, 32, 16)\n const skyMaterial = new MeshBasicMaterial({ fog: false, side: BackSide, depthWrite: false, depthTest: false })\n\n if (skyColor.length > 3) {\n paintFaces(skyGeometry, radius, skyAngle, toColorArray(skyColor), true)\n skyMaterial.vertexColors = true\n } else {\n skyMaterial.color.setRGB(skyColor[0], skyColor[1], skyColor[2])\n }\n\n const sky = new Mesh(skyGeometry, skyMaterial)\n group.add(sky)\n }\n\n // ground\n\n if (groundColor) {\n if (groundColor.length > 0) {\n const groundGeometry = new SphereGeometry(radius, 32, 16, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI)\n const groundMaterial = new MeshBasicMaterial({\n fog: false,\n side: BackSide,\n vertexColors: true,\n depthWrite: false,\n depthTest: false,\n })\n\n paintFaces(groundGeometry, radius, groundAngle, toColorArray(groundColor), false)\n\n const ground = new Mesh(groundGeometry, groundMaterial)\n group.add(ground)\n }\n }\n\n // render background group first\n\n group.renderOrder = -Infinity\n\n return group\n }\n\n function buildShapeNode(node) {\n const fields = node.fields\n\n // if the appearance field is NULL or unspecified, lighting is off and the unlit object color is (0, 0, 0)\n\n let material = new MeshBasicMaterial({ color: 0x000000 })\n let geometry\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'appearance':\n if (fieldValues[0] !== null) {\n material = getNode(fieldValues[0])\n }\n\n break\n\n case 'geometry':\n if (fieldValues[0] !== null) {\n geometry = getNode(fieldValues[0])\n }\n\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n // build 3D object\n\n let object\n\n if (geometry && geometry.attributes.position) {\n const type = geometry._type\n\n if (type === 'points') {\n // points\n\n const pointsMaterial = new PointsMaterial({ color: 0xffffff })\n\n if (geometry.attributes.color !== undefined) {\n pointsMaterial.vertexColors = true\n } else {\n // 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\n\n if (material.isMeshPhongMaterial) {\n pointsMaterial.color.copy(material.emissive)\n }\n }\n\n object = new Points(geometry, pointsMaterial)\n } else if (type === 'line') {\n // lines\n\n const lineMaterial = new LineBasicMaterial({ color: 0xffffff })\n\n if (geometry.attributes.color !== undefined) {\n lineMaterial.vertexColors = true\n } else {\n // 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\n\n if (material.isMeshPhongMaterial) {\n lineMaterial.color.copy(material.emissive)\n }\n }\n\n object = new LineSegments(geometry, lineMaterial)\n } else {\n // consider meshes\n\n // check \"solid\" hint (it's placed in the geometry but affects the material)\n\n if (geometry._solid !== undefined) {\n material.side = geometry._solid ? FrontSide : DoubleSide\n }\n\n // check for vertex colors\n\n if (geometry.attributes.color !== undefined) {\n material.vertexColors = true\n }\n\n object = new Mesh(geometry, material)\n }\n } else {\n object = new Object3D()\n\n // if the geometry field is NULL or no vertices are defined the object is not drawn\n\n object.visible = false\n }\n\n return object\n }\n\n function buildAppearanceNode(node) {\n let material = new MeshPhongMaterial()\n let transformData\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'material':\n if (fieldValues[0] !== null) {\n const materialData = getNode(fieldValues[0])\n\n if (materialData.diffuseColor) material.color.copy(materialData.diffuseColor)\n if (materialData.emissiveColor) material.emissive.copy(materialData.emissiveColor)\n if (materialData.shininess) material.shininess = materialData.shininess\n if (materialData.specularColor) material.specular.copy(materialData.specularColor)\n if (materialData.transparency) material.opacity = 1 - materialData.transparency\n if (materialData.transparency > 0) material.transparent = true\n } else {\n // if the material field is NULL or unspecified, lighting is off and the unlit object color is (0, 0, 0)\n\n material = new MeshBasicMaterial({ color: 0x000000 })\n }\n\n break\n\n case 'texture':\n const textureNode = fieldValues[0]\n if (textureNode !== null) {\n if (textureNode.name === 'ImageTexture' || textureNode.name === 'PixelTexture') {\n material.map = getNode(textureNode)\n } else {\n // MovieTexture not supported yet\n }\n }\n\n break\n\n case 'textureTransform':\n if (fieldValues[0] !== null) {\n transformData = getNode(fieldValues[0])\n }\n\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n // only apply texture transform data if a texture was defined\n\n if (material.map) {\n // respect VRML lighting model\n\n if (material.map.__type) {\n switch (material.map.__type) {\n case TEXTURE_TYPE.INTENSITY_ALPHA:\n material.opacity = 1 // ignore transparency\n break\n\n case TEXTURE_TYPE.RGB:\n material.color.set(0xffffff) // ignore material color\n break\n\n case TEXTURE_TYPE.RGBA:\n material.color.set(0xffffff) // ignore material color\n material.opacity = 1 // ignore transparency\n break\n\n default:\n }\n\n delete material.map.__type\n }\n\n // apply texture transform\n\n if (transformData) {\n material.map.center.copy(transformData.center)\n material.map.rotation = transformData.rotation\n material.map.repeat.copy(transformData.scale)\n material.map.offset.copy(transformData.translation)\n }\n }\n\n return material\n }\n\n function buildMaterialNode(node) {\n const materialData = {}\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'ambientIntensity':\n // field not supported\n break\n\n case 'diffuseColor':\n materialData.diffuseColor = new Color(fieldValues[0], fieldValues[1], fieldValues[2])\n break\n\n case 'emissiveColor':\n materialData.emissiveColor = new Color(fieldValues[0], fieldValues[1], fieldValues[2])\n break\n\n case 'shininess':\n materialData.shininess = fieldValues[0]\n break\n\n case 'specularColor':\n materialData.emissiveColor = new Color(fieldValues[0], fieldValues[1], fieldValues[2])\n break\n\n case 'transparency':\n materialData.transparency = fieldValues[0]\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n return materialData\n }\n\n function parseHexColor(hex, textureType, color) {\n let value\n\n switch (textureType) {\n case TEXTURE_TYPE.INTENSITY:\n // Intensity texture: A one-component image specifies one-byte hexadecimal or integer values representing the intensity of the image\n value = parseInt(hex)\n color.r = value\n color.g = value\n color.b = value\n color.a = 1\n break\n\n case TEXTURE_TYPE.INTENSITY_ALPHA:\n // Intensity+Alpha texture: A two-component image specifies the intensity in the first (high) byte and the alpha opacity in the second (low) byte.\n value = parseInt('0x' + hex.substring(2, 4))\n color.r = value\n color.g = value\n color.b = value\n color.a = parseInt('0x' + hex.substring(4, 6))\n break\n\n case TEXTURE_TYPE.RGB:\n // RGB texture: Pixels in a three-component image specify the red component in the first (high) byte, followed by the green and blue components\n color.r = parseInt('0x' + hex.substring(2, 4))\n color.g = parseInt('0x' + hex.substring(4, 6))\n color.b = parseInt('0x' + hex.substring(6, 8))\n color.a = 1\n break\n\n case TEXTURE_TYPE.RGBA:\n // RGBA texture: Four-component images specify the alpha opacity byte after red/green/blue\n color.r = parseInt('0x' + hex.substring(2, 4))\n color.g = parseInt('0x' + hex.substring(4, 6))\n color.b = parseInt('0x' + hex.substring(6, 8))\n color.a = parseInt('0x' + hex.substring(8, 10))\n break\n\n default:\n }\n }\n\n function getTextureType(num_components) {\n let type\n\n switch (num_components) {\n case 1:\n type = TEXTURE_TYPE.INTENSITY\n break\n\n case 2:\n type = TEXTURE_TYPE.INTENSITY_ALPHA\n break\n\n case 3:\n type = TEXTURE_TYPE.RGB\n break\n\n case 4:\n type = TEXTURE_TYPE.RGBA\n break\n\n default:\n }\n\n return type\n }\n\n function buildPixelTextureNode(node) {\n let texture\n let wrapS = RepeatWrapping\n let wrapT = RepeatWrapping\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'image':\n const width = fieldValues[0]\n const height = fieldValues[1]\n const num_components = fieldValues[2]\n\n const textureType = getTextureType(num_components)\n\n const data = new Uint8Array(4 * width * height)\n\n const color = { r: 0, g: 0, b: 0, a: 0 }\n\n for (let j = 3, k = 0, jl = fieldValues.length; j < jl; j++, k++) {\n parseHexColor(fieldValues[j], textureType, color)\n\n const stride = k * 4\n\n data[stride + 0] = color.r\n data[stride + 1] = color.g\n data[stride + 2] = color.b\n data[stride + 3] = color.a\n }\n\n texture = new DataTexture(data, width, height)\n texture.needsUpdate = true\n texture.__type = textureType // needed for material modifications\n break\n\n case 'repeatS':\n if (fieldValues[0] === false) wrapS = ClampToEdgeWrapping\n break\n\n case 'repeatT':\n if (fieldValues[0] === false) wrapT = ClampToEdgeWrapping\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n if (texture) {\n texture.wrapS = wrapS\n texture.wrapT = wrapT\n }\n\n return texture\n }\n\n function buildImageTextureNode(node) {\n let texture\n let wrapS = RepeatWrapping\n let wrapT = RepeatWrapping\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'url':\n const url = fieldValues[0]\n if (url) texture = textureLoader.load(url)\n break\n\n case 'repeatS':\n if (fieldValues[0] === false) wrapS = ClampToEdgeWrapping\n break\n\n case 'repeatT':\n if (fieldValues[0] === false) wrapT = ClampToEdgeWrapping\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n if (texture) {\n texture.wrapS = wrapS\n texture.wrapT = wrapT\n }\n\n return texture\n }\n\n function buildTextureTransformNode(node) {\n const transformData = {\n center: new Vector2(),\n rotation: new Vector2(),\n scale: new Vector2(),\n translation: new Vector2(),\n }\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'center':\n transformData.center.set(fieldValues[0], fieldValues[1])\n break\n\n case 'rotation':\n transformData.rotation = fieldValues[0]\n break\n\n case 'scale':\n transformData.scale.set(fieldValues[0], fieldValues[1])\n break\n\n case 'translation':\n transformData.translation.set(fieldValues[0], fieldValues[1])\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n return transformData\n }\n\n function buildGeometricNode(node) {\n return node.fields[0].values\n }\n\n function buildWorldInfoNode(node) {\n const worldInfo = {}\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'title':\n worldInfo.title = fieldValues[0]\n break\n\n case 'info':\n worldInfo.info = fieldValues\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n return worldInfo\n }\n\n function buildIndexedFaceSetNode(node) {\n let color, coord, normal, texCoord\n let ccw = true,\n solid = true,\n creaseAngle = 0\n let colorIndex, coordIndex, normalIndex, texCoordIndex\n let colorPerVertex = true,\n normalPerVertex = true\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'color':\n const colorNode = fieldValues[0]\n\n if (colorNode !== null) {\n color = getNode(colorNode)\n }\n\n break\n\n case 'coord':\n const coordNode = fieldValues[0]\n\n if (coordNode !== null) {\n coord = getNode(coordNode)\n }\n\n break\n\n case 'normal':\n const normalNode = fieldValues[0]\n\n if (normalNode !== null) {\n normal = getNode(normalNode)\n }\n\n break\n\n case 'texCoord':\n const texCoordNode = fieldValues[0]\n\n if (texCoordNode !== null) {\n texCoord = getNode(texCoordNode)\n }\n\n break\n\n case 'ccw':\n ccw = fieldValues[0]\n break\n\n case 'colorIndex':\n colorIndex = fieldValues\n break\n\n case 'colorPerVertex':\n colorPerVertex = fieldValues[0]\n break\n\n case 'convex':\n // field not supported\n break\n\n case 'coordIndex':\n coordIndex = fieldValues\n break\n\n case 'creaseAngle':\n creaseAngle = fieldValues[0]\n break\n\n case 'normalIndex':\n normalIndex = fieldValues\n break\n\n case 'normalPerVertex':\n normalPerVertex = fieldValues[0]\n break\n\n case 'solid':\n solid = fieldValues[0]\n break\n\n case 'texCoordIndex':\n texCoordIndex = fieldValues\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n if (coordIndex === undefined) {\n console.warn('THREE.VRMLLoader: Missing coordIndex.')\n\n return new BufferGeometry() // handle VRML files with incomplete geometry definition\n }\n\n const triangulatedCoordIndex = triangulateFaceIndex(coordIndex, ccw)\n\n let colorAttribute\n let normalAttribute\n let uvAttribute\n\n if (color) {\n if (colorPerVertex === true) {\n if (colorIndex && colorIndex.length > 0) {\n // if the colorIndex field is not empty, then it is used to choose colors for each vertex of the IndexedFaceSet.\n\n const triangulatedColorIndex = triangulateFaceIndex(colorIndex, ccw)\n colorAttribute = computeAttributeFromIndexedData(triangulatedCoordIndex, triangulatedColorIndex, color, 3)\n } else {\n // if the colorIndex field is empty, then the coordIndex field is used to choose colors from the Color node\n\n colorAttribute = toNonIndexedAttribute(triangulatedCoordIndex, new Float32BufferAttribute(color, 3))\n }\n } else {\n if (colorIndex && colorIndex.length > 0) {\n // if the colorIndex field is not empty, then they are used to choose one color for each face of the IndexedFaceSet\n\n const flattenFaceColors = flattenData(color, colorIndex)\n const triangulatedFaceColors = triangulateFaceData(flattenFaceColors, coordIndex)\n colorAttribute = computeAttributeFromFaceData(triangulatedCoordIndex, triangulatedFaceColors)\n } else {\n // if the colorIndex field is empty, then the color are applied to each face of the IndexedFaceSet in order\n\n const triangulatedFaceColors = triangulateFaceData(color, coordIndex)\n colorAttribute = computeAttributeFromFaceData(triangulatedCoordIndex, triangulatedFaceColors)\n }\n }\n }\n\n if (normal) {\n if (normalPerVertex === true) {\n // consider vertex normals\n\n if (normalIndex && normalIndex.length > 0) {\n // if the normalIndex field is not empty, then it is used to choose normals for each vertex of the IndexedFaceSet.\n\n const triangulatedNormalIndex = triangulateFaceIndex(normalIndex, ccw)\n normalAttribute = computeAttributeFromIndexedData(\n triangulatedCoordIndex,\n triangulatedNormalIndex,\n normal,\n 3,\n )\n } else {\n // if the normalIndex field is empty, then the coordIndex field is used to choose normals from the Normal node\n\n normalAttribute = toNonIndexedAttribute(triangulatedCoordIndex, new Float32BufferAttribute(normal, 3))\n }\n } else {\n // consider face normals\n\n if (normalIndex && normalIndex.length > 0) {\n // if the normalIndex field is not empty, then they are used to choose one normal for each face of the IndexedFaceSet\n\n const flattenFaceNormals = flattenData(normal, normalIndex)\n const triangulatedFaceNormals = triangulateFaceData(flattenFaceNormals, coordIndex)\n normalAttribute = computeAttributeFromFaceData(triangulatedCoordIndex, triangulatedFaceNormals)\n } else {\n // if the normalIndex field is empty, then the normals are applied to each face of the IndexedFaceSet in order\n\n const triangulatedFaceNormals = triangulateFaceData(normal, coordIndex)\n normalAttribute = computeAttributeFromFaceData(triangulatedCoordIndex, triangulatedFaceNormals)\n }\n }\n } else {\n // 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\n\n normalAttribute = computeNormalAttribute(triangulatedCoordIndex, coord, creaseAngle)\n }\n\n if (texCoord) {\n // texture coordinates are always defined on vertex level\n\n if (texCoordIndex && texCoordIndex.length > 0) {\n // if the texCoordIndex field is not empty, then it is used to choose texture coordinates for each vertex of the IndexedFaceSet.\n\n const triangulatedTexCoordIndex = triangulateFaceIndex(texCoordIndex, ccw)\n uvAttribute = computeAttributeFromIndexedData(triangulatedCoordIndex, triangulatedTexCoordIndex, texCoord, 2)\n } else {\n // if the texCoordIndex field is empty, then the coordIndex array is used to choose texture coordinates from the TextureCoordinate node\n\n uvAttribute = toNonIndexedAttribute(triangulatedCoordIndex, new Float32BufferAttribute(texCoord, 2))\n }\n }\n\n const geometry = new BufferGeometry()\n const positionAttribute = toNonIndexedAttribute(triangulatedCoordIndex, new Float32BufferAttribute(coord, 3))\n\n geometry.setAttribute('position', positionAttribute)\n geometry.setAttribute('normal', normalAttribute)\n\n // optional attributes\n\n if (colorAttribute) geometry.setAttribute('color', colorAttribute)\n if (uvAttribute) geometry.setAttribute('uv', uvAttribute)\n\n // \"solid\" influences the material so let's store it for later use\n\n geometry._solid = solid\n geometry._type = 'mesh'\n\n return geometry\n }\n\n function buildIndexedLineSetNode(node) {\n let color, coord\n let colorIndex, coordIndex\n let colorPerVertex = true\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'color':\n const colorNode = fieldValues[0]\n\n if (colorNode !== null) {\n color = getNode(colorNode)\n }\n\n break\n\n case 'coord':\n const coordNode = fieldValues[0]\n\n if (coordNode !== null) {\n coord = getNode(coordNode)\n }\n\n break\n\n case 'colorIndex':\n colorIndex = fieldValues\n break\n\n case 'colorPerVertex':\n colorPerVertex = fieldValues[0]\n break\n\n case 'coordIndex':\n coordIndex = fieldValues\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n // build lines\n\n let colorAttribute\n\n const expandedLineIndex = expandLineIndex(coordIndex) // create an index for three.js's linesegment primitive\n\n if (color) {\n if (colorPerVertex === true) {\n if (colorIndex.length > 0) {\n // if the colorIndex field is not empty, then one color is used for each polyline of the IndexedLineSet.\n\n const expandedColorIndex = expandLineIndex(colorIndex) // compute colors for each line segment (rendering primitve)\n colorAttribute = computeAttributeFromIndexedData(expandedLineIndex, expandedColorIndex, color, 3) // compute data on vertex level\n } else {\n // if the colorIndex field is empty, then the colors are applied to each polyline of the IndexedLineSet in order.\n\n colorAttribute = toNonIndexedAttribute(expandedLineIndex, new Float32BufferAttribute(color, 3))\n }\n } else {\n if (colorIndex.length > 0) {\n // if the colorIndex field is not empty, then colors are applied to each vertex of the IndexedLineSet\n\n const flattenLineColors = flattenData(color, colorIndex) // compute colors for each VRML primitve\n const expandedLineColors = expandLineData(flattenLineColors, coordIndex) // compute colors for each line segment (rendering primitve)\n colorAttribute = computeAttributeFromLineData(expandedLineIndex, expandedLineColors) // compute data on vertex level\n } else {\n // if the colorIndex field is empty, then the coordIndex field is used to choose colors from the Color node\n\n const expandedLineColors = expandLineData(color, coordIndex) // compute colors for each line segment (rendering primitve)\n colorAttribute = computeAttributeFromLineData(expandedLineIndex, expandedLineColors) // compute data on vertex level\n }\n }\n }\n\n //\n\n const geometry = new BufferGeometry()\n\n const positionAttribute = toNonIndexedAttribute(expandedLineIndex, new Float32BufferAttribute(coord, 3))\n geometry.setAttribute('position', positionAttribute)\n\n if (colorAttribute) geometry.setAttribute('color', colorAttribute)\n\n geometry._type = 'line'\n\n return geometry\n }\n\n function buildPointSetNode(node) {\n let color, coord\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'color':\n const colorNode = fieldValues[0]\n\n if (colorNode !== null) {\n color = getNode(colorNode)\n }\n\n break\n\n case 'coord':\n const coordNode = fieldValues[0]\n\n if (coordNode !== null) {\n coord = getNode(coordNode)\n }\n\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n const geometry = new BufferGeometry()\n\n geometry.setAttribute('position', new Float32BufferAttribute(coord, 3))\n if (color) geometry.setAttribute('color', new Float32BufferAttribute(color, 3))\n\n geometry._type = 'points'\n\n return geometry\n }\n\n function buildBoxNode(node) {\n const size = new Vector3(2, 2, 2)\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'size':\n size.x = fieldValues[0]\n size.y = fieldValues[1]\n size.z = fieldValues[2]\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n const geometry = new BoxGeometry(size.x, size.y, size.z)\n\n return geometry\n }\n\n function buildConeNode(node) {\n let radius = 1,\n height = 2,\n openEnded = false\n\n const fields = node.fields\n\n for (let i = 0, l = fields.length; i < l; i++) {\n const field = fields[i]\n const fieldName = field.name\n const fieldValues = field.values\n\n switch (fieldName) {\n case 'bottom':\n openEnded = !fieldValues[0]\n break\n\n case 'bottomRadius':\n radius = fieldValues[0]\n break\n\n case 'height':\n height = fieldValues[0]\n break\n\n case 'side':\n // field not supported\n break\n\n default:\n console.warn('THREE.VRMLLoader: Unknown field:', fieldName)\n break\n }\n }\n\n const geometry = new ConeGeometry(radius, height, 16, 1, openEnded)\n\n return geometry\