Archived

This topic is now archived and is closed to further replies.

Douglas

user-controlled quad rotation

Recommended Posts

Douglas    122
I began to code a tiny application which purpose it is to let the user control in every moment of the execution about which axes the quad is to be rotated. For example, if key ''z'' is pressed, the quad is to be rotated about its z-axis. However, I tried to code this functionality in several ways and had several bugs each time and don''t want to explain the whole story of failure up to now. Hence, I''d be rather thankful if anyone could give me a snippet of code which successfully does what I am trying to realize.

Share this post


Link to post
Share on other sites
Sander    1332
If you don''t want to explain the story, it''s pretty hard for us to help you. Basically how you can do it is to create an x, y and z variable that you increment each time you press one of the keys. Then in the renderloop have something like:

glIdentity();
glRotatef(x, 1.0f, 0.0f, 0.0f);
glRotatef(y, 0.0f, 1.0f, 0.0f);
glRotatef(z, 0.0f, 0.0f, 1.0f);
DrawQuad();

Sander Maréchal
[Lone Wolves Game Development][RoboBlast][Articles][GD Emporium][Webdesign][E-mail]

Share this post


Link to post
Share on other sites
Douglas    122
Ok, here is the full story (as far as I can remember it).
I started with something like NeHe''s rotation tutorial:


glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View
glTranslatef(-1.5f,0.0f,-6.0f); // Move Into The Screen And Left

glRotatef(rtri,0.0f,1.0f,0.0f); // Rotate The Triangle On The Y axis ( NEW )

glBegin(GL_TRIANGLES); // Start Drawing A Triangle
glVertex3f( 0.0f, 1.0f, 0.0f); // First Point Of The Triangle
glVertex3f(-1.0f,-1.0f, 0.0f); // Second Point Of The Triangle
glVertex3f( 1.0f,-1.0f, 0.0f); // Third Point Of The Triangle
glEnd();




But since I wanted the rotation to be user-controlled, I made the elements
of the rotation vector in glRotatef variable:

glRotatef(rtri, vector.x, vector.y, vector.z);

where their value were either 1, -1 or 0 depending on the gamepad input.
Starting the changed code puzzled me since the triangle (if not rotated)
moved a long way into the screen and came back to its initial position
(like a spinning jojo if you''re watching the ground).
But I figured out that this was a mathematical / logical problem:
If no button is pressed then all elements of the vector defining the axis
to rotate the object about are zero and the axis is just a point which leaves
infinity possibilities to rotate.




Therefore, I distinguished between a standard- and a non-standard-scenaria:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View
glTranslatef(-1.5f,0.0f,-6.0f); // Move Into The Screen And Left

if(vector.x != 0 || vector.y != 0 || vector.z != 0)
{
++rtri;
glRotatef(rtri, vector.x, vector.y, vector.z);
}
else
{
// no rtri increase because no rotation is requested
}

glBegin(GL_TRIANGLES); // Start Drawing A Triangle
glVertex3f( 0.0f, 1.0f, 0.0f); // First Point Of The Triangle
glVertex3f(-1.0f,-1.0f, 0.0f); // Second Point Of The Triangle
glVertex3f( 1.0f,-1.0f, 0.0f); // Third Point Of The Triangle
glEnd();

This code fixes the "bouncing into the depth" bug described above.
The rotation is performed absolutely properly as long as the button
state doesn''t change. That means that if you press a button which wasn''t
pressed directly before or you release a button which was pressed before,
then the triangles abruptly jumps into another position (I mean angular
and not (x,y,z)). If all buttons are released, the triangle suddenly has
the position it had at the execution start.




In order to fix it, I did something like this:

else
{
// no rtri increase because no rotation is requested
glRotatef(rtri, oldVector.x, oldVector.y, oldVector.z)
}

The results:
If all buttons are released, then the triangle doensn''t jump back to its initial
position but nevertheless a jump occurs every time the button state is changed.
I got the idea, that it is caused by a twofold logical error:
1. The "steadily increasing the angle [by one degree]" only works if you rotate in
one direction all the time but not if the direction changed
2. The rotation as done above doesn''t consider the position in which the triangle
is at the moment
That''s way I replaced the rtri with a "dynamic" value, the length of the "totalRotationVector"
(where the totalRotationVector is the sum of all rotation vectors) and the vector itself as well:

glRotatef(totalVector.length, totalVector.x, totalvector.y, totalvector.z);

But this is scrap, too.
I''m tired of writing and hope you see my problem.

Share this post


Link to post
Share on other sites
Sander    1332
It looks like your problems are caused by the way you feed the rotations to OpenGL. You try to use only one glRotate() function. You might want to try using 3 functions in a row (see my previous post). The glRotate function has the first variable as a rotation in degrees and the onther 3 vaiables as a vector that describes the axis of rotation. This vector should be normalized (hence your code messes up when they''re all set to 0.0f or mutliple one''s are 1.0f).

