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