2
0
Mr.doob 3 жил өмнө
parent
commit
3280febab0

+ 3 - 1
examples/js/animation/MMDAnimationHelper.js

@@ -44,7 +44,9 @@
 				cameraAnimation: true
 			};
 
-			this.onBeforePhysics = function () {}; // experimental
+			this.onBeforePhysics = function
+			/* mesh */
+			() {}; // experimental
 
 
 			this.sharedPhysics = false;

+ 6 - 2
examples/js/controls/experimental/CameraControls.js

@@ -510,7 +510,9 @@
 
 		}
 
-		function handleMouseUp() { // no-op
+		function
+		/*event*/
+		handleMouseUp() { // no-op
 		}
 
 		function handleMouseWheel( event ) {
@@ -691,7 +693,9 @@
 
 		}
 
-		function handleTouchEnd() { // no-op
+		function
+		/*event*/
+		handleTouchEnd() { // no-op
 		} //
 		// event handlers - FSM: listen for events and reset state
 		//

+ 18 - 7
examples/js/loaders/EXRLoader.js

@@ -1606,13 +1606,24 @@
 
 			}
 
-			function parseInt64( dataView, offset ) {
+			const parseInt64 = function ( dataView, offset ) {
+
+				let int;
+
+				if ( 'getBigInt64' in DataView.prototype ) {
+
+					int = Number( dataView.getBigInt64( offset.value, true ) );
+
+				} else {
+
+					int = dataView.getUint32( offset.value + 4, true ) + Number( dataView.getUint32( offset.value, true ) << 32 );
+
+				}
 
-				var int = Number( dataView.getBigInt64( offset.value, true ) );
 				offset.value += ULONG_SIZE;
 				return int;
 
-			}
+			};
 
 			function parseFloat32( dataView, offset ) {
 
@@ -1830,10 +1841,10 @@
 				const spec = dataView.getUint8( 5, true ); // fullMask
 
 				EXRHeader.spec = {
-					singleTile: !! ( spec & 1 ),
-					longName: !! ( spec & 2 ),
-					deepFormat: !! ( spec & 4 ),
-					multiPart: !! ( spec & 8 )
+					singleTile: !! ( spec & 2 ),
+					longName: !! ( spec & 4 ),
+					deepFormat: !! ( spec & 8 ),
+					multiPart: !! ( spec & 16 )
 				}; // start of header
 
 				offset.value = 8; // start at 8 - after pre-amble

+ 524 - 421
examples/js/loaders/LDrawLoader.js

@@ -17,6 +17,8 @@
 	const FILE_LOCATION_TRY_RELATIVE = 4;
 	const FILE_LOCATION_TRY_ABSOLUTE = 5;
 	const FILE_LOCATION_NOT_FOUND = 6;
+	const MAIN_COLOUR_CODE = '16';
+	const MAIN_EDGE_COLOUR_CODE = '24';
 
 	const _tempVec0 = new THREE.Vector3();
 
@@ -142,6 +144,17 @@
 
 	}
 
+	class ConditionalLineSegments extends THREE.LineSegments {
+
+		constructor( geometry, material ) {
+
+			super( geometry, material );
+			this.isConditionalLine = true;
+
+		}
+
+	}
+
 	function generateFaceNormals( faces ) {
 
 		for ( let i = 0, l = faces.length; i < l; i ++ ) {
@@ -497,13 +510,7 @@
 
 	function isPartType( type ) {
 
-		return type === 'Part';
-
-	}
-
-	function isModelType( type ) {
-
-		return type === 'Model' || type === 'Unofficial_Model';
+		return type === 'Part' || type === 'Unofficial_Part';
 
 	}
 
@@ -597,14 +604,15 @@
 
 		}
 
-	}
+	} // Fetches and parses an intermediate representation of LDraw parts files.
+
 
 	class LDrawParsedCache {
 
 		constructor( loader ) {
 
 			this.loader = loader;
-			this.cache = {};
+			this._cache = {};
 
 		}
 
@@ -651,6 +659,7 @@
 			result.totalFaces = original.totalFaces;
 			result.startingConstructionStep = original.startingConstructionStep;
 			result.materials = original.materials;
+			result.group = null;
 			return result;
 
 		}
@@ -733,7 +742,7 @@
 
 		}
 
-		parse( text ) {
+		parse( text, fileName = null ) {
 
 			const loader = this.loader; // final results
 
@@ -745,7 +754,7 @@
 
 			const getLocalMaterial = colorCode => {
 
-				return colorCode in materials ? materials[ colorCode ] : null;
+				return materials[ colorCode ] || null;
 
 			};
 
@@ -1135,37 +1144,376 @@
 				subobjects,
 				totalFaces,
 				startingConstructionStep,
-				materials
+				materials,
+				fileName,
+				group: null
 			};
 
-		}
+		} // returns an (optionally cloned) instance of the data
 
