VMRLLoader color issue

Hi.

I have a very strange problem wiht VMRLLoader.

I have a WRL model in which I have only 2 materials with only 2 colors in my scene but when I load the WRL file I see lots of colors.

This are the MAterial definition of this 2 materials:
material DEF _material0 Material {
diffuseColor 0.396078 0.435294 0.470588
}
material DEF _material1 Material {
diffuseColor 0 1 1
transparency 0.5
}

But when I load the file I see more than 20 colors.

It is really strange.

Can you please share the entire VRML asset in this thread?

Here you can see:
test.wrl (5.3 KB)

In this model there are only 2 material|colors:

    material DEF _material0 Material {
            diffuseColor  0.866667 0.666667 0.266667
    }
    material DEF _material1 Material {
            emissiveColor 0 0 0
    }

And I see this:

I’ve debugged the issue and for some reasons the loader is not able to parse the material definitions in the VRML asset. Hence, it assigns to each mesh a new material with a random color.

TBH, the status of VRMLLoader is not satisfying since the way the loader parses a WRL file is error-prone. Besides, many definitions like IndexedLineSet are not supported so far. I suggest you try to use a different 3D format if possible. VRMLLoader needs a rewrite and I don’t think this will be done in the near future because the format is so old.

Hi Mugen.

Don’t worry, I will make my own loader.

Working with WRL is a must. Our files come from CATIA V4, V5.

Our files have always the same information, no animations, no textures, no basic geometries. They contain only PointSet, IndexedLineSet and IndexedFaceSet.

In fact, I have nearly finished it and it works quite well.

Best regards.

How are you parsing WRL files? Most issues in VRMLLoader are parser related. It would be great if we could improve this part of the loader.

Hi Michael.

I am following a order pattern. In fact my WRL files have always the same structure:

Here my code:

     function LoadVMRL(inFile) {
		var fileArray = fs.readFileSync(inFile, 'utf8').toString().split("\r\n");
		for (var i=0; i<fileArray.length; i++) {
			var lineText  = fileArray[i].trim().replace(/[\t, ,\,]+/g, ' ').replace(/diffuseColor|emissiveColor/g, 'colorMaterial');
			var lineFields = lineText.split(" ");
			var firstField = lineFields[0];
			switch (firstField) {
				case "material": // {}
					inMaterial = true;
					materialName  = lineFields[2].replace('_', '');
					if (geometryCount > 0) {
						createGeometry(geometryInfo);
					}
					geometryInfo = {
						name: materialName, // numero de mesh
						type: undefined, // PointSet, IndexedLineSet, IndexedFaceSet
						color: undefined, // color del material
						position: [], // arrayOf vertex need to compute normals
						index: [] // array of Index
					}
					// check if material is being defined or already exists:
					if (lineFields[1] == "DEF") {
						// Material being defined
						materialArray[materialName] = {
							name: materialName,
							color: undefined
						}
					} else {
						geometryInfo.color = materialArray[materialName];
					}
					geometryCount += 1;
					break;
				case "colorMaterial":
					materialArray[materialName] = [parseFloat(lineFields[1]), parseFloat(lineFields[2]), parseFloat(lineFields[3])];
					geometryInfo.color = materialArray[materialName];
					break;
				case "geometry":
					geometryInfo.type = lineFields[1];
					break;
				case "}":
					if (inMaterial == true) {
						inMaterial = false;
					}
					break;
				case "]":
					if (inPoint == true) {
						inPoint = false;
					} else if (inCoordIndex == true) {
						inCoordIndex = false;
					}
					break;
				case "point":
					inPoint = true;
					break;
				case "coordIndex":
					inCoordIndex = true;
					break;
				default:
					// Any other data
					if (isNumber(firstField) == true) {
						// numeric data, find if we are in Point or coordIndex Section I always take the 3 first values parsed to Float
						if (inPoint == true){
							geometryInfo.position.push(parseFloat(lineFields[0]));
							geometryInfo.position.push(parseFloat(lineFields[1]));
							geometryInfo.position.push(parseFloat(lineFields[2]));
						} else if (inCoordIndex == true) {
							geometryInfo.index.push(parseFloat(lineFields[0]));
							geometryInfo.index.push(parseFloat(lineFields[1]));
							geometryInfo.index.push(parseFloat(lineFields[2]));
						}
					}
					break;
			}
		}
		
		// types: PointSet, IndexedLineSet, IndexedFaceSet:
		var name = getFileName(outFile).replace('.glb', '');
		var scene2 = new THREE.Scene();
		scene2.name =  name;
		var object3d = new THREE.Object3D();
		object3d.name =  "O_" + name;
		scene2.add(object3d);

		if (geometryArray["PointSet"] != undefined) {
			var mergedPointGeos = mergeBufferGeometries(geometryArray["PointSet"]);
			var pointMesh = new THREE.Points(mergedPointGeos);
			pointMesh.name = "P_" + name;
			object3d.add(pointMesh)
		}
		if (geometryArray["IndexedLineSet"] != undefined) {
			var mergedLineGeos = mergeBufferGeometries(geometryArray["IndexedLineSet"]);
			var lineMesh = new THREE.LineSegments(mergedLineGeos);
			lineMesh.name = "L_" + name;
			object3d.add(lineMesh)
			console.log(lineMesh);
		}
		if (geometryArray["IndexedFaceSet"] != undefined) {
			var mergedMeshGeos = mergeBufferGeometries(geometryArray["IndexedFaceSet"]);
			var faceMesh = new THREE.Mesh(mergedMeshGeos);
			faceMesh.name = "M_" + name;
			object3d.add(faceMesh)
		}
		return (scene2);
	}

