203 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			203 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | import { | ||
|  | 	BufferAttribute, | ||
|  | 	BufferGeometry, | ||
|  | 	Group, | ||
|  | 	LineSegments, | ||
|  | 	Matrix3, | ||
|  | 	Mesh | ||
|  | } from 'three'; | ||
|  | 
 | ||
|  | import { mergeGeometries } from './BufferGeometryUtils.js'; | ||
|  | 
 | ||
|  | class LDrawUtils { | ||
|  | 
 | ||
|  | 	static mergeObject( object ) { | ||
|  | 
 | ||
|  | 		// Merges geometries in object by materials and returns new object. Use on not indexed geometries.
 | ||
|  | 		// The object buffers reference the old object ones.
 | ||
|  | 		// Special treatment is done to the conditional lines generated by LDrawLoader.
 | ||
|  | 
 | ||
|  | 		function extractGroup( geometry, group, elementSize, isConditionalLine ) { | ||
|  | 
 | ||
|  | 			// Extracts a group from a geometry as a new geometry (with attribute buffers referencing original buffers)
 | ||
|  | 
 | ||
|  | 			const newGeometry = new BufferGeometry(); | ||
|  | 
 | ||
|  | 			const originalPositions = geometry.getAttribute( 'position' ).array; | ||
|  | 			const originalNormals = elementSize === 3 ? geometry.getAttribute( 'normal' ).array : null; | ||
|  | 
 | ||
|  | 			const numVertsGroup = Math.min( group.count, Math.floor( originalPositions.length / 3 ) - group.start ); | ||
|  | 			const vertStart = group.start * 3; | ||
|  | 			const vertEnd = ( group.start + numVertsGroup ) * 3; | ||
|  | 
 | ||
|  | 			const positions = originalPositions.subarray( vertStart, vertEnd ); | ||
|  | 			const normals = originalNormals !== null ? originalNormals.subarray( vertStart, vertEnd ) : null; | ||
|  | 
 | ||
|  | 			newGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); | ||
|  | 			if ( normals !== null ) newGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) ); | ||
|  | 
 | ||
