3
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ Your description of the axes differs from Unity's conventions, where z+ is defined as "forward." To see this, take your left hand and point your thumb (x) right and index finger (y) up. Your middle finger (z) bent at the first knuckle should point forward in front of you, as Unity's does. Given this, there's two ways to interpret your description of BVH's right-handed coordinate system — either it has Z forward as written and X left, or X right and Z backward (opposite Unity's z convention). Can you clarify which you mean? \$\endgroup\$
    – DMGregory
    Commented May 1, 2017 at 19:04
  • \$\begingroup\$ Sorry for the confusion. BVH uses Z forward, X left, and Y up. I may be misinterpreting Unity's coordinate system--are you saying it's Z forward, X right, and Y up? \$\endgroup\$ Commented May 1, 2017 at 19:28

1 Answer 1

3
\$\begingroup\$

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);
}
\$\endgroup\$
4
  • \$\begingroup\$ Your explanation of the coordinate system change in (2) and (3) makes a lot of sense and worked perfectly. I initially assumed the multiplication order was right-to-left as you describe in (1), but the Unity documentation says left-to-right for quaternions. I had to keep the L-to-R order to make everything work. Can you update your answer and I'll accept it? \$\endgroup\$ Commented May 2, 2017 at 13:26
  • \$\begingroup\$ Looks like a difference in terminology. "…then rhs, relative to the reference frame resulting from lhs rotation" — they're describing local-compounding rotations, where you keep track of the local axes of your original space and rotate relative to those transformed axes. I described world-compounding rotations, where as we move left each subsequent rotation is applied with respect to the world axes. This is more consistent with the quaternion/matrix*vector multiplication order, which also puts input on the right. If BVH's self-described order is local-compounding then you'd reverse this too. \$\endgroup\$
    – DMGregory
    Commented May 2, 2017 at 13:40
  • \$\begingroup\$ I see, thank you for the explanation, I think i have a much better understanding now. Sorry I don't have enough rep in GameDev to upvote :( \$\endgroup\$ Commented May 2, 2017 at 13:43
  • \$\begingroup\$ No worries, I've got plenty of rep, so I'm not demanding of votes. ;) Amusingly, the Unity docs themselves are inconsistent which composition convention they use - the page on Transform.eulerAngles describes it using the same world-compounding convention I used, "z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order)" to describe yRot * xRot * zRot \$\endgroup\$
    – DMGregory
    Commented May 2, 2017 at 13:59

You must log in to answer this question.

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