0

I have made a simple animation in blender where the object(Bone in this case) is rotated from 0 to 360 degrees within 40 keyframes I then exported that data(Each keyframe) to glTF which stores the rotation of each keyframe as quaternion. After that i read each keyframe from the file and converted the quaternion(I also normalized the quaternion before that just in case) to XYZ euler angles, however the results are very unexpected and confusing.

This is the output:

Quat XYZW: -6.584865E-8, -6.71794E-8, -4.853328E-15, 1.0
maps to Euler(XYZ) In radians: -1.316973E-7, -1.343588E-7, -1.8554003E-14 | In degrees: -7.545699778706759E-6, -7.69819215990934E-6, -1.0630660420387862E-12

Quat XYZW: -6.584723E-8, 0.0065628546, -4.321644E-10, 0.9999785 maps to Euler(XYZ) In radians: -1.316973E-7, 0.013125804, -1.8542322E-14 | In degrees: -7.545699778706759E-6, 0.7520531864073278, -1.0623967938537065E-12

Quat XYZW: -6.5826804E-8, 0.025757458, -1.6961029E-9, 0.99966824 maps to Euler(XYZ) In radians: -1.316973E-7, 0.051520616, -1.823185E-14 | In degrees: -7.545699778706759E-6, 2.9519138441784234, -1.0446080897377453E-12

Quat XYZW: -6.5742256E-8, 0.056824293, -3.741812E-9, 0.99838424 maps to Euler(XYZ) In radians: -1.3169732E-7, 0.113709845, -1.8772983E-14 | In degrees: -7.545700592928757E-6, 6.515094187466668, -1.0756126741124562E-12

Quat XYZW: -6.5525505E-8, 0.0989473, -6.515556E-9, 0.9950927 maps to Euler(XYZ) In radians: -1.3169729E-7, 0.19821896, -2.0836097E-14 | In degrees: -7.545698964484761E-6, 11.357109632076618, -1.1938204425972884E-12

Quat XYZW: -6.5091555E-8, 0.15120457, -9.956626E-9, 0.9885025 maps to Euler(XYZ) In radians: -1.3169729E-7, 0.3035735, -1.861474E-14 | In degrees: -7.545698964484761E-6, 17.3934797025985, -1.0665460355383746E-12

Quat XYZW: -6.43445E-8, 0.21251643, -1.3993929E-8, 0.9771575 maps to Euler(XYZ) In radians: -1.3169728E-7, 0.42829898, -2.1480152E-14 | In degrees: -7.545698150262762E-6, 24.539723923622546, -1.2307220789639523E-12

Quat XYZW: -6.31839E-8, 0.2815993, -1.8542945E-8, 0.9595321 maps to Euler(XYZ) In radians: -1.3169729E-7, 0.5709209, -2.1111827E-14 | In degrees: -7.545698964484761E-6, 32.71135712398392, -1.2096185822782078E-12

Quat XYZW: -6.15112E-8, 0.35693273, -2.350355E-8, 0.9341301 maps to Euler(XYZ) In radians: -1.3169728E-7, 0.7299645, -3.3372332E-14 | In degrees: -7.545698150262762E-6, 41.823884741007696, -1.9120937992588885E-12

Quat XYZW: -5.923645E-8, 0.43674588, -2.8759136E-8, 0.89958495 maps to Euler(XYZ) In radians: -1.3169728E-7, 0.90395635, -3.4464144E-14 | In degrees: -7.545698150262762E-6, 51.79288392900523, -1.9746500140954907E-12

Quat XYZW: -5.6284716E-8, 0.51902527, -3.4177116E-8, 0.854759 maps to Euler(XYZ) In radians: -1.3169728E-7, 1.0914207, -3.0811074E-14 | In degrees: -7.545698150262762E-6, 62.533796946051744, -1.765344512671221E-12

Quat XYZW: -5.260214E-8, 0.60155135, -3.9611358E-8, 0.79883415 maps to Euler(XYZ) In radians: -1.3169719E-7, 1.2908835, -1.2859481E-13 | In degrees: -7.545693264930771E-6, 73.9621787483438, -7.367939727230888E-12

