Initializing 3D Canvas...

The Quaternion

3 min read•1 page

Definition

A quaternion is a 4D complex number used to represent 3D orientation. Unlike Euler angles (XYZ rotation) which suffer from Gimbal Lock, Quaternions provide a robust, lock-free way to represent and interpolate rotations.

python
1import math
2
3# --- 2D Rotation with Python's native complex numbers ---
4# Rotate 2D point (1, 0) by 45 degrees
5point_2d = 1.0 + 0.0j
6angle_2d = math.radians(45)
7
8# Rotation factor: e^(i*theta) = cos(theta) + i*sin(theta)
9rotation_2d = complex(math.cos(angle_2d), math.sin(angle_2d))
10rotated_2d = point_2d * rotation_2d
11print(f"2D Rotated: ({rotated_2d.real:.3f}, {rotated_2d.imag:.3f})")
12
13# --- 3D Rotation with Quaternions (Hypercomplex Numbers) ---
14# Extends complex numbers to 4D with three imaginary units: i, j, k
15# satisfying: i² = j² = k² = ijk = -1
16# q = w + xi + yj + zk
17q = Quaternion(w=1.0, x=0.0, y=0.0, z=0.0) # Identity rotation
18is_unit = math.isclose(q.w**2 + q.x**2 + q.y**2 + q.z**2, 1.0)
19print(f"Is unit quaternion: {is_unit}")
Relationship to Complex Numbers:
Just as standard 2D complex numbers z = a + bi (with i² = -1) rotate points in 2D by multiplication, quaternions extend this to 4D space as hypercomplex numbers of the form q = w + xi + yj + zk. Hamilton's fundamental formula defines the relationship between the three imaginary units:
i² = j² = k² = ijk = -1
Multiplying a 3D point by a quaternion via the sandwich product q * v * q⁻¹ performs a clean, gimbal-lock-free 3D rotation.
Rotate X
0.00
Rotate Y
45.00
Rotate Z
0.00
3 min read•1 page

Gimbal Lock:

The primary motivation for Quaternions: solving the singularity of Euler angles when the middle gimbal reaches 90°.
python
1import math
2
3def euler_zyx_matrix(roll_x: float, pitch_y: float, yaw_z: float):
4 """When pitch_y == pi/2 (90 deg), cos(pitch_y) = 0 and sin(pitch_y) = 1.
5 The rotation matrix collapses so yaw_z and roll_x act on the exact same axis!
6 """
7 # At pitch_y = 90 deg:
8 # R = [
9 # [0, sin(roll_x - yaw_z), cos(roll_x - yaw_z)],
10 # [0, cos(roll_x - yaw_z), -sin(roll_x - yaw_z)],
11 # [-1, 0, 0 ]
12 # ]
13 # Notice only (roll_x - yaw_z) appears: 1 degree of freedom is lost (Gimbal Lock)!
14 return roll_x - yaw_z
Imagine a gyroscope with three rings (X, Y, Z). If you rotate the middle ring (Pitch) exactly 90 degrees, it folds flat against the outer ring. Suddenly, two of your rings spin on the exact same axis — losing a degree of freedom! Quaternions rotate smoothly in 4D space without singularities.
1. Pitch (Y) -> Try 90°
90.00
2. Roll / Yaw -> Compare Euler vs Quaternion
45.00
3 min read•1 page

Data Structure (x, y, z, w):

