Quaternion Baldness

Problems building or running the engine, queries about how to use features etc.
Post Reply
Clive Jackson
Greenskin
Posts: 123
Joined: Mon Jan 03, 2005 3:16 am
Contact:

Quaternion Baldness

Post by Clive Jackson »

Before I get started I just wanted to thank Simbad and the rest of the team for producing a truly excellent product. I think Ogre will be the marker used to measure all other 3D Engines of this type both open source and commercial. Also congratulations on the imminent release of V 1.0.0

I have two questions if someone could help me:

Question 1:
Due to user interface requirements I need to be able to determine the orientation of an object and display it to the user in XYZ format as degrees around the local axis i.e. X: 90, Z: 0, Y: 45 I then need to be able to take a users modifications to these values and apply them back to the objects orientation.

The problem is that I’m mathematically challenged and seem to be experiencing some difficulty understanding how to manipulate a Quaternion. :oops: Despite searching this forum, other forums and reading books on the subject it would appear that I’m still mixing my Apples and Oranges and getting fruit salad.

Could someone help me to understand why I’m seeing the values from the code below. I’m assuming that too many people are using this for it to be a bug and is therefore a mistake on my part.

Code: Select all

void createDefaultCamera( void )
	{
		Ogre::Quaternion q;
		Ogre::Radian rad;

		mCamera = mSceneMgr->createCamera( "Default" );
		mCamera->setPosition( Vector3( 0, 0, 500 ) );
		mCamera->setFixedYawAxis( false );
		

		////////////////////////////////////////////
		// Set Yaw Only

		// Zero out 
		mCamera->setOrientation( Ogre::Quaternion::IDENTITY );

		rad = Ogre::Radian( 1.5 );
		mCamera->yaw( rad );
		
		q = mCamera->getOrientation();
		rad = q.getPitch(); // rad is set to 1.5
		rad = q.getYaw(); // rad is set to 0
		rad = q.getRoll(); // rad is set to 0


		////////////////////////////////////////////
		// Set Pitch Only

		// Zero out 
		mCamera->setOrientation( Ogre::Quaternion::IDENTITY );

		rad = Ogre::Radian( 1.5 );
		mCamera->pitch( rad );
		
		q = mCamera->getOrientation();
		rad = q.getPitch(); // rad is set to 0
		rad = q.getYaw(); // rad is set to 0
		rad = q.getRoll(); // rad is set to 1.5


		////////////////////////////////////////////
		// Set Roll Only

		// Zero out 
		mCamera->setOrientation( Ogre::Quaternion::IDENTITY );

		rad = Ogre::Radian( 1.5 );
		mCamera->roll( rad );
		
		q = mCamera->getOrientation();
		rad = q.getPitch(); // rad is set to 0
		rad = q.getYaw(); // rad is set to 1.5
		rad = q.getRoll(); // rad is set to 0


		////////////////////////////////////////////
		// Combine

		// Zero out 
		mCamera->setOrientation( Ogre::Quaternion::IDENTITY );

		rad = Ogre::Radian( 1.5 );
		mCamera->pitch( rad );

		rad = Ogre::Radian( 2 );
		mCamera->yaw( rad );

		rad = Ogre::Radian( 2.5 );
		mCamera->roll( rad );

		q = mCamera->getOrientation();
		rad = q.getPitch(); // rad is set to -0.50071
		rad = q.getYaw(); // rad is set to 0.048277
		rad = q.getRoll(); // rad is set to 1.60435


		// Look back along -Z
		mCamera->lookAt( Vector3( 0, 0, -300 ) );

		mCamera->setNearClipDistance( 5 );
	}

It would appear that getPitch() returns the yaw, getRoll() returns the pitch and that getYaw() returns the roll. That is until you combine calls to pitch(), yaw() and roll() at which point the numbers returned by getPitch(), getYaw() and getRoll() cease to make any sense to me. As I said I believe this to be my misunderstanding of how to manipulate Quaternion’s. Changing setFixedYawAxis() to true seems to have no impact on the first 3 tests. The result on the last test is to produce more numbers that I don’t understand the relevance of.

Question 2:
Could someone tell me how to convert a Quaternion to a Vector and then convert the Vector back to a Quaternion without data loss of course. I have seen this done in other products like. I need to be able to do this in order to comply with an existing network protocol.

If anyone could help me out with this it would certainly ease my rate of hair loss :D

I’m using Ogre 0.15.1 on XP with MS VC 2003
Thanks
Clive
User avatar
haffax
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 4823
Joined: Fri Jun 18, 2004 1:40 pm
Location: Berlin, Germany
x 8
Contact:

Post by haffax »

