Honeycomb Engine: Dev Blog 3 ~ Quaternions for Rotations

Quaternions. The mere mention of these mathematical constructs would’ve sent shivers down my spine a couple of years ago. They’re really weird and complex. However, when you’re making a game engine and you have to model the rotations of objects, you’d be crazy not to use them for several reasons.

The easiest way for people to think about an arbitrary rotation in 3D space is to think of three separate rotations around the x-, y- and z-axes respectively. This way of representing a rotation is commonly known as Euler angles. The advantage is that it’s easy for someone to reason about a rotation represented in Euler angles because they’re so simple – I just rotate around x, then y, then z, it’s that easy. However, this brings a couple of problems with it too; namely, it’s inefficient to do three separate rotations for every single rotation you apply, and Euler angles can run into a problem called Gimbal lock.

In a system where three gimbals each provide an axis of movement around a central point, it is possible to lose one degree of freedom by rotating one of the gimbals 90° such that its axis converges on one of the other gimbals. When this happens, the other two gimbals will rotate along the same axis. The word ‘lock’ is a tad misleading, because you can fix the problem simply by rotating the first gimbal back, but it still leads to weird behaviour sometimes. It’s best explained through an animated diagram.

Quaternions fix both of these issues. Firstly, Euler’s rotation theorem (Yes, Euler got around a lot) tells us that we can represent any rotation or sequence of rotations around a fixed point as one single composite rotation around some axis through that point. As it turns out, we can model rotations around an arbitrary axis as a quaternion!

img_20161109_003123

Rotating around an axis rather than three rotations around x, y and z axes.

To understand how this works, we first need to know what a quaternion is. I’m not going to go into too much detail, because I want to keep this post relevant to how we can use them for rotations. At their most basic level, a quaternion, q, is made up of four real numbers, x, y, z and w, where:

q = w + xi + yj + zk

Here, i, j and k represent versors, or unit quaternions. Don’t worry about what they mean too much, because your head might go nuclear trying to wrap your brain around them. What we want to do is represent x, y, z and w in a way that’s useful to us. Luckily, it’s really simple: take our axis to be a three-dimensional vector (a, b, c) and our angle to be θ. Then our quaternion’s real number parts are:

x = a * sin(θ/2)
y = b * sin(θ/2)
z = c * sin(θ/2)
w = cos(θ/2)

Now we have a quaternion that represents an angle-axis rotation, immune from the problem of gimbal lock. To apply the rotation to an object, we multiply the point we want to rotate by the rotation quaternion. To figure out what the quaternion is doing, we can take x, y, z and w and calculate the angle and axis from them – first of all we can find the angle:

θ = 2 * cos⁻¹(w)

Then, we can use this angle to figure out what the rotational axis – a 3D vector – is:

a = x / sin(θ/2)
b = y / sin(θ/2)
c = z / sin(θ/2)

With this in mind, it’s a little easier to reason about what quaternions are doing. Keep in mind that all these rotations are about the origin, so if you want to rotate around an arbitrary point, you’ll need to translate your object by the inverse position vector of the point, do the rotation, then translate by the position vector of the point to undo the first translation.

rotation-by-translation

This image is about 2D translations, but the general idea still applies.

To make a rotation by compositing two different rotations, we can just multiply their respective quaternion rotations together, paying attention to the order:

new_rotation = second_rotation * first_rotation

The other great thing about quaternions is that we can mix between two rotations really easily, with more believable results that using Euler angles. Whereas we’d use a regular lerp operation (linear interpolation) to move from one rotation vector to another if we were using Euler angles, we can use the more sophisticated slerp (spherical linear interpolation) technique for quaternion-based rotations.

To imagine the difference between these two techniques, imagine you were stood on a completely flat world, whether it be in 2D or 3D, and I asked you to walk from point A to point B on that surface in a straight line at a constant speed – that’s you using a lerp. Now imagine doing the same, but on the surface of a perfectly spherical meteorite instead – of course, walking in a straight line would end up with you inside the meteorite, which is impossible, but if you walk across the surface directly to your destination, that’s what a slerp is doing. Super simple stuff!

on-top-of-the-world

The flag looks like the USB logo, but it’s supposed to be an angle-axis diagram.

