瀏覽代碼

VRMLLoader: New implementation

Mugen87 6 年之前
父節點
當前提交
ecda989fcf

File diff suppressed because it is too large
+ 1 - 0
examples/js/libs/chevrotain.min.js


+ 1996 - 762
examples/js/loaders/VRMLLoader.js

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

+ 2006 - 765
examples/jsm/loaders/VRMLLoader.js

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

+ 86 - 0
examples/models/vrml/test/creaseAngle.wrl

@@ -0,0 +1,86 @@
+#VRML V2.0 utf8
+
+#Created by Cinema 4D
+
+DEF Plane Transform {
+  children [ 
+    Shape {
+      appearance DEF MAT_Mat Appearance {
+        material Material {
+          ambientIntensity 0.2
+          diffuseColor 0.33 0.33 0.33
+          specularColor 0.025 0.025 0.025
+          emissiveColor 0 0 0
+          shininess 0.025
+          transparency 0
+        }
+      }
+      geometry DEF FACESET_Plane IndexedFaceSet {
+        ccw FALSE
+        creaseAngle 0.698132
+        coord Coordinate {
+          point [ -200 0 -200,-200 0 0,-200 0 200,0 0 -200,0 0 0,
+            0 0 200,200 0 -200,200 0 0,200 0 200
+          ]
+        }
+        texCoord TextureCoordinate {
+          point [ 0 0,
+            0.5 0,1 0,0 0.5,0.5 0.5,1 0.5,
+            0 1,0.5 1,1 1
+          ]
+        }
+        coordIndex [ 0,3,4,1,-1,1,4,5,2,-1,3,6,7,4,-1,
+          4,7,8,5,-1
+        ]
+        texCoordIndex [ 0,3,4,1,-1,1,4,5,2,-1,3,6,7,4,-1,
+          4,7,8,5,-1
+        ]
+      }
+    }
+  ]
+}
+DEF Null Transform {
+  translation 0 -200 0
+  children [ 
+    DEF Platonic Transform {
+      translation 0 400 0
+      children [ 
+        Shape {
+          appearance USE MAT_Mat
+          geometry DEF FACESET_Platonic IndexedFaceSet {
+            ccw FALSE
+            creaseAngle 3.141593
+            coord Coordinate {
+              point [ -85.065216 52.573195 0,-85.065216 -52.573195 0,-52.573195 0 85.065216,52.573195 0 85.065216,85.065216 52.573195 0,
+                85.065216 -52.573195 0,52.573195 0 -85.065216,-52.573195 0 -85.065216,0 85.065216 52.573195,0 85.065216 -52.573195,
+                0 -85.065216 -52.573195,0 -85.065216 52.573195
+              ]
+            }
+            texCoord TextureCoordinate {
+              point [ 0.5 0,1 1,1 1,
+                0 1,0.5 0,0 1,0.5 0,1 1,
+                0.5 0,0 1,1 1,0 1,0.5 0,
+                1 1,0.5 0,1 1,0 1,1 1,
+                0 1,0.5 0,1 1,0 1,0.5 0,
+                0 1,1 1,0 1,1 1,0.5 0,
+                1 1,0.5 0,1 1,0 1
+              ]
+            }
+            coordIndex [ 9,8,0,-1,8,2,0,-1,8,3,2,-1,8,4,3,-1,
+              8,9,4,-1,4,9,6,-1,6,9,7,-1,9,0,7,-1,
+              11,10,1,-1,2,11,1,-1,2,3,11,-1,3,5,11,-1,
+              5,10,11,-1,5,6,10,-1,6,7,10,-1,7,1,10,-1,
+              1,7,0,-1,2,1,0,-1,4,5,3,-1,4,6,5,-1
+            ]
+            texCoordIndex [ 25,24,0,-1,23,7,0,-1,23,10,6,-1,23,13,8,-1,
+              23,26,12,-1,11,26,19,-1,18,26,22,-1,25,1,22,-1,
+              31,28,4,-1,5,30,4,-1,5,10,29,-1,9,15,29,-1,
+              16,28,29,-1,16,17,27,-1,18,20,27,-1,21,2,27,-1,
+              3,20,0,-1,5,2,0,-1,11,15,8,-1,11,17,14,-1
+            ]
+          }
+        }
+      ]
+    }
+  ]
+}

+ 120 - 0
examples/models/vrml/test/crystal.wrl

@@ -0,0 +1,120 @@
+#VRML V2.0 utf8
+WorldInfo { title "created By KrystalShaper"}
+NavigationInfo { type "EXAMINE"}
+DEF BKG Background { skyColor [ 1.0 1.0 1.0 ] }
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 0.0 1.0 1.0  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+-0.6423 1.0 0.3744 ,
+0.5456 1.0 0.1594 ,
+0.5864 1.0 0.0723 ,
+0.6423 1.0 -0.3744 ,
+-0.5456 1.0 -0.1594 ,
+-0.5864 1.0 -0.0723 ,
+]} coordIndex[ 0,1,2,3,4,5, -1] }}]}
+
+Transform { children [ Shape{ 
+appearance Appearance{material Material{diffuseColor 0.0 1.0 1.0  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+-0.5864 -1.0 -0.0723 ,
+-0.5456 -1.0 -0.1594 ,
+0.6423 -1.0 -0.3744 ,
+0.5864 -1.0 0.0723 ,
+0.5456 -1.0 0.1594 ,
+-0.6423 -1.0 0.3744 ,
+]} coordIndex[ 0,1,2,3,4,5, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 0.0 0.0 1.0  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+-1.2103 0.0 0.4772 ,
+-0.6423 -1.0 0.3744 ,
+0.5456 -1.0 0.1594 ,
+1.1135 0.0 0.0566 ,
+0.5456 1.0 0.1594 ,
+-0.6423 1.0 0.3744 ,
+]} coordIndex[ 0,1,2,3,4,5, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 0.0 0.0 1.0  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+1.2103 0.0 -0.4772 ,
+0.6423 -1.0 -0.3744 ,
+-0.5456 -1.0 -0.1594 ,
+-1.1135 0.0 -0.0566 ,
+-0.5456 1.0 -0.1594 ,
+0.6423 1.0 -0.3744 ,
+]} coordIndex[ 0,1,2,3,4,5, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 0.275 1.0 0.333  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+0.6423 1.0 -0.3744 ,
+0.5864 1.0 0.0723 ,
+1.1544 0.0 -0.0305 ,
+1.2103 0.0 -0.4772 ,
+]} coordIndex[ 0,1,2,3, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 0.275 1.0 0.333  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+-0.6423 1.0 0.3744 ,
+-0.5864 1.0 -0.0723 ,
+-1.1544 0.0 0.0305 ,
+-1.2103 0.0 0.4772 ,
+]} coordIndex[ 0,1,2,3, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 0.275 1.0 0.333  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+-1.2103 0.0 0.4772 ,
+-1.1544 0.0 0.0305 ,
+-0.5864 -1.0 -0.0723 ,
+-0.6423 -1.0 0.3744 ,
+]} coordIndex[ 0,1,2,3, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 0.275 1.0 0.333  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+1.2103 0.0 -0.4772 ,
+1.1544 0.0 -0.0305 ,
+0.5864 -1.0 0.0723 ,
+0.6423 -1.0 -0.3744 ,
+]} coordIndex[ 0,1,2,3, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 1.0 0.275 0.373  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+1.1135 0.0 0.0566 ,
+1.1544 0.0 -0.0305 ,
+0.5864 1.0 0.0723 ,
+0.5456 1.0 0.1594 ,
+]} coordIndex[ 0,1,2,3, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 1.0 0.275 0.373  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+-1.1135 0.0 -0.0566 ,
+-1.1544 0.0 0.0305 ,
+-0.5864 1.0 -0.0723 ,
+-0.5456 1.0 -0.1594 ,
+]} coordIndex[ 0,1,2,3, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 1.0 0.275 0.373  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+-0.5456 -1.0 -0.1594 ,
+-0.5864 -1.0 -0.0723 ,
+-1.1544 0.0 0.0305 ,
+-1.1135 0.0 -0.0566 ,
+]} coordIndex[ 0,1,2,3, -1] }}]}
+
+Transform { children [ Shape{
+appearance Appearance{material Material{diffuseColor 1.0 0.275 0.373  }}
+geometry IndexedFaceSet{ coord Coordinate{ point[
+0.5456 -1.0 0.1594 ,
+0.5864 -1.0 0.0723 ,
+1.1544 0.0 -0.0305 ,
+1.1135 0.0 0.0566 ,
+]} coordIndex[ 0,1,2,3, -1] }}]}

