Quaternion and Euler rotation in animation producing different results

Started by
12 comments, last by JoeJ 5 months ago

Hi,

I've been trying to convert my Euler rotations for animation to Quaternions, and I have finding myself in a situation where the behaviour and result is significantly different.

When applying Euler angles, I am having to negate all my rotations and then pass them to my rotation matrices and assemble the matrices as X *Y *Z where Z is multiplied first then Y and then X.

vec3 rotations = vec3(....)
rotations = -rotations;
auto rotation = Mat4::RotateX(rotations.x) * Mat4::RotateY(rotations.y) * Mat4::RotateZ(rotations.z);

auto offset_matrix = Mat::Translate(vec3(offset_from_parent.x, offset_from_parent.y, offset_from_parent.z));
global_matrix = parent_matrix * offset_matrix * rotation;

This produces the correct result with rotation matrices and Euler angles:

Animation pose using rotation matrices and Euler angles. (Rendering global_matrix at each joint)

I am trying to replicate these rotations but this time, using Quaternions.

I am doing the following:

vec3 rotations = vec3(....)
rotations = -rotations;

Quaternion rotX = Quaternion(rotations.x, vec3(1.0f, 0.0f, 0.0f));
Quaternion rotY = Quaternion(rotations.y, vec3(0.0f, 1.0f, 0.0f));
Quaternion rotZ = Quaternion(rotations.z, vec3(0.0f, 0.0f, 1.0f));
Quaternion concat = rotZ * rotY * rotX;
Mat4 quatRotMat = concat.ToRotationMatrix();
global_matrix = parent_matrix * offset_matrix * quatRotMat;

However, the result is incredibly different and I am not able to understand why.

Rendering pose using Quaternions and quatRotMat (Rendering global_matrix at each joint)

As I investigated further I found that, while Euler requires rotations to be negated, if I do not negate the rotations then the entire skeleton looks correct except the arms. The arms go in the air which seems like it might be simple fix of negating z, however negating z brings the arms down but they are still incorrectly placed.

This is using Quaternions but without negating the rotations. The rest of the skeleton looks fairly correctly except the arms.

Negating the Z rotation angle, brings the arms down however, this has the arms down but they're moved back.

Negating z rotation angle moves arms down but they're slightly behind the torso.
Arms are behind and not side-by-side with torso

Wondering if anyones had a similar issue before and could provide some insight as to where I might be going wrong.

This is my Quaternion code:

Quaternion::Quaternion(float angle, const vec3& v)
{
    float angleRadians = angle * M_PI / 180.0;
    float rotAngle = angleRadians / 2.0f;

    float cosAngle = std::cos(rotAngle);
    float sinAngle = std::sin(rotAngle);

    w = cosAngle;
    x = v.x * sinAngle;
    y = v.y * sinAngle;
    z = v.z * sinAngle;
}

Mat4 Quaternion::ToRotationMatrix()
{
    float x2 = x * x;
    float y2 = y * y;
    float z2 = z * z;
    float xy = x * y;
    float xz = x * z;
    float yz = y * z;
    float wx = w * x;
    float wy = w * y;
    float wz = w * z;

    Mat4 ret = Matrix4::Identity();

    ret.coordinates[0][0] = 1.0f - 2.0f * (y2 + z2);
    ret.coordinates[1][0] = 2.0f * (xy - wz);
    ret.coordinates[2][0] = 2.0f * (xz + wy);
    ret.coordinates[3][0] = 0.0f;

    ret.coordinates[0][1] = 2.0f * (xy + wz);
    ret.coordinates[1][1] = 1.0f - 2.0f * (x2 + z2);
    ret.coordinates[2][1] = 2.0f * (yz + wx);
    ret.coordinates[3][1] = 0.0f;

    ret.coordinates[0][2] = 2.0f * (xz + wy);
    ret.coordinates[1][2] = 2.0f * (yz - wx);
    ret.coordinates[2][2] = 1.0f - 2.0f * (x2 + y2);
    ret.coordinates[3][2] = 0.0f;

    ret.coordinates[0][3] = 0.0f;
    ret.coordinates[1][3] = 0.0f;
    ret.coordinates[2][3] = 0.0f;
    ret.coordinates[3][3] = 1.0f;

    return ret;
}

inline Quaternion operator*(const Quaternion& left, const Quaternion& right)
{
    float w = (left.w * right.w) - (left.x * right.x) - (left.y * right.y) - (left.z * right.z);
    float x = (left.x * right.w) + (left.w * right.x) + (left.y * right.z) - (left.z * right.y);
    float y = (left.y * right.w) + (left.w * right.y) + (left.z * right.x) - (left.x * right.z);
    float z = (left.z * right.w) + (left.w * right.z) + (left.x * right.y) - (left.y * right.x);

    return {w, x, y, z};
}

Advertisement

New post, same problem. : )

