Skip to main content
gamedev.net
🔒 Locked 🎮 Unity

Rotation Problem

Started by medevilenemy Jun 19, 2007 at 10:15 PM 17 replies 2.3k views
Original Post
medevilenemy
medevilenemy
A friend of mine, who is helping with my current project, wrote the following code to handle object rotations in the game. It works perfectly for rotations about the X and Z axes, but rotation about the Y doesn't work properly. For some reason, after about 180 degrees of rotation about the Y an object will just jump back to 0 degrees.

void rotateTo(Vector3 forward, Vector3 top)
{//calls glRotate on some arbitary model to get the same rotation as evinced by the rotation of the vectors forward and top
//Precondition: Have called correct glGet()
	Vector3 zforward=Vector3(0,0,-1),ztop=Vector3(0,1,0);	//This assumes the default orientation is forward=-Z, top=+Y
	GLfloat length=forward.length();
	Vector3 axis=forward.cross(zforward)/length;	//Axis is perpendicular to the plane that encompasses where
	GLfloat angle=acosf(forward.dot(zforward))/fabs(length);
	glRotatef(angle*M180_PI, axis.vec[0], axis.vec[1], axis.vec[2]);

	{//Rotate ztop like I rotated the model
		GLfloat c=cosf(angle),s=sinf(angle),omc=1-c,m[9];
		m[0]=omc*axis.vec[0]*axis.vec[0]+c;	m[1]=omc*axis.vec[0]*axis.vec[1]-s*axis.vec[2];	m[2]=omc*axis.vec[0]*axis.vec[2]+s*axis.vec[1];
		m[3]=omc*axis.vec[0]*axis.vec[1]+s*axis.vec[2];	m[4]=omc*axis.vec[1]*axis.vec[1]+c;	m[5]=omc*axis.vec[1]*axis.vec[2]-s*axis.vec[0];
		m[6]=omc*axis.vec[0]*axis.vec[2]-s*axis.vec[1];	m[7]=omc*axis.vec[1]*axis.vec[2]+s*axis.vec[0];	m[8]=omc*axis.vec[2]*axis.vec[2]+c;
		Vector3 result;
		result.vec[0]=ztop.vec[0]*m[0]+ztop.vec[1]*m[1]+ztop.vec[2]*m[2];
		result.vec[1]=ztop.vec[0]*m[3]+ztop.vec[1]*m[4]+ztop.vec[2]*m[5];
		result.vec[2]=ztop.vec[0]*m[6]+ztop.vec[1]*m[7]+ztop.vec[2]*m[8];
		result.normalize();
		ztop=result;
	}
	length=top.length();
	axis=top.cross(ztop)/length;
	angle=acosf(top.dot(ztop))/fabs(length)*M180_PI;
	glRotatef(angle,axis.vec[0],axis.vec[1],axis.vec[2]);
}

void rotateTo()
    {
        Vector3 fore,top;
        fore.vec[0]=ViewDir.x;
        fore.vec[1]=ViewDir.y;
        fore.vec[2]=ViewDir.z;
        top.vec[0]=UpVector.x;
        top.vec[1]=UpVector.y;
        top.vec[2]=UpVector.z;
        ::rotateTo(fore,top);
    };


