You probably recall from graphics class that we like to use objects called "quaternions" to represent rotations. These notes provide an engineer's-eye view of why this makes sense.
Recall: Why not euler angles? Gimbal lock.
Let's start with an obvious alternative to Euler angles: storing a rotation as a pair of an unit-length axis \(u\) (the direction to rotate around) and an angle \(\theta\) (the angle to rotate by). Now we can have our code perform rotations as follows:
\[ \mathrm{rotate}((u,\theta),s) \equiv (s\cdot u)u + \cos(\theta)(s - (s\cdot u)u) + \sin(\theta)(u \times s) \]This is a perfectly fine way to rotate things, but we find ourself with a few annoying problems: composing rotations; \(\theta\) being an angle (gotta compute trig functions every rotation; gotta throw a mod2pi into computations occasionally).
Let's define three units -- \(\mathbf{i}\), \(\mathbf{j}\), and \(\mathbf{k}\) -- and the following identity \(\mathbf{i}^2=\mathbf{j}^2=\mathbf{k}^2=\mathbf{i}\mathbf{j}\mathbf{k}=-1\).
Consequences of the above identity (note: unit products turn out to be antisymmetric!):
\[ \mathbf{i}\mathbf{j} = -\mathbf{i}\mathbf{j}(\mathbf{k}^2) = -(\mathbf{i}\mathbf{j}\mathbf{k})\mathbf{k} = \mathbf{k} \] \[ \mathbf{j}\mathbf{i} =-\mathbf{k}^2 \mathbf{j}\mathbf{i} =-(\mathbf{k}\mathbf{i}\mathbf{j}) \mathbf{j}\mathbf{i} =-\mathbf{k}(\mathbf{i}\mathbf{j} \mathbf{j}\mathbf{i}) =-\mathbf{k} \] \[ \mathbf{i}\mathbf{k} =-\mathbf{j} \] \[ \mathbf{k}\mathbf{i} = \mathbf{j} \] \[ \mathbf{j}\mathbf{k} = \mathbf{i} \] \[ \mathbf{k}\mathbf{j} =-\mathbf{i} \]We'll call the field we get by adjoining \(\mathbb{R}\) with these units \(\mathbb{H}\):
\[ \mathbb{H} \equiv \{ x \mathbf{i} + y \mathbf{j} + z \mathbf{k} + w \;|\; x,y,z,w\in\mathbb{R} \} \]As a quick warm-up, let's look at what happens if we take two vectors in \(\mathbb{R}^3\), map them to \(\mathbb{H}\) in a reasonable way (\( \def\H#1{{\overrightarrow{#1}}} \H{s} \equiv s_x\mathbf{i} + s_y\mathbf{j} + s_z\mathbf{j} \)), and multiply them:
\[ \begin{eqnarray} \H{u}\H{v} & = & (u_x\mathbf{i} + u_y\mathbf{j} + u_z\mathbf{k}) (v_x\mathbf{i} + v_y\mathbf{j} + v_z\mathbf{k}) \\ & = & u_x\mathbf{i} (v_x\mathbf{i} + v_y\mathbf{j} + v_z\mathbf{k}) + u_y\mathbf{j} (v_x\mathbf{i} + v_y\mathbf{j} + v_z\mathbf{k}) + u_z\mathbf{k} (v_x\mathbf{i} + v_y\mathbf{j} + v_z\mathbf{k}) \\ & = & (-u_x v_x + u_x v_y\mathbf{k} - u_x v_z\mathbf{j}) + (-u_y v_x\mathbf{k} - u_y v_y + u_y v_z\mathbf{i}) + (u_z v_x\mathbf{j} - u_z v_y\mathbf{i} - u_z v_z) \\ & = & (u_y v_z - u_z v_y)\mathbf{i} + (-u_x v_z + u_z v_x)\mathbf{j} + (u_x v_y - u_y v_x)\mathbf{k} + (-u_x v_x - u_y v_y - u_z v_z) \\ & = & \H{u\times v} - u\cdot{}v \end{eqnarray} \]Woah! Notice that we've just computed the cross product minus the dot product, sort of! Actually, this ends up being sort of obvious if you squint a bit, and think about \(\mathbf{i}\), \(\mathbf{j}\), and \(\mathbf{k}\) as the vectors \((1,0,0)\), \((0,1,0)\), and \((0,0,1)\) and the products between then as being resolved with \(\times\).
What about multiplying two quaternions \(q,r\in\mathbb{H}\)?
\[ \begin{eqnarray} (\H{q_{xyz}}+q_{w})(\H{r_{xyz}}+r_{w}) & = & \H{q_{xyz}}(\H{r_{xyz}}+r_{w}) + q_{w}(\H{r_{xyz}}+r_{w}) \\ & = & \H{q_{xyz}}\H{r_{xyz}} +\H{q_{xyz}r_{w}} +\H{q_{w}r_{xyz}} +q_{w}r_{w} \\ & = & \H{q_{xyz} \times r_{xyz}} - q_{xyz} \cdot r_{xyz} +\H{q_{xyz}r_{w}} +\H{q_{w}r_{xyz}} +q_{w}r_{w} \\ & = & \H{q_{xyz} \times r_{xyz} + q_{xyz}r_{w} + q_{w}r_{xyz} } - q_{xyz} \cdot r_{xyz} +q_{w}r_{w} \end{eqnarray} \]One more multiplication exercise:
\[ \require{cancel} \begin{eqnarray} (\H{q}+w)\H{s}(-\H{q}+w) & = & (\H{q}+w)(-\H{s}\H{q}+\H{s}w) \\ & = & (\H{q}+w)(-\H{s\times q} + s \cdot q + \H{s}w) \\ & = & -\H{q}\H{s\times q} + \H{q}(s \cdot q) + \H{q}\H{s}w -w\H{s\times q} + w s \cdot q + w \H{s}w \\ & = & -\H{q\times(s\times q)} + \cancel{q \cdot (s \times q)} + \H{q}(s \cdot q) + w\H{q\times s} - \cancel{w(q \cdot s)} -w\H{s\times q} + \cancel{w s \cdot q} + w \H{s}w \\ & = & \H{-q\times(s\times q) + (s \cdot q)q + 2w(q\times s) + w^2 s} \end{eqnarray} \]What happens if we set \(q = \sin(\frac{\theta}{2})u\) (for unit vector \(u\)) and \(w = \cos(\frac{\theta}{2}) \)?
\[ \require{cancel} \begin{eqnarray} (\H{q}+w)\H{s}(-\H{q}+w) & = & \H{-q\times(s\times q) + (s \cdot q)q + 2w(q\times s) + w^2 s} \\ & = & \H{ - \sin^2\left(\frac{\theta}{2}\right)(u\times(s\times u)) + \sin^2\left(\frac{\theta}{2}\right)((s\cdot u) u) + 2\cos\left(\frac{\theta}{2}\right)\sin\left(\frac{\theta}{2}\right)(u\times s) + \cos^2\left(\frac{\theta}{2}\right)s } \\ & = & \H{ -\sin^2\left(\frac{\theta}{2}\right)(s-(s \cdot u)u) + \sin^2\left(\frac{\theta}{2}\right)((s\cdot u) u) + 2\cos\left(\frac{\theta}{2}\right)\sin\left(\frac{\theta}{2}\right)(u\times s) + \cos^2\left(\frac{\theta}{2}\right)s } \\ & = & \H{ 2\sin^2\left(\frac{\theta}{2}\right)((s\cdot u) u) + 2\cos\left(\frac{\theta}{2}\right)\sin\left(\frac{\theta}{2}\right)(u\times s) + \left(\cos^2\left(\frac{\theta}{2}\right) - \sin^2\left(\frac{\theta}{2}\right)\right)s } \\ & = & \H{ \left(-1+1-2\sin^2\left(\frac{\theta}{2}\right)\right)(-(s\cdot u) u) + \sin(\theta)(u\times s) + \cos(\theta)s } \\ & = & \H{ (-1+\cos(\theta))(-(s\cdot u) u) + \sin(\theta)(u\times s) + \cos(\theta)s } \\ & = & \H{ (s\cdot u) u + \sin(\theta)(u\times s) + \cos(\theta)(s - (s\cdot u) u) } \end{eqnarray} \]Which is exactly the formula for rotating by angle \(\theta\) around axis \( u \)!
Quaternions seem to strike a decent balance between memory and efficiency for basic operations; that is, they are the same size as axis+angle, but much cheaper to concatenate or rotate a point by; though for batch rotations, converting to mat3 is still the best strategy.
They are also easy to correct for numerical drift, and very easy to interpolate (see: glm::slerp
).
Memory? Same as a+a, better than mat3:
Storage | |||
---|---|---|---|
rep'n | floats | ||
a+a | 4 | ||
mat3 | 9 | ||
quat | 4 |
Rotation Efficiency? Better than a+a, worse than mat3: (so if you are rotating a lot of points, use a mat3)
Rotate a Vector | |||
---|---|---|---|
rep'n | add | mul | trig |
a+a | 11 | 18 | 2 |
mat3 | 6 | 9 | 0 |
quat | 15 | 23 | 0 |
Concatenation Efficiency? Better than mat3, much better than a+a: (so great for, e.g., character skeletons)
Combine Rotations | |||
---|---|---|---|
rep'n | add | mul | trig |
a+a | ? | ? | yes |
mat3 | 18 | 27 | 0 |
quat | 15 | 16 | 0 |
Error removal? ("normalization"), much better than mat3 (requires, e.g., svd or Gram-Schmidt):
Normalize Rotations | |||
---|---|---|---|
rep'n | add | mul | invSqrt |
a+a | 2 | 6 | 1 |
mat3 | 16-ish | 30-ish | 3 |
quat | 3 | 8 | 1 |