This question already has answers here:
Quaternion rotation without Euler angles
(3 answers)
Closed 4 years ago.
So I'm writing a program where objects move around spacesim-style, in order to learn how to move things smoothly through 3D space. After messing around with Euler angles a bit, it seems they aren't really appropriate for free-form 3D movement in arbitrary directions, so I decided to move on to what seems to be best for the job - quaternions. I intend for the object to rotate around its local X-Y-Z axes at all times, never around the global X-Y-Z axes.
I've tried to implement a system of rotation using quaternions, but something isn't working. When rotating the object along a single axis, if no previous rotations were undertaken, the thing rotates fine along a given axis. However, when applying one rotation after another has been performed, the second rotation is not always along the local axis it's supposed to be rotating along - for instance, after a rotation of about 90° around the Z axis, a rotation around the Y axis still takes place around the global Y axis, rather than the new local Y axis which is aligned with the global X axis.
Huh. So let's go through this step by step. The mistake must be in here somewhere.
STEP 1 - Capture Input
I figured it would be best to use Euler angles (or a Pitch-Yaw-Roll scheme) for capturing player input. At the moment, arrow keys control Pitch and Yaw, whereas Q and E control Roll. I capture player input thus (I am using SFML 1.6):
///SPEEDS
float ForwardSpeed = 0.05;
float TurnSpeed = 0.5;
//Rotation
sf::Vector3<float> Rotation;
Rotation.x = 0;
Rotation.y = 0;
Rotation.z = 0;
//PITCH
if (m_pApp->GetInput().IsKeyDown(sf::Key::Up) == true)
{
Rotation.x -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Down) == true)
{
Rotation.x += TurnSpeed;
}
//YAW
if (m_pApp->GetInput().IsKeyDown(sf::Key::Left) == true)
{
Rotation.y -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Right) == true)
{
Rotation.y += TurnSpeed;
}
//ROLL
if (m_pApp->GetInput().IsKeyDown(sf::Key::Q) == true)
{
Rotation.z -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::E) == true)
{
Rotation.z += TurnSpeed;
}
//Translation
sf::Vector3<float> Translation;
Translation.x = 0;
Translation.y = 0;
Translation.z = 0;
//Move the entity
if (Rotation.x != 0 ||
Rotation.y != 0 ||
Rotation.z != 0)
{
m_Entity->ApplyForce(Translation, Rotation);
}
m_Entity is the thing I'm trying to rotate. It also contains the quaternion and rotation matrices representing the object's rotation.
STEP 2 - Update quaternion
I'm not 100% sure this is the way it's supposed to be done, but this is what I tried doing in Entity::ApplyForce():
//Rotation
m_Rotation.x += Rotation.x;
m_Rotation.y += Rotation.y;
m_Rotation.z += Rotation.z;
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(m_Rotation.x, m_Rotation.y, m_Rotation.z);// * m_qRotation;
m_qRotation.RotationMatrix(m_RotationMatrix);
As you can see, I'm not sure whether it's best to just build a new quaternion from updated Euler angles, or whether I'm supposed to multiply the quaternion representing the change with the quaternion representing the overall current rotation, which is the impression I got when reading this guide. If the latter, my code would look like this:
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(Rotation.x, Rotation.y, Rotation.z) * m_qRotation;
m_Rotation is the object's current rotation stored in PYR format; Rotation is the change demanded by player input. Either way, though, the problem might be in my implementation of my Quaternion class. Here is the whole thing:
Quaternion::Quaternion(float Pitch, float Yaw, float Roll)
{
float Pi = 4 * atan(1);
//Set the values, which came in degrees, to radians for C++ trig functions
float rYaw = Yaw * Pi / 180;
float rPitch = Pitch * Pi / 180;
float rRoll = Roll * Pi / 180;
//Components
float C1 = cos(rYaw / 2);
float C2 = cos(rPitch / 2);
float C3 = cos(rRoll / 2);
float S1 = sin(rYaw / 2);
float S2 = sin(rPitch / 2);
float S3 = sin(rRoll / 2);
//Create the final values
a = ((C1 * C2 * C3) - (S1 * S2 * S3));
x = (S1 * S2 * C3) + (C1 * C2 * S3);
y = (S1 * C2 * C3) + (C1 * S2 * S3);
z = (C1 * S2 * C3) - (S1 * C2 * S3);
}
//Overload the multiplier operator
Quaternion Quaternion::operator* (Quaternion OtherQuat)
{
float A = (OtherQuat.a * a) - (OtherQuat.x * x) - (OtherQuat.y * y) - (OtherQuat.z * z);
float X = (OtherQuat.a * x) + (OtherQuat.x * a) + (OtherQuat.y * z) - (OtherQuat.z * y);
float Y = (OtherQuat.a * y) - (OtherQuat.x * z) - (OtherQuat.y * a) - (OtherQuat.z * x);
float Z = (OtherQuat.a * z) - (OtherQuat.x * y) - (OtherQuat.y * x) - (OtherQuat.z * a);
Quaternion NewQuat = Quaternion(0, 0, 0);
NewQuat.a = A;
NewQuat.x = X;
NewQuat.y = Y;
NewQuat.z = Z;
return NewQuat;
}
//Calculates a rotation matrix and fills Matrix with it
void Quaternion::RotationMatrix(GLfloat* Matrix)
{
//Column 1
Matrix[0] = (a*a) + (x*x) - (y*y) - (z*z);
Matrix[1] = (2*x*y) + (2*a*z);
Matrix[2] = (2*x*z) - (2*a*y);
Matrix[3] = 0;
//Column 2
Matrix[4] = (2*x*y) - (2*a*z);
Matrix[5] = (a*a) - (x*x) + (y*y) - (z*z);
Matrix[6] = (2*y*z) + (2*a*x);
Matrix[7] = 0;
//Column 3
Matrix[8] = (2*x*z) + (2*a*y);
Matrix[9] = (2*y*z) - (2*a*x);
Matrix[10] = (a*a) - (x*x) - (y*y) + (z*z);
Matrix[11] = 0;
//Column 4
Matrix[12] = 0;
Matrix[13] = 0;
Matrix[14] = 0;
Matrix[15] = 1;
}
There's probably something in there to make somebody wiser than me cringe, but I can't see it. For converting from Euler angles to a quaternion, I used the "first method" according to this source, which also seems to suggest that the equation automatically creates a unit quaternion ("clearly normalized"). For multiplying quaternions, I again drew on this C++ guide.
STEP 3 - Deriving a rotation matrix from the quaternion
Once that is done, as per R. Martinho Fernandes' answer to this question, I try to build a rotation matrix from the quaternion and use that to update my object's rotation, using the above Quaternion::RotationMatrix() code in the following line:
m_qRotation.RotationMatrix(m_RotationMatrix);
I should note that m_RotationMatrix is GLfloat m_RotationMatrix[16], as per the required parameters of glMultMatrix, which I believe I am supposed to use later on when displaying the object. It is initialized as:
m_RotationMatrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
Which I believe is the "neutral" OpenGL rotation matrix (every 4 values together represent a column, correct? Again, I get this from the glMultMatrix page).
STEP 4 - Display!
Finally, we get to the function run each cycle for the object that is supposed to display it.
glPushMatrix();
glTranslatef(m_Position.x, m_Position.y, m_Position.z);
glMultMatrixf(m_RotationMatrix);
//glRotatef(m_Rotation.y, 0.0, 1.0, 0.0);
//glRotatef(m_Rotation.z, 0.0, 0.0, 1.0);
//glRotatef(m_Rotation.x, 1.0, 0.0, 0.0);
//glRotatef(m_qRotation.a, m_qRotation.x, m_qRotation.y, m_qRotation.z);
//[...] various code displaying the object's VBO
glPopMatrix();
I have left my previous failed attempts there, commented out.
Conclusion - Sad panda
That is the conclusion of the life cycle of player input, from cradle to OpenGL-managed grave.
I've obviously not understood something, since the behavior I get isn't the behavior I want or expect. But I'm not particularly experienced with matrix math or quaternions, so I don't have the insight required to see the error in my ways.
Can somebody help me out here?
All you have done is effectively implement Euler angles with quaternions. That's not helping.
The problem with Euler angles is that, when you compute the matrices, each angle is relative to the rotation of the matrix that came before it. What you want is to take an object's current orientation, and apply a rotation along some axis, producing a new orientation.
You can't do that with Euler angles. You can with matrices, and you can with quaternions (as they're just the rotation part of a matrix). But you can't do it by pretending they are Euler angles.
This is done by not storing angles at all. Instead, you just have a quaternion which represents the current orientation of the object. When you decide to apply a rotation to it (of some angle by some axis), you construct a quaternion that represents that rotation by an angle around that axis. Then you right-multiply that quaternion with the current orientation quaternion, producing a new current orientation.
When you render the object, you use the current orientation as... the orientation.
Quaternions represent orientations around 3D compound axes.
But they can also represent 'delta-rotations'.
To 'rotate an orientation', we need an orientation (a quat), and a rotation (also a quat), and we multiply them together, resulting in (you guessed it) a quat.
You noticed they are not commutative, that means the order we multiply them in absolutely matters, just like for matrices.
The order tends to depend on the implementation of your math library, but really, there's only two possible ways to do it, so it shouldn't take you too long to figure out which one is the right one - if things are 'orbiting' instead of 'rotating', then you have them the wrong way around.
For your example of yaw and pitch, I would build my 'delta-rotation' quaternion from yaw, pitch and roll angles, with roll set to zero, and then apply that to my 'orientation' quaternion, rather than doing the rotations one axis at a time.
Related
I am working on a simulation of plane movement. For now, I used Euler angles to transform "body frame" to "world frame" and it works fine.
Recently I learned about quaternions and their advantages over the rotation matrix (gimbal lock) and I tried to implement it using yaw/pitch/roll angles from the simulator.
Quaternion
If I understand correctly the quaternion represents two things. It has an x, y, and z component, which represents the axis about which a rotation will occur. It also has a w component, which represents the amount of rotation which will occur about this axis. In short, a vector, and a float. A quaternion can be represented as 4 element vector:
q=[w,x,y,z]
To calculate result (after full rotation) equation is using:
p'=qpq'
where:
p=[0,x,y,z]-direction vector
q=[w,x,y,z]-rotation
q'=[w,-x,-y,-z]
Algorithm
Create quaternion:
Using wikipedia I create quaternion by rotating around 3 axes (q):
Quaterniond toQuaternion(double yaw, double pitch, double roll) // yaw (Z), pitch (Y), roll (X)
{
//Degree to radius:
yaw = yaw * M_PI / 180;
pitch = pitch * M_PI / 180;
roll = roll * M_PI / 180;
// Abbreviations for the various angular functions
double cy = cos(yaw * 0.5);
double sy = sin(yaw * 0.5);
double cp = cos(pitch * 0.5);
double sp = sin(pitch * 0.5);
double cr = cos(roll * 0.5);
double sr = sin(roll * 0.5);
Quaterniond q;
q.w = cy * cp * cr + sy * sp * sr;
q.x = cy * cp * sr - sy * sp * cr;
q.y = sy * cp * sr + cy * sp * cr;
q.z = sy * cp * cr - cy * sp * sr;
return q;
}
Define plane direction (heading) vector:
p = [0,1,0,0]
Calculate Hamilton product:
p'=qpq'
q'= [w, -qx, -qy, -qz]
p' = (H(H(q, p), q')
Quaterniond HamiltonProduct(Quaterniond u, Quaterniond v)
{
Quaterniond result;
result.w = u.w*v.w - u.x*v.x - u.y*v.y - u.z*v.z;
result.x = u.w*v.x + u.x*v.w + u.y*v.z - u.z*v.y;
result.y = u.w*v.y - u.x*v.z + u.y*v.w + u.z*v.x;
result.z = u.w*v.z + u.x*v.y - u.y*v.x + u.z*v.w;
return result;
}
Result
My result will be a vector:
v=[p'x,p'y,p'z]
It works fine but the same as Euler angle rotation (gimbal lock). Is it because I use also euler angles here? I don't really see how it should work without rotation around 3 axes. Should I rotate around every axis separately?
I will be grateful for any advice and help with understanding this problem.
EDIT (how application works)
1. My application based on data streaming, it means that after every 1ms it checks if there are new data (new orientation of plane).
Example:
At the begging pitch/roll/yaw = 0, after 1ms yaw is changed by 10 degree so application reads pitch=0, roll=0, yaw = 10. After next 1ms yaw is changed again by 20 degrees. So input data will look like this: pitch=0, roll=0, yaw = 30.
2. Create direction quaternion - p
At the begging, I define that direction (head) of my plane is on X axis. So my local direction is
v=[1,0,0]
in quaternion (my p) is
p=[0,1,0,0]
Vector3 LocalDirection, GlobalDirection; //head
Quaterniond p,P,q, Q, pq; //P = p', Q=q'
LocalDirection.x = 1;
LocalDirection.y = 0;
LocalDirection.z = 0;
p.w = 0;
p.x = direction.x;
p.y = direction.y;
p.z = direction.z;
3. Create rotation
After every 1ms I check the rotation angles (Euler) from data streaming and calculate q using toQuaternion
q = toQuaternion(yaw, pitch, roll); // create quaternion after rotation
Q.w = q.w;
Q.x = -q.x;
Q.y = -q.y;
Q.z = -q.z;
4. Calculate "world direction"
Using Hamilton product I calculate quaternion after rotation which is my global direction:
pq = HamiltonProduct(q, p);
P = HamiltonProduct(pq, Q);
GlobalDirection.x = P.x;
GlobalDirection.y = P.y;
GlobalDirection.z = P.z;
5. Repeat 3-4 every 1ms
It seems that your simulation uses Euler angles for rotating objects each frame. You then convert those angles to quaternions afterwards. That won't solve gimbal lock.
Gimbal lock can happen anytime when you add Euler angles to Euler angles. It's not enough to solve this when going from local space to world space. You also need your simulation to use quaternions between frames.
Basically everytime your object is changing its rotation, convert the current rotation to a quaternion, multiply in the new rotation delta, and convert the result back to Euler angles or whatever you use to store rotations.
I'd recommend rewriting your application to use and story only quaternions. Whenever a user does input or some other logic of your game wants to rotate something, you immediately convert that input into a quaternion and feed it into the simulation.
With your toQuaternion and HamiltonProduct you have all the tools you need for that.
EDIT In response to your edit explaining how your application works.
At the begging pitch/roll/yaw = 0, after 1ms yaw is changed by 10 degree so application reads pitch=0, roll=0, yaw = 10. After next 1ms yaw is changed again by 20 degrees. So input data will look like this: pitch=0, roll=0, yaw = 30.
That is where gimbal lock happens. You convert to quaternions after you calculate the rotations. That is wrong. You need to use quaternions in this very first step. Don't do after 1ms yaw is changed by 10 degree so application reads pitch=0, roll=0, yaw = 10, do this:
Store the rotation as quaternion, not as euler angles;
Convert the 10 degree yaw turn into a quaternion;
Multiply the stored quaternion and the 10 degree yaw quaternion;
Store the result.
To clarify: Your steps 2, 3 and 4 are fine. The problem is in step 1.
On a side note, this:
It has an x, y, and z component, which represents the axis about which a rotation will occur. It also has a w component, which represents the amount of rotation which will occur about this axis
is not exactly correct. The components of a quaternion aren't directly axis and angle, they are sin(angle/2) * axis and cos(angle/2) (which is what your toQuaternion method produces). This is important as it gives you nice unit quaternions that form a 4D sphere where every point on the surface (surspace?) represents a rotation in 3D space, beautifully allowing for smooth interpolations between any two rotations.
This question already has answers here:
Quaternion rotation without Euler angles
(3 answers)
Closed 4 years ago.
So I'm writing a program where objects move around spacesim-style, in order to learn how to move things smoothly through 3D space. After messing around with Euler angles a bit, it seems they aren't really appropriate for free-form 3D movement in arbitrary directions, so I decided to move on to what seems to be best for the job - quaternions. I intend for the object to rotate around its local X-Y-Z axes at all times, never around the global X-Y-Z axes.
I've tried to implement a system of rotation using quaternions, but something isn't working. When rotating the object along a single axis, if no previous rotations were undertaken, the thing rotates fine along a given axis. However, when applying one rotation after another has been performed, the second rotation is not always along the local axis it's supposed to be rotating along - for instance, after a rotation of about 90° around the Z axis, a rotation around the Y axis still takes place around the global Y axis, rather than the new local Y axis which is aligned with the global X axis.
Huh. So let's go through this step by step. The mistake must be in here somewhere.
STEP 1 - Capture Input
I figured it would be best to use Euler angles (or a Pitch-Yaw-Roll scheme) for capturing player input. At the moment, arrow keys control Pitch and Yaw, whereas Q and E control Roll. I capture player input thus (I am using SFML 1.6):
///SPEEDS
float ForwardSpeed = 0.05;
float TurnSpeed = 0.5;
//Rotation
sf::Vector3<float> Rotation;
Rotation.x = 0;
Rotation.y = 0;
Rotation.z = 0;
//PITCH
if (m_pApp->GetInput().IsKeyDown(sf::Key::Up) == true)
{
Rotation.x -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Down) == true)
{
Rotation.x += TurnSpeed;
}
//YAW
if (m_pApp->GetInput().IsKeyDown(sf::Key::Left) == true)
{
Rotation.y -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Right) == true)
{
Rotation.y += TurnSpeed;
}
//ROLL
if (m_pApp->GetInput().IsKeyDown(sf::Key::Q) == true)
{
Rotation.z -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::E) == true)
{
Rotation.z += TurnSpeed;
}
//Translation
sf::Vector3<float> Translation;
Translation.x = 0;
Translation.y = 0;
Translation.z = 0;
//Move the entity
if (Rotation.x != 0 ||
Rotation.y != 0 ||
Rotation.z != 0)
{
m_Entity->ApplyForce(Translation, Rotation);
}
m_Entity is the thing I'm trying to rotate. It also contains the quaternion and rotation matrices representing the object's rotation.
STEP 2 - Update quaternion
I'm not 100% sure this is the way it's supposed to be done, but this is what I tried doing in Entity::ApplyForce():
//Rotation
m_Rotation.x += Rotation.x;
m_Rotation.y += Rotation.y;
m_Rotation.z += Rotation.z;
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(m_Rotation.x, m_Rotation.y, m_Rotation.z);// * m_qRotation;
m_qRotation.RotationMatrix(m_RotationMatrix);
As you can see, I'm not sure whether it's best to just build a new quaternion from updated Euler angles, or whether I'm supposed to multiply the quaternion representing the change with the quaternion representing the overall current rotation, which is the impression I got when reading this guide. If the latter, my code would look like this:
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(Rotation.x, Rotation.y, Rotation.z) * m_qRotation;
m_Rotation is the object's current rotation stored in PYR format; Rotation is the change demanded by player input. Either way, though, the problem might be in my implementation of my Quaternion class. Here is the whole thing:
Quaternion::Quaternion(float Pitch, float Yaw, float Roll)
{
float Pi = 4 * atan(1);
//Set the values, which came in degrees, to radians for C++ trig functions
float rYaw = Yaw * Pi / 180;
float rPitch = Pitch * Pi / 180;
float rRoll = Roll * Pi / 180;
//Components
float C1 = cos(rYaw / 2);
float C2 = cos(rPitch / 2);
float C3 = cos(rRoll / 2);
float S1 = sin(rYaw / 2);
float S2 = sin(rPitch / 2);
float S3 = sin(rRoll / 2);
//Create the final values
a = ((C1 * C2 * C3) - (S1 * S2 * S3));
x = (S1 * S2 * C3) + (C1 * C2 * S3);
y = (S1 * C2 * C3) + (C1 * S2 * S3);
z = (C1 * S2 * C3) - (S1 * C2 * S3);
}
//Overload the multiplier operator
Quaternion Quaternion::operator* (Quaternion OtherQuat)
{
float A = (OtherQuat.a * a) - (OtherQuat.x * x) - (OtherQuat.y * y) - (OtherQuat.z * z);
float X = (OtherQuat.a * x) + (OtherQuat.x * a) + (OtherQuat.y * z) - (OtherQuat.z * y);
float Y = (OtherQuat.a * y) - (OtherQuat.x * z) - (OtherQuat.y * a) - (OtherQuat.z * x);
float Z = (OtherQuat.a * z) - (OtherQuat.x * y) - (OtherQuat.y * x) - (OtherQuat.z * a);
Quaternion NewQuat = Quaternion(0, 0, 0);
NewQuat.a = A;
NewQuat.x = X;
NewQuat.y = Y;
NewQuat.z = Z;
return NewQuat;
}
//Calculates a rotation matrix and fills Matrix with it
void Quaternion::RotationMatrix(GLfloat* Matrix)
{
//Column 1
Matrix[0] = (a*a) + (x*x) - (y*y) - (z*z);
Matrix[1] = (2*x*y) + (2*a*z);
Matrix[2] = (2*x*z) - (2*a*y);
Matrix[3] = 0;
//Column 2
Matrix[4] = (2*x*y) - (2*a*z);
Matrix[5] = (a*a) - (x*x) + (y*y) - (z*z);
Matrix[6] = (2*y*z) + (2*a*x);
Matrix[7] = 0;
//Column 3
Matrix[8] = (2*x*z) + (2*a*y);
Matrix[9] = (2*y*z) - (2*a*x);
Matrix[10] = (a*a) - (x*x) - (y*y) + (z*z);
Matrix[11] = 0;
//Column 4
Matrix[12] = 0;
Matrix[13] = 0;
Matrix[14] = 0;
Matrix[15] = 1;
}
There's probably something in there to make somebody wiser than me cringe, but I can't see it. For converting from Euler angles to a quaternion, I used the "first method" according to this source, which also seems to suggest that the equation automatically creates a unit quaternion ("clearly normalized"). For multiplying quaternions, I again drew on this C++ guide.
STEP 3 - Deriving a rotation matrix from the quaternion
Once that is done, as per R. Martinho Fernandes' answer to this question, I try to build a rotation matrix from the quaternion and use that to update my object's rotation, using the above Quaternion::RotationMatrix() code in the following line:
m_qRotation.RotationMatrix(m_RotationMatrix);
I should note that m_RotationMatrix is GLfloat m_RotationMatrix[16], as per the required parameters of glMultMatrix, which I believe I am supposed to use later on when displaying the object. It is initialized as:
m_RotationMatrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
Which I believe is the "neutral" OpenGL rotation matrix (every 4 values together represent a column, correct? Again, I get this from the glMultMatrix page).
STEP 4 - Display!
Finally, we get to the function run each cycle for the object that is supposed to display it.
glPushMatrix();
glTranslatef(m_Position.x, m_Position.y, m_Position.z);
glMultMatrixf(m_RotationMatrix);
//glRotatef(m_Rotation.y, 0.0, 1.0, 0.0);
//glRotatef(m_Rotation.z, 0.0, 0.0, 1.0);
//glRotatef(m_Rotation.x, 1.0, 0.0, 0.0);
//glRotatef(m_qRotation.a, m_qRotation.x, m_qRotation.y, m_qRotation.z);
//[...] various code displaying the object's VBO
glPopMatrix();
I have left my previous failed attempts there, commented out.
Conclusion - Sad panda
That is the conclusion of the life cycle of player input, from cradle to OpenGL-managed grave.
I've obviously not understood something, since the behavior I get isn't the behavior I want or expect. But I'm not particularly experienced with matrix math or quaternions, so I don't have the insight required to see the error in my ways.
Can somebody help me out here?
All you have done is effectively implement Euler angles with quaternions. That's not helping.
The problem with Euler angles is that, when you compute the matrices, each angle is relative to the rotation of the matrix that came before it. What you want is to take an object's current orientation, and apply a rotation along some axis, producing a new orientation.
You can't do that with Euler angles. You can with matrices, and you can with quaternions (as they're just the rotation part of a matrix). But you can't do it by pretending they are Euler angles.
This is done by not storing angles at all. Instead, you just have a quaternion which represents the current orientation of the object. When you decide to apply a rotation to it (of some angle by some axis), you construct a quaternion that represents that rotation by an angle around that axis. Then you right-multiply that quaternion with the current orientation quaternion, producing a new current orientation.
When you render the object, you use the current orientation as... the orientation.
Quaternions represent orientations around 3D compound axes.
But they can also represent 'delta-rotations'.
To 'rotate an orientation', we need an orientation (a quat), and a rotation (also a quat), and we multiply them together, resulting in (you guessed it) a quat.
You noticed they are not commutative, that means the order we multiply them in absolutely matters, just like for matrices.
The order tends to depend on the implementation of your math library, but really, there's only two possible ways to do it, so it shouldn't take you too long to figure out which one is the right one - if things are 'orbiting' instead of 'rotating', then you have them the wrong way around.
For your example of yaw and pitch, I would build my 'delta-rotation' quaternion from yaw, pitch and roll angles, with roll set to zero, and then apply that to my 'orientation' quaternion, rather than doing the rotations one axis at a time.
So I am using a quaternion to create a segment of two points in 3D space, and trying to recompute a similar quaternion later on (one representing the same vector through space; I am aware that the segment's rotation around itself is undefined). I am creating the segment as such:
sf::Vector3<float> Start(0, 0, 0);
sf::Vector3<float> End = Start;
//Create a vector from the start to the end
sf::Vector3<float> Translation = Orientation.MultVect(sf::Vector3<float>(0, 1, 0));
//Add that vector onto the start position
End.x += Translation.x * Length;
End.y += Translation.y * Length;
End.z += Translation.z * Length;
Where Orientation::MultVect() looks like this:
sf::Vector3<float> Quaternion::MultVect(sf::Vector3<float> Vector)
{
//From http://www.idevgames.com/articles/quaternions
Quaternion VectorQuat = Quaternion();
VectorQuat.x = Vector.x;
VectorQuat.y = Vector.y;
VectorQuat.z = Vector.z;
VectorQuat.w = 0.0;
Quaternion Inverse = (*this);
Inverse.Invert();
Quaternion Result = Inverse * VectorQuat * (*this);
sf::Vector3<float> ResultVector;
ResultVector.x = Result.x;
ResultVector.y = Result.y;
ResultVector.z = Result.z;
return ResultVector;
}
Now this function seems to work rather well in other contexts, so I don't think the problem is here, but you never know. I should also mention that the point ends up where I expect it to, given the Quaternion I feed if (which I construct from Euler angles, sometimes with multiplication with other quaternions).
The problem appears, to me, to lie in recomputing the quaternion from Start and End. To do so, I use this function, which works well when orienting objects in the scene towards other objects (unless the objects in question are along the exact same Y axis, in which case I get quaternions with NaN values). Here is how I do that:
Quaternion Quaternion::FromLookVector(sf::Vector3<float> FromPoint, sf::Vector3<float> ToPoint)
{
///Based on this post:
///http://stackoverflow.com/questions/13014973/quaternion-rotate-to
//Get the normalized vector from origin position to ToPoint
sf::Vector3<double> VectorTo(ToPoint.x - FromPoint.x,
ToPoint.y - FromPoint.y,
ToPoint.z - FromPoint.z);
//Get the length of VectorTo
double VectorLength = sqrt(VectorTo.x*VectorTo.x +
VectorTo.y*VectorTo.y +
VectorTo.z*VectorTo.z);
//Normalize VectorTo
VectorTo.x /= -VectorLength;
VectorTo.y /= -VectorLength;
VectorTo.z /= -VectorLength;
//Define a unit up vector
sf::Vector3<double> VectorUp(0, -1, 0);
//The X axis is the cross product of both
//Get the cross product as the axis of rotation
sf::Vector3<double> AxisX(VectorTo.y*VectorUp.z - VectorTo.z*VectorUp.y,
VectorTo.z*VectorUp.x - VectorTo.x*VectorUp.z,
VectorTo.x*VectorUp.y - VectorTo.y*VectorUp.x);
//Normalize the axis
//Get the length of VectorTo
double AxisXLength = sqrt(AxisX.x*AxisX.x +
AxisX.y*AxisX.y +
AxisX.z*AxisX.z);
//Normalize VectorTo
AxisX.x /= AxisXLength;
AxisX.y /= AxisXLength;
AxisX.z /= AxisXLength;
//Get the adjusted Y vector
//Get the cross product of the other two axes
sf::Vector3<double> AxisY(VectorTo.y*AxisX.z - VectorTo.z*AxisX.y,
VectorTo.z*AxisX.x - VectorTo.x*AxisX.z,
VectorTo.x*AxisX.y - VectorTo.y*AxisX.x);
//Normalize the axis
//Get the length of VectorTo
double AxisYLength = sqrt(AxisY.x*AxisY.x +
AxisY.y*AxisY.y +
AxisY.z*AxisY.z);
//Normalize VectorTo
AxisY.x /= AxisYLength;
AxisY.y /= AxisYLength;
AxisY.z /= AxisYLength;
//A matrix representing the Thing's orientation
GLfloat RotationMatrix[16] = {(float)AxisX.x,
(float)AxisX.y,
(float)AxisX.z,
0,
(float)AxisY.x,
(float)AxisY.y,
(float)AxisY.z,
0,
(float)VectorTo.x,
(float)VectorTo.y,
(float)VectorTo.z,
0,
0,
0,
0,
1};
Quaternion LookQuat = Quaternion::FromMatrix(RotationMatrix);
//Reset the quaternion orientation
return LookQuat;
}
So when I compute the segments, I also check what their reconstructed values would be, like this:
sf::Vector3<float> Start(0, 0, 0);
sf::Vector3<float> End = Start;
//Create a vector from the start to the end
sf::Vector3<float> Translation = Orientation.MultVect(sf::Vector3<float>(0, 1, 0));
//Add that vector onto the start position
End.x += Translation.x * Length;
End.y += Translation.y * Length;
End.z += Translation.z * Length;
std::cout << "STATIC END (";
std::cout << End.x << ",";
std::cout << End.y << ",";
std::cout << End.z << ")\n";
///TEST
Quaternion Reconstructed = Quaternion::FromLookVector(Start, End);
Translation = Reconstructed.MultVect(sf::Vector3<float>(0, 1, 0));
sf::Vector3<float> TestEnd = Start;
TestEnd.x += Translation.x * Length;
TestEnd.y += Translation.y * Length;
TestEnd.z += Translation.z * Length;
std::cout << "RECONSTRUCTED END (";
std::cout << TestEnd.x << ",";
std::cout << TestEnd.y << ",";
std::cout << TestEnd.z << ")\n";
And the two don't match up. For example, if the static end point is (0,14.3998,0.0558498), then the recomputed point is (0,8.05585,-6.39976). The two should be identical, though. The undefined part of the rotation shouldn't change the position of the end point, only the roll (or Z-rotation, or whatever you want to call it), which, since this is a segment, doesn't matter.
Note that when I end up using this for things other than simple segments, the roll will matter, which is why I use an up vector to make sure the objects I place along these segments will always have their tops facing upwards as much as possible (objects looking straight up or down can have a special arbitrary roll determined separately, if need be). Another goal is creating multiple segments strung together, each rotating relative to the orientation of the one that came before it rather than rotating relative to global space.
So what am I doing wrong here? Why can't I recompute a second quaternion that performs the same translation as the first one?
I'm not entirely sure how you're calculating the 'rotating' quaternion between the two vectors, but I'm pretty sure it's very cumbersome. At least, if I understand you correctly, you have 'look' vectors which point in some direction, and the object 'looks' along that direction from the origin (0,0,0), correct?.
If the above is the case, it should not be too difficult. One thing I find quite peculiar though is that your quaternion - vector multiplication seems to be in reverse order. I have quaternion * vector defined as:
quat qt = *this * quat(0, vec.x, vec.y, vec.z) * inverse();
return vec3(qt.x, qt.y, qt.z);
In which the quat constructor is defined as quat(w, x, y, z) and the inverse() method returns a copy. Inverse is equal to conjugate, and it is defined as (w, -x, -y, -z). BUT, for this to be true your quaternions have to be normalized, only then will they actually represent an orientation (and only then will the inverse equal the conjugate). Then I have quaternion multiplication defined as follows:
// This describes A * B (not communative!)
w = A.w * B.w - A.x * B.x - A.y * B.y - A.z * B.z;
x = A.w * B.x + A.x * B.w + A.y * B.z - A.z * B.y;
y = A.w * B.y + A.y * B.w + A.z * B.x - A.x * B.z;
z = A.w * B.z + A.z * B.w + A.x * B.y - A.y * B.x;
With that out of the way, you want to be able to construct a quaternion from 'angle-axis'. Meaning it should take an axis of rotation, and an angle to rotate around that axis (in radians). I shall just give you that algorithm, as it doesn't make much sense intuitively:
// if axis is already unit length, remove the division
double halfAngle = angle * 0.5f; // In radians
double scale = sin(halfAngle) / axis.magnitude();
w = cos(halfAngle);
x = axis.x * scale;
y = axis.y * scale;
z = axis.z * scale;
So now we just have to calculate an axis to rotate around, and how much we want to rotate around it, in radians. At first sight this might seem complex, but it is just a case of understanding what is going on. You have two vectors, A and B. You want to calculate a quaternion which describes a rotation FROM A, TO B. To get the axis to rotate around, we just want a perpendicular axis to both, obviously this would be done by taking the cross product. If you're using a right handed coordinate system, it would be:
axis = A × B
If you're using a left handed coordinate system, I think you should just inverse the order, but don't take my word on that. Now to get the angle between the two vectors. This can very simply be done by taking the dot product. The only catch is that you have to normalize both vectors, so they have length 1, and won't alter the outcome of the dot product. This way the dot product will return the cosine of the angle, so to get the actual angle we can do:
angle = acos(normalize(A) * normalize(B))
The multiplication sign stands for the dot product of course. Now we just plug the axis and angle in the algorithm I gave you above, and we have a quaternion describing a 'rotation' from look vector A to look vector B. Now if the vectors point in the exact same direction, it would be unwise to apply the algorithm, as the axis will be (0,0,0). If you look at the algorithm I hope you see that will either try to divide by zero or simply output all zeros. So whenever I apply that algorithm I first check if the axis is not all zeros.
The formula you're currently using seems very strange and inefficient to me. I don't really understand why you're first computing a matrix either, computing a quaternion from a matrix is quite an expensive computation. In fact I believe computing the opposite, a matrix from a quaternion, is even faster.
Anyway, good luck getting it to work!
This question already has answers here:
Quaternion rotation without Euler angles
(3 answers)
Closed 4 years ago.
So I'm writing a program where objects move around spacesim-style, in order to learn how to move things smoothly through 3D space. After messing around with Euler angles a bit, it seems they aren't really appropriate for free-form 3D movement in arbitrary directions, so I decided to move on to what seems to be best for the job - quaternions. I intend for the object to rotate around its local X-Y-Z axes at all times, never around the global X-Y-Z axes.
I've tried to implement a system of rotation using quaternions, but something isn't working. When rotating the object along a single axis, if no previous rotations were undertaken, the thing rotates fine along a given axis. However, when applying one rotation after another has been performed, the second rotation is not always along the local axis it's supposed to be rotating along - for instance, after a rotation of about 90° around the Z axis, a rotation around the Y axis still takes place around the global Y axis, rather than the new local Y axis which is aligned with the global X axis.
Huh. So let's go through this step by step. The mistake must be in here somewhere.
STEP 1 - Capture Input
I figured it would be best to use Euler angles (or a Pitch-Yaw-Roll scheme) for capturing player input. At the moment, arrow keys control Pitch and Yaw, whereas Q and E control Roll. I capture player input thus (I am using SFML 1.6):
///SPEEDS
float ForwardSpeed = 0.05;
float TurnSpeed = 0.5;
//Rotation
sf::Vector3<float> Rotation;
Rotation.x = 0;
Rotation.y = 0;
Rotation.z = 0;
//PITCH
if (m_pApp->GetInput().IsKeyDown(sf::Key::Up) == true)
{
Rotation.x -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Down) == true)
{
Rotation.x += TurnSpeed;
}
//YAW
if (m_pApp->GetInput().IsKeyDown(sf::Key::Left) == true)
{
Rotation.y -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Right) == true)
{
Rotation.y += TurnSpeed;
}
//ROLL
if (m_pApp->GetInput().IsKeyDown(sf::Key::Q) == true)
{
Rotation.z -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::E) == true)
{
Rotation.z += TurnSpeed;
}
//Translation
sf::Vector3<float> Translation;
Translation.x = 0;
Translation.y = 0;
Translation.z = 0;
//Move the entity
if (Rotation.x != 0 ||
Rotation.y != 0 ||
Rotation.z != 0)
{
m_Entity->ApplyForce(Translation, Rotation);
}
m_Entity is the thing I'm trying to rotate. It also contains the quaternion and rotation matrices representing the object's rotation.
STEP 2 - Update quaternion
I'm not 100% sure this is the way it's supposed to be done, but this is what I tried doing in Entity::ApplyForce():
//Rotation
m_Rotation.x += Rotation.x;
m_Rotation.y += Rotation.y;
m_Rotation.z += Rotation.z;
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(m_Rotation.x, m_Rotation.y, m_Rotation.z);// * m_qRotation;
m_qRotation.RotationMatrix(m_RotationMatrix);
As you can see, I'm not sure whether it's best to just build a new quaternion from updated Euler angles, or whether I'm supposed to multiply the quaternion representing the change with the quaternion representing the overall current rotation, which is the impression I got when reading this guide. If the latter, my code would look like this:
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(Rotation.x, Rotation.y, Rotation.z) * m_qRotation;
m_Rotation is the object's current rotation stored in PYR format; Rotation is the change demanded by player input. Either way, though, the problem might be in my implementation of my Quaternion class. Here is the whole thing:
Quaternion::Quaternion(float Pitch, float Yaw, float Roll)
{
float Pi = 4 * atan(1);
//Set the values, which came in degrees, to radians for C++ trig functions
float rYaw = Yaw * Pi / 180;
float rPitch = Pitch * Pi / 180;
float rRoll = Roll * Pi / 180;
//Components
float C1 = cos(rYaw / 2);
float C2 = cos(rPitch / 2);
float C3 = cos(rRoll / 2);
float S1 = sin(rYaw / 2);
float S2 = sin(rPitch / 2);
float S3 = sin(rRoll / 2);
//Create the final values
a = ((C1 * C2 * C3) - (S1 * S2 * S3));
x = (S1 * S2 * C3) + (C1 * C2 * S3);
y = (S1 * C2 * C3) + (C1 * S2 * S3);
z = (C1 * S2 * C3) - (S1 * C2 * S3);
}
//Overload the multiplier operator
Quaternion Quaternion::operator* (Quaternion OtherQuat)
{
float A = (OtherQuat.a * a) - (OtherQuat.x * x) - (OtherQuat.y * y) - (OtherQuat.z * z);
float X = (OtherQuat.a * x) + (OtherQuat.x * a) + (OtherQuat.y * z) - (OtherQuat.z * y);
float Y = (OtherQuat.a * y) - (OtherQuat.x * z) - (OtherQuat.y * a) - (OtherQuat.z * x);
float Z = (OtherQuat.a * z) - (OtherQuat.x * y) - (OtherQuat.y * x) - (OtherQuat.z * a);
Quaternion NewQuat = Quaternion(0, 0, 0);
NewQuat.a = A;
NewQuat.x = X;
NewQuat.y = Y;
NewQuat.z = Z;
return NewQuat;
}
//Calculates a rotation matrix and fills Matrix with it
void Quaternion::RotationMatrix(GLfloat* Matrix)
{
//Column 1
Matrix[0] = (a*a) + (x*x) - (y*y) - (z*z);
Matrix[1] = (2*x*y) + (2*a*z);
Matrix[2] = (2*x*z) - (2*a*y);
Matrix[3] = 0;
//Column 2
Matrix[4] = (2*x*y) - (2*a*z);
Matrix[5] = (a*a) - (x*x) + (y*y) - (z*z);
Matrix[6] = (2*y*z) + (2*a*x);
Matrix[7] = 0;
//Column 3
Matrix[8] = (2*x*z) + (2*a*y);
Matrix[9] = (2*y*z) - (2*a*x);
Matrix[10] = (a*a) - (x*x) - (y*y) + (z*z);
Matrix[11] = 0;
//Column 4
Matrix[12] = 0;
Matrix[13] = 0;
Matrix[14] = 0;
Matrix[15] = 1;
}
There's probably something in there to make somebody wiser than me cringe, but I can't see it. For converting from Euler angles to a quaternion, I used the "first method" according to this source, which also seems to suggest that the equation automatically creates a unit quaternion ("clearly normalized"). For multiplying quaternions, I again drew on this C++ guide.
STEP 3 - Deriving a rotation matrix from the quaternion
Once that is done, as per R. Martinho Fernandes' answer to this question, I try to build a rotation matrix from the quaternion and use that to update my object's rotation, using the above Quaternion::RotationMatrix() code in the following line:
m_qRotation.RotationMatrix(m_RotationMatrix);
I should note that m_RotationMatrix is GLfloat m_RotationMatrix[16], as per the required parameters of glMultMatrix, which I believe I am supposed to use later on when displaying the object. It is initialized as:
m_RotationMatrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
Which I believe is the "neutral" OpenGL rotation matrix (every 4 values together represent a column, correct? Again, I get this from the glMultMatrix page).
STEP 4 - Display!
Finally, we get to the function run each cycle for the object that is supposed to display it.
glPushMatrix();
glTranslatef(m_Position.x, m_Position.y, m_Position.z);
glMultMatrixf(m_RotationMatrix);
//glRotatef(m_Rotation.y, 0.0, 1.0, 0.0);
//glRotatef(m_Rotation.z, 0.0, 0.0, 1.0);
//glRotatef(m_Rotation.x, 1.0, 0.0, 0.0);
//glRotatef(m_qRotation.a, m_qRotation.x, m_qRotation.y, m_qRotation.z);
//[...] various code displaying the object's VBO
glPopMatrix();
I have left my previous failed attempts there, commented out.
Conclusion - Sad panda
That is the conclusion of the life cycle of player input, from cradle to OpenGL-managed grave.
I've obviously not understood something, since the behavior I get isn't the behavior I want or expect. But I'm not particularly experienced with matrix math or quaternions, so I don't have the insight required to see the error in my ways.
Can somebody help me out here?
All you have done is effectively implement Euler angles with quaternions. That's not helping.
The problem with Euler angles is that, when you compute the matrices, each angle is relative to the rotation of the matrix that came before it. What you want is to take an object's current orientation, and apply a rotation along some axis, producing a new orientation.
You can't do that with Euler angles. You can with matrices, and you can with quaternions (as they're just the rotation part of a matrix). But you can't do it by pretending they are Euler angles.
This is done by not storing angles at all. Instead, you just have a quaternion which represents the current orientation of the object. When you decide to apply a rotation to it (of some angle by some axis), you construct a quaternion that represents that rotation by an angle around that axis. Then you right-multiply that quaternion with the current orientation quaternion, producing a new current orientation.
When you render the object, you use the current orientation as... the orientation.
Quaternions represent orientations around 3D compound axes.
But they can also represent 'delta-rotations'.
To 'rotate an orientation', we need an orientation (a quat), and a rotation (also a quat), and we multiply them together, resulting in (you guessed it) a quat.
You noticed they are not commutative, that means the order we multiply them in absolutely matters, just like for matrices.
The order tends to depend on the implementation of your math library, but really, there's only two possible ways to do it, so it shouldn't take you too long to figure out which one is the right one - if things are 'orbiting' instead of 'rotating', then you have them the wrong way around.
For your example of yaw and pitch, I would build my 'delta-rotation' quaternion from yaw, pitch and roll angles, with roll set to zero, and then apply that to my 'orientation' quaternion, rather than doing the rotations one axis at a time.
so I've got a math function here that supposed to return a rotated point, and takes an original point, point to rotate around (origin) and radians to rotate it.
However it rotating only at half speed (aka 180 degree movement = 90 degree rotation)
sf::Vector2f RotatePoint(sf::Vector2f origin, sf::Vector2f point, float radian) {
float s = sin(radian);
float c = cos(radian);
// translate point back to origin:
point.x -= origin.x;
point.y -= origin.y;
// rotate point
float xnew = point.x * c - point.y * s;
float ynew = point.x * s + point.y * c;
// translate point back to global coords:
sf::Vector2f TranslatedPoint;
TranslatedPoint.x = xnew + origin.x;
TranslatedPoint.y = ynew + origin.y;
return TranslatedPoint;
}
The function looks ok to me. The rotation, in case you're wondering, is just multiplying a vector by the 2D Euclidean rotation matrix (http://en.wikipedia.org/wiki/Rotation_matrix ). The only errors I can think of are some misunderstanding of the usage of the function. E.g. that 2*PI radians = 360 degrees, or that the rotation is counterclockwise.
Your code seems fine. Do we agree that 180° in degrees is Pi in radians ?
The function is fine. However, you might want to fresh up on your linear algebra a bit: You basically compute
return rotation * (point - origin) + origin;
with rotation as the matrix consisting of cos(radian) on the diagonals and +/- sin(radian) on the off-diagonals. So, the whole function is a one-liner if you let your linear algebra library compute that matrix; and if you factor out the -origin part (remember, linear algebra is linear), it becomes:
return rotation * point + ( - rotation * origin + origin );
where the second part is point-invariant and can be precomputed.