Quaternions part 3
If you’re looking for the start of the series, go here.
In this post, i’ll be going over the slerp function, which interpolates between two quaternions. There’s a wrong way to do it, a vector way to do it, and a quaternion way to do it.
A warning
For every rotation, there are two quaternions that represent it. Namely,
and
. The reason is that if you flip the axis of rotation, and rotate by a negative angle, it’s the same as rotating by a positive angle around the original axis. This can give you trouble when interpolating quaternions, because
will not be the same rotation as
except at
and
. For correct results, you want the two quaternions to be the ‘closer’ representation. The easiest way to get good results is to find the cosine of the angle between the quaternions (by treating them as 4d vectors):
. if the result is negative, then negate one of the quaternions. This will make the angle traversed by the slerp function be the smaller of the two possibilities.
Also, if you want to rotate through an angle of more than
, you can’t do it easily with a single slerp, since slerp follows the shortest path. To get around this, you can use an intermediate rotation, and slerp from the start to the intermediate, then from the intermediate to the end. You can also keep slerping past 1: slerping to 1 will traverse angle
, so if you wanted to do an extra full rotation, you could keep going until you got to
, so you could slerp until
, but many libraries will clamp at 1.
The wrong way
![]()
This will follow the correct path, but it will do so at an inconsistent speed. the interpolation will be faster when t is close to 0 or 1 than when it is close to
.
The vector way
The basic idea of this method is to consider the quaternions as unit 4d vectors. Given two different unit quaternions, the shortest path between them is going to lie in the plane formed by the two quaternions and the origin. In other words, the path that the slerp takes is made of linear combinations of the input quaternions. If the two quaternions were orthogonal (
), it would be easy. You could just treat the two quaternions as basis vectors of a 2d space, and use ordinary 2d rotation:
![]()
But since the quaternions are not always orthogonal, we have to make an orthogonal quaternion, and then instead of rotating by
, we have to rotate by
where
is the angle between the quaternions, which is
. We can get an orthogonal quaternion by subtracting the projection of
onto
from
:
![]()
![]()
The quaternion way
The vector way works just as well, but is a little awkward if you’re trying to take derivatives, and it doesn’t generalize to higher order interpolation (quadratic, cubic, etc.). The basic idea for quaternion slerp is to find the quaternion
that you need to multiply
by to get
, and use powers of
:
![]()
![]()
But in order to do this, we need to figure out what
is. We can do that by going back to axis-angle representation, but because of history, we’ll call the convert-to-quaternion function
or
, and our convert-to-axis-angle function
. I’ll go into that naming later. Also, notice that instead of having a separate axis and angle, we use a unit axis vector, and multiply it by half the angle. The basic idea of the
function is to convert to axis-angle, multiply the angle by t, then convert back.
![]()
exponentiation:
![]()
or
![]()
log:
![]()
It’s also worth mentioning that you can linearly interpolate rotations in log-space, and then convert the result back. This will give you a constant-speed rotation, and you can handle rotations larger than
if you store them as axis-angle, but it will not always follow the shortest path. If the axes are the same direction (or opposite direction), it will work well, but
Why exp/log?
So, to those familiar with complex numbers, exponentiation and logarithms shouldn’t look too surprising. To the rest of you, i’ll try to make some sense of it. It’s based on the Taylor series, which is
, where
is the n’th derivative of f. One special function is
, which is its own derivative, so:
![]()
also, sin and cos have interesting derivatives. for sin, every even derivative is 0 at 0, and the odd ones alternate between 1 and -1. cos is similar:
![]()
![]()
Also, given a unit vector
,
. So,
,
,
,
, and higher powers just loop through those 4 values. So, let’s see what happens when we try plugging a vector into
:
![]()
![]()
![]()
![]()
![]()
and that looks a lot like our sin and cos taylor series:
![]()
and log is supposed to be the inverse of
, so:
![]()
and we already know that our quaternions are in that form, so
![]()
And if you use
instead of
, you get exactly the axis-angle formula from part 1.
And that’s pretty much all the math you need to use quaternions. In future posts, i’ll talk about higher degree interpolation, and provide some sample code in some language (probably c# or c++).
Twisted Oak Studios offers consulting and development on high-tech interactive projects. Check out our portfolio, or Give us a shout if you have anything you think some really rad engineers should help you with.
Older Posts
- Eventual Exceptions vs Programming in a Minimal Functional Style
- The Mystery of Flunf
- Explain it like I’m Five: The Socialist Millionaire Problem and Secure Multi-Party Computation
- Computer Science Blows My Mind
- A visit to Execution Labs in Montréal
- Transmuting Dice, Conserving Entropy
- Rule of Thumb: Ask for the Clock
- Rule of Thumb: Use Purposefully Weakened Methods
- Rule of thumb: Preconditions Should be Checked Explicitly
- Intersecting Linked Lists Faster
- Mouse Path Smoothing for Jack Lumber
- My Bug, My Bad #2: Sunk by Float
- Repeat Yourself Differently
- Grover’s Quantum Search Algorithm
- Followup to Non-Nullable Types vs C#
- Optimizing Just in Time with Expression Trees
- When One-Way Latency Doesn’t Matter
- Determining exactly if/when/where a moving line intersected a moving point
- Emulating Actors in C# with Async/Await
- Making an immutable queue with guaranteed constant time operations
- Improving Checked Exceptions
- Perishable Collections: The Benefits of Removal-by-Lifetime
- Decoupling shared control
- Decoupling inlined UI code
- Linq to Collections: Beyond IEnumerable<T>
- Publish your .Net library as a NuGet package
- When null is not enough: an option type for C#
- Unfathomable Bugs #5: Readonly or not
- Minkowski sums: examples
- My Bug, My Bad #1: Fractal Spheres
- Working around the brittle UI Virtualization in Windows 8
- Encapsulating Angles
- Unfathomable Bugs #4: Keys that aren’t
- How would I even use a monad (in C#)?
- Useful/Interesting Methods #1: Observable.WhenEach
- Unfathomable Bugs #3: Stringing you along
- Anonymous Implementation Classes – A Design Pattern for C#
- Tasks for ActionScript 3 – Improving on Event-Driven Programming
- Minkowski sums and differences
- Non-Nullable Types vs C#: Fixing the Billion Dollar Mistake
- Unfathomable Bugs #2: Slashing Out
- Script templates and base classes
- Unity font extraction
- Abusing “Phantom Types” to Encode List Lengths Into Their Type
- Constructive Criticism of the Reactive Extensions API
- Quaternions part 2
- Quaternions part 1
- Unfathomable Bugs #1: You can have things! You can have things IN things! You can have …
- Coroutines – More than you want to know
- Asset Bundle Helper
- The Visual Studio goes away
- .Net’s time traveling StopWatch
- Polish
- Introducing Catalyst
