# Euler Angles and Gimbal Lock

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

- Category: Mathematics › Transforms
- Level: common
- Tags: euler, yaw pitch roll, gimbal lock, rotation order, rotation
- Updated: 2026-11-01
- URL: https://gdcs.coody.app/mathematics/transforms/euler-angles

## What it is

Euler angles store an orientation as three angles around fixed axes: **yaw** (turn left/right, around up), **pitch** (nod up/down, around the side axis) and **roll** (tilt, around forward). They are easy to read, easy to type into an inspector, and map directly onto mouse-look and flight controls.

Three numbers are not enough on their own: you also need the **order**. In Three.js, order `'XYZ'` means the matrix is `Rx·Ry·Rz`. Think of three nested rings (gimbals): X is the outer ring, Y sits inside it, Z is innermost and carries the object. The same three angles in order `'YXZ'` give a different orientation. For characters and FPS cameras, `'YXZ'` is the useful one: yaw outermost around world up, then pitch, then roll.

**Gimbal lock** happens when the middle ring turns ±90°. The inner ring's axis then lines up with the outer ring's axis, so the first and last angles spin the object around the same line. One degree of freedom is gone: in that pose, no small change of the three angles can produce a turn about the missing axis. Interpolating Euler angles near that pose produces wild swings.

Use Euler angles at the edges, for input and editing, and store or blend orientation as [quaternions](https://gdcs.coody.app/mathematics/rotations/quaternions). For an FPS camera, clamp pitch to about ±89° and the lock never happens.

## Key formulas

- **Order 'YXZ' (yaw, pitch, roll):** $R = R_y(\textcolor{#3ee09a}{\psi})\;R_x(\textcolor{#f65a5a}{\theta})\;R_z(\textcolor{#fafafa}{\phi})$ — Outer ring yaw ψ (green, Y), middle ring pitch θ (red, X), inner ring roll φ (white, Z): the axis colours in the visual.
- **Single-axis rotation:** $R_y(\psi) = \begin{bmatrix} \cos\psi & 0 & \sin\psi \\ 0 & 1 & 0 \\ -\sin\psi & 0 & \cos\psi \end{bmatrix}$
- **Gimbal lock (order 'XYZ', middle = Y):** $R_x(a)\,R_y(\tfrac{\pi}{2})\,R_z(c) = R_x(a + c)\,R_y(\tfrac{\pi}{2})$ — Only a + c matters: two angles, one effect.

## Rules of thumb

- Clamp FPS pitch to ±89° (≈ ±1.553 rad), never exactly ±90°.
- Mouse-look: keep yaw and pitch as your own two numbers and rebuild the rotation each frame with order `'YXZ'`.
- Wrap yaw into (-180°, 180°] when you store it, and blend angles along the shortest arc.
- Setting `object.rotation.order` after the fact reinterprets the same numbers as a different orientation.
- Converting a matrix or quaternion back to Euler is not unique; expect jumps such as (180°, 0, 180°) vs (0, 180°, 0).

## Implementation

FPS look with yaw/pitch kept separately, order `'YXZ'`:

```ts
let yaw = 0
let pitch = 0
const LIMIT = THREE.MathUtils.degToRad(89)

function onPointerMove(e: PointerEvent, sensitivity = 0.002) {
  yaw -= e.movementX * sensitivity
  pitch -= e.movementY * sensitivity
  pitch = THREE.MathUtils.clamp(pitch, -LIMIT, LIMIT)
  yaw = THREE.MathUtils.euclideanModulo(yaw + Math.PI, Math.PI * 2) - Math.PI
}

function update() {
  // Yaw around world Y first, then pitch around the camera's own X.
  camera.rotation.set(pitch, yaw, 0, "YXZ")
}

// Blending orientations: convert to quaternions, never lerp the angles.
const q = new THREE.Quaternion().setFromEuler(camera.rotation)
```

## Common mistakes

- Leaving the default order `'XYZ'` on an FPS camera: pitch then tilts the horizon as you turn.
- Lerping Euler angles between two poses; near gimbal lock or across ±180° it spins the long way.
- Letting pitch reach ±90° and wondering why yaw now rolls the view.
- Assuming another engine uses the same order or sign: check, and convert through a quaternion or matrix.

## Interactive visual

Three nested gimbal rings, each coloured by the axis it turns around (X red, Y green, Z white); the outer ring is the first letter of the order. The yellow ship rides the inner ring. The outer and inner rings wobble while the middle ring holds the slider angle. The solid line is the outer axis and the dashed line the inner axis; at 90° they merge into one line and both wobbles turn the ship the same way.

## Prompt

```text
Build a first-person camera controller in TypeScript with Three.js. Keep yaw and pitch as two numbers, not as the camera's Euler. On pointer move: yaw -= dx * sensitivity, pitch -= dy * sensitivity, clamp pitch to ±89° (in radians), wrap yaw into (-π, π]. Each frame write camera.rotation.set(pitch, yaw, 0, 'YXZ') so yaw is around world Y and pitch around the camera's own X, with no roll. Expose `forward(out)` that returns the horizontal walking direction (ignore pitch) without allocating. Write Vitest tests: pitch never exceeds ±89°; with yaw = 90° the forward vector is (-1, 0, 0); the camera's up vector keeps y > 0 for every input.
```

## Related

- [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.
- [Transforms and Matrices](https://gdcs.coody.app/mathematics/transforms/transforms-and-matrices.md) — A 4×4 matrix stores translation, rotation and scale in one object. Multiply to combine transforms, invert to undo them, and mind the order.
- [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.
- [Trigonometry for Games](https://gdcs.coody.app/mathematics/trigonometry/trigonometry.md) — Sine and cosine turn an angle into a direction on the unit circle; atan2 turns a direction back into an angle for aiming and turning.

## Further reading

- [Three.js docs — Euler](https://threejs.org/docs/#api/en/math/Euler)
- [Three.js docs — Object3D.rotation](https://threejs.org/docs/#api/en/core/Object3D.rotation)

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