AnyLeaf Blog

Quaternions: A practical guide

Written on Aug. 6, 2022, 7:53 a.m.
Updated Aug. 21, 2022, 3:51 a.m.

Overview

This article demonstrates how to use quaternions for practical applications. In it, we build intuition about how to use them as building blocks to solve engineering and geometry problems. Applications include computer graphics, attitude systems for air and spacecraft, biomechanics, and structural chemistry models. You may have read that quaternions have advantages over Euler angles to represent orientations, including preventing gimbal lock, and advantages over rotation matrices, such as more efficient computation, and compact form. This is true. You may have also read that they're complicated, difficult to use, or counter-intuitive. This isn't!

This article describes several operations that use quaternions and vectors. It doesn't discuss why they work. There are many articles and videos available where you can learn about this, including perspectives from numerical algorithms, visualizations (including stereographic projections of 4d space!), and symbolic operations. Examples are included in the References section at the bottom. This article, in contrast, will make you effective at using quaternions as an engineering tool.

Why are we taking this approach instead of building deeper knowledge? We're building intuition so you'll be able to learn a few general tools, then be able to apply them to a broad range of applications. You should be able to recognize when they're appropriate, how to combine them, and how to apply them to physical systems and models. A deeper approach might describe why the arithmetic we define them with works. A shallower approach might provide complete orientation and rotation systems, like in a computer graphics library. Hopefully we've hit a sweet spot.

This article uses mathematical notation, in a format that can easily be translated to any procedural programming language. It includes the information you need to turn these operations into code, in a language-agnostic format. It uses some mathematical conventions, with limited abstractness.

The key takeaway is that quaternions are a powerful, elegant tool that can be used whenever modelling rotations or orientations in 3D. When viewed this way, they're neither complicated to use, nor to understand.

If you'd like an interactive description, check out this 3Blue1Brown + Ben Eater collaboration. It's a page containing interactive videos that explain and visualize the significance of quaternions, including for rotations. It lets you pause the videos, then interact with the visualizations. Further, this article doesn't describe the algebra of quaternions; if you're curious, this article goes into detail.

Ken Shoemake popularized quaternions for use in computer graphics in this 1985 paper. He also wrote an outstanding tutorial. Check these out.


Basics

A quaternion is described by a set of 4 numbers, labeled \(w\), \(x\), \(y\), and \(z\). In code, this may be represented by a struct (or class) with 4 floating-point fields, and functions (eg methods) that perform various operations, and overload arithmetic operators. We can format it like this:

$$ Q = [w, x, y, z] $$

All quaternions in this article are unit quaternions; they have a magnitude of 1. These are also known as versors. Magnitude in this context is defined in a similar way as with vectors: The square root of the sum of squares of each component:

$$ || Q || = \sqrt{w^2 + x^2 + y^2 + z^2} $$

$$ \hat{Q} = \frac{Q}{|| Q ||} $$

All vectors in this article are unit vectors in 3 dimensions. Many of the operations involve vectors; some knowledge of vectors and linear algebra is required.

Unit quaternions double-cover the set of all possible 3d rotations; every rotation can be represented by either of 2 unique quaternions. For details on this, check out this paper by Neil Bickford.


Rotation and orientation

Orientation and rotation quaternions are equivalent: The distinction is that ones used for orientation describe the operation that rotates the identity quaternion to the orientation. The identity quaternion is defined as having a \(w\) component equal to 1, and all others equal to 0. The distinction is how you use them; it's important to keep track of which quaternions you use for orientation vice rotations, even though the mathematics is the same.

As a convention, we'll use \( O \) for quaternions that represent orientations, and \( R \) for ones that represent rotations.

Quaternions are sometimes compared with matrices as ways to transform space. We'll go over this with more detail below; the key takeaway is that matrices represent linear transforms; quaternions represent a special case of linear transform: rotations in 3 dimensions.

An important distinction: Quaternions directly describe rotations, and aren't tied to any coordinate system or absolute orientation. Conversely, most computations need a result tied to absolute orientation; a coordinate system. Examples: An aircraft's upright attitude; a 3D model's up orientation; a vector defined by global coordinates.


