]> 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 df90d37bd3eacd3d9b80007e0a822658a306840c..8e8958d18e159776dcd3ef3ce188284e42f1964b 100644 (file)
@@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "test.h"
 
+#include <cmath>
 #include "util/numeric.h"
 #include "util/string.h"
 
@@ -30,6 +31,7 @@ class TestUtilities : public TestBase {
        void runTests(IGameDef *gamedef);
 
        void testAngleWrapAround();
+       void testWrapDegrees_0_360_v3f();
        void testLowercase();
        void testTrim();
        void testIsYes();
@@ -43,11 +45,15 @@ class TestUtilities : public TestBase {
        void testStrToIntConversion();
        void testStringReplace();
        void testStringAllowed();
+       void testAsciiPrintableHelper();
        void testUTF8();
+       void testRemoveEscapes();
        void testWrapRows();
        void testIsNumber();
        void testIsPowerOfTwo();
        void testMyround();
+       void testStringJoin();
+       void testEulerConversion();
 };
 
 static TestUtilities g_test_instance;
@@ -55,6 +61,7 @@ static TestUtilities g_test_instance;
 void TestUtilities::runTests(IGameDef *gamedef)
 {
        TEST(testAngleWrapAround);
+       TEST(testWrapDegrees_0_360_v3f);
        TEST(testLowercase);
        TEST(testTrim);
        TEST(testIsYes);
@@ -68,11 +75,15 @@ void TestUtilities::runTests(IGameDef *gamedef)
        TEST(testStrToIntConversion);
        TEST(testStringReplace);
        TEST(testStringAllowed);
+       TEST(testAsciiPrintableHelper);
        TEST(testUTF8);
+       TEST(testRemoveEscapes);
        TEST(testWrapRows);
        TEST(testIsNumber);
        TEST(testIsPowerOfTwo);
        TEST(testMyround);
+       TEST(testStringJoin);
+       TEST(testEulerConversion);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -99,29 +110,71 @@ inline float ref_WrapDegrees_0_360(float f)
 }
 
 
-void TestUtilities::testAngleWrapAround()
-{
-       UASSERT(fabs(modulo360f(100.0) - 100.0) < 0.001);
-       UASSERT(fabs(modulo360f(720.5) - 0.5) < 0.001);
-       UASSERT(fabs(modulo360f(-0.5) - (-0.5)) < 0.001);
-       UASSERT(fabs(modulo360f(-365.5) - (-5.5)) < 0.001);
+void TestUtilities::testAngleWrapAround() {
+    UASSERT(fabs(modulo360f(100.0) - 100.0) < 0.001);
+    UASSERT(fabs(modulo360f(720.5) - 0.5) < 0.001);
+    UASSERT(fabs(modulo360f(-0.5) - (-0.5)) < 0.001);
+    UASSERT(fabs(modulo360f(-365.5) - (-5.5)) < 0.001);
 
-       for (float f = -720; f <= -360; f += 0.25) {
-               UASSERT(fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001);
-       }
+    for (float f = -720; f <= -360; f += 0.25) {
+        UASSERT(std::fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001);
+    }
+
+    for (float f = -1440; f <= 1440; f += 0.25) {
+        UASSERT(std::fabs(modulo360f(f) - fmodf(f, 360)) < 0.001);
+        UASSERT(std::fabs(wrapDegrees_180(f) - ref_WrapDegrees180(f)) < 0.001);
+        UASSERT(std::fabs(wrapDegrees_0_360(f) - ref_WrapDegrees_0_360(f)) < 0.001);
+        UASSERT(wrapDegrees_0_360(
+                std::fabs(wrapDegrees_180(f) - wrapDegrees_0_360(f))) < 0.001);
+    }
+
+}
 
-       for (float f = -1440; f <= 1440; f += 0.25) {
-               UASSERT(fabs(modulo360f(f) - fmodf(f, 360)) < 0.001);
-               UASSERT(fabs(wrapDegrees_180(f) - ref_WrapDegrees180(f)) < 0.001);
-               UASSERT(fabs(wrapDegrees_0_360(f) - ref_WrapDegrees_0_360(f)) < 0.001);
-               UASSERT(wrapDegrees_0_360(fabs(wrapDegrees_180(f) - wrapDegrees_0_360(f))) < 0.001);
+void TestUtilities::testWrapDegrees_0_360_v3f()
+{
+    // only x test with little step
+       for (float x = -720.f; x <= 720; x += 0.05) {
+        v3f r = wrapDegrees_0_360_v3f(v3f(x, 0, 0));
+        UASSERT(r.X >= 0.0f && r.X < 360.0f)
+        UASSERT(r.Y == 0.0f)
+        UASSERT(r.Z == 0.0f)
+    }
+
+    // only y test with little step
+    for (float y = -720.f; y <= 720; y += 0.05) {
+        v3f r = wrapDegrees_0_360_v3f(v3f(0, y, 0));
+        UASSERT(r.X == 0.0f)
+        UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
+        UASSERT(r.Z == 0.0f)
+    }
+
+    // only z test with little step
+    for (float z = -720.f; z <= 720; z += 0.05) {
+        v3f r = wrapDegrees_0_360_v3f(v3f(0, 0, z));
+        UASSERT(r.X == 0.0f)
+        UASSERT(r.Y == 0.0f)
+        UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
        }
+
+    // test the whole coordinate translation
+    for (float x = -720.f; x <= 720; x += 2.5) {
+        for (float y = -720.f; y <= 720; y += 2.5) {
+            for (float z = -720.f; z <= 720; z += 2.5) {
+                v3f r = wrapDegrees_0_360_v3f(v3f(x, y, z));
+                UASSERT(r.X >= 0.0f && r.X < 360.0f)
+                UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
+                UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
+            }
+        }
+    }
 }
 
 
 void TestUtilities::testLowercase()
 {
        UASSERT(lowercase("Foo bAR") == "foo bar");
+       UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà");
+       UASSERT(lowercase("MINETEST-powa") == "minetest-powa");
 }
 
 
@@ -232,6 +285,18 @@ void TestUtilities::testStringAllowed()
        UASSERT(string_allowed_blacklist("hello123", "123") == false);
 }
 
+void TestUtilities::testAsciiPrintableHelper()
+{
+       UASSERT(IS_ASCII_PRINTABLE_CHAR('e') == true);
+       UASSERT(IS_ASCII_PRINTABLE_CHAR('\0') == false);
+
+       // Ensures that there is no cutting off going on...
+       // If there were, 331 would be cut to 75 in this example
+       // and 73 is a valid ASCII char.
+       int ch = 331;
+       UASSERT(IS_ASCII_PRINTABLE_CHAR(ch) == false);
+}
+
 void TestUtilities::testUTF8()
 {
        UASSERT(wide_to_utf8(utf8_to_wide("")) == "");
@@ -239,6 +304,23 @@ void TestUtilities::testUTF8()
                == "the shovel dug a crumbly node!");
 }
 
+void TestUtilities::testRemoveEscapes()
+{
+       UASSERT(unescape_enriched<wchar_t>(
+               L"abc\x1bXdef") == L"abcdef");
+       UASSERT(unescape_enriched<wchar_t>(
+               L"abc\x1b(escaped)def") == L"abcdef");
+       UASSERT(unescape_enriched<wchar_t>(
+               L"abc\x1b((escaped with parenthesis\\))def") == L"abcdef");
+       UASSERT(unescape_enriched<wchar_t>(
+               L"abc\x1b(incomplete") == L"abc");
+       UASSERT(unescape_enriched<wchar_t>(
+               L"escape at the end\x1b") == L"escape at the end");
+       // Nested escapes not supported
+       UASSERT(unescape_enriched<wchar_t>(
+               L"abc\x1b(outer \x1b(inner escape)escape)def") == L"abcescape)def");
+}
+
 void TestUtilities::testWrapRows()
 {
        UASSERT(wrap_rows("12345678",4) == "1234\n5678");
@@ -282,7 +364,7 @@ void TestUtilities::testIsPowerOfTwo()
                UASSERT(is_power_of_two((1 << exponent)) == true);
                UASSERT(is_power_of_two((1 << exponent) + 1) == false);
        }
-       UASSERT(is_power_of_two((u32)-1) == false);
+       UASSERT(is_power_of_two(U32_MAX) == false);
 }
 
 void TestUtilities::testMyround()
@@ -293,3 +375,136 @@ void TestUtilities::testMyround()
        UASSERT(myround(-6.5f) == -7);
 }
 
+void TestUtilities::testStringJoin()
+{
+       std::vector<std::string> input;
+       UASSERT(str_join(input, ",") == "");
+
+       input.emplace_back("one");
+       UASSERT(str_join(input, ",") == "one");
+
+       input.emplace_back("two");
+       UASSERT(str_join(input, ",") == "one,two");
+
+       input.emplace_back("three");
+       UASSERT(str_join(input, ",") == "one,two,three");
+
+       input[1] = "";
+       UASSERT(str_join(input, ",") == "one,,three");
+
+       input[1] = "two";
+       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));
+}