beginner rotation/translation problem

Started by
8 comments, last by Thirthe 15 years, 9 months ago
hello all, i have a problem with the most basic stuff and i feel terrible. i am trying to recreate, in my own special way, NeHe's 11th lesson, where i can walk through a simple 3D world. so far i have a large 100*100 plane(on x,z axis') and i can walk forward/backward nicely. if i turn in origin point, the camera rotates nicely and i can walk forward and backward nicely, as well(basic trig). but the problem arises, when i go forward a little and then try to turn. instead that the camera would rotate in-place it warps(translates) to another location. first i tried with loading the identity matrix, rotating the camera to the stored angle and then translating her to the stored position, but that did not work(i suspect my resize() funct. and yes, i did translate first and rotate second.), so i just rotate and translate on-the-fly or step by step. i also tried putting glRotate(), in the key() funct where case 'a' and case 'd' evaluates, between glPushMatrix() and glPopMatrix(), but then nothing happens... why not? anyway, my code:

// NOTE: i used the cur_* vars, when i tried translating/rotation objects from the origin point(identity matrix). they're kinda obsolete atm, but i've left them in anyway.

GLfloat cur_x = 0.f;
const GLfloat cur_y = -1.f;
GLfloat cur_z = 0.f;

GLfloat angle = 90.f;
GLfloat step_x = 0.f;
GLfloat step_z = 0.f;


//
//        GLUT callback Handlers
//

static void idle(void)
{
    glutPostRedisplay();
}

static void key( const unsigned char key, const int x, const int y )
{
    glMatrixMode( GL_PROJECTION );

    switch (key)
    {
        case 27 :
        case 'q':
            deinit();
        break;
        case 'w':
            // move forward
            step_x = cosf( angle * (M_PI/180.f) ) * 1.f;
            step_z = sinf( angle * (M_PI/180.f) ) * 1.f;

            cur_x += step_x;
            cur_z += step_z;

            glTranslatef( step_x, 0.f, step_z );


        break;
        case 's':
            // move backward
            step_x = cosf( angle * (M_PI/180.f) ) * -1.f;
            step_z = sinf( angle * (M_PI/180.f) ) * -1.f;

            cur_x += step_x;
            cur_z += step_z;

            glTranslatef( step_x, 0.f, step_z );


        break;
        case 'd':
            // turn right
            if( (angle += 5.f) > 360.f )
                angle = 0.f;

            glRotatef( 5, 0.f, 1.f, 0.f );


        break;
        case 'a':
            // turn left
            if( (angle -= 5.f) < 0.f )
                angle = 360.f;

            glRotatef( 5, 0.f, -1.f, 0.f );


        break;
    }

    glutPostRedisplay();
}

static void resize( const int width, const int height )
{
    const GLdouble ar = (GLdouble)width / (GLdouble)height;

    glViewport(0, 0, (GLsizei)width, (GLsizei)height);

    glMatrixMode(GL_PROJECTION);

    glLoadIdentity();
    glFrustum( -ar, ar, -1.0, 1.0, 1.0, 169.0 );
    glTranslatef( cur_x, cur_y, cur_z );  // (0, -1, 0)

    // NOTE: i call MODELVIEW in the beginning of the display() funct
}

static void display(void)
{
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glCallList( DL ); // the 100*100 plane

    glutSwapBuffers();
    glFlush();
}

/* Program entry point */
int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitWindowSize(640,480);
    glutInitWindowPosition(10,10);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);

    glutCreateWindow("Walk");

    glutReshapeFunc(resize);
    glutDisplayFunc(display);
    glutKeyboardFunc(key);
    glutIdleFunc(idle);

    init();

    glutMainLoop();

    return EXIT_SUCCESS;
}


also, two side-questions: 1.) do i really need to call glFlush() in this case? 2.) should i call glEnable() and glDisable() when needed OR should i call glEnable() in the beginning of the program and glDisable() when cleaning up? thanks! [Edited by - Thirthe on June 24, 2008 4:41:35 PM]
Advertisement
Before I do this, I admit that 3d math is my weakness (I'm all combinatorics >_<), but I have done that tutorial and I did have that very problem and I did fix it..the trick is remembering how I did it.

It had to do with the order of translation; try playing with the order in which you multiple the matricies together. IIRC the solution liked within there.

I bet someone smarter than me will post a direct solution before you try it though =)
Your camera is moving, then you are rotating around the camera's origin, not the camera's position. You will i expect have the issue if you rotate, move then rotate again..

Effectively what your doing is moving from point A -> B, then rotating to point C from B relative to point A. Which would make an arc from point B -> C with the radius of the distance of A -> B, if you kept going you would probably end up where you started, kinda like circle strafing.

You need to use the position of the camera as the origin for the rotation if you are only wanting to rotate around the camera. So when you move from point A -> B, you rotate your camera relative to point B, then when you move from point B -> C, and rotate your then using C...

I've done it in DirectX but not OpenGL. Originally i had the same problem.

I found its useful to keep this in mind when dealing with matrices and when you want to scale, move and rotate, do it in this order: SCALE * ROTATION * POSITION.

never fails from what i've seen.
Yeah, You want to combine linear transformations in the order: Scale/Rotate/Translate, where the scale and rotation are done while the object you're translating is in local coordinates.

Order of multiplication also is important. You can either append or prepend transformations. By prepending rotations on an identity matrix, you can then combine this matrix with the current transformation to perform transformations in world coordinates. By appending rotations you simply rotate the current transform. This will perform tranformations in the object's coordinate space.

