ReadonlyelementsThe nine elements in column-major order. The array itself is never replaced, so views taken by a renderer stay valid for the matrix's lifetime; only its contents change.
OptionalonOptional change hook invoked at the end of every mutator that actually writes elements. Engine-internal: owners install it, user code normally leaves it unset. It is intentionally not copied by Matrix3.copy or Matrix3.clone.
Allocates a new matrix with the same elements. The clone has no change hook. This is the only allocating method on the type (§7b).
Copies the elements of m into this matrix. The change hook is not copied.
Determinant of this matrix. Does not mutate and does not allocate.
Reads nine column-major elements starting at offset. This is the
inverse of reading Matrix3.elements and the entry point used by
serialization (§79) and by tests that build matrices from raw numbers.
Resets this matrix to the identity.
Inverts this matrix in place (general inverse via the adjugate — no assumption of orthogonality or affinity).
Singular behaviour (defined here, plan §1 rule 12): a matrix whose
determinant is exactly 0 has no inverse, so this method leaves the
elements unchanged, returns this, and does not fire the change hook —
the hook reports a change, and nothing changed. Callers that must
distinguish "inverted" from "left alone" check m.determinant() !== 0
first. Resetting to the identity was rejected: it silently substitutes a
different, plausible-looking transform for the failure, which is the
hardest kind of bug to trace. Throwing was rejected because inversion sits
on per-frame paths (§43, §47) where an exception would take down the frame.
A nearly singular matrix still inverts, producing large elements; this method deliberately applies no epsilon, since the right threshold depends on the caller's units (§40).
Matrix product this = this * m — this matrix is the left operand, so the
combined transform applies m first and this matrix second (the same
ordering as Quaternion.multiply). Aliasing-safe: m may be this.
Copies the upper-left 3×3 of a column-major Matrix4 into this
matrix. Translation (e[12..14]) and the homogeneous row are ignored —
the linear part that transforms directions, which is what a normal matrix
starts from.
Always writes and always fires the change hook: even a copy of the identity is a write.
Builds the normal matrix of m: transpose(inverse(upper 3×3)), the
linear map that keeps object-space normals perpendicular to surfaces after
a non-uniform scale (§68). Translation does not participate.
The inverse is Matrix3.invert's general inverse, so a singular
upper 3×3 (determinant exactly 0) is the same no-op: this matrix is
left unchanged, the change hook does not fire, and the method returns
this. Callers that must distinguish "built" from "left alone" check
the upper 3×3's determinant first — the same rule invert documents.
The change hook fires once, after the last write, never per intermediate copy / invert / transpose. That is plan D3: one mutation, one notification.
Transposes this matrix in place. Diagonal elements stay put; the three off-diagonal pairs swap. Fires the change hook once, after the last write.
Mutable 3×3 matrix stored column-major in a
Float64Array(9)(§7b).Element layout —
elements[column * 3 + row], the same order GPU APIs andWebGL2RenderingContext.uniformMatrix3fvexpect:Float64Array(notFloat32Array) is deliberate: the scene graph does its arithmetic in double precision and narrows to float only at the backend boundary (§62), so accumulated transform error stays below anything the determinism tiers (§33) care about.Allocation policy (§7b, plan D7): every method mutates in place and returns
this; only Matrix3.clone allocates. Scalar queries (Matrix3.determinant) never allocate and never mutate.Change notification (plan D3): every mutator invokes Matrix3.onChanged exactly once, after the last element is written. Writing
elementsdirectly bypasses the hook by design — the same rule asv.x = 1on the vector types.