At its core, a quaternion is stored in memory as an ordered list of four floating-point numbers (x, y, z, and w) satisfying x² + y² + z² + w² = 1.
python
1import math
2
3# Define unit axis and rotation angle
4axis = Vector3(0.577, 0.577, 0.577) # normalized (1, 1, 1)
5angle_rad = math.radians(theta)
6
7# A rotation by angle theta around a unit axis (ux, uy, uz) maps to:
8# w = cos(theta / 2)
9# (x, y, z) = (ux, uy, uz) * sin(theta / 2)
10theta_half = angle_rad / 2.0
11w = math.cos(theta_half)
12s_half = math.sin(theta_half)
13
14x = axis.x * s_half
15y = axis.y * s_half
16z = axis.z * s_half
17
18# Unit norm invariant: x^2 + y^2 + z^2 + w^2 == 1.0
Trigonometric Vector & Scalar Split:
For a rotation of angle θ around a normalized axis u = (ux, uy, uz), the quaternion components are defined by:
• Real scalar part: w = cos(θ / 2)
• Imaginary vector part: (x, y, z) = u * sin(θ / 2)
Angle θ (°)
90.00
4 min read•1 page

FromAxisAngle / FromEuler:

Constructs a 4D Quaternion from human-readable inputs like Euler angles (X, Y, Z) or an Axis + Angle.
python
1import math
2
3def from_axis_angle(axis: Vector3, angle_rad: float) -> Quaternion:
4 """Constructs a unit quaternion from a normalized rotation axis and angle."""
5 u = axis.Normalize()
6 half = angle_rad * 0.5
7 s = math.sin(half)
8 return Quaternion(u.x * s, u.y * s, u.z * s, math.cos(half))
9
10def from_euler(rx: float, ry: float, rz: float) -> Quaternion:
11 """Constructs a quaternion from Euler angles (Roll, Pitch, Yaw in radians)."""
12 cx, sx = math.cos(rx * 0.5), math.sin(rx * 0.5)
13 cy, sy = math.cos(ry * 0.5), math.sin(ry * 0.5)
14 cz, sz = math.cos(rz * 0.5), math.sin(rz * 0.5)
15
16 w = cx * cy * cz + sx * sy * sz
17 x = sx * cy * cz - cx * sy * sz
18 y = cx * sy * cz + sx * cy * sz
19 z = cx * cy * sz - sx * sy * cz
20 return Quaternion(x, y, z, w)
Euler: Combine three sequential rotations (Roll, Pitch, Yaw).
Axis-Angle: Pick a 3D vector (the axis) and rotate around it by angle θ.
Construction Mode
Angle / Pitch X (°)
45.00
Axis Tilt / Yaw Y (°)
45.00
Roll Z (°)
0.00
4 min read•1 page

Applying Rotation:

Quaternions are easily constructed from an Axis and an Angle, and then applied to transform vectors and points without the artifacts of Euler angles.
python
1import math
2
3
4def rotate_vector(vec: Vector3, axis: Vector3, angle_degrees: float) -> Vector3:
5 # Convert angle to radians
6 angle_rad = math.radians(angle_degrees)
7
8 # Create the rotation quaternion
9 q = Quaternion.from_axis_angle(axis.normalized(), angle_rad)
10
11 # Apply rotation directly to vector
12 return q.rotate(vec)
13
14# --- Manual Conjugation Formula: q * v * q* ---
15# Translates point/vector rotation using raw quaternion multiplication
16def rotate_vector_manual(vec: Vector3, q: Quaternion) -> Vector3:
17 # 1. Represent vector as a "pure" quaternion (w = 0)
18 v_pure = Quaternion(w=0.0, x=vec.x, y=vec.y, z=vec.z)
19
20 # 2. Get conjugate of the rotation quaternion
21 q_conj = Quaternion(w=q.w, x=-q.x, y=-q.y, z=-q.z)
22
23 # 3. Rotate via sandwich product: v' = q * v * q*
24 rotated_pure = q * v_pure * q_conj
25
26 # 4. Extract 3D coordinates from vector part (x, y, z)
27 return Vector3(rotated_pure.x, rotated_pure.y, rotated_pure.z)
28
29# Example usage:
30v = Vector3(2.0, 0.0, 0.0)
31rot_axis = Vector3(0.0, 1.0, 1.0)
32result = rotate_vector(v, rot_axis, 90.0)
The Conjugation (Sandwich) Formula:
Mathematically, to rotate a 3D point or vector v by quaternion q, we represent v as a pure imaginary quaternion (0, vx, vy, vz). We then compute the sandwich product:
v' = q * v * q*
where q* is the conjugate of q. The rotated vector is extracted from the imaginary part of the result.
Angle (deg)
90.00
3 min read•1 page

