From 4fb06d70fca79e361f98ab826b1659f3afb3c139 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 30 Sep 2024 10:58:54 -0700 Subject: [PATCH] refactored some unit tests --- include/omath/Mat.h | 304 ++++++++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/UnitTestMat.cpp | 245 ++++++++++++++++++++++++++++++++++ 3 files changed, 550 insertions(+) create mode 100644 include/omath/Mat.h create mode 100644 tests/UnitTestMat.cpp diff --git a/include/omath/Mat.h b/include/omath/Mat.h new file mode 100644 index 0000000..36789cf --- /dev/null +++ b/include/omath/Mat.h @@ -0,0 +1,304 @@ +// +// Created by vlad on 9/29/2024. +// +#pragma once +#include +#include +#include +#include "Vector3.h" +#include +#include "Angles.h" + + +namespace omath +{ + template + class Mat final + { + public: + + constexpr Mat() + { + Clear(); + } + + + constexpr Mat(const std::initializer_list>& rows) + { + if (rows.size() != Rows) + throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); + + auto rowIt = rows.begin(); + for (size_t i = 0; i < Rows; ++i, ++rowIt) + { + if (rowIt->size() != Columns) + throw std::invalid_argument("All rows must have the same number of columns as template parameter Columns"); + + auto colIt = rowIt->begin(); + for (size_t j = 0; j < Columns; ++j, ++colIt) + { + At(i, j) = *colIt; + } + } + } + + + constexpr Mat(const Mat& other) + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = other.At(i, j); + } + + + constexpr Mat(Mat&& other) noexcept + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = other.At(i, j) ; + } + + [[nodiscard]] + static constexpr size_t RowCount() noexcept { return Rows; } + + [[nodiscard]] + static constexpr size_t ColumnsCount() noexcept { return Columns; } + + [[nodiscard]] + constexpr std::pair Size() const noexcept { return { Rows, Columns }; } + + + [[nodiscard]] constexpr const float& At(const size_t rowIndex, const size_t columnIndex) const + { + if (rowIndex >= Rows || columnIndex >= Columns) + throw std::out_of_range("Index out of range"); + + return m_data[rowIndex * Columns + columnIndex]; + } + [[nodiscard]] constexpr float& At(const size_t rowIndex, const size_t columnIndex) + { + return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); + } + [[nodiscard]] + constexpr float Sum() const + { + float sum = 0.f; + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + sum += At(i, j); + + return sum; + } + + constexpr void Clear() + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = 0.f; + } + + // Operator overloading for multiplication with another Mat + template + constexpr Mat operator*(const Mat& other) const + { + Mat result; + + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < OtherColumns; ++j) + { + float sum = 0.f; + for (size_t k = 0; k < Columns; ++k) + sum += At(i, k) * other.At(k, j); + result.At(i, j) = sum; + } + return result; + } + + constexpr Mat& operator*=(float f) + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) *= f; + return *this; + } + + constexpr Mat operator*(float f) const + { + Mat result(*this); + result *= f; + return result; + } + + constexpr Mat& operator/=(float f) + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) /= f; + return *this; + } + + constexpr Mat operator/(float f) const + { + Mat result(*this); + result /= f; + return result; + } + + constexpr Mat& operator=(const Mat& other) + { + if (this == &other) + return *this; + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = other.At(i, j); + return *this; + } + + constexpr Mat& operator=(Mat&& other) noexcept + { + if (this == &other) + return *this; + + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = other.At(i, j); + + return *this; + } + + [[nodiscard]] + constexpr Mat Transpose() const + { + Mat transposed; + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + transposed.At(j, i) = At(i, j); + + return transposed; + } + + [[nodiscard]] + constexpr float Determinant() const + { + static_assert(Rows == Columns, "Determinant is only defined for square matrices."); + + if constexpr (Rows == 1) + return At(0, 0); + + else if constexpr (Rows == 2) + return At(0, 0) * At(1, 1) - At(0, 1) * At(1, 0); + else + { + float det = 0.f; + for (size_t i = 0; i < Columns; ++i) + { + const float cofactor = (i % 2 == 0 ? 1.f : -1.f) * At(0, i) * Minor(0, i).Determinant(); + det += cofactor; + } + return det; + } + } + + [[nodiscard]] + constexpr Mat Minor(size_t row, size_t column) const + { + Mat result; + for (size_t i = 0, m = 0; i < Rows; ++i) + { + if (i == row) + continue; + for (size_t j = 0, n = 0; j < Columns; ++j) + { + if (j == column) + continue; + result.At(m, n) = At(i, j); + ++n; + } + ++m; + } + return result; + } + + [[nodiscard]] + std::string ToString() const + { + std::ostringstream oss; + for (size_t i = 0; i < Rows; ++i) + { + for (size_t j = 0; j < Columns; ++j) + { + oss << At(i, j); + if (j != Columns - 1) + oss << ' '; + } + oss << '\n'; + } + return oss.str(); + } + + // Static methods that return fixed-size matrices + [[nodiscard]] + constexpr static Mat<4, 4> ToScreenMat(float screenWidth, float screenHeight) + { + Mat<4, 4> mat; + mat.At(0, 0) = screenWidth / 2.f; + mat.At(1, 1) = -screenHeight / 2.f; + mat.At(2, 2) = 1.f; + mat.At(3, 0) = screenWidth / 2.f; + mat.At(3, 1) = screenHeight / 2.f; + mat.At(3, 3) = 1.f; + return mat; + } + + [[nodiscard]] + constexpr static Mat<4, 4> TranslationMat(const Vector3& diff) + { + Mat<4, 4> mat; + mat.At(0, 0) = 1.f; + mat.At(1, 1) = 1.f; + mat.At(2, 2) = 1.f; + mat.At(3, 3) = 1.f; + mat.At(3, 0) = diff.x; + mat.At(3, 1) = diff.y; + mat.At(3, 2) = diff.z; + return mat; + } + + [[nodiscard]] + constexpr static Mat<4, 4> OrientationMat(const Vector3& forward, const Vector3& right, const Vector3& up) + { + Mat<4, 4> mat; + + mat.At(0, 0) = right.x; + mat.At(0, 1) = up.x; + mat.At(0, 2) = forward.x; + mat.At(1, 0) = right.y; + mat.At(1, 1) = up.y; + mat.At(1, 2) = forward.y; + mat.At(2, 0) = right.z; + mat.At(2, 1) = up.z; + mat.At(2, 2) = forward.z; + mat.At(3, 3) = 1.f; + + return mat; + } + + [[nodiscard]] + constexpr static Mat<4, 4> ProjectionMat(const float fieldOfView, const float aspectRatio, const float near, const float far) + { + Mat<4, 4> mat; + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + mat.At(0, 0) = 1.f / (aspectRatio * fovHalfTan); + mat.At(1, 1) = 1.f / fovHalfTan; + mat.At(2, 2) = (far + near) / (far - near); + mat.At(2, 3) = (2.f * near * far) / (far - near); + mat.At(3, 2) = -1.f; + + return mat; + } + + private: + std::array m_data; + }; +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 920fae4..90f0bea 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,7 @@ include(GoogleTest) add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp + UnitTestMat.cpp UnitTestAstar.cpp UnitTestProjection.cpp UnitTestVector3.cpp diff --git a/tests/UnitTestMat.cpp b/tests/UnitTestMat.cpp new file mode 100644 index 0000000..7a0048c --- /dev/null +++ b/tests/UnitTestMat.cpp @@ -0,0 +1,245 @@ +// UnitTestMat.cpp +#include +#include "omath/Mat.h" +#include "omath/Vector3.h" + +using namespace omath; + +class UnitTestMat : public ::testing::Test +{ +protected: + Mat<2, 2> m1; + Mat<2, 2> m2; + + void SetUp() override + { + m1 = Mat<2, 2>(); + m2 = Mat<2, 2>{{1.0f, 2.0f}, {3.0f, 4.0f}}; + } +}; + +// Test constructors +TEST_F(UnitTestMat, Constructor_Default) +{ + Mat<3, 3> m; + EXPECT_EQ(m.RowCount(), 3); + EXPECT_EQ(m.ColumnsCount(), 3); + for (size_t i = 0; i < 3; ++i) + for (size_t j = 0; j < 3; ++j) + EXPECT_FLOAT_EQ(m.At(i, j), 0.0f); +} + +TEST_F(UnitTestMat, Constructor_InitializerList) +{ + constexpr Mat<2, 2> m{{1.0f, 2.0f}, {3.0f, 4.0f}}; + EXPECT_EQ(m.RowCount(), 2); + EXPECT_EQ(m.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m.At(0, 1), 2.0f); + EXPECT_FLOAT_EQ(m.At(1, 0), 3.0f); + EXPECT_FLOAT_EQ(m.At(1, 1), 4.0f); +} + +TEST_F(UnitTestMat, Constructor_Copy) +{ + Mat<2, 2> m3 = m2; + EXPECT_EQ(m3.RowCount(), m2.RowCount()); + EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); + EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); + EXPECT_FLOAT_EQ(m3.At(1, 1), m2.At(1, 1)); +} + +TEST_F(UnitTestMat, Constructor_Move) +{ + Mat<2, 2> m3 = std::move(m2); + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 4.0f); + // m2 is in a valid but unspecified state after move +} + +// Test matrix operations +TEST_F(UnitTestMat, Operator_Multiplication_Matrix) +{ + Mat<2, 2> m3 = m2 * m2; + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 7.0f); + EXPECT_FLOAT_EQ(m3.At(0, 1), 10.0f); + EXPECT_FLOAT_EQ(m3.At(1, 0), 15.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 22.0f); +} + +TEST_F(UnitTestMat, Operator_Multiplication_Scalar) +{ + Mat<2, 2> m3 = m2 * 2.0f; + EXPECT_FLOAT_EQ(m3.At(0, 0), 2.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 8.0f); +} + +TEST_F(UnitTestMat, Operator_Division_Scalar) +{ + Mat<2, 2> m3 = m2 / 2.0f; + EXPECT_FLOAT_EQ(m3.At(0, 0), 0.5f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 2.0f); +} + +// Test matrix functions +TEST_F(UnitTestMat, Transpose) +{ + Mat<2, 2> m3 = m2.Transpose(); + EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); + EXPECT_FLOAT_EQ(m3.At(0, 1), m2.At(1, 0)); + EXPECT_FLOAT_EQ(m3.At(1, 0), m2.At(0, 1)); + EXPECT_FLOAT_EQ(m3.At(1, 1), m2.At(1, 1)); +} + +TEST_F(UnitTestMat, Determinant) +{ + const float det = m2.Determinant(); + EXPECT_FLOAT_EQ(det, -2.0f); +} + +TEST_F(UnitTestMat, Sum) +{ + const float sum = m2.Sum(); + EXPECT_FLOAT_EQ(sum, 10.0f); +} + +TEST_F(UnitTestMat, Clear) +{ + m2.Clear(); + for (size_t i = 0; i < m2.RowCount(); ++i) + for (size_t j = 0; j < m2.ColumnsCount(); ++j) + EXPECT_FLOAT_EQ(m2.At(i, j), 0.0f); +} + +TEST_F(UnitTestMat, ToString) +{ + const std::string str = m2.ToString(); + EXPECT_FALSE(str.empty()); + EXPECT_EQ(str, "1 2\n3 4\n"); +} + +// Test assignment operators +TEST_F(UnitTestMat, AssignmentOperator_Copy) +{ + Mat<2, 2> m3; + m3 = m2; + EXPECT_EQ(m3.RowCount(), m2.RowCount()); + EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); + EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); +} + +TEST_F(UnitTestMat, AssignmentOperator_Move) +{ + Mat<2, 2> m3; + m3 = std::move(m2); + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 4.0f); + // m2 is in a valid but unspecified state after move +} + +// Test static methods +TEST_F(UnitTestMat, StaticMethod_ToScreenMat) +{ + Mat<4, 4> screenMat = Mat<4, 4>::ToScreenMat(800.0f, 600.0f); + EXPECT_FLOAT_EQ(screenMat.At(0, 0), 400.0f); + EXPECT_FLOAT_EQ(screenMat.At(1, 1), -300.0f); + EXPECT_FLOAT_EQ(screenMat.At(3, 0), 400.0f); + EXPECT_FLOAT_EQ(screenMat.At(3, 1), 300.0f); + EXPECT_FLOAT_EQ(screenMat.At(3, 3), 1.0f); +} + +// Test static method: TranslationMat +TEST_F(UnitTestMat, StaticMethod_TranslationMat) +{ + Vector3 diff{10.0f, 20.0f, 30.0f}; + Mat<4, 4> transMat = Mat<4, 4>::TranslationMat(diff); + EXPECT_FLOAT_EQ(transMat.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(transMat.At(3, 0), diff.x); + EXPECT_FLOAT_EQ(transMat.At(3, 1), diff.y); + EXPECT_FLOAT_EQ(transMat.At(3, 2), diff.z); + EXPECT_FLOAT_EQ(transMat.At(3, 3), 1.0f); +} + +// Test static method: OrientationMat +TEST_F(UnitTestMat, StaticMethod_OrientationMat) +{ + constexpr Vector3 forward{0.0f, 0.0f, 1.0f}; + constexpr Vector3 right{1.0f, 0.0f, 0.0f}; + constexpr Vector3 up{0.0f, 1.0f, 0.0f}; + constexpr Mat<4, 4> orientMat = Mat<4, 4>::OrientationMat(forward, right, up); + EXPECT_FLOAT_EQ(orientMat.At(0, 0), right.x); + EXPECT_FLOAT_EQ(orientMat.At(0, 1), up.x); + EXPECT_FLOAT_EQ(orientMat.At(0, 2), forward.x); + EXPECT_FLOAT_EQ(orientMat.At(1, 0), right.y); + EXPECT_FLOAT_EQ(orientMat.At(1, 1), up.y); + EXPECT_FLOAT_EQ(orientMat.At(1, 2), forward.y); + EXPECT_FLOAT_EQ(orientMat.At(2, 0), right.z); + EXPECT_FLOAT_EQ(orientMat.At(2, 1), up.z); + EXPECT_FLOAT_EQ(orientMat.At(2, 2), forward.z); +} + +// Test static method: ProjectionMat +TEST_F(UnitTestMat, StaticMethod_ProjectionMat) +{ + constexpr float fieldOfView = 45.0f; + constexpr float aspectRatio = 1.33f; + constexpr float near = 0.1f; + constexpr float far = 100.0f; + const Mat<4, 4> projMat = Mat<4, 4>::ProjectionMat(fieldOfView, aspectRatio, near, far); + + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + EXPECT_FLOAT_EQ(projMat.At(0, 0), 1.f / (aspectRatio * fovHalfTan)); + EXPECT_FLOAT_EQ(projMat.At(1, 1), 1.f / fovHalfTan); + EXPECT_FLOAT_EQ(projMat.At(2, 2), (far + near) / (far - near)); + EXPECT_FLOAT_EQ(projMat.At(2, 3), (2.f * near * far) / (far - near)); + EXPECT_FLOAT_EQ(projMat.At(3, 2), -1.f); +} + +// Test exception handling in At() method +TEST_F(UnitTestMat, Method_At_OutOfRange) +{ + EXPECT_THROW(std::ignore = m2.At(2, 0), std::out_of_range); + EXPECT_THROW(std::ignore = m2.At(0, 2), std::out_of_range); +} + +// Test Determinant for 3x3 matrix +TEST(UnitTestMatStandalone, Determinant_3x3) +{ + constexpr auto det = Mat<3, 3>{{6, 1, 1}, {4, -2, 5}, {2, 8, 7}}.Determinant(); + EXPECT_FLOAT_EQ(det, -306.0f); +} + +// Test Minor for 3x3 matrix +TEST(UnitTestMatStandalone, Minor_3x3) +{ + constexpr Mat<3, 3> m{{3, 0, 2}, {2, 0, -2}, {0, 1, 1}}; + auto minor = m.Minor(0, 0); + EXPECT_EQ(minor.RowCount(), 2); + EXPECT_EQ(minor.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(minor.At(0, 0), 0.0f); + EXPECT_FLOAT_EQ(minor.At(0, 1), -2.0f); + EXPECT_FLOAT_EQ(minor.At(1, 0), 1.0f); + EXPECT_FLOAT_EQ(minor.At(1, 1), 1.0f); +} + +// Test Transpose for non-square matrix +TEST(UnitTestMatStandalone, Transpose_NonSquare) +{ + constexpr Mat<2, 3> m{{1.0f, 2.0f, 3.0f}, {4.0f, 5.0f, 6.0f}}; + auto transposed = m.Transpose(); + EXPECT_EQ(transposed.RowCount(), 3); + EXPECT_EQ(transposed.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(transposed.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(transposed.At(1, 0), 2.0f); + EXPECT_FLOAT_EQ(transposed.At(2, 0), 3.0f); + EXPECT_FLOAT_EQ(transposed.At(0, 1), 4.0f); + EXPECT_FLOAT_EQ(transposed.At(1, 1), 5.0f); + EXPECT_FLOAT_EQ(transposed.At(2, 1), 6.0f); +}