-		loadData( fileName ) {
+
+		getData( fileName, clone = true ) {
 
 			const key = fileName.toLowerCase();
+			const result = this._cache[ key ];
+
+			if ( result === null || result instanceof Promise ) {
+
+				return null;
+
+			}
 
-			if ( ! ( key in this.cache ) ) {
+			if ( clone ) {
+
+				return this.cloneResult( result );
+
+			} else {
+
+				return result;
+
+			}
 
-				this.cache[ key ] = this.fetchData( fileName ).then( text => {
+		} // kicks off a fetch and parse of the requested data if it hasn't already been loaded. Returns when
+		// the data is ready to use and can be retrieved synchronously with "getData".
 
-					return this.parse( text );
+
+		async ensureDataLoaded( fileName ) {
+
+			const key = fileName.toLowerCase();
+
+			if ( ! ( key in this._cache ) ) {
+
+				// replace the promise with a copy of the parsed data for immediate processing
+				this._cache[ key ] = this.fetchData( fileName ).then( text => {
+
+					const info = this.parse( text, fileName );
+					this._cache[ key ] = info;
+					return info;
 
 				} );
 
 			}
 
-			return this.cache[ key ].then( result => {
+			await this._cache[ key ];
 
-				return this.cloneResult( result );
+		} // sets the data in the cache from parsed data
 
-			} );
+
+		setData( fileName, text ) {
+
+			const key = fileName.toLowerCase();
+			this._cache[ key ] = this.parse( text, fileName );
 
 		}
 
-		setData( fileName, text ) {
+	} // returns the material for an associated color code. If the color code is 16 for a face or 24 for
+	// an edge then the passthroughColorCode is used.
+
+
+	function getMaterialFromCode( colorCode, parentColorCode, materialHierarchy, forEdge ) {
+
+		const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE;
+
+		if ( isPassthrough ) {
+
+			colorCode = parentColorCode;
+
+		}
+
+		return materialHierarchy[ colorCode ] || null;
+
+	} // Class used to parse and build LDraw parts as three.js objects and cache them if they're a "Part" type.
+
+
+	class LDrawPartsGeometryCache {
+
+		constructor( loader ) {
+
+			this.loader = loader;
+			this.parseCache = new LDrawParsedCache( loader );
+			this._cache = {};
+
+		} // Convert the given file information into a mesh by processing subobjects.
+
+
+		async processIntoMesh( info ) {
+
+			const loader = this.loader;
+			const parseCache = this.parseCache;
+			const faceMaterials = new Set(); // Processes the part subobject information to load child parts and merge geometry onto part
+			// piece object.
+
+			const processInfoSubobjects = async ( info, subobject = null ) => {
+
+				const subobjects = info.subobjects;
+				const promises = []; // Trigger load of all subobjects. If a subobject isn't a primitive then load it as a separate
+				// group which lets instruction steps apply correctly.
+
+				for ( let i = 0, l = subobjects.length; i < l; i ++ ) {
+
+					const subobject = subobjects[ i ];
+					const promise = parseCache.ensureDataLoaded( subobject.fileName ).then( () => {
+
+						const subobjectInfo = parseCache.getData( subobject.fileName, false );
+
+						if ( ! isPrimitiveType( subobjectInfo.type ) ) {
+
+							return this.loadModel( subobject.fileName ).catch( error => {
+
+								console.warn( error );
+								return null;
+
+							} );
+
+						}
+
+						return processInfoSubobjects( parseCache.getData( subobject.fileName ), subobject );
+
+					} );
+					promises.push( promise );
+
+				}
+
+				const group = new THREE.Group();
+				group.userData.category = info.category;
+				group.userData.keywords = info.keywords;
+				info.group = group;
+				const subobjectInfos = await Promise.all( promises );
+
+				for ( let i = 0, l = subobjectInfos.length; i < l; i ++ ) {
+
+					const subobject = info.subobjects[ i ];
+					const subobjectInfo = subobjectInfos[ i ];
+
+					if ( subobjectInfo === null ) {
+
+						// the subobject failed to load
+						continue;
+
+					} // if the subobject was loaded as a separate group then apply the parent scopes materials
+
+
+					if ( subobjectInfo.isGroup ) {
+
+						const subobjectGroup = subobjectInfo;
+						subobject.matrix.decompose( subobjectGroup.position, subobjectGroup.quaternion, subobjectGroup.scale );
+						subobjectGroup.userData.startingConstructionStep = subobject.startingConstructionStep;
+						subobjectGroup.name = subobject.fileName;
+						loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );
+						group.add( subobjectGroup );
+						continue;
+
+					} // add the subobject group if it has children in case it has both children and primitives
+
+
+					if ( subobjectInfo.group.children.length ) {
+
+						group.add( subobjectInfo.group );
+
+					} // transform the primitives into the local space of the parent piece and append them to
+					// to the parent primitives list.
+
+
+					const parentLineSegments = info.lineSegments;
+					const parentConditionalSegments = info.conditionalSegments;
+					const parentFaces = info.faces;
+					const lineSegments = subobjectInfo.lineSegments;
+					const conditionalSegments = subobjectInfo.conditionalSegments;
+					const faces = subobjectInfo.faces;
+					const matrix = subobject.matrix;
+					const inverted = subobject.inverted;
+					const matrixScaleInverted = matrix.determinant() < 0;
+					const colorCode = subobject.colorCode;
+					const lineColorCode = colorCode === MAIN_COLOUR_CODE ? MAIN_EDGE_COLOUR_CODE : colorCode;
+
+					for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
+
+						const ls = lineSegments[ i ];
+						const vertices = ls.vertices;
+						vertices[ 0 ].applyMatrix4( matrix );
+						vertices[ 1 ].applyMatrix4( matrix );
+						ls.colorCode = ls.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : ls.colorCode;
+						ls.material = ls.material || getMaterialFromCode( ls.colorCode, ls.colorCode, info.materials, true );
+						parentLineSegments.push( ls );
+
+					}
+
+					for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
+
+						const os = conditionalSegments[ i ];
+						const vertices = os.vertices;
+						const controlPoints = os.controlPoints;
+						vertices[ 0 ].applyMatrix4( matrix );
+						vertices[ 1 ].applyMatrix4( matrix );
+						controlPoints[ 0 ].applyMatrix4( matrix );
+						controlPoints[ 1 ].applyMatrix4( matrix );
+						os.colorCode = os.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : os.colorCode;
+						os.material = os.material || getMaterialFromCode( os.colorCode, os.colorCode, info.materials, true );
+						parentConditionalSegments.push( os );
+
+					}
+
+					for ( let i = 0, l = faces.length; i < l; i ++ ) {
+
+						const tri = faces[ i ];
+						const vertices = tri.vertices;
+
+						for ( let i = 0, l = vertices.length; i < l; i ++ ) {
+
+							vertices[ i ].applyMatrix4( matrix );
+
+						}
+
+						tri.colorCode = tri.colorCode === MAIN_COLOUR_CODE ? colorCode : tri.colorCode;
+						tri.material = tri.material || getMaterialFromCode( tri.colorCode, colorCode, info.materials, false );
+						faceMaterials.add( tri.colorCode ); // If the scale of the object is negated then the triangle winding order
+						// needs to be flipped.
+
+						if ( matrixScaleInverted !== inverted ) {
 
+							vertices.reverse();
+
+						}
+
+						parentFaces.push( tri );
+
+					}
+
+					info.totalFaces += subobjectInfo.totalFaces;
+
+				} // Apply the parent subobjects pass through material code to this object. This is done several times due
+				// to material scoping.
+
+
+				if ( subobject ) {
+
+					loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials );
+
+				}
+
+				return info;
+
+			}; // Track material use to see if we need to use the normal smooth slow path for hard edges.
+
+
+			for ( let i = 0, l = info.faces; i < l; i ++ ) {
+
+				faceMaterials.add( info.faces[ i ].colorCode );
+
+			}
+
+			await processInfoSubobjects( info );
+
+			if ( loader.smoothNormals ) {
+
+				const checkSubSegments = faceMaterials.size > 1;
+				generateFaceNormals( info.faces );
+				smoothNormals( info.faces, info.lineSegments, checkSubSegments );
+
+			} // Add the primitive objects and metadata.
+
+
+			const group = info.group;
+
+			if ( info.faces.length > 0 ) {
+
+				group.add( createObject( info.faces, 3, false, info.totalFaces ) );
+
+			}
+
+			if ( info.lineSegments.length > 0 ) {
+
+				group.add( createObject( info.lineSegments, 2 ) );
+
+			}
+
+			if ( info.conditionalSegments.length > 0 ) {
+
+				group.add( createObject( info.conditionalSegments, 2, true ) );
+
+			}
+
+			return group;
+
+		}
+
+		hasCachedModel( fileName ) {
+
+			return fileName !== null && fileName.toLowerCase() in this._cache;
+
+		}
+
+		async getCachedModel( fileName ) {
+
+			if ( fileName !== null && this.hasCachedModel( fileName ) ) {
+
+				const key = fileName.toLowerCase();
+				const group = await this._cache[ key ];
+				return group.clone();
+
+			} else {
+
+				return null;
+
+			}
+
+		} // Loads and parses the model with the given file name. Returns a cached copy if available.
+
+
+		async loadModel( fileName ) {
+
+			const parseCache = this.parseCache;
 			const key = fileName.toLowerCase();
-			this.cache[ key ] = Promise.resolve( this.parse( text ) );
+
+			if ( this.hasCachedModel( fileName ) ) {
+
+				// Return cached model if available.
+				return this.getCachedModel( fileName );
+
+			} else {
+
+				// Otherwise parse a new model.
+				// Ensure the file data is loaded and pre parsed.
+				await parseCache.ensureDataLoaded( fileName );
+				const info = parseCache.getData( fileName );
+				const promise = this.processIntoMesh( info ); // Now that the file has loaded it's possible that another part parse has been waiting in parallel
+				// so check the cache again to see if it's been added since the last async operation so we don't
+				// do unnecessary work.
+
+				if ( this.hasCachedModel( fileName ) ) {
+
+					return this.getCachedModel( fileName );
+
+				} // Cache object if it's a part so it can be reused later.
+
+
+				if ( isPartType( info.type ) ) {
+
+					this._cache[ key ] = promise;
+
+				} // return a copy
+
+
+				const group = await promise;
+				return group.clone();
+
+			}
+
+		} // parses the given model text into a renderable object. Returns cached copy if available.
+
+
+		async parseModel( text ) {
+
+			const parseCache = this.parseCache;
+			const info = parseCache.parse( text );
+
+			if ( isPartType( info.type ) && this.hasCachedModel( info.fileName ) ) {
+
+				return this.getCachedModel( info.fileName );
+
+			}
+
+			return this.processIntoMesh( info );
 
 		}
 
@@ -1290,7 +1638,7 @@
 
 			}
 
