Saturday, February 25, 2017

c# - Euler (right-handed) to Quaternion (left-handed) conversion in Unity


I'm having trouble figuring out the proper conversions from Euler angles to Quaternions. The Eulers come from BVH files, which use a right-handed coordinate system (Y is up, Z is forward) and can have any rotation order specified. The Quaternions are in Unity, which is left-handed (Y is up, Z is back).



I'm composing the Quaternions from three AngleAxis rotations, but messing up the order somewhere. So far I can't find understandable and generalized information for doing these types of conversions.


public enum AxisOrder
{
XYZ, XZY, YXZ, YZX, ZXY, ZYX, None
}

public Quaternion EulerToQuat(Vector3 eulerAngles, AxisOrder rotationOrder)
{
// I've tried various combinations of axis angles here, but would
// like to understand what's wrong rather than brute-force

// every combination
var xRot = Quaternion.AngleAxis(eulerAngles.x, Vector3.left);
var yRot = Quaternion.AngleAxis(eulerAngles.y, Vector3.down);
var zRot = Quaternion.AngleAxis(eulerAngles.z, Vector3.back);

switch (rotationOrder)
{
case AxisOrder.XYZ: return (xRot * yRot) * zRot;
case AxisOrder.XZY: return (xRot * zRot) * yRot;
case AxisOrder.YXZ: return (yRot * xRot) * zRot;

case AxisOrder.YZX: return (yRot * zRot) * xRot;
case AxisOrder.ZXY: return (zRot * xRot) * yRot;
case AxisOrder.ZYX: return (zRot * yRot) * xRot;
}

return Quaternion.identity;
}

...and converting translation data like so (I believe this is working):


public static Vector3 BvhToUnityTranslation(float xPos, float yPos, float zPos)

{
return new Vector3(xPos, yPos, -zPos);
}

If anyone can help me better understand how to conceptualize coordinate system conversions, and where I'm going wrong, I'd greatly appreciate it.



Answer



Three points we need to consider:




  1. Matrices and quaternions in Unity multiply from right to left, if we think of each subsequent rotation being applied with regard to the world axes:



    output space or vector = <-- transformation * <--- input space or vector

    As discussed below, if BVH's "XYZ" order means "rotate about the world X axis, then the world Y axis, then the world Z axis" you'd stack your rotations in Unity right-to-left: Z * Y * X


    But, it sounds like BVH's XYZ means "rotate about the local X axis, then the local Y axis, then the local Z axis" which means in Unity we'd match the order left-to-right: X * Y * Z




  2. When switching the handedness of the coordinate system, all rotation angles are negated (if we keep the axis of rotation consistent)




  3. The axes map up like so, based on the conversation above:



    direction    Unity       BVH
    ----------------------------
    right x+ x-
    up y+ y+
    forward z+ z+


So we should be able to get correct conversion between the two spaces with a few small changes:


public Quaternion BvhToUnityRotation(Vector3 eulerAngles, AxisOrder rotationOrder)
{

// BVH's x+ axis is Unity's left (x-)
var xRot = Quaternion.AngleAxis(-eulerAngles.x, Vector3.left);
// Unity & BVH agree on the y & z axes
var yRot = Quaternion.AngleAxis(-eulerAngles.y, Vector3.up);
var zRot = Quaternion.AngleAxis(-eulerAngles.z, Vector3.forward);

switch (rotationOrder)
{
// Reproduce rotation order (no need for parentheses - it's associative)
case AxisOrder.XYZ: return xRot * yRot * zRot;

case AxisOrder.XZY: return xRot * zRot * yRot;
case AxisOrder.YXZ: return yRot * xRot * zRot;
case AxisOrder.YZX: return yRot * zRot * xRot;
case AxisOrder.ZXY: return zRot * xRot * yRot;
case AxisOrder.ZYX: return zRot * yRot * xRot;
}

return Quaternion.identity;
}


And:


public static Vector3 BvhToUnityTranslation(float xPos, float yPos, float zPos)
{
// Flipping x axis instead.
return new Vector3(-xPos, yPos, zPos);
}

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...