Quat XYZW: -4.8161187E-8, 0.68195695, -4.490595E-8, 0.7313923 maps to Euler(XYZ) In radians: -1.3169702E-7, 1.50087, -3.0508725E-13 | In degrees: -7.54568349426679E-6, 85.99351599018624, -1.748021205393232E-11

Quat XYZW: -4.2964448E-8, 0.7578121, -4.990091E-8, 0.6524729 maps to Euler(XYZ) In radians: 3.1415925, 1.4216864, -3.1415927 | In degrees: 179.999991348578, 81.45663113589697, -180.00000500895632

Quat XYZW: -3.704698E-8, 0.82672364, -5.4438644E-8, 0.56260824 maps to Euler(XYZ) In radians: 3.1415925, 1.1950747, -3.1415927 | In degrees: 179.999991348578, 68.47273522183704, -180.00000500895632

Quat XYZW: -3.0476517E-8, 0.8864487, -5.837146E-8, 0.46282688 maps to Euler(XYZ) In radians: 3.1415925, 0.96236306, -3.1415927 | In degrees: 179.999991348578, 55.13934194304214, -180.00000500895632

Quat XYZW: -2.335154E-8, 0.9350088, -6.156907E-8, 0.35462454 maps to Euler(XYZ) In radians: 3.1415925, 0.72502494, -3.1415927 | In degrees: 179.999991348578, 41.54086902255905, -180.00000500895632

Quat XYZW: -1.5797392E-8, 0.97079647, -6.3925626E-8, 0.23990472 maps to Euler(XYZ) In radians: 3.1415925, 0.48453543, -3.1415927 | In degrees: 179.999991348578, 27.761834928724795, -180.00000500895632

Quat XYZW: -7.960307E-9, 0.9926662, -6.5365725E-8, 0.12088809 maps to Euler(XYZ) In radians: 3.1415925, 0.24236898, -3.1415927 | In degrees: 179.999991348578, 13.886719710062323, -180.00000500895632

Quat XYZW: 7.731664E-15, 1.0, -6.584865E-8, 2.3468012E-8 maps to Euler(XYZ) In radians: 3.1415925, 4.6936023E-8, -3.1415927 | In degrees: 179.999991348578, 2.6892360289340074E-6, -180.00000500895632

Quat XYZW: -7.920893E-9, -0.9927389, 6.5370514E-8, 0.120289244 maps to Euler(XYZ) In radians: 3.1415925, -0.24116248, -3.1415927 | In degrees: 179.999991348578, -13.81759221924627, -180.00000500895632

Quat XYZW: -1.5651457E-8, -0.9713415, 6.396153E-8, 0.23768824 maps to Euler(XYZ) In radians: 3.1415925, -0.47997037, -3.1415927 | In degrees: 179.999991348578, -27.500276249553743, -180.00000500895632

Quat XYZW: -2.305319E-8, -0.9367149, 6.168141E-8, 0.3500934 maps to Euler(XYZ) In radians: 3.1415925, -0.7153417, -3.1415927 | In degrees: 179.999991348578, -40.98605958646809, -180.00000500895632

Quat XYZW: -3.0003555E-8, -0.89016205, 5.861596E-8, 0.45564413 maps to Euler(XYZ) In radians: 3.1415925, -0.94619143, -3.1415927 | In degrees: 179.999991348578, -54.21277555571114, -180.00000500895632

Quat XYZW: -3.6401097E-8, -0.83331436, 5.4872622E-8, 0.55279946 maps to Euler(XYZ) In radians: 3.1415925, -1.1714399, 3.1415927 | In degrees: 179.999991348578, -67.1185614264228, 180.00000500895632

Quat XYZW: -4.216858E-8, -0.76805305, 5.0575256E-8, 0.6403863 maps to Euler(XYZ) In radians: 3.1415925, -1.3900026, -3.1415927 | In degrees: 179.999991348578, -79.64128296772347, -180.00000500895632

Quat XYZW: -4.7255202E-8, -0.6964209, 4.5858375E-8, 0.7176336 maps to Euler(XYZ) In radians: -1.3169738E-7, -1.5407987, 0.0 | In degrees: -7.545703849816751E-6, -88.28126053192533, 0.0

