]> git.lizzy.rs Git - minetest.git/blob - 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
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "test.h"
21
22 #include <cmath>
23 #include "util/numeric.h"
24 #include "util/string.h"
25
26 class TestUtilities : public TestBase {
27 public:
28         TestUtilities() { TestManager::registerTestModule(this); }
29         const char *getName() { return "TestUtilities"; }
30
31         void runTests(IGameDef *gamedef);
32
33         void testAngleWrapAround();
34         void testWrapDegrees_0_360_v3f();
35         void testLowercase();
36         void testTrim();
37         void testIsYes();
38         void testRemoveStringEnd();
39         void testUrlEncode();
40         void testUrlDecode();
41         void testPadString();
42         void testStartsWith();
43         void testStrEqual();
44         void testStringTrim();
45         void testStrToIntConversion();
46         void testStringReplace();
47         void testStringAllowed();
48         void testAsciiPrintableHelper();
49         void testUTF8();
50         void testRemoveEscapes();
51         void testWrapRows();
52         void testIsNumber();
53         void testIsPowerOfTwo();
54         void testMyround();
55         void testStringJoin();
56         void testEulerConversion();
57 };
58
59 static TestUtilities g_test_instance;
60
61 void TestUtilities::runTests(IGameDef *gamedef)
62 {
63         TEST(testAngleWrapAround);
64         TEST(testWrapDegrees_0_360_v3f);
65         TEST(testLowercase);
66         TEST(testTrim);
67         TEST(testIsYes);
68         TEST(testRemoveStringEnd);
69         TEST(testUrlEncode);
70         TEST(testUrlDecode);
71         TEST(testPadString);
72         TEST(testStartsWith);
73         TEST(testStrEqual);
74         TEST(testStringTrim);
75         TEST(testStrToIntConversion);
76         TEST(testStringReplace);
77         TEST(testStringAllowed);
78         TEST(testAsciiPrintableHelper);
79         TEST(testUTF8);
80         TEST(testRemoveEscapes);
81         TEST(testWrapRows);
82         TEST(testIsNumber);
83         TEST(testIsPowerOfTwo);
84         TEST(testMyround);
85         TEST(testStringJoin);
86         TEST(testEulerConversion);
87 }
88
89 ////////////////////////////////////////////////////////////////////////////////
90
91 inline float ref_WrapDegrees180(float f)
92 {
93         // This is a slower alternative to the wrapDegrees_180() function;
94         // used as a reference for testing
95         float value = fmodf(f + 180, 360);
96         if (value < 0)
97                 value += 360;
98         return value - 180;
99 }
100
101
102 inline float ref_WrapDegrees_0_360(float f)
103 {
104         // This is a slower alternative to the wrapDegrees_0_360() function;
105         // used as a reference for testing
106         float value = fmodf(f, 360);
107         if (value < 0)
108                 value += 360;
109         return value < 0 ? value + 360 : value;
110 }
111
112
113 void TestUtilities::testAngleWrapAround() {
114     UASSERT(fabs(modulo360f(100.0) - 100.0) < 0.001);
115     UASSERT(fabs(modulo360f(720.5) - 0.5) < 0.001);
116     UASSERT(fabs(modulo360f(-0.5) - (-0.5)) < 0.001);
117     UASSERT(fabs(modulo360f(-365.5) - (-5.5)) < 0.001);
118
119     for (float f = -720; f <= -360; f += 0.25) {
120         UASSERT(std::fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001);
121     }
122
123     for (float f = -1440; f <= 1440; f += 0.25) {
124         UASSERT(std::fabs(modulo360f(f) - fmodf(f, 360)) < 0.001);
125         UASSERT(std::fabs(wrapDegrees_180(f) - ref_WrapDegrees180(f)) < 0.001);
126         UASSERT(std::fabs(wrapDegrees_0_360(f) - ref_WrapDegrees_0_360(f)) < 0.001);
127         UASSERT(wrapDegrees_0_360(
128                 std::fabs(wrapDegrees_180(f) - wrapDegrees_0_360(f))) < 0.001);
129     }
130
131 }
132
133 void TestUtilities::testWrapDegrees_0_360_v3f()
134 {
135     // only x test with little step
136         for (float x = -720.f; x <= 720; x += 0.05) {
137         v3f r = wrapDegrees_0_360_v3f(v3f(x, 0, 0));
138         UASSERT(r.X >= 0.0f && r.X < 360.0f)
139         UASSERT(r.Y == 0.0f)
140         UASSERT(r.Z == 0.0f)
141     }
142
143     // only y test with little step
144     for (float y = -720.f; y <= 720; y += 0.05) {
145         v3f r = wrapDegrees_0_360_v3f(v3f(0, y, 0));
146         UASSERT(r.X == 0.0f)
147         UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
148         UASSERT(r.Z == 0.0f)
149     }
150
151     // only z test with little step
152     for (float z = -720.f; z <= 720; z += 0.05) {
153         v3f r = wrapDegrees_0_360_v3f(v3f(0, 0, z));
154         UASSERT(r.X == 0.0f)
155         UASSERT(r.Y == 0.0f)
156         UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
157         }
158
159     // test the whole coordinate translation
160     for (float x = -720.f; x <= 720; x += 2.5) {
161         for (float y = -720.f; y <= 720; y += 2.5) {
162             for (float z = -720.f; z <= 720; z += 2.5) {
163                 v3f r = wrapDegrees_0_360_v3f(v3f(x, y, z));
164                 UASSERT(r.X >= 0.0f && r.X < 360.0f)
165                 UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
166                 UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
167             }
168         }
169     }
170 }
171
172
173 void TestUtilities::testLowercase()
174 {
175         UASSERT(lowercase("Foo bAR") == "foo bar");
176         UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà");
177         UASSERT(lowercase("MINETEST-powa") == "minetest-powa");
178 }
179
180
181 void TestUtilities::testTrim()
182 {
183         UASSERT(trim("") == "");
184         UASSERT(trim("dirt_with_grass") == "dirt_with_grass");
185         UASSERT(trim("\n \t\r  Foo bAR  \r\n\t\t  ") == "Foo bAR");
186         UASSERT(trim("\n \t\r    \r\n\t\t  ") == "");
187 }
188
189
190 void TestUtilities::testIsYes()
191 {
192         UASSERT(is_yes("YeS") == true);
193         UASSERT(is_yes("") == false);
194         UASSERT(is_yes("FAlse") == false);
195         UASSERT(is_yes("-1") == true);
196         UASSERT(is_yes("0") == false);
197         UASSERT(is_yes("1") == true);
198         UASSERT(is_yes("2") == true);
199 }
200
201
202 void TestUtilities::testRemoveStringEnd()
203 {
204         const char *ends[] = {"abc", "c", "bc", "", NULL};
205         UASSERT(removeStringEnd("abc", ends) == "");
206         UASSERT(removeStringEnd("bc", ends) == "b");
207         UASSERT(removeStringEnd("12c", ends) == "12");
208         UASSERT(removeStringEnd("foo", ends) == "");
209 }
210
211
212 void TestUtilities::testUrlEncode()
213 {
214         UASSERT(urlencode("\"Aardvarks lurk, OK?\"")
215                         == "%22Aardvarks%20lurk%2C%20OK%3F%22");
216 }
217
218
219 void TestUtilities::testUrlDecode()
220 {
221         UASSERT(urldecode("%22Aardvarks%20lurk%2C%20OK%3F%22")
222                         == "\"Aardvarks lurk, OK?\"");
223 }
224
225
226 void TestUtilities::testPadString()
227 {
228         UASSERT(padStringRight("hello", 8) == "hello   ");
229 }
230
231 void TestUtilities::testStartsWith()
232 {
233         UASSERT(str_starts_with(std::string(), std::string()) == true);
234         UASSERT(str_starts_with(std::string("the sharp pickaxe"),
235                 std::string()) == true);
236         UASSERT(str_starts_with(std::string("the sharp pickaxe"),
237                 std::string("the")) == true);
238         UASSERT(str_starts_with(std::string("the sharp pickaxe"),
239                 std::string("The")) == false);
240         UASSERT(str_starts_with(std::string("the sharp pickaxe"),
241                 std::string("The"), true) == true);
242         UASSERT(str_starts_with(std::string("T"), std::string("The")) == false);
243 }
244
245 void TestUtilities::testStrEqual()
246 {
247         UASSERT(str_equal(narrow_to_wide("abc"), narrow_to_wide("abc")));
248         UASSERT(str_equal(narrow_to_wide("ABC"), narrow_to_wide("abc"), true));
249 }
250
251
252 void TestUtilities::testStringTrim()
253 {
254         UASSERT(trim("  a") == "a");
255         UASSERT(trim("   a  ") == "a");
256         UASSERT(trim("a   ") == "a");
257         UASSERT(trim("") == "");
258 }
259
260
261 void TestUtilities::testStrToIntConversion()
262 {
263         UASSERT(mystoi("123", 0, 1000) == 123);
264         UASSERT(mystoi("123", 0, 10) == 10);
265 }
266
267
268 void TestUtilities::testStringReplace()
269 {
270         std::string test_str;
271         test_str = "Hello there";
272         str_replace(test_str, "there", "world");
273         UASSERT(test_str == "Hello world");
274         test_str = "ThisAisAaAtest";
275         str_replace(test_str, 'A', ' ');
276         UASSERT(test_str == "This is a test");
277 }
278
279
280 void TestUtilities::testStringAllowed()
281 {
282         UASSERT(string_allowed("hello", "abcdefghijklmno") == true);
283         UASSERT(string_allowed("123", "abcdefghijklmno") == false);
284         UASSERT(string_allowed_blacklist("hello", "123") == true);
285         UASSERT(string_allowed_blacklist("hello123", "123") == false);
286 }
287
288 void TestUtilities::testAsciiPrintableHelper()
289 {
290         UASSERT(IS_ASCII_PRINTABLE_CHAR('e') == true);
291         UASSERT(IS_ASCII_PRINTABLE_CHAR('\0') == false);
292
293         // Ensures that there is no cutting off going on...
294         // If there were, 331 would be cut to 75 in this example
295         // and 73 is a valid ASCII char.
296         int ch = 331;
297         UASSERT(IS_ASCII_PRINTABLE_CHAR(ch) == false);
298 }
299
300 void TestUtilities::testUTF8()
301 {
302         UASSERT(wide_to_utf8(utf8_to_wide("")) == "");
303         UASSERT(wide_to_utf8(utf8_to_wide("the shovel dug a crumbly node!"))
304                 == "the shovel dug a crumbly node!");
305 }
306
307 void TestUtilities::testRemoveEscapes()
308 {
309         UASSERT(unescape_enriched<wchar_t>(
310                 L"abc\x1bXdef") == L"abcdef");
311         UASSERT(unescape_enriched<wchar_t>(
312                 L"abc\x1b(escaped)def") == L"abcdef");
313         UASSERT(unescape_enriched<wchar_t>(
314                 L"abc\x1b((escaped with parenthesis\\))def") == L"abcdef");
315         UASSERT(unescape_enriched<wchar_t>(
316                 L"abc\x1b(incomplete") == L"abc");
317         UASSERT(unescape_enriched<wchar_t>(
318                 L"escape at the end\x1b") == L"escape at the end");
319         // Nested escapes not supported
320         UASSERT(unescape_enriched<wchar_t>(
321                 L"abc\x1b(outer \x1b(inner escape)escape)def") == L"abcescape)def");
322 }
323
324 void TestUtilities::testWrapRows()
325 {
326         UASSERT(wrap_rows("12345678",4) == "1234\n5678");
327         // test that wrap_rows doesn't wrap inside multibyte sequences
328         {
329                 const unsigned char s[] = {
330                         0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x72, 0x61, 0x70, 0x74, 0x6f,
331                         0x72, 0x2f, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0x2f,
332                         0x6d, 0x69, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x62, 0x69,
333                         0x6e, 0x2f, 0x2e, 0x2e, 0};
334                 std::string str((char *)s);
335                 UASSERT(utf8_to_wide(wrap_rows(str, 20)) != L"<invalid UTF-8 string>");
336         };
337         {
338                 const unsigned char s[] = {
339                         0x74, 0x65, 0x73, 0x74, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81,
340                         0xd1, 0x82, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82,
341                         0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0};
342                 std::string str((char *)s);
343                 UASSERT(utf8_to_wide(wrap_rows(str, 8)) != L"<invalid UTF-8 string>");
344         }
345 }
346
347
348 void TestUtilities::testIsNumber()
349 {
350         UASSERT(is_number("123") == true);
351         UASSERT(is_number("") == false);
352         UASSERT(is_number("123a") == false);
353 }
354
355
356 void TestUtilities::testIsPowerOfTwo()
357 {
358         UASSERT(is_power_of_two(0) == false);
359         UASSERT(is_power_of_two(1) == true);
360         UASSERT(is_power_of_two(2) == true);
361         UASSERT(is_power_of_two(3) == false);
362         for (int exponent = 2; exponent <= 31; ++exponent) {
363                 UASSERT(is_power_of_two((1 << exponent) - 1) == false);
364                 UASSERT(is_power_of_two((1 << exponent)) == true);
365                 UASSERT(is_power_of_two((1 << exponent) + 1) == false);
366         }
367         UASSERT(is_power_of_two(U32_MAX) == false);
368 }
369
370 void TestUtilities::testMyround()
371 {
372         UASSERT(myround(4.6f) == 5);
373         UASSERT(myround(1.2f) == 1);
374         UASSERT(myround(-3.1f) == -3);
375         UASSERT(myround(-6.5f) == -7);
376 }
377
378 void TestUtilities::testStringJoin()
379 {
380         std::vector<std::string> input;
381         UASSERT(str_join(input, ",") == "");
382
383         input.emplace_back("one");
384         UASSERT(str_join(input, ",") == "one");
385
386         input.emplace_back("two");
387         UASSERT(str_join(input, ",") == "one,two");
388
389         input.emplace_back("three");
390         UASSERT(str_join(input, ",") == "one,two,three");
391
392         input[1] = "";
393         UASSERT(str_join(input, ",") == "one,,three");
394
395         input[1] = "two";
396         UASSERT(str_join(input, " and ") == "one and two and three");
397 }
398
399
400 static bool within(const f32 value1, const f32 value2, const f32 precision)
401 {
402         return std::fabs(value1 - value2) <= precision;
403 }
404
405 static bool within(const v3f &v1, const v3f &v2, const f32 precision)
406 {
407         return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
408                 && within(v1.Z, v2.Z, precision);
409 }
410
411 static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
412                 const f32 precision)
413 {
414         const f32 *M1 = m1.pointer();
415         const f32 *M2 = m2.pointer();
416         for (int i = 0; i < 16; i++)
417                 if (! within(M1[i], M2[i], precision))
418                         return false;
419         return true;
420 }
421
422 static bool roundTripsDeg(const v3f &v, const f32 precision)
423 {
424         core::matrix4 m;
425         setPitchYawRoll(m, v);
426         return within(v, getPitchYawRoll(m), precision);
427 }
428
429 void TestUtilities::testEulerConversion()
430 {
431         // This test may fail on non-IEEE systems.
432         // Low tolerance is 4 ulp(1.0) for binary floats with 24 bit mantissa.
433         // (ulp = unit in the last place; ulp(1.0) = 2^-23).
434         const f32 tolL = 4.76837158203125e-7f;
435         // High tolerance is 2 ulp(180.0), needed for numbers in degrees.
436         // ulp(180.0) = 2^-16
437         const f32 tolH = 3.0517578125e-5f;
438         v3f v1, v2;
439         core::matrix4 m1, m2;
440         const f32 *M1 = m1.pointer();
441         const f32 *M2 = m2.pointer();
442
443         // Check that the radians version and the degrees version
444         // produce the same results. Check also that the conversion
445         // works both ways for these values.
446         v1 = v3f(M_PI/3.0, M_PI/5.0, M_PI/4.0);
447         v2 = v3f(60.0f, 36.0f, 45.0f);
448         setPitchYawRollRad(m1, v1);
449         setPitchYawRoll(m2, v2);
450         UASSERT(within(m1, m2, tolL));
451         UASSERT(within(getPitchYawRollRad(m1), v1, tolL));
452         UASSERT(within(getPitchYawRoll(m2), v2, tolH));
453
454         // Check the rotation matrix produced.
455         UASSERT(within(M1[0], 0.932004869f, tolL));
456         UASSERT(within(M1[1], 0.353553385f, tolL));
457         UASSERT(within(M1[2], 0.0797927827f, tolL));
458         UASSERT(within(M1[4], -0.21211791f, tolL));
459         UASSERT(within(M1[5], 0.353553355f, tolL));
460         UASSERT(within(M1[6], 0.911046684f, tolL));
461         UASSERT(within(M1[8], 0.293892622f, tolL));
462         UASSERT(within(M1[9], -0.866025448f, tolL));
463         UASSERT(within(M1[10], 0.404508471f, tolL));
464
465         // Check that the matrix is still homogeneous with no translation
466         UASSERT(M1[3] == 0.0f);
467         UASSERT(M1[7] == 0.0f);
468         UASSERT(M1[11] == 0.0f);
469         UASSERT(M1[12] == 0.0f);
470         UASSERT(M1[13] == 0.0f);
471         UASSERT(M1[14] == 0.0f);
472         UASSERT(M1[15] == 1.0f);
473         UASSERT(M2[3] == 0.0f);
474         UASSERT(M2[7] == 0.0f);
475         UASSERT(M2[11] == 0.0f);
476         UASSERT(M2[12] == 0.0f);
477         UASSERT(M2[13] == 0.0f);
478         UASSERT(M2[14] == 0.0f);
479         UASSERT(M2[15] == 1.0f);
480
481         // Compare to Irrlicht's results. To be comparable, the
482         // angles must come in a different order and the matrix
483         // elements to compare are different too.
484         m2.setRotationRadians(v3f(v1.Z, v1.X, v1.Y));
485         UASSERT(within(M1[0], M2[5], tolL));
486         UASSERT(within(M1[1], M2[6], tolL));
487         UASSERT(within(M1[2], M2[4], tolL));
488
489         UASSERT(within(M1[4], M2[9], tolL));
490         UASSERT(within(M1[5], M2[10], tolL));
491         UASSERT(within(M1[6], M2[8], tolL));
492
493         UASSERT(within(M1[8], M2[1], tolL));
494         UASSERT(within(M1[9], M2[2], tolL));
495         UASSERT(within(M1[10], M2[0], tolL));
496
497         // Check that Eulers that produce near gimbal-lock still round-trip
498         UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 0.f), tolH));
499         UASSERT(roundTripsDeg(v3f(89.9999f, 0.f, 19.f), tolH));
500         UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 19.f), tolH));
501
502         // Check that Eulers at an angle > 90 degrees may not round-trip...
503         v1 = v3f(90.00001f, 1.f, 1.f);
504         setPitchYawRoll(m1, v1);
505         v2 = getPitchYawRoll(m1);
506         //UASSERT(within(v1, v2, tolL)); // this is typically false
507         // ... however the rotation matrix is the same for both
508         setPitchYawRoll(m2, v2);
509         UASSERT(within(m1, m2, tolL));
510 }