Slerp (Interpolation):

Spherical Linear Interpolation smoothly blends between two quaternion orientations. Unlike Euler angle interpolation (which can wobble or experience Gimbal Lock), Slerp takes the shortest, most direct angular path with a constant angular velocity.
python
1import math
2
3def slerp(qa: Quaternion, qb: Quaternion, t: float) -> Quaternion:
4 """Spherical Linear Interpolation along the 4D unit hypersphere."""
5 dot = qa.x * qb.x + qa.y * qb.y + qa.z * qb.z + qa.w * qb.w
6
7 # Ensure shortest arc on S^3 (flip sign if dot < 0)
8 if dot < 0.0:
9 qb = Quaternion(-qb.x, -qb.y, -qb.z, -qb.w)
10 dot = -dot
11
12 if dot > 0.9995:
13 # Linear fallback for very small angles
14 return (qa * (1.0 - t) + qb * t).Normalize()
15
16 omega = math.acos(dot)
17 sin_omega = math.sin(omega)
18
19 w_a = math.sin((1.0 - t) * omega) / sin_omega
20 w_b = math.sin(t * omega) / sin_omega
21
22 return Quaternion(
23 w_a * qa.x + w_b * qb.x,
24 w_a * qa.y + w_b * qb.y,
25 w_a * qa.z + w_b * qb.z,
26 w_a * qa.w + w_b * qb.w
27 )
Interpolate (t)
0.50
2 min read•1 page

Multiplication (Combine):

To combine two rotations, you multiply their quaternions using the Hamilton product. Order matters! Q1 * Q2 is not the same as Q2 * Q1. In most 3D math libraries (like Three.js), A.multiply(B) applies rotation B after rotation A.
python
1def hamilton_product(q1: Quaternion, q2: Quaternion) -> Quaternion:
2 """Combines two rotations via the non-commutative Hamilton product (q1 * q2)."""
3 w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
4 x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y
5 y = q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x
6 z = q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w
7 return Quaternion(x, y, z, w)
Sequence
0.00
2 min read•1 page

Inverse (Conjugate):

The Inverse of a quaternion perfectly reverses its rotation. If you rotate an object byQ and then by Q.invert(), the object returns exactly to its starting orientation (the Identity). Note: For unit/normalized quaternions, the Inverse is mathematically equal to its Conjugate.
python
1import math
2
3def reverse_rotation(q: Quaternion) -> Quaternion:
4 # For a unit quaternion, the inverse is equivalent to its conjugate.
5 # Returns a new quaternion representing the reverse rotation.
6 return Quaternion(w=q.w, x=-q.x, y=-q.y, z=-q.z)
7
8# Example usage:
9q = Quaternion.from_axis_angle(Vector3(1, 0, 0), math.radians(45))
10q_inv = reverse_rotation(q)
11
12# Multiplying Q by Q_inv yields the Identity quaternion (no rotation)
13q_identity = q * q_inv
Sequence
0.00
2 min read•1 page

Normalize:

Only Unit Quaternions (magnitude exactly equal to 1) represent valid pure rotations. After many math operations, floating-point drift can un-normalize them, causing unwanted scaling artifacts. Always Normalize your quaternions if doing heavy continuous math!
python
1import math
2
3def fix_drift(q: Quaternion) -> Quaternion:
4 # Creates a copy of the quaternion and normalizes it.
5 # Normalizing scales the magnitude back to 1.0, removing floating-point drift scaling errors.
6 mag = math.sqrt(q.w**2 + q.x**2 + q.y**2 + q.z**2)
7 if mag == 0:
8 return Quaternion(1.0, 0.0, 0.0, 0.0)
9 return Quaternion(w=q.w/mag, x=q.x/mag, y=q.y/mag, z=q.z/mag)
10
11# Example usage:
12# Create an unnormalized quaternion (with scale drift)
13q_drifted = Quaternion(1.5, 0.0, 0.0, 0.0)
14q_clean = fix_drift(q_drifted)
State
0.00
2 min read•1 page