I think it has something to do with the right vector being left out, but I dont know how to incorporate it. Anyone have any suggestions? (the right vector is a class member, I just don't know how to put it into this code if the lack of it is the problem). [Edited by - medevilenemy on June 19, 2007 10:37:16 PM]
There was a saying we had in college: Those who walk into the engineering building are never quite the same when they walk out.
EdgarVerona
EdgarVerona
Shoot, I remember seeing a problem like this a while ago. I believe it's because acos only returns valid values between a certain range of radians. It's similar to how the absolute value of -5 and 5 are both 5.

I believe when using asin, acos, or atan you have to evaluate your result differently depending on if it's >= 2PI, <= 2PI, or some others... I'll try to do a quick look and see if I can find the specifics. But basically where you do that acos evaluation you'll need an if statement that negates the number you come up with depending on that value.

I'll see if I can find some source code to elaborate, brb.
Edgar Verona"But when a long train of abuses and usurpations, pursuing invariably the same Object evinces a design to reduce them under absolute Despotism, it is their right, it is their duty, to throw off such Government, and to provide new Guards for their future security."
EdgarVerona
EdgarVerona
Here you go. This is a link from a Java forum that shows someone implementing this check for arctangent. I believe it works the same for arccos and arcsin, but I've always used atan personally.

Java Thread about this topic, useful for Pseudocode or actual code

Remember that:

sin = opp / hyp
cos = adj / hyp
tan = opp / adj

atan is easier to use because you already (or should already) have your vector broken into x and y components. You can plug them into atan without much grief.
Edgar Verona"But when a long train of abuses and usurpations, pursuing invariably the same Object evinces a design to reduce them under absolute Despotism, it is their right, it is their duty, to throw off such Government, and to provide new Guards for their future security."
Carleone
Carleone
Btw. Direction of cross product deels with angle sign.
Problem of this code is that u do not take in account, when forward and zforward are on one line. In that case cross product returns zero length vector. U need check it, and write something like this:
if(length(axis)<=ZERO){
if(forward.dot(zforward)>0.){
// no rotate
} else {
// we need to rotate on 180 degrees around axis, that is cross of zforward and ztop
axis = zforward.cross(ztop);
angle = PI;
}
}

Here u go. Same with rotation around top.
medevilenemy
medevilenemy
Thanks a lot! That may just do it. I'll be sure to post results here.
There was a saying we had in college: Those who walk into the engineering building are never quite the same when they walk out.
erissian
erissian
void rotate_to(Vector from, Vector to, Vector forward, Vector up) {  /* First we guarantee our math will be easy by making     sure we are dealing with only unit vectors. */  from    = from.normal();  to      = to.normal();  forward = forward.normal();  up      = up.normal();  /* Then we will determine the pitch axis using the     and up vectors. Then we determine the pitch angle     by getting the angle between from and to projected     on the forward-up plane. */  Vector pitch_axis = up.cross(forward);  Vector from_projected = Vector(0,up.dot(forward),forward.dot(from));  Vector to_projected = Vector(0,up.dot(to),forward.dot(to));  float pitch_angle = atan2f(to_projected.y-from_projected.y,to_projected.z-from_projected.z);  /* The next step is determining the angle of separation using the up axis. We basically repeat the above process using the forward and pitch_axis vectors instead of up and forward. */  Vector from_projected = Vector(0,forward.dot(forward),pitch_axis.dot(from));  Vector to_projected = Vector(0,forward.dot(to),pitch_axis.dot(to));  float yaw_angle = atan2f(to_projected.y-from_projected.y,to_projected.z-from_projected.z);  /* Now we apply the rotations. We want to pitch, then yaw, so we call them in reverse order. */  glRotatef(pitch_angle,pitch_axis.x,pitch_axis.y,pitch_axis.z);  glRotatef(yaw_angle,up.x,up.y,up.z);}


I wrote this on-the-fly, so no guarantees about correct signs or ordering. :)

Edit: Must type faster! 3 posts since I initially hit reply :)
We''re sorry, but you don''t have the clearance to read this post. Please exit your browser at this time. (Code 23)
medevilenemy
medevilenemy
heh, yeah posting can be like that sometimes... that and gamedev itself has seemed a litle slow lately (at least for me).
There was a saying we had in college: Those who walk into the engineering building are never quite the same when they walk out.
SuperSayu
SuperSayu
Hi, writer of the OP's problem code here.

Erissian, I'm not sure how to use your code. The way my code works is to rotate a model (the glRotate() calls) based on how two vectors, forward and top, are rotated relative to the zero vectors zforward and ztop. If you could either explain how yours works, or ideally fudge the code so that it works the way mine does, that would be keen. If someone else can come up with another algorithm to do the same, preferably a fast one, by all means share.

Carleone, fwiw you're probably right but I don't think that's the major problem of my code, so I'll put it off for now. Still, thanks, I probably wouldn't have caught that.

The logic of the code, if anyone needs to know, is to rotate first the front of the model, then the top, to where it is by using the cross product to produce the surface normal of the plane between zforward and forward, rotating the model in that plane by the correct angle (the arccosine of the dot product). This should produce a model that is in the correct orientation in two dimensions but not the third, so we perform the same rotation on the ztop vector (so we know where it ended up after the first rotation), and perform another 3d planar rotation from ztop' to top, which should correctly orient us in the third dimension without screwing with the correct positioning in the first two. In theory.

From my tests, the code seems to work 92%(11/12) of the time. Rotating around the X- and Z-axes seems to work 100%; when rotated about the Y-axis, however, there is a segment of about 90 degrees where the rotation reliably flips 180 degrees for no obvious reason. I don't know numerically what those 90 degrees are or even if it's exactly 90 degrees, although that makes the most sense... somehow.

I tried medevilenemy's idea of adding in a third rotation, but that got rid of one problem and created another. From those results I'm thinking the second rotation may be part of the problem. If anyone can sees a problem with my logic instead of my code, point that out as well, please. But... if it flat out didn't work, I could deal with that. But it's irritating because it seems to work most of the time, because it means that my logic is either correct or deceptively close, and therefore the problem is in the nitty gritty. If it's flat out a bad algorithm, then... but until I can prove that, I have to squint at the code and wonder what I'm missing.

