So you meet this cool API online somewhere. It looks pretty fun, pretty powerful. Like just what you've been looking for. You dive in, you're having a good time, things are going great, when all of a sudden, BAM, it whips out a quaternion.
You want to be cool and use it, but you don't really know what you're doing. You panic, you start fiddling around, but everything falls apart. You’re left frustrated, with a broken mess that doesn't make any sense.
Don't worry. We've all been there.
It doesn't have to be like that though. I'm Paul Bernhardt, and I'm here to help. Come with me.
What is a Quaternion?
I was once like you. For a while it seemed like everywhere I looked there was a quaternion staring back at me. You'll find them representing the orientation of every object in Unity. The Myo armband gives you a quaternion whenever you ask for its orientation as well. In fact any time you have an orientation or rotation, you are very likely to run into a quaternion. Here's the thing though: It turns out, quaternions really aren't that scary.
You can think of one as simply a rotation delta. It represents the shortest path to get from one orientation to another. Either from some "forward" origin direction (in which case, it's an orientation) or the direction and magnitude to rotate another orientation (ie, you could have a quaternion that represents rotating 90 degrees clockwise about the Y axis), in which case, it's a rotation.
And that's it. You can stop right there and skip to the next section if you want. Treat them as a magic blob and use a library to handle everything. Do you hand craft each IP packet in binary before you send it out through the network? No. Who cares. We're computer scientists and the entire field is built on layers upon layers of abstraction.
Both the Myo SDK and Unity come with the functions you need, and most other platforms will as well. Don't try and unpack the secret sauce, just enjoy the burger.
That's not going to be enough for everyone, of course. Just don't forget you can pull the ripcord any time and skip to the next section if you want.
Now, you're probably used to dealing with rotations in Euler angles (roll, pitch and yaw). Those can be thought of as a sequence of steps. A rotational path, or journey, if you will. Pitch X degrees, roll Y degrees, then yaw Z (or whatever). Those are nice and intuitive, but they have a few flaws. Gimbal lock is a big one. That's basically when two of your axis line up and you lose a degree of freedom.
Gimbal lock is annoying, but that "or whatever" bit I mentioned in the last paragraph is actually another flaw. Are you pitching or yawing first? What's your frame of reference? Do you have a left or right handed coordinate space? Sure, you can pick answers for all of those choices easily enough, but as soon as you start talking to someone else, or looking at their code, you need to make sure you're on the same page. And for whatever you pick, there are going to be certain combinations of values where things just don't work well.
What I mean is that converting to Euler angles can go all... squirrely near the extremes. The problem is that there are multiple solutions for a given orientation. For example, imagine my arm starts here:
and ends up like this
Now, you and I know the shortest path is to move it like this:
But it's not the only way. You can also do this:
At some point, depending on the angles involved and how you are doing the conversion, you will flip from the first path to the second. There is always going to be some place where expressing rotations with Eulers breaks down like that. You may not even notice a problem when this happens, as the resulting position is the same, but if you're graphing the individual components you'll see points where they go crazy.
Rotations with quaternions are nice and unambiguous.
Ok, so they represent an orientation or rotation delta, and don't have the problems of Euler angles. But what ARE they, under the hood? Well, they are four dimensional vectors of the following form:
[x, y, z, w] (generally, sometimes it's
[w, x, y, z]).
There's more to it than that, but I know right now a bunch of you are thinking something like "Oh, so that's basically like a vector representing Euler angles, plus some
w parameter that I can just figure out later."
Forget that noise. If you try and use a quaternion like that, you're going to have a bad time. x, y and z are actually factors of complex numbers, and w is a scalar. We could spend all day (or several days) explaining how exactly that works, but we're rapidly getting away from the point of this article, which is how you can use a quaternion.
I wouldn't recommend poking around inside a quaternion, but if you do, keep in mind that for it to be a valid rotation it always needs to be normalized (ie, x2 + y2 + z2 + w2 = 1). If you really want to know all the math, I'd recommend starting at Wolfram. But like I said, best to just not mess with the secret sauce.
What can you do with them?
So you told the Myo armband you wanted orientation information and it gave you a quaternion. Now what? What can you do with it?
Firstly, you can turn it into a set of Euler angles (roll, pitch and yaw). Yes, I just said they can be bad, but there are a few reasons you may still want to use them. For example, you might convert to Eulers if you only care about a certain axis of movement, or want to use each axis for a different thing. So you might use yaw to fly a ship left and right, with pitch controlling speed, and roll not doing anything. For that the easiest way is to convert your Quaternion to Euler angles.
You can also use them as an understandable way of expressing orientation (or a rotation) to a user. They aren't going to want to see a raw quaternion.
Most libraries will have a function to do this for you, but if you're stuck our HelloMyo sample gives you a reasonable implementation you can use.
When converting to Euler angles though, it's important to wait until the LAST possible moment. Do as much with quaternions as you can and then convert to Eulers right at the end. Otherwise, you may run into one of the problems we discussed earlier.
Rotating another Quaternion
At some point you are going to want to rotate something. In a game engine like Unity, every transform has an orientation property stored as a quaternion. Outside of a game engine, you may want to store a certain orientation of a user's arm as "centered" or “forward” and calculate any rotations relative to that.
This is really the biggest benefit to quaternions. Rotating smoothly and directly from one set of Euler angles to another is a pain. With quaternions, it's as simple as multiplication. Typically you will take the orientation you have (as a quaternion) and just multiply by the rotation (another quaternion) you want to apply.
This is also probably the most important feature to call out, because your library may not even have a "rotate" function, expecting you to know to just use the normal multiplication operator. That's how to rotate our Quaternion class, and that's how it works in Unity as well.
Note that rotation is not commutative, meaning
A * B is different from
B * A. So for example, yawing and then rolling leaves you pointing in one direction:
Whereas rolling and then yawing leaves you pointing somewhere completely different:
Remember that a quaternion can be both an orientation and a rotation. In
A * B,
A is the base orientation and
B is the rotation you apply to it. If you used
B as a base and applied
A as a rotation, you get something different.
Centring your frame of reference
You may want to calculate all orientations in reference to some "
centre" (ie, if the user were wearing a Myo armband and pointed their arm at the screen, you may want to determine every new orientation in that frame of reference). So, you instruct the user to point their arm at the screen and make a gesture. You take that orientation, invert it, and store it as the “
centre”. Inverting a quaternion is not very hard. Your library will probably have an
invert function, but be on the lookout for
conjugate instead. For a unit quaternion (which is what all valid orientations are) it's the same operation. The Myo SDK only provides conjugate.
Anyway, once you have your inverted centre and you want use it figure out which way the user's arm is pointing, just multiply
Current Rotation by
Centre to cancel out the difference, giving you an orientation in your desired frame of reference.
Rotating a Vector
You can use a quaternion to rotate more than other quaternions. For example, the acceleration
Vector3 you get from the Myo armband's
onAccelerationData is actually in "Myo space" (meaning that the vector's “up” is perpendicular to the pod with the logo). That's not always the most useful, and you may want to determine what the acceleration is in “world space” (ie, up being parallel to gravity and therefore perpendicular to the ground). There will probably be some kind of rotate function like the one we provide, but if not you just need to multiply your quaternion by your vector, and then again by your quaternion's inverse. It's possible your library may have overloaded multiplication again to make
quaternion * vector do the entire rotation though, so just make sure you read the docs.
Those are the big, non-intuitive operations I wanted to explain. If your interest in quaternions begins and ends with what the Myo SDK gives you, that's probably all you need. See you later! If you are using another library or SDK like Unity, there are two weird little functions you may still be wondering about:
Basically, they stand for linear interpoloation and spherical linear interpolation respectively, and it lets you smoothly transition between two rotations (or other things, lerp and slerp are applicable beyond quaternions). The idea is that it lets you say "Here are my start and end rotations, give me a rotation that's X% of the way through", where the percentage would change with time (ie, with every frame).
Lerp is very fast, but slerp will look better for rotations (since they happen on a sphere). If you want more information, check out this great post about lerping like a pro.
As I said, you'll often see these in other libraries, but not the Myo SDK. The reason is that there's no "interpolation" needed, because the orientation you get from the Myo armband is already getting updated at 50Hz based on the user's own arm movements. That should be plenty smooth enough for real time updates. “Real time” is key though, if you are just storing orientation frames to display in slow motion later, yeah, you'll probably need slerp at that point.
There are a few other functions you may see in libraries, like calculating the magnitude of the angle between two quaternions, or converting them to other representations, but they should be pretty clearly labeled and self explanatory. If not, your library sucks.
And that's it! Quaternions aren't really that scary. As I said, just use a library. The Quaternion class used in the Myo SDK comes with pretty much all you really need to use it with our Myo armband, and engines like Unity have pretty comprehensive solutions as well.
So next time you see a quaternion, don't be scared. Be awesome instead!
Good luck, and happy coding!