From bd5f8392be392f13d7256bcce416b2ec2f06ca6b Mon Sep 17 00:00:00 2001 From: Stan Date: Fri, 13 Sep 2024 13:27:42 +0200 Subject: [PATCH] Make CMatrix3D::operator*= behave as expected. --- source/lib/self_test.h | 13 ++- source/maths/Matrix3D.h | 10 +- source/maths/tests/test_Matrix3d.h | 155 +++++++++++++++++++++++++++-- 3 files changed, 167 insertions(+), 11 deletions(-) diff --git a/source/lib/self_test.h b/source/lib/self_test.h index 9c3cf22245..a42c455d06 100644 --- a/source/lib/self_test.h +++ b/source/lib/self_test.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -24,8 +24,9 @@ #define INCLUDED_SELF_TEST // for convenience, to avoid having to include all of these manually -#include "lib/status.h" +#include "lib/lib.h" #include "lib/os_path.h" +#include "lib/status.h" #include "lib/posix/posix.h" #define CXXTEST_HAVE_EH @@ -147,6 +148,14 @@ std::vector ts_make_vector(T* start, size_t size_bytes) #define TS_ASSERT_VECTOR_EQUALS_ARRAY(vec1, array) TS_ASSERT_EQUALS(vec1, ts_make_vector((array), sizeof(array))) #define TS_ASSERT_VECTOR_CONTAINS(vec1, element) TS_ASSERT(std::find((vec1).begin(), (vec1).end(), element) != (vec1).end()); +#define TS_ASSERT_MATRIX_EQUALS_DELTA(m1, m2, size, epsilon) \ + for (int j = 0; j < size; ++j) \ + TS_ASSERT_DELTA(m1._data[j], m2._data[j], epsilon); + +#define TS_ASSERT_MATRIX_DIFFERS_DELTA(m1, m2, size, epsilon) \ + for (int j = 0; j < size; ++j) \ + TS_ASSERT(!feq(m1._data[j], m2._data[j], epsilon)); + class ScriptInterface; // Script-based testing setup (defined in test_setup.cpp). Defines TS_* functions. void ScriptTestSetup(const ScriptInterface&); diff --git a/source/maths/Matrix3D.h b/source/maths/Matrix3D.h index 16df365e52..8e8523c141 100644 --- a/source/maths/Matrix3D.h +++ b/source/maths/Matrix3D.h @@ -77,6 +77,14 @@ public: { } + CMatrix3D(const float data[]) : + _11(data[0]), _21(data[1]), _31(data[2]), _41(data[3]), + _12(data[4]), _22(data[5]), _32(data[6]), _42(data[7]), + _13(data[8]), _23(data[9]), _33(data[10]), _43(data[11]), + _14(data[12]), _24(data[13]), _34(data[14]), _44(data[15]) + { + } + // accessors to individual elements of matrix // NOTE: in this function definition, 'col' and 'row' represent the column and row into the // internal element matrix which is the transposed of the mathematical notation, so the first @@ -128,7 +136,7 @@ public: // matrix multiplication/assignment CMatrix3D& operator*=(const CMatrix3D &matrix) { - Concatenate(matrix); + *this = *this * matrix; return *this; } diff --git a/source/maths/tests/test_Matrix3d.h b/source/maths/tests/test_Matrix3d.h index 4e449ff50a..5a4c68d1ac 100644 --- a/source/maths/tests/test_Matrix3d.h +++ b/source/maths/tests/test_Matrix3d.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -27,6 +27,7 @@ class TestMatrix : public CxxTest::TestSuite { std::mt19937 m_Engine; + const float m_Epsilon{0.0001f}; public: void setUp() @@ -46,19 +47,157 @@ public: } CMatrix3D n; m.GetInverse(n); - m *= n; + m.Concatenate(n); // verify identity has 1s on diagonal and 0 otherwise for (int x = 0; x < 4; ++x) { for (int y = 0; y < 4; ++y) { const float expected = (x==y)? 1.0f : 0.0f; - TS_ASSERT_DELTA(m(x,y), expected, 0.0002f); + TS_ASSERT_DELTA(m(x,y), expected, m_Epsilon); } } } } + void test_compoundMultiplication() + { + const float invertibleData[16] = { + 2.f, -3.f, 0.f, 1.f, + -2.f, 3.f, 0.f, 0.f, + 1.f, -2.f, 1.f, 0.f, + 1.f, -1.f, 0.f, 0.f + }; + + // Invertible matrix. + CMatrix3D a(invertibleData); + CMatrix3D n; + a.GetInverse(n); + a *= n; + CMatrix3D a2(invertibleData); + n *= a2; + + TS_ASSERT_MATRIX_EQUALS_DELTA(a, n, 16, m_Epsilon); + + // Non invertible matrix. + const float nonInvertibleData[16] = { + 2.f, -3.f, 0.f, 1.f, + -2.f, 3.f, 0.f, 0.f, + 1.f, -2.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 0.f + }; + + CMatrix3D b(nonInvertibleData); + b.GetInverse(n); + b *= n; + CMatrix3D b2(nonInvertibleData); + n *= b2; + + TS_ASSERT_MATRIX_DIFFERS_DELTA(b, n, 16, m_Epsilon); + } + + void test_multiplication() + { + const float data1[16] = { + 2.f, -3.f, 0.f, 1.f, + -2.f, 3.f, 0.f, 0.f, + 1.f, -2.f, 1.f, 0.f, + 1.f, -1.f, 0.f, 0.f + }; + + const float data2[16] = { + 22.f, -3.f, 0.f, 1.f, + -2.f, 3.f, 8.f, 12.f, + 1.f, -2.f, 1.f, 0.f, + 1.f, -1.f, 16.f, 0.f + }; + + CMatrix3D mat1(data1); + CMatrix3D mat2(data2); + + CMatrix3D mat3 = mat2 * mat1; + + const float result[16] = { + 51.f, -16.f, -8.f, -34.f, + -50.f, 15.f, 24.f, 34.f, + 27.f, -11.f, -15.f, -23.f, + 24.f, -6.f, -8.f, -11.f + }; + + CMatrix3D resultMat3(result); + + TS_ASSERT_MATRIX_EQUALS_DELTA(mat3, resultMat3, 16, m_Epsilon); + + const float result2[16] = { + 51.f, -76.f, 0.f, 22.f, + 10.f, -13.f, 8.f, -2.f, + 7.f, -11.f, 1.f, 1.f, + 20.f, -38.f, 16.f, 1.f + }; + + CMatrix3D resultMat4(result2); + + CMatrix3D mat4 = mat1 * mat2; + + TS_ASSERT_MATRIX_EQUALS_DELTA(mat4, resultMat4, 16, m_Epsilon); + + mat1.Concatenate(mat2); + + TS_ASSERT_MATRIX_EQUALS_DELTA(mat1, resultMat3, 16, m_Epsilon); + + mat1 = CMatrix3D(data1); + + mat2.Concatenate(mat1); + + TS_ASSERT_MATRIX_EQUALS_DELTA(mat2, resultMat4, 16, m_Epsilon); + } + + void test_nonCommutative() + { + const float data1[16] = { + 1.f, 1.f, 1.f, 1.f, + 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f + }; + + const float data2[16] = { + 1.f, 0.f, 0.f, 0.f, + 1.f, 0.f, 0.f, 0.f, + 1.f, 0.f, 0.f, 0.f, + 1.f, 0.f, 0.f, 0.f + }; + + CMatrix3D mat1(data1); + CMatrix3D mat2(data2); + + mat1 *= mat2; + + const float result[16] = { + 1.f, 1.f, 1.f, 1.f, + 1.f, 1.f, 1.f, 1.f, + 1.f, 1.f, 1.f, 1.f, + 1.f, 1.f, 1.f, 1.f + }; + + CMatrix3D resultMat3(result); + + TS_ASSERT_MATRIX_EQUALS_DELTA(mat1, resultMat3, 16, m_Epsilon); + + const float result2[16] = { + 4.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f + }; + + CMatrix3D mat3(data1); + CMatrix3D resultMat4(result2); + mat2 *= mat3; + + TS_ASSERT_MATRIX_EQUALS_DELTA(mat2, resultMat4, 16, m_Epsilon); + } + void test_quats() { std::uniform_real_distribution distribution01(0.0f, std::nextafter(1.0f, 2.0f)); @@ -108,7 +247,7 @@ public: for (int x = 0; x < 4; ++x) for (int y = 0; y < 4; ++y) - TS_ASSERT_DELTA(a(x,y), b(x,y), 0.0002f); + TS_ASSERT_DELTA(a(x,y), b(x,y), m_Epsilon); a = m; b = m; @@ -118,7 +257,7 @@ public: for (int x = 0; x < 4; ++x) for (int y = 0; y < 4; ++y) - TS_ASSERT_DELTA(a(x,y), b(x,y), 0.0002f); + TS_ASSERT_DELTA(a(x,y), b(x,y), m_Epsilon); a = m; b = m; @@ -128,7 +267,7 @@ public: for (int x = 0; x < 4; ++x) for (int y = 0; y < 4; ++y) - TS_ASSERT_DELTA(a(x,y), b(x,y), 0.0002f); + TS_ASSERT_DELTA(a(x,y), b(x,y), m_Epsilon); } void test_getRotation() @@ -147,7 +286,7 @@ public: { float a = 2 * M_PI * distribution01(m_Engine) - M_PI; m.SetYRotation(a); - TS_ASSERT_DELTA(m.GetYRotation(), a, 0.001f); + TS_ASSERT_DELTA(m.GetYRotation(), a, m_Epsilon); } } @@ -170,6 +309,6 @@ public: for (int x = 0; x < 4; ++x) for (int y = 0; y < 4; ++y) - TS_ASSERT_DELTA(a(x,y), b(x,y), 0.0002f); + TS_ASSERT_DELTA(a(x,y), b(x,y), m_Epsilon); } };