gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > [mini-tuto] How to rotate an object around its local axis

#144560 - Noda - Sat Nov 03, 2007 9:17 pm

Hi! After my problem concerning that question had been solved (see http://forum.gbadev.org/viewtopic.php?t=14394), I've been told to write a mini-tuto with the solution, and I thought it was a good idea, so here it is ;)

In my case, the goal was to have a ball rolling on a surface, so I'll take that as an example.

MINI-FAQ:

- Q: Why can't I use glRotate() to simple rotate my object?
- A: glRotate() is based on the world's axis, so when it will work for a single axis rotation, but when you want to rotate an object along mulple axis at the same time, the first rotation affects the other axis causing your rotation to be wrong. That's why you need to use object's local axis to do the rotation.


HOW TO:


There's different methods to do this, but I'll explain only the one I used.

First you need to have a matrix for your object, storing its current rotation.
Let's call it the object matrix, O.
At first you initialize it with its original rotation (identity in the general case).

Code:
    m3x3 objmat;
    // initialize ball rotation
    matLoadIdentity(&matobj);


Then you have to build the rotation matrix, R: it's the one that will be used to rotate your object. The rotation that will be applied here is relative to the current rotation state of the object (stored in O), so you have to use relative angles here.

Here is how it is built:

Code:
    m3x3 matrot;

    int32 cosX = cosf32(xa);
    int32 cosY = cosf32(ya);
    int32 cosZ = cosf32(za);
    int32 sinX = sinf32(xa);
    int32 sinY = sinf32(ya);
    int32 sinZ = sinf32(za);

    matrot.m[0] = mulf32(cosZ, cosY);
    matrot.m[3] = mulf32(cosZ, mulf32(sinY, sinX)) - mulf32(sinZ, cosX);
    matrot.m[6] = mulf32(sinZ, sinX) + mulf32(cosZ, mulf32(sinY, cosX));

    matrot.m[1] = mulf32(sinZ, cosY);
    matrot.m[4] = mulf32(cosZ, cosX) + mulf32(sinZ, mulf32(sinY, sinX));
    matrot.m[7] = mulf32(sinZ, mulf32(sinY, cosX)) - mulf32(cosZ, sinX);

    matrot.m[2] = -sinY;
    matrot.m[5] = mulf32(cosY, sinX);
    matrot.m[8] = mulf32(cosY, cosX);


After that, you want to rotate your object: it's done by multiplying your object matrix by the rotation matrix.

O' = O * R.

Note that the order of the multiplication is important, as with matrix, A*B is diiferent from B*A!

Code:
    m3x3 matnew;
    matMult(&matobj, &matrot, &matnew);


Now we have the new rotation applied for our object, let's save it into our object matrix:

Code:
matCopy(&matobj, &matnew);


Another step that need to be done (but not necessarily every frame), is to re-orthogonalize and re-normalize the matrix. Because we are using here 20.12 fixed point precision (the NDS original format), the matrix gets somewhat distorted due to rounding errors, that's whay you need regulary apply this step to the object matrix.

Code:
matOrthogonalize(&matobj);


Now everything is set up to draw our object:

Code:
    // push the current matrix onto the stack (save state)
    glPushMatrix();

      glTranslate3f32(x, y, z);     // move the object
      glMultMatrix3x3(&matobj);  // rotate the object

      // draw you model here

    glPopMatrix(A);


Then you're done!
Note that the rotation is here applied before the rotation, it's strange because it should have been after but if you put it after the point of rotation will be affected (it should be the opposite). Anyways, that's how it's working for me, it's not really a problem but I had to point it.

Here is now for you the code of the functions I used, I did not detailled them before to simplify the explications.

Code:
// orthogonalize & normalize the given 3x3 matrix
void matOrthogonalize(m3x3 *mat)
{
    int32 *x = &mat->m[0];
    int32 *y = &mat->m[3];
    int32 *z = &mat->m[6];

    // calculate the 2nd axis from the 1st & the 3rd
    crossf32(z, x, y);

    // calculate the 3rd axis from the 1st & the 2nd
    crossf32(x, y, z);

    // normalize the axis
    normalizef32(x);
    normalizef32(y);
    normalizef32(z);
}

// multiply two 3x3 matrix
void matMult(m3x3 *mat1, m3x3 *mat2, m3x3 *mat)
{
    int i, j, k;
   
    for(i=0; i<9; i++)
        mat->m[i] = 0;
   
    for(i=0; i<3; i++)
        for(j=0; j<3; j++)
            for (k=0; k<3; k++)
                mat->m[3*i + j] += mulf32(mat1->m[3*i + k], mat2->m[3*k + j]);
}

// reset the given 3x3 matrix to identity
void matLoadIdentity(m3x3 *mat)
{
    mat->m[0] = 1 << 12;
    mat->m[3] = 0;
    mat->m[6] = 0;

    mat->m[1] = 0;
    mat->m[4] = 1 << 12;
    mat->m[7] = 0;

    mat->m[2] = 0;
    mat->m[5] = 0;
    mat->m[8] = 1 << 12;
}

// copy the second 3x3 matrix into the first one
void matCopy(m3x3 *matdest, m3x3 *matsrc)
{
    int i;
    for(i=0; i<9; i++)
        matdest->m[i] = matsrc->m[i];
}


Hope that will be useful to some, enjoy ;)

