]> git.lizzy.rs Git - minetest.git/blobdiff - src/unittest/test_utilities.cpp
Use true pitch/yaw/roll rotations without loss of precision by pgimeno (#8019)
[minetest.git] / src / unittest / test_utilities.cpp
index 112b53f0b111f23a17132f8466e229ba6684dff4..8e8958d18e159776dcd3ef3ce188284e42f1964b 100644 (file)
@@ -53,6 +53,7 @@ class TestUtilities : public TestBase {
        void testIsPowerOfTwo();
        void testMyround();
        void testStringJoin();
+       void testEulerConversion();
 };
 
 static TestUtilities g_test_instance;
@@ -82,6 +83,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
        TEST(testIsPowerOfTwo);
        TEST(testMyround);
        TEST(testStringJoin);
+       TEST(testEulerConversion);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -171,6 +173,8 @@ void TestUtilities::testWrapDegrees_0_360_v3f()
 void TestUtilities::testLowercase()
 {
        UASSERT(lowercase("Foo bAR") == "foo bar");
+       UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà");
+       UASSERT(lowercase("MINETEST-powa") == "minetest-powa");
 }
 
 
@@ -392,3 +396,115 @@ void TestUtilities::testStringJoin()
        UASSERT(str_join(input, " and ") == "one and two and three");
 }
 
+
+static bool within(const f32 value1, const f32 value2, const f32 precision)
+{
+       return std::fabs(value1 - value2) <= precision;
+}
+
+static bool within(const v3f &v1, const v3f &v2, const f32 precision)
+{
+       return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
+               && within(v1.Z, v2.Z, precision);
+}
+
+static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
+               const f32 precision)
+{
+       const f32 *M1 = m1.pointer();
+       const f32 *M2 = m2.pointer();
+       for (int i = 0; i < 16; i++)
+               if (! within(M1[i], M2[i], precision))
+                       return false;
+       return true;
+}
+
+static bool roundTripsDeg(const v3f &v, const f32 precision)
+{
+       core::matrix4 m;
+       setPitchYawRoll(m, v);
+       return within(v, getPitchYawRoll(m), precision);
+}
+
+void TestUtilities::testEulerConversion()
+{
+       // This test may fail on non-IEEE systems.
+       // Low tolerance is 4 ulp(1.0) for binary floats with 24 bit mantissa.
+       // (ulp = unit in the last place; ulp(1.0) = 2^-23).
+       const f32 tolL = 4.76837158203125e-7f;
+       // High tolerance is 2 ulp(180.0), needed for numbers in degrees.
+       // ulp(180.0) = 2^-16
+       const f32 tolH = 3.0517578125e-5f;
+       v3f v1, v2;
+       core::matrix4 m1, m2;
+       const f32 *M1 = m1.pointer();
+       const f32 *M2 = m2.pointer();
+
+       // Check that the radians version and the degrees version
+       // produce the same results. Check also that the conversion
+       // works both ways for these values.
+       v1 = v3f(M_PI/3.0, M_PI/5.0, M_PI/4.0);
+       v2 = v3f(60.0f, 36.0f, 45.0f);
+       setPitchYawRollRad(m1, v1);
+       setPitchYawRoll(m2, v2);
+       UASSERT(within(m1, m2, tolL));
+       UASSERT(within(getPitchYawRollRad(m1), v1, tolL));
+       UASSERT(within(getPitchYawRoll(m2), v2, tolH));
+
+       // Check the rotation matrix produced.
+       UASSERT(within(M1[0], 0.932004869f, tolL));
+       UASSERT(within(M1[1], 0.353553385f, tolL));
+       UASSERT(within(M1[2], 0.0797927827f, tolL));
+       UASSERT(within(M1[4], -0.21211791f, tolL));
+       UASSERT(within(M1[5], 0.353553355f, tolL));
+       UASSERT(within(M1[6], 0.911046684f, tolL));
+       UASSERT(within(M1[8], 0.293892622f, tolL));
+       UASSERT(within(M1[9], -0.866025448f, tolL));
+       UASSERT(within(M1[10], 0.404508471f, tolL));
+
+       // Check that the matrix is still homogeneous with no translation
+       UASSERT(M1[3] == 0.0f);
+       UASSERT(M1[7] == 0.0f);
+       UASSERT(M1[11] == 0.0f);
+       UASSERT(M1[12] == 0.0f);
+       UASSERT(M1[13] == 0.0f);
+       UASSERT(M1[14] == 0.0f);
+       UASSERT(M1[15] == 1.0f);
+       UASSERT(M2[3] == 0.0f);
+       UASSERT(M2[7] == 0.0f);
+       UASSERT(M2[11] == 0.0f);
+       UASSERT(M2[12] == 0.0f);
+       UASSERT(M2[13] == 0.0f);
+       UASSERT(M2[14] == 0.0f);
+       UASSERT(M2[15] == 1.0f);
+
+       // Compare to Irrlicht's results. To be comparable, the
+       // angles must come in a different order and the matrix
+       // elements to compare are different too.
+       m2.setRotationRadians(v3f(v1.Z, v1.X, v1.Y));
+       UASSERT(within(M1[0], M2[5], tolL));
+       UASSERT(within(M1[1], M2[6], tolL));
+       UASSERT(within(M1[2], M2[4], tolL));
+
+       UASSERT(within(M1[4], M2[9], tolL));
+       UASSERT(within(M1[5], M2[10], tolL));
+       UASSERT(within(M1[6], M2[8], tolL));
+
+       UASSERT(within(M1[8], M2[1], tolL));
+       UASSERT(within(M1[9], M2[2], tolL));
+       UASSERT(within(M1[10], M2[0], tolL));
+
+       // Check that Eulers that produce near gimbal-lock still round-trip
+       UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 0.f), tolH));
+       UASSERT(roundTripsDeg(v3f(89.9999f, 0.f, 19.f), tolH));
+       UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 19.f), tolH));
+
+       // Check that Eulers at an angle > 90 degrees may not round-trip...
+       v1 = v3f(90.00001f, 1.f, 1.f);
+       setPitchYawRoll(m1, v1);
+       v2 = getPitchYawRoll(m1);
+       //UASSERT(within(v1, v2, tolL)); // this is typically false
+       // ... however the rotation matrix is the same for both
+       setPitchYawRoll(m2, v2);
+       UASSERT(within(m1, m2, tolL));
+}