diff --git a/src/core/include/pw/core/math.hpp b/src/core/include/pw/core/math.hpp index 61cd9e3..0330f6b 100644 --- a/src/core/include/pw/core/math.hpp +++ b/src/core/include/pw/core/math.hpp @@ -25,6 +25,8 @@ #include #include +#include + //#include //#include diff --git a/src/core/include/pw/core/matrix.hpp b/src/core/include/pw/core/matrix.hpp index 0072d6c..96260b5 100644 --- a/src/core/include/pw/core/matrix.hpp +++ b/src/core/include/pw/core/matrix.hpp @@ -26,9 +26,193 @@ #include #include #include +#include namespace pw { +template +struct matrix final { + static_assert(Rows > 0 && Cols > 0, "undefined matrix type"); + + static constexpr unsigned int diag_size{std::min(Rows, Cols)}; + static constexpr unsigned int rows{Rows}; + static constexpr unsigned int cols{Cols}; + static constexpr bool is_square{Rows == Cols}; + + using diag_type = vector; + using transpose_type = matrix; + using minor_type = matrix; + using column_type = vector; + + vector, Rows> m_{}; + + auto&& data(this auto&& self) { + return std::forward(self).m_[0].data(); + } + + auto&& operator[](this auto&& self, int r) { + return std::forward(self).m_[r]; + } + + constexpr auto diagonal() const noexcept -> diag_type { + return [this](std::index_sequence) { + return diag_type{(*this)[Is][Is]...}; + }(std::make_index_sequence{}); + } + + constexpr auto trace() const noexcept -> Scalar { + static_assert(Rows == Cols, + "trace is only defined for square matrices"); + return [this, sum = Scalar{0}]( + std::index_sequence) mutable { + return ((*this)[Is][Is] + ... + sum); + }(std::make_index_sequence{}); + } + + constexpr auto + column(std::unsigned_integral auto c) const noexcept -> column_type { + return [this, &c](std::index_sequence) { + return column_type{(*this)[Rs][c]...}; + }(std::make_index_sequence{}); + } + + constexpr auto transposed() const noexcept -> transpose_type { + return [this](std::index_sequence) { + return transpose_type{(*this).column(Cs)...}; + }(std::make_index_sequence{}); + } + + static constexpr auto all(const Scalar& value) { + return [&value](std::index_sequence) { + return matrix{vector::all((value) + Rs - Rs)...}; + }(std::make_index_sequence{}); + } + + static constexpr auto identity() noexcept -> matrix { + static_assert(Rows == Cols, + "identity only defined for square matrices"); + return [](std::index_sequence) { + return matrix{vector::basis(Rs)...}; + }(std::make_index_sequence{}); + } + + constexpr auto minor(std::unsigned_integral auto r0, + std::unsigned_integral auto c0) const noexcept { + static_assert(Rows > 1 && Cols > 1, "cannot create minor matrix"); + return [this, &r0, &c0](std::index_sequence) { + return matrix{ + (*this)[(Rs < r0) ? Rs : Rs + 1].minor(c0)...}; + }(std::make_index_sequence{}); + } + + constexpr auto determinant() const noexcept -> Scalar { + static_assert(Rows == Cols, + "determinant only defined for square matrices"); + return [this](std::index_sequence) { + return ((((Cs % 2 == 0) ? (*this)[0][Cs] : -(*this)[0][Cs]) * + (*this).minor(0u, Cs).determinant()) + + ...); + }(std::make_index_sequence{}); + } + + constexpr auto inverse() const noexcept -> matrix { + const auto inv_det{Scalar(1) / this->determinant()}; + matrix inv; // no initialization needed! + for (auto c = 0u; c < Cols; c++) { + for (auto r = 0u; r < Rows; r++) { + const auto min_det = this->minor(r, c).determinant(); + const auto co_fact{((r + c) % 2 == 1) ? -min_det : min_det}; + inv[r][c] = inv_det * co_fact; + } + } + return inv; + } + + constexpr matrix operator*(const Scalar& v) const noexcept { + return [this, &v](std::index_sequence) { + return matrix{((*this)[Rs] * v)...}; + }(std::make_index_sequence{}); + } + constexpr matrix operator/(const Scalar& v) const noexcept { + return operator*(Scalar{1} / v); + } + + constexpr matrix operator+(const Scalar& v) const noexcept { + return [this, &v](std::index_sequence) { + return matrix{((*this)[Rs] + v)...}; + }(std::make_index_sequence{}); + } + constexpr matrix operator-(const Scalar& v) const noexcept { + return operator+(-v); + } + + constexpr auto operator*=(const Scalar& v) noexcept { + [this, &v](std::index_sequence) { + (((*this)[Rs] *= v), ...); + }(std::make_index_sequence{}); + return *this; + } + + constexpr auto operator/=(const Scalar& v) noexcept { + return operator*=(Scalar{1} / v); + } + + constexpr auto operator+=(const Scalar& v) noexcept { + [this, &v](std::index_sequence) { + (((*this)[Rs] += v), ...); + }(std::make_index_sequence{}); + return *this; + } + + constexpr auto operator-=(const Scalar& v) noexcept { + return operator+=(-v); + } +}; + +template <> constexpr float matrix::determinant() const noexcept { + return (*this)[0][0]; +} + +template <> +constexpr double matrix::determinant() const noexcept { + return (*this)[0][0]; +} + +template +constexpr auto operator*(const matrix& A, + const matrix& B) noexcept { + matrix result{}; + [&](std::index_sequence) { + ( + [&](auto&& Ai, std::index_sequence) { + ( + [&](auto&& Ai, auto&& Bi, + std::index_sequence) { + ((result[Ai][Bi] += A[Ai][iI] * B[iI][Bi]), + ...); // do the actual multiplication + }(Ai, Ib, std::make_index_sequence{}), + ...); // forward row of A, colum of B and unpack inner loop + }(Ia, std::make_index_sequence{}), + ...); // forward current row, unpack A + }(std::make_index_sequence{}); // rows of A as input + return result; +} + +template +constexpr auto operator*(const matrix& A, + const vector& B) noexcept { + // first step - should move to concepts to allow for std::array and + // std::span to be allowed + vector, Ca> result{}; + [&](std::index_sequence) { + ( + [&](auto&& Ai, std::index_sequence) { + ((result[Ib] += A[Ai][Ib] * B[Ib]), ...); + }(Ia, std::make_index_sequence{}), + ...); // forward current row, unpack A + }(std::make_index_sequence{}); // rows of A as input + return result; +} } // namespace pw diff --git a/src/core/include/pw/core/point.hpp b/src/core/include/pw/core/point.hpp index 5a541ca..d01e05f 100644 --- a/src/core/include/pw/core/point.hpp +++ b/src/core/include/pw/core/point.hpp @@ -34,9 +34,6 @@ struct point_ { T_ x {0}, y {0}; - point_() = default; - point_(T_ x_,T_ y_) : x(x_), y(y_) {} - template point_ cast() const { return point_(static_cast(x),static_cast(y)); } diff --git a/src/core/include/pw/core/vector.hpp b/src/core/include/pw/core/vector.hpp index 4f31032..de03827 100644 --- a/src/core/include/pw/core/vector.hpp +++ b/src/core/include/pw/core/vector.hpp @@ -23,6 +23,8 @@ #ifndef PW_CORE_VECTOR_HPP #define PW_CORE_VECTOR_HPP +#include + namespace pw { template struct vector final { @@ -112,10 +114,56 @@ template struct vector final { return vector{{(*this)[Ss] - v[Ss]...}}; }(std::make_index_sequence{}); } + + constexpr vector& operator*=(const Scalar& v) noexcept { + [this, &v](std::index_sequence) { + (((*this)[Ss] *= v), ...); + }(std::make_index_sequence{}); + return *this; + } + + constexpr vector& operator/=(const Scalar& v) noexcept { + return operator*=(Scalar{1} / v); + } + + constexpr vector& operator+=(const Scalar& v) noexcept { + [this, &v](std::index_sequence) { + (((*this)[Ss] += v), ...); + }(std::make_index_sequence{}); + return *this; + } + + constexpr vector& operator-=(const Scalar& v) noexcept { + return operator+=(-v); + } + + constexpr auto project() const noexcept -> vector { + return [this](std::index_sequence) { + return vector{ + {(*this)[Ss] / (*this)[size - 1]...}}; + }(std::make_index_sequence{}); + } + + constexpr auto + unproject(auto&& w) const noexcept -> vector { + return [this, &w](std::index_sequence) { + return vector{{(Ss < size) ? (*this)[Ss] : w...}}; + }(std::make_index_sequence{}); + } + + template + constexpr auto cast() const noexcept { + return [this](std::index_sequence) { + return vector{ ScalarCast{(*this)[Ss]}...}; + }(std::make_index_sequence{}); + } }; -template -struct vector2 : vector { +// deduction guide for vector +template > +vector(U...) -> vector; + +template struct vector2 : vector { constexpr const Scalar& x() const { return (*this)[0]; } constexpr Scalar& x() { return (*this)[0]; } @@ -159,18 +207,15 @@ template struct vector4 : vector { constexpr const Scalar& w() const { return (*this)[3]; } constexpr Scalar& w() { return (*this)[3]; } - constexpr auto xyz() const { return base_type::swizzle(0,1,2); } + constexpr auto xyz() const { return base_type::swizzle(0, 1, 2); } }; +using vector2f = vector2; +using vector2d = vector2; +using vector2i = vector2; - -using vector2f = vector2; -using vector2d = vector2; -using vector2i = vector2; - -using vector3f = vector3; -using vector3d = vector3; - +using vector3f = vector3; +using vector3d = vector3; } // namespace pw diff --git a/src/core/tests/CMakeLists.txt b/src/core/tests/CMakeLists.txt index a358baf..600d6b3 100644 --- a/src/core/tests/CMakeLists.txt +++ b/src/core/tests/CMakeLists.txt @@ -7,8 +7,8 @@ macro(make_test arg1) endmacro() -# make_test(pwcore_test_matrix) -# make_test(pwcore_test_vector) +make_test(pwcore_test_matrix) +make_test(pwcore_test_vector) # make_test(pwcore_test_axisangle) # make_test(pwcore_test_quaternion) # make_test(pwcore_test_transform_tools) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a654f1f..8eca65c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,12 +10,9 @@ set(pixwerx_test_files pw_test_core.cpp ) - - - add_executable(pixwerx_tests ${pixwerx_test_files}) -target_link_libraries(pixwerx_tests PRIVATE +target_link_libraries(pixwerx_tests PRIVATE snitch::snitch pwcore ) diff --git a/tests/pw_test_core.cpp b/tests/pw_test_core.cpp index 9222b6c..bbd009a 100644 --- a/tests/pw_test_core.cpp +++ b/tests/pw_test_core.cpp @@ -3,42 +3,55 @@ #include #include - - TEST_CASE("core", "[matrix]") { - auto m44 = pw::matrix{}; + auto mat = pw::matrix{}; - REQUIRE(m44[0][0] == 0); - REQUIRE(m44[0][1] == 0); - REQUIRE(m44[1][0] == 0); - REQUIRE(m44[1][1] == 0); + REQUIRE(mat[0][0] == 0); + REQUIRE(mat[0][1] == 0); + REQUIRE(mat[1][0] == 0); + REQUIRE(mat[1][1] == 0); - // auto m44_1 = pw::matrix<2, 2, float>{ 5, 4, 3, 2 }; + auto mat_1 = pw::matrix{ + pw::vector{5.f, 4}, + pw::vector{3.f, 2} + }; - // REQUIRE(m44_1(0, 0) == 5); - // REQUIRE(m44_1(0, 1) == 3); - // REQUIRE(m44_1(1, 0) == 4); - // REQUIRE(m44_1(1, 1) == 2); + REQUIRE(mat_1[0][0] == 5); + REQUIRE(mat_1[0][1] == 4); + REQUIRE(mat_1[1][0] == 3); + REQUIRE(mat_1[1][1] == 2); - // auto m44_2 = pw::matrix2x2_::all(42.42f); + const auto val {42.42f}; - // REQUIRE(m44_2(0, 0) == 42.42f); - // REQUIRE(m44_2(0, 1) == 42.42f); - // REQUIRE(m44_2(1, 0) == 42.42f); - // REQUIRE(m44_2(1, 1) == 42.42f); + auto mat_2 = pw::matrix::all(val); - // m44 *= 2; - // REQUIRE(m44(0, 0) == 84.84f); - // REQUIRE(m44(0, 1) == 84.84f); - // REQUIRE(m44(1, 0) == 84.84f); - // REQUIRE(m44(1, 1) == 84.84f); + REQUIRE(mat_2[0][0] == val); + REQUIRE(mat_2[0][1] == val); + REQUIRE(mat_2[1][0] == val); + REQUIRE(mat_2[1][1] == val); - // m44.set_identity(); - // REQUIRE(m44(0, 0) == 1.0f); - // REQUIRE(m44(0, 1) == 0.0f); - // REQUIRE(m44(1, 0) == 0.0f); - // REQUIRE(m44(1, 1) == 1.0f); + mat_2 *= 2; + REQUIRE(mat_2[0][0] == (val * 2)); + REQUIRE(mat_2[0][1] == (val * 2)); + REQUIRE(mat_2[1][0] == (val * 2)); + REQUIRE(mat_2[1][1] == (val * 2)); + + + mat_2 = pw::matrix::identity(); + + REQUIRE(mat_2[0][0] == 1.0f); + REQUIRE(mat_2[0][1] == 0.0f); + REQUIRE(mat_2[1][0] == 0.0f); + REQUIRE(mat_2[1][1] == 1.0f); + + + auto mat_2_inv = mat_2.inverse(); + + REQUIRE(mat_2_inv[0][0] == 1.0f); + REQUIRE(mat_2_inv[0][1] == 0.0f); + REQUIRE(mat_2_inv[1][0] == 0.0f); + REQUIRE(mat_2_inv[1][1] == 1.0f); // auto m44_2 = pw::matrix2x2::make(0, 1, 2, 3); @@ -60,7 +73,14 @@ TEST_CASE("core", "[matrix]") { TEST_CASE("core", "[vector.matrix]") { - // auto vec4_1 = pw::vector4_{}; + auto vec4_1 = pw::vector4{}; + + REQUIRE(vec4_1[0] == 0.0f); + REQUIRE(vec4_1[1] == 0.0f); + REQUIRE(vec4_1[2] == 0.0f); + REQUIRE(vec4_1[3] == 0.0f); + + // auto mat4_1 = pw::matrix_<4,1,float>{};