mrdoob/three.js

View on GitHub
examples/jsm/loaders/VRMLLoader.js

Summary

Maintainability
F
1 mo
Test Coverage
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 '../libs/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 };