0
\$\begingroup\$

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));
\$\endgroup\$
9
  • \$\begingroup\$ 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. \$\endgroup\$
    – DMGregory
    Commented Dec 11, 2021 at 13:51
  • \$\begingroup\$ 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 \$\endgroup\$
    – Suic
    Commented Dec 11, 2021 at 14:44
  • \$\begingroup\$ 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. \$\endgroup\$
    – Suic
    Commented Dec 11, 2021 at 14:45
  • \$\begingroup\$ 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. \$\endgroup\$
    – DMGregory
    Commented Dec 11, 2021 at 17:01
  • \$\begingroup\$ 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. \$\endgroup\$
    – DMGregory
    Commented Dec 11, 2021 at 17:18

1 Answer 1

0
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ 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 \$\endgroup\$
    – Suic
    Commented Dec 11, 2021 at 16:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .