Orientation Matrix - Basics
By: Social, Last Updated: 12/28/2007, Used: Blender 2.45

PROLOGUE:

As most of us know, "herman.tulleken" already wrote some (very detailed/elaborate) documentation on the BGE's orientation matrix. His tutorial can be found here: Link

While I and everyone in the community hold nothing but the utmost respect for herman; I think most of us would agree that his tutorial is somewhat "inaccessible" to the general user population.

My overall impression is this: There's a need for a more "down to earth" tutorial, with a focus on the very basics of how the matrix works, and how one can use it to apply some rotation to an object in the gamespace.

The following is my attempt to satisfy that (perceived?) need.

ENTER THE MATRIX:

 [1,    0,    0] x[0,    1,    0] y[0,    0,    1] z  X     Y     Z

I want you think of it like this: Each column (indicated by upper-case characters above) represents a point in *local* 3D space *TO WHICH AN AXIS IS POINTING TO*, and each row (indicated by lower-case characters) represents the x,y and z coordinates of that point.

So, we rotate an object by feeding it's orientation matrix new coordinates. Those coordinates describe the points to which our axes will *point to* (The axes point in a new direction; the object rotates).

Now, the example matrix I used above is somewhat special, because this matrix is known as the "identity matrix". You can look for an official textbook definition if you want, but for now I think it would be more beneficial if we just define "identity" as a state in which the coordinates in our matrix describe points that (when our axes point to them) align our objects' axes to be in parallel with the global axes.

TEST THE BASE CONCEPT:

If our assumptions as to how the matrix works are "in the ballpark", then doing something like rotating our object around the Z axis, 90 degrees *counter-clockwise*, would require nothing more than shifting a few 1's in the matrix. To be more specific, our new matrix in that case would look like this:

 [0,   -1,    0] [1,    0,    0][0,    0,    1]

Open the Test_suite.blend. Note that the script "Test_1" is simply there to apply the above rotation matrix, via setOrientation, to the object we see in the 3D window.

Start the game with [P], and then press space to execute the rotation. As you can see:

X axis points to (0, 1, 0) <- where Y axis previously pointed
Y axis points to (-1, 0, 0) <- in the negative x direction, so it's a -1
Z axis points to (0, 0, 1) <- no change, because that's the axis we rotate around

This means that our predictions were correct, and we just executed a successful 90 degree rotation.

As an independent exercise, try to modify the matrix in the "Test_1" script, to apply an identical 90 degree rotation around the Z axis, only this time in the *clockwise* direction.
^
If you can't do that, you should re-read this tutorial from the beginning. Otherwise, feel free to continue on to the next section.

DEFORMATION:

Well, 90 degree turns seem easy enough, but what about 45 degree turns? If you are even remotely "forward thinking" than in all likelihood you already pieced together a logical matrix of your own, which could implement such a rotation.

Again, let's go for the familiar *counter-clockwise* rotation around the Z axis. A 45 degree turn in that case could look something like this:

 [1,   -1,    0][1,    1,    0][0,    0,    1]

Let's test that. Modify the rotation matrix in the "Test_1" script, to reflect the one above, and give it a try. (Same procedure as before)

Assuming you followed instructions, after running the simulation and pressing space, you should witness the exact rotation we expected to get....only with one minor "side-effect": Our object has been scaled up by a very noticeable factor.

We got this deformation because our "point to" points for our axes are not on the arc of the *base circle* (I want you to define the "base circle" as just an ordinary circle with a radius of exactly 1). You see, the matrix also affects the general mesh form.

To see a helpful illustration of that transformation, in the test suite, switch to a scene named "Circle". If not already selected, select the circle in the 3D window, and enter edit mode [TAB].

Think of it this way: Each object starts out with axes of standard length - that length being 1. Now, the very tip top *end point* of those axes is represented by the points in our matrix columns. See what's happening? Every time we feed coordinates into the matrix which plot that *end point* outside of the base circle (and that's what we did in the above matrix) - we make that axis longer, and the BGE translates that as an instruction to increase the size of the object along that axis. Placing the end point inside the base circle would produce an inverse effect; the object would get smaller along that axis.

The main point here is: In order to rotate your object and keep it's form intact, your matrix column representing the axis in question, *must* describe a point that resides *on* the base circle at any rotation.

To make that happen, we need to implement a bit of math.

TRIGONOMETRY:

The public school system (in the USA at least) is a travesty. I know it, you know it, and it sucks. We can either cry about that, or we can dropkick that particular "brain trust" in the face, and continue to learn on our own -> using teh interwebs (aka "a series of pipes").

Heh, don't worry, the only trigonometry functions that we'll really need in order to achieve our goals here are the "sine" and "cosine" functions. More specifically, you need to know what the python math module functions "sin()" and "cos()" take in as arguments, and return as results:

As arguments, both of these functions will accept an angle measure. This measure *should be in RADIANS*, not degrees. One radian is equal to "180/pi" (around 57.2958) degrees, and it's a standard unit of measure for angles in the higher math/science communities. Typing in "radian" into google will provide additional details for the more curious. For everyone else; just knowing how to convert degrees to radians (angle_radians = angle_degrees/(180/pi)) should be sufficient at this time.

As results, "sin(angle_radians)" returns a *y* coordinate on the base circle, and "cos(angle_radians)" returns an *x* coordinate on the base circle - both of which, when put together, describe the correct *end point* for our axis in question. This in turn keeps our rotating object "deformation free".

