Quaternions Without Fear
William Rowan Hamilton carved i² = j² = k² = ijk = −1 into a Dublin bridge in 1843 after years of trying to extend complex numbers to three dimensions. The resulting quaternions — a four-dimensional number system — turned out to be the most elegant way to represent 3D rotations: no gimbal lock, smooth spherical interpolation, and only 4 numbers instead of 9 matrix entries. Every game engine and 3D renderer uses them internally.
1. From Complex Numbers to ℍ
Complex numbers ℂ = {a + bi} extend ℝ with one imaginary unit satisfying i² = −1. They represent 2D rotations: multiplying by e^{iθ} = cos θ + i sin θ rotates a point by θ.
Hamilton's insight: to represent 3D rotations we need three imaginary units i, j, k satisfying:
The quaternions ℍ form a division algebra — every non-zero quaternion has a multiplicative inverse. They are the last associative division algebra (by Frobenius's theorem: only ℝ, ℂ, ℍ qualify).
2. Quaternion Algebra
Non-commutativity
pq ≠ qp in general. This mirrors the fact that 3D rotations don't commute: rotate X first, then Y is different from Y first, then X.
Anti-podal identity
q and −q represent the same rotation. The double-cover S³ → SO(3) means every rotation corresponds to two unit quaternions.
Pure quaternion
A quaternion with w=0. Products of pure quaternions contain the dot product and cross product of 3D vectors — Hamilton's reason for inventing them.
Exponential map
exp(θ/2 · n̂) = cos(θ/2) + n̂ sin(θ/2). Analogous to Euler's formula, this generates all unit quaternions from axis-angle (n̂, θ).
3. Rotations via Quaternions
To rotate a 3D vector v by angle θ around unit axis n̂, form the unit quaternion:
The angle/2 appears because unit quaternions live on S³ (the 3-sphere in ℝ⁴) which double-covers SO(3). A full rotation of 2π corresponds to q going halfway around S³.
4. Gimbal Lock in Euler Angles
Euler angles represent a rotation as three sequential rotations about coordinate axes (e.g., ZYX convention: yaw, pitch, roll). The middle rotation by 90° aligns two axes, reducing the effective degrees of freedom from 3 to 2 — gimbal lock:
Quaternions have no gimbal lock because they parametrise SO(3) globally (minus the double-cover antipodal identification) without singularities. This is why flight control systems, spacecraft attitude control, and 3D engines all use quaternions internally.
5. Quaternion ↔ Matrix ↔ Euler
6. SLERP — Spherical Linear Interpolation
Linear interpolation (LERP) of rotations does not stay on the unit sphere. SLERP interpolates along the great circle arc — the geodesic — between two unit quaternions:
SLERP produces constant angular velocity motion between two orientations — key for smooth camera panning, character animation blending, and spacecraft attitude manoeuvres.
7. JavaScript Quaternion Class
// Minimal quaternion library for 3D rotations
class Quat {
constructor(w = 1, x = 0, y = 0, z = 0) {
this.w = w; this.x = x; this.y = y; this.z = z;
}
static fromAxisAngle(ax, ay, az, angle) {
const s = Math.sin(angle / 2);
return new Quat(Math.cos(angle / 2), ax*s, ay*s, az*s);
}
mul(q) {
return new Quat(
this.w*q.w - this.x*q.x - this.y*q.y - this.z*q.z,
this.w*q.x + this.x*q.w + this.y*q.z - this.z*q.y,
this.w*q.y - this.x*q.z + this.y*q.w + this.z*q.x,
this.w*q.z + this.x*q.y - this.y*q.x + this.z*q.w
);
}
conjugate() { return new Quat(this.w, -this.x, -this.y, -this.z); }
norm() { return Math.sqrt(this.w**2+this.x**2+this.y**2+this.z**2); }
normalise() { const n=this.norm(); return new Quat(this.w/n,this.x/n,this.y/n,this.z/n); }
// Rotate vector v = {x,y,z} using sandwich product q·v·q*
rotateVec(v) {
const p = new Quat(0, v.x, v.y, v.z);
const r = this.mul(p).mul(this.conjugate());
return {x: r.x, y: r.y, z: r.z};
}
// SLERP between this and q at parameter t ∈ [0,1]
slerp(q, t) {
let dot = this.w*q.w + this.x*q.x + this.y*q.y + this.z*q.z;
// Ensure shortest path
if (dot < 0) { q = new Quat(-q.w,-q.x,-q.y,-q.z); dot = -dot; }
if (dot > 0.9995) {
// Use NLERP for near-identical quaternions
return new Quat(
this.w + t*(q.w-this.w), this.x + t*(q.x-this.x),
this.y + t*(q.y-this.y), this.z + t*(q.z-this.z)
).normalise();
}
const theta = Math.acos(dot);
const sinT = Math.sin(theta);
const s0 = Math.sin((1-t)*theta)/sinT;
const s1 = Math.sin(t*theta)/sinT;
return new Quat(s0*this.w+s1*q.w, s0*this.x+s1*q.x,
s0*this.y+s1*q.y, s0*this.z+s1*q.z);
}
toMatrix4() {
const {w,x,y,z} = this;
return [/* column-major 4×4 for WebGL */
1-2*(y*y+z*z), 2*(x*y+w*z), 2*(x*z-w*y), 0,
2*(x*y-w*z), 1-2*(x*x+z*z), 2*(y*z+w*x), 0,
2*(x*z+w*y), 2*(y*z-w*x), 1-2*(x*x+y*y), 0,
0, 0, 0, 1
];
}
}
// Example: rotate (1,0,0) by 90° around Y-axis → should give (0,0,−1)
const q = Quat.fromAxisAngle(0, 1, 0, Math.PI / 2);
const v = q.rotateVec({x:1, y:0, z:0});
console.log(v); // → {x: ≈0, y: 0, z: ≈−1}
8. Applications
-
Game engines (Unity, Unreal): The
Quaternionclass is the primary rotation type. SLERP powers animation blending and smooth camera movement. - Spacecraft attitude control: NASA uses quaternion feedback control (e.g., on Voyager, ISS) because no gimbal lock means guaranteed numerical stability at any orientation.
- Inertial measurement units (IMUs): Gyroscope integration is done in quaternion space — integrating angular velocity ω by updating q via q̇ = ½ · q · (0,ω).
- Dual quaternions: An extension encoding both rotation and translation in 8 numbers. Used in skeletal animation for more natural "bending" of skinned meshes than matrices.
- Quaternion neural networks: Replacing real-valued weights with quaternion weights can reduce parameter count 4× and encode 3D rotational equivariance natively.
THREE.Quaternion with setFromAxisAngle,
multiply, slerp and toArray —
the same operations as our class above, optimised for WebGL.