Summary
Remove the per-call new Vector3(...) allocation in the root-node infinite-distance path of TransformNode.computeWorldMatrix() and replace it with a zero-allocation implementation based on direct matrix element reads or reusable temp storage.
This is a small, low-risk optimization that preserves public API and behavior while reducing work on a hot path that can run once per visible infinite-distance root node per frame.
Current Code
Current implementation in packages/dev/core/src/Meshes/transformNode.ts#L1108:
The temporary cameraGlobalPosition object is allocated even though:
- only the translation components
m[12..14]are needed - the values are consumed immediately
- the vector is not exposed outside the function
Motivation
This path is structurally more expensive than it needs to be:
- one heap allocation per recompute
- one constructor call
- one extra object read layer before
copyFromFloats
The code is also avoidably indirect. The translation is already available in the matrix.
Even if the absolute gain is small, this is a good target because:
- it is isolated
- it does not affect semantics
- it composes well with other
computeWorldMatrix()improvements
Proposal
Replace:
const cameraGlobalPosition = new Vector3(cameraWorldMatrix.m[12], cameraWorldMatrix.m[13], cameraWorldMatrix.m[14]);
translation = TransformNode._TmpTranslation;
translation.copyFromFloats(this._position.x + cameraGlobalPosition.x, this._position.y + cameraGlobalPosition.y, this._position.z + cameraGlobalPosition.z);
With:
const cameraWorldMatrix = camera.getWorldMatrix();
const m = cameraWorldMatrix.m;
translation = TransformNode._TmpTranslation;
translation.copyFromFloats(this._position.x + m[12], this._position.y + m[13], this._position.z + m[14]);
Why This Version
This is preferable to introducing another temp Vector3 because:
- it avoids allocation completely
- it avoids another temp object write/read round-trip
- it keeps the code local and explicit
- it matches how the data is actually consumed
Correctness
Behavior is unchanged:
- The only values read from
cameraGlobalPositionarex,y,z. - Those are exactly
cameraWorldMatrix.m[12],m[13],m[14]. translationstill ends up with the same final values.
This optimization does not change:
- parent handling
- camera selection
- matrix composition order
- any public return value
Risk
Risk is low.
Potential concerns:
- If future code wants the camera position object for other reasons, this path would need to be revisited.
- If
camera.getWorldMatrix().mever stops exposing translation in12/13/14, the whole engine would already be broken in many places. This is standard Babylon matrix layout.
Expected Impact
Per-call impact:
- lower allocation pressure
- lower GC pressure
- slightly less CPU work
Scene-level impact:
- most relevant in scenes with infinite-distance root nodes that recompute world matrices often
- likely modest on its own
- useful as part of a bundle of
computeWorldMatrix()cleanups
Suggested Patch Shape
Single-file change:
No public API change, no serialization change, no behavior change.
Notes
Scanned the whole _evaluateActiveMeshes and computeWorldMatrix for performance, and that’s the one without breaking change or edge-case microbenchmark regression.