Skip to content

MathematicsTransformscore3 min read

Transforms and Matrices

pack move, turn and resize into one multiply

A 4×4 matrix stores translation, rotation and scale in one object. Multiply to combine transforms, invert to undo them, and mind the order.

View .md

The solid white ship uses the order you pick; the grey wireframe ghost uses the other order with the same T, R and S. The short axis lines at the origin are world X (red) and Z (white). Rotate with a non-zero translation and watch R·T·S swing the ship around the origin while T·R·S only turns it in place.

What it is

Every object in a 3D scene has a transform: where it is (translation T), how it is turned (rotation R) and how big it is (scale S). A 4×4 matrix can hold all three, so moving a vertex from model space into the world is one matrix–vector multiply, and a chain of transforms collapses into one matrix.

The 4th row and column exist because translation is not linear in 3D. Writing a point as (x, y, z, 1) (homogeneous coordinates) lets the last column add the offset. Directions use w = 0, so they rotate and scale but never move. That is why normals and velocities must not pick up an object's position.

Order matters. With column vectors (Three.js, OpenGL, most maths texts) the rightmost matrix acts first. T·R·S scales the mesh, spins it in place, then moves it: the usual object transform. R·T·S moves it first and then spins the whole offset around the origin, so the object orbits instead. Matrix multiplication is not commutative.

In Three.js each Object3D keeps position, quaternion and scale, and builds matrix = T·R·S from them. matrixWorld = parent.matrixWorld · matrix is refreshed by updateMatrixWorld(), which the renderer calls once per frame. See local vs world space for the hierarchy.

  • matrix
  • matrix4
  • TRS
  • transform
  • homogeneous coordinates

Key formulas

Object transform (rightmost first)

White is the object built with T·R·S; the grey outline is the same pieces multiplied as R·T·S.

Homogeneous point and direction
Inverse of a TRS

Reverse the order and invert each piece. A pure rotation inverts by transposing.

Normal matrix

Needed for normals when the scale is non-uniform.

Rules of thumb

  • Read a product right to left: A·B·p applies B, then A.
  • Three.js a.multiply(b) is a = a·b; a.premultiply(b) is a = b·a.
  • If you set object.matrix yourself, set matrixAutoUpdate = false or Three.js overwrites it from position/quaternion/scale.
  • Call updateMatrixWorld() before reading matrixWorld in the same frame you moved something.
  • Keep scales uniform where you can; non-uniform scale under a rotated child creates shear that cannot be decomposed back into TRS.

Implementation

Build, combine and invert transforms with THREE.Matrix4:
TypeScript
const T = new THREE.Matrix4().makeTranslation(3, 0, 0)const R = new THREE.Matrix4().makeRotationY(Math.PI / 2)const S = new THREE.Matrix4().makeScale(2, 2, 2)
// T·R·S: scale, then rotate in place, then move. (Rightmost first.)const trs = new THREE.Matrix4().multiplyMatrices(T, R).multiply(S)// R·T·S: move first, then rotate the offset → the object orbits.const rts = new THREE.Matrix4().multiplyMatrices(R, T).multiply(S)
const p = new THREE.Vector3(0, 0, 0)p.clone().applyMatrix4(trs) // (3, 0, 0)p.clone().applyMatrix4(rts) // (0, 0, -3)
// Same as T·R·S, from parts:const m = new THREE.Matrix4().compose(position, quaternion, scale)
// Undo: world point back into the object's space.const inv = trs.clone().invert()const local = worldPoint.clone().applyMatrix4(inv)
// Directions ignore translation (w = 0).const dir = new THREE.Vector3(1, 0, 0).transformDirection(trs)

Common mistakes

  • Multiplying in the wrong order and getting orbiting instead of spinning (or vice versa).
  • Transforming a direction or normal with applyMatrix4, which adds the translation. Use transformDirection or the normal matrix.
  • Reading matrixWorld right after moving a parent, before updateMatrixWorld() has run.
  • Writing to object.matrix while matrixAutoUpdate is still true; your values vanish next frame.
  • Inverting a matrix every frame when you only need the transpose of a pure rotation.

Prompt

Prompt for your AI assistant
Write `src/lib/mat4.ts` in TypeScript with column-major 4×4 matrices stored as number[16] (same layout as THREE.Matrix4.elements): identity, translation(x,y,z), rotationX/Y/Z(rad), scaling(x,y,z), multiply(a,b) where b is applied first, invert(m) returning null when |det| < 1e-12, transformPoint (w = 1) and transformDirection (w = 0). Add composeTRS(position, quaternion, scale) that matches THREE.Matrix4.compose. Write Vitest tests: T·R·S and R·T·S give different results for a 90° Y rotation; invert(m)·m equals identity within 1e-6 for a random TRS; transformDirection ignores translation; composeTRS matches Three.js element by element. No Three.js imports in the library itself.

In learning paths

Further reading

Updated 2026-11-01