# Skeletal animation: unexpected quarternion values

I'm following ThinMatrix's skeletal animation tutorial and his custom written Collada parser is deriving a different rotation quaternion from each keyframe matrix than the Assimp importer library does. They are close, except the values are signed inversely and unpredicatbly.

I dug around his importer code but it's a mess. I assume he is manually performing an additional axis calculation somewhere, any ideas what it could be and why?

Original Matrix

1 0 0 0
0 -0.06466547 -0.997907 0
0 0.997907 -0.06466556 3.810999
0 0 0 1


Assimp rotation quaternion : (0.7296115 , 0 , 0 , 0.683862)

ThinMatrix's rotation quaternion : -0.72961134 , 0, 0 , 0.68386203)

And for reference, the position vec3: (0, 0, 3.810999)

Update:

With an identity matrix both Assimp and the ThinMatrix code produce (0, 0, 0, 1)

• This sounds like it might be a difference in the coordinate system each method expects as input/output. (eg. flipping one axis would negate the values of the other two axes). Seeing a negative in the W is unusual though - can you show a few example input matrices and the output you get? (eg. identity matrix, and multiples of 90 about each axis) Jul 12, 2018 at 16:47
• @DMGregory turns out it's not always the X and W values which are inverted, in the example I added it's just the X. Jul 12, 2018 at 17:00
• Not sure why that happens. But IIRC, negating XYZ (all 3 at the same time) negates the rotation angle (negating W does that as well). Jul 12, 2018 at 17:07
• We'll need more than one example to diagnose this. Can you please give us a representative spread across the space of rotations? Jul 12, 2018 at 17:40
• @DMGregory with an identity matrix both produce (0, 0, 0, 1) and I don't understand what you mean by "spread across the space of rotations"... could you please show me the matrices you mean? Jul 12, 2018 at 17:55

As tkausl suggested, the issue was opposite row/column major matricies. It was problematic because Assimp gives you the position vector and rotation quarternion but does not store the matrix it was derived from, or allow it to be transpose beforehand.

The solution was to manually convert the quarternion back to a matrix, tranpose it, then back to a quarternion, using the following:

public Matrix4 QuarternionToRotationMatrix(OpenTK.Quaternion q)
{
Matrix4 matrix = new Matrix4();
float xy = q.X * q.Y;
float xz = q.X * q.Z;
float xw = q.X * q.W;
float yz = q.Y * q.Z;
float yw = q.Y * q.W;
float zw = q.Z * q.W;
float xSquared = q.X * q.X;
float ySquared = q.Y * q.Y;
float zSquared = q.Z * q.Z;
matrix.M11 = 1 - 2 * (ySquared + zSquared);
matrix.M12 = 2 * (xy - zw);
matrix.M13 = 2 * (xz + yw);
matrix.M14 = 0;
matrix.M21 = 2 * (xy + zw);
matrix.M22 = 1 - 2 * (xSquared + zSquared);
matrix.M23 = 2 * (yz - xw);
matrix.M24 = 0;
matrix.M31 = 2 * (xz - yw);
matrix.M32 = 2 * (yz + xw);
matrix.M33 = 1 - 2 * (xSquared + ySquared);
matrix.M34 = 0;
matrix.M41 = 0;
matrix.M42 = 0;
matrix.M43 = 0;
matrix.M44 = 1;
return matrix;
}

public OpenTK.Quaternion QuaternionFromMatrix(Matrix4 matrix)
{
float w, x, y, z;
float diagonal = matrix.M11 + matrix.M22 + matrix.M33;
if (diagonal > 0)
{
float w4 = (float)(Math.Sqrt(diagonal + 1f) * 2f);
w = w4 / 4f;
x = (matrix.M32 - matrix.M23) / w4;
y = (matrix.M13 - matrix.M31) / w4;
z = (matrix.M21 - matrix.M12) / w4;
}
else if ((matrix.M11 > matrix.M22) && (matrix.M11 > matrix.M33))
{
float x4 = (float)(Math.Sqrt(1f + matrix.M11 - matrix.M22 - matrix.M33) * 2f);
w = (matrix.M32 - matrix.M23) / x4;
x = x4 / 4f;
y = (matrix.M12 + matrix.M21) / x4;
z = (matrix.M13 + matrix.M31) / x4;
}
else if (matrix.M22 > matrix.M33)
{
float y4 = (float)(Math.Sqrt(1f + matrix.M22 - matrix.M11 - matrix.M33) * 2f);
w = (matrix.M13 - matrix.M31) / y4;
x = (matrix.M12 + matrix.M21) / y4;
y = y4 / 4f;
z = (matrix.M23 + matrix.M32) / y4;
}
else
{
float z4 = (float)(Math.Sqrt(1f + matrix.M33 - matrix.M11 - matrix.M22) * 2f);
w = (matrix.M21 - matrix.M12) / z4;
x = (matrix.M13 + matrix.M31) / z4;
y = (matrix.M23 + matrix.M32) / z4;
z = z4 / 4f;
}
return new OpenTK.Quaternion(x, y, z, w);
}

• Note that the transpose of a rotation matrix is its inverse. So instead of converting your quaternion to a matrix, transposing, and converting back, you may instead be able to invert the quaternion directly (negate the imaginary parts). Jul 13, 2018 at 12:04