Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | 1x 1x 1x 21x 21x 21x 21x 21x 21x 21x 21x 21x 1137x 692x 692x 692x 692x 692x 123x 123x 692x 692x 692x 692x 21x 21x 21x 21x 123x 123x 123x 123x 123x 123x 123x 123x 123x 123x 123x 123x 123x 123x 123x 123x 692x 692x 123x 123x 21x 21x | // src/cad/mesh-consolidator.ts
// Performance optimizer for LEGO models:
// Consolidates hundreds of individual part meshes into a few merged meshes grouped by material,
// reducing draw calls by 95-99% while preserving authentic visuals and shadows.
import * as THREE from 'three';
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
export interface ConsolidationOptions {
castShadow?: boolean;
receiveShadow?: boolean;
clusterId?: string;
}
/**
* Consolidates all static Mesh descendants of a Three.js Group into merged meshes
* grouped by material. Replaces hundreds of individual draw calls with 1-4 draw calls.
*/
export function consolidateGroupMeshes(
rootGroup: THREE.Group,
options: ConsolidationOptions = {}
): THREE.Group {
if (rootGroup.children.length <= 1) {
return rootGroup;
}
rootGroup.updateMatrixWorld(true);
const invRoot = rootGroup.matrixWorld.clone().invert();
// Bucket geometries by material instance
const byMaterial = new Map<THREE.Material, THREE.BufferGeometry[]>();
const originalMeshes: THREE.Mesh[] = [];
rootGroup.traverse((child) => {
if ((child as THREE.Mesh).isMesh && !(child as THREE.InstancedMesh).isInstancedMesh) {
const mesh = child as THREE.Mesh;
if (!mesh.geometry) return;
originalMeshes.push(mesh);
const mat = mesh.material as THREE.Material;
if (!byMaterial.has(mat)) {
byMaterial.set(mat, []);
}
// Transform geometry into rootGroup's local coordinate space
const localMat = mesh.matrixWorld.clone().premultiply(invRoot);
const clonedGeom = mesh.geometry.clone().applyMatrix4(localMat);
byMaterial.get(mat)!.push(clonedGeom);
}
});
// If there are no meshes to consolidate (or just 1), keep as is
if (originalMeshes.length <= 1) {
return rootGroup;
}
// Clear existing unmerged children
rootGroup.clear();
// Create merged meshes for each unique material
for (const [mat, geometries] of byMaterial.entries()) {
try {
const mergedGeom = BufferGeometryUtils.mergeGeometries(geometries, false);
if (mergedGeom) {
const mergedMesh = new THREE.Mesh(mergedGeom, mat);
mergedMesh.castShadow = options.castShadow ?? true;
mergedMesh.receiveShadow = options.receiveShadow ?? true;
mergedMesh.userData = {
isConsolidated: true,
clusterId: options.clusterId ?? rootGroup.userData?.clusterId,
partCount: geometries.length,
};
rootGroup.add(mergedMesh);
}
} catch (err) {
console.warn('[mesh-consolidator] Failed to merge geometries for material:', err);
} finally {
// Clean up temporary intermediate geometries
for (const g of geometries) {
g.dispose();
}
}
}
return rootGroup;
}
|