while ( 1 ) { ...read some angular velocities from a joystick or something ; ...add the angular velocities to the current angles ; ...convert current angles into a matrix ; ...invert the matrix ; ...put it onto the GL_MODELVIEW stack ; ...draw the scene ; }This doesn't work - or at least, it

Imagine an aeroplane. The three rotations are called 'Heading', 'Pitch' and 'Roll'. Heading is sometimes called 'Yaw' - but I hate that choice since the abbreviation is 'Y' and that gets confused with the Y axis - so I'll stick with 'Heading'.

- Heading change is turning to the left or right.
- Pitch change is moving the nose down and the tail up (or vice-versa).
- Roll change is moving one wingtip up and the other down.

(When you use three angles to represent a rotation, this is often referred to as 'Euler Angle Representation' or just 'Eulers' for short. I usually write an euler as (H,P,R).)

In fact, the problem is that when you compose these rotations, you MUST do so in some particular order - you can choose what that order is - but your mathematics will always force you to make a choice. That is to say, you might choose to perform Roll first, then Pitch and then Heading. Let's look at what can happen with this sequence of operations - and to keep it simple, let's consider just roll and pitch:

(If you are right-handed, you'll find this discussion much easier to follow
if you are holding a paper plane. Fold one **now**! Left handed
people usually have better spatial reasoning skills and can grok this using
their brains alone :-)

- The plane (with no rotations) is straight and level and pointing North.
- Let's give it a Heading/Pitch/Roll of (0,90,90) and see what happens:
- The plane now has 90 degrees of roll (so the right wingtip points at the sky, and the left points at the ground).
- ...and 90 degrees of pitch (which means that the nose pitched UP)...so the nose points West and the tail is off to the East, the right wingtip still points at the sky, and the left still points at the ground.
- Now, the pilot attempts to roll the plane back to level by pushing his joystick to the right - which with the code above will progressively subtract roll angle until it's zero again. You might expect this to result in the nose pointing to the West still - but now flying nice and level...
- So at the end of this manouver, the HPR angle went from (0,90,90) to (0,90,0) - where is the plane actually pointing?
- Let's go back to zero rotations (straight and level - pointing north) and see where (0,90,0) puts us:
- Well, zero roll puts the wings nice and level - left wing to the west and right wing to the east...and 90 pitch points the nose up to the sky!

This class of effect is often termed 'gimbal lock' - after an effect that happens with real-world mechanisms that have three single-axis rotational joints. When two of those joints end up parallel to each other, you have lost one degree of freedom in the system. I have a car spark-plug wrench that is flexible enough to reach into tight corners - but which relies on this effect to allow you to actually turn the spark plug.

Add in the third rotation angle and matters can get seriously out of hand.

We need to understand what went wrong. The problem is that the **initial**
roll happened BEFORE the pitch, but the 'unroll' at the end is something that you
hoped would operate on the **results** of the first roll+pitch - not
simply an undoing of the initial roll which is what the naive code at the top of
this document actually does.

Well, it's tempting to modify our main loop as follows:

...convert initial position into a 'position' matrix ; while ( 1 ) { ...read some angular velocities from a joystick or something ; ...convert angular velocities into a 'velocity' matrix ; ...post-multiply the position matrix by the velocity matrix ...invert the position matrix ; ...put the result onto the GL_MODELVIEW stack ; ...draw the scene ; }...this certainly

Annoyingly though, there is a practical problem with this approach and that is that there is roundoff error in the system.

When you store rotation as Euler angles, there can be tiny amounts of roundoff error - if you change heading by 0.1 degrees for 3600 frames, the resulting net heading won't quite be zero. However, to a player steering a character with the joystick, this error isn't noticable - and is naturally corrected as a result of human input.

Unfortunately, a 3x3 or 4x4 matrix can represent a lot more operations than just rotation. By choosing the right set of numbers, a matrix can represent a shear or a stretch or something. What happens when you repeatedly multiply rotation matrices is that the matrix gradually starts to shear and stretch - after tens of minutes of action, you'll start to see the scene distort visibly. Since the user has no control over this, he has no way to unconciously correct for it - and we are screwed.

There are mechanisms to 'renormalize' a matrix. If you read my companion document "Matrices can be your Friends" then you'll realise that you can normalize the three rows of the matrix and jiggle them such that they are mutually perpendicular - and you'll have a 'pure' rotation matrix once more. Do that operation ever frame - or perhaps every few frames and nobody will notice any sudden 'correction' of the matrix.

That works acceptably well - but I observe that many people find it hard to work with a system in which the current rotation is known only as a matrix. If you wanted to implement a compass for example - it would be nice to know the players heading. That information isn't 'obviously' available in the matrix. Hence, many people conver the matrix back into euler angles:

...convert initial position into a 'position' matrix ; while ( 1 ) { ...read some angular velocities from a joystick or something ; ...convert angular velocities into a 'velocity' matrix ; ...post-multiply the position matrix by the velocity matrix ; ...renormalize the current position matrix ; ...convert the current position matrix into Euler angles ; ...invert the position matrix ; ...put the result onto the GL_MODELVIEW stack ; ...draw the scene ; }If you are going to do that - then you can do this instead:

while ( 1 ) { ...read some angular velocities from a joystick or something ; ...convert angular velocities into a 'velocity' matrix ; ...convert current position Eulers into a 'position' matrix ; ...post-multiply the position matrix by the velocity matrix ; ...convert the current position matrix back into Euler angles ; ...invert the position matrix ; ...put the result onto the GL_MODELVIEW stack ; ...draw the scene ; }This saves you the messy matrix renormalization step since the matrix is regenerated from angles each frame. However, care has to be taken since the step of converting a matrix back into Euler angles results in some 'unknown' values at some orientations. If the eyepoint it pitched by 90 degrees - then roll and heading do the same thing. Hence, although roll+heading==constant, the split between the two is ill-defined and could change dramatically from one frame to the next. If you use heading alone to generate (say) a compass reading then expect it to go crazy at certain pitch or roll angles. That happens in the real world too - so don't feel too bad about it!

This is the same idea as the four parameters of the glRotate command (although the four numbers in a Quaternion are scaled differently for reasons of mathematical convenience).

I don't want to go into quaternions in any detail here, but they are pretty cool since (like matrices) they encode rotation in an order-independent manner - yet (like Euler angles) can ONLY encode rotation - and are hence much less susceptable to ugly roundoff error issues (although you do have to ensure the length of the vector part stays nailed at 1.0 at all times - that's a much simpler idea than renormalizing a matrix.