Mat4
A 4×4 column-major matrix — the SDK's transform and projection type. Same contract as
Vec3 / Quat: methods are pure (each returns a new Mat4), and a raw
length-16 array or Float32Array works wherever a matrix is accepted (Mat4Like). A 3D node's
transform reads and writes as a Mat4 through node.matrix / node.worldMatrix (Node).
- build a transform matrix from position, rotation and scale
- combine a parent and a child transform in the right order
- convert a point from local space to world space and back
- get the position, rotation and scale back out of a matrix
- make a camera view matrix and a perspective projection
- hand a matrix to a host or shader as a flat array
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, 16 / 9, 0.1, 100) // fovy in RADIANS, aspect w / h
const mvp = proj.mul(view).mul(world) // right-to-left: world, then view, then projBuild a transform matrix from position, rotation and scale: Mat4.compose
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 DEGREESCombine, invert and compare matrices: mul / invert
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 */): booleanA child's world matrix is parentWorld.mul(childLocal). The inverse goes the other way, from
world space into the matrix's local space:
const parent = Mat4.compose([0, 1, -3], Quat.fromEuler(0, 90, 0), 2)
const local = parent.invert().transformPoint([4, 1, -3]) // world point → the parent's local space
const back = parent.transformPoint(local) // local → world: ≈ (4, 1, -3) again
console.log(local.x, local.y, local.z, back.x, back.y, back.z)Chain local transforms onto a matrix: translate / rotate / scale
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).
Transform a point or a direction: transformPoint
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.
Get position, rotation and scale out of a matrix: decompose
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).
Camera view and projection matrices: lookAt / perspective
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).
Read and write the raw numbers: .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 purposeUnlike the vectors, the backing store is public. The 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.
Copy a matrix out as an array or Float32Array: toFloat32Array
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(): Float32Array // copy for a host / GPU edgePitfalls
// ✗ 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 - Node —
node.matrix/node.worldMatrix - Conventions — degrees vs radians, −Z forward, Y-up