Apply a 16-element column-major transform to a THREE.Object3D, preserving the
full affine matrix. Throws on malformed input via the validation guard.
The matrix is installed DIRECTLY rather than decomposed. Matrix4.decompose()
factors into position / quaternion / scale, and TRS cannot represent SHEAR —
so applying a non-uniform scale after a rotation (which
luxar.transforms.compose supports, and which
packages/luxar/examples/transform_example.py authors) came back altered.
Measured before this change: the authored matrix
[[1,1,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]] should send (0,1,0) to (1,1,0);
decomposition yielded (0.5, 1.319, 0). Silent geometry corruption, which for a
scientific viewer is the worst failure mode available.
Nothing downstream needs the TRS fields: no production code reads
.position/.quaternion/.scale off a data node (every such access is a
camera), and bounds, picking, frustum culling and depth sorting all consume the
full matrixWorld. The shaders take modelViewMatrix whole — including the
gsplat covariance congruence Σ' = A Σ Aᵀ, which is already general-linear and so
is correct under shear the moment the matrix survives to the GPU.
updateMatrixWorld(true) keeps the world matrix synchronously readable by
creation-time consumers. The following matrixWorldNeedsUpdate assignment is
equally important: loaders attach nodes after applying their transform, so a
later on-demand updateWorldMatrix() must re-resolve the matrix against the
real parent. The renderer's scene update clears that flag on the next frame.
Apply a 16-element column-major transform to a THREE.Object3D, preserving the full affine matrix. Throws on malformed input via the validation guard.
The matrix is installed DIRECTLY rather than decomposed.
Matrix4.decompose()factors into position / quaternion / scale, and TRS cannot represent SHEAR — so applying a non-uniform scale after a rotation (whichluxar.transforms.composesupports, and whichpackages/luxar/examples/transform_example.pyauthors) came back altered. Measured before this change: the authored matrix[[1,1,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]should send(0,1,0)to(1,1,0); decomposition yielded(0.5, 1.319, 0). Silent geometry corruption, which for a scientific viewer is the worst failure mode available.Nothing downstream needs the TRS fields: no production code reads
.position/.quaternion/.scaleoff a data node (every such access is a camera), and bounds, picking, frustum culling and depth sorting all consume the fullmatrixWorld. The shaders takemodelViewMatrixwhole — including the gsplat covariance congruence Σ' = A Σ Aᵀ, which is already general-linear and so is correct under shear the moment the matrix survives to the GPU.updateMatrixWorld(true)keeps the world matrix synchronously readable by creation-time consumers. The followingmatrixWorldNeedsUpdateassignment is equally important: loaders attach nodes after applying their transform, so a later on-demandupdateWorldMatrix()must re-resolve the matrix against the real parent. The renderer's scene update clears that flag on the next frame.