If you really want to use only one call to glRotatef() (because you want to avoid gimbal lock or something similar) you should search for axis-angle rotations and/or quaternions. Take a look at the Math & Physics FAQ which linkes to the Matrix and quaternion FAQ. More info about quaternions can also be found at Darwin 3D''s quaternion articles (1998).

Sander Maréchal
[Lone Wolves Game Development][RoboBlast][Articles][GD Emporium][Webdesign][E-mail]

Share this post


Link to post
Share on other sites
Douglas    122
a) What is a "gimbal lock"?
b) What do you think will happen if you (try) to normalize a zero vector? It will lead to an undefined result, won't it?
c) After reading your last post I reconsidered my code and wrote this. The triangle performs no more unwanted jumps but it is somewhat unexact: The axes the triangle rotates about are no identical with the axes it should rotate about:



// Clear the color and depth buffers.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// reset position to origin of coordinate system

glLoadIdentity();

// move 5 units into the screen in order to have a proper
// viewing distance to the object

glTranslatef(posVector.x, posVector.y, posVector.z);

//--------------------
// move back and forth
//--------------------

if(SDL_JoystickGetButton(joystick, 0))
posVector.z += 0.03f;
if(SDL_JoystickGetButton(joystick, 1 ))
posVector.z -= 0.03f;

//----------------------------------
// move horizontally and vertically
//----------------------------------

if(SDL_JoystickGetAxis(joystick, 0) < -30000)
posVector.x -= 0.03f;
if(SDL_JoystickGetAxis(joystick, 0) > 30000)
posVector.x += 0.03f;
if(SDL_JoystickGetAxis(joystick, 1) > 30000)
posVector.y -= 0.03f;
if(SDL_JoystickGetAxis(joystick, 1) < -30000)
posVector.y += 0.03f;

//---------------------------------
// limit execution speed to 100 fps
//---------------------------------

SDL_Delay(10);

//---------------------
// rotate around x axis
//---------------------

if(SDL_JoystickGetButton(joystick, 6))
++angleVector.x;
else if(SDL_JoystickGetButton(joystick, 7))
--angleVector.x;

//---------------------
// rotate around y axis
//---------------------

if(SDL_JoystickGetButton(joystick, 8))
++angleVector.y;
else if(SDL_JoystickGetButton(joystick, 9))
--angleVector.y;

//---------------------
// rotate around z axis
//---------------------

if(SDL_JoystickGetButton(joystick, 10))
++angleVector.z;
else if(SDL_JoystickGetButton(joystick, 11))
--angleVector.z;

//--------------------
// core
//--------------------

glRotatef(angleVector.x, 1, 0, 0);
glRotatef(angleVector.y, 0, 1, 0);
glRotatef(angleVector.z, 0, 0, 1);

drawTriangle();

[edited by - Douglas on September 1, 2003 1:14:53 PM]

[edited by - Douglas on September 1, 2003 2:10:29 PM]

[edited by - Douglas on September 1, 2003 2:11:57 PM]

[edited by - Douglas on September 1, 2003 2:12:35 PM]

Share this post


Link to post
Share on other sites
Sander    1332
A) Gimbal lock can occur if you rotate more than 90 degrees over multiple axis. In essence, the axes get screwed up and the computer can''t tell top from bottom anymore. Gimbal lock accidentally ''locks'' one of your rotation axis because it coïncides with another axis (say: X-axis falls on top of Y-axis). The result: You loose one axis of rotation (rotating over X and Y gives the same result since it''s the same axis now!). The best way around gimbal lock is using matrices (whickh can be slow and unstable) or quaternions (which are 4 dimensional rotations).

B) The results are eighter undefined (floating point rounding errors) or you crash your app as you try to divide by zero.

C) That''s probabely caused because you rotate and translate in the wrong order. Change your DrawTriangle routine so that it draws a triangle around the origin. Your orininal one looks reasonable enough:

glBegin(GL_TRIANGLES); // Start Drawing A Triangle
glVertex3f( 0.0f, 1.0f, 0.0f); // First Point Of The Triangle
glVertex3f(-1.0f,-1.0f, 0.0f); // Second Point Of The Triangle
glVertex3f( 1.0f,-1.0f, 0.0f); // Third Point Of The Triangle
glEnd();

Next, change your drawing routine. First translate, then rotate. So:

glTranslatef(posVector.x, posVector.y, posVector.z);
glRotatef(angleVector.x, 1, 0, 0);
glRotatef(angleVector.y, 0, 1, 0);
glRotatef(angleVector.z, 0, 0, 1);
DrawTriangle();

That should fix it.



