Vittorio Romeo
Vittorio Romeo

Reputation: 93304

GLM conversion from euler angles to quaternion and back does not hold

I am trying to convert the orientation of an OpenVR controller that I have stored as a glm::vec3 of Euler angles into a glm::fquat and back, but I get wildly different results and the in-game behavior is just wrong (hard to explain, but the orientation of the object behaves normally for a small range of angles, then flips in weird axes).

This is my conversion code:

// get `orientation` from OpenVR controller sensor data

const glm::vec3 eulerAnglesInDegrees{orientation[PITCH], orientation[YAW], orientation[ROLL]};
debugPrint(eulerAnglesInDegrees);

const glm::fquat quaternion{glm::radians(eulerAnglesInDegrees)};
const glm::vec3 result{glm::degrees(glm::eulerAngles(quaternion))};
debugPrint(result);

// `result` should represent the same orientation as `eulerAnglesInDegrees`

I would expect eulerAnglesInDegrees and result to either be the same or equivalent representations of the same orientation, but that is apparently not the case. These are some example values I get printed out:

39.3851 5.17816 3.29104 
39.3851 5.17816 3.29104 

32.7636 144.849 44.3845 
-147.236 35.1512 -135.616 

39.3851 5.17816 3.29104 
39.3851 5.17816 3.29104 

32.0103 137.415 45.1592 
-147.99 42.5846 -134.841 

As you can see above, for some orientation ranges the conversion is correct, but for others it is completely different.

What am I doing wrong?

I've looked at existing questions and attempted a few things, including trying out every possible rotation order listed here, conjugating the quaternion, and other random things like flipping pitch/yaw/roll. Nothing gave me the expected result.

How can I convert euler angles to quaternions and back, representing the original orientation, using glm?


Some more examples of discrepancies:

original:      4; 175;   26; 
computed:   -175;   4; -153; 
difference:  179; 171;  179; 

original:     -6; 173;   32; 
computed:    173;   6; -147; 
difference: -179; 167;  179; 

original:      9; 268;  -46; 
computed:   -170; -88;  133; 
difference:  179; 356; -179; 

original:    -27; -73;  266; 
computed:    -27; -73;  -93; 
difference:    0;   0;  359; 

original:    -33; 111;  205; 
computed:    146;  68;   25; 
difference: -179;  43;  180; 

I tried to find a pattern to fix the final computed results, but it doesn't seem like there's one easy to identify.


GIF + video of the behavior:

Video excerpt


Visual representation of my intuition/current understanding:

Visual diagram

Upvotes: 4

Views: 4278

Answers (3)

Vittorio Romeo
Vittorio Romeo

Reputation: 93304

Roughly following tony's advice and after some trial&error and pattern identification, I managed to figure out a way to restore the original values after the conversion.

  • ox, oy, and oz are the original pitch, yaw, and roll in degrees, before any conversion;

  • fx, fy, and fz are the new pitch, yaw, and roll in degrees, obtained after converting "Euler -> quaternion -> Euler" (via glm::degrees(glm::eulerAngles(glm::normalize(quaternion)))).

if (oy > 90.f)
{
    fx -= 180.f;
    fy -= 180.f;
    fy *= -1.f;
    fz += 180.f;

    if (ox > 0.f)
    {
        fx += 360.f;
    }
}

The above code seems to make the original angle values and the one after the conversion match exactly. While it answers the original question, it doesn't solve my actual issue... I was converting to a quaternion in order to smoothly interpolate to another angle. However, it seems that using glm::mix on the quaternion after the conversion results - again - in very unpredictable rotations.

Upvotes: 1

traversaro
traversaro

Reputation: 230

The definition of the any kind of 3 angles to represent a rotation is not only given by the order of the rotations, if they are extrinsic or extrinsic, but also which interval of angles you choose when you define the mapping of every element of the 3D Rotation Group to a tuple of 3 angles.

Unfortunately, it is common for software libraries to fail to explicit mention which subset of angles they support, so typically it is necessary to either test their behaviour or to direct inspect the source code. For a relevant issue regarding glm see https://github.com/g-truc/glm/issues/569, and see https://github.com/robotology/idyntree/pull/504 for a related discussion on another library on which I work.

In glm master, from a quick inspection of the code (https://github.com/g-truc/glm/blob/6543cc9ad1476dd62fbfbe3194fcf19412f0cbc0/glm/gtc/quaternion.inl#L10) and from the fact that in C++ asin image is roughly (-90.0, 90.0) and atan2 image is roughly (-180.0, 180.0), the assumed interval in glm seems to be roughly (-180.0, 180.0) x (-90.0, 90.0) x (-180.0, 180.0), so by limiting the second angle (the yaw, using the names that you are using) to (-90.0, 90.0). So, what you are seeing at the GLM level is basically a mapping from the provided angles to equivalent angles in the (-180.0, 180.0) x (-90.0, 90.0) x (-180.0, 180.0) range.

However, the fact that this angles are equivalent depends on how they are used, i.e. if you have a library that clamps the euler angles outside the used ranges, instead of converting it to equivalent angles, then you will obtain strange results. For this reason, I think it would be interesting to understand your problem to know how this angles are generated (the middle angles in particular seems to be part of the range (-90, 270) that is a strange, even if valid choice) and how they are interpreted to render the object in the visualization. Once you understand that, even if the rendering function works fine for angles in the original applications ranges, you can write a function to map "original application angles" to "GLM angles" and its inverse, that you can use for your original purpose.

Upvotes: 2

tony
tony

Reputation: 3887

32.7636 144.849 44.3845 -147.236 35.1512 -135.616

Those are the same. Left 33 or right 147. You are 180 from each other. Now look up 145 - that's past up that's 35 from horizon, your back is arched. Now roll to get your back to the sky.

If you need to use Euler, try to keep pitch in -90 to +90, and roll in -180 to +180;

if (pitch > 90) {
   pitch -= 90;
   yaw += 180;
   roll += 180;
}
if (roll > 180) {
   roll = 360 - roll;
}

or something like that.

Upvotes: 4

Related Questions