I know that is not the best way but being honest with you the current WRLLoader code is very tricky for me

Take in mind that this code is only valid for loading WRL files comming from our CATIA conversion to WRL.

Anyway, I think this way is much more human readable than the current VMRLLoader.js code.

; )

Of course, to enable animations, textures and other WRL available features the code should be changed. I am not either taking the group structure neither the transform section, even it would be very easy to addapt the code.

Best regards

Hi Michael.

For people interested in this very old format, I have given support for LineSegments and Points and also fixed the color bug issue.

I am not used with PRs,if you want to test the code and validate, feel free to use it.

Here the code:

/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.VRMLLoader = function ( manager ) {

	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

};

THREE.VRMLLoader.prototype = {

	constructor: THREE.VRMLLoader,

	// for IndexedFaceSet support
	isRecordingPoints: false,
	isRecordingFaces: false,
	points: [],
	indexes: [],

	// for Background support
	isRecordingAngles: false,
	isRecordingColors: false,
	angles: [],
	colors: [],

	recordingFieldname: null,

	crossOrigin: 'anonymous',

	load: function ( url, onLoad, onProgress, onError ) {

		var scope = this;

		var path = ( scope.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path;

		var loader = new THREE.FileLoader( this.manager );
		loader.setPath( scope.path );
		loader.load( url, function ( text ) {

			onLoad( scope.parse( text, path ) );

		}, onProgress, onError );

	},

	setPath: function ( value ) {

		this.path = value;
		return this;

	},

	setResourcePath: function ( value ) {

		this.resourcePath = value;
		return this;

	},

	setCrossOrigin: function ( value ) {

		this.crossOrigin = value;
		return this;

	},

	parse: function ( data, path ) {

		var scope = this;

		var textureLoader = new THREE.TextureLoader( this.manager );
		textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );

		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;

			/**
			 * 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 direction = ( topDown === true ) ? 1 : - 1;

				var coord = [], A = {}, B = {}, applyColor = false;

				for ( var k = 0; k < angles.length; k ++ ) {

					// push the vector at which the color changes

					var vec = {
						x: direction * ( Math.cos( angles[ k ] ) * radius ),
						y: direction * ( Math.sin( angles[ k ] ) * radius )
					};

					coord.push( vec );

				}

				var index = geometry.index;
				var positionAttribute = geometry.attributes.position;
				var colorAttribute = new THREE.BufferAttribute( new Float32Array( geometry.attributes.position.count * 3 ), 3 );

				var position = new THREE.Vector3();
				var color = new THREE.Color();

				for ( var i = 0; i < index.count; i ++ ) {

					var vertexIndex = index.getX( i );

					position.fromBufferAttribute( positionAttribute, vertexIndex );

					for ( var j = 0; j < colors.length; j ++ ) {

						// linear interpolation between aColor and bColor, calculate proportion
						// A is previous point (angle)

						if ( j === 0 ) {

							A.x = 0;
							A.y = ( topDown === true ) ? radius : - 1 * radius;

						} else {

							A.x = coord[ j - 1 ].x;
							A.y = coord[ j - 1 ].y;

						}

						// B is current point (angle)

						B = coord[ j ];

						if ( B !== undefined ) {

							// p has to be between the points A and B which we interpolate

							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 ];

								// below is simple linear interpolation

								var t = Math.abs( position.y - A.y ) / ( A.y - B.y );

								// 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 );

								colorAttribute.setXYZ( vertexIndex, color.r, color.g, color.b );

							} else {

								var colorIndex = ( topDown === true ) ? colors.length - 1 : 0;
								var c = colors[ colorIndex ];
								colorAttribute.setXYZ( vertexIndex, c.r, c.g, c.b );

							}

						}

					}

				}

				geometry.addAttribute( 'color', colorAttribute );

			}

			var index = [];

			function parseProperty( node, line ) {

				var parts = [], part, property = {}, fieldName;

				/**
				 * Expression for matching relevant information, such as a name or value, but not the separators
				 * @type {RegExp}
				 */
				var regex = /[^\s,\[\]]+/g;

				var point;

				while ( null !== ( part = regex.exec( line ) ) ) {

					parts.push( part[ 0 ] );

				}

				fieldName = parts[ 0 ];


				// trigger several recorders
				switch ( fieldName ) {

					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;

					case 'point':
					case 'vector':
						scope.recordingFieldname = fieldName;
						scope.isRecordingPoints = true;
						scope.points = [];
						break;

					case 'colorIndex':
					case 'coordIndex':
					case 'normalIndex':
					case 'texCoordIndex':
						scope.recordingFieldname = fieldName;
						scope.isRecordingFaces = true;
						scope.indexes = [];
						break;

				}

				if ( scope.isRecordingFaces ) {

					// the parts hold the indexes as strings
					if ( parts.length > 0 ) {

						for ( var ind = 0; ind < parts.length; ind ++ ) {

							// the part should either be positive integer or -1
							if ( ! /(-?\d+)/.test( parts[ ind ] ) ) {

								continue;

							}

							// end of current face
							if ( parts[ ind ] === '-1' ) {

								if ( index.length > 0 ) {

									scope.indexes.push( index );

								}

								// start new one
								index = [];

							} else {

								index.push( parseInt( parts[ ind ] ) );

							}

						}

					}

					// end
					if ( /]/.exec( line ) ) {

						if ( index.length > 0 ) {

							scope.indexes.push( index );

						}

						// start new one
						index = [];

						scope.isRecordingFaces = false;
						node[ scope.recordingFieldname ] = scope.indexes;

					}

				} else if ( scope.isRecordingPoints ) {

					if ( node.nodeType == 'Coordinate' ) {

						while ( null !== ( parts = float3_pattern.exec( line ) ) ) {

							point = {
								x: parseFloat( parts[ 1 ] ),
								y: parseFloat( parts[ 2 ] ),
								z: parseFloat( parts[ 3 ] )
							};

							scope.points.push( point );

						}

					}

					if ( node.nodeType == 'Normal' ) {

  						while ( null !== ( parts = float3_pattern.exec( line ) ) ) {

							point = {
								x: parseFloat( parts[ 1 ] ),
								y: parseFloat( parts[ 2 ] ),
								z: parseFloat( parts[ 3 ] )
							};

							scope.points.push( point );

						}

					}

					if ( node.nodeType == 'TextureCoordinate' ) {

						while ( null !== ( parts = float2_pattern.exec( line ) ) ) {

							point = {
								x: parseFloat( parts[ 1 ] ),
								y: parseFloat( parts[ 2 ] )
							};

							scope.points.push( point );

						}

					}

					// end
					if ( /]/.exec( line ) ) {

						scope.isRecordingPoints = false;
						node.points = scope.points;

					}

				} else if ( scope.isRecordingAngles ) {

					// the parts hold the angles as strings
					if ( parts.length > 0 ) {

						for ( var ind = 0; ind < parts.length; ind ++ ) {

							// the part should be a float
							if ( ! float_pattern.test( parts[ ind ] ) ) {

								continue;

							}

							scope.angles.push( parseFloat( parts[ ind ] ) );

						}

					}

					// end
					if ( /]/.exec( line ) ) {

						scope.isRecordingAngles = false;
						node[ scope.recordingFieldname ] = scope.angles;

					}

				} else if ( scope.isRecordingColors ) {

					while ( null !== ( parts = float3_pattern.exec( line ) ) ) {

						var color = {
							r: parseFloat( parts[ 1 ] ),
							g: parseFloat( parts[ 2 ] ),
							b: parseFloat( parts[ 3 ] )
						};

						scope.colors.push( color );

					}

					// end
					if ( /]/.exec( line ) ) {

						scope.isRecordingColors = false;
						node[ scope.recordingFieldname ] = scope.colors;

					}

				} else if ( parts[ parts.length - 1 ] !== 'NULL' && fieldName !== 'children' ) {

					switch ( fieldName ) {

						case 'diffuseColor':
						case 'emissiveColor':
						case 'specularColor':
						case 'color':

							if ( parts.length !== 4 ) {

								console.warn( 'THREE.VRMLLoader: Invalid color format detected for %s.', fieldName );
								break;

							}

							property = {
								r: parseFloat( parts[ 1 ] ),
								g: parseFloat( parts[ 2 ] ),
								b: parseFloat( parts[ 3 ] )
							};

							break;

						case 'location':
						case 'direction':
						case 'translation':
						case 'scale':
						case 'size':
							if ( parts.length !== 4 ) {

								console.warn( 'THREE.VRMLLoader: Invalid vector format detected for %s.', fieldName );
								break;

							}

							property = {
								x: parseFloat( parts[ 1 ] ),
								y: parseFloat( parts[ 2 ] ),
								z: parseFloat( parts[ 3 ] )
							};

							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;

							}

							property = parseFloat( parts[ 1 ] );

							break;

						case 'rotation':
							if ( parts.length !== 5 ) {

								console.warn( 'THREE.VRMLLoader: Invalid quaternion format detected for %s.', fieldName );
								break;

							}

							property = {
								x: parseFloat( parts[ 1 ] ),
								y: parseFloat( parts[ 2 ] ),
								z: parseFloat( parts[ 3 ] ),
								w: parseFloat( parts[ 4 ] )
							};

							break;

						case 'on':
						case 'ccw':
						case 'solid':
						case 'colorPerVertex':
						case 'convex':
							if ( parts.length !== 2 ) {

								console.warn( 'THREE.VRMLLoader: Invalid format detected for %s.', fieldName );
								break;

							}

							property = parts[ 1 ] === 'TRUE' ? true : false;

							break;

					}

					// VRMLLoader does not support text so it can't process the "string" property yet

					if ( fieldName !== 'string' ) node[ fieldName ] = property;

				}

				return property;

			}

			function iterationCopy(src) {
				let target = {};
				for (let prop in src) {
					if (src.hasOwnProperty(prop)) {
						target[prop] = src[prop];
					}
				}
				return target;
			}

			function getTree( lines ) {
				
				
				var materialArray = [];
				//console.log("lines: " , lines);
				var tree = { 'string': 'Scene', children: [] };
				var current = tree;
				var matches;
				var specification;

				for ( var i = 0; i < lines.length; i ++ ) {

					var comment = '';

					var line = lines[ i ];

					// omit whitespace only lines
					if ( null !== ( /^\s+?$/g.exec( line ) ) ) {

						continue;

					}

					line = line.trim();

					// skip empty lines
					if ( line === '' ) {

						continue;

					}

					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 ];

					}

					if ( matches = /([^\s]*){1}(?:\s+)?{/.exec( line ) ) {

						// first subpattern should match the Node name

						var block = { 'nodeType': matches[ 1 ], 'string': line, 'parent': current, 'children': [], 'comment': comment };
						if (block != undefined) {
							if (block.nodeType == "Material") {
								//console.log("block1", block);
								var materialName =  block.string.split(' ')[2];
								block.name = materialName;
								var materialClone = {};
								for (var prop in block) {
									if (prop != "parent"){
										materialClone[prop] = block[prop];
									}
								}
								//materialClone.name = materialName;
								materialArray[materialName] = materialClone;
							} 
						}
						current.children.push( block );
						
						current = block;

						if ( /}/.exec( line ) ) {

							// example: geometry Box { size 1 1 1 } # all on the same line
							specification = /{(.*)}/.exec( line )[ 1 ];

							// todo: remove once new parsing is complete?
							block.children.push( specification );

							parseProperty( current, specification );

							current = current.parent;

						}

					} else if ( /}/.exec( line ) ) {

						current = current.parent;

					} else if ( matches = /USE/.exec( line ) ) {
						//console.log("matches" , matches);
						var block = { 'nodeType': matches[ 1 ], 'string': line, 'parent': current, 'children': [], 'comment': comment };
						
						var parent = block.parent;
						var materialName = block.string.split(' ')[2];
						block = materialArray[materialName];
						block.parent = current;
						
						//console.log("block2", block);
						
						
						for (idx=0; idx<block.children.length; idx++) {
							var parts = block.children[idx].replace(/ +/g, ' ').split(' ');
							
							var prop = parts[0];
							//console.log("prop: ", prop, "; parts: ", parts);
							switch (prop) {
								case 'diffuseColor':
								case 'emissiveColor':
								case 'specularColor':
								case 'color':
									if ( parts.length !== 4 ) {
										console.warn( '1.- THREE.VRMLLoader: Invalid color format detected');
										break;
									}
									var property = {
										r: parseFloat( parts[ 1 ] ),
										g: parseFloat( parts[ 2 ] ),
										b: parseFloat( parts[ 3 ] )
									};
									block[prop] = property;
									break;
								case 'intensity':
								case 'transparency':
								case 'shininess':
								case 'ambientIntensity':
									if ( parts.length !== 2 ) {
										console.warn( '2.- THREE.VRMLLoader: Invalid single float value specification detected' );
										break;
									}
									property = parseFloat( parts[ 1 ] );
									block[prop] = property;
									break;
							}
						}
						
						current.children.push( block );
						//console.log("block3", block);
					
					} else if ( line !== '' ) {
						//console.log("line:", line);
						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 );

					}

				}
				//console.log("tree: " , tree);
				return tree;
				
			}

			function parseNode( data, parent ) {

				var object;

				if ( typeof data === 'string' ) {

					if ( /USE/.exec( data ) ) {

						var defineKey = /USE\s+?([^\s]+)/.exec( data )[ 1 ];

						if ( undefined == defines[ defineKey ] ) {

							console.warn( 'THREE.VRMLLoader: %s is not defined.', defineKey );

						} else {

							if ( /appearance/.exec( data ) && defineKey ) {

								parent.material = defines[ defineKey ].clone();

							} 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 ) {

									parent.geometry.solid = false;
									parent.material.side = THREE.DoubleSide;

								}

							} else if ( defineKey ) {

								object = defines[ defineKey ].clone();
								parent.add( object );

							}

						}

					}

					return;

				}

				object = parent;

				//if ( data.string.indexOf( 'AmbientLight' ) > - 1 && data.nodeType === 'PointLight' ) {
                //
				//	data.nodeType = 'AmbientLight';
                //
				//}

				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 ) {

					l_color.copy( data.color );

				}

				//if ( data.nodeType === 'AmbientLight' ) {
                //
				//	object = new THREE.AmbientLight( l_color, l_intensity );
				//	object.visible = l_visible;
                //
				//	parent.add( object );
                //
				//} else if ( data.nodeType === 'PointLight' ) {
                //
				//	var l_distance = 0;
                //
				//	if ( data.radius !== undefined && data.radius < 1000 ) {
                //
				//		l_distance = data.radius;
                //
				//	}
                //
				//	object = new THREE.PointLight( l_color, l_intensity, l_distance );
				//	object.visible = l_visible;
                //
				//	parent.add( object );
                //
				//} 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;
                //
				//	if ( data.radius !== undefined && data.radius < 1000 ) {
                //
				//		l_distance = data.radius;
                //
				//	}
                //
				//	if ( data.cutOffAngle !== undefined ) {
                //
				//		l_angle = data.cutOffAngle;
                //
				//	}
                //
				//	object = new THREE.SpotLight( l_color, l_intensity, l_distance, l_angle, l_penumbra );
				//	object.visible = l_visible;
                //
				//	parent.add( object );

				//} else 
				
				if ( data.nodeType === 'Transform' || data.nodeType === 'Group' ) {

					object = new THREE.Object3D();

					if ( /DEF/.exec( data.string ) ) {

						object.name = /DEF\s+([^\s]+)/.exec( data.string )[ 1 ];
						defines[ object.name ] = object;

					}

					if ( data.translation !== undefined ) {

						var t = data.translation;

						object.position.set( t.x, t.y, t.z );

					}

					if ( data.rotation !== undefined ) {

						var r = data.rotation;

						object.quaternion.setFromAxisAngle( new THREE.Vector3( r.x, r.y, r.z ), r.w );

					}

					if ( data.scale !== undefined ) {

						var s = data.scale;

						object.scale.set( s.x, s.y, s.z );

					}

					parent.add( object );

				} else if ( data.nodeType === 'Shape' ) {

					//object = new THREE.Mesh();
					if (data.children[1] != undefined) {
						switch (data.children[1].nodeType) {
							case "IndexedFaceSet":
								object = new THREE.Mesh();							
								break;
							case "IndexedLineSet":
								object = new THREE.LineSegments();
								break;
							case "PointSet":
								object = new THREE.Points();
								break;
						}
					}

					if ( /DEF/.exec( data.string ) ) {

						object.name = /DEF\s+([^\s]+)/.exec( data.string )[ 1 ];

						defines[ object.name ] = object;

					}

					parent.add( object );

				//} else if ( data.nodeType === 'Background' ) {
                //
				//	var segments = 20;
                //
				//	// sky (full sphere):
                //
				//	var radius = 2e4;
                //
				//	var skyGeometry = new THREE.SphereBufferGeometry( radius, segments, segments );
				//	var skyMaterial = new THREE.MeshBasicMaterial( { fog: false, side: THREE.BackSide } );
                //
				//	if ( data.skyColor.length > 1 ) {
                //
				//		paintFaces( skyGeometry, radius, data.skyAngle, data.skyColor, true );
                //
				//		skyMaterial.vertexColors = THREE.VertexColors;
                //
				//	} else {
                //
				//		var color = data.skyColor[ 0 ];
				//		skyMaterial.color.setRGB( color.r, color.b, color.g );
                //
				//	}
                //
				//	scene.add( new THREE.Mesh( skyGeometry, skyMaterial ) );
                //
				//	// ground (half sphere):
                //
				//	if ( data.groundColor !== undefined ) {
                //
				//		radius = 1.2e4;
                //
				//		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 );
                //
				//		scene.add( new THREE.Mesh( groundGeometry, groundMaterial ) );
                //
				//	}

				} else if ( /geometry/.exec( data.string ) ) {

					if ( data.nodeType === 'Box' ) {

						var s = data.size;

						parent.geometry = new THREE.BoxBufferGeometry( s.x, s.y, s.z );

					} else if ( data.nodeType === 'Cylinder' ) {

						parent.geometry = new THREE.CylinderBufferGeometry( data.radius, data.radius, data.height );

					} else if ( data.nodeType === 'Cone' ) {

						parent.geometry = new THREE.CylinderBufferGeometry( data.topRadius, data.bottomRadius, data.height );

					} else if ( data.nodeType === 'Sphere' ) {

						parent.geometry = new THREE.SphereBufferGeometry( data.radius );

					} else if ( data.nodeType === 'IndexedLineSet' ) {
						var geometry = new THREE.BufferGeometry();
						var positions = [];
						var position1, position2;
						var i, il, j, jl;
						for ( i = 0, il = data.children.length; i < il; i ++ ) {
							var child = data.children[ i ];
							// positions
							if ( child.nodeType === 'Coordinate' ) {
								if ( child.points ) {
									for ( j = 0, jl = child.points.length; j < jl-1; j ++ ) {
										position1 = child.points[ j ];
										position2 = child.points[ j+1 ];
										positions.push( position1.x, position1.y, position1.z );
										positions.push( position2.x, position2.y, position2.z );
									}
								}
								if ( child.string.indexOf( 'DEF' ) > - 1 ) {
									var name = /DEF\s+([^\s]+)/.exec( child.string )[ 1 ];
									defines[ name ] = positions.slice( 0 );
									//console.log("defines[ name ] 1", defines[ name ]);
								}
								if ( child.string.indexOf( 'USE' ) > - 1 ) {
									var defineKey = /USE\s+([^\s]+)/.exec( child.string )[ 1 ];
									positions = defines[ defineKey ];
									//console.log("defines[ defineKey ] 1", defines[ defineKey ]);
								}
							}
						}
						if ( data.coordIndex == undefined) {
							// do not add dummy mesh to the scene
							parent.parent.remove( parent );
						}
						if ( false === data.solid ) {
							parent.material.side = THREE.DoubleSide;
						}
						// we need to store it on the geometry for use with defines
						geometry.solid = data.solid;
						geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
						geometry.computeVertexNormals();
						geometry.computeBoundingSphere();
						// see if it's a define
						if ( /DEF/.exec( data.string ) ) {
							geometry.name = /DEF ([^\s]+)/.exec( data.string )[ 1 ];
							defines[ geometry.name ] = geometry;
							//console.log("geometry 3: " , geometry);
						}
						parent.geometry = geometry;

					} else if ( data.nodeType === 'Text' ) {

						console.warn( 'THREE.VRMLLoader: Text not supported yet.' );
						parent.parent.remove( parent );

					} else if ( data.nodeType === 'IndexedFaceSet' ) {

						var geometry = new THREE.BufferGeometry();

						var positions = [];
						var colors = [];
						var normals = [];
						var uvs = [];

						var position, color, normal, uv;

						var i, il, j, jl;

						for ( i = 0, il = data.children.length; i < il; i ++ ) {

							var child = data.children[ i ];

							// uvs

							if ( child.nodeType === 'TextureCoordinate' ) {

								if ( child.points ) {

									for ( j = 0, jl = child.points.length; j < jl; j ++ ) {

										uv = child.points[ j ];
										uvs.push( uv.x, uv.y );

									}

								}

							}

							// normals

							if ( child.nodeType === 'Normal' ) {

								if ( child.points ) {

									for ( j = 0, jl = child.points.length; j < jl; j ++ ) {

										normal = child.points[ j ];
										normals.push( normal.x, normal.y, normal.z );

									}

								}

							}

							// colors

							if ( child.nodeType === 'Color' ) {

								if ( child.color ) {

									for ( j = 0, jl = child.color.length; j < jl; j ++ ) {

										color = child.color[ j ];
										colors.push( color.r, color.g, color.b );

									}

								}

							}

							// positions

							if ( child.nodeType === 'Coordinate' ) {

								if ( child.points ) {

									for ( j = 0, jl = child.points.length; j < jl; j ++ ) {

										position = child.points[ j ];
										positions.push( position.x, position.y, position.z );

									}

								}

								if ( child.string.indexOf( 'DEF' ) > - 1 ) {

									var name = /DEF\s+([^\s]+)/.exec( child.string )[ 1 ];

									defines[ name ] = positions.slice( 0 );

								}

								if ( child.string.indexOf( 'USE' ) > - 1 ) {

									var defineKey = /USE\s+([^\s]+)/.exec( child.string )[ 1 ];

									positions = defines[ defineKey ];

								}

							}

						}

						// some shapes only have vertices for use in other shapes

						if ( data.coordIndex ) {

							function triangulateIndexArray( indexArray, ccw, colorPerVertex ) {

								if ( ccw === undefined ) {

									// ccw is true by default
									ccw = true;

								}

								var triangulatedIndexArray = [];
								var skip = 0;

								for ( i = 0, il = indexArray.length; i < il; i ++ ) {

									if ( colorPerVertex === false ) {

										var colorIndices = indexArray[ i ];

										for ( j = 0, jl = colorIndices.length; j < jl; j ++ ) {

											var index = colorIndices[ j ];

											triangulatedIndexArray.push( index, index, index );

										}

									} else {

										var indexedFace = indexArray[ i ];

										// VRML support multipoint indexed face sets (more then 3 vertices). You must calculate the composing triangles here

										skip = 0;

										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 ) ];

											triangulatedIndexArray.push( i1, i2, i3 );

											skip ++;

										}

									}

								}

								return triangulatedIndexArray;

							}

							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;

							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 pointMap = Object.create( null );

							for ( i = 0; i < positionIndexes.length; i ++ ) {

								var pointAttributes = [];

								var positionIndex = positionIndexes[ i ];
								var normalIndex = normalIndexes[ i ];
								var colorIndex = colorIndexes[ i ];
								var uvIndex = uvIndexes[ i ];

								var base = 10; // which base to use to represent each value

								pointAttributes.push( positionIndex.toString( base ) );

								if ( normalIndex !== undefined ) {

									pointAttributes.push( normalIndex.toString( base ) );

								}

								if ( colorIndex !== undefined ) {

									pointAttributes.push( colorIndex.toString( base ) );

								}

								if ( uvIndex !== undefined ) {

									pointAttributes.push( uvIndex.toString( base ) );

								}

								var pointId = pointAttributes.join( ',' );
								var newIndex = pointMap[ pointId ];

								if ( newIndex === undefined ) {

									newIndex = newPositions.length / 3;
									pointMap[ pointId ] = newIndex;

									newPositions.push(
										positions[ positionIndex * 3 ],
										positions[ positionIndex * 3 + 1 ],
										positions[ positionIndex * 3 + 2 ]
									);

									if ( normalIndex !== undefined && normals.length > 0 ) {

										newNormals.push(
											normals[ normalIndex * 3 ],
											normals[ normalIndex * 3 + 1 ],
											normals[ normalIndex * 3 + 2 ]
										);

									}

									if ( colorIndex !== undefined && colors.length > 0 ) {

										newColors.push(
											colors[ colorIndex * 3 ],
											colors[ colorIndex * 3 + 1 ],
											colors[ colorIndex * 3 + 2 ]
										);

									}

									if ( uvIndex !== undefined && uvs.length > 0 ) {

										newUvs.push(
											uvs[ uvIndex * 2 ],
											uvs[ uvIndex * 2 + 1 ]
										);

									}

								}

								newIndexes.push( newIndex );

							}

							positions = newPositions;
							normals = newNormals;
							colors = newColors;
							uvs = newUvs;

							geometry.setIndex( newIndexes );

						} else {

							// do not add dummy mesh to the scene

							parent.parent.remove( parent );

						}

						if ( false === data.solid ) {

							parent.material.side = THREE.DoubleSide;

						}

						// we need to store it on the geometry for use with defines
						geometry.solid = data.solid;

						geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );

						if ( colors.length > 0 ) {

							geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );

							parent.material.vertexColors = THREE.VertexColors;

						}

						if ( uvs.length > 0 ) {

							geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );

						}

						if ( normals.length > 0 ) {

							geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );

						} else {

							// convert geometry to non-indexed to get sharp normals
							geometry = geometry.toNonIndexed();
							geometry.computeVertexNormals();

						}

						geometry.computeBoundingSphere();

						// see if it's a define
						if ( /DEF/.exec( data.string ) ) {

							geometry.name = /DEF ([^\s]+)/.exec( data.string )[ 1 ];
							defines[ geometry.name ] = geometry;

						}

						parent.geometry = geometry;

					} else if ( data.nodeType === 'PointSet' ) {
						var geometry = new THREE.BufferGeometry();
						var positions = [];
						var position;
						var i, il, j, jl;
						for ( i = 0, il = data.children.length; i < il; i ++ ) {
							var child = data.children[ i ];
							// positions
							if ( child.nodeType === 'Coordinate' ) {
								if ( child.points ) {
									for ( j = 0, jl = child.points.length; j < jl; j ++ ) {
										position = child.points[ j ];
										positions.push( position.x, position.y, position.z );
									}
								}
								if ( child.string.indexOf( 'DEF' ) > - 1 ) {
									var name = /DEF\s+([^\s]+)/.exec( child.string )[ 1 ];
									defines[ name ] = positions.slice( 0 );
									//console.log("defines[ name ] 4: ", defines[ name ]);
								}
								if ( child.string.indexOf( 'USE' ) > - 1 ) {
									var defineKey = /USE\s+([^\s]+)/.exec( child.string )[ 1 ];
									positions = defines[ defineKey ];
									//console.log("defines[ name ] 5: ", defines[ name ]);
								}
							}
						}
						if ( false === data.solid ) {
							parent.material.side = THREE.DoubleSide;
						}
						// we need to store it on the geometry for use with defines
						geometry.solid = data.solid;
						geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
						geometry.computeBoundingSphere();
						// see if it's a define
						if ( /DEF/.exec( data.string ) ) {
							geometry.name = /DEF ([^\s]+)/.exec( data.string )[ 1 ];
							defines[ geometry.name ] = geometry;
							//console.log("geometry 5: " , geometry);
						}
						parent.geometry = geometry;
					}
					
					return;

				} else if ( /appearance/.exec( data.string ) ) {
					//console.log("data",  data);
					for ( var i = 0; i < data.children.length; i ++ ) {

						var child = data.children[ i ];

						if ( child.nodeType === 'Material' ) {
							//console.log(child);
							var material = new THREE.MeshStandardMaterial();
							if ( child.name !== undefined ) {
								material.name = child.name
							}
							if ( child.diffuseColor !== undefined ) {
								var d = child.diffuseColor;
								material.color.setRGB( d.r, d.g, d.b );
								material.emissive.setRGB( d.r, d.g, d.b )
							}
							if ( child.emissiveColor !== undefined ) {
								var e = child.emissiveColor;
								material.emissive.setRGB( e.r, e.g, e.b );
								material.color.setRGB( e.r, e.g, e.b );
							}
							if ( child.transparency !== undefined ) {
								var t = child.transparency;
								material.opacity = Math.abs( 1 - t );
								material.transparent = true;
							}
							//console.log(material);
							//var material = new THREE.MeshPhongMaterial();
                            //
							//if ( child.diffuseColor !== undefined ) {
                            //
							//	var d = child.diffuseColor;
                            //
							//	material.color.setRGB( d.r, d.g, d.b );
                            //
							//}
                            //
							//if ( child.emissiveColor !== undefined ) {
                            //
							//	var e = child.emissiveColor;
                            //
							//	material.emissive.setRGB( e.r, e.g, e.b );
                            //
							//}
                            //
							//if ( child.specularColor !== undefined ) {
                            //
							//	var s = child.specularColor;
                            //
							//	material.specular.setRGB( s.r, s.g, s.b );
                            //
							//}
                            //
							//if ( child.transparency !== undefined ) {
                            //
							//	var t = child.transparency;
                            //
							//	// transparency is opposite of opacity
							//	material.opacity = Math.abs( 1 - t );
                            //
							//	material.transparent = true;
                            //
							//}

							//if ( /DEF/.exec( data.string ) ) {
                            //
							//	material.name = /DEF ([^\s]+)/.exec( data.string )[ 1 ];
                            //
							//	defines[ material.name ] = material;
                            //
							//}

							parent.material = material;

						}

						if ( child.nodeType === 'ImageTexture' ) {

							var textureName = /"([^"]+)"/.exec( child.children[ 0 ] );

							if ( textureName ) {

								parent.material.name = textureName[ 1 ];

								parent.material.map = textureLoader.load( textureName[ 1 ] );

							}

						}

					}

					return;

				}

				for ( var i = 0, l = data.children.length; i < l; i ++ ) {

					parseNode( data.children[ i ], object );

				}

			}
			//getTree( lines )
			parseNode( getTree( lines ), scene );

		}

		var scene = new THREE.Scene();

		var lines = data.split( '\n' );

		// some lines do not have breaks

		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 );

			} else if ( /\].*}/.test( line ) ) {

				// split lines with ]..}
				var parts = line.split( ']' ).join( ']\n' ).split( '\n' );
				parts.unshift( 1 );
				parts.unshift( i );
				lines.splice.apply( lines, parts );

			}

			line = lines[ i ];

			if ( /}.*}/.test( line ) ) {

				// split lines with }..}
				var parts = line.split( '}' ).join( '}\n' ).split( '\n' );
				parts.unshift( 1 );
				parts.unshift( i );
				lines.splice.apply( lines, parts );

			}

			line = lines[ i ];

			if ( /^\b[^\s]+\b$/.test( line.trim() ) ) {

				// prevent lines with single words like "coord" or "geometry", see #12209
				lines[ i + 1 ] = line + ' ' + lines[ i + 1 ].trim();
				lines.splice( i, 1 );

			} else if ( ( line.indexOf( 'coord' ) > - 1 ) && ( line.indexOf( '[' ) < 0 ) && ( line.indexOf( '{' ) < 0 ) ) {

				// force the parser to create Coordinate node for empty coords
				// coord USE something -> coord USE something Coordinate {}

				lines[ i ] += ' Coordinate {}';

			}

		}

		var header = lines.shift();

		if ( /V1.0/.exec( header ) ) {

			console.warn( 'THREE.VRMLLoader: V1.0 not supported yet.' );

		} else if ( /V2.0/.exec( header ) ) {

			parseV2( lines, scene );

		}
		//console.log(scene);
		return scene;

	}

};

Best regards

1 Like