3

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.

enter image description here

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

  1. 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;
}
  1. Define plane direction (heading) vector:

    p = [0,1,0,0]

  2. 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

begginer
  • 189
  • 2
  • 10

1 Answers1

3

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:

  1. Store the rotation as quaternion, not as euler angles;
  2. Convert the 10 degree yaw turn into a quaternion;
  3. Multiply the stored quaternion and the 10 degree yaw quaternion;
  4. 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.

Max Vollmer
  • 8,412
  • 9
  • 28
  • 43
  • Thanks for your comment but I am still not sure what is the difference between what you recommend and my algorithm. I have local (constant quaternion) with my plane direction - p[0,1,0,0] and every time when simulator change orientation of plane I create quaternion q (using toQuaternion) and by using p'=qpq' I calculate the new direction in world space. The problem is that I adding new Euler angles to the previous one, is it? Could you provide maybe an example (step by step) how it should work? – begginer Jun 13 '19 at 10:03
  • I can only give an answer to the question you ask. If you need help with your code, you should post your code. As long as I don't know what your application looks like, I also don't know what steps would be required for your application to behave correctly. – Max Vollmer Jun 13 '19 at 10:07
  • I added how my application works. If you could look at it I will be grateful :) – begginer Jun 13 '19 at 10:32
  • Thank you for your answer. So if I understand you correctly, based on an example which I put in "Edit", I should, first of all, create a "quaternion zero" of 0 rotation (using toQuaternion(0,0,0)) and get the result (p') as I described. Then when the yaw changes I have to convert 10 degree yaw to quaternion (toQuaternion(10,0,0)) and multiply with "quaternion zero" and get new p' using this new quaternion. And then if yaw changes again by 20 degree, I create again a new quaternion by putting 20 not 30 degree(!) (toQuaternion(20,0,0)) and multiply with the previous quaternion? – begginer Jun 13 '19 at 13:27
  • 1
    @begginer Exactly. – Max Vollmer Jun 13 '19 at 13:30
  • and I multiply quaternions by using "Hamilton Product" function, right? – begginer Jun 13 '19 at 13:36