Sander Maréchal
[Lone Wolves Game Development][RoboBlast][Articles][GD Emporium][Webdesign][E-mail]

Share this post


Link to post
Share on other sites
Douglas    122
I did what you suggested me, but without success.
Here's the whole function (and please don't tell me again that I should move glTranslatef since it really does come first in my code):


void drawScreen(void)
{
static skpVector3D posVector = { 0, 0, -5};
static skpVector3D angleVector = { 0, 0, 0 };

// draw backsides as well

glDisable(GL_CULL_FACE);

// We don't want to modify the projection matrix.

glMatrixMode(GL_MODELVIEW);

// prepare joystick input

SDL_Joystick *joystick;
joystick = SDL_JoystickOpen(0);

// Clear the color and depth buffers.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// reset position to origin of coordinate system

glLoadIdentity();

// move 5 units into the screen in order to have a proper
// viewing distance to the object

glTranslatef(posVector.x, posVector.y, posVector.z);

// keep joystick data up to date

SDL_JoystickUpdate();

//---------------------
// rotate around x axis
//---------------------

if(SDL_JoystickGetButton(joystick, 6))
++angleVector.x;
else if(SDL_JoystickGetButton(joystick, 7))
--angleVector.x;

// ... same with y- and z-axis

glRotatef(angleVector.x, 1, 0, 0);
glRotatef(angleVector.y, 0, 1, 0);
glRotatef(angleVector.z, 0, 0, 1);

glBegin(GL_TRIANGLES); // Start Drawing A Triangle
glVertex3f( 0.0f, 1.0f, 0.0f); // First Point Of The Triangle
glVertex3f(-1.0f,-1.0f, 0.0f); // Second Point Of The Triangle
glVertex3f( 1.0f,-1.0f, 0.0f); // Third Point Of The Triangle
glEnd();

//-------------
// swap buffers
//-------------

SDL_GL_SwapBuffers();
}

[edited by - Douglas on September 1, 2003 2:46:48 PM]

[edited by - Douglas on September 1, 2003 2:49:42 PM]

[edited by - Douglas on September 1, 2003 2:53:10 PM]

Share this post


Link to post
Share on other sites
Sander    1332
Well, I can''t spot any errors in your code. What is it that''s not working? I take it that it is rotating around the correct point (the middle of the triangles) but that the rotations are going in another direction that you expect them to go (Can be a sign of gimal lock!). If that is the case, try to reverse the order of the three rotations (first rotate around z, then y and finally x). That will make it more naturally. Enough for simple games (platformers, shooters).

If you really want more complicated rotations (say, for a simple 3D space shooter, in which you can make loopings and stuff) then you really need quaternions or matrices. I suggest you take a look at the OpenGL tutorials at Game Tutorials. Yhey have a pretty straightforward quaternion tut (OpenGL tut''s page 3 or 4 if I remember correctly).



Sander Maréchal
[Lone Wolves Game Development][RoboBlast][Articles][GD Emporium][Webdesign][E-mail]

Share this post


Link to post
Share on other sites
Douglas    122
I don't want quaternions or spaceships. Everything I want right now is a correctly rotating nasty triangle. Of course I tried your last tip to change to order of the three glRotatef statement but without success.
And you're right: It's rotating around the right point but it nevertheless looks strange, but I'm unable to describe it.

[edited by - Douglas on September 1, 2003 3:27:57 PM]

Share this post


Link to post
Share on other sites
Sander    1332
Well, there''s one last thing you can so. Wrap the project in a zip file and post ia link. If it''s the rotation order or gimbal lock it''ll be easily spotted by others. Also, a spaceship is no different from your triangle. If you rotate it around too much you can get gimbal lock. And there are really only two ways out of that: Quaternions and Matrices.

Sander Maréchal
[Lone Wolves Game Development][RoboBlast][Articles][GD Emporium][Webdesign][E-mail]

Share this post


Link to post
Share on other sites
Anthony Serrano    3285
The problem is that you cannot get the effect that you are looking for from a single rotation; whenever the user changes the rotation axis, you instantly lose all the rotations that were done up till then. In order to get the effect you''re looking for you need the following bits of code.

In your initialization section:
GLfloat rotationAccumulator[16]={
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1};


Between your calls to glClear and glBegin:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if ( (vector.x!=0) || (vector.y!=0) || (vector.z!=0) ){
glRotate(rtri, vector.x, vector.y, vector.z);
}
glMultMatrix(rotationAccumulator);
glGetFloatv(GL_MODELVIEW_MATRIX, rotationAccumulator);


Remember that, this way, you will need to periodically renormalize rotationAccumulator.

Share this post


Link to post
Share on other sites
Sander    1332
IIRC a quaternion is normalized if all it''s values add up to one. You can normalize them the same way as a vector. First compute the length as so:

l=sqrt(quat.a^2 + quat.b^2 + quat.c^2 + quat.d^2);

then divide them all by the length:

quat.a /= l;
quat.b /= l;
quat.c /= l;
quat.d /= l;

that should do it.

Sander Maréchal
[Lone Wolves Game Development][RoboBlast][Articles][GD Emporium][Webdesign][E-mail]

Share this post


Link to post
Share on other sites
Douglas    122
1) A quaternion is basically a vector with an angle component , isn''t it?

And in glRotatef(angle, x, y, z) I pass data typical for a quat.:
An Angle w which tells glRotatef how much to rotate and a three-dimensional vector which tells glRotatef about which axis the rotation is to be performed. Doesn''t that mean that glRotatef already works with quaternions and hence, gimbal lock shouldn''t occur?

2) What is rotationAccumulator: An array where each line builds a quaternion (which would mean 4 quat. in total)?

3) The undocumented third-party 3D math library I use has the following method declaration for quaternion normalization:

float Normalize(skpQuaternion*, float);

The first parameter is the quaternion to be normalized, no question about that. But I havn''t figured out the meaning of the second parameter yet. Since everything you''ve to know to normalize a quat. is the quat. itself. One could argue that it might be the magnitude, but I doubt that since the quat. class doens''t offer a method to calculate it.

Share this post


Link to post
Share on other sites
Sander    1332
1) No. A Quaternion is a 4 dimensional rotation. But it can be converted to a vector and an angle (called an axis-angle rotation). glRotatef uses axis-angles like:

glRotatef(angle, axis.x, axis.y, axis.z);

Also, axis-angle rotations can still suffer from gimbal lock. What you do is you take an axis-angle, convert it to a Quaternion, rotate the Quaternion, convert back to axis-angle and feed it to OpenGL.

2) the rotationAccumulator in the above example looks like a 4x4 matrix. You can use 3x3 and 4x4 matrices to avoid gimbal-lock as well. Only, they''re bigger that Quaterions, slower to compute and less stable. Personally, I use a 4x4 matrix for my camera and Quaternions for the rest. The ease of matrices is that they contain the vectors pointing straight up, right and forward. This makes billboarding very easy.

The matrix from the code actually is an identity matrix. Lok these up in the Matrix and Quaternion FAQ

3) I have two guesses. The first one is that you also pass the quaternions length as a parameter. If you know the length already it is pointless to use anothe expensive sqrt() function again. My other guess would be that you pass the ''desired'' length of the quaternion. As I explained above, normalization makes all the components add up to 1. Maybe you can also make them ann up to 2 or 3 or 0.6. I can''t tell unless you post the functions code.

Sander Maréchal
[Lone Wolves Game Development][RoboBlast][Articles][GD Emporium][Webdesign][E-mail]

Share this post


Link to post
Share on other sites
Douglas    122
a) If rotationAccumulator is mathematically a 4x4 matrix, then how to normalize it like Anthony Serrano said (matrices can''t be normalized!)?

b) If I had the implemantion of the quaternion class and not a .lib file, I could easily figure it out myself. This way, I have to test it practically...

Share this post


Link to post
Share on other sites
Sander    1332
Sorry, can''t help you. I don''t know how to normalize a matrix and it''s not listed in the Matrix and Quaternion FAQ. Your best bet would be the ''Math & Physics'' forum.

If I were to guess though: In a rotation matrix (that''s the top-left 3x3 part of the 4x4 matrix) the top row is the vector pointing right, the second is the vector pointing up and the third one is the vector pointing forward. I guess you would renormalize these vectors, since rounding errors can make them of different size than 1. Also, you would have to make sure that all 3 vectors are still perpendicular to eachother (this is the part I don''t know).

Sander Maréchal
[Lone Wolves Game Development][RoboBlast][Articles][GD Emporium][Webdesign][E-mail]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
An easy but non optimized way to orthonormalize a matrix
void orthonormalize (sgMat4 c) {
sgVec3 xp, yp, zp;

sgSetVec3 (xp, c[0][0], c[1][0], c[2][0]);
sgSetVec3(zp, c[0][2], c[1][2], c[2][2]);

sgNormaliseVec3(xp);
sgNormaliseVec3(zp);
sgVectorProductVec3(yp,zp,xp);
sgVectorProductVec3(xp, yp, zp);
c[0][0]=xp[0]; c[1][0]=xp[1];c[2][0]=xp[2];
c[0][1]=yp[0]; c[1][1]=yp[1];c[2][1]=yp[2];
c[0][2]=zp[0]; c[1][2]=zp[1];c[2][2]=zp[2];
}

The above is using using the open source plib that also has quaternions
http://plib.sourceforge.net/
Also of interest
http://www.sjbaker.org/steve/omniv/eulers_are_evil.html

Share this post


Link to post
Share on other sites