How to write Euler to Quaternion conversion, aka quaternion hell!!!

Started by
1 comment, last by dhall3000 16 years, 5 months ago
1,2,3...i've lost count how many days i've been stuck on this. if you have quaternion know-how, PLEASE help!! ok, now that i've composed myself... [SHORT VERSION] How do I write a function called EulerToQuaternion(float yaw, float pitch, float roll) that I can use to set the position of my camera? It's a left-handed coordinate system and I assume left-handed rotation (but not positive...whatever is standard in MDX 1.1). I'm not sure if handedness matters for the raw code of this conversion function (i.e. would the function be written differently for right vs. left?) and conceptually I'm not sure of how to visualize this quaternion. With respect to the origin of the earth? If so, how does the initial orientation relate to the way the conversion function is written? Or should it be with respect to the camera's eye? As you can see I'm completely lost. Also, my camera looks down the positive Z axis and the positive X axis extends off to the right. [LONG VERSION] I'm building an application that is very Google Earth-esque, complete with all the basic features of being able to pan around, zoom in and out and "jump to" different locations. I'm using C# and MDX 1.1. My camera iss been left-handed, matrix-based. Movements have been handled with Axis/Angle rotations. I have Vector3's to maintain m_up, m_look and m_position information and from those I do like this: Matrix rotation = Matrix.RotationAxis(axis, rotateAngle); m_up = Vector3.TransformNormal(m_up, rotation); m_look = Vector3.TransformNormal(m_look, rotation); m_position = Vector3.TransformNormal(m_position, rotation); m_lookAt = m_position + m_look; m_view = Matrix.LookAtLH(m_position, m_lookAt, m_up); Then in my camera Update() routine I do the following: m_up.X = m_view.M12; m_up.Y = m_view.M22; m_up.Z = m_view.M32; m_look.X = m_view.M13; m_look.Y = m_view.M23; m_look.Z = m_view.M33; This has worked very well until now but I want to get more fancy and precise with my camera movements so I'm trying to migrate to a quaternion-based camera. I'll describe what I want but my skills with quaternions are almost non-existent so I might not even make sense. Indeed, I'm not even confident I have a high-level understanding of what they are, what they do and how they work. And I've only been graphics programming for a year so I'm still a relative newbie there too. Anyway, assume for now that for this first version we'll constrain the camera to always looks directly at the center of the earth. Also, the center of the earth is at the X,Y,Z origin, the north pole lies on the Y axis and the point latitude 0, longitude 0 is along the X axis. I'd like to keep one "master" quaternion called m_orientation. This quaternion could then be used to both determine the camera's position and its orientation. I want to be able to get a new orientation quaternion by calling a function EulertToQuaternion(float yaw, float pitch, float roll). I would then pass longitude as yaw, latitude as pitch and heading as roll. Think of heading as corresponding to the little camera spin tool in the upper right corner of Google Earth; when it's pointed straight up you're in a "north up view" and your heading is 0. Following is the camera constructor showing how I initially get the camera set up. Then the application can manipulate the camera during normal operation by making subsequent calls to SetCameraLocation(...) The problem is that the m_position point is not being set right. But suprisingly (given how much agony I've been through) the camera remains looking at the correct point; the center of the earth, even as the position moves all over the place to incorrect locations. And btw, the m_position's length remains correct, just its positioning is wrong. The camera constructor looks like this: public Camera(float initialLat, float initialLon) { //this gets the xyz point corresponding to lat/lon == (0,0) m_position = WorldLatLonConverter.GetXYZFromLatLon(0f, 0f); m_initialLook = Vector3.Normalize(-m_position); m_initialUp = new Vector3(0f, 1f, 0f); //this should be the orientation of being over lat/lon == (0,0) and //looking straight towards the center of the earth. alternative way to //look at this is yaw == pitch == roll == 0 m_orientation = EulerToQuaternion(0, 0, 0); Update(); //now that I have my camera setup correctly over the 0,0 point, move //to the location specified in the parameters with a north up view SetCameraLocation(initialLat, initialLon, 0f); } Not sure where I got this equation from...somewhere on the net. But I'm not sure if it's left-handed (or if that even applies to quaternions). public static Quaternion EulerToQuaternion(double yaw, double pitch, double roll) { double cy = Math.Cos(yaw * 0.5); double cp = Math.Cos(pitch * 0.5); double cr = Math.Cos(roll * 0.5); double sy = Math.Sin(yaw * 0.5); double sp = Math.Sin(pitch * 0.5); double sr = Math.Sin(roll * 0.5); double qw = cy * cp * cr + sy * sp * sr; double qx = sy * cp * cr - cy * sp * sr; double qy = cy * sp * cr + sy * cp * sr; double qz = cy * cp * sr - sy * sp * cr; return new Quaternion((float)qx, (float)qy, (float)qz, (float)qw); } This conversion equation comes from: http://www.euclideanspace.com/maths/geometry/rotations/euler/AndyGoldstein.htm it gives me different results from the above but still wrong private Quaternion EulerToQuaternion2(float heading, float attitude, float bank) { //heading Vector3 Vh = new Vector3(0f, 0f, 1f) * (float)Math.Sin(heading / 2); Quaternion Qh = new Quaternion(Vh.X, Vh.Y, Vh.Z, (float)Math.Cos(heading / 2)); //attitude Vector3 Va = new Vector3(0f, 1f, 0f) * (float)Math.Sin(attitude / 2); Quaternion Qa = new Quaternion(Va.X, Va.Y, Va.Z, (float)Math.Cos(attitude / 2)); //bank Vector3 Vb = new Vector3(1f, 0f, 0f) * (float)Math.Sin(bank / 2); Quaternion Qb = new Quaternion(Vb.X, Vb.Y, Vb.Z, (float)Math.Cos(bank / 2)); Quaternion Qr = Qh * Qa * Qb; return Qr; } public void Rotate(Quaternion rotationQuaternion) { rotationQuaternion.Normalize(); m_orientation = m_orientation * rotationQuaternion; m_orientation.Normalize(); Matrix rotation = Matrix.RotationQuaternion(m_orientation); //we set the look and up vectors off of the orientation quaternion to ensure there's //never any error due to floating point rouding etc. m_up = Vector3.TransformNormal(m_initialUp, rotation); m_look = Vector3.TransformNormal(m_initialLook, rotation); //as we rotate we need to move the position as well, spun about the axis by angle radians rotation = Matrix.RotationQuaternion(rotationQuaternion); m_position = Vector3.TransformNormal(m_position, rotation); m_lookAt = m_position + m_look; m_view = Matrix.LookAtLH(Position, m_lookAt, m_up); UpdateCameraValues(); } public void SetCameraLocation(float lat, float lon, float heading) { m_orientation = Quaternion.Identity; Quaternion rotation = EulerToQuaternion2(Geometry.DegreeToRadian(lon), Geometry.DegreeToRadian(lat), Geometry.DegreeToRadian(heading)); Vector3 newPosition = new Vector3(m_position.Length(), 0f, 0f); m_position = newPosition; Rotate(rotation); }
Advertisement
In such cases, it's often useful to go back to basics and work through the low-level maths. For example, create 3 unit vectors that point along the axes in the positive direction, create a quaternion that will have a simple effect (e.g. rotate 90" cw on the y-axis), then apply this quaternion to your unit vectors (by converting the quaternion to a matrix then multiplying the vectors by the matrix, or multiplying the vectors by the quaternion directly). Check where the unit vectors end up pointing - if the results don't make sense you need to go back and understand why not, check your assumptions, conventions etc.. When you get rotation about a single axis working on all axes, move on to 2 axis then 3 axis rotation, at each step making sure you get the results you expect. Once you are happy the low-level maths is working as you need it, then move further up your pipeline and do similar tests.