Dot(Quaternion):

Calculates the 4D dot product between two quaternions, representing their orientation similarity.
python
1def quaternion_dot(q1: Quaternion, q2: Quaternion) -> float:
2 # 4D Dot Product of components
3 return q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z
4
5# Example usage:
6q_a = Quaternion(1.0, 0.0, 0.0, 0.0) # Identity
7q_b = Quaternion.from_axis_angle(Vector3(0, 1, 0), math.radians(90))
8dot_val = quaternion_dot(q_a, q_b) # Returns ~0.707 (cos(45°))
Mathematical Property:
For two normalized/unit quaternions, their dot product equals the cosine of half the angle between their orientations:
q1 · q2 = cos(θ / 2)
If |q1 · q2| = 1.0, the orientations are identical. If q1 · q2 = 0.0, the rotations are 180° apart. Note that q and -q represent the exact same physical orientation.
Rotate Y (deg)
0.00
2 min read•1 page

AngleTo(Quaternion):

Calculates the angular difference between two quaternion rotations in radians.
python
1import math
2
3def quaternion_angle_to(q1: Quaternion, q2: Quaternion) -> float:
4 # 4D dot product
5 dot = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z
6
7 # Restrict to absolute dot product for shortest angular path
8 abs_dot = min(1.0, abs(dot))
9
10 # Angle formula: theta = 2 * acos(|q1 · q2|)
11 return 2.0 * math.acos(abs_dot)
12
13# Example usage:
14q_a = Quaternion(1.0, 0.0, 0.0, 0.0) # Identity
15q_b = Quaternion.from_axis_angle(Vector3(0, 1, 0), math.radians(90))
16angle = quaternion_angle_to(q_a, q_b) # Returns ~1.5708 radians (90°)
Shortest Arc Angle:
Because q and -q represent the same spatial rotation, taking the absolute value of the dot product before calculating acos ensures we get the shortest angular distance (between 0 and π radians).
Angle Y (deg)
0.00
2 min read•1 page

RotationDifference(Quaternion, Quaternion):

Calculates the relative quaternion rotation required to transform orientation A into orientation B.
python
1def rotation_difference(q_from: Quaternion, q_to: Quaternion) -> Quaternion:
2 # Compute relative rotation: q_diff = q_to * q_from_inverse
3 # For unit quaternions, inverse is the conjugate
4 q_from_conj = Quaternion(w=q_from.w, x=-q_from.x, y=-q_from.y, z=-q_from.z)
5
6 # Multiply to find difference rotation
7 return q_to * q_from_conj
8
9# Example usage:
10q_start = Quaternion.from_axis_angle(Vector3(0, 1, 0), math.radians(30))
11q_target = Quaternion.from_axis_angle(Vector3(0, 1, 0), math.radians(90))
12q_diff = rotation_difference(q_start, q_target) # Returns 60° rotation around Y
Algebraic Relationship:
To find the rotation q_diff that maps orientation A to B, we satisfy the equation:
q_diff * q_A = q_B => q_diff = q_B * q_A⁻¹
Since both are unit quaternions, we can optimize by multiplying q_B with the conjugate q_A* directly.
Change Target
45.00
3 min read•1 page

Align Vectors:

