# Transforms and Matrices

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

- Category: Mathematics › Transforms
- Level: core
- Tags: matrix, matrix4, TRS, transform, homogeneous coordinates
- Updated: 2026-11-01
- URL: https://gdcs.coody.app/mathematics/transforms/transforms-and-matrices

## 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](https://gdcs.coody.app/mathematics/transforms/local-vs-world-space) for the hierarchy.

## Key formulas

- **Object transform (rightmost first):** $M = \textcolor{#fafafa}{T\,R\,S}, \qquad \textcolor{#a1a1aa}{R\,T\,S} \ne T\,R\,S$ — 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:** $\begin{bmatrix} R S & t \\ 0 & 1 \end{bmatrix}\begin{bmatrix} p \\ 1 \end{bmatrix} = RSp + t, \qquad \begin{bmatrix} R S & t \\ 0 & 1 \end{bmatrix}\begin{bmatrix} d \\ 0 \end{bmatrix} = RSd$
- **Inverse of a TRS:** $(T R S)^{-1} = S^{-1} R^{\mathsf{T}} T^{-1}, \qquad M^{-1} M = I$ — Reverse the order and invert each piece. A pure rotation inverts by transposing.
- **Normal matrix:** $N = \left(M_{3\times3}^{-1}\right)^{\mathsf{T}}$ — 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`:

```ts
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.

## Interactive visual

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.

## Prompt

```text
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.
```

## Related

- [Local vs World Space](https://gdcs.coody.app/mathematics/transforms/local-vs-world-space.md) — A child's position is stored relative to its parent. Chain the parent matrices to reach world space, or invert the chain to come back.
- [Euler Angles and Gimbal Lock](https://gdcs.coody.app/mathematics/transforms/euler-angles.md) — Euler angles describe orientation as yaw, pitch and roll applied in a fixed order. Easy to edit, but pitch ±90° makes two axes line up: gimbal lock.
- [Quaternions](https://gdcs.coody.app/mathematics/rotations/quaternions.md) — A unit quaternion stores a 3D rotation as an axis and half-angle. It composes cheaply, never gimbal-locks and slerps along the shortest arc.
- [Vector Basics](https://gdcs.coody.app/mathematics/vectors/vectors.md) — Vectors store positions, offsets, velocities and directions. Learn add, subtract, scale, length, squared length, normalization and distance.
- [Dot and Cross Product](https://gdcs.coody.app/mathematics/vectors/dot-and-cross-product.md) — The dot product measures how aligned two vectors are and projects one onto the other. The cross product builds a perpendicular normal.

## Further reading

- [Three.js docs — Matrix4](https://threejs.org/docs/#api/en/math/Matrix4)
- [Three.js manual — Matrix transformations](https://threejs.org/manual/#en/matrix-transformations)
- [Scratchapixel — Geometry: matrices](https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/geometry/matrices.html)

---
Source: Game Development Cheat Sheet (https://gdcs.coody.app)
