MCP 3D Printer Server
by DMontgomery40
Verified
import * as THREE from 'three';
class SceneOptimizer {
constructor( scene, options = {} ) {
this.scene = scene;
this.debug = options.debug || false;
}
bufferToHash( buffer ) {
let hash = 0;
if ( buffer.byteLength !== 0 ) {
let uintArray;
if ( buffer.buffer ) {
uintArray = new Uint8Array(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength
);
} else {
uintArray = new Uint8Array( buffer );
}
for ( let i = 0; i < buffer.byteLength; i ++ ) {
const byte = uintArray[ i ];
hash = ( hash << 5 ) - hash + byte;
hash |= 0;
}
}
return hash;
}
getMaterialPropertiesHash( material ) {
const mapProps = [
'map',
'alphaMap',
'aoMap',
'bumpMap',
'displacementMap',
'emissiveMap',
'envMap',
'lightMap',
'metalnessMap',
'normalMap',
'roughnessMap',
];
const mapHash = mapProps
.map( ( prop ) => {
const map = material[ prop ];
if ( ! map ) return 0;
return `${map.uuid}_${map.offset.x}_${map.offset.y}_${map.repeat.x}_${map.repeat.y}_${map.rotation}`;
} )
.join( '|' );
const physicalProps = [
'transparent',
'opacity',
'alphaTest',
'alphaToCoverage',
'side',
'vertexColors',
'visible',
'blending',
'wireframe',
'flatShading',
'premultipliedAlpha',
'dithering',
'toneMapped',
'depthTest',
'depthWrite',
'metalness',
'roughness',
'clearcoat',
'clearcoatRoughness',
'sheen',
'sheenRoughness',
'transmission',
'thickness',
'attenuationDistance',
'ior',
'iridescence',
'iridescenceIOR',
'iridescenceThicknessRange',
'reflectivity',
]
.map( ( prop ) => {
if ( typeof material[ prop ] === 'undefined' ) return 0;
if ( material[ prop ] === null ) return 0;
return material[ prop ].toString();
} )
.join( '|' );
const emissiveHash = material.emissive ? material.emissive.getHexString() : 0;
const attenuationHash = material.attenuationColor
? material.attenuationColor.getHexString()
: 0;
const sheenColorHash = material.sheenColor
? material.sheenColor.getHexString()
: 0;
return [
material.type,
physicalProps,
mapHash,
emissiveHash,
attenuationHash,
sheenColorHash,
].join( '_' );
}
getAttributesSignature( geometry ) {
return Object.keys( geometry.attributes )
.sort()
.map( ( name ) => {
const attribute = geometry.attributes[ name ];
return `${name}_${attribute.itemSize}_${attribute.normalized}`;
} )
.join( '|' );
}
getGeometryHash( geometry ) {
const indexHash = geometry.index
? this.bufferToHash( geometry.index.array )
: 'noIndex';
const positionHash = this.bufferToHash( geometry.attributes.position.array );
const attributesSignature = this.getAttributesSignature( geometry );
return `${indexHash}_${positionHash}_${attributesSignature}`;
}
getBatchKey( materialProps, attributesSignature ) {
return `${materialProps}_${attributesSignature}`;
}
analyzeModel() {
const batchGroups = new Map();
const singleGroups = new Map();
const uniqueGeometries = new Set();
this.scene.updateMatrixWorld( true );
this.scene.traverse( ( node ) => {
if ( ! node.isMesh ) return;
const materialProps = this.getMaterialPropertiesHash( node.material );
const attributesSignature = this.getAttributesSignature( node.geometry );
const batchKey = this.getBatchKey( materialProps, attributesSignature );
const geometryHash = this.getGeometryHash( node.geometry );
uniqueGeometries.add( geometryHash );
if ( ! batchGroups.has( batchKey ) ) {
batchGroups.set( batchKey, {
meshes: [],
geometryStats: new Map(),
totalInstances: 0,
materialProps: node.material.clone(),
} );
}
const group = batchGroups.get( batchKey );
group.meshes.push( node );
group.totalInstances ++;
if ( ! group.geometryStats.has( geometryHash ) ) {
group.geometryStats.set( geometryHash, {
count: 0,
vertices: node.geometry.attributes.position.count,
indices: node.geometry.index ? node.geometry.index.count : 0,
geometry: node.geometry,
} );
}
group.geometryStats.get( geometryHash ).count ++;
} );
// Move single instance groups to singleGroups
for ( const [ batchKey, group ] of batchGroups ) {
if ( group.totalInstances === 1 ) {
singleGroups.set( batchKey, group );
batchGroups.delete( batchKey );
}
}
return { batchGroups, singleGroups, uniqueGeometries: uniqueGeometries.size };
}
createBatchedMeshes( batchGroups ) {
const meshesToRemove = new Set();
for ( const [ , group ] of batchGroups ) {
const maxGeometries = group.totalInstances;
const maxVertices = Array.from( group.geometryStats.values() ).reduce(
( sum, stats ) => sum + stats.vertices,
0
);
const maxIndices = Array.from( group.geometryStats.values() ).reduce(
( sum, stats ) => sum + stats.indices,
0
);
const batchedMaterial = new group.materialProps.constructor( group.materialProps );
if ( batchedMaterial.color !== undefined ) {
// Reset color to white, color will be set per instance
batchedMaterial.color.set( 1, 1, 1 );
}
const batchedMesh = new THREE.BatchedMesh(
maxGeometries,
maxVertices,
maxIndices,
batchedMaterial
);
const referenceMesh = group.meshes[ 0 ];
batchedMesh.name = `${referenceMesh.name}_batch`;
const geometryIds = new Map();
const inverseParentMatrix = new THREE.Matrix4();
if ( referenceMesh.parent ) {
referenceMesh.parent.updateWorldMatrix( true, false );
inverseParentMatrix.copy( referenceMesh.parent.matrixWorld ).invert();
}
for ( const mesh of group.meshes ) {
const geometryHash = this.getGeometryHash( mesh.geometry );
if ( ! geometryIds.has( geometryHash ) ) {
geometryIds.set( geometryHash, batchedMesh.addGeometry( mesh.geometry ) );
}
const geometryId = geometryIds.get( geometryHash );
const instanceId = batchedMesh.addInstance( geometryId );
const localMatrix = new THREE.Matrix4();
mesh.updateWorldMatrix( true, false );
localMatrix.copy( mesh.matrixWorld );
if ( referenceMesh.parent ) {
localMatrix.premultiply( inverseParentMatrix );
}
batchedMesh.setMatrixAt( instanceId, localMatrix );
batchedMesh.setColorAt( instanceId, mesh.material.color );
meshesToRemove.add( mesh );
}
if ( referenceMesh.parent ) {
referenceMesh.parent.add( batchedMesh );
}
}
return meshesToRemove;
}
removeEmptyNodes( object ) {
const children = [ ...object.children ];
for ( const child of children ) {
this.removeEmptyNodes( child );
if ( ( child instanceof THREE.Group || child.constructor === THREE.Object3D )
&& child.children.length === 0 ) {
object.remove( child );
}
}
}
disposeMeshes( meshesToRemove ) {
meshesToRemove.forEach( ( mesh ) => {
if ( mesh.parent ) {
mesh.parent.remove( mesh );
}
if ( mesh.geometry ) mesh.geometry.dispose();
if ( mesh.material ) {
if ( Array.isArray( mesh.material ) ) {
mesh.material.forEach( ( m ) => m.dispose() );
} else {
mesh.material.dispose();
}
}
} );
}
logDebugInfo( stats ) {
console.group( 'Scene Optimization Results' );
console.log( `Original meshes: ${stats.originalMeshes}` );
console.log( `Batched into: ${stats.batchedMeshes} BatchedMesh` );
console.log( `Single meshes: ${stats.singleMeshes} Mesh` );
console.log( `Total draw calls: ${stats.drawCalls}` );
console.log( `Reduction Ratio: ${stats.reductionRatio}% fewer draw calls` );
console.groupEnd();
}
toBatchedMesh() {
const { batchGroups, singleGroups, uniqueGeometries } = this.analyzeModel();
const meshesToRemove = this.createBatchedMeshes( batchGroups );
this.disposeMeshes( meshesToRemove );
this.removeEmptyNodes( this.scene );
if ( this.debug ) {
const totalOriginalMeshes = meshesToRemove.size + singleGroups.size;
const totalFinalMeshes = batchGroups.size + singleGroups.size;
const stats = {
originalMeshes: totalOriginalMeshes,
batchedMeshes: batchGroups.size,
singleMeshes: singleGroups.size,
drawCalls: totalFinalMeshes,
uniqueGeometries: uniqueGeometries,
reductionRatio: ( ( 1 - totalFinalMeshes / totalOriginalMeshes ) * 100 ).toFixed( 1 ),
};
this.logDebugInfo( stats );
}
return this.scene;
}
// Placeholder for future implementation
toInstancingMesh() {
throw new Error( 'InstancedMesh optimization not implemented yet' );
}
}
export { SceneOptimizer };