Thanks to sajiimori, Rajveer & silent_code for helping me getting the solution of this problem.

#144618 - sajiimori - Sun Nov 04, 2007 8:43 pm

So this uses Euler angles -- pitch, yaw, roll, which you called xa, ya, and za -- to rotate an object along its local axes? If so, it's important to note the order in which the rotations are applied, for the same reasons that rotation order was important when using world axes: earlier rotations affect later ones.

You can remove a normalize call from matOrthogonalize. I posted an example in the original thread.

Definitely grab a faster matrix multiply from somewhere. You can also remove the extra copy by multiplying straight into the final destination, if the multiply function is designed to read once and write once.

#144629 - sajiimori - Sun Nov 04, 2007 11:44 pm

Here's a thread with some information about reversing the order of transformations on GL-like platforms:
http://www.gamedev.net/community/forums/topic.asp?topic_id=469955&whichpage=1&#3089272

Honestly, I've never really understood it -- I've been skating by with rules of thumb.

Edit: I just found this explanation, which helped me a lot!
http://glprogramming.com/red/chapter03.html#name2

#144678 - Noda - Mon Nov 05, 2007 6:08 pm

Nice find about the order of matrix multiplications ;)

Concerning the code, I know it's not optimized, but it was designed to be easy to understand & functional ;) (I could also have used the hardware matrix unit to make the multiplication)

By the way, could it be possible to have a pinned post with a link to all tutorials posted on Gbaddev? that could be nice and useful to avoid wasting time with the search function..

#144693 - sajiimori - Mon Nov 05, 2007 8:57 pm

On the topic of understandability: I'm still not sure what the code does. Are you using Euler angles to rotate an object along its local axes, and if so, in what order are the angles applied? (XYZ, ZXY, etc)

#144732 - Noda - Tue Nov 06, 2007 3:53 am

The code I've posted takes angles in degrees and rotates the axis in this order: Z -> Y -> X.
I know this leads to the same problem as glRotate, but the fact is that it's not noticable, as the object matrix is then updated ;)

#145038 - Moby Disk - Sat Nov 10, 2007 8:48 pm

It looks to me like doing the matrix multiplications yourself would result in the same exact problem. In the past, I've used quaternions instead of euler angles to solve this problem.

Matrix and Quaternion FAQ:
http://www.j3d.org/matrix_faq/matrfaq_latest.html

I made an implementation of quaternions for the PC years ago. At some point I'll be doing 3D on the DS and I'll want to port it.
http://www.mobydisk.com/softdev/software/geogl/index.html

When you all do matrix operations on the DS, are you using fixed-point, or do you use floating point and then convert to fixed?

#145073 - Noda - Sun Nov 11, 2007 4:50 am

The fact is that it's working using this simple method ;-)

And for sur the matrix operation are done in fixed point.