|  | 			if ( isConditionalLine ) { | ||
|  | 
 | ||
|  | 				const controlArray0 = geometry.getAttribute( 'control0' ).array.subarray( vertStart, vertEnd ); | ||
|  | 				const controlArray1 = geometry.getAttribute( 'control1' ).array.subarray( vertStart, vertEnd ); | ||
|  | 				const directionArray = geometry.getAttribute( 'direction' ).array.subarray( vertStart, vertEnd ); | ||
|  | 
 | ||
|  | 				newGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) ); | ||
|  | 				newGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) ); | ||
|  | 				newGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			return newGeometry; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function addGeometry( mat, geometry, geometries ) { | ||
|  | 
 | ||
|  | 			const geoms = geometries[ mat.uuid ]; | ||
|  | 			if ( ! geoms ) { | ||
|  | 
 | ||
|  | 				geometries[ mat.uuid ] = { | ||
|  | 					mat: mat, | ||
|  | 					arr: [ geometry ] | ||
|  | 				}; | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				geoms.arr.push( geometry ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function permuteAttribute( attribute, elemSize ) { | ||
|  | 
 | ||
|  | 			// Permutes first two vertices of each attribute element
 | ||
|  | 
 | ||
|  | 			if ( ! attribute ) return; | ||
|  | 
 | ||
|  | 			const verts = attribute.array; | ||
|  | 			const numVerts = Math.floor( verts.length / 3 ); | ||
|  | 			let offset = 0; | ||
|  | 			for ( let i = 0; i < numVerts; i ++ ) { | ||
|  | 
 | ||
|  | 				const x = verts[ offset ]; | ||
|  | 				const y = verts[ offset + 1 ]; | ||
|  | 				const z = verts[ offset + 2 ]; | ||
|  | 
 | ||
|  | 				verts[ offset ] = verts[ offset + 3 ]; | ||
|  | 				verts[ offset + 1 ] = verts[ offset + 4 ]; | ||
|  | 				verts[ offset + 2 ] = verts[ offset + 5 ]; | ||
|  | 
 | ||
|  | 				verts[ offset + 3 ] = x; | ||
|  | 				verts[ offset + 4 ] = y; | ||
|  | 				verts[ offset + 5 ] = z; | ||
|  | 
 | ||
|  | 				offset += elemSize * 3; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Traverse the object hierarchy collecting geometries and transforming them to world space
 | ||
|  | 
 | ||
|  | 		const meshGeometries = {}; | ||
|  | 		const linesGeometries = {}; | ||
|  | 		const condLinesGeometries = {}; | ||
|  | 
 | ||
|  | 		object.updateMatrixWorld( true ); | ||
|  | 		const normalMatrix = new Matrix3(); | ||
|  | 
 | ||
|  | 		object.traverse( c => { | ||
|  | 
 | ||
|  | 			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( geometry.attributes.position, elemSize ); | ||
|  | 					permuteAttribute( geometry.attributes.normal, elemSize ); | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 				geometry.applyMatrix4( c.matrixWorld ); | ||
|  | 
 | ||
|  | 				if ( c.isConditionalLine ) { | ||
|  | 
 | ||
|  | 					geometry.attributes.control0.applyMatrix4( c.matrixWorld ); | ||
|  | 					geometry.attributes.control1.applyMatrix4( c.matrixWorld ); | ||
|  | 					normalMatrix.getNormalMatrix( c.matrixWorld ); | ||
|  | 					geometry.attributes.direction.applyNormalMatrix( normalMatrix ); | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 				const geometries = c.isMesh ? meshGeometries : ( c.isConditionalLine ? condLinesGeometries : linesGeometries ); | ||
|  | 
 | ||
|  | 				if ( Array.isArray( c.material ) ) { | ||
|  | 
 | ||
|  | 					for ( const groupIndex in geometry.groups ) { | ||
|  | 
 | ||
|  | 						const group = geometry.groups[ groupIndex ]; | ||
|  | 						const mat = c.material[ group.materialIndex ]; | ||
|  | 						const newGeometry = extractGroup( geometry, group, elemSize, c.isConditionalLine ); | ||
|  | 						addGeometry( mat, newGeometry, geometries ); | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 				} else { | ||
|  | 
 | ||
|  | 					addGeometry( c.material, geometry, geometries ); | ||
|  | 
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} ); | ||
|  | 
 | ||
|  | 		// Create object with merged geometries
 | ||
|  | 
 | ||
|  | 		const mergedObject = new Group(); | ||
|  | 
 | ||
|  | 		const meshMaterialsIds = Object.keys( meshGeometries ); | ||
|  | 		for ( const meshMaterialsId of meshMaterialsIds ) { | ||
|  | 
 | ||
|  | 			const meshGeometry = meshGeometries[ meshMaterialsId ]; | ||
|  | 			const mergedGeometry = mergeGeometries( meshGeometry.arr ); | ||
|  | 			mergedObject.add( new Mesh( mergedGeometry, meshGeometry.mat ) ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		const linesMaterialsIds = Object.keys( linesGeometries ); | ||
|  | 		for ( const linesMaterialsId of linesMaterialsIds ) { | ||
|  | 
 | ||
|  | 			const lineGeometry = linesGeometries[ linesMaterialsId ]; | ||
|  | 			const mergedGeometry = mergeGeometries( lineGeometry.arr ); | ||
|  | 			mergedObject.add( new LineSegments( mergedGeometry, lineGeometry.mat ) ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		const condLinesMaterialsIds = Object.keys( condLinesGeometries ); | ||
|  | 		for ( const condLinesMaterialsId of condLinesMaterialsIds ) { | ||
|  | 
 | ||
|  | 			const condLineGeometry = condLinesGeometries[ condLinesMaterialsId ]; | ||
|  | 			const mergedGeometry = mergeGeometries( condLineGeometry.arr ); | ||
|  | 			const condLines = new LineSegments( mergedGeometry, condLineGeometry.mat ); | ||
|  | 			condLines.isConditionalLine = true; | ||
|  | 			mergedObject.add( condLines ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		mergedObject.userData.constructionStep = 0; | ||
|  | 		mergedObject.userData.numConstructionSteps = 1; | ||
|  | 
 | ||
|  | 		return mergedObject; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | export { LDrawUtils }; |