So, the correct version of our previously imperfect matrix would be:

 *Taking in account that "ar = 45/(180/pi)"[cos(ar), -sin(ar),   0][sin(ar) ,   cos(ar), 0][0        , 0          ,  1]

Let's test this. In the test suite (I assume you're back in the "Main" scene), have the text editor show the script "Test_2". Note the differences between the code in "Test_1" and "Test_2". If you have a solid enough grasp of everything so far, modify the python controller so that the script being used is "Test_2", and then execute the same test procedure as in our prior tests.

If everything went well, you should see a "deformation free", 45 degree, counter-clockwise rotation around the Z axis.

3 AXES, ONE MATRIX, A PROBLEM:

Let's re-examine the general protocol for rotating around any one axis:

 Around the Z axis (what we have been doing so far):[cos(ar), -sin(ar), 0][sin(ar),  cos(ar), 0][ 0      ,  0         , 1]Around the Y axis:[cos(ar) ,0,  sin(ar)][0         ,1,          0][-sin(ar), 0, cos(ar)]Around the X axis:[1,          0,          0][0,  cos(ar), -sin(ar)][0,   sin(ar),  cos(ar)]

Make sure you clearly understand why the above matrix arrangements produce their respective rotations before reading further. Draw circles and points on paper if you have to, but make sure you have the above material "down pat".

By now, you probably noticed that there is a certain limitation as to how we can rotate our object with the above methods. We can only rotate on one axis at a time, because applying a rotation matrix for one axis destroys any matrix data we might have had from some previous rotation on a different axis. In other words: Only the last applied rotation is expressed, and there seems to be no way to retain other rotations before the last one, because in the end everything has to be fed to a single rotation matrix.

So how do we make combined rotations in this case?

Well, yet again it's an issue that requires a bit of new math to plow through.

MATRIX MULTIPLICATION:

It's not nearly as difficult as it sounds. It looks intimidating, sure, but the process is actually very straight-forward:

 Matrix X             Matrix Y                                                                Matrix R[a1, a2, a3]      [b1, b2, b3]      [a1*b1 + a2*b4 + a3*b7, a1*b2 + a2*b5 + a3*b8, a1*b3 + a2*b6 + a3*b9][a4, a5, a6]  *   [b4, b5, b6]  =  [a4*b1 + a5*b4 + a6*b7, a4*b2 + a5*b5 + a6*b8, a4*b3 + a5*b6 + a6*b9][a7, a8, a9]      [b7, b8, b9]      [a7*b1 + a8*b4 + a9*b7, a7*b2 + a8*b5 + a9*b8, a7*b3 + a8*b6 + a9*b9]

Imagine "Matrix X" to hold data representing rotations around the X axis, and imagine "Matrix Y" holds data representing rotations around the Y axis; Then "Matrix R" consists of information that describes both X and Y axis rotations at the same time, and correctly so.

In order to combine two rotation matrices into one, we simply multiply them. That is our solution.

All we need to do now is write a function that will correctly multiply two matrices together, and use that to produce a single "master" matrix - which we could then feed to "setOrientation()".

Just as there are "many ways to skin a cat", there are also many ways to write this function. I did it like this:

 def matMult(matA, matB):    matC = [[0, 0, 0],                 [0, 0, 0],                 [0, 0, 0]]    for i in range(3):        for j in range(3):            a = matA[ i ][0] * matB[0][ j ]            b = matA[ i ][1] * matB[1][ j ]            a = matA[ i ][2] * matB[2][ j ]            matC[ i ][ j ] = a + b + c    return matC

To see it all in action, let's run another test: In the test suite, have the text editor show the script named "Test_3". Note the differences between the code in "Test_2" and "Test_3". Specifically: note the fact that there are now three different angle variables, and three different matrices.

As things are initially set up in the "Test_3" script, we are applying a 45 degree rotation on both the X and Z axes (we can apply rotations to all three axes at the same time, of course, but for the sake of clarity we'll stick to just 2 this time around) - all axes are taken in account to produce the final matrix.

Modify the python controller so that the script being used is "Test_3", and then execute the same procedure as in our prior tests.

The result: Our object is rotated in such a way so that it seemingly forms the shape of the letter Y.

WHO'S ON FIRST?:

Still using the "Test_3" set up, assign 90 to both "adx" and "adz" variables (they were 45 in the previous test), but *before* you run the test, try to predict how our object will rotate in this new case. Considering these are 90 degree rotations, making a prediction should be relatively easy.

Now, run the test.

If you arrived at your prediction by applying the rotation around the X axis first, and then rotating around the Z axis; you are now viewing the expected image.

If you arrived at your prediction by applying the rotation around the Z axis first, and then rotating around the X axis second; you're probably asking "Why did this happen?".

Either way, you should should know the following:

*The order in which each axis is rotated matters, because a different order can produce a different rotation, even when the angles themselves are left unchanged.* Also, the order of matrix multiplication matters -> because it is that order that determines the order in which each axis is rotated.

So, in script "Test_3", "matMult(matMult(x_mtx, y_mtx), z_mtx)" implies that X axis is rotated first, Y second, and Z last. Keep that in mind, and there should be no surprises (at least when it comes to what the final rotation should look like with any given set of angles).

EPILOGUE:

So there, now you know the basics of how to rotate an object using the orientation matrix - or at least you should. If not, I recommend re-reading this tutorial *carefully* and making sure you followed all the instructions pertaining to the tests.

If you feel like you grasped a good part of it, but find a few concepts still somewhat "fuzzy"; I recommend using the "test suite" I provided here, and *running your own experiments*.

The importance of self initiated testing cannot be overstated.

Don't be afraid to break stuff.

Social