Thanks for the help so far.
medevilenemy
medevilenemy
I am not really very good with this stuff, but my guess is that Carleone is correct. SuperSayu, perhaps you could run the math a couple times and see if there are any errant values.
There was a saying we had in college: Those who walk into the engineering building are never quite the same when they walk out.
Aressera
Aressera
one word - matrices
erissian
erissian
Quote:
Original post by SuperSayu
Erissian, I'm not sure how to use your code. The way my code works is to rotate a model (the glRotate() calls) based on how two vectors, forward and top, are rotated relative to the zero vectors zforward and ztop. If you could either explain how yours works, or ideally fudge the code so that it works the way mine does, that would be keen. If someone else can come up with another algorithm to do the same, preferably a fast one, by all means share.


My code would be a straight replacement for void rotateTo(Vector3,Vector3). It accomplishes the same thing, essentially saying "Hey, figure out what rotation would make this vector turn into that vector, and then apply it."

As far as naming conventions, my "from" is equivalent to your "forward" and my "to" is equivalent to your "top". Additionally I've included my own "forward" and "up". I use these to make the function more general; you never know how somebody intends to define their coordinate system. Sometimes Y is up and Z is forward, but sometimes Z is up and X is forward. Personally, I use the former convention, but I don't want to make any assumptions.

To reduce the number of replacements you would have to make, you could declare it this way:

void rotate_to(Vector from, Vector to, Vector forward=Vector(0,0,1), Vector up=Vector(0.1,0));

That way it would be legal to call it with just two arguments.

Basically, what I do here is apply the rotation that would turn "from" into "to," under the restriction that they should be generally oriented the same way afterwards, ie. if you transformed a standing man, he should still be standing afterwards.

What I do first is figure out the difference angle due to a rotation on X. I prefer to do my rotations in XYZ axes order. However, like I said before, not everybody uses the same convention, so it's more appropriate to say that I like to rotate in right-up-forward axes order.

So once we know what pitch our intermediate vector is at, we need to work out the yaw, or rotation around Y (or whatever your up is). We calculate that difference angle, and then we apply both rotations in order.

This is all assuming that I read your code correctly in the first place :)
We''re sorry, but you don''t have the clearance to read this post. Please exit your browser at this time. (Code 23)
SuperSayu
SuperSayu
Erissian: Yeah... it doesn't work. It sort of moves the model in little circles instead of turning it on its axis, and I don't even know how that works. I'm not sure I want to know, frankly.

I'm also not sure we're solving the same problem. You seem to be doing a translation from From to To, but said to replace From with Forward and To with Top, or something like that... that the function translates Forward INTO top, which isn't how my code works at all... it transforms the set (zForward, zTop) [Note zForward and zTop are declared elsewhere, sorry if that confused anyone] into (Forward, Top) where forward is the front of the model and top is, well, the top of the model. Those two variables represent everything we need to know about how the model is oriented (We keep track of three, the third being the side vector, but we don't really need that and so it's left out... there's only one normal vector through the origin that is perpendicular to both the forward and top vectors, and it can be found with the cross product)

Aressera: One word--unhelpfullyambiguousrepliesdon'thelpmesolvetheproblem. (Warning: this word may not be in the *official* scrabble dictionary.)

EdgarVerona: Sorry I didn't get to you last time. I thought about your reply, and for a moment thought I understood everything, but alas, on reflection I don't think that's the problem. You see, the dot product of two normal vectors will always be inside the acos()'s domain, and it will always produce the right value because the smallest angle between any two vectors (out of the two possible in the plane of rotation) will always be less than or equal to 180 degrees. If the angle would be larger than 180 degrees, the cross product will generate the other possible rotation, and so we still get the lesser of the two angles.

Also let me reiterate, the area where the rotation flips is close to 90 degrees of input, not 180 degrees of input, so whatever the problem is it has to be something that would produce such a result. It may just be an phenomenon unique to my algorithm, and I'll have to find another, but it's maddening in that it almost wants to work. Ah well.

...Hmm. Maybe if I...? *Goes off to experiment*
Aressera
Aressera
Quote:
Original post by SuperSayu
Aressera: One word--unhelpfullyambiguousrepliesdon'thelpmesolvetheproblem. (Warning: this word may not be in the *official* scrabble dictionary.)


I resent that sarcasm. I was lazy and hoped someone else would chime in with this:

