Search code examples
rotationgeometryeigenquaternionseigen3

quaternion.inverse() * quaternion is not zero


I'm using the Eigen C++ library. Multiplying a quaternion with it's inverse should always give the zero rotation, right?

I have a specific testcase where it doesn't.

// This case works fine:
OrientationTestsEigen(1.7118, 0.8036, 1.5977);
// This fails:
OrientationTestsEigen(1.7118679426016505, 0.80361404966200567, 1.5977764119637190);

Test code:

bool approxEqual(double a, double b)
{
    return fabs(a - b) < 0.0001;
}

void QuaternionToYPR(const Quaterniond& quat, double &y, double &p, double &r)
{
    Eigen::Vector3d rpy = quat.toRotationMatrix().eulerAngles(2, 0, 1);
    r = rpy.x();
    p = rpy.y();
    y = rpy.z();
}

void OrientationTestsEigen(double yaw, double pitch, double roll)
{
    // Convert Yaw, Pitch, Roll to Quaternion
    Eigen::AngleAxisd yawAngle(yaw, Eigen::Vector3d::UnitY());
    Eigen::AngleAxisd pitchAngle(pitch, Eigen::Vector3d::UnitX());
    Eigen::AngleAxisd rollAngle(roll, Eigen::Vector3d::UnitZ());
    Quaterniond quaternionRepresentation = rollAngle *  pitchAngle * yawAngle;

    // quat * quat.inverse() should result in zero rotation
    Quaterniond shouldBeZero = quaternionRepresentation * quaternionRepresentation.inverse();
    double y3, p3, r3;
    QuaternionToYPR(shouldBeZero, y3, p3, r3);
    assert(fabs(y3) < 0.001);
    assert(fabs(p3) < 0.001);
    assert(fabs(r3) < 0.001);
}

I find it very strange that the first case works, but the second (with only a tiny difference in values) fails. What I'm getting is:

  • Testcase 1: yaw, pitch and roll are zero, as expected.
  • Testcase 2: I'm getting -PI for yaw, PI for pitch, PI for roll. Pitch only goes up to 90°, so effectively this is yaw=0, pitch=0 and roll=180°.

Why do we get 180° roll in the second case, and not zero?


Solution

  • The same rotation can be represented by MULtiple Euler angles (yaw, pitch, roll). So your comparison is not correct.

    You can compare rotation matrices or estimate angle of rotation between result quaternion and Identity quaternion.

    shouldBeZero.angularDistance(Quaterniond::Identity());