Welcome to the next step in our journey to build a physics engine. In the last lesson, you constructed a crucial part of our math library: the ability to represent 3D orientation and convert between formats like matrices, axis-angle, and quaternions. You now have a quaternion type, but it's like a tool that's still in its box. Today, we're going to take that tool out and learn how to use it.
This lesson focuses on implementing the fundamental operations that make quaternions the powerhouse of 3D rotation. We will cover three key implementations:
- Multiplication, which allows us to combine successive rotations.
- Normalization, which keeps our rotations pure and prevents numerical errors from accumulating.
- Vector Rotation, the main event, where we use a quaternion to actually rotate a point or direction in 3D space.
By the end of this lesson, you will have endowed your quaternion class with the core arithmetic needed for a physics simulation, paving the way for us to simulate rotating objects.
Composing Rotations with Multiplication
First, let's tackle multiplication. When we multiply two quaternions, we are not performing a simple component-wise multiplication. Instead, the product of two quaternions, say , represents the composition of their respective rotations. If represents rotation A and represents rotation B, then represents rotation A followed by rotation B. This is a crucial point: the order matters, and quaternion multiplication is non-commutative, just like matrix multiplication.
The underlying rules for this operation stem from the relationships between the imaginary components :
This cyclical and anti-commutative relationship can be visualized as follows:

While we could derive the full multiplication formula from these rules, it's more practical to use a form that leverages your existing vector math library. If we represent two quaternions as scalar-vector pairs, and , their product is:
Here, · is the dot product and × is the cross product. This formula elegantly combines the quaternion algebra with the vector operations you are already familiar with.
(Yet another) Introduction to quaternions
This blog post provides one of the clearest explanations of quaternion multiplication. It introduces the scalar-vector representation and derives the multiplication rule in terms of dot and cross products.
Focus on the section Scalar-vector representation. This will solidify the connection between the formula above and the underlying algebra. Don't worry about memorizing the long 4-tuple formula; the scalar-vector version is what you'll want to implement.
Now, let's translate this into code. You can implement this as an overloaded operator* for your quaternion class.
Developing a Math Engine in C++: Implementing Quaternions — Harold Serrano - Game Engine Developer
This article provides a straightforward C++ implementation for quaternion operations.
In the Product section, review the C++ code for operator*. Note how it directly maps to the scalar-vector formula we just discussed, using functions like dot() and cross(), which you should already have in your vec3 class.
Maintaining Stability with Normalization
When we simulate motion over time, we will repeatedly multiply quaternions. Due to the inherent limitations of floating-point arithmetic, small errors will accumulate, and our quaternions will gradually drift away from being "unit quaternions" (quaternions with a length of 1). A non-unit quaternion represents more than just a pure rotation; it includes scaling. To prevent our simulated objects from mysteriously shrinking or expanding, we must periodically re-normalize their orientation quaternions.
The process is identical to normalizing a vector:
- Calculate the quaternion's magnitude (or norm).
- Divide each component of the quaternion by this magnitude.
The magnitude of a quaternion is given by:
A normalized quaternion is then:
To implement this, you'll want to add norm() (or magnitude()) and normalize() methods to your quaternion class.
This video provides a quick and clear summary of the essential quaternion operations.
Watch the segment explaining the formulas for norm and normalization. This will visually reinforce the simple math behind the operation. The code implementation is a direct translation of these vector-like formulas.
The Main Event: Rotating a Vector
Now we arrive at the primary application of quaternions in our engine: rotating a vector. You might intuitively think we could just multiply the quaternion by the vector, but it's not quite that simple. This operation would result in a new quaternion that has a non-zero scalar part, so it's no longer a pure vector.
The correct method is the sandwich product. To rotate a vector by a unit quaternion , we first represent as a "pure" quaternion with a zero scalar part: . The rotated vector, , is found in the vector part of the resulting quaternion :
Where is the inverse of . A wonderful property of unit quaternions is that their inverse is simply their conjugate. The conjugate of a quaternion is .
So, for a unit quaternion , the formula simplifies to:
The result, , will be a pure quaternion, and its vector part will be our desired rotated vector .
Game Engine Programming 047.3 - Rotations and quaternions | C++ Game Engine
This video offers an excellent step-by-step derivation of the vector rotation formula.
First, watch the short segment from that shows why a simple multiplication doesn't work. Then, follow the detailed derivation from where the full sandwich product is built up and shown to be equivalent to the standard axis-angle rotation formula. This provides the "why" behind the magic formula.
To implement this, you will need a function that takes a vector and a quaternion. The steps are:
- Create a pure quaternion
pfrom the input vectorv. - Get the conjugate of the rotation quaternion
q. You'll need to implement aconjugate()method first. - Perform the multiplication:
p_prime = q * p * q_conjugate. - Return the vector part of
p_prime.
Let's test this with a concrete example. Suppose we want to rotate the vector by 90 degrees around the Y-axis.
- The axis is and the angle is .
- The rotation quaternion is .
- The vector as a pure quaternion is .
- The conjugate is .
- Calculating yields the pure quaternion .
- The new rotated vector is .
This is the correct result in a right-handed coordinate system. You can now use your debug drawing system to visualize this transformation, drawing the original vector, the rotation axis, and the newly rotated vector to confirm your implementation is correct.
Conclusion
Congratulations, you have now implemented the core machinery of quaternion mathematics. These operations are the foundation upon which all rotational dynamics in our physics engine will be built.
Key Takeaways:
- Quaternion multiplication (
p * q) composes rotations and is non-commutative. Its implementation is most intuitive using the scalar-vector form with dot and cross products. - Normalization is a vital step to counteract floating-point error and ensure quaternions remain pure rotations.
- The sandwich product (
q * v * q*) is the standard, robust method for rotating a 3D vector with a unit quaternion.
You now have a complete and functional quaternion class. We have covered how to represent orientation and how to manipulate it. The only missing piece is how orientation changes over time. In our next lesson, we will bridge the gap between kinematics and dynamics by calculating the time derivative of a quaternion from an angular velocity. This will be the final mathematical tool we need before we can fully integrate the motion of rigid bodies.