Anchoring quaternions to coordinates and directions

Let's examine a 3d model of a boat. Its orientation is stored as a quaternion; one that represents the result of its rotation on the unit quaternion. How do we orient it so that its hull is pointed down? The key in how the model is defined, and how the graphics engine is set up. If you'd like the unit quaternion to represent an upright and level orientation, the boat's model should be defined using an up direction that's the same as the graphics engine expects. If not, you can use a different quaternion to represent up.

In an aircraft's attitude system, something similar applies: We move from relative quaternions to absolute orientation by anchoring the system's inputs to, for example, the earth's gravitational field. We could define the up orientation as a unit quaternion where an accelerometer's z axis reads +1G. With this in mind, how the accelerometer is oriented within the aircraft provides the anchor.

As you read through the operations below, you'll notice quaternions can be used to define a rotation between vectors, or rotate a vector to a new position. The theme is that quaternions represent transitions between 2 or more vectors. You may have seen functions in graphics libraries that construct quaternions that point in a direction - note how they always include one or more reference vectors as arguments - eg up or forward.

What about quaternions operating on other unanchored quaternions? These aren't yet anchored, and in most systems, will ultimately act on something else, directly or indirectly. A common example is describing rotations relative to an anchored orientation, or a vector.


Rotating a vector using a quaternion

Rotating a vector is one of the most common applications of quaternions, and is a building block for other operations. Here's how it's done, assuming a unit quaternion and unit vector. \( \circ \) means compose operations; ie multiplying quaternions by other quaternions, and vectors.

$$ \hat{v}' = Q \circ \hat{v} \circ Q^{-1} $$

Where \( \hat{v} \) is the original vector, \( \hat{v}' \) is the rotated vector, \( Q \) is the quaternion that defines the rotation, and \( Q^{-1} \) is its inverse. To take the inverse of a (unit) quaternion, keep same w component, and negate the others:

$$ Q^{-1} = [Q_w, -Q_x, -Q_y, -Q_z] $$

This is also known as the quaternion's conjugate, written as \( Q^* \). For unit quaternions, the conjugate and inverse is the same. An example: if \(Q = [1, 2, 0, -1] \), \(Q^{-1} = [1, -2, 0, 1] \).

Quaternion multiplication is defined as follows. Note that to multiply a quaternion by a vector, convert the vector to a quaternion with \(w=0\), and with \(x\), \(y\), and \(z\) as the vector's respective components.

$$ w = L_w R_w - L_x R_x - L_y R_y - L_z R_z $$ $$ x = L_w R_x + L_x R_w + L_y R_z - L_z R_y $$ $$ y = L_w R_y - L_x R_z + L_y R_w + L_z R_x $$ $$ z = L_w R_z + L_x R_y - L_y R_x + L_z R_w $$

$$ Q_{LR} = L \circ R = [w, x, y, z] $$


Specifying a quaternion that describes the rotation from one vector to another

There are multiple rotations that can describe this; the equation above describes the shortest rotation. \( R \) is the quaternion that rotates unit vector \( \hat{v1} \) to unit vector \( \hat{v2} \).

$$ w = 1 + \hat{v1} \cdot \hat{v2} $$ $$ [x, y, z] = \hat{v1} \times \hat{v2} $$ $$ R = \frac{ [w, x, y, z] }{ ||[w, x, y, z]|| } $$

Note: The above approach will fail if the vectors are parallel; to correct this in code, check that the dot product is equal to 1 or -1. If equal to 1, return the identity quaternion. If equal to -1, return the identity quaternion rotated by π radians. To do this, construct a rotation-around-axis (described below), using a vector orthonormal to either input vector as the axis, and π as the angle. You can construct the orthonormal vector by taking the cross product between our first bond, and an arbitrary unit vector.

This is a versatile operation! Earlier, we mentioned how quaternions aren't anchored to coordinates or directions. Let's say we'd like a 3d model to align with a certain direction. To do this, we use the operation above, with \( \hat{v1} \) as the up vector the model uses, and \( \hat{v2} \) as a vector defining the direction.


Specifying a quaternion that describes rotation around an axis

To create a quaternion \( R \) that describes the rotation of amount \( \theta \) around axis \( \hat{v} \), use this equation:

$$ w = \cos{\frac{\theta}{2}} $$ $$ x = \sin{\frac{\theta}{2}} \hat{v} _x $$ $$ y = \sin{\frac{\theta}{2}} \hat{v} _y $$ $$ z = \sin{\frac{\theta}{2}} \hat{v} _z $$

$$ R = [w, x, y, z] $$

One example of this use is changing the orientation of computer-graphics objects in response to user input. For example, modifying camera orientation in response to mouse movements. You could create a rotation around the X axis ( \( \hat{1, 0, 0} \) ) with one around the y axis ( \(\hat{1, 0, 0} \) ), then apply both of these to the current orientation: \( O_{new} = R_x R_y O \) The rotation amount would be the degree of input in the given directions.


Rotating an orientation, and composing rotations

To rotate a quaternion representing an orientation, perform quaternion multiplication with the rotation quaternion on the left:

$$ O' = R \circ O $$

To compose multiple rotations, perform quaternion multiplication with the first rotation to apply on the right, and the last on the left. When multiple rotations are composed in this way, they are applied right to left. For example, this is how you would apply 2 rotations to an orientation:

$$ R_{12} = R_2 \circ R_1 $$

Here's an example applying both of those techniques; \( O' \) is the orientation as if \( R_1 \), were applied to \( O \), then \( R_2 \), then \( R_3 \).

