From adcf7612434d573085f97b6bbd002640cf0a3043 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sun, 26 Jan 2025 19:03:33 +0300 Subject: [PATCH] Support dynamic size in math::vector --- .../math/include/psemek/math/detail/array.hpp | 9 ++ libs/math/include/psemek/math/dynamic.hpp | 27 +++++ libs/math/include/psemek/math/vector.hpp | 110 ++++++++++++------ libs/math/tests/vector.cpp | 38 ++++++ 4 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 libs/math/include/psemek/math/dynamic.hpp diff --git a/libs/math/include/psemek/math/detail/array.hpp b/libs/math/include/psemek/math/detail/array.hpp index db36bbcb..ca6e2e79 100644 --- a/libs/math/include/psemek/math/detail/array.hpp +++ b/libs/math/include/psemek/math/detail/array.hpp @@ -1,7 +1,10 @@ #pragma once +#include #include +#include + namespace psemek::math::detail { @@ -31,6 +34,12 @@ namespace psemek::math::detail }; }; + template + struct array + { + using type = std::vector; + }; + template struct all_convertible_to; diff --git a/libs/math/include/psemek/math/dynamic.hpp b/libs/math/include/psemek/math/dynamic.hpp new file mode 100644 index 00000000..1a18b928 --- /dev/null +++ b/libs/math/include/psemek/math/dynamic.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +namespace psemek::math +{ + + constexpr std::size_t dynamic = static_cast(-1); + + struct dynamic_size_mismatch + : util::exception + { + dynamic_size_mismatch(std::size_t size1, std::size_t size2, util::stacktrace stacktrace = {}) + : util::exception(std::format("Dynamic array size mismatch: {} != {}", size1, size2), std::move(stacktrace)) + {} + }; + + inline void check_dynamic_size(std::size_t size1, std::size_t size2) + { + if (size1 != size2) + throw dynamic_size_mismatch(size1, size2); + } + +} diff --git a/libs/math/include/psemek/math/vector.hpp b/libs/math/include/psemek/math/vector.hpp index 5573b748..a3c79def 100644 --- a/libs/math/include/psemek/math/vector.hpp +++ b/libs/math/include/psemek/math/vector.hpp @@ -2,12 +2,12 @@ #include #include +#include #include #include #include #include -#include #include #include @@ -24,15 +24,31 @@ namespace psemek::math vector() = default; + explicit vector(std::size_t size) requires (N != dynamic) + { + assert(size == N); + } + + explicit vector(std::size_t size) requires (N == dynamic) + : coords(size, T{}) + {} + template - requires((sizeof...(Args) == N) && detail::all_convertible_to::value) + requires((sizeof...(Args) == N || N == dynamic) && detail::all_convertible_to::value) vector(Args && ... args) : coords{ static_cast(std::forward(args))... } {} std::size_t dimension() const { - return N; + if constexpr (N == dynamic) + { + return coords.size(); + } + else + { + return N; + } } T & operator[](std::size_t i) @@ -51,7 +67,8 @@ namespace psemek::math vector & operator += (vector const & v); vector & operator -= (vector const & v); - static vector zero(); + static vector zero() requires (N != dynamic); + static vector zero(std::size_t size); }; template @@ -60,8 +77,8 @@ namespace psemek::math template vector cast(vector const & v) { - vector r; - for (std::size_t i = 0; i < N; ++i) + vector r(v.dimension()); + for (std::size_t i = 0; i < v.dimension(); ++i) r[i] = static_cast(v[i]); return r; } @@ -69,7 +86,9 @@ namespace psemek::math template std::strong_ordering operator <=> (vector const & v1, vector const & v2) { - for (std::size_t i = 0; i < N; ++i) + check_dynamic_size(v1.dimension(), v2.dimension()); + + for (std::size_t i = 0; i < v1.dimension(); ++i) { if (v1[i] < v2[i]) return std::strong_ordering::less; @@ -118,8 +137,8 @@ namespace psemek::math template vector operator * (vector const & v, T const & s) { - vector r; - for (std::size_t i = 0; i < N; ++i) + vector r(v.dimension()); + for (std::size_t i = 0; i < v.dimension(); ++i) r[i] = v[i] * s; return r; } @@ -127,8 +146,8 @@ namespace psemek::math template vector operator * (T const & s, vector const & v) { - vector r; - for (std::size_t i = 0; i < N; ++i) + vector r(v.dimension()); + for (std::size_t i = 0; i < v.dimension(); ++i) r[i] = s * v[i]; return r; } @@ -136,8 +155,8 @@ namespace psemek::math template vector operator / (vector const & v, T const & s) { - vector r; - for (std::size_t i = 0; i < N; ++i) + vector r(v.dimension()); + for (std::size_t i = 0; i < v.dimension(); ++i) r[i] = v[i] / s; return r; } @@ -145,8 +164,8 @@ namespace psemek::math template vector operator - (vector const & v) { - vector r; - for (std::size_t i = 0; i < N; ++i) + vector r(v.dimension()); + for (std::size_t i = 0; i < v.dimension(); ++i) r[i] = -v[i]; return r; } @@ -154,8 +173,10 @@ namespace psemek::math template vector operator + (vector const & v1, vector const & v2) { - vector r; - for (std::size_t i = 0; i < N; ++i) + check_dynamic_size(v1.dimension(), v2.dimension()); + + vector r(v1.dimension()); + for (std::size_t i = 0; i < v1.dimension(); ++i) r[i] = v1[i] + v2[i]; return r; } @@ -163,8 +184,10 @@ namespace psemek::math template vector operator - (vector const & v1, vector const & v2) { - vector r; - for (std::size_t i = 0; i < N; ++i) + check_dynamic_size(v1.dimension(), v2.dimension()); + + vector r(v1.dimension()); + for (std::size_t i = 0; i < v1.dimension(); ++i) r[i] = v1[i] - v2[i]; return r; } @@ -194,7 +217,7 @@ namespace psemek::math } template - vector vector::zero() + vector vector::zero() requires (N != dynamic) { vector r; for (std::size_t i = 0; i < N; ++i) @@ -202,11 +225,27 @@ namespace psemek::math return r; } + template + vector vector::zero(std::size_t size) + { + if constexpr (N != dynamic) + { + assert(N == size); + } + + vector r(size); + for (std::size_t i = 0; i < size; ++i) + r[i] = T(0); + return r; + } + template T dot(vector const & v1, vector const & v2) { + check_dynamic_size(v1.dimension(), v2.dimension()); + T r{}; - for (std::size_t i = 0; i < N; ++i) + for (std::size_t i = 0; i < v1.dimension(); ++i) r += v1[i] * v2[i]; return r; } @@ -223,7 +262,7 @@ namespace psemek::math using std::abs; using std::max; T r = abs(v[0]); - for (std::size_t i = 1; i < N; ++i) + for (std::size_t i = 1; i < v.dimension(); ++i) r = max(r, abs(v[i])); return r; } @@ -279,10 +318,10 @@ namespace psemek::math template vector ort(vector const & v) { - vector result; + vector result = vector::zero(v.dimension()); if constexpr ((N % 2) == 0) { - for (std::size_t i = 0; i < N; i += 2) + for (std::size_t i = 0; i < v.dimension(); i += 2) { result[i] = -v[i + 1]; result[i + 1] = v[i]; @@ -294,7 +333,7 @@ namespace psemek::math using std::abs; - for (std::size_t k = 1; k < N; ++k) + for (std::size_t k = 1; k < v.dimension(); ++k) { if (abs(v[k]) > abs(v[i])) { @@ -303,7 +342,6 @@ namespace psemek::math } } - result = vector::zero(); result[i] = -v[j]; result[j] = v[i]; } @@ -357,14 +395,16 @@ namespace psemek::math return {v[0] * c - v[1] * s, v[0] * s + v[1] * c}; } - template - auto pointwise(F && f, vector const & ... vs) + template + auto pointwise(F && f, vector const & v0, vector const & ... vs) { - using R = decltype(f(vs[0]...)); + using R = decltype(f(v0[0], vs[0]...)); - vector result; - for (std::size_t i = 0; i < N; ++i) - result[i] = f(vs[i]...); + (check_dynamic_size(v0.dimension(), vs.dimension()), ...); + + vector result(v0.dimension()); + for (std::size_t i = 0; i < v0.dimension(); ++i) + result[i] = f(v0[i], vs[i]...); return result; } @@ -448,7 +488,7 @@ namespace psemek::math } os << '(' << v[0]; - for (std::size_t i = 1; i < N; ++i) + for (std::size_t i = 1; i < v.dimension(); ++i) os << ", " << v[i]; os << ')'; return os; @@ -458,7 +498,7 @@ namespace psemek::math bool isfinite(vector const & v) { using std::isfinite; - for (std::size_t i = 0; i < N; ++i) + for (std::size_t i = 0; i < v.dimension(); ++i) if (!isfinite(v[i])) return false; return true; @@ -476,7 +516,7 @@ namespace std { std::hash h; std::uint64_t r = 0; - for (std::size_t i = 0; i < N; ++i) + for (std::size_t i = 0; i < v.dimension(); ++i) ::psemek::util::hash_combine(r, h(v[i])); return r; } diff --git a/libs/math/tests/vector.cpp b/libs/math/tests/vector.cpp index a34d8c30..bcbfe53e 100644 --- a/libs/math/tests/vector.cpp +++ b/libs/math/tests/vector.cpp @@ -34,6 +34,24 @@ test_case(math_vector_init) expect_equal(pf[2], 5.f); } +test_case(math_vector_dynamic) +{ + vector pi1(std::size_t(15)); + vector pi2{1, 2, 3, 4, 5}; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + expect_equal(pi1.dimension(), 15); + expect_equal(pi2.dimension(), 5); + + expect_equal(pi2[0], 1); + expect_equal(pi2[1], 2); + expect_equal(pi2[2], 3); + expect_equal(pi2[3], 4); + expect_equal(pi2[4], 5); +} + test_case(math_vector_deduce) { vector pi{1, 2}; @@ -70,6 +88,16 @@ test_case(math_vector_arithmetic) expect_equal(vf1 + vf2, (vector{13.f, 15.f, 17.f})); expect_equal(vf1 - vf2, (vector{-3.f, -3.f, -3.f})); expect_equal(2.f * vf1 - 3.f * vf2, (vector{-14.f, -15.f, -16.f})); + + vector vd1{-1.f, -2.f, -3.f}; + vector vd2{-4.f, -5.f, -6.f}; + + expect_equal(-vd1, (vector{1.f, 2.f, 3.f})); + expect_equal(5.f * vd1, (vector{-5.f, -10.f, -15.f})); + expect_equal(vd1 * 5.f, (vector{-5.f, -10.f, -15.f})); + expect_equal(vd1 + vd2, (vector{-5.f, -7.f, -9.f})); + expect_equal(vd1 - vd2, (vector{3.f, 3.f, 3.f})); + expect_equal(2.f * vd1 - 3.f * vd2, (vector{10.f, 11.f, 12.f})); } test_case(math_vector_dot) @@ -83,6 +111,11 @@ test_case(math_vector_dot) vector vf2{8.f, 9.f, 10.f}; expect_equal((dot(vf1, vf2)), 164.f); + + vector vd1{-1.f, -2.f, -3.f}; + vector vd2{-4.f, -5.f, -6.f}; + + expect_equal((dot(vd1, vd2)), 32.f); } test_case(math_vector_det) @@ -118,18 +151,22 @@ test_case(math_vector_cast) { vector pi{1, 2}; vector pf{3.f, 4.f, 5.f}; + vector pd{-1.f, -2.f, -3.f}; expect_equal(cast(pi), (vector{1.f, 2.f})); expect_equal(cast(pf), (vector{3, 4, 5})); + expect_equal(cast(pd), (vector{-1, -2, -3})); } test_case(math_vector_ort) { vector v1{1.f, 2.f}; vector v2{3.f, 4.f, 5.f}; + vector v3{-1.f, -2.f, -3.f}; expect_small((dot(v1, ort(v1))), 1e-6); expect_small((dot(v2, ort(v2))), 1e-6); + expect_small((dot(v3, ort(v3))), 1e-6); } test_case(math_vector_cross) @@ -169,6 +206,7 @@ test_case(math_vector_slerp) // 10-degree steps auto s = slerp(v1, v2, i / 9.f); expect_close((length(s)), 1.f, 1e-6); + expect_close((angle(s)), rad(10.f * i), 1e-6); expect_gequal((det(v1, s)), 0.f); expect_gequal((det(s, v2)), 0.f); }