Reputation: 93304
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:
Visual representation of my intuition/current understanding:
Upvotes: 4
Views: 4278
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
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
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