+ 33 - 0
examples/models/vrml/test/lines.wrl

@@ -0,0 +1,33 @@
+#VRML V2.0 utf8
+#IndexedLineSet example
+Shape {
+	geometry IndexedLineSet {
+		coord Coordinate {
+			point [
+				-1.0 -1.0 0.0, #vertex 0
+				1.0 1.0 0.0, #vertex 1
+				1.0 -1.0 0.0, #vertex 2
+			]
+		}
+		colorPerVertex FALSE
+		color Color {
+			color [
+				1.0 0.0 0.0, #red
+				0.0 1.0 0.0, #green
+				0.0 0.0 1.0, #blue
+			]
+		}
+		coordIndex [
+			#red line
+			0, 1, -1,
+			#green line
+			1, 2, -1,
+			#blue line
+			2, 0, -1
+		]
+		colorIndex [
+			2, 1, 0
+		]	
+	}
+}
+

二進制
examples/models/vrml/test/map.gif


+ 352 - 0
examples/models/vrml/test/meshWithLines.wrl

@@ -0,0 +1,352 @@
+#VRML V2.0 utf8
+
+WorldInfo {
+	info [ "File created using CATIA" ]
+}
+NavigationInfo {
+	type [ "EXAMINE" , "WALK" , "FLY" ]
+}
+Background {
+	skyColor [ 0 0 0 ]
+}
+Viewpoint {
+	position      0.411502 0.183945 0.216403
+	orientation   0.326678 0.502925 0.800218 2.185925
+	fieldOfView   0.471225
+	description   "Main Viewpoint"
+}
+Viewpoint {
+	position      0.288675 0.288675 0.288675
+	orientation   0.187053 0.451587 0.872399 2.448076
+	fieldOfView   0.471225
+	description   "Iso View"
+}
+Viewpoint {
+	position      0.500000 0.000000 0.000000
+	orientation   0.577350 0.577350 0.577350 2.094395
+	fieldOfView   0.471225
+	description   "Front View"
+}
+Viewpoint {
+	position      -0.500000 0.000000 0.000000
+	orientation   0.577350 -0.577350 -0.577350 2.094395
+	fieldOfView   0.471225
+	description   "Back View"
+}
+Viewpoint {
+	position      0.000000 -0.500000 0.000000
+	orientation   1.000000 -0.000173 0.000173 1.570796
+	fieldOfView   0.471225
+	description   "Left View"
+}
+Viewpoint {
+	position      0.000000 0.500000 0.000000
+	orientation   -0.000122 -0.707107 -0.707107 3.141348
+	fieldOfView   0.471225
+	description   "Right View"
+}
+Viewpoint {
+	position      0.000000 0.000000 0.500000
+	orientation   0.000000 0.000000 1.000000 1.570796
+	fieldOfView   0.471225
+	description   "Top View"
+}
+Viewpoint {
+	position      0.000000 0.000000 -0.500000
+	orientation   0.707107 0.707107 0.000000 3.141593
+	fieldOfView   0.471225
+	description   "Bottom View"
+}
+Transform {
+	scale         0.001 0.001 0.001
+	children [
+Transform {
+	children [
+Transform {
+	children [
+Group {
+	children [
+	]
+}
+Group {
+	children [
+DEF _000000002D59E720 Group {
+	children [
+Shape {
+appearance Appearance {
+	material DEF _material0 Material {
+		diffuseColor  0.866667 0.666667 0.266667
+	}
+}
+geometry IndexedFaceSet {
+solid FALSE
+coord DEF _coord000000002EB30170 Coordinate {
+	point	[
+		 -12 -13.5 -1.8,
+		 -12 -13.5 -0,
+		 -12 13.5 -1.8,
+		 -12 13.5 -0,
+		 110 -13.5 -1.8,
+		 110 -13.5 0,
+		 110 13.5 -1.8,
+		 110 13.5 0,
+		]
+	}
+coordIndex [
+ 7,5,6,-1,
+ 5,4,6,-1,
+]
+
+}
+}
+Shape {
+appearance Appearance {
+material USE _material0
+}
+geometry IndexedFaceSet {
+solid FALSE
+coord USE _coord000000002EB30170 
+coordIndex [
+ 6,2,7,-1,
+ 2,3,7,-1,
+]
+
+}
+}
+Shape {
+appearance Appearance {
+material USE _material0
+}
+geometry IndexedFaceSet {
+solid FALSE
+coord USE _coord000000002EB30170 
+coordIndex [
+ 4,0,6,-1,
+ 0,2,6,-1,
+]
+
+}
+}
+Shape {
+appearance Appearance {
+material USE _material0
+}
+geometry IndexedFaceSet {
+solid FALSE
+coord USE _coord000000002EB30170 
+coordIndex [
+ 5,1,4,-1,
+ 1,0,4,-1,
+]
+
+}
+}
+Shape {
+appearance Appearance {
+material USE _material0
+}
+geometry IndexedFaceSet {
+solid FALSE
+coord USE _coord000000002EB30170 
+coordIndex [
+ 7,3,5,-1,
+ 3,1,5,-1,
+]
+
+}
+}
+Shape {
+appearance Appearance {
+material USE _material0
+}
+geometry IndexedFaceSet {
+solid FALSE
+coord USE _coord000000002EB30170 
+coordIndex [
+ 1,3,0,-1,
+ 3,2,0,-1,
+]
+
+}
+}
+Shape {
+appearance Appearance {
+	material DEF _material1 Material {
+		emissiveColor 0 0 0
+	}
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 -12 -13.5 -1.8,
+		 -12 -13.5 -0,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 -12 -13.5 -0,
+		 -12 13.5 -0,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 -12 13.5 -0,
+		 -12 13.5 -1.8,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 -12 13.5 -1.8,
+		 -12 -13.5 -1.8,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 110 -13.5 0,
+		 110 13.5 0,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 110 13.5 0,
+		 -12 13.5 -0,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 -12 -13.5 -0,
+		 110 -13.5 0,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 110 -13.5 -1.8,
+		 110 -13.5 0,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 -12 -13.5 -1.8,
+		 110 -13.5 -1.8,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 110 13.5 -1.8,
+		 110 -13.5 -1.8,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 -12 13.5 -1.8,
+		 110 13.5 -1.8,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+Shape {
+appearance Appearance {
+material USE _material1
+}
+geometry IndexedLineSet {
+	coord Coordinate {
+point	[
+		 110 13.5 0,
+		 110 13.5 -1.8,
+		]
+}
+	coordIndex	[0 1  -1 ]
+}
+}
+	]
+}
+	]
+}
+	]
+}
+	]
+}
+	]
+}
+

+ 71 - 0
examples/models/vrml/test/meshWithTexture.wrl

@@ -0,0 +1,71 @@
+#VRML V2.0 utf8
+
+DEF Plane001 Transform {
+  translation -6.849 25.21 0
+  rotation -1 0 0 -1.571
+  children [
+    Shape {
+      appearance Appearance {
+        material Material {
+          diffuseColor 0.5882 0.5882 0.5882
+          ambientIntensity 1.0
+          specularColor 0 0 0
+          shininess 0.145
+          transparency 0
+        }
+        texture ImageTexture {
+          url "map.gif"
+        }
+      }
+      geometry DEF Plane001FACES IndexedFaceSet {
+        ccw TRUE
+        solid TRUE
+        coord DEF Plane001COORD Coordinate { point [
+          -20.27 0 21.37, -10.14 0 21.37, 0 0 21.37, 10.14 0 21.37,
+          20.27 0 21.37, -20.27 0 10.68, -10.14 0 10.68, 0 0 10.68,
+          10.14 0 10.68, 20.27 0 10.68, -20.27 0 0, -10.14 0 0,
+          0 0 0, 10.14 0 0, 20.27 0 0, -20.27 0 -10.68, -10.14 0 -10.68,
+          0 0 -10.68, 10.14 0 -10.68, 20.27 0 -10.68, -20.27 0 -21.37,
+          -10.14 0 -21.37, 0 0 -21.37, 10.14 0 -21.37, 20.27 0 -21.37]
+        }
+        normal Normal { vector [
+          0 1 0, ] }
+        normalPerVertex TRUE
+        texCoord DEF Plane001TEXCOORD TextureCoordinate { point [
+          0 0, 0.25 0, 0.5 0, 0.75 0, 1 0, 0 0, 0.25 0, 0.5 0,
+          0.75 0, 1 0, 0 0, 0.25 0, 0.5 0, 0.75 0, 1 0, 0 0.25,
+          0.25 0.25, 0.5 0.25, 0.75 0.25, 1 0.25, 0 0.5, 0.25 0.5,
+          0.5 0.5, 0.75 0.5, 1 0.5, 0 0.75, 0.25 0.75, 0.5 0.75,
+          0.75 0.75, 1 0.75, 0 1, 0.25 1, 0.5 1, 0.75 1, 1 1]
+        }
+        coordIndex [
+          5, 0, 6, -1, 1, 6, 0, -1, 6, 1, 7, -1, 2, 7, 1, -1, 7, 2, 8, -1,
+          3, 8, 2, -1, 8, 3, 9, -1, 4, 9, 3, -1, 10, 5, 11, -1,
+          6, 11, 5, -1, 11, 6, 12, -1, 7, 12, 6, -1, 12, 7, 13, -1,
+          8, 13, 7, -1, 13, 8, 14, -1, 9, 14, 8, -1, 15, 10, 16, -1,
+          11, 16, 10, -1, 16, 11, 17, -1, 12, 17, 11, -1, 17, 12, 18, -1,
+          13, 18, 12, -1, 18, 13, 19, -1, 14, 19, 13, -1, 20, 15, 21, -1,
+          16, 21, 15, -1, 21, 16, 22, -1, 17, 22, 16, -1, 22, 17, 23, -1,
+          18, 23, 17, -1, 23, 18, 24, -1, 19, 24, 18, -1]
+        texCoordIndex [
+          15, 10, 16, -1, 11, 16, 10, -1, 16, 11, 17, -1, 12, 17, 11, -1,
+          17, 12, 18, -1, 13, 18, 12, -1, 18, 13, 19, -1, 14, 19, 13, -1,
+          20, 15, 21, -1, 16, 21, 15, -1, 21, 16, 22, -1, 17, 22, 16, -1,
+          22, 17, 23, -1, 18, 23, 17, -1, 23, 18, 24, -1, 19, 24, 18, -1,
+          25, 20, 26, -1, 21, 26, 20, -1, 26, 21, 27, -1, 22, 27, 21, -1,
+          27, 22, 28, -1, 23, 28, 22, -1, 28, 23, 29, -1, 24, 29, 23, -1,
+          30, 25, 31, -1, 26, 31, 25, -1, 31, 26, 32, -1, 27, 32, 26, -1,
+          32, 27, 33, -1, 28, 33, 27, -1, 33, 28, 34, -1, 29, 34, 28, -1]
+        normalIndex [
+          0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
+          0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
+          0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
+          0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
+          0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
+          0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
+          0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0,
+          0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, ]
+        }
+    }
+  ]
+}

+ 21 - 0
examples/models/vrml/test/points.wrl

@@ -0,0 +1,21 @@
+#VRML V2.0 utf8
+#PointSet example
+
+Shape {
+	geometry PointSet {
+		coord Coordinate { 
+			point [
+				-1.0 -1.0 0.0,
+				1.0 1.0 0.0,
+				0.0 0.0 0.0,
+			]
+		}
+		color Color {
+			color [
+				1.0 0.0 0.0,
+				0.0 1.0 0.0,
+				0.0 0.0 1.0,
+			]
+		}
+	}
+}

+ 1 - 0
examples/webgl_loader_vrml.html

@@ -26,6 +26,7 @@
 
 
 		<script src="js/controls/OrbitControls.js"></script>
 		<script src="js/controls/OrbitControls.js"></script>
 
 
+		<script src="js/libs/chevrotain.min.js"></script>
 		<script src="js/loaders/VRMLLoader.js"></script>
 		<script src="js/loaders/VRMLLoader.js"></script>
 
 
 		<script src="js/WebGL.js"></script>
 		<script src="js/WebGL.js"></script>

Some files were not shown because too many files changed in this diff