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 math23# --- 2D Rotation with Python's native complex numbers ---4# Rotate 2D point (1, 0) by 45 degrees5point_2d = 1.0 + 0.0j6angle_2d = math.radians(45)78# 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_2d11print(f"2D Rotated: ({rotated_2d.real:.3f}, {rotated_2d.imag:.3f})")1213# --- 3D Rotation with Quaternions (Hypercomplex Numbers) ---14# Extends complex numbers to 4D with three imaginary units: i, j, k15# satisfying: i² = j² = k² = ijk = -116# q = w + xi + yj + zk17q = Quaternion(w=1.0, x=0.0, y=0.0, z=0.0) # Identity rotation18is_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
Multiplying a 3D point by a quaternion via the sandwich product
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 = -1Multiplying a 3D point by a quaternion via the sandwich product
q * v * q⁻¹ performs a clean, gimbal-lock-free 3D rotation.Rotate X
0.00Rotate Y
45.00Rotate Z
0.003 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 math23def 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.002. Roll / Yaw -> Compare Euler vs Quaternion
45.003 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 math23# Define unit axis and rotation angle4axis = Vector3(0.577, 0.577, 0.577) # normalized (1, 1, 1)5angle_rad = math.radians(theta)67# 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.011w = math.cos(theta_half)12s_half = math.sin(theta_half)1314x = axis.x * s_half15y = axis.y * s_half16z = axis.z * s_half1718# Unit norm invariant: x^2 + y^2 + z^2 + w^2 == 1.0
Trigonometric Vector & Scalar Split:
For a rotation of angle
• Real scalar part:
• Imaginary vector part:
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.004 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 math23def 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.57 s = math.sin(half)8 return Quaternion(u.x * s, u.y * s, u.z * s, math.cos(half))910def 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)1516 w = cx * cy * cz + sx * sy * sz17 x = sx * cy * cz - cx * sy * sz18 y = cx * sy * cz + sx * cy * sz19 z = cx * cy * sz - sx * sy * cz20 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
Axis-Angle: Pick a 3D vector (the axis) and rotate around it by angle
θ.Construction Mode
Angle / Pitch X (°)
45.00Axis Tilt / Yaw Y (°)
45.00Roll Z (°)
0.004 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 math234def rotate_vector(vec: Vector3, axis: Vector3, angle_degrees: float) -> Vector3:5 # Convert angle to radians6 angle_rad = math.radians(angle_degrees)78 # Create the rotation quaternion9 q = Quaternion.from_axis_angle(axis.normalized(), angle_rad)1011 # Apply rotation directly to vector12 return q.rotate(vec)1314# --- Manual Conjugation Formula: q * v * q* ---15# Translates point/vector rotation using raw quaternion multiplication16def 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)1920 # 2. Get conjugate of the rotation quaternion21 q_conj = Quaternion(w=q.w, x=-q.x, y=-q.y, z=-q.z)2223 # 3. Rotate via sandwich product: v' = q * v * q*24 rotated_pure = q * v_pure * q_conj2526 # 4. Extract 3D coordinates from vector part (x, y, z)27 return Vector3(rotated_pure.x, rotated_pure.y, rotated_pure.z)2829# 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
where
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.003 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 math23def 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.w67 # 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 = -dot1112 if dot > 0.9995:13 # Linear fallback for very small angles14 return (qa * (1.0 - t) + qb * t).Normalize()1516 omega = math.acos(dot)17 sin_omega = math.sin(omega)1819 w_a = math.sin((1.0 - t) * omega) / sin_omega20 w_b = math.sin(t * omega) / sin_omega2122 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.w27 )
Interpolate (t)
0.502 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.z4 x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y5 y = q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x6 z = q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w7 return Quaternion(x, y, z, w)
Sequence
0.002 min read•1 page
Inverse (Conjugate):
The Inverse of a quaternion perfectly reverses its rotation. If you rotate an object by
Q 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 math23def 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)78# Example usage:9q = Quaternion.from_axis_angle(Vector3(1, 0, 0), math.radians(45))10q_inv = reverse_rotation(q)1112# Multiplying Q by Q_inv yields the Identity quaternion (no rotation)13q_identity = q * q_inv
Sequence
0.002 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 math23def 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)1011# 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.002 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 components3 return q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z45# Example usage:6q_a = Quaternion(1.0, 0.0, 0.0, 0.0) # Identity7q_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:
If
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.002 min read•1 page
AngleTo(Quaternion):
Calculates the angular difference between two quaternion rotations in radians.
python
1import math23def quaternion_angle_to(q1: Quaternion, q2: Quaternion) -> float:4 # 4D dot product5 dot = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z67 # Restrict to absolute dot product for shortest angular path8 abs_dot = min(1.0, abs(dot))910 # Angle formula: theta = 2 * acos(|q1 · q2|)11 return 2.0 * math.acos(abs_dot)1213# Example usage:14q_a = Quaternion(1.0, 0.0, 0.0, 0.0) # Identity15q_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
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.002 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_inverse3 # For unit quaternions, inverse is the conjugate4 q_from_conj = Quaternion(w=q_from.w, x=-q_from.x, y=-q_from.y, z=-q_from.z)56 # Multiply to find difference rotation7 return q_to * q_from_conj89# 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
Since both are unit quaternions, we can optimize by multiplying
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.003 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 math23def align_vectors(v_from: Vector3, v_to: Vector3) -> Quaternion:4 # Ensure vectors are normalized5 v1 = v_from.normalized()6 v2 = v_to.normalized()78 # Calculate dot product and cross product axis9 dot = v1.dot(v2)10 axis = v1.cross(v2)1112 # Handle collinear opposite vectors (180 deg)13 if dot < -0.999999:14 # Find orthogonal vector to v1 as axis15 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)1819 # Handle collinear same vectors (0 deg)20 if dot > 0.999999:21 return Quaternion(1.0, 0.0, 0.0, 0.0) # Identity2223 # Calculate half-angle components directly for stable math24 s = math.sqrt((1.0 + dot) * 2.0)25 inv_s = 1.0 / s2627 w = s * 0.528 x = axis.x * inv_s29 y = axis.y * inv_s30 z = axis.z * inv_s3132 quat = Quaternion(w, x, y, z)33 return quat.normalized()
Target Y
1.50Target Z
1.004 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 math23def 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)910def 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)1617 # 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)2021 # 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.00Yaw (Y)
45.003 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.w45 xx, yy, zz = x * x, y * y, z * z6 xy, xz, yz = x * y, x * z, y * z7 wx, wy, wz = w * x, w * y, w * z89 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