-			if ( prevMaterial !== elem.material ) {
+			if ( prevMaterial !== elem.colorCode ) {
 
 				if ( prevMaterial !== null ) {
 
@@ -1300,25 +1648,43 @@
 
 				const material = elem.material;
 
-				if ( elementSize === 3 ) {
+				if ( material !== null ) {
 
-					materials.push( material );
+					if ( elementSize === 3 ) {
 
-				} else if ( elementSize === 2 ) {
+						materials.push( material );
 
-					if ( isConditionalSegments ) {
+					} else if ( elementSize === 2 ) {
 
-						materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial );
+						if ( material !== null ) {
 
-					} else {
+							if ( isConditionalSegments ) {
+
+								materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial );
+
+							} else {
+
+								materials.push( material.userData.edgeMaterial );
+
+							}
 
-						materials.push( material.userData.edgeMaterial );
+						} else {
+
+							materials.push( null );
+
+						}
 
 					}
 
+				} else {
+
+					// If a material has not been made available yet then keep the color code string in the material array
+					// to save the spot for the material once a parent scopes materials are being applied to the object.
+					materials.push( elem.colorCode );
+
 				}
 
-				prevMaterial = elem.material;
+				prevMaterial = elem.colorCode;
 				index0 = offset / 3;
 				numGroupVerts = vertices.length;
 
@@ -1350,7 +1716,15 @@
 
 		if ( elementSize === 2 ) {
 
-			object3d = new THREE.LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
+			if ( isConditionalSegments ) {
+
+				object3d = new ConditionalLineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
+
+			} else {
+
+				object3d = new THREE.LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
+
+			}
 
 		} else if ( elementSize === 3 ) {
 
@@ -1407,24 +1781,20 @@
 	} //
 
 
-	const MAIN_COLOUR_CODE = '16';
-	const MAIN_EDGE_COLOUR_CODE = '24';
-
 	class LDrawLoader extends THREE.Loader {
 
 		constructor( manager ) {
 
 			super( manager ); // Array of THREE.Material
 
-			this.materials = []; // Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
-			// This also allows to handle the embedded text files ("0 FILE" lines)
+			this.materials = [];
+			this.materialLibrary = {}; // This also allows to handle the embedded text files ("0 FILE" lines)
 
-			this.parseCache = new LDrawParsedCache( this ); // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
+			this.partsCache = new LDrawPartsGeometryCache( this ); // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
 
-			this.fileMap = {};
-			this.rootParseScope = this.newParseScopeLevel(); // Add default main triangle and line edge materials (used in pieces that can be colored with a main color)
+			this.fileMap = {}; // Initializes the materials library with default materials
 
-			this.setMaterials( [ this.parseColorMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ), this.parseColorMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ] ); // If this flag is set to true the vertex normals will be smoothed.
+			this.setMaterials( [] ); // If this flag is set to true the vertex normals will be smoothed.
 
 			this.smoothNormals = true; // The path to load parts from the LDraw parts library from.
 
@@ -1476,24 +1846,24 @@
 			fileLoader.setWithCredentials( this.withCredentials );
 			fileLoader.load( url, text => {
 
-				const parsedInfo = this.parseCache.parse( text );
-				this.processObject( parsedInfo, null, url, this.rootParseScope ).then( function ( result ) {
+				this.partsCache.parseModel( text, this.materialLibrary ).then( group => {
 
-					onLoad( result.groupObject );
+					this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
+					this.computeConstructionSteps( group );
+					onLoad( group );
 
-				} );
+				} ).catch( onError );
 
 			}, onProgress, onError );
 
 		}
 
-		parse( text, path, onLoad ) {
+		parse( text, onLoad ) {
 
-			// Async parse. This function calls onParse with the parsed THREE.Object3D as parameter
-			const parsedInfo = this.parseCache.parse( text );
-			this.processObject( parsedInfo, null, path, this.rootParseScope ).then( function ( result ) {
+			this.partsCache.parseModel( text, this.materialLibrary ).then( group => {
 
-				onLoad( result.groupObject );
+				this.computeConstructionSteps( group );
+				onLoad( group );
 
 			} );
 
@@ -1501,86 +1871,47 @@
 
 		setMaterials( materials ) {
 
-			// Clears parse scopes stack, adds new scope with material library
-			this.rootParseScope = this.newParseScopeLevel( materials );
-			this.rootParseScope.isFromParse = false;
-			this.materials = materials;
-			return this;
-
-		}
-
-		setFileMap( fileMap ) {
-
-			this.fileMap = fileMap;
-			return this;
-
-		}
+			this.materialLibrary = {};
+			this.materials = [];
 
-		newParseScopeLevel( materials = null, parentScope = null ) {
+			for ( let i = 0, l = materials.length; i < l; i ++ ) {
 
-			// Adds a new scope level, assign materials to it and returns it
-			const matLib = {};
+				this.addMaterial( materials[ i ] );
 
-			if ( materials ) {
+			} // Add default main triangle and line edge materials (used in pieces that can be colored with a main color)
 
-				for ( let i = 0, n = materials.length; i < n; i ++ ) {
 
-					const material = materials[ i ];
-					matLib[ material.userData.code ] = material;
+			this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ) );
+			this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) );
+			return this;
 
-				}
+		}
 
-			}
+		setFileMap( fileMap ) {
 
-			const newParseScope = {
-				parentScope: parentScope,
-				lib: matLib,
-				url: null,
-				// Subobjects
-				subobjects: null,
-				inverted: false,
-				category: null,
-				keywords: null,
-				// Current subobject
-				currentFileName: null,
-				mainColorCode: parentScope ? parentScope.mainColorCode : MAIN_COLOUR_CODE,
-				mainEdgeColorCode: parentScope ? parentScope.mainEdgeColorCode : MAIN_EDGE_COLOUR_CODE,
-				matrix: new THREE.Matrix4(),
-				type: 'Model',
-				groupObject: null,
-				// If false, it is a root material scope previous to parse
-				isFromParse: true,
-				faces: [],
-				lineSegments: [],
-				conditionalSegments: [],
-				totalFaces: 0,
-				faceMaterials: new Set(),
-				// If true, this object is the start of a construction step
-				startingConstructionStep: false
-			};
-			return newParseScope;
+			this.fileMap = fileMap;
+			return this;
 
 		}
 
-		addMaterial( material, parseScope ) {
+		addMaterial( material ) {
 
 			// Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
-			const matLib = parseScope.lib;
+			const matLib = this.materialLibrary;
 
 			if ( ! matLib[ material.userData.code ] ) {
 
 				this.materials.push( material );
+				matLib[ material.userData.code ] = material;
 
 			}
 
-			matLib[ material.userData.code ] = material;
 			return this;
 
 		}
 
-		getMaterial( colorCode, parseScope = this.rootParseScope ) {
+		getMaterial( colorCode ) {
 
-			// Given a color code search its material in the parse scopes stack
 			if ( colorCode.startsWith( '0x2' ) ) {
 
 				// Special 'direct' material value (RGB color)
@@ -1589,24 +1920,104 @@
 
 			}
 
-			while ( parseScope ) {
+			return this.materialLibrary[ colorCode ] || null;
+
+		} // Applies the appropriate materials to a prebuilt hierarchy of geometry. Assumes that color codes are present
+		// in the material array if they need to be filled in.
+
+
+		applyMaterialsToMesh( group, parentColorCode, materialHierarchy, finalMaterialPass = false ) {
+
+			// find any missing materials as indicated by a color code string and replace it with a material from the current material lib
+			const loader = this;
+			const parentIsPassthrough = parentColorCode === MAIN_COLOUR_CODE;
+			group.traverse( c => {
+
+				if ( c.isMesh || c.isLineSegments ) {
+
+					if ( Array.isArray( c.material ) ) {
 
-				const material = parseScope.lib[ colorCode ];
+						for ( let i = 0, l = c.material.length; i < l; i ++ ) {
 
-				if ( material ) {
+							if ( ! c.material[ i ].isMaterial ) {
 
-					return material;
+								c.material[ i ] = getMaterial( c, c.material[ i ] );
+
+							}
+
+						}
+
+					} else if ( ! c.material.isMaterial ) {
+
+						c.material = getMaterial( c, c.material );
+
+					}
+
+				}
+
+			} ); // Returns the appropriate material for the object (line or face) given color code. If the code is "pass through"
+			// (24 for lines, 16 for edges) then the pass through color code is used. If that is also pass through then it's
+			// simply returned for the subsequent material application.
+
+			function getMaterial( c, colorCode ) {
+
+				// if our parent is a passthrough color code and we don't have the current material color available then
+				// return early.
+				if ( parentIsPassthrough && ! ( colorCode in materialHierarchy ) && ! finalMaterialPass ) {
+
+					return colorCode;
+
+				}
+
+				const forEdge = c.isLineSegments || c.isConditionalLine;
+				const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE;
+
+				if ( isPassthrough ) {
+
+					colorCode = parentColorCode;
+
+				}
+
+				let material = null;
+
+				if ( colorCode in materialHierarchy ) {
+
+					material = materialHierarchy[ colorCode ];
+
+				} else if ( finalMaterialPass ) {
+
+					// see if we can get the final material from from the "getMaterial" function which will attempt to
+					// parse the "direct" colors
+					material = loader.getMaterial( colorCode );
+
+					if ( material === null ) {
+
+						// otherwise throw an error if this is final opportunity to set the material
+						throw new Error( `LDrawLoader: Material properties for code ${colorCode} not available.` );
+
+					}
 
 				} else {
 
-					parseScope = parseScope.parentScope;
+					return colorCode;
 
 				}
 
-			} // Material was not found
+				if ( c.isLineSegments ) {
+
+					material = material.userData.edgeMaterial;
 
+					if ( c.isConditionalLine ) {
 
-			return null;
+						material = material.userData.conditionalEdgeMaterial;
+
+					}
+
+				}
+
+				return material;
+
+			}
 
 		}
 
@@ -1867,117 +2278,9 @@
 			material.userData.code = code;
 			material.name = name;
 			material.userData.edgeMaterial = edgeMaterial;
+			this.addMaterial( material );
 			return material;
 
-		} //
-
-
-		objectParse( info, parseScope ) {
-
-			// Retrieve data from the parent parse scope
-			const currentParseScope = parseScope;
-			const parentParseScope = currentParseScope.parentScope; // Main color codes passed to this subobject (or default codes 16 and 24 if it is the root object)
-
-			const mainColorCode = currentParseScope.mainColorCode;
-			const mainEdgeColorCode = currentParseScope.mainEdgeColorCode;
-
-			const parseColorCode = ( colorCode, forEdge ) => {
-
-				// Parses next color code and returns a THREE.Material
-				if ( ! forEdge && colorCode === MAIN_COLOUR_CODE ) {
-
-					colorCode = mainColorCode;
-
-				}
-
-				if ( forEdge && colorCode === MAIN_EDGE_COLOUR_CODE ) {
-
-					colorCode = mainEdgeColorCode;
-
-				}
-
-				const material = this.getMaterial( colorCode, currentParseScope );
-
-				if ( ! material ) {
-
-					throw new Error( 'LDrawLoader: Unknown color code "' + colorCode + '" is used but it was not defined previously.' );
-
-				}
-
-				return material;
-
-			};
-
-			const faces = info.faces;
-			const lineSegments = info.lineSegments;
-			const conditionalSegments = info.conditionalSegments;
-			const materials = info.materials;
-
-			if ( currentParseScope.inverted ) {
-
-				faces.reverse();
-
-			}
-
-			for ( const colorCode in materials ) {
-
-				this.addMaterial( materials[ colorCode ], currentParseScope );
-
-			}
-
-			for ( let i = 0, l = faces.length; i < l; i ++ ) {
-
-				const face = faces[ i ];
-
-				if ( face.material === null ) {
-
-					face.material = parseColorCode( face.colorCode, false );
-
-				}
-
-			}
-
-			for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
-
-				const ls = lineSegments[ i ];
-
-				if ( ls.material === null ) {
-
-					ls.material = parseColorCode( ls.colorCode, true );
-
-				}
-
-			}
-
-			for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
-
-				const cs = conditionalSegments[ i ];
-
-				if ( cs.material === null ) {
-
-					cs.material = parseColorCode( cs.colorCode, true );
-
-				}
-
-			}
-
-			currentParseScope.faces = info.faces;
-			currentParseScope.conditionalSegments = info.conditionalSegments;
-			currentParseScope.lineSegments = info.lineSegments;
-			currentParseScope.category = info.category;
-			currentParseScope.keywords = info.keywords;
-			currentParseScope.subobjects = info.subobjects;
-			currentParseScope.type = info.type;
-			currentParseScope.totalFaces = info.totalFaces;
-			const isRoot = ! parentParseScope.isFromParse;
-
-			if ( isRoot || ! isPrimitiveType( info.type ) ) {
-
-				currentParseScope.groupObject = new THREE.Group();
-				currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
-
-			}
-
 		}
 
 		computeConstructionSteps( model ) {
@@ -2003,206 +2306,6 @@
 
 		}
 
-		finalizeObject( subobjectParseScope ) {
-
-			// fail gracefully if an object could not be loaded
-			if ( subobjectParseScope === null ) {
-
-				return;
-
-			}
-
-			const parentParseScope = subobjectParseScope.parentScope; // Smooth the normals if this is a part or if this is a case where the subpart
-			// is added directly into the parent model (meaning it will never get smoothed by
-			// being added to a part)
-
-			const doSmooth = isPartType( subobjectParseScope.type ) || isPrimitiveType( subobjectParseScope.type ) && isModelType( subobjectParseScope.parentScope.type );
-
-			if ( this.smoothNormals && doSmooth ) {
-
-				generateFaceNormals( subobjectParseScope.faces ); // only check subsetgments if we have multiple materials in a single part because this seems to be the case where it's needed most --
-				// there may be cases where a single edge line crosses over polygon edges that are broken up by multiple materials.
-
-				const checkSubSegments = subobjectParseScope.faceMaterials.size > 1;
-				smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments, checkSubSegments );
-
-			}
-
-			const isRoot = ! parentParseScope.isFromParse;
-
-			if ( ! isPrimitiveType( subobjectParseScope.type ) || isRoot ) {
-
-				const objGroup = subobjectParseScope.groupObject;
-
-				if ( subobjectParseScope.faces.length > 0 ) {
-
-					objGroup.add( createObject( subobjectParseScope.faces, 3, false, subobjectParseScope.totalFaces ) );
-
-				}
-
-				if ( subobjectParseScope.lineSegments.length > 0 ) {
-
-					objGroup.add( createObject( subobjectParseScope.lineSegments, 2 ) );
-
-				}
-
-				if ( subobjectParseScope.conditionalSegments.length > 0 ) {
-
-					objGroup.add( createObject( subobjectParseScope.conditionalSegments, 2, true ) );
-
-				}
-
-				if ( parentParseScope.groupObject ) {
-
-					objGroup.name = subobjectParseScope.fileName;
-					objGroup.userData.category = subobjectParseScope.category;
-					objGroup.userData.keywords = subobjectParseScope.keywords;
-					subobjectParseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
-					parentParseScope.groupObject.add( objGroup );
-
-				}
-
-			} else {
-
-				const parentLineSegments = parentParseScope.lineSegments;
-				const parentConditionalSegments = parentParseScope.conditionalSegments;
-				const parentFaces = parentParseScope.faces;
-				const parentFaceMaterials = parentParseScope.faceMaterials;
-				const lineSegments = subobjectParseScope.lineSegments;
-				const conditionalSegments = subobjectParseScope.conditionalSegments;
-				const faces = subobjectParseScope.faces;
-				const faceMaterials = subobjectParseScope.faceMaterials;
-				const matrix = subobjectParseScope.matrix;
-				const matrixScaleInverted = matrix.determinant() < 0;
-
-				for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
-
-					const ls = lineSegments[ i ];
-					const vertices = ls.vertices;
-					vertices[ 0 ].applyMatrix4( matrix );
-					vertices[ 1 ].applyMatrix4( matrix );
-					parentLineSegments.push( ls );
-
-				}
-
-				for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
-
-					const os = conditionalSegments[ i ];
-					const vertices = os.vertices;
-					const controlPoints = os.controlPoints;
-					vertices[ 0 ].applyMatrix4( matrix );
-					vertices[ 1 ].applyMatrix4( matrix );
-					controlPoints[ 0 ].applyMatrix4( matrix );
-					controlPoints[ 1 ].applyMatrix4( matrix );
-					parentConditionalSegments.push( os );
-
-				}
-
-				for ( let i = 0, l = faces.length; i < l; i ++ ) {
-
-					const tri = faces[ i ];
-					const vertices = tri.vertices;
-
-					for ( let i = 0, l = vertices.length; i < l; i ++ ) {
-
-						vertices[ i ].applyMatrix4( matrix );
-
-					} // If the scale of the object is negated then the triangle winding order
-					// needs to be flipped.
-
-
-					if ( matrixScaleInverted !== subobjectParseScope.inverted ) {
-
-						vertices.reverse();
-
-					}
-
-					parentFaces.push( tri );
-
-				}
-
-				parentParseScope.totalFaces += subobjectParseScope.totalFaces;
-				faceMaterials.forEach( material => parentFaceMaterials.add( material ) );
-
-			}
-
-		}
-
-		async processObject( parsedInfo, subobject, url, parentScope ) {
-
-			const scope = this;
-			const parseScope = this.newParseScopeLevel( null, parentScope );
-			parseScope.url = url; // Set current matrix
-
-			if ( subobject ) {
-
-				parseScope.matrix.copy( subobject.matrix );
-				parseScope.inverted = subobject.inverted;
-				parseScope.startingConstructionStep = subobject.startingConstructionStep;
-				parseScope.fileName = subobject.fileName;
-
-				if ( subobject.colorCode === MAIN_COLOUR_CODE && parentScope ) {
-
-					parseScope.mainColorCode = parentScope.mainColorCode;
-					parseScope.mainEdgeColorCode = parentScope.mainEdgeColorCode;
-
-				} else if ( subobject.colorCode !== MAIN_COLOUR_CODE ) {
-
-					parseScope.mainColorCode = subobject.colorCode;
-					parseScope.mainEdgeColorCode = subobject.colorCode;
-
-				}
-
-			} // Parse the object
-
-
-			this.objectParse( parsedInfo, parseScope );
-			const subobjects = parseScope.subobjects;
-			const promises = [];
-
-			for ( let i = 0, l = subobjects.length; i < l; i ++ ) {
-
-				promises.push( loadSubobject( subobjects[ i ] ) );
-
-			} // Kick off of the downloads in parallel but process all the subobjects
-			// in order so all the assembly instructions are correct
-
-
-			const subobjectScopes = await Promise.all( promises );
-
-			for ( let i = 0, l = subobjectScopes.length; i < l; i ++ ) {
-
-				this.finalizeObject( subobjectScopes[ i ] );
-
-			} // If it is root object then finalize this object and compute construction steps
-
-
-			if ( ! parentScope.isFromParse ) {
-
-				this.finalizeObject( parseScope );
-				this.computeConstructionSteps( parseScope.groupObject );
-
-			}
-
-			return parseScope;
-
-			function loadSubobject( subobject ) {
-
-				return scope.parseCache.loadData( subobject.fileName ).then( function ( parsedInfo ) {
-
-					return scope.processObject( parsedInfo, subobject, url, parseScope );
-
-				} ).catch( function ( err ) {
-
-					console.warn( err );
-					return null;
-
-				} );
-
-			}
-
-		}
-
 	}
 
 	THREE.LDrawLoader = LDrawLoader;