Quat XYZW: -5.163706E-8, -0.62053615, 4.0861455E-8, 0.78417784 maps to Euler(XYZ) In radians: -1.3169742E-7, -1.3388525, -1.2364265E-13 | In degrees: -7.545706292482747E-6, -76.71059905903273, -7.0842017918353375E-12

Quat XYZW: -5.531615E-8, -0.5425092, 3.572349E-8, 0.84004986 maps to Euler(XYZ) In radians: -1.316973E-7, -1.1468425, -3.4545395E-14 | In degrees: -7.545699778706759E-6, -65.70923385346072, -1.979305341354779E-12

Quat XYZW: -5.8318243E-8, -0.4643712, 3.057821E-8, 0.8856407 maps to Euler(XYZ) In radians: -1.316973E-7, -0.965849, -2.4987489E-14 | In degrees: -7.545699778706759E-6, -55.33907033475165, -1.4316776537211207E-12

Quat XYZW: -6.0689615E-8, -0.38801494, 2.5550252E-8, 0.9216531 maps to Euler(XYZ) In radians: -1.3169732E-7, -0.7969536, -2.5416875E-14 | In degrees: -7.545700592928757E-6, -45.6620788092451, -1.4562796829062145E-12

Quat XYZW: -6.2493015E-8, -0.31515533, 2.0752546E-8, 0.9490401 maps to Euler(XYZ) In radians: -1.3169732E-7, -0.6412406, -1.773355E-14 | In degrees: -7.545700592928757E-6, -36.740379847442306, -1.0160575447920945E-12

Quat XYZW: -6.380327E-8, -0.24730308, 1.6284568E-8, 0.9689382 maps to Euler(XYZ) In radians: -1.3169729E-7, -0.49979177, -1.2143506E-14 | In degrees: -7.545698964484761E-6, -28.63595912360844, -6.957716214363101E-13

Quat XYZW: -6.470256E-8, -0.18575978, 1.22320225E-8, 0.9825952 maps to Euler(XYZ) In radians: -1.316973E-7, -0.3736901, -1.5264296E-14 | In degrees: -7.545699778706759E-6, -21.410865491258477, -8.745797402829878E-13

Quat XYZW: -6.5275735E-8, -0.13162617, 8.6673975E-9, 0.99129945 maps to Euler(XYZ) In radians: -1.3169732E-7, -0.2640185, -1.8401186E-14 | In degrees: -7.545700592928757E-6, -15.127146096360107, -1.0543102956962941E-12

Quat XYZW: -6.56057E-8, -0.08582368, 5.6513647E-9, 0.99631035 maps to Euler(XYZ) In radians: -1.316973E-7, -0.17185877, -1.8029164E-14 | In degrees: -7.545699778706759E-6, -9.846782344310256, -1.0329950079242779E-12

Quat XYZW: -6.5769136E-8, -0.049127325, 3.234959E-9, 0.9987925 maps to Euler(XYZ) In radians: -1.3169728E-7, -0.09829421, -1.7849729E-14 | In degrees: -7.545698150262762E-6, -5.631843579191415, -1.0227141133972815E-12

Quat XYZW: -6.583243E-8, -0.022202317, 1.4619833E-9, 0.9997535 maps to Euler(XYZ) In radians: -1.3169732E-7, -0.04440828, -1.911468E-14 | In degrees: -7.545700592928757E-6, -2.5444070424923027, -1.0951905372995666E-12

Quat XYZW: -6.58476E-8, -0.0056411726, 3.714543E-10, 0.9999841 maps to Euler(XYZ) In radians: -1.316973E-7, -0.011282405, -1.859742E-14 | In degrees: -7.545699778706759E-6, -0.6464341826508654, -1.0655536652056328E-12

Quat XYZW: -6.584865E-8, 2.0243377E-8, -1.061E-14, 1.0 maps to Euler(XYZ) In radians: -1.316973E-7, 4.0486754E-8, -1.8554003E-14 | In degrees: -7.545699778706759E-6, 2.319720102041324E-6, -1.0630660420387862E-12

As seen after it reaches 90 degrees, it goes backwards but instead of x, z staying at ≈ 0 they're set to PI and -PI (or 180 and -180) what i expected was x, z staying at ≈ 0 and y going from 0 to 360 because the keyframes i only rotated 0-360 degrees in the y axis in blender.

