Support dynamic size in math::vector

This commit is contained in:
Nikita Lisitsa 2025-01-26 19:03:33 +03:00
parent 6fc476f1f0
commit adcf761243
4 changed files with 149 additions and 35 deletions

View file

@ -1,7 +1,10 @@
#pragma once
#include <psemek/math/dynamic.hpp>
#include <psemek/util/exception.hpp>
#include <vector>
namespace psemek::math::detail
{
@ -31,6 +34,12 @@ namespace psemek::math::detail
};
};
template <typename T>
struct array<T, dynamic>
{
using type = std::vector<T>;
};
template <typename T, typename ... Args>
struct all_convertible_to;

View file

@ -0,0 +1,27 @@
#pragma once
#include <psemek/util/exception.hpp>
#include <cstddef>
#include <format>
namespace psemek::math
{
constexpr std::size_t dynamic = static_cast<std::size_t>(-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);
}
}

View file

@ -2,12 +2,12 @@
#include <psemek/math/detail/array.hpp>
#include <psemek/util/hash.hpp>
#include <psemek/util/assert.hpp>
#include <iostream>
#include <cstddef>
#include <utility>
#include <type_traits>
#include <stdexcept>
#include <cmath>
#include <compare>
@ -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 <typename ... Args>
requires((sizeof...(Args) == N) && detail::all_convertible_to<T, Args...>::value)
requires((sizeof...(Args) == N || N == dynamic) && detail::all_convertible_to<T, Args...>::value)
vector(Args && ... args)
: coords{ static_cast<T>(std::forward<Args>(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 <typename ... Args>
@ -60,8 +77,8 @@ namespace psemek::math
template <typename T1, typename T, std::size_t N>
vector<T1, N> cast(vector<T, N> const & v)
{
vector<T1, N> r;
for (std::size_t i = 0; i < N; ++i)
vector<T1, N> r(v.dimension());
for (std::size_t i = 0; i < v.dimension(); ++i)
r[i] = static_cast<T1>(v[i]);
return r;
}
@ -69,7 +86,9 @@ namespace psemek::math
template <typename T, std::size_t N>
std::strong_ordering operator <=> (vector<T, N> const & v1, vector<T, N> 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 <typename T, std::size_t N>
vector<T, N> operator * (vector<T, N> const & v, T const & s)
{
vector<T, N> r;
for (std::size_t i = 0; i < N; ++i)
vector<T, N> 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 <typename T, std::size_t N>
vector<T, N> operator * (T const & s, vector<T, N> const & v)
{
vector<T, N> r;
for (std::size_t i = 0; i < N; ++i)
vector<T, N> 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 <typename T, std::size_t N>
vector<T, N> operator / (vector<T, N> const & v, T const & s)
{
vector<T, N> r;
for (std::size_t i = 0; i < N; ++i)
vector<T, N> 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 <typename T, std::size_t N>
vector<T, N> operator - (vector<T, N> const & v)
{
vector<T, N> r;
for (std::size_t i = 0; i < N; ++i)
vector<T, N> 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 <typename T, std::size_t N>
vector<T, N> operator + (vector<T, N> const & v1, vector<T, N> const & v2)
{
vector<T, N> r;
for (std::size_t i = 0; i < N; ++i)
check_dynamic_size(v1.dimension(), v2.dimension());
vector<T, N> 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 <typename T, std::size_t N>
vector<T, N> operator - (vector<T, N> const & v1, vector<T, N> const & v2)
{
vector<T, N> r;
for (std::size_t i = 0; i < N; ++i)
check_dynamic_size(v1.dimension(), v2.dimension());
vector<T, N> 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 <typename T, std::size_t N>
vector<T, N> vector<T, N>::zero()
vector<T, N> vector<T, N>::zero() requires (N != dynamic)
{
vector<T, N> r;
for (std::size_t i = 0; i < N; ++i)
@ -202,11 +225,27 @@ namespace psemek::math
return r;
}
template <typename T, std::size_t N>
vector<T, N> vector<T, N>::zero(std::size_t size)
{
if constexpr (N != dynamic)
{
assert(N == size);
}
vector<T, N> r(size);
for (std::size_t i = 0; i < size; ++i)
r[i] = T(0);
return r;
}
template <typename T, std::size_t N>
T dot(vector<T, N> const & v1, vector<T, N> 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 <typename T, std::size_t N>
vector<T, N> ort(vector<T, N> const & v)
{
vector<T, N> result;
vector<T, N> result = vector<T, N>::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<T, N>::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 <typename F, std::size_t N, typename ... Ts>
auto pointwise(F && f, vector<Ts, N> const & ... vs)
template <typename F, std::size_t N, typename T, typename ... Ts>
auto pointwise(F && f, vector<T, N> const & v0, vector<Ts, N> const & ... vs)
{
using R = decltype(f(vs[0]...));
using R = decltype(f(v0[0], vs[0]...));
vector<R, N> result;
for (std::size_t i = 0; i < N; ++i)
result[i] = f(vs[i]...);
(check_dynamic_size(v0.dimension(), vs.dimension()), ...);
vector<R, N> 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<T, N> 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<T> 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;
}

View file

@ -34,6 +34,24 @@ test_case(math_vector_init)
expect_equal(pf[2], 5.f);
}
test_case(math_vector_dynamic)
{
vector<int, dynamic> pi1(std::size_t(15));
vector<int, dynamic> pi2{1, 2, 3, 4, 5};
static_assert(std::is_same_v<decltype(pi1)::scalar_type, int>);
static_assert(std::is_same_v<decltype(pi2)::scalar_type, int>);
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<float, dynamic> vd1{-1.f, -2.f, -3.f};
vector<float, dynamic> vd2{-4.f, -5.f, -6.f};
expect_equal(-vd1, (vector<float, dynamic>{1.f, 2.f, 3.f}));
expect_equal(5.f * vd1, (vector<float, dynamic>{-5.f, -10.f, -15.f}));
expect_equal(vd1 * 5.f, (vector<float, dynamic>{-5.f, -10.f, -15.f}));
expect_equal(vd1 + vd2, (vector<float, dynamic>{-5.f, -7.f, -9.f}));
expect_equal(vd1 - vd2, (vector<float, dynamic>{3.f, 3.f, 3.f}));
expect_equal(2.f * vd1 - 3.f * vd2, (vector<float, dynamic>{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<float, dynamic> vd1{-1.f, -2.f, -3.f};
vector<float, dynamic> 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<int, dynamic> pd{-1.f, -2.f, -3.f};
expect_equal(cast<float>(pi), (vector{1.f, 2.f}));
expect_equal(cast<int>(pf), (vector{3, 4, 5}));
expect_equal(cast<int>(pd), (vector<int, dynamic>{-1, -2, -3}));
}
test_case(math_vector_ort)
{
vector v1{1.f, 2.f};
vector v2{3.f, 4.f, 5.f};
vector<int, dynamic> 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);
}