Dex: Quaternions
Rotations with quaternions
(This is a Dex reimplementation of the @imadr's lovely little blog post Rotation with Quaternions that includes an example front-end in three.js.)
A quaternion is a 4 dimensional complex-like number, it has four components, three of which are the "imaginary" part.
$$ q = a+b\textrm{i}+c\textrm{j}+d\textrm{k} $$ $$ q = (b,c,d, a) $$ $$ \textrm{i}^{2}=\textrm{j}^{2}=\textrm{k}^{2}=\textrm{i}\textrm{j}\textrm{k}=-1 $$
We represent a quaternion with this data structure:
The four components are usually ordered (w,x,y,z) but I like to put (w) at the end. Initializing a quaternion:
Quaternion magnitude
A quaternion is basically a 4 dimensional vector.
It has a vector magnitude (or norm, or length): $$||q|| = \sqrt{x^{2}+y^{2}+z^{2}+w^{2}}$$
Quaternion normalization
Like vectors a quaternion can be normalized by dividing each component by the magnitude:
A special property of quaternions is that a unit quaternion (a quaternion with magnitude (1)) represents a rotation in 3D space.
Scaling a quaternion
Scaling a quaternion is multiplying each of its components by a real number (the scalar):
Identity quaternion
There is a special quaternion called the identity quaternion which corresponds to no rotation:
Geometrically, we can also consider ((0, 0, 0, -1)) to be an identity quaternion since it corresponds to no rotation.
Quaternion multiplication
Multiplying two unit quaternions represents a composition of two rotations.
Quaternion multiplication isn't commutative . If we want to apply a rotation (q_{1}) then a rotation (q_{2}), the resulting rotation (q_{3}) is: $$q_{3}=q_{2}.q_{1}$$
Quaternion multiplication looks like this: $$q_{1} = a+b\textrm{i}+c\textrm{j}+d\textrm{k}$$ $$q_{2} = e+f\textrm{i}+g\textrm{j}+h\textrm{k}$$
$$ q_{1}.q_{2} = (ae-bf-cg-dh)+(af+be+ch-dg)\textrm{i}+(ag-bh+ce+df)\textrm{j}+(ah+bg-cf+de)\textrm{k} $$
We will make this into a Monoid in Dex.
Quaternion vs Euler angles
We use quaternions instead of Euler angles to represent rotations for a couple of reasons:
- Euler angles suffer from gimbal lock
- Interpolating between two Euler angles lead to weird results
We represent the orientation of an object using only a quaternion, then we multiply that orientation by another quaternion to rotate it.
However writing a rotation directly in quaternion form isn't really intuitive, what we do instead is *convert an Euler angle to a quaternion then use it for rotating.
If we have an Euler angle rotation in the order ZYX (Yaw -> Pitch -> Roll, we can chose any order but must stay consistent), we can convert it to a quaternion like this:
$$ q = \begin{bmatrix} \sin(x/2)\cos(y/2)\cos(z/2)-\cos(x/2)\sin(y/2)\sin(z/2) \\ \cos(x/2)\sin(y/2)\cos(z/2)+\sin(x/2)\cos(y/2)\sin(z/2) \\ \cos(x/2)\cos(y/2)\sin(z/2)-\sin(x/2)\sin(y/2)\cos(z/2) \\ \cos(x/2)\cos(y/2)\cos(z/2)+\sin(x/2)\sin(y/2)\sin(z/2) \\ \end{bmatrix} $$
Drawing with Quaternions
Libraries like three.js allow us to use quaternions directly for rotations.
This script implements a simple 3D box positioned based on a quaternion.
All the way around!
Quaternion Conjugate
The conjugate of a quaternion (q) is:
$$q^{*} = a-b\textrm{i}-c\textrm{j}-d\textrm{k}$$
Quaternion Inverse
The inverse of a quaternion (q), is the conjugate divided by the magnitude squared: $$q^{-1} = \frac{q^{*}}{||q||^{2}}$$
For unit quaternions, the conjugate is equal to the inverse. Multiplying a quaternion by its inverse results in the identity quaternion:
$$q.q^{-1} = (0, 0, 0, 1)$$
Let's try it out
Quaternion difference
The difference of two quaternions (q_{1}) and (q_{2}) is another quaternion (q_{3}) that rotates from (q_{1}) to (q_{2}):
$$q_{3} = q_{1}^{-1}.q_{2}$$
Quaternion Exp and Log
The exponential and the logarithm of a quaternion won't be very useful by themselves, but we will use them to compute other functions later.
Given a quaternion (q = (x,y,z,w)) and its vector part (v = (x,y,z)), the exponential of that quaternion is also a quaternion, and it's given by this formula:
$$\exp(q) = \exp(w)\begin{pmatrix} \frac{v_{x}}{||v||}\sin(||v||)\\ \frac{v_{y}}{||v||}\sin(||v||)\\ \frac{v_{z}}{||v||}\sin(||v||)\\ \cos(||v||) \end{pmatrix}$$
Tangent: to implement quaternion logs we need arccos. We don't have this yet in Dex so this is an approximation (https://developer.download.nvidia.com/cg/acos.html)
NVidia CG Toolkit 3.1. NVidia Corporation. https://developer.nvidia.com/cg-toolkit
The logarithm of a quaternion is also a quaternion and is given by this formula:
$$\log(q) = \begin{pmatrix} \frac{v_{x}}{||v||}\arccos(\frac{w}{||q||})\\ \frac{v_{y}}{||v||}\arccos(\frac{w}{||q||})\\ \frac{v_{z}}{||v||}\arccos(\frac{w}{||q||})\\ \log(||q||) \end{pmatrix}$$
Quaternion exponentiation
Raising a quaternion to a power results in either a fraction or a multiple of that quaternion. (q^{2}) represents twice the rotation of (q), and (q^{0.5}) represents half of that rotation.
$$q^{n} = \exp(n\log(q))$$
Quaternion slerping
Arguably one of the most important advantages of quaternions, "Slerp" stands for spherical linear interpolation. It's a function thats takes three parameters: a quaternion (q_{1}), a quaternion (q_{2}) and an interpolation parameter (t) that goes from (0) to (1). It gives us an intermediate rotation depending on the value of (t).
$$\textrm{slerp}(q_{1}, q_{2}, t) = q_{1}(q_{1}^{-1}q_{2})^{t}$$
Here's what it looks like each step of the way