SO, I think you guys really need to rethink the way you're doing rotations in game if you want 6 degrees of freedom. Ideally you should use 3x3 matrices or quaternions to represent a rotation (I recommend matrices for simplicity and ease of use) for an object. AT ALL COSTS, avoid using angles. Ideally, your matrix class should be able to construct a rotation matrix for a rotation about the X axis, Y axis, and Z axis. Then you can multiply them in any order to get whatever rotation you desire. This gets rid of the phenomenon you are describing: gimbal-lock because the order of rotations does not matter and is customizable.

In order to interface this with OpenGL, you build a 4x4 transformation matrix (float[16]) representing the position and orientation of an object. By using the glMultMatrix(float*) function, you can multiply that transformation by you current world transformation matrix.

Despite the fact that I disagree with the mantra of this code, Here is how to fix your problem:

// I changed this function's interface to better make use of// the OpenGL calls involved. You translate and rotate in the same// function call.void transform(Vector3 position, Vector3 forward, Vector3 top){	// assume that forward is the x coordinate axis,	// top is the y coordinate axis	// therefore, the z axis (right) is forward cross top	// you might need to negate this vector (I don't remember the	// right hand rule)	Vector3 right = cross( forward, top );		// build the transformation matrix, using the forward (x) vector	// as the first column, top (y) vector as the second, and 	// right (z) vector as the third. If these vectors are all unit	// vectors, then this matrix represents a rotation matrix.		float* trans = { forward.x,  top.x,      right.x,    0.0f			 forward.y,  top.y,      right.y,    0.0f			 forward.z,  top.z,      right.z,    0.0f			 position.x, position.y, position.z, 0.0f };		glMultMatrix( trans );		// fine}
medevilenemy
medevilenemy
Earlier tonight, SuperSayu Sent me the following:

void rotateTo(Vector3 forward,GLfloat RotatedZ){   forward.normalize();   glRotatef(RotatedZ,0,0,1);   Vector3 zforward=Vector3(0,0,1);   Vector3 axis=forward.cross(zforward);   GLfloat angle=acosf(forward.dot(zforward));   glRotatef(angle*M180_PI, axis.vec[0], axis.vec[1], axis.vec[2]);}void rotateTo(){    Vector3 fore;    fore.vec[0]=ViewDir.x;    fore.vec[1]=ViewDir.y;    fore.vec[2]=ViewDir.z;    ::rotateTo(fore,RotatedZ);};


I have tested this new code and it seems to fix the problem completely. In any case, thanks for all the suggestions everyone.
There was a saying we had in college: Those who walk into the engineering building are never quite the same when they walk out.
Aressera
Aressera
Quote:
Original post by medevilenemy
Earlier tonight, SuperSayu Sent me the following:

*** Source Snippet Removed ***

I have tested this new code and it seems to fix the problem completely. In any case, thanks for all the suggestions everyone.


mine is better (faster because it avoids the sin/cos functions which are slow), look above you.
medevilenemy
medevilenemy
Sayu, what do you think?
There was a saying we had in college: Those who walk into the engineering building are never quite the same when they walk out.
SuperSayu
SuperSayu
Quote:
Original post by Aressera
I resent that sarcasm. I was lazy and hoped someone else would chime in with this:

Sorry. I thought it was funny.

Quote:

Despite the fact that I disagree with the mantra of this code, Here is how to fix your problem: (...)


Now that is helpful. I've been trying to figure out how the matrixes used by opengl were constructed and used, but I haven't found any resources that explained them. If it's as simple as that, then that really helps. I'll do some tests and get back to you, but I don't suspect I'll run into any problems.

Edit: It seems to work, although this seems to be the correct matrix:
        GLfloat matrix[16] = {  ViewDir.x, ViewDir.y, ViewDir.z, 0.f,                                UpVector.x, UpVector.y, UpVector.z, 0.f,                                RightVector.x, RightVector.y, RightVector.z, 0.f,                                0.f, 0.f, 0.f, 1.f};

Notably, the rows and columns are switched, and the last entry has to be a 1. If the rows and columns aren't switched, the rotations are wrong, and if the last entry isn't 1, the model doesn't even appear. You'll also note I didn't use the translation feature here; I'll have to talk about it with Medevilenemy before I make that change.

In any case, problem solved, thanks all!

[Edited by - SuperSayu on June 25, 2007 10:55:25 AM]
Aressera
Aressera
thanks, I did that from memory so i might have gotten it switched around but meh.
medevilenemy
medevilenemy
It just goes to show that the solutions to many of our bugs tend to be far easier than we think they would be. In this case, the solution code is about a fifth of the original code, and probably runs a good two or three times faster. Kind of cool, actually.
There was a saying we had in college: Those who walk into the engineering building are never quite the same when they walk out.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.