I am not sure why this happens, my desired result is x, z staying at ≈ 0 and y going from 0 TO 2PI for example(implying 0-360 degrees)

I also highly doubt the conversion to euler xyz is wrong because i tried both wikis implementation of it and JOML's(math library for opengl in java)

Converted code of DMGregory's answer that is producing NaN for some values

    private Vector3f getEuler(Quaternionf q) {
        double PI = Math.PI;
    Vector3f transformedForward = new Vector3f(2 * q.x * q.z, 2 * q.y * q.z - 2 * q.w * q.x, MathUtils
            .sq(q.w) + MathUtils.sq(q.z) - MathUtils.sq(q.x) - MathUtils.sq(q.y));

    double yaw = PI + Math.atan2(-2 * (q.x * q.z + q.w * q.y),
            q.x * q.x + q.y * q.y - q.z * q.z - q.w * q.w);

    double pitch = Math.asin(2 * (q.w * q.x - q.y * q.z));
    Vector3f noRollRight = new Vector3f(q.w * q.w + q.z * q.z - q.x * q.x - q.y * q.y, 0, -2 * (q.x * q.z - q.w * q.y))
            .normalize();

    Vector3f noRollUp = transformedForward.cross(noRollRight);

    Vector3f transformedUp = new Vector3f(2 * (q.x * q.y - q.w * q.z),
            q.w * q.w + q.y * q.y - q.x * q.x - q.z * q.z,
            2 * (q.w * q.x + q.y * q.z));

    double roll = Math.acos(transformedUp.dot(noRollUp)) * -Math.signum(transformedUp.dot(noRollRight));

    return new Vector3f((float) yaw, (float) pitch, (float) roll);
}

6.283185, -0.79695374, NaN for (-3.880E-1 -6.192E-8 -2.607E-8  9.217E-1)
6.283185, -0.64124054, NaN for (-3.152E-1 -6.376E-8 -2.117E-8  9.490E-1)

First 3 values are the euler angles(yaw, pitch, roll) and the quaternion is in the paranthesis(in XYZW form)

Debugging the value of noRollUp, transformedUp for the first example above:

noRollUp: (-1.375E-7  6.989E-1 -7.152E-1),
transformedUp: ( 9.610E-8  6.989E-1 -7.152E-1)
for Quaternion (-3.880E-1 -6.192E-8 -2.607E-8  9.217E-1)
Result:
6.283185, -0.79695374, NaN for (-3.880E-1 -6.192E-8 -2.607E-8  9.217E-1)

I can't immediately see anything wrong with it.

After clamping the input of acos for roll to -1 - 1, that issue is resolved:

        double roll = Math.acos(MathUtils.clamp(transformedUp.dot(noRollUp) * -Math.signum(transformedUp
                .dot(noRollRight)), -1f, 1f));
