This article incorrectly states that gimbal lock is a property of Euler angles, and that using quaternions prevents it.
This is a common misconception.
Euler angles can be used to rotate an object exactly the same way as quaternions do with no gimbal lock. Similarly, you can apply quaternions in such a way that gimbal lock will happen (if you wanted to represent a physical system of gimbals with quaternions, where that is a physical property).
Here's an abstract view regarding the inevitability of gimbal lock: The state of a single gimbal is described by an angle. Since angles are mod 360°, that is topologically a circle. The state of three gimbals are then given by three angles. One point on each of three circles is a point on a three-dimensional torus T^3. With no gimbal lock, you get a map from T^3 to SO(3), which is locally a diffeomorphism at every point. For topological reasons, no such map exists: Due to compactness, it would be a cover, but the only covers of SO(3) are SO(3) itself (a single cover) and the three-sphere (unit quaternions, a double cover). And T^3 is distinct from either of these two. Hence gimbal lock is unavoidable with three gimbals. (Four gimbals is a different story, but then you have a redundant dimension to play with.)
lol i'm a dummy for never realizing that SO(3) isn't homeomorphic (the diffeo part isn't necessary to prove this...) to T^3 (which i naively thought because s in SO(3) seemingly has 3 free parameters). surprise surprise SO(3) is actually homeomorphic to P^3 lol.
You can drop “seemingly”: SO(3) is indeed tree-dimensional. And SO(3) a.k.a. P^3 has fundamental group Z_2 a.k.a. GF(2), whereas T^3 has fundamental group Z^3, so they are quite different beasts indeed.
Similarly, slerp is also not a property of quaternions [1], contrary to the claim in the article, and is usually not implemented inside quaternion libraries using quaternion exponentiation like the article does, but by computing angles explicitly [2], for robustness I believe, and also because slerp is designed for normalized quats, not for general quats with non-unit magnitude.
With these two things combined - the two most commonly cited reasons about why to use quaternions (using slerp and avoiding gimbal lock) - what are other reasons to use quaternions? I’m aware that there are moderate compute savings in some cases (a matrix obviously has more degrees of freedom than a rigid orientation). Are there other good reasons to deal in quaternions? There are some reasonable ideas about why not to use them. [3]
One reason to accumulate into quaternions vs a matrix is that compounded errors can add scaling and shear to a rotation matrix, whereas unit quaternions remain pure rotation (and non-unit quaternions can be trivially normalized).
That's reasonable, but compound matrix ops can also re-normalized and re-orthogonalized as they go, right? It's easier with a quat, for sure, but does it make up for the general complications with using quats?
That's why I posted an article that describes what the complications are, reference number [3] above. It's mainly a game-centric and people-centric view of problems with quaternions, not a list of technical problems with the representation. In short, many programmers don't understand quaternions, so using them puts an education burden on the team, a burden that is most frequently un-met. Quats can be less efficient, if you're not paying attention to what you're doing.
The part that is less efficient is applying a rotation to a vector (you need to "sandwich multiply" by your quaternion which involves 3×4 + 4×4 = 28 multiplications, whereas with a matrix you only need 3x3 = 9 multiplications).
But composing, interpolating, exponentiating, etc. is a lot nicer with quaternions. (Easier to reason about, numerically better behaved, computationally cheaper.)
If you need to apply the same rotation to a large number of separate vectors, keep your rotation representation as a quaternion internally and convert to a matrix just before vector rotation.
Many programmers don't understand matrices either. If you're going to be working with 3D engines, some of this stuff you'll just have to sit down and learn.
It depends a bit on the engine too. A long time ago I designed & built a multi-platform 3D game engine that used quaternions internally for many things, but exposed Euler PYR angles for most "game-level" object controls. We (the engine team) asked around and ended up deciding that the minuscule performance gain and better mathematical behavior from using quats everywhere would be more than offset by some of the devs being unable to reason about how the enemy ended up pointing the wrong direction, when looking at values in the debugger.
That is true, but it's safe to say that the number of people who do understand matrices is far greater than the number of people who understand quaternions. It's also worth pointing out that game engines and graphics APIs all deal in matrices, but don't all deal in quaternions. Mats can do what quats can do, but it's not true the other way around, quats cannot do everything matrices can do. Quats don't help with perspective, non-uniform scaling, or shear, just to name a few.
I don't think the "far" part is safe to say, not in this day and age.
Also, it is not really interesting what matrices or quaternions "can do". What is interesting is what we can do with them. And quaternions make a lot of things possible that are intractable or at least a huge pain with matrices.
> compound matrix ops can also re-normalized and re-orthogonalized as they go
Certainly, but as you say it's a bit harder.
> does it make up for the general complications with using quats?
I'm sure that's context dependent, and don't have much of an opinion which way it's likely to break in general. I was just throwing something in the "pro" column that hadn't yet been discussed.
> ...not implemented inside quaternion libraries using quaternion exponentiation like the article does, but by computing angles explicitly...
How else would you compute quaternion exponentiation? I don't think there's really a dichotomy here. When you compute quaternion exponentiation, one natural way to do it (as with complex numbers) is to think of the quaternions as a real magnitude multiplied by a phase. For complex numbers, the magnitude grows according to exp(x) and the phase evolves according to cos(x)+isin(x). This just falls out of Euler's formula. If you know that the magnitude is 1, you take the exp(x) term out, and you end up with a point that moves in a circle.
The same thing applies to quaternions.
I'm aware that there are other ways to compute quaternion exponentiation, but this is just a natural way to do it, especially for people who aren't experts in numeric programming.
The article's quat_pow is implemented using 'quat_exp(quat_scale(quat_log(q), n));' where the code example I posted computes the half-angle of the rotation. If you stand back, I'd agree with you that there's an equivalence here, and it could be argued that Euclideanspace's code example is a kind of simplification and flattening of using an exponentiation function. Still, there are real differences between these two implementations, and the article's here looks conceptually simple, while the one people use in practice looks more complicated, and requires knowing how quaternions works. It's natural if you're fluent in quats, but not necessarily intuitive otherwise.
> If you stand back, I'd agree with you that there's an equivalence here, and it could be argued that Euclideanspace's code example is a kind of simplification and flattening of using an exponentiation function.
You don't have to stand back that far! They're really quite similar pieces of code.
quat_log() is basically a conversion to axis-angle. quat_exp() is basically a conversion from axis-angle back to quaternions. So, the quat_exp(t quat_log(x)) formula, with different names for the functions, is described as:
1. Figure out the angle between the starting and ending position, and the axis of rotation.
2. Vary the angle of rotation smoothly from t=0..1.
3. Convert back from axis-angle to an orientation.
The only funny thing here is that the axis-angle encoding of quaternions uses a magnitude which a factor of two away from the angle in radians, so you'll see the sample code you linked to (with SLERP) use variables like "halfTheta" and "cosHalfTheta", where the quat_exp() and quat_log() formulas simply won't name them that way.
In the end I think the point of learning more math is so you can see past the differences in naming and recognize when two seemingly different approaches to the same problem are really just two different sets of terminology and names for the same approach.
I believe that even though slerp isn't a property of quaterions and can be retrieve without them, having your orientation represented as a vector is a clean way for the developer to hold the state of an orientation and interpolate it. What's happening under the hood shouldn't matter much, the abstraction in the code using quaterions makes it easier for the developer to interpolate orientations without the worry of gibal locking.
It is easier to deal with accumulating floating point when using quaternions than when using matrices. Various other operations are also quite easy to express in terms of quaternions, such as swing/twist decomposition.
Are you simply saying that quaternions can be used to perform the same rotation as the Euler method or are you saying that the rotation information along the Euler axes can also be lost even when using the quaternion method?
Said another way, are you conflating gimbal lock the physical property, with gimbal lock the common bug of creating irreversible rotations or is the bug still possible?
Thanks for the heads up, I'm going to rephrase the statement about gimbal lock and link you article.
And just to be 100% sure, is the approach I'm using the right one: storing a quaternion instead of 3 angles, multiplying, overwriting the rotation value?
And the idea is, _that's_ how you avoid gimbal lock. It's that implementation of multiplying, and overwriting, which can be done with any system that describes and applies rotation, including Euler angles, rotation matrices, etc.
Great article. Presumably the reason this confusing exists exists is that people don't want to store full rotation matrices, so their choices are Euler angles or quaternions, and Euler angles guarantee gimbal lock by quaternions allow you to avoid it. Is that right?
Basically, you store 3 quaternions, representing 3 angles, and combine them to get the final rotation.
You might say "This is just quaternions emulating Euler angles!" and my answer is, sure. You can say the same about rotation matrices. There's nothing inherent about rotation matrices that makes them susceptible to gimbal lock. You can implement them as representing 3 fixed angles, thus gimbal lock, or you can implement them as accumulating rotations, thus no gimbal lock. Same is true of quaternions.
The fact that you can get gimbal lock with quaternions is a feature, not a bug. Quaternions are just one way to describe rotations. Gimbal lock is a natural phenomenon of certain physical rotation systems, and can be described whether you use quaternions, or matrices etc.
If you want to represent different rotations as differential Euler angles, then I think you can say it's a property of Euler angles, or at least linked to them.
This is a common misconception.
Euler angles can be used to rotate an object exactly the same way as quaternions do with no gimbal lock. Similarly, you can apply quaternions in such a way that gimbal lock will happen (if you wanted to represent a physical system of gimbals with quaternions, where that is a physical property).
I wrote a short article demonstrating and clarifying this, hope it helps: https://omar-shehata.medium.com/how-to-fix-gimbal-lock-in-n-...