diff --git a/src/foliage/arpeggio-batcher.ts b/src/foliage/arpeggio-batcher.ts index 3dbaa2e..e485273 100644 --- a/src/foliage/arpeggio-batcher.ts +++ b/src/foliage/arpeggio-batcher.ts @@ -21,6 +21,7 @@ import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js'; const MAX_FERNS = 2000; const FRONDS_PER_FERN = 5; +const _scratchMatrix = new THREE.Matrix4(); export class ArpeggioFernBatcher { initialized: boolean; @@ -332,9 +333,10 @@ export class ArpeggioFernBatcher { // So I just need to copy dummy position/rotation/scale. this.dummy.rotation.copy(dummy.rotation); this.dummy.scale.setScalar(scale); - this.dummy.updateMatrix(); - this.mesh!.setMatrixAt(i, this.dummy.matrix); + // ⚡ OPTIMIZATION: Eliminate CPU overhead and GC spikes from Matrix4 composition by writing directly to instanceMatrix.array + _scratchMatrix.compose(this.dummy.position, this.dummy.quaternion, this.dummy.scale); + _scratchMatrix.toArray(this.mesh!.instanceMatrix.array, i * 16); // Color this._color.setHex(color); @@ -353,9 +355,11 @@ export class ArpeggioFernBatcher { this.dummy.position.copy(dummy.position); this.dummy.rotation.copy(dummy.rotation); this.dummy.scale.copy(dummy.scale); - this.dummy.updateMatrix(); - this.mesh!.setMatrixAt(index, this.dummy.matrix); + // ⚡ OPTIMIZATION: Eliminate CPU overhead and GC spikes from Matrix4 composition by writing directly to instanceMatrix.array + _scratchMatrix.compose(this.dummy.position, this.dummy.quaternion, this.dummy.scale); + _scratchMatrix.toArray(this.mesh!.instanceMatrix.array, index * 16); + this.mesh!.instanceMatrix.needsUpdate = true; } diff --git a/src/foliage/portamento-batcher.ts b/src/foliage/portamento-batcher.ts index 32cbf79..680517b 100644 --- a/src/foliage/portamento-batcher.ts +++ b/src/foliage/portamento-batcher.ts @@ -22,6 +22,7 @@ import { } from 'three/tsl'; const MAX_PINES = 200; // conservative default for performance +const _scratchMatrix = new THREE.Matrix4(); export class PortamentoPineBatcher { initialized = false; @@ -171,10 +172,12 @@ export class PortamentoPineBatcher { this.dummy.position.copy(dummy.position); this.dummy.quaternion.copy(dummy.quaternion); this.dummy.scale.copy(dummy.scale); - this.dummy.updateMatrix(); - this.trunkMesh!.setMatrixAt(i, this.dummy.matrix); - this.needleMesh!.setMatrixAt(i, this.dummy.matrix); + // ⚡ OPTIMIZATION: Eliminate CPU overhead and GC spikes from Matrix4 composition by writing directly to instanceMatrix.array + _scratchMatrix.compose(this.dummy.position, this.dummy.quaternion, this.dummy.scale); + _scratchMatrix.toArray(this.trunkMesh!.instanceMatrix.array, i * 16); + _scratchMatrix.toArray(this.needleMesh!.instanceMatrix.array, i * 16); + this.bendAttribute!.setX(i, 0); this.trunkMesh!.instanceMatrix.needsUpdate = true; @@ -191,9 +194,12 @@ export class PortamentoPineBatcher { this.dummy.position.copy(dummy.position); this.dummy.quaternion.copy(dummy.quaternion); this.dummy.scale.copy(dummy.scale); - this.dummy.updateMatrix(); - this.trunkMesh!.setMatrixAt(idx, this.dummy.matrix); - this.needleMesh!.setMatrixAt(idx, this.dummy.matrix); + + // ⚡ OPTIMIZATION: Eliminate CPU overhead and GC spikes from Matrix4 composition by writing directly to instanceMatrix.array + _scratchMatrix.compose(this.dummy.position, this.dummy.quaternion, this.dummy.scale); + _scratchMatrix.toArray(this.trunkMesh!.instanceMatrix.array, idx * 16); + _scratchMatrix.toArray(this.needleMesh!.instanceMatrix.array, idx * 16); + this.trunkMesh!.instanceMatrix.needsUpdate = true; this.needleMesh!.instanceMatrix.needsUpdate = true; }