Easiest way to fix your problem is probably to just track position, and rotation values and calculate a new tranformation matrix using the proper order of operations each frame, but you may find the above information useful.
First of all, telling about the order of matrices in a matrix product is senseless as long as nothing was said about using either column or row vectors. OpenGL uses column vectors, while D3D uses row vectors. The mathematical correspondence is given by the transpose operation, denoted by a superscripted T (or also a t). And since
( M1 * M2 )T == M2T * M1T
you can see that the order is reversed if switching from the one to the other kind.

That said, the order suggested by Kalten
SCALE * ROTATION * POSITION
using D3D is to be reversed if used for OpenGL. In the following I'll use column vectors, since the OP deals with OpenGL.

Now, how is such a transformation to be interpreted? E.g. a vertex position v is transformed
v' := T * R * S * v
where T denotes a translation, R a rotation, and S a scaling. It means that the vertex position is scaled (okay, the _space_ containing the position is scaled), and the scaled one is rotated, and the scaled and rotated one is translated.

v is given in a co-ordinate space, often but not necessarily the so-called "model local space". Then the 1st applied transformation (S in the case above) gives us v in another co-ordinate space (a scaled one w.r.t. the original one, obviously). Neither does scaling change the position of the zero vector 0, nor does it change the orientation of the axes (or the basis vectors, to be precise). Both features are good for the rotation by R to be followed, since they mean that the orientation and position of the rotated result are exactly the same if the object wouldn't be scaled. So the scaling does not perturb the rotation if that order is applied.

If, on the other hand, S * R is applied, then the orientation of the object is changed before the scaling is done. Now, scaling changes the size along the principal axes, and those are already changed! That means that, using this order of transformations, the shape will be deformed instead of just transformed!

Similar explanation could be found for the translation and rotation. The translation is independend on the orientation, but the rotation is not independend on the origin. That means that the translation should be "more far away" from the local space than the rotation should.

In summary, this means
T * ( R * ( S * v ) )
is the order with the "best locality" (i.e. the lowest impact of transformations on the other transformations). And, due to a mathematical rule, the parantheses play no role for the result, so that the above is identical to the above definition of v'. (Don't confuse this with commutativity.)

Now, if you track all of the transformations by itself, you can compute the overall transformation just-in-time for each frame. That is what dashurc has suggested at the end of his post. If, on the other hand, you don't do so, then you have to enforce the localities by applying extra transformations. E.g. assume that the combined transformation
M := T * R
is current. If you then want to rotate the object furthur (i.e. you perform a stepwise rotation), then you have to undo the translation first. However, at the very result, you don't want the object being translated as a side-effect of the rotation, so you also have to undo the undo. In summary, what you have to do, is
M' := O * R' * O-1 * M
where O denotes the current origin of the rotation, and R' the additional rotation to be applied.

Normally, O==T would be the case. With that substitution it is easy to check the formula:
M' := T * R' * T-1 * M
== T * R' * T-1 * T * R
== T * R' * R
what exactly shows that the rotations are concatenated without being influenced by the position any more. The same principle can be applied to other transformation spaces as well, of course.


Although being filled up with math, I hope that the above sheds some light onto the basics.
Quote:Original post by haegarr
That said, the order suggested by Kalten
SCALE * ROTATION * POSITION
using D3D is to be reversed if used for OpenGL. In the following I'll use column vectors, since the OP deals with OpenGL.


I didnt realise it was reversed for OpenGL which is why i outlined that i had used D3D, wouldnt have even occured cause all of the matrix algebra that i've done would suggest that it would be done by rows not columns, seems backwards, i spose that its good that i'm using D3D then... haha.
Quote:Original post by Kalten
I didnt realise it was reversed for OpenGL which is why i outlined that i had used D3D, wouldnt have even occured cause all of the matrix algebra that i've done would suggest that it would be done by rows not columns, seems backwards, i spose that its good that i'm using D3D then... haha.

Well, I should have emphasized that "reverse" is meant w.r.t. the order of matrices in the product, as can be seen from the first formula in my post above. It is definitely not meant how the transformations affect the vertices! This is because if you write down the application of transformation as its transpose, you'll get
( M1 * M2 * v )T == vT * M2T * M1T

As you can see, the transformation close to v is in both cases M2! That means that in both cases M2 is applied to v, and M1 is applied to the result then. So, one cannot say that "the transformations are applied in reverse order" or such, since they are effectively applied the same way. It is just a matter of how the math works.
thank you all for help.
the sequence i used was Identity*R*T and it worked.

i still wonder about the performance questions, though:
1.) do i really need to call glFlush() in this case?
2.) should i call glEnable() and glDisable() when needed OR should i call glEnable() in the beginning of the program and glDisable() when cleaning up?
Quote:Original post by Thirthe
i still wonder about the performance questions, though:
1.) do i really need to call glFlush() in this case?

glutSwapBuffers should do the trick. For fun, though, give it a shot and make sure.
Quote:2.) should i call glEnable() and glDisable() when needed OR should i call glEnable() in the beginning of the program and glDisable() when cleaning up?

I'm not really sure what you mean here... glEn/Disable are for using specific capabilities within OpenGL, not OpenGL as a whole. So, you should use them when you need to. If you find yourself needing to use lighting throughout your rendering process, then glEnable it once and leave it alone (no need to glDisable it at the end). If, though, you don't want lighting in a certain part, such as your 2D menu overlay, then glEnable it at the beginning of each frame right before rendering any 3D lit geometry, then glDisable it right before drawing your menu. You'll want to minimize the amount you need to call it with smart ordering of drawing, but definitely use it when you need it. More info here.

Is that what you meant?
This was exactly what I've meant, thanks! :)

This topic is closed to new replies.

Advertisement