Matrices can be your Friends.
By Steve Baker
Take an OpenGL matrix:
float m [ 16 ] ;
m[0] m[4] m[ 8] m[12] m[1] m[5] m[ 9] m[13] m[2] m[6] m[10] m[14] m[3] m[7] m[11] m[15]
...but we are OpenGL programmers - not mathematicians - right?! The reason OpenGL arrays are laid out in what some people would consider to be the opposite direction to mathematical convention is somewhat lost in the mists of time. However, it turns out to be a happy accident as we will see later.
If you are dealing with a matrix which only deals with rigid bodies (ie no scale, shear, squash, etc) then the last row (array elements 3,7,11 and 15) are always 0,0,0 and 1 respectively and so long as they always maintain those values, we can safely forget about them for now.
The first three elements of the rightmost column of the matrix is just the overall translation. If you imagine some kind of neat little compact object (like a teapot), then array elements 12,13 and 14 tell you where it is in the world. It doesn't matter what combinations of rotations and translations it took to produce the matrix, the rightmost column tells you where the object basically is. It is often fortunate that the OpenGL matrix array is laid out the way it is because it results in those three elements being consecutive in memory.
OK, so now we are down to only nine random-looking numbers. These are the top three elements of each of the first three columns - and collectively they represent the rotation of the object.
The easy way to decode those numbers is to imagine what happens to four points near to the origin after they are transformed by the matrix:
(0,1,0) | /(0,0,1) | / |/___(1,0,0) (0,0,0)
After the matrix has transformed this cube, where does it end up?
Well, if we neglect the translation part (the bottom row), then the pure rotation part simply describes the new location of the points on the cube:
(1,0,0) ---> ( m[0], m[1], m[2] ) (0,1,0) ---> ( m[4], m[5], m[6] ) (0,0,1) ---> ( m[8], m[9], m[10]) (0,0,0) ---> ( 0, 0, 0 )
(1,0,0) ---> ( m[0], m[1], m[2] ) + ( m[12], m[13], m[14] ) (0,1,0) ---> ( m[4], m[5], m[6] ) + ( m[12], m[13], m[14] ) (0,0,1) ---> ( m[8], m[9], m[10]) + ( m[12], m[13], m[14] ) (0,0,0) ---> ( 0, 0, 0 ) + ( m[12], m[13], m[14] )
Just imagine a little cube at the origin - pretend it's firmly attached to your model. Think about where the cube ends up as the model moves - write down where it's vertices would end up and there is your matrix.
So, if I gave you this matrix:
0.707, -0.707, 0, 10 0.707, 0.707, 0, 10 0 , 0 , 1, 0 0 , 0 , 0, 1
With practice, you can figure out what that last row of numbers does to the little cube too.
So, would you like to know how to use a matrix to squash, stretch, shear, etc? Just think about where the axes of that little cube end up - write them down and you are done. What does a cube of jello look like when there is a strong wind blowing from X=-infinity?
1, 0.3, 0, 0 0, 0.9, 0, 0 0, 0 , 1, 0 0, 0 , 0, 1
Suppose your cartoon character is going to jump vertically, and you want to do a bit of pre-squash before the jump... and post-stretch during the jump. Just gradually vary the matrix from:
1 , 0 , 0, 0 1 , 0 , 0, 0 0 , 0.8, 0, 0 0 , 1.2, 0, 0 0 , 0 , 1, 0 ===> 0 , 0 , 1, 0 0 , 0 , 0, 1 0 , 0 , 0, 1
1.2, 0 , 0 , 0 0.9,0 , 0 , 0 0 , 0.8, 0 , 0 0 ,1.2, 0 , 0 0 , 0 , 1.2, 0 ===> 0 ,0 ,0.9, 0 0 , 0 , 0 , 1 0 ,0 , 0 , 1
Not only is it easier to think transforms out this way, but it's invariably more efficient too. By seeing the entire transformation as one whole operation on a unit cube, you save a long sequence of glRotate/glTranslate/glScale commands - which each imply a complicated set of multiply/add steps to concatenate the new transform with whatever is on the top of the stack.
Finally, there is one matrix that we all need to know - the "Identity" matrix:
1, 0, 0, 0 0, 1, 0, 0 0, 0, 1, 0 0, 0, 0, 1
Matrices are really easy - it's just a matter of looking at them pictorially.