🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Quaternion to Euler very imprecise

Started by
19 comments, last by Zakwayda 5 years, 1 month ago

Hi everybody,
I was always amazed in the bad way how Quaternion to Euler gives so much imprecise result.
Here he code I use:


void Quaternion::FromEulerAngles(const float X, const float Y, const float Z)
{
    const float HP = 0.5f * X;
    const float HY = 0.5f * Y;
    const float HR = 0.5f * Z;
    const float SinHP = CMath::Sin(HP);
    const float SinHY = CMath::Sin(HY);
    const float SinHR = CMath::Sin(HR);
    const float CosHP = CMath::Cos(HP);
    const float CosHY = CMath::Cos(HY);
    const float CosHR = CMath::Cos(HR);
    x = CosHY * SinHP * CosHR + SinHY * CosHP * SinHR;
    y = SinHY * CosHP * CosHR - CosHY * SinHP * SinHR;
    z = CosHY * CosHP * SinHR - SinHY * SinHP * CosHR;
    w = CosHY * CosHP * CosHR + SinHY * SinHP * SinHR;
}

void Quaternion::ToEulerAngles(float* X, float* Y, float* Z) const
{
    const float SingularityTest = (x * y) + (z * w);
    if (SingularityTest > 0.4999995f)
    {
        *Y = 2.0f * std::atan2(x, w);
        *Z = Math::HALF_PI;
        *X = 0.0f;
    }
    else if (SingularityTest < -0.4999995f)
    {
        *Y = -2.0f * std::atan2(x, w);
        *Z = -Math::HALF_PI;
        *X = 0.0f;
    }
    else
    {
        const float zz = z * z;
        *Y = std::atan2(2.0f * ((y * w) - (x * z)), 1.0f - 2.0f * ((y * y) + zz));
        *Z = std::asin(2.0f * SingularityTest);
        *X = std::atan2(2.0f * ((x * w) - (y * z)), 1.0f - 2.0f * ((x * x) + zz));
    }
}

It's not just a little imprecision but really the result is noticeable because the difference is large when you create the quaternion after the conversion and see the result on a mesh.
Is it normal and no way to have better result exists?
Thanks

Advertisement

If you don't get any answers based on what you've posted so far, here are a few questions (the answers to which could be helpful):

- What Euler order convention do the functions in question use (e.g. XYZ, XZY, etc.)?

- What math library are you using? If it's your own, what are your sources for the conversion algorithms?

- What kind of difference are you seeing before and after conversion? Is the orientation just shifted a bit, or is it completely different?

I don't use a lib, it's a custom Quaternion using ZYX order, conversion based on this source:
https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/

Quite apart from the conventions as Zakwayda mentions, here's my rubbish take on it as a non-mathematician :

As I understand it, Euler angles have some real limitations to describing rotations, they suffer from gimbal lock and loss of degree of freedom.