Camera has two coordinate systems when it is not attached to a scene node, but manipulated stand alone. The world's coordinate system and its local system. Camera::getOrientation() and Camera::setOrientation() work in the world coordinate system. yaw(), pitch() and roll() work in its local system.

I'd recommend to attach the camera to a SceneNode and try again with that, here you have more control over the coordinate system with the transform space argument.
Note that SceneNode transformations aren't applied before the next frame is drawn. In order to force the update of a node you must use SceneNode::_update().

The way you manipulate you Camera is bound to produce a Gimbal Lock sooner or later. It is better to apply the rotation with a quaternion.

Look at the Quaternion-API for the conversion functions. You can't convert a quaternion to a vector. But you can convert between quaternion and a rotation axis / angle value pair and back. See Quaternion::ToAngleAxis() and Quaternion::FromAngleAxis().
team-pantheon programmer
creators of Rastullahs Lockenpracht
Clive Jackson
Greenskin
Posts: 123
Joined: Mon Jan 03, 2005 3:16 am
Contact:

Post by Clive Jackson »

tanis, thanks for the heads up on the different camera systems I’ll certainly remember that for future reference. Unfortunately if I remove the camera from the picture and deal with quats directly I still experience similar problems. I was just using the camera code as an example.

I guess my sample code detracted from the problem at hand. Let me see if I can restate the problem I have. From what I have read the only effective way of communicating with a user regarding an objects orientation is by using Euler Angles. People are familiar with the concept of rotating something a specified number of degrees around an axis. I think most if not all modeling programs take this approach.

My approach rightly or wrongly is to keep everything a quat for as long as possible and only convert when needed. Hopefully this approach will avoid the problem of Gimbal lock.

So in a nutshell I need to get orientation data from a user in the form of X: 45 Y:30: Z:0. I then need to get this into a quat and then apply the quat to the target object.

Then at some point latter on I need to get the target objects orientation in the form of a quat, interrogate the quat and get back X: 45 Y:30: Z:0. In other words I want to be able to get the same data out as I put in as this is what the user is expecting.

Unfortunately my attempts to do this have failed, the data I’m getting back does not match the input. I’m obviously doing something wrong or my approach is wrong, could someone tell me why?

Here are some of the different approaches I have tried.

Code: Select all

		Ogre::Quaternion qNew;
		Ogre::Quaternion qTotal;
		Ogre::Matrix3 kRot;
		Ogre::Radian x, y, z;

		// Approach 1
		qTotal = Ogre::Quaternion::IDENTITY;
		qNew = Ogre::Quaternion::IDENTITY;

		qNew.FromAngleAxis( Ogre::Radian( 1.5 ), Ogre::Vector3::UNIT_X );
		qTotal = qNew * qTotal;

		qNew.FromAngleAxis( Ogre::Radian( 2.0 ), Ogre::Vector3::UNIT_Y );
		qTotal = qNew * qTotal;

		qNew.FromAngleAxis( Ogre::Radian( 2.5 ), Ogre::Vector3::UNIT_Z );
		qTotal = qNew * qTotal;

		qTotal.ToRotationMatrix( kRot );

		kRot.ToEulerAnglesZYX( z, y, x );

		// z = -0.64159262, qTotal.getYaw() returns the same value, why is it not 2.5?
		// y = 1.1415926, qTotal.getPitch() returns the same value,  why is it not 2.0?
		// x = -1.6415925, qTotal.getRoll() returns the same value, why is it not 1.5?


		// Approach 2
		x = Ogre::Radian( 1.5 );
		y = Ogre::Radian( 2 );
		z = Ogre::Radian( 2.5 );

		kRot.FromEulerAnglesZYX( z, y, x );

		qTotal.FromRotationMatrix( kRot );

		qTotal.ToRotationMatrix( kRot );

		kRot.ToEulerAnglesZYX( z, y, x );

		// z = -0.64159262, qTotal.getYaw() returns the same value, why is it not 2.5?
		// y = 1.1415926, qTotal.getPitch() returns the same value,  why is it not 2.0?
		// x = -1.6415925, qTotal.getRoll() returns the same value, why is it not 1.5?
I guess the good news is that both approaches return the same data so I’m at least consistent in that regard. :D

I did find a very useful tool : http://www.gamemath.com/Euler.htm This is the web site for the book 3D Math Primer, which has been very useful but obviously not quite useful enough given this post.
Last edited by Clive Jackson on Tue Feb 01, 2005 8:54 pm, edited 1 time in total.
Thanks
Clive
User avatar
haffax
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 4823
Joined: Fri Jun 18, 2004 1:40 pm
Location: Berlin, Germany
x 8
Contact:

Post by haffax »

Clive, there is a reason, that there are many different To/FromEulerAngles() methods. It is because with euler angles sequence of rotations matter.

You use FromEulerAnglesZYX() this means first the roll is applied then the yaw then the pitch. Each time from the objects point of view. Try it with some object you have at hand. Say a match box. First yaw it by 90° then pitch it by 90°, remember its position. Now apply first the pitch then the yaw. You'll see it has a completly different position now. (I just tried it, just to be sure. :P )

Anyway, I never tried what you want to achieve, but I intuitivly guess, you get the expected results when you read the euler angles in reversed order.

Apply it with ToEulerAngleXYZ(), retrieve it with FromEulerAngleZYX(). Never tried this it's just an idea.
team-pantheon programmer
creators of Rastullahs Lockenpracht
Clive Jackson
Greenskin
Posts: 123
Joined: Mon Jan 03, 2005 3:16 am
Contact:

Post by Clive Jackson »

I'll give that a try, I did try a few of the combinations but not all of them, however at this stage I'm not hopeful. I have been doing more research. If I understand this stuff correctly, in particular Euler Angles, the problem I'm hitting is that Euler Angles can be represented in different ways. Quote from "3D Math Primer" book: "pitching down 135 degrees is the same as heading 180 degrees, pitching down 45 degrees and banking 180 degrees".

So in my example setting

x = 1.5
y = 2.0
z = 2.5

would appear to be the same as:

x = -1.6415925
y = 1.1415926
z = -0.64159262

I have not tested this but this would be my guess. Can anyone confirm that this is true?

I tried to see how Wings 3D and Quark do it but neither of them user quaternions.

I want to and need to use quaternions, so it looks like this is a no go. :cry:

Thanks for your help with this, it is much much appreciated.
Thanks
Clive
Clive Jackson
Greenskin
Posts: 123
Joined: Mon Jan 03, 2005 3:16 am
Contact:

Post by Clive Jackson »

I have just worked out the answer to my other question. :D How to convert a quaternion to a vector and back again. When I say vector, I guess what I really mean is 3 numbers and a vector is just an easy way to store them. The numbers themselves do not have any meaning as a vector per say.

Code: Select all

Ogre::Quaternion qNew;
Ogre::Quaternion qOld;
Ogre::Vector3 v;

// Add some rotation to the quaternion
qNew.FromAngleAxis( Ogre::Radian( 1.5 ), Ogre::Vector3::UNIT_X );
qOld = qNew * qOld;

qNew.FromAngleAxis( Ogre::Radian( 2.0 ), Ogre::Vector3::UNIT_Y );
qOld = qNew * qOld;

qNew.FromAngleAxis( Ogre::Radian( 2.5 ), Ogre::Vector3::UNIT_Z );
qOld = qNew * qOld;

// Convert quaternion to a vector
v = Ogre::Vector3( qOld.x, qOld.y, qOld.z );

// Convert vector to quaternion
qNew.w = (Ogre::Real)sqrt( 1.0 - (v.x * v.x + v.y * v.y + v.z * v.z));
qNew.x = v.x;
qNew.y = v.y;
qNew.z = v.z;

// qNew now equals qOld
I needed to be able to do this so that I could be compatible with another applications network protocol.

You never know it may be useful to someone else. :roll:
Thanks
Clive
User avatar
sinbad
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 19269
Joined: Sun Oct 06, 2002 11:19 pm
Location: Guernsey, Channel Islands
x 67
Contact:

Post by sinbad »

Do you mean euler angle rotations? Use Quaternion's getYaw, getRoll, and getPitch, but be careful about gimbal lock.
Clive Jackson
Greenskin
Posts: 123
Joined: Mon Jan 03, 2005 3:16 am
Contact:

Post by Clive Jackson »

Sinbad,

With the first issue I was finding that the output from getPitch(), getYaw() and getRoll() did not exactly match the input to the quat using FromAxisAngle(). (See code in previous posts.) I'm assuming that this is because euler rotations can be expressed in different ways.

Right now I'm going to bail on the issue. In my application Quats are abstracted as a Rotation and will be essentially write only. I'll offer the users getPitch() etc but with the appropriate caveat.

I certainly want to avoid Gimbal lock issues however I can't see any other way of obtaining typed input from a user regarding the rotation they want for an object apart from letting them specify XYZ values. These values will be applied to the quat as soon as possible but I guess that does not necesssarily head off the problem.

With the second issue I just needed to conform to a quirk of the network protocol of another app. It seems to work OK, by not sending the value for w the protocol designer saved themselves a few bytes of network trafic in return for a call to sqrt().
Thanks
Clive
Post Reply