Suic
  • 119
  • 6
  • This is a very normal problem to run into with Euler angles, and arguably a good reason to avoid using Euler angles in the first place. By definition, they have to have this kind of unintuitive wrap-around behaviour somewhere, no matter how you write your conversion routine. So eventually you'll come across something you want to do that crosses the wrap-around line and ends up in unintuitive territory. – DMGregory Dec 11 '21 at 13:51
  • The post you linked does seem like the issue i have, altho the answer doesn't really address it, so im not sure to solve it. I am aware that i should be avoiding Euler angles in the first place, however i am working on a tool for a rather old engine that uses euler angles, which is why i have to do convert quaternions/matrices to euler angles. What's my best option here? my desired result in this case is that the y value goes from 0 to TAU continuous for example(as it's a 0-360 degree rotation in blender) and x, z stay 0 as they're not rotated – Suic Dec 11 '21 at 14:44
  • However if this is not possible, then im open for other options that could potentially work for me, also if i missed/forgot to clarify something important in my question please let me know. – Suic Dec 11 '21 at 14:45
  • You can troubleshoot your code error by logging the values of noRollUp and transformedUp. Likely a small rounding error has led to one or both being slightly greater than 1.0 in magnitude, so you may just want to clamp the input to acos between -1 and 1 to avoid issues there. – DMGregory Dec 11 '21 at 17:01
  • Make sure the lower bound of your clamp is -1 as I mentioned above, not 0. -1 is a valid input to acos, and is important for representing upside-down rotations. – DMGregory Dec 11 '21 at 17:18
  • It still seems to have some issues, for example in my example animation where only Y axis is moved from 0-360 degrees, the roll is increasing aswell (z axis) causing incorrect rotation Could you clarify what axis the the yaw, pitch, roll correspond to, as it might just be that i did it in the wrong order. Edit: sorry i will fix the clamp mistake now – Suic Dec 11 '21 at 17:20
  • Could you clarify what axis the yaw, pitch, roll correspond to* sorry i mistyped that in my previous comment. – Suic Dec 11 '21 at 17:28
  • Typing "yaw roll pitch" into a search engine turns up plenty of diagrams showing how those terms relate to axes. Your transformedForward code does not match my answer below - you're missing the + 2 * q.w*q.y term in the x component. I've tested the code below and it's producing correct rotation matches, apart from the expected rounding errors when working with limited precision floats. – DMGregory Dec 11 '21 at 18:53
  • I fixed that now, however it turns out that my issue was the fact that i was using a function that returned the euler angles in XYZ (order(?)) instead of ZXY which is what i wanted, this solved all the issues for me, i can't believe i didn't realize it sooner(maybe cause the library didn't have that function initially) Anyway i have marked your answer as the accepted one – Suic Dec 11 '21 at 20:06

1 Answers1

0

You can write your own quaternion to Euler angle routine that puts the wrap-around in a different place.

Since you haven't specified your coordinate system, I'll show an example with a Unity-style x+ right, y+ up, z+ forward scheme, and a Z X Y Euler angle rotation order (from most local to most global).

First we'll take your quaternion and see where it places the object's forward vector:

$$\begin{align} \vec f^\prime = q \vec f q^{-1} &= (x, y, z, w)(0, 0, 1, 0)(-x, -y, -z, w)\\ &= (xi + yj + zk + w)(1k)(-xi - yj - zk + w)\\ &= (xi + yj + zk + w)(yi - xj + wk - z)\\ &= (2i(xz + wy) + 2j(yz - wx) + k(w^2 + z^2 - x^2 - y^2) + 0)\\ \vec f ^\prime &= (2xz+2wy, 2yz-2wx, w^2 + z^2 - x^2 - y^2) \end{align}$$

We can then get the yaw angle from this vector in the range \$0 - 2\pi\$ with:

yaw = PI + atan2(-2 * (q.x * q.z + q.w * q.y)
                  q.x*q.x + q.y*q.y - q.z*q.z - q.w*q.w);

And the pitch angle in the range \$\frac {-\pi} 2 - \frac \pi 2\$ with:

pitch = asin(2 * (q.w*q.x - q.y*q.z);

If we had zero roll, then our right vector would be:

noRollRight = normalize(q.w*q.w +q.z*q.z - q.x*q.x - q.y*q.y, 0, -2 * (q.x*q.z - q.w*q.y));

And our up vector would be:

noRollUp = cross(transformedForward, noRollRight);

Then we can repeat the calculation above to work out our actual up vector:

transformedUp = ( 2 * (q.x*q.y - q.w*q.z),
                  q.w*q.w + q.y*q.y-q.x*q.x - q.z*q.z,
                  2 * (q.w*q.x + q.y*q.z) );

And the roll angle between \$-\pi - \pi\$ is then:

roll = acos(dot(transformedUp, noRollUp)) * - sign(dot(transformedUp, noRollRight));

There may be additional simplifications/optimizations you can do, but that basic recipe shows how you can make your own quaternion to Euler angle conversion function with the wrap-around thresholds at your chosen locations.

DMGregory
  • 134,153
  • 22
  • 242
  • 357
  • Thank you for the detailed reply(gonna try it out now), i indeed forgot to specify my coordinate system(it's the same as you described(which is also what opengl uses afaik) but y and z are inverted in my case) however that shouldn't be a problem as i can just convert it The order of the euler angles is the same in my system (Z, X, Y) order where z = roll, x = pitch, y = yaw – Suic Dec 11 '21 at 16:08