To align one vector directly onto another, we find the shortest-arc rotation between them. The rotation axis is the cross product of the two vectors, and the angle is the arccosine of their dot product.
python
1import math
2
3def align_vectors(v_from: Vector3, v_to: Vector3) -> Quaternion:
4 # Ensure vectors are normalized
5 v1 = v_from.normalized()
6 v2 = v_to.normalized()
7
8 # Calculate dot product and cross product axis
9 dot = v1.dot(v2)
10 axis = v1.cross(v2)
11
12 # Handle collinear opposite vectors (180 deg)
13 if dot < -0.999999:
14 # Find orthogonal vector to v1 as axis
15 orthogonal = Vector3(1, 0, 0) if abs(v1.x) < 0.8 else Vector3(0, 1, 0)
16 axis = v1.cross(orthogonal).normalized()
17 return Quaternion.from_axis_angle(axis, math.pi)
18
19 # Handle collinear same vectors (0 deg)
20 if dot > 0.999999:
21 return Quaternion(1.0, 0.0, 0.0, 0.0) # Identity
22
23 # Calculate half-angle components directly for stable math
24 s = math.sqrt((1.0 + dot) * 2.0)
25 inv_s = 1.0 / s
26
27 w = s * 0.5
28 x = axis.x * inv_s
29 y = axis.y * inv_s
30 z = axis.z * inv_s
31
32 quat = Quaternion(w, x, y, z)
33 return quat.normalized()
Target Y
1.50
Target Z
1.00
4 min read•1 page

Extraction (To Euler / Axis-Angle):

Since raw quaternion values (x, y, z, w) are difficult to visualize, we often extract Euler Angles (Pitch, Yaw, Roll) or the Axis/Angle representation from them to display in user interfaces or debug output.
python
1import math
2
3def extract_axis_angle(q: Quaternion):
4 """Extracts (angle_rad, axis_vec) from a unit quaternion."""
5 s = math.sqrt(max(0.0, 1.0 - q.w * q.w))
6 if s < 1e-4:
7 return 0.0, Vector3(0.0, 1.0, 0.0)
8 return 2.0 * math.acos(q.w), Vector3(q.x / s, q.y / s, q.z / s)
9
10def extract_euler_xyz(q: Quaternion) -> tuple[float, float, float]:
11 """Extracts Roll (X), Pitch (Y), Yaw (Z) Euler angles in radians."""
12 # Roll (X-axis rotation)
13 sinr_cosp = 2.0 * (q.w * q.x + q.y * q.z)
14 cosr_cosp = 1.0 - 2.0 * (q.x * q.x + q.y * q.y)
15 roll = math.atan2(sinr_cosp, cosr_cosp)
16
17 # Pitch (Y-axis rotation)
18 sinp = max(-1.0, min(1.0, 2.0 * (q.w * q.y - q.z * q.x)))
19 pitch = math.asin(sinp)
20
21 # Yaw (Z-axis rotation)
22 siny_cosp = 2.0 * (q.w * q.z + q.x * q.y)
23 cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z)
24 yaw = math.atan2(siny_cosp, cosy_cosp)
25 return roll, pitch, yaw
Pitch (X)
45.00
Yaw (Y)
45.00
3 min read•1 page

ToMatrix():

Bridges the abstract mathematical world of Quaternions to the physical rendering pipeline of Matrices.
python
1def QuaternionToMatrix(q: Quaternion) -> list[list[float]]:
2 """Converts a unit quaternion (x, y, z, w) into a 3x3 rotation matrix."""
3 x, y, z, w = q.x, q.y, q.z, q.w
4
5 xx, yy, zz = x * x, y * y, z * z
6 xy, xz, yz = x * y, x * z, y * z
7 wx, wy, wz = w * x, w * y, w * z
8
9 return [
10 [1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy)],
11 [ 2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx)],
12 [ 2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy)]
13 ]
Quaternions are compact and immune to Gimbal Lock, while GPUs transform vertex buffers using 4x4 Transformation Matrices. The columns of the resulting 3x3 upper-left matrix are literally the rotated X, Y, and Z basis axes!
Rotate Quaternion (°)
45.00

Related Concepts