$$ O' = R_3 \circ R_2 \circ R_1 \circ O $$

Where \( O \) is the initial orientation, and \( O' \) is the orientation after applying \(R_{r1} \) followed by \( R_{r2} \). Note that equivalently, you could compose the rotations first, then apply them. (eg by storing as a variable, or using parentheses to group the rotations)

Quaternion multiplication is non-commutative, meaning order matters.


Finding the quaternion that rotates one orientation to another

This is a re-arrangement of the quaternion multiplication procedure above that rotates an orientation, using this division definition: \( \frac{Q_1}{Q_2} = Q_1 \circ Q_2^{-1} \)

$$ R= O_2 \circ O_1^{-1} $$


Combining these operations

The operations above are powerful when combined - they can solve many orientation and orientation problems.

For example, to align a 3D model with a certain direction, construct a quaternion that rotates the model's desired axis (for example, the up vector) with a vector pointing in the desired direction: This quaternion can be used as the orientation for that model in a graphics engine. You may also need to apply a rotation around that direction vector to fully-specify the orientation. The final orientation will be a composition of these two quaternions.

Consider this example: You are measuring altitude from an aircraft using a radar altimeter. This altimeter points out the bottom of the aircraft, and measures distance to the ground. If the aircraft is level, this is straightforward. What if it's at an angle? The reading will be too high, because the sensor is no longer pointing directly towards the ground. If it's in a pitch (or roll), but otherwise level, you can multiply the cosine of the pitch (or roll) angle by the measured angle to scale it down.

What if the aircraft's in a pitch and roll? Find the aircraft's down vector by rotating \( [0, -1, 0] \) using the aircraft's orientation (attitude) quaternion. Find the angle between this and straight down ( \( [0, -1, 0] \) ) by taking arccos of the dot product between that down vector and the aircraft's down vector. Multiply the sensor reading by the cosine of this angle to find the altitude.

The sections below describe less-fundamental operations that may also be useful:


Quaternions for change of basis

Matrices are the most common way to describe change-of-basis operations in linear algebra. For change of basis operations that are purely rotational (don't stretch or skew the space), quaternions are a more efficient (an arguably more intuitive) approach.

To do this, create a rotation quaternion that rotates between 2 vectors, as described above. Set the first vector to the starting basis vector you wish wish to change, and the second vector to its equivalent in the new basis.


Construction from Euler angles

Creating an orientation quaternion from Euler angles follows from 2 points above: #1: Orientation quaternions are rotations of the identity quaternion. #2: We can construct a rotation around an axis using an axis vector and rotation angle. So, to construct an orientation from angles, we can combine rotations from each basis vector. \( fromAxisAngle \) below is the procedure described in the Specifying a quaternion that describes rotation around an axis section above.

$$ O_x = fromAxisAngle([1, 0, 0], \phi) $$ $$ O_y = fromAxisAngle([0, 1, 0], \psi) $$ $$ O_z = fromAxisAngle([0, 0, 1], \theta) $$ $$ O = O_x \circ O_y \circ O_z $$

Where \( \phi \), \( \psi \), and \( \theta \) are Euler angles around \(x\), \(y\), and \(z\) axes respectively.

Alternatively, we can combine some operations with the following approach:

$$ cy = \cos(\frac{\theta}{2}) $$ $$ sy = \sin(\frac{\theta}{2}) $$ $$ cp = \cos(\frac{\psi}{2}) $$ $$ sp = \sin(\frac{\psi}{2}) $$ $$ cr = \cos(\frac{\phi}{2}) $$ $$ sr = \sin(\frac{\phi}{2}) $$

$$ w = cr \cdot cp \cdot cy + sr \cdot sp \cdot sy $$ $$ x = sr \cdot cp \cdot cy - cr \cdot sp \cdot sy $$ $$ y = cr \cdot sp \cdot cy + sr \cdot cp \cdot sy $$ $$ z = cr \cdot cp \cdot sy - sr \cdot sp \cdot cy $$

$$ O = [w, x, y, z] $$


Conversion to Euler angles

( todo)


Spherical linear interpretation (SLERP)

Given two quaternions that represent orientation, this is how you calculate an intermediate orientation a given portion between the two:

$$ (Q_2 \circ Q_1^{-1})^t \circ Q_1 $$

Where t is the portion between the two quaternions, on a scale between 0 and 1. Of note, the inverse of a quaternion, as described earlier, is itself with its x, y, and k components negated. To take the exponent of a quaternion with a number, (to be continued!)

Interpolation is useful when creating animations, since it lets you determine the orientation at arbitrary steps between starting and ending orientations.


An example using several of the above techniques

Here's an example of applying the techniques above to a practical problem. Note how we use a few tools to do a variety of manipulations on 3d objects. Let's consider the case where we are modelling a chain of atoms connected to each other by chemical bonds. We'll focus on a single bond; we can iterate this procedure to make additional bonds. This is called the forward kinematics problem. We'll ignore physical subtleties, and model the situation like this:

We can break this problem down into determining the orientation and position of each individual atom, using the previous atom's orientation and position, and the bond geometry.

We will define 2 unit vectors that represent the bonds to, and from this atom; these are called bond angles. These will be defined in relation to an atom; these are in local space, not global coordinates (worldspace). Their relative positions are what's important, so we'll pick an arbitrary unit vector for the first:

$$ \hat{bondToNext} = [1, 0, 0] $$

To find the other angle, we can construct a quaternion from the angle between the bonds, and an arbitrary axis orthonormal to the first bond. Note that we can use an arbitrary orthnormal axis because we only care about the relative bond positions. We create it by taking the cross product between our first bond, and an arbitrary unit vector.

$$ \hat{axis} = \hat{bondToNext} \times \hat{anyUnitVec} $$ $$ Q_{bonds} = fromAxisAngle( \hat{axis}, \theta_{bonds} ) $$

Then, we create the other bond vector (The one defined as being at a given angle from the other) by rotating the first using the rotation we created:

$$ \hat{bondToPrev} = Q_{bonds} \circ \hat{bondToNext} \circ Q_{bonds}^{-1} $$

The result is 2 vectors that are aligned relative to each other at the specified bond angle, \( \theta_{bonds} \) We will use these local vectors for all atoms in the chain.

We then align the local bond vector of the previous atom to worldspace based on the prev atom's orientation. We do this by aligning the \( toNext \) local bond from the previous atom to worldspace, then aligning the \( toPrev \) local bond of this atom to it.

$$ bondToThisWorldspace = O_{prev} \circ \hat{bondToNext} \circ O_{prev}^{-1} $$

We apply the rotation quaternion that aligns the (inverse of) the local-space bond to the prev atom with the world-space "to" bond of the previous atom. This is also the orientation of our atom, without applying the dihedral angle (rotation around the bond).

$$ R_{bondAlignment}= fromUnitVecs(- \hat{bondFromThis}, \hat{bondToThisWorldspace}) $$

Now, we rotate the orientation along the dihedral angle, which is a rotation along the bond axis to a specified angle:

$$ R_{aroundAxis} = fromAxisAngle(\hat{bondToThisWorldspace}, \theta) $$

We find our orientation for this atom by applying the 2 rotations sequentially to create our orientation using quaternion multiplication. As we mentioned earlier, order matters here, and operations are ordered right-to-left. So, we apply the bond alignment rotation before the around-axis one.

$$ O = R_{aroundAxis} \circ R_{bondAlignment} \circ O_{prev} $$

To continue the chain, we repeat the process, using this new orientation as \( O_{prev} \) in the next iteration. The next atom's position is \( \hat{bondToNext} \) rotated by this atom's orientation \( O \), multiplied by the bond's length.

The procedure above can also be used to solve an equivalent problem in robotics: Determining the position of a robot arm that has multiple joints.

We can also model the chemical bonds themselves. Given a tube-shaped model in the graphics engine that's defined with it long axis as up ( \( [0, 1, 0] \) ), how could we specify the orientation and position of this to represent a connection between atoms? Hint: One of the sections above describes part of this procedure.


On information content

Let's compare quaternions, rotation matrices, and Euler angles from the perspective of information. You might notice that a 3D rotation matrix has 9 numerical values, while a quaternion, which can represent the same rotation, has only 4. This begs the question: given that a quaternion with 4 values can represent any 3d rotation, why do rotation matrices have 5 extra values? The answer is that matrices can be used to represent more than rotations: They can represent any linear transformation, of which rotations are a subset.

Matrices can be used to stretch, skew, sheer, and rotate, in any combination, making them more flexible. If we are only rotating, matrices encode extra information - this results in more values to store, and less-efficient computation. It also means they're an under-constrained system. With this in mind: Use Quaternions if only modeling rotation. Use matrices if modelling other linear transforms.

Note that compared to Euler angles, quaternions store an extra value. (4 value in a quaternion, vs 3 in Euler angles) This extra value is due to the constraint that the ones we're using are strictly unit quaternions; this explains the extra degree of freedom.


A note on rotors and bivectors

A recent article by Marc Ten Bosch makes a case that quaternions are an obfuscated, unintuitive, specialized case of Rotors. This line from that article highlights one of its main points: "I have always found it important to actually understand the things I am using". In our article, we've explicitly avoided the deeper understanding Mr. Bosch alludes to; this is partly due to his point - it's difficult to understand why quaternion operations work. In his article and accompanying video, Mr. Bosch describes how it's notoriously difficult to achieve this deeper geometric understanding using quaternions, and is easier with rotors.

Rotors can be used for everything quaternions can. For the practical approach we've taken here, rotors don't have a tangible advantage; their operations are equivalent. Quaternions have the distinct advantage of being standardized on domains like computer graphics and attitude determination. With this in mind, I don't agree with the article's title of Let's remove Quaternions from every 3D Engine, but agree that rotors are a worthwhile alternative to explore and implement.

From the article: "We can notice that 3D Rotors look a lot like Quaternions... In fact the code/math is basically the same! The main difference is that \( i \), \( j\) and \( k \) get replaced by \( y∧z \), \( x∧z \) and \( x∧y \), but they work mostly the same way... However, as we have seen, 3D Rotors are a 3D concept that does not require the use of '4D double rotations' or 'stereographic projection' to visualize."

When examining rotations in dimensions higher than 3, Rotors are an exquisite tool, since they generalize to any number of dimensions.


An exercise: Eigenvalues and eigenvectors

For a given linear transform, eigenvectors are vectors that don't change direction when the transform is applied. Eigenvalues are the scale factor the length of these vectors. These are usually discussed in contexts of matrices, but apply to quaternions as well.

This 3Blue1Brown video has information that might help. It has info on the significance of eigenvectors on rotations. It also includes an alternative way of defining linear transforms using them, that maps directly to one of the techniques we described above.


Conclusion

If you're modeling something that uses 3D rotations or orientation, quaternions should be in your toolkit. You should know what operations they can be used for, and be able to look up how to apply them.


References