Browse Source

Merge pull request #16424 from gkjohnson/ldraw-group-parts

LDrawLoader: Group object by Parts and smooth normals
Mr.doob 6 years ago
parent
commit
0a68a7d153
2 changed files with 469 additions and 139 deletions
  1. 423 138
      examples/js/loaders/LDrawLoader.js
  2. 46 1
      examples/webgl_loader_ldraw.html

+ 423 - 138
examples/js/loaders/LDrawLoader.js

@@ -7,6 +7,201 @@
 
 THREE.LDrawLoader = ( function () {
 
+	var tempVec0 = new THREE.Vector3();
+	var tempVec1 = new THREE.Vector3();
+	function smoothNormals( triangles, lineSegments ) {
+
+		function hashVertex( v ) {
+
+			// NOTE: 1e2 is pretty coarse but was chosen because it allows edges
+			// to be smoothed as expected (see minifig arms). The errors between edges
+			// could be due to matrix multiplication.
+			var x = ~ ~ ( v.x * 1e2 );
+			var y = ~ ~ ( v.y * 1e2 );
+			var z = ~ ~ ( v.z * 1e2 );
+			return `${ x },${ y },${ z }`;
+
+		}
+
+		function hashEdge( v0, v1 ) {
+
+			return `${ hashVertex( v0 ) }_${ hashVertex( v1 ) }`;
+
+		}
+
+		var hardEdges = new Set();
+		var halfEdgeList = {};
+		var fullHalfEdgeList = {};
+		var normals = [];
+
+		// Save the list of hard edges by hash
+		for ( var i = 0, l = lineSegments.length; i < l; i ++ ) {
+
+			var ls = lineSegments[ i ];
+			var v0 = ls.v0;
+			var v1 = ls.v1;
+			hardEdges.add( hashEdge( v0, v1 ) );
+			hardEdges.add( hashEdge( v1, v0 ) );
+
+		}
+
+		// track the half edges associated with each triangle
+		for ( var i = 0, l = triangles.length; i < l; i ++ ) {
+
+			var tri = triangles[ i ];
+			for ( var i2 = 0, l2 = 3; i2 < l2; i2 ++ ) {
+
+				var index = i2;
+				var next = ( i2 + 1 ) % 3;
+				var v0 = tri[ `v${ index }` ];
+				var v1 = tri[ `v${ next }` ];
+				var hash = hashEdge( v0, v1 );
+
+				// don't add the triangle if the edge is supposed to be hard
+				if ( hardEdges.has( hash ) ) continue;
+				halfEdgeList[ hash ] = tri;
+				fullHalfEdgeList[ hash ] = tri;
+
+			}
+
+		}
+
+		// NOTE: Some of the normals wind up being skewed in an unexpected way because
+		// quads provide more "influence" to some vertex normals than a triangle due to
+		// the fact that a quad is made up of two triangles and all triangles are weighted
+		// equally. To fix this quads could be tracked separately so their vertex normals
+		// are weighted appropriately or we could try only adding a normal direction
+		// once per normal.
+
+		// Iterate until we've tried to connect all triangles to share normals
+		while ( true ) {
+
+			// Stop if there are no more triangles left
+			var halfEdges = Object.keys( halfEdgeList );
+			if ( halfEdges.length === 0 ) break;
+
+			// Exhaustively find all connected triangles
+			var i = 0;
+			var queue = [ fullHalfEdgeList[ halfEdges[ 0 ] ] ];
+			while ( i < queue.length ) {
+
+				// initialize all vertex normals in this triangle
+				var tri = queue[ i ];
+				i ++;
+
+				var faceNormal = tri.faceNormal;
+				if ( tri.n0 === null ) {
+
+					tri.n0 = faceNormal.clone();
+					normals.push( tri.n0 );
+
+				}
+
+				if ( tri.n1 === null ) {
+
+					tri.n1 = faceNormal.clone();
+					normals.push( tri.n1 );
+
+				}
+
+				if ( tri.n2 === null ) {
+
+					tri.n2 = faceNormal.clone();
+					normals.push( tri.n2 );
+
+				}
+
+				// Check if any edge is connected to another triangle edge
+				for ( var i2 = 0, l2 = 3; i2 < l2; i2 ++ ) {
+
+					var index = i2;
+					var next = ( i2 + 1 ) % 3;
+					var v0 = tri[ `v${ index }` ];
+					var v1 = tri[ `v${ next }` ];
+
+					// delete this triangle from the list so it won't be found again
+					var hash = hashEdge( v0, v1 );
+					delete halfEdgeList[ hash ];
+
+					var reverseHash = hashEdge( v1, v0 );
+					var otherTri = fullHalfEdgeList[ reverseHash ];
+					if ( otherTri ) {
+
+						// NOTE: If the angle between triangles is > 67.5 degrees then assume it's
+						// hard edge. There are some cases where the line segments do not line up exactly
+						// with or span multiple triangle edges (see Lunar Vehicle wheels).
+						if ( Math.abs( otherTri.faceNormal.dot( tri.faceNormal ) ) < 0.25 ) {
+
+							continue;
+
+						}
+
+						// if this triangle has already been traversed then it won't be in
+						// the halfEdgeList. If it has not then add it to the queue and delete
+						// it so it won't be found again.
+						if ( reverseHash in halfEdgeList ) {
+
+							queue.push( otherTri );
+							delete halfEdgeList[ reverseHash ];
+
+						}
+
+						// Find the matching edge in this triangle and copy the normal vector over
+						for ( var i3 = 0, l3 = 3; i3 < l3; i3 ++ ) {
+
+							var otherIndex = i3;
+							var otherNext = ( i3 + 1 ) % 3;
+							var otherV0 = otherTri[ `v${ otherIndex }` ];
+							var otherV1 = otherTri[ `v${ otherNext }` ];
+
+							var otherHash = hashEdge( otherV0, otherV1 );
+							if ( otherHash === reverseHash ) {
+
+								if ( otherTri[ `n${ otherIndex }` ] === null ) {
+
+									var norm = tri[ `n${ next }` ];
+									otherTri[ `n${ otherIndex }` ] = norm;
+									norm.add( otherTri.faceNormal );
+
+								}
+
+								if ( otherTri[ `n${ otherNext }` ] === null ) {
+
+									var norm = tri[ `n${ index }` ];
+									otherTri[ `n${ otherNext }` ] = norm;
+									norm.add( otherTri.faceNormal );
+
+								}
+
+								break;
+
+							}
+
+						}
+
+					}
+
+				}
+
+			}
+
+		}
+
+		// The normals of each face have been added up so now we average them by normalizing the vector.
+		for ( var i = 0, l = normals.length; i < l; i ++ ) {
+
+			normals[ i ].normalize();
+
+		}
+
+	}
+
+	function isPrimitiveType( type ) {
+
+		return /primitive/i.test( type ) || type === 'Subpart';
+
+	}
+
 	function LineParser( line, lineNumber ) {
 
 		this.line = line;
@@ -119,11 +314,11 @@ THREE.LDrawLoader = ( function () {
 		// Sort the triangles or line segments by colour code to make later the mesh groups
 		elements.sort( sortByMaterial );
 
-		var vertices = [];
+		var positions = [];
+		var normals = [];
 		var materials = [];
 
 		var bufferGeometry = new THREE.BufferGeometry();
-		bufferGeometry.clearGroups();
 		var prevMaterial = null;
 		var index0 = 0;
 		var numGroupVerts = 0;
@@ -134,10 +329,17 @@ THREE.LDrawLoader = ( function () {
 			var v0 = elem.v0;
 			var v1 = elem.v1;
 			// Note that LDraw coordinate system is rotated 180 deg. in the X axis w.r.t. Three.js's one
-			vertices.push( v0.x, v0.y, v0.z, v1.x, v1.y, v1.z );
+			positions.push( v0.x, v0.y, v0.z, v1.x, v1.y, v1.z );
 			if ( elementSize === 3 ) {
 
-				vertices.push( elem.v2.x, elem.v2.y, elem.v2.z );
+				positions.push( elem.v2.x, elem.v2.y, elem.v2.z );
+
+				var n0 = elem.n0 || elem.faceNormal;
+				var n1 = elem.n1 || elem.faceNormal;
+				var n2 = elem.n2 || elem.faceNormal;
+				normals.push( n0.x, n0.y, n0.z );
+				normals.push( n1.x, n1.y, n1.z );
+				normals.push( n2.x, n2.y, n2.z );
 
 			}
 
@@ -169,7 +371,13 @@ THREE.LDrawLoader = ( function () {
 
 		}
 
-		bufferGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+		bufferGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
+
+		if ( elementSize === 3 ) {
+
+			bufferGeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
+
+		}
 
 		var object3d = null;
 
@@ -179,8 +387,6 @@ THREE.LDrawLoader = ( function () {
 
 		} else if ( elementSize === 3 ) {
 
-			bufferGeometry.computeVertexNormals();
-
 			object3d = new THREE.Mesh( bufferGeometry, materials );
 
 		}
@@ -223,10 +429,8 @@ THREE.LDrawLoader = ( function () {
 		// If not (the default), only one object which contains all the merged primitives will be created.
 		this.separateObjects = false;
 
-		// Current merged object and primitives
-		this.currentGroupObject = null;
-		this.currentTriangles = null;
-		this.currentLineSegments = null;
+		// If this flag is set to true the vertex normals will be smoothed.
+		this.smoothNormals = true;
 
 	}
 
@@ -278,6 +482,15 @@ THREE.LDrawLoader = ( function () {
 
 				var parentParseScope = scope.getParentParseScope();
 
+				// Set current matrix
+				if ( subobject ) {
+
+					parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
+					parseScope.matrix.copy( subobject.matrix );
+					parseScope.inverted = subobject.inverted;
+
+				}
+
 				// Add to cache
 				var currentFileName = parentParseScope.currentFileName;
 				if ( currentFileName !== null ) {
@@ -292,21 +505,12 @@ THREE.LDrawLoader = ( function () {
 
 				}
 
-				parseScope.inverted = subobject !== undefined ? subobject.inverted : false;
 
 				// Parse the object (returns a THREE.Group)
-				var objGroup = scope.parse( text );
-
-				// Load subobjects
-				parseScope.subobjects = objGroup.userData.subobjects;
-				parseScope.numSubobjects = parseScope.subobjects.length;
-				parseScope.subobjectIndex = 0;
-
+				scope.parse( text );
 				var finishedCount = 0;
 				onSubobjectFinish();
 
-				return objGroup;
-
 				function onSubobjectFinish() {
 
 					finishedCount ++;
@@ -337,18 +541,101 @@ THREE.LDrawLoader = ( function () {
 
 				function finalizeObject() {
 
-					if ( ! scope.separateObjects && ! parentParseScope.isFromParse ) {
+					if ( scope.smoothNormals && parseScope.type === 'Part' ) {
+
+						smoothNormals( parseScope.triangles, parseScope.lineSegments );
+
+					}
+
+					var isRoot = ! parentParseScope.isFromParse;
+					if ( scope.separateObjects && ! isPrimitiveType( parseScope.type ) || isRoot ) {
+
+
+						const objGroup = parseScope.groupObject;
+						if ( parseScope.triangles.length > 0 ) {
+
+							objGroup.add( createObject( parseScope.triangles, 3 ) );
+
+						}
+
+						if ( parseScope.lineSegments.length > 0 ) {
+
+							objGroup.add( createObject( parseScope.lineSegments, 2 ) );
+
+						}
+
+						if ( parseScope.conditionalSegments.length > 0 ) {
 
-						// We are finalizing the root object and merging primitives is activated, so create the entire Mesh and LineSegments objects now
-						if ( scope.currentLineSegments.length > 0 ) {
+							var lines = createObject( parseScope.conditionalSegments, 2 );
+							lines.isConditionalLine = true;
+							lines.visible = false;
+							objGroup.add( lines );
 
-							objGroup.add( createObject( scope.currentLineSegments, 2 ) );
+						}
+
+						if ( parentParseScope.groupObject ) {
+
+							objGroup.name = parseScope.fileName;
+							objGroup.userData.category = parseScope.category;
+							objGroup.userData.keywords = parseScope.keywords;
+							parseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
+
+							parentParseScope.groupObject.add( objGroup );
+
+						}
+
+					} else {
+
+						var separateObjects = scope.separateObjects;
+						var parentLineSegments = parentParseScope.lineSegments;
+						var parentConditionalSegments = parentParseScope.conditionalSegments;
+						var parentTriangles = parentParseScope.triangles;
+
+						var lineSegments = parseScope.lineSegments;
+						var conditionalSegments = parseScope.conditionalSegments;
+						var triangles = parseScope.triangles;
+
+						for ( var i = 0, l = lineSegments.length; i < l; i ++ ) {
+
+							var ls = lineSegments[ i ];
+							if ( separateObjects ) {
+
+								ls.v0.applyMatrix4( parseScope.matrix );
+								ls.v1.applyMatrix4( parseScope.matrix );
+
+							}
+							parentLineSegments.push( ls );
+
+						}
+
+						for ( var i = 0, l = conditionalSegments.length; i < l; i ++ ) {
+
+							var os = conditionalSegments[ i ];
+							if ( separateObjects ) {
+
+								os.v0.applyMatrix4( parseScope.matrix );
+								os.v1.applyMatrix4( parseScope.matrix );
+
+							}
+							parentConditionalSegments.push( os );
 
 						}
 
-						if ( scope.currentTriangles.length > 0 ) {
+						for ( var i = 0, l = triangles.length; i < l; i ++ ) {
+
+							var tri = triangles[ i ];
+							if ( separateObjects ) {
 
-							objGroup.add( createObject( scope.currentTriangles, 3 ) );
+								tri.v0 = tri.v0.clone().applyMatrix4( parseScope.matrix );
+								tri.v1 = tri.v1.clone().applyMatrix4( parseScope.matrix );
+								tri.v2 = tri.v2.clone().applyMatrix4( parseScope.matrix );
+
+								tempVec0.subVectors( tri.v1, tri.v0 );
+								tempVec1.subVectors( tri.v2, tri.v1 );
+								tri.faceNormal.crossVectors( tempVec0, tempVec1 ).normalize();
+
+							}
+							parentTriangles.push( tri );
 
 						}
 
@@ -358,7 +645,7 @@ THREE.LDrawLoader = ( function () {
 
 					if ( onProcessed ) {
 
-						onProcessed( objGroup );
+						onProcessed( parseScope.groupObject );
 
 					}
 
@@ -370,12 +657,6 @@ THREE.LDrawLoader = ( function () {
 					parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
 					parseScope.currentFileName = subobject.originalFileName;
 
-					if ( ! scope.separateObjects ) {
-
-						// Set current matrix
-						parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
-
-					}
 
 					// If subobject was cached previously, use the cached one
 					var cached = scope.subobjectCache[ subobject.originalFileName.toLowerCase() ];
@@ -485,22 +766,6 @@ THREE.LDrawLoader = ( function () {
 
 					}
 
-					// Add the subobject just loaded
-					addSubobject( subobject, subobjectGroup );
-
-				}
-
-				function addSubobject( subobject, subobjectGroup ) {
-
-					if ( scope.separateObjects ) {
-
-						subobjectGroup.name = subobject.fileName;
-						objGroup.add( subobjectGroup );
-						subobjectGroup.matrix.copy( subobject.matrix );
-						subobjectGroup.matrixAutoUpdate = false;
-
-					}
-
 					scope.fileMap[ subobject.originalFileName ] = subobject.url;
 
 				}
@@ -536,8 +801,6 @@ THREE.LDrawLoader = ( function () {
 
 			this.materials = materials;
 
-			this.currentGroupObject = null;
-
 			return this;
 
 		},
@@ -568,9 +831,6 @@ THREE.LDrawLoader = ( function () {
 			}
 
 			var topParseScope = this.getCurrentParseScope();
-
-			var parentParseScope = this.getParentParseScope();
-
 			var newParseScope = {
 
 				lib: matLib,
@@ -581,15 +841,22 @@ THREE.LDrawLoader = ( function () {
 				numSubobjects: 0,
 				subobjectIndex: 0,
 				inverted: false,
+				category: null,
+				keywords: null,
 
 				// Current subobject
 				currentFileName: null,
 				mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
 				mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
 				currentMatrix: new THREE.Matrix4(),
+				matrix: new THREE.Matrix4(),
 
 				// If false, it is a root material scope previous to parse
-				isFromParse: true
+				isFromParse: true,
+
+				triangles: null,
+				lineSegments: null,
+				conditionalSegments: null,
 			};
 
 			this.parseScopesStack.push( newParseScope );
@@ -932,26 +1199,7 @@ THREE.LDrawLoader = ( function () {
 			// Parse result variables
 			var triangles;
 			var lineSegments;
-
-			if ( this.separateObjects ) {
-
-				triangles = [];
-				lineSegments = [];
-
-			} else {
-
-				if ( this.currentGroupObject === null ) {
-
-					this.currentGroupObject = new THREE.Group();
-					this.currentTriangles = [];
-					this.currentLineSegments = [];
-
-				}
-
-				triangles = this.currentTriangles;
-				lineSegments = this.currentLineSegments;
-
-			}
+			var conditionalSegments;
 
 			var subobjects = [];
 
@@ -973,6 +1221,12 @@ THREE.LDrawLoader = ( function () {
 			var currentEmbeddedFileName = null;
 			var currentEmbeddedText = null;
 
+			var bfcCertified = false;
+			var bfcCCW = true;
+			var bfcInverted = false;
+			var bfcCull = true;
+			var type = '';
+
 			var scope = this;
 			function parseColourCode( lineParser, forEdge ) {
 
@@ -1009,7 +1263,7 @@ THREE.LDrawLoader = ( function () {
 
 				if ( ! scope.separateObjects ) {
 
-					v.applyMatrix4( parentParseScope.currentMatrix );
+					v.applyMatrix4( currentParseScope.currentMatrix );
 
 				}
 
@@ -1017,11 +1271,6 @@ THREE.LDrawLoader = ( function () {
 
 			}
 
-			var bfcCertified = false;
-			var bfcCCW = true;
-			var bfcInverted = false;
-			var bfcCull = true;
-
 			// Parse all line commands
 			for ( lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
 
@@ -1076,6 +1325,45 @@ THREE.LDrawLoader = ( function () {
 
 							switch ( meta ) {
 
+								case '!LDRAW_ORG':
+
+									type = lp.getToken();
+
+									if ( ! parsingEmbeddedFiles ) {
+
+										currentParseScope.triangles = [];
+										currentParseScope.lineSegments = [];
+										currentParseScope.conditionalSegments = [];
+										currentParseScope.type = type;
+
+										var isRoot = ! parentParseScope.isFromParse;
+										if ( isRoot || scope.separateObjects && ! isPrimitiveType( type ) ) {
+
+											currentParseScope.groupObject = new THREE.Group();
+
+										}
+
+										// If the scale of the object is negated then the triangle winding order
+										// needs to be flipped.
+										var matrix = currentParseScope.matrix;
+										if (
+											matrix.determinant() < 0 && (
+												scope.separateObjects && isPrimitiveType( type ) ||
+												! scope.separateObjects
+											) ) {
+
+											currentParseScope.inverted = ! currentParseScope.inverted;
+
+										}
+
+										triangles = currentParseScope.triangles;
+										lineSegments = currentParseScope.lineSegments;
+										conditionalSegments = currentParseScope.conditionalSegments;
+
+									}
+
+									break;
+
 								case '!COLOUR':
 
 									var material = this.parseColourMetaDirective( lp );
@@ -1237,14 +1525,6 @@ THREE.LDrawLoader = ( function () {
 
 						}
 
-						// If the scale of the object is negated then the triangle winding order
-						// needs to be flipped.
-						if ( scope.separateObjects === false && matrix.determinant() < 0 ) {
-
-							bfcInverted = ! bfcInverted;
-
-						}
-
 						subobjects.push( {
 							material: material,
 							matrix: matrix,
@@ -1261,11 +1541,14 @@ THREE.LDrawLoader = ( function () {
 						break;
 
 					// Line type 2: Line segment
+					// Line type 5: Conditional Line segment
 					case '2':
+					case '5':
 
 						var material = parseColourCode( lp, true );
+						var arr = lineType === '2' ? lineSegments : conditionalSegments;
 
-						lineSegments.push( {
+						arr.push( {
 							material: material.userData.edgeMaterial,
 							colourCode: material.userData.code,
 							v0: parseVector( lp ),
@@ -1282,7 +1565,7 @@ THREE.LDrawLoader = ( function () {
 						var inverted = currentParseScope.inverted;
 						var ccw = bfcCCW !== inverted;
 						var doubleSided = ! bfcCertified || ! bfcCull;
-						var v0, v1, v2;
+						var v0, v1, v2, faceNormal;
 
 						if ( ccw === true ) {
 
@@ -1298,12 +1581,22 @@ THREE.LDrawLoader = ( function () {
 
 						}
 
+						tempVec0.subVectors( v1, v0 );
+						tempVec1.subVectors( v2, v1 );
+						faceNormal = new THREE.Vector3()
+							.crossVectors( tempVec0, tempVec1 )
+							.normalize();
+
 						triangles.push( {
 							material: material,
 							colourCode: material.userData.code,
 							v0: v0,
 							v1: v1,
-							v2: v2
+							v2: v2,
+							faceNormal: faceNormal,
+							n0: null,
+							n1: null,
+							n2: null
 						} );
 
 						if ( doubleSided === true ) {
@@ -1313,7 +1606,11 @@ THREE.LDrawLoader = ( function () {
 								colourCode: material.userData.code,
 								v0: v0,
 								v1: v2,
-								v2: v1
+								v2: v1,
+								faceNormal: faceNormal,
+								n0: null,
+								n1: null,
+								n2: null
 							} );
 
 						}
@@ -1328,7 +1625,7 @@ THREE.LDrawLoader = ( function () {
 						var inverted = currentParseScope.inverted;
 						var ccw = bfcCCW !== inverted;
 						var doubleSided = ! bfcCertified || ! bfcCull;
-						var v0, v1, v2, v3;
+						var v0, v1, v2, v3, faceNormal;
 
 						if ( ccw === true ) {
 
@@ -1346,12 +1643,22 @@ THREE.LDrawLoader = ( function () {
 
 						}
 
+						tempVec0.subVectors( v1, v0 );
+						tempVec1.subVectors( v2, v1 );
+						faceNormal = new THREE.Vector3()
+							.crossVectors( tempVec0, tempVec1 )
+							.normalize();
+
 						triangles.push( {
 							material: material,
 							colourCode: material.userData.code,
 							v0: v0,
 							v1: v1,
-							v2: v2
+							v2: v2,
+							faceNormal: faceNormal,
+							n0: null,
+							n1: null,
+							n2: null
 						} );
 
 						triangles.push( {
@@ -1359,7 +1666,11 @@ THREE.LDrawLoader = ( function () {
 							colourCode: material.userData.code,
 							v0: v0,
 							v1: v2,
-							v2: v3
+							v2: v3,
+							faceNormal: faceNormal,
+							n0: null,
+							n1: null,
+							n2: null
 						} );
 
 						if ( doubleSided === true ) {
@@ -1369,7 +1680,11 @@ THREE.LDrawLoader = ( function () {
 								colourCode: material.userData.code,
 								v0: v0,
 								v1: v2,
-								v2: v1
+								v2: v1,
+								faceNormal: faceNormal,
+								n0: null,
+								n1: null,
+								n2: null
 							} );
 
 							triangles.push( {
@@ -1377,18 +1692,17 @@ THREE.LDrawLoader = ( function () {
 								colourCode: material.userData.code,
 								v0: v0,
 								v1: v3,
-								v2: v2
+								v2: v2,
+								faceNormal: faceNormal,
+								n0: null,
+								n1: null,
+								n2: null
 							} );
 
 						}
 
 						break;
 
-					// Line type 5: Optional line
-					case '5':
-						// Line type 5 is not implemented
-						break;
-
 					default:
 						throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
 						break;
@@ -1403,40 +1717,11 @@ THREE.LDrawLoader = ( function () {
 
 			}
 
-			//
-
-			var groupObject = null;
-
-			if ( this.separateObjects ) {
-
-				groupObject = new THREE.Group();
-
-				if ( lineSegments.length > 0 ) {
-
-					groupObject.add( createObject( lineSegments, 2 ) );
-
-
-				}
-
-				if ( triangles.length > 0 ) {
-
-					groupObject.add( createObject( triangles, 3 ) );
-
-				}
-
-			} else {
-
-				groupObject = this.currentGroupObject;
-
-			}
-
-			groupObject.userData.category = category;
-			groupObject.userData.keywords = keywords;
-			groupObject.userData.subobjects = subobjects;
-
-			//console.timeEnd( 'LDrawLoader' );
-
-			return groupObject;
+			currentParseScope.category = category;
+			currentParseScope.keywords = keywords;
+			currentParseScope.subobjects = subobjects;
+			currentParseScope.numSubobjects = subobjects.length;
+			currentParseScope.subobjectIndex = 0;
 
 		}
 

+ 46 - 1
examples/webgl_loader_ldraw.html

@@ -109,7 +109,10 @@
 				guiData = {
 					modelFileName: modelFileList[ 'Car' ],
 					envMapActivated: false,
-					separateObjects: false
+					separateObjects: false,
+					displayLines: true,
+					conditionalLines: false,
+					smoothNormals: true
 				};
 
 				gui = new dat.GUI();
@@ -134,6 +137,23 @@
 
 				} );
 
+				gui.add( guiData, 'smoothNormals' ).name( 'Smooth Normals' ).onChange( function ( value ) {
+
+					reloadObject( false );
+
+				} );
+
+				gui.add( guiData, 'displayLines' ).name( 'Display Lines' ).onChange( function ( value ) {
+
+					updateLineSegments();
+
+				} );
+
+				gui.add( guiData, 'conditionalLines' ).name( 'Conditional Lines' ).onChange( function ( value ) {
+
+					updateLineSegments();
+
+				} );
 				window.addEventListener( 'resize', onWindowResize, false );
 
 				progressBarDiv = document.createElement( 'div' );
@@ -153,6 +173,28 @@
 
 			}
 
+			function updateLineSegments() {
+
+				model.traverse( c => {
+
+					if ( c.isLineSegments ) {
+
+						if ( c.isConditionalLine ) {
+
+							c.visible = guiData.conditionalLines;
+
+						} else {
+
+							c.visible = guiData.displayLines;
+
+						}
+
+					}
+
+				} );
+
+			}
+
 			function reloadObject( resetCamera ) {
 
 				if ( model ) {
@@ -168,6 +210,7 @@
 
 				var lDrawLoader = new THREE.LDrawLoader();
 				lDrawLoader.separateObjects = guiData.separateObjects;
+				lDrawLoader.smoothNormals = guiData.smoothNormals;
 				lDrawLoader
 					.setPath( ldrawPath )
 					.load( guiData.modelFileName, function ( group2 ) {
@@ -218,6 +261,8 @@
 
 						}
 
+						updateLineSegments();
+
 						// Adjust camera and light
 
 						var bbox = new THREE.Box3().setFromObject( model );