That rounds off pretty much all I know about quaternions and how much I could be bothered to look up online. If I got anything wrong, please do tell me because it’s definitely possible. Remember, if quaternions start to hurt your head, which happened plenty of times to me, keep in mind the most important thing: Don’t Worry About It™. You don’t need to know them inside out to be able to use them.

Advertisements

Game Design Tips #16 – [Unity + C#] Linear Interpolation

It’s been an awfully long time since my last post – blame coursework! I have a few weeks to get a couple of posts in before I have more coursework and then exams, so enjoy me while I’m here.

If you’ve coded at all with Unity, you’ve probably noticed its API has a lot of methods called ‘lerp’. Mathf.Lerp(), Vector3.Lerp(), Color.Lerp(); all of these are very helpful methods. But what exactly do they do, and how should we be using them properly? First of all, I’m gonna go ahead and show you how they’re meant to be used, then I’m gonna break them horribly to show you cheap ways of doing cool stuff.

How to use Lerp without being hacky

‘Linear Interpolation’ is a lot of syllables to get your head around, so first of all, let me explain what it means. Let’s say we have a thing. We want the thing to change into another thing at a constant (linear) rate. This is what linear interpolation does – it takes a vector, colour, float, you name it, and changes it into a new one.

tip-16_img01

This is the lerp method for Vector3. We pass in startPos, endPos (both Vector3s) and a float parameter; this parameter should be between 0.0 and 1.0. When time = 0.0, the object’s position will be 100% at startPos and 0% at endPos, and when time = 1.0, it will be placed at 0% startPos and 100% endPos. It’s easiest to demonstrate what lerp is doing when talking about Color.Lerp(), so we can use a pretty diagram:

tip-16_img02

Here, the square represents the RGB colour scale found in Unity’s colour picker (well, flipped horizontally). When we use Color.Lerp() in this way, when the third parameter is 0, the method returns a red colour (#ff0000 in hexadecimal RGB colour representation) and when the parameter is 1, we get full white (#ffffff). At any point in between, we blend between the two linearly – at 0.5, we’ll get a pink-ish colour exactly halfway between red and white on the RGB scale (#ff7f7f roughly) and at 0.75, we get something mostly white but retaining a tiny bit of red (about #ffbfbf).

Once we understand what lerp is doing when we pass in certain numbers, we can pull everything together cleanly into a coroutine.

tip-16_img03

We pass in the sprite renderer whose colour will change, the colour it will change to and how long it will take to change. The coroutine automatically works out all of the needed variables for the user, so we don’t need to pass in the start colour. Every frame, we set the new colour of the renderer based on what percentage the current time is between the start and end times. Simple! The final step is to ensure the colour gets set to the end colour exactly, since the loop might have exited only 99.9% through the lerp (it’s never going to be noticeable to the human eye, but I’m doing this for the sake of completeness and to avoid triggering anyone’s OCD).

How to use Lerp badly

Now that we know how to use lerp properly, it’s time to be a terrible person! I’ll show you one popular way of using lerp badly. Assume, for the following example, that the ‘speed’ variable is set to 0.1f.

tip-16_img04

tip-16_img05

I’ve tried to illustrate what happens on each frame here. At the start (on the first time interval), the sprite colour is at its original colour. At the second time interval (0.1s after starting), we have moved 10% from the original position towards the final position. This is easy to understand – we move 10% of the way between full red and full white (assuming the original colour was red and targetCol is white). On the next interval, we have travelled another 10%. However, the starting point was not the original red – it was 10% towards white. This means the current colour is 10% between the last colour and full white; it is now 19% towards full white. This process continues; rather than moving linearly between red and white, the end result is a curve.

The reason this works is because the time parameter we pass in is always a fraction between 0 and 1, but the start colour is always changing too. It’s not exact, but roughly every 0.1s, the colour will change 10%. The numbers above won’t be precise since the lerp is running every frame, not every 0.1s, but hopefully this gives you an idea of how this blasphemy is working behind the scenes. This isn’t how lerp is intended to be used, but it is a cheap way of moving between vectors, colours or floats on a curve. It’s disgustingly amazing – dismazing, if you will.

It’s also worth noting that if the parameter passed to lerp is set to below 0 or above 1, it will snap up or down to those boundary values. Now you should be a certified lerp expert! I’ve put both versions of the linear interpolation script online for you to download if you’re too lazy to copy them (which is only a problem because I’m too lazy to make them look nice without resorting to taking screenshots. My bad).