+ 5 - 5
examples/js/loaders/MD2Loader.js

@@ -114,7 +114,7 @@
 
 				for ( let j = 0; j < 16; j ++ ) {
 
-					const character = data.getUint8( offset + j, true );
+					const character = data.getUint8( offset + j );
 					if ( character === 0 ) break;
 					string[ j ] = character;
 
@@ -129,11 +129,11 @@
 
 				for ( let j = 0; j < header.num_vertices; j ++ ) {
 
-					let x = data.getUint8( offset ++, true );
-					let y = data.getUint8( offset ++, true );
-					let z = data.getUint8( offset ++, true );
+					let x = data.getUint8( offset ++ );
+					let y = data.getUint8( offset ++ );
+					let z = data.getUint8( offset ++ );
 
-					const n = _normalData[ data.getUint8( offset ++, true ) ];
+					const n = _normalData[ data.getUint8( offset ++ ) ];
 
 					x = x * scale.x + translation.x;
 					y = y * scale.y + translation.y;

+ 0 - 6
examples/js/modifiers/EdgeSplitModifier.js

@@ -166,12 +166,6 @@
 
 			if ( geometry.index == null ) {
 
-				if ( THREE.BufferGeometryUtils === undefined ) {
-
-					throw new Error( 'THREE.EdgeSplitModifier relies on THREE.BufferGeometryUtils' );
-
-				}
-
 				geometry = THREE.BufferGeometryUtils.mergeVertices( geometry );
 
 			}

+ 0 - 10
examples/js/modifiers/SimplifyModifier.js

@@ -13,16 +13,6 @@
 
 	class SimplifyModifier {
 
-		constructor() {
-
-			if ( THREE.BufferGeometryUtils === undefined ) {
-
-				throw new Error( 'THREE.SimplifyModifier relies on THREE.BufferGeometryUtils' );
-
-			}
-
-		}
-
 		modify( geometry, count ) {
 
 			if ( geometry.isGeometry === true ) {

+ 20 - 11
examples/js/utils/LDrawUtils.js

@@ -47,7 +47,11 @@
 						arr: [ geometry ]
 					};
 
-				} else geoms.arr.push( geometry );
+				} else {
+
+					geoms.arr.push( geometry );
+
+				}
 
 			}
 
@@ -87,23 +91,24 @@
 				if ( c.isMesh | c.isLineSegments ) {
 
 					const elemSize = c.isMesh ? 3 : 2;
+					const geometry = c.geometry.clone();
 					const matrixIsInverted = c.matrixWorld.determinant() < 0;
 
 					if ( matrixIsInverted ) {
 
-						permuteAttribute( c.geometry.attributes.position, elemSize );
-						permuteAttribute( c.geometry.attributes.normal, elemSize );
+						permuteAttribute( geometry.attributes.position, elemSize );
+						permuteAttribute( geometry.attributes.normal, elemSize );
 
 					}
 
-					c.geometry.applyMatrix4( c.matrixWorld );
+					geometry.applyMatrix4( c.matrixWorld );
 
 					if ( c.isConditionalLine ) {
 
-						c.geometry.attributes.control0.applyMatrix4( c.matrixWorld );
-						c.geometry.attributes.control1.applyMatrix4( c.matrixWorld );
+						geometry.attributes.control0.applyMatrix4( c.matrixWorld );
+						geometry.attributes.control1.applyMatrix4( c.matrixWorld );
 						normalMatrix.getNormalMatrix( c.matrixWorld );
-						c.geometry.attributes.direction.applyNormalMatrix( normalMatrix );
+						geometry.attributes.direction.applyNormalMatrix( normalMatrix );
 
 					}
 
@@ -111,16 +116,20 @@
 
 					if ( Array.isArray( c.material ) ) {
 
-						for ( const groupIndex in c.geometry.groups ) {
+						for ( const groupIndex in geometry.groups ) {
 
-							const group = c.geometry.groups[ groupIndex ];
+							const group = geometry.groups[ groupIndex ];
 							const mat = c.material[ group.materialIndex ];
-							const newGeometry = extractGroup( c.geometry, group, elemSize, c.isConditionalLine );
+							const newGeometry = extractGroup( geometry, group, elemSize, c.isConditionalLine );
 							addGeometry( mat, newGeometry, geometries );
 
 						}
 
-					} else addGeometry( c.material, c.geometry, geometries );
+					} else {
+
+						addGeometry( c.material, geometry, geometries );
+
+					}
 
 				}