Remove Infinite-Distance Root Allocation in `TransformNode.computeWorldMatrix`

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 cameraGlobalPosition are x, y, z.
  • Those are exactly cameraWorldMatrix.m[12], m[13], m[14].
  • translation still 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().m ever stops exposing translation in 12/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.

1 Like

Looks good!

Do you want to create a PR for it?