Mat4
A 4×4 column-major matrix — the SDK's transform/projection type. Same contract as
Vec3 / Quat: all methods are pure (each returns a new Mat4; only
set / copy mutate), and anywhere a matrix is accepted a raw length-16 array or Float32Array
works too (Mat4Like).
Unlike the vectors, the backing store is public: .m is the raw column-major number[]
(length 16), yours to read and write directly.
At a glance
const world = Mat4.compose([0, 1, -3], Quat.fromEuler(0, 45, 0), 1.5) // T · R · S
const p = world.transformPoint([0, 0, -1]) // local point → world
const { position, rotation, scale } = world.decompose()
const view = Mat4.lookAt([0, 2, 5], [0, 0, 0]) // camera at (0,2,5) looking at origin
const proj = Mat4.perspective(60 * DEG2RAD, w / h, 0.1, 100)
const mvp = proj.mul(view).mul(world) // right-to-left: world, then view, then projThe raw array: .m
mat.m: number[] // length 16, column-major; translation lives in m[12..14]
mat.m[12] += vx * dt // direct numeric surgery is fine — .m is public on purpose
mat.toFloat32Array() // copy out as Float32Array for a host/GPU edgeThe fluent methods never mutate .m — they return a new Mat4 — so writing to .m is the one
way to change a matrix in place besides set / copy.
Creating
new Mat4() // identity
new Mat4(src: Mat4Like) // copies src (a Mat4, its .m, or a raw length-16 array)
Mat4.identity(): Mat4
Mat4.from(src: Mat4Like): Mat4
Mat4.compose(position, rotation, scale? /* 1 */): Mat4 // T · R · S; scale: vector or scalar
Mat4.fromTranslation(v): Mat4
Mat4.fromScale(v /* vector or scalar */): Mat4
Mat4.fromQuat(q): Mat4
Mat4.fromEuler(x, y, z, order? /* "YXZ" */): Mat4 // angles in DEGREESCombining
a.mul(b): Mat4 // a · b — applies b FIRST, then a (right-to-left, like glsl)
a.premul(b): Mat4 // b · a — the other side
a.invert(): Mat4 // singular matrices return identity
a.transpose(): Mat4
a.determinant(): number
a.equals(b, eps? /* 1e-6 */): booleanBuilding transforms (local-space, post-multiply)
m.translate(v): Mat4 // m · T(v)
m.rotate(rad, axis): Mat4 // m · R(axis, rad) — angle in RADIANS, axis normalized for you
m.rotateX(rad) m.rotateY(rad) m.rotateZ(rad)
m.scale(v /* vector or scalar */): Mat4Each applies its transform in the matrix's local space (post-multiplication), so a chain
reads like a scene-graph descent: Mat4.fromTranslation(pos).rotateY(a).scale(2).
Transforming vectors
m.transformPoint(v): Vec3 // full transform: translation + perspective divide
m.transformDirection(v): Vec3 // rotation/scale only — no translation, no divideVec3.transform(m) is transformPoint from the vector's side.
Decomposition
m.position: Vec3 // getter — translation column (m[12..14])
m.scaling: Vec3 // per-axis scale (basis vector lengths)
m.rotation: Quat // rotation with scale divided out
m.eulerAngles: Vec3 // DEGREES, "YXZ" order
m.toEuler(order? /* "YXZ" */): Vec3
m.decompose(): { position: Vec3, rotation: Quat, scale: Vec3 }
m.basisX / m.basisY / m.basisZ: Vec3 // the local axes in world space (columns 0/1/2)All getters return fresh values — mutating m.position does not write back into the matrix
(write m.m[12..14] directly for that).
Cameras & projection
Mat4.lookAt(eye, center, up? /* Vec3.up */): Mat4 // a VIEW matrix (world → camera)
Mat4.targetTo(eye, target, up? /* Vec3.up */): Mat4 // a WORLD matrix placing an object at eye,
// oriented to face target
Mat4.perspective(fovy, aspect, near, far): Mat4 // fovy in RADIANS; far may be Infinity
Mat4.ortho(left, right, bottom, top, near, far): Mat4projections use the WebGL/OpenGL clip convention (NDC z ∈ [−1, 1]). lookAt vs
targetTo is the classic trap: lookAt builds the inverse (view) matrix; to orient a node
toward something, you want targetTo (or Quat.lookRotation).
Interop
m.set(values: ArrayLike<number>): this // the only mutators (besides writing .m)
m.copy(src: Mat4Like): this
m.clone(): Mat4
m.toArray(): number[] // copy of .m
m.toFloat32Array(): Float32ArrayPitfalls
// ✗ degrees into rotate/perspective — raw-angle arguments are radians
m.rotateY(90); Mat4.perspective(60, aspect, 0.1, 100)
m.rotateY(90 * DEG2RAD); Mat4.perspective(60 * DEG2RAD, aspect, 0.1, 100) // ✓
// ✗ treating .m as row-major — it's column-major; translation is m[12], m[13], m[14]
m.m[3] = x
m.m[12] = x // ✓See also
- Vec2 & Vec3 —
Vec3.transform(m), basis directions - Quat —
Quat.fromEuler/lookRotation;mat.rotationround-trips withMat4.fromQuat - Conventions — degrees vs radians, −Z forward, Y-up