featured image
Tutorials

Quaternions are Spooky

   - 

I’m going to take a break from particles for a bit. I’ve got a ton more stuff to cover like alpha control, using quads instead of points, and other fun effects, but I need to sidetrack to Quaternions today.

Yes, that’s right. Quaternions. If you have already heard of them you might be cowering under your desk in fear. If you haven’t heard of them, take a look at this wikipedia page “explaining them”. Not very useful is it. Take a break and sit under your desk for a few minutes. It helps.

This article is part of my ongoing series of medium difficulty ThreeJS tutorials. I’ve long wanted something in between the intro “How to draw a cube” and “Let’s fill the screen with shader madness” levels. So here it is.

The short definition is a quaternion is four numbers that can be used to represent a rotation and the avoid gimbal lock Going any deeper into the math won’t actually give us an intuitive sense of how to use them, so I’m going to skip all of the usual diagrams and go straight into why you’d want to use them in the first place.

What we want

Here’s our picture for today. I want to make a little sphere world that I can walk around on Little Prince style. At first it seems simple. I added a sphere, positioned the top just below the camera, and rotate it forward when I press the up arrow. This is easy to do in ThreeJS by modifying the sphere’s rotation.x angle, which rotates around the x axis.

quaternions

The problem comes when I try to turn. For the left and right arrow keys I change the value of the sphere’s rotation.y angle, which rotates around the y axis. This doesn’t do what I expected.

I expected the sphere to rotate around my current Y axis, which is the camera. Instead it rotated around the Y axis of the sphere. The problem is that the sphere’s y axis no longer aligns with mine if I’ve moved forward at all. In short we can’t compose two rotations together in 3D space without weird side effects and transformations. In the worst case we get gimbal lock, meaning it might actually be impossible to turn at certain times.

Introducing Quaternions

So far we have been using Euler angles, which are a rotation in radians around a particular axis (x, y, or z, usually). In 3D engines they avoid this problem by using quaternions. cue scary music.

To use quaternions we don’t need to know how they work. ThreeJS already does all of the tricky math for us. We just need to know they are opaque objects representing a relative rotation and we can use them to modify our vectors.

Let’s get down to some code. For each arrow key I want to apply a rotation relative to the current orientation. Each of these is represented by a radian rotation around an axis, then converted to a quaternion.

const ANGULAR_SPEED = 0.05
const MOVEMENTS = {
    ArrowUp:    new Quaternion().setFromAxisAngle(new Vector3(1,0,0),toRad(ANGULAR_SPEED)),
    ArrowDown:  new Quaternion().setFromAxisAngle(new Vector3(1,0,0),toRad(-ANGULAR_SPEED*6)),
    ArrowLeft:  new Quaternion().setFromAxisAngle(new Vector3(0,1,0),toRad(-ANGULAR_SPEED*6)),
    ArrowRight: new Quaternion().setFromAxisAngle(new Vector3(0,1,0),toRad(ANGULAR_SPEED*6)),
}

These are the same rotations I was talking about earlier, just converted to Quaternions. Now to use these on each keydown we just need this:

on(document,’keydown’,(e)=>{
  if(MOVEMENTS[e.key]) {
     const cur = sphere.quaternion
     const rot = MOVEMENTS[e.key]
     cur.multiplyQuaternions(rot,cur)
   }
})

What?! That’s it?! Yep. We get the current orientation from the sphere, pick the right one for the current keypress, then multiply them to produce the new one. So how does this work?

Analogies are Tricky

Think about it like the 2D version. Imagine we have a line anchored in the middle of a circle like the minute hand of a clock. The line has a current orientation measured in degrees or radians. Suppose it is pointing at 3 o’clock and we want to rotate it to 4 o’clock. That means going from 90 degrees to 120 degrees. So we can define a relative rotation of 30 degrees. To apply this rotation we add it to the current orientation. If we wanted to go the other way we’d add a rotation of -30 degrees. We are combining the current orientation with an extra rotation. These are represented as single values in radians.

The 3D version is the same. We want to combine the current orientation with an extra rotation. The difference is that they are represented by quaternions instead of single values, and that we multiply them instead of adding. Other than that we can think of them as simply angles in 3D instead of 2D.

That’s all you need to know about Quaternions. They represent rotations in 3D space. To create one use one of the util functions on the Quaternion class like setFromAxisAngle(), setFromUnitVectors() or setFromEuler().

Modifying Quaternions

One disadvantage of Quaternions is that we can’t modify them directly. In the 2D version if I want to rotate only half as much I just divide the angle in half. Instead of adding 30 I can add 15. Unfortunately we can’t modify Quaternions directly without complex math. To calculate half of the relative rotation you can use the setFromAxisAngle like I did with the keystroke handlers above (using half the angle), or you can use the static Slerp function which will calculate a new quaternion for you.

Three.Quaternion.Slerp(start,end,target,t)

The start is the starting orientation, end is the ending orientation (the full 30 degrees), target is an empty Quaternion that the result will be copied into, and t is the fraction you want to use (0.5 for half). Slerp is typically used for interpolating a quaternion over time. Say you want your character to rotate from forward to sideways over ten seconds, then you would make a t value that goes from 0 to 1 over ten seconds and call it on every frame to get the current orientation.

So that’s quaternions. Not nearly as scary now, is it.

Spherical Bonus

As part of my sphere demo I wanted to pick random points on the surface of the sphere, then put trees or something at each point. It turns out randomly picking a point on a sphere also involves tricky math to derive, but the actual equation is pretty simple.

If it was a cube you could pick a random X, Y, and Z coordinate and then project it onto the sphere. This won’t be truly random, though, because you’ll end up with a higher concentration of points near the corners of the original cube. Instead we need to use Spherical coordinates.

The algorithm works like this. Pick two random numbers from 0 to 1 called u and v. Use u to create a random angle between 0 and 2 * Pi (0 to 360 in degrees). Call this angle theta. Use v to calculate a random angle between -pi/2 and pi/2 (-90 to 90), much like the latitude lines on a globe. Call this angle phi. These two values, theta and phi, are the spherical coordinates of the point. To ensure the points are evenly distributed we must use arc-cosine. Here’s the code:

function randomSpherePoint(radius){
   //pick numbers between 0 and 1
   var u = Math.random();
   var v = Math.random();
   // create random spherical coordinate
   var theta = 2 * Math.PI * u;
   var phi = Math.acos(2 * v — 1);
   return new Spherical(radius,phi,theta)
}

ThreeJS has a nice Spherical object with handy methods to convert to regular Cartesian coordinates. To cover a sphere of radius 10 with one hundred small points (spheres of 0.1 radius), do this:

for(let i=0; i<100; i++) {
    const building = new Mesh(
        new SphereBufferGeometry(0.1),
        new MeshStandardMaterial({color:’white’})
    )
    const pt = randomSpherePoint(10)
    building.position.setFromSpherical(pt)
    globe.add(building)
}

That’s it! See you next time.

Until Next Time

Remember, if you are working on a cool WebVR experience that you’d like to have showcased right in Firefox Reality, let us know.