Although they are conceptually easy to understand, I believe they are at best 'ok' at describing some limited set of rotations, and incredibly bad at describing others once you get into gimbal lock territory (it's like trying to shoehorn an xyz vector into an xy vector afaik with the loss of degree of freedom).

As such going from a human understandable form yaw / pitch / roll to a quaternion / matrix is common in e.g. camera or model transforms (as long as you don't get too ambitious), but for anything much more complex than that (including interpolation like slerp) you would tend to use quaternions / axis angle / or other better rotation measures. And trying to go from quaternion to euler is just bound to go horribly wrong, even the thought of it sends shivers down my spine.

Incidentally, that guy in the video pronounces it 'oiler', but that is some silly swiss german thing, it is much more sensible to pronounce it 'youler', take it from me as an englishman, inventors of the greatest language in the world (aside from c++). I don't put eul in my motorbike engine, and I don't watch videos on oiltube, so why should my angles involve oil? :D

1 hour ago, Alundra said:

if (SingularityTest > 0.4999995f)
...
else if (SingularityTest < -0.4999995f)
...
else

You are using floating point. This probably does not do what you think it does. 

It looks like you are relying on your knowledge that there are enough bits in a float to represent approximately 7 decimal digits. Unfortunately for that code, it will only give you the results you expect when the floating decimal point is set at a single scale.  The tolerance you've got there is extremely small, particularly when mixed with trig functions.

Floating point numbers have variable precision, hence the name of a "floating" point. There is an old article, What Every Computer Scientist Should Know About Floating-Point Arithmetic, that covers much of the details about why it is the case.  That paper is math heavy, but should explain that floating point is an approximation.  Once you're finished with that paper, you need to understand what the hardware actually does.  Features like range reduction make the hardware faster, but result in slightly less precise answers than you may expect.  Other floating point configuration options can also make a difference, as can interference from other programs and even other libraries your code uses. (If you're building a 64-bit program that uses SIMD registers instead of FPU registers that specific issue is less frequent, but still potentially a problem.)

After that, the operations you perform yourself in the code affect numeric precision. Because of the nature of math, when you write x = CosHY * SinHP * CosHR + SinHY * CosHP * SinHR; by definition you have lost numeric precision. It cannot be avoided. That by itself can be enough to lose those the precision your code has specified.

Once you understand the math side, you need to understand what it means for angles (or in this case, for a quaternion) to have an error that is on the scale of 10-6. That's quite a small variance you've allowed.

The math itself is probably both precise enough and accurate enough for what you are trying to do.  

It might help to use doubles instead of floats. But even better, you could stop using Euler angles, since they are rarely what you need. Why do you think you need to convert from quaternion to Euler angles?

11 hours ago, lawnjelly said:

Incidentally, that guy in the video pronounces it 'oiler', but that is some silly swiss german thing, it is much more sensible to pronounce it 'youler', take it from me as an englishman, inventors of the greatest language in the world (aside from c++). I don't put eul in my motorbike engine, and I don't watch videos on oiltube, so why should my angles involve oil? :D

Don't.  Just don't.  "Euler" isn't some arbitrary sequence of letters, it's the last name of the mathematician Leonhard Euler (pronounced /ˈɔɪlər/), and deliberately mispronouncing somebody's name is just incredibly insulting.

1 hour ago, a light breeze said:

it's the last name of the mathematician Leonhard Euler (pronounced /ˈɔɪlər/), and deliberately mispronouncing somebody's name is just incredibly insulting.

I doubt he (Euler) cares very much.

🙂🙂🙂🙂🙂<←The tone posse, ready for action.

The problem is the imprecision is not little but very large...
Puting a quaternion with 45 degrees on X, Y and Z gives this quaternion:

Quote

x: 0.461940
y: 0.191342
z: 0.191342
w: 0.844623

The euler resulting from this quaternion is:

Quote

x: 54.735610
y: 9.735610
z: 30.000000

Of course we are far from 45 degrees that we started but the visual result is almost the same but the Y angle is far.
The "9.735610" on the Y axis should in reality be around 23 degrees.

image.png.b2e81ef9e376a9f86f3008c09fb8c6b7.png

Up is the result of the Quaternion to Euler, Down is the reference 45 degrees on each axis.

10 hours ago, alvaro said:

It might help to use doubles instead of floats. But even better, you could stop using Euler angles, since they are rarely what you need. Why do you think you need to convert from quaternion to Euler angles?

Used for the editor to show the euler angles to be artist friendly. But I begin to think that it should completely be avoided and do the calculation differently to get the good angles.

- When the gizmo is used, instead of add another quaternion for the transform, surely the euler can be modified local and world space and then the quaternion can be created safely from these angles.

- When you attach one object to another, the relative angles can surely be calculated safely too to have a good representation of the actual euler angles.

It's actually the two cases where I use the quaternion to euler because I do quaternion operation then I set the new quaternion but of course then the eulers is computed by converting the quaternion to euler if you set a quaternion and not euler angles for the new rotation.

I tried my euler to quaternion and it gave a different result to yours. I checked and mine follows the convention on wikipedia:

https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Source_Code

which appears to follow heading, attitude, bank convention.

The source you linked also claims to follow the same convention:

https://www.euclideanspace.com/maths/standards/index.htm

Yet the equations you use are different to that on wikipedia. I would include the code here but the code thing on the forum isn't working for me for some reason.

*edit* Just as an aside, I found the wikipedia source code for conversion from euler to quaternion seems to contain an error for the y component. The three.js version seems accurate for conversion from / to euler so I am switching to this.

This topic is closed to new replies.

Advertisement