It's hard to spot your exact problem just from looking at the code you posted, but if you can't get it fixed, there are also lots of quaternion camera articles around that can be easily searched for which may offer extra insight - there's even one on gamedev.net.
Thanks very much for taking the time to post. Writing the post forced me to rethink it all through again, after which I prayed, went back to work and God quickly led me to a piece of info that was missing in my thinking here:

http://www.gamedev.net/community/forums/topic.asp?topic_id=72342

For some reason the part about how quaternions were "nothing magic" sparked new thinking and I rewrote the code once through and it worked! I'll repost the working code so others might benefit from my suffering. Thanks again!

public Camera(float initialLat, float initialLon)
{

CreateProjectionMatrix((float)Math.PI / 3.0f, 1.3f, 1.0f, 8000.0f);

//this gets the xyz point corresponding to lat/lon == (0,0)
m_position = WorldLatLonConverter.GetXYZFromLatLon(0f, 0f);

m_initialLook = Vector3.Normalize(-m_position);
m_initialUp = new Vector3(0f, 1f, 0f);

//this should be the orientation of being over lat/lon == (0,0) and
//looking straight towards the center of the earth. alternative way to
//look at this is yaw == pitch == roll == 0
m_orientation = EulerToQuaternion(0, 0, 0);

Update();

SetCameraLocation(initialLat, initialLon, 0f);

}

public void SetCameraLocation(float lat, float lon, float heading)
{

lon = Geometry.DegreeToRadian(lon);
lat = Geometry.DegreeToRadian(lat);
heading = Geometry.DegreeToRadian(heading);

m_orientation = Quaternion.Identity;

Quaternion rotation = EulerToQuaternion(lon, lat, heading);

//the rotation that's about to be applied will only work
//for setting the position if the m_position is along the
//x-axis like it was when it was initialized.
Vector3 newPosition = new Vector3(m_position.Length(), 0f, 0f);
m_position = newPosition;

Rotate(rotation);

}

private Quaternion EulerToQuaternion(float yaw, float pitch, float roll)
{

//rotate about the z axis
Vector3 Vpitch = new Vector3(0f, 0f, 1f) * (float)Math.Sin(pitch / 2);
Quaternion Qpitch = new Quaternion(Vpitch.X, Vpitch.Y, Vpitch.Z, (float)Math.Cos(pitch / 2));

//rotate about the y axis
//the '-' is because the way direction of positive longitude changes
//is right-handed but our system is left-handed
Vector3 Vyaw = new Vector3(0f, 1f, 0f) * (float)Math.Sin(-yaw / 2);
Quaternion Qyaw = new Quaternion(Vyaw.X, Vyaw.Y, Vyaw.Z, (float)Math.Cos(-yaw / 2));

//rotate about the x axis
Vector3 Vroll = new Vector3(1f, 0f, 0f) * (float)Math.Sin(roll / 2);
Quaternion Qroll = new Quaternion(Vroll.X, Vroll.Y, Vroll.Z, (float)Math.Cos(roll / 2));

Quaternion Qrotation = Qroll * Qpitch * Qyaw;

return Qrotation;

}

public void Rotate(Quaternion rotationQuaternion)
{

rotationQuaternion.Normalize();

m_orientation = m_orientation * rotationQuaternion;
m_orientation.Normalize();

Matrix rotation = Matrix.RotationQuaternion(m_orientation);

//we set the look and up vectors off of the orientation quaternion to ensure there's
//never any error due to floating point rouding etc.
m_up = Vector3.TransformNormal(m_initialUp, rotation);
m_look = Vector3.TransformNormal(m_initialLook, rotation);

//as we rotate we need to move the position as well, spun about the axis by angle radians
rotation = Matrix.RotationQuaternion(rotationQuaternion);
m_position = Vector3.TransformNormal(m_position, rotation);

m_lookAt = m_position + m_look;

m_view = Matrix.LookAtLH(Position, m_lookAt, m_up);

UpdateCameraValues();

}

This topic is closed to new replies.

Advertisement