You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
5.7 KiB

6 months ago
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 };