From 2f42e2e29cd5a5976fc42f46762e03f91b2aa2a4 Mon Sep 17 00:00:00 2001 From: Noeri Huisman <8823461+mrxz@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:09:08 +0200 Subject: [PATCH] Reduce small memory allocations when loading ply files (#147) * Replace for-of loops to reduce memory allocations in hot-path for ply loading * Use temporary THREE.Quaternion and THREE.Vector instances to avoid allocations --- src/ply.ts | 16 ++++++++-------- src/utils.ts | 19 ++++++++++++++----- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/ply.ts b/src/ply.ts index b7b71ce..4ee0807 100644 --- a/src/ply.ts +++ b/src/ply.ts @@ -239,8 +239,8 @@ export class PlyReader { // Parse all the items in the element const callback = elementCallback(element) ?? (() => {}); for (let index = 0; index < count; index++) { - for (const parser of parsers) { - parser(); + for (let parserIndex = 0; parserIndex < parsers.length; parserIndex++) { + parsers[parserIndex](); } callback(index, item); } @@ -296,17 +296,17 @@ export class PlyReader { if (!sh1) { throw new Error("Missing sh1"); } - for (const [i, key] of sh1Props.entries()) { - sh1[i] = ((item[key] as number) * 8) / 255 - 4; + for (let i = 0; i < sh1Props.length; i++) { + sh1[i] = ((item[sh1Props[i]] as number) * 8) / 255 - 4; } if (sh2) { - for (const [i, key] of sh2Props.entries()) { - sh2[i] = ((item[key] as number) * 8) / 255 - 4; + for (let i = 0; i < sh2Props.length; i++) { + sh2[i] = ((item[sh2Props[i]] as number) * 8) / 255 - 4; } } if (sh3) { - for (const [i, key] of sh3Props.entries()) { - sh3[i] = ((item[key] as number) * 8) / 255 - 4; + for (let i = 0; i < sh3Props.length; i++) { + sh3[i] = ((item[sh3Props[i]] as number) * 8) / 255 - 4; } } shCallback?.(index, sh1, sh2, sh3); diff --git a/src/utils.ts b/src/utils.ts index f73e317..c88a549 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -499,6 +499,10 @@ export function setPackedSplatScales( (packedSplats[i4 + 3] & 0xff000000); } +// Temporary storage used in `encodeQuatOCtXy88R8` and `decodeQuatOctXy88R8` to +// avoid allocation new Quaternions and Vector3 instances. +const tempQuaternion = new THREE.Quaternion(); + // Encode the rotation quatX, quatY, quatZ, quatW in the packedSplats Uint32Array, // leaving all other fields as is. export function setPackedSplatQuat( @@ -510,7 +514,7 @@ export function setPackedSplatQuat( quatW: number, ) { const uQuat = encodeQuatOctXy88R8( - new THREE.Quaternion(quatX, quatY, quatZ, quatW), + tempQuaternion.set(quatX, quatY, quatZ, quatW), ); // const uQuat = encodeQuatXyz888(new THREE.Quaternion(quatX, quatY, quatZ, quatW)); // const uQuat = encodeQuatEulerXyz888(new THREE.Quaternion(quatX, quatY, quatZ, quatW)); @@ -960,6 +964,11 @@ export function decodeQuatXyz888( return out; } +// Temporary storage used in `encodeQuatOCtXy88R8` and `decodeQuatOctXy88R8` to +// avoid allocation new Quaternions and Vector3 instances. +const tempNormalizedQuaternion = new THREE.Quaternion(); +const tempAxis = new THREE.Vector3(); + /** * Encodes a THREE.Quaternion into a 24‐bit integer. * @@ -972,7 +981,7 @@ export function decodeQuatXyz888( */ export function encodeQuatOctXy88R8(q: THREE.Quaternion): number { // Force the minimal representation (q.w >= 0) - const qnorm = q.clone().normalize(); + const qnorm = tempNormalizedQuaternion.copy(q).normalize(); if (qnorm.w < 0) { qnorm.set(-qnorm.x, -qnorm.y, -qnorm.z, -qnorm.w); } @@ -984,8 +993,8 @@ export function encodeQuatOctXy88R8(q: THREE.Quaternion): number { ); const axis = xyz_norm < 1e-6 - ? new THREE.Vector3(1, 0, 0) - : new THREE.Vector3(qnorm.x, qnorm.y, qnorm.z).divideScalar(xyz_norm); + ? tempAxis.set(1, 0, 0) + : tempAxis.set(qnorm.x, qnorm.y, qnorm.z).divideScalar(xyz_norm); // const foldAxis = (axis.z < 0); // --- Folded Octahedral Mapping (inline) --- @@ -1036,7 +1045,7 @@ export function decodeQuatOctXy88R8( const t = Math.max(-f_z, 0); f_x += f_x >= 0 ? -t : t; f_y += f_y >= 0 ? -t : t; - const axis = new THREE.Vector3(f_x, f_y, f_z).normalize(); + const axis = tempAxis.set(f_x, f_y, f_z).normalize(); // Decode the angle: θ ∈ [0,π] const theta = (angleInt / 255) * Math.PI;