But i just realize what's the obvious next step imo: Remember how we compared your quat to matrix conversion results with mine?
I think you should do something similar: Write test code to compare your working Euler angles to matrix conversion with your broken version of the same thing using quaternions.
Then you can see if there is a difference and figure out where and why.

For example:

Mat4 testA = Mat4::RotateX(30);
Quaternion rotX = Quaternion(30, vec3(1.0f, 0.0f, 0.0f));

Mat4 testB = rotX.ToRotationMatrix();

// plot both to see if testA == testB as expected


The the next step would be to use 2 angles:

Mat4 testA = Mat4::RotateX(30) * Mat4::RotateY(80);
Quaternion rotX = Quaternion(30, vec3(1.0f, 0.0f, 0.0f));

Quaternion rotY = Quaternion(30, vec3(0.0f, 1.0f, 0.0f));

Mat4 testB = Quaternion(rotX * rotY).ToRotationMatrix();

// plot both to see if testA == testB as expected


As you add more details to reproduce the whole math, you should be able to find the culprit?

Wait - your images look like your matrices are not orthogonal! Meaning, X, Y and Z axis are not 90 degrees appart. They look skewed?

If so, that's it. Quaternions can't represent skew, just pure rotations. So you can not reproduce the matrix math with quaternions. And because usually there is no skew in character animation, it's likely your matrix code which is broken.

So I just starting testing like this and I notice that when I do 90 degree rotation on X using rotation matrix I get a difference result to rotating 90 degree using quaternion and then converting to rotation matrix but I am unsure why.

These are my results

Quat Matrix: 
         1          0          0          0 
         0   5.96e-08          1          0 
         0         -1   5.96e-08          0 
         0          0          0          1 
Rotation Matrix X: 
         1          0          0          0 
         0 -4.371e-08         -1          0 
         0          1 -4.371e-08          0 
         0          0          0          1 

If i'm right about the skew, the most likely reason would be that one (or more) of your Mat4::RotateX,Y,Z functions have a bug. Flipping a sign there can generate skew instead rotation easily.

Looking at your numbers i see there is one sign flipped. Which means one has different handness than the other. Matrices with unexpected handness often work, until you try to convert them to quaternions. Then the flaw shows up.

So yeah, google to compare your rotation functions with other implementations. Pretty sure the bug is there.

It seems that rotation matrix X had a bug where the sin was flipped. Despite this, the skeleton renders and animates correctly using pure rotation matrices? Even if this bug exists, it does not affect the Quaternions since they dont use that rotate X function at all and just do their own rotations and then covert into a matrix. So im a little confused

snoken said:
Despite this, the skeleton renders and animates correctly using pure rotation matrices?

No. It never was correct. It looked correct to you eventually, but it wasn't.

snoken said:
Even if this bug exists, it does not affect the Quaternions since they dont use that rotate X function at all

The bug affects your reference or ground truth, so it also affected your conclusions about the quaternion code path.

Unfortunately, you're back to the start because you don't know what's right.
You need a solid reference, e.g. loading the bvh file into Blender and comparing a difficult pose very closely. If really needed, you could even export / import the pose from Blender. That's some work, but sometimes we even code a whole path tracer just to get some reference images.
You also missed the skew in the matrices. It's just a little bit, but i can see it although i can't rotate the images to get a 3D impression.
So that means you need to look really closely, to spot bugs early. Probably that's also a matter of ‘being experienced with 3d programming’.
For example, i often use the Windows snipping tool to make screenshots from my app. Then i close the app, change code, run again and then compare with the former screenshot to see precise changes. Really a useful debugging tool sometimes.

Thanks for the reply! I just double checked my pure rotation matrices. I was mistaken, the X rotation was not bugged and was actually implemented correctly.

However, it seems that from comparing rotation matrix X and quaternion rotation matrix X, it looks like one rotates clockwise while the other counterclockwise?

snoken said:
However, it seems that from comparing rotation matrix X and quaternion rotation matrix X, it looks like one rotates clockwise while the other counterclockwise?

Yeah that's possible.
We can argue no if that's a matter of convention and choice, or if it's a bug.

To me it's clearly a bug, because it causes uncertainty. If you can, fix the libraries so they are consistent and correct (just don't ask me which is correct :D ).
If you can not, stop using the functions and make your own replacements.

However, if the only bug is clockwise vs. counter clockwise, then this does not explain the skew.
To test if your matrices are orthogonal, a simple test would be that all dot products of one basis vector with another must be zero, and additionally the determinant of the 3x3 rotation matrix should be one.

See this guy here:

No way the green axis has 90 degrees to the blue and red axis.
But the same shows up in your quaternion images too, so maybe there is still some other bug as well.

I think they are orthogonal, it just renders like that im assuming due to FOV. I rendered the left shoulder matrices and they look quite different, their orientation but I am not sure why. These matrices are the rotations only.

Image
Left = Combined rotation matrices. Right = Quaternion to rotation matrix. Demonstrated for Left Arm.

This topic is closed to new replies.

Advertisement