Formatting + new packing related functions

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-12-11 12:55:04 +02:00
parent a5d669235e
commit 1a42238a41

View File

@@ -21,9 +21,11 @@
#pragma once #pragma once
#include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <format> #include <format>
#include <numbers> #include <numbers>
#include <optional> #include <optional>
@@ -36,15 +38,15 @@
namespace smath { namespace smath {
template<std::size_t N, typename T> template<std::size_t N, typename T>
requires std::is_arithmetic_v<T> requires std::is_arithmetic_v<T> struct Vec;
struct Vec;
namespace detail { namespace detail {
#define SMATH_STR(x) #x #define SMATH_STR(x) #x
#define SMATH_XSTR(x) SMATH_STR(x) #define SMATH_XSTR(x) SMATH_STR(x)
consteval bool streq(const char *a, const char *b) { consteval bool streq(const char *a, const char *b)
{
for (;; ++a, ++b) { for (;; ++a, ++b) {
if (*a != *b) if (*a != *b)
return false; return false;
@@ -59,7 +61,8 @@ enum class AngularUnit {
Turns, Turns,
}; };
consteval std::optional<AngularUnit> parse_unit(const char *s) { consteval std::optional<AngularUnit> parse_unit(char const *s)
{
if (streq(s, "rad")) if (streq(s, "rad"))
return AngularUnit::Radians; return AngularUnit::Radians;
if (streq(s, "deg")) if (streq(s, "deg"))
@@ -76,7 +79,8 @@ static_assert(SMATH_ANGLE_UNIT_ID != std::nullopt,
template<std::size_t N> struct FixedString { template<std::size_t N> struct FixedString {
char data[N] {}; char data[N] {};
static constexpr std::size_t size = N - 1; static constexpr std::size_t size = N - 1;
constexpr FixedString(char const (&s)[N]) { constexpr FixedString(char const (&s)[N])
{
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
data[i] = s[i]; data[i] = s[i];
} }
@@ -87,19 +91,63 @@ template <std::size_t M, class U> struct is_Vec<Vec<M, U>> : std::true_type {};
template<class X> template<class X>
inline constexpr bool is_Vec_v = is_Vec<std::remove_cvref_t<X>>::value; inline constexpr bool is_Vec_v = is_Vec<std::remove_cvref_t<X>>::value;
template<class X> template<class X>
inline constexpr bool is_scalar_v = inline constexpr bool is_scalar_v
std::is_arithmetic_v<std::remove_cvref_t<X>>; = std::is_arithmetic_v<std::remove_cvref_t<X>>;
template<class X> struct Vec_size; template<class X> struct Vec_size;
template<std::size_t M, class U> template<std::size_t M, class U>
struct Vec_size<Vec<M, U>> : std::integral_constant<std::size_t, M> { }; struct Vec_size<Vec<M, U>> : std::integral_constant<std::size_t, M> { };
template<class T> constexpr auto pack_unorm8(T v) -> std::uint8_t
{
static_assert(std::is_floating_point_v<T>);
T c = std::clamp(v, T(0), T(1));
T scaled = c * T(255);
int i = static_cast<int>(scaled + T(0.5));
if (i < 0)
i = 0;
if (i > 255)
i = 255;
return static_cast<std::uint8_t>(i);
}
template<class T> constexpr auto pack_snorm8(T v) -> std::int8_t
{
static_assert(std::is_floating_point_v<T>);
T c = std::clamp(v, T(-1), T(1));
T scaled = c * T(127);
int i
= static_cast<int>(scaled >= T(0) ? scaled + T(0.5) : scaled - T(0.5));
if (i < -127)
i = -127;
if (i > 127)
i = 127;
return static_cast<std::int8_t>(i);
}
template<class T> constexpr auto unpack_unorm8(std::uint8_t b) -> T
{
static_assert(std::is_floating_point_v<T>);
return static_cast<T>(b) / T(255);
}
template<class T> constexpr auto unpack_snorm8(std::int8_t b) -> T
{
static_assert(std::is_floating_point_v<T>);
int i = static_cast<int>(b);
if (i < -127)
i = -127;
if (i > 127)
i = 127;
return static_cast<T>(i) / T(127);
}
} // namespace detail } // namespace detail
template<std::size_t N, typename T = float> template<std::size_t N, typename T = float>
requires std::is_arithmetic_v<T> requires std::is_arithmetic_v<T> struct Vec : std::array<T, N> {
struct Vec : std::array<T, N> {
private: private:
template <class X> static consteval std::size_t extent() { template<class X> static consteval std::size_t extent()
{
if constexpr (detail::is_Vec_v<X>) if constexpr (detail::is_Vec_v<X>)
return detail::Vec_size<std::remove_cvref_t<X>>::value; return detail::Vec_size<std::remove_cvref_t<X>>::value;
else if constexpr (detail::is_scalar_v<X>) else if constexpr (detail::is_scalar_v<X>)
@@ -107,33 +155,38 @@ private:
else else
return 0; // Should be unreachable return 0; // Should be unreachable
} }
template <class... Args> static consteval std::size_t total_extent() { template<class... Args> static consteval std::size_t total_extent()
{
return (extent<Args>() + ... + 0); return (extent<Args>() + ... + 0);
} }
public: public:
// Constructors // Constructors
constexpr Vec() noexcept { constexpr Vec() noexcept
{
for (auto &v : *this) for (auto &v : *this)
v = T(0); v = T(0);
} }
explicit constexpr Vec(T const &s) noexcept { explicit constexpr Vec(T const &s) noexcept
{
for (auto &v : *this) for (auto &v : *this)
v = s; v = s;
} }
template<typename... Args> template<typename... Args>
requires((detail::is_scalar_v<Args> || detail::is_Vec_v<Args>) && ...) && requires((detail::is_scalar_v<Args> || detail::is_Vec_v<Args>) && ...)
(total_extent<Args...>() == N) && && (total_extent<Args...>() == N)
(!(sizeof...(Args) == 1 && (detail::is_Vec_v<Args> && ...))) && (!(sizeof...(Args) == 1 && (detail::is_Vec_v<Args> && ...)))
constexpr Vec(Args &&...args) noexcept { constexpr Vec(Args &&...args) noexcept
{
std::size_t i = 0; std::size_t i = 0;
(fill_one(i, std::forward<Args>(args)), ...); (fill_one(i, std::forward<Args>(args)), ...);
} }
// Member accesses // Member accesses
// NOTE: This can (probably) be improved with C++26 reflection in the future. // NOTE: This can (probably) be improved with C++26 reflection in the
// future.
#define VEC_ACC(component, req, idx) \ #define VEC_ACC(component, req, idx) \
constexpr auto component() noexcept -> T &requires(N >= req) { \ constexpr auto component() noexcept -> T &requires(N >= req) { \
return (*this)[idx]; \ return (*this)[idx]; \
@@ -163,17 +216,20 @@ public:
#undef VEC_ACC #undef VEC_ACC
template<class... Args, std::size_t... Is> template<class... Args, std::size_t... Is>
constexpr void unpack_impl(std::index_sequence<Is...>, constexpr void unpack_impl(
Args &...args) noexcept { std::index_sequence<Is...>, Args &...args) noexcept
{
((args = (*this)[Is]), ...); ((args = (*this)[Is]), ...);
} }
template <class... Args> constexpr void unpack(Args &...args) noexcept { template<class... Args> constexpr void unpack(Args &...args) noexcept
{
unpack_impl(std::index_sequence_for<Args...> {}, args...); unpack_impl(std::index_sequence_for<Args...> {}, args...);
} }
// Unary // Unary
constexpr auto operator-() noexcept -> Vec { constexpr auto operator-() noexcept -> Vec
{
Vec r {}; Vec r {};
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
r[i] = -(*this)[i]; r[i] = -(*this)[i];
@@ -181,16 +237,20 @@ public:
} }
// RHS operations // RHS operations
friend constexpr auto operator+(T s, Vec const &v) noexcept -> Vec { friend constexpr auto operator+(T s, Vec const &v) noexcept -> Vec
{
return v + s; return v + s;
} }
friend constexpr auto operator-(T s, Vec const &v) noexcept -> Vec { friend constexpr auto operator-(T s, Vec const &v) noexcept -> Vec
{
return Vec(s) - v; return Vec(s) - v;
} }
friend constexpr auto operator*(T s, Vec const &v) noexcept -> Vec { friend constexpr auto operator*(T s, Vec const &v) noexcept -> Vec
{
return v * s; return v * s;
} }
friend constexpr auto operator/(T s, Vec const &v) noexcept -> Vec { friend constexpr auto operator/(T s, Vec const &v) noexcept -> Vec
{
Vec r {}; Vec r {};
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
r[i] = s / v[i]; r[i] = s / v[i];
@@ -199,14 +259,16 @@ public:
// Members // Members
#define VEC_OP(op) \ #define VEC_OP(op) \
constexpr auto operator op(Vec const &rhs) const noexcept -> Vec { \ constexpr auto operator op(Vec const &rhs) const noexcept -> Vec \
{ \
Vec result {}; \ Vec result {}; \
for (std::size_t i = 0; i < N; ++i) { \ for (std::size_t i = 0; i < N; ++i) { \
result[i] = (*this)[i] op rhs[i]; \ result[i] = (*this)[i] op rhs[i]; \
} \ } \
return result; \ return result; \
} \ } \
constexpr auto operator op(T const &rhs) const noexcept -> Vec { \ constexpr auto operator op(T const &rhs) const noexcept -> Vec \
{ \
Vec result {}; \ Vec result {}; \
for (std::size_t i = 0; i < N; ++i) { \ for (std::size_t i = 0; i < N; ++i) { \
result[i] = (*this)[i] op rhs; \ result[i] = (*this)[i] op rhs; \
@@ -219,12 +281,14 @@ public:
VEC_OP(/) VEC_OP(/)
#undef VEC_OP #undef VEC_OP
#define VEC_OP_ASSIGN(sym) \ #define VEC_OP_ASSIGN(sym) \
constexpr Vec &operator sym##=(Vec const &rhs) noexcept { \ constexpr Vec &operator sym##=(Vec const &rhs) noexcept \
{ \
for (std::size_t i = 0; i < N; ++i) \ for (std::size_t i = 0; i < N; ++i) \
(*this)[i] sym## = rhs[i]; \ (*this)[i] sym## = rhs[i]; \
return *this; \ return *this; \
} \ } \
constexpr Vec &operator sym##=(T const &s) noexcept { \ constexpr Vec &operator sym##=(T const &s) noexcept \
{ \
for (std::size_t i = 0; i < N; ++i) \ for (std::size_t i = 0; i < N; ++i) \
(*this)[i] sym## = s; \ (*this)[i] sym## = s; \
return *this; \ return *this; \
@@ -235,14 +299,16 @@ public:
VEC_OP_ASSIGN(/) VEC_OP_ASSIGN(/)
#undef VEC_OP_ASSIGN #undef VEC_OP_ASSIGN
constexpr auto operator==(Vec const &v) const noexcept -> bool { constexpr auto operator==(Vec const &v) const noexcept -> bool
{
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
if ((*this)[i] != v[i]) if ((*this)[i] != v[i])
return false; return false;
return true; return true;
} }
constexpr auto operator!=(Vec const &v) const noexcept -> bool { constexpr auto operator!=(Vec const &v) const noexcept -> bool
{
return !(*this == v); return !(*this == v);
} }
@@ -262,13 +328,15 @@ public:
template<typename U = T> template<typename U = T>
requires std::is_floating_point_v<U> requires std::is_floating_point_v<U>
constexpr auto normalized_safe(U eps = EPS_DEFAULT) const noexcept -> Vec { constexpr auto normalized_safe(U eps = EPS_DEFAULT) const noexcept -> Vec
{
auto m = magnitude(); auto m = magnitude();
return (m > eps) ? (*this) / m : Vec {}; return (m > eps) ? (*this) / m : Vec {};
} }
template<typename U = T> template<typename U = T>
requires std::is_floating_point_v<U> requires std::is_floating_point_v<U>
constexpr auto normalize_safe(U eps = EPS_DEFAULT) const noexcept -> Vec { constexpr auto normalize_safe(U eps = EPS_DEFAULT) const noexcept -> Vec
{
return normalized_safe(eps); return normalized_safe(eps);
} }
@@ -288,7 +356,8 @@ public:
return this->normalized(); return this->normalized();
} }
[[nodiscard]] constexpr auto dot(Vec<N, T> const &other) const noexcept -> T { [[nodiscard]] constexpr auto dot(Vec<N, T> const &other) const noexcept -> T
{
T res = 0; T res = 0;
for (std::size_t i = 0; i < N; ++i) { for (std::size_t i = 0; i < N; ++i) {
res += (*this)[i] * other[i]; res += (*this)[i] * other[i];
@@ -299,8 +368,9 @@ public:
static constexpr T EPS_DEFAULT = T(1e-6); static constexpr T EPS_DEFAULT = T(1e-6);
template<class U = T> template<class U = T>
requires std::is_floating_point_v<U> requires std::is_floating_point_v<U>
[[nodiscard]] constexpr auto [[nodiscard]] constexpr auto approx_equal(
approx_equal(Vec const &rhs, U eps = EPS_DEFAULT) const noexcept { Vec const &rhs, U eps = EPS_DEFAULT) const noexcept
{
using F = std::conditional_t<std::is_floating_point_v<U>, U, double>; using F = std::conditional_t<std::is_floating_point_v<U>, U, double>;
for (size_t i = 0; i < N; ++i) for (size_t i = 0; i < N; ++i)
if (std::abs(F((*this)[i] - rhs[i])) > F(eps)) if (std::abs(F((*this)[i] - rhs[i])) > F(eps))
@@ -320,8 +390,8 @@ public:
} }
template<typename U = T> template<typename U = T>
requires(N == 3) requires(N == 3) constexpr auto cross(Vec const &r) const noexcept -> Vec
constexpr auto cross(const Vec &r) const noexcept -> Vec { {
return { (*this)[1] * r[2] - (*this)[2] * r[1], return { (*this)[1] * r[2] - (*this)[2] * r[1],
(*this)[2] * r[0] - (*this)[0] * r[2], (*this)[2] * r[0] - (*this)[0] * r[2],
(*this)[0] * r[1] - (*this)[1] * r[0] }; (*this)[0] * r[1] - (*this)[1] * r[0] };
@@ -344,15 +414,16 @@ public:
template<class U> template<class U>
requires(std::is_arithmetic_v<U> && N >= 1) requires(std::is_arithmetic_v<U> && N >= 1)
constexpr explicit(!std::is_convertible_v<U, T>) constexpr explicit(!std::is_convertible_v<U, T>)
Vec(Vec<N, U> const &other) noexcept { Vec(Vec<N, U> const &other) noexcept
{
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
this->operator[](i) = static_cast<T>(other[i]); this->operator[](i) = static_cast<T>(other[i]);
} }
template<class U> template<class U>
requires(std::is_arithmetic_v<U> && N >= 1) requires(std::is_arithmetic_v<U> && N >= 1) constexpr explicit(
constexpr explicit(!std::is_convertible_v<T, U>) !std::is_convertible_v<T, U>) operator Vec<N, U>() const noexcept
operator Vec<N, U>() const noexcept { {
Vec<N, U> r {}; Vec<N, U> r {};
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
r[i] = static_cast<U>((*this)[i]); r[i] = static_cast<U>((*this)[i]);
@@ -361,51 +432,60 @@ public:
template<class U> template<class U>
requires(std::is_arithmetic_v<U> && !std::is_same_v<U, T>) requires(std::is_arithmetic_v<U> && !std::is_same_v<U, T>)
constexpr auto operator=(Vec<N, U> const &rhs) noexcept -> Vec & { constexpr auto operator=(Vec<N, U> const &rhs) noexcept -> Vec &
{
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
(*this)[i] = static_cast<T>(rhs[i]); (*this)[i] = static_cast<T>(rhs[i]);
return *this; return *this;
} }
private: private:
constexpr void fill_one(std::size_t &i, const T &v) noexcept { constexpr void fill_one(std::size_t &i, T const &v) noexcept
{
(*this)[i++] = v; (*this)[i++] = v;
} }
#ifdef SMATH_IMPLICIT_CONVERSIONS #ifdef SMATH_IMPLICIT_CONVERSIONS
template<class U> template<class U>
requires std::is_arithmetic_v<U> && (!std::is_same_v<U, T>) requires std::is_arithmetic_v<U>
constexpr void fill_one(std::size_t &i, const U &v) noexcept { && (!std::is_same_v<U, T>)constexpr void fill_one(
std::size_t &i, const U &v) noexcept
{
(*this)[i++] = static_cast<T>(v); (*this)[i++] = static_cast<T>(v);
} }
template<std::size_t M, class U> template<std::size_t M, class U>
constexpr void fill_one(std::size_t &i, const Vec<M, U> &v) noexcept { constexpr void fill_one(std::size_t &i, Vec<M, U> const &v) noexcept
{
for (std::size_t k = 0; k < M; ++k) for (std::size_t k = 0; k < M; ++k)
(*this)[i++] = static_cast<T>(v[k]); (*this)[i++] = static_cast<T>(v[k]);
} }
#endif // SMATH_IMPLICIT_CONVERSIONS #endif // SMATH_IMPLICIT_CONVERSIONS
template<std::size_t M> template<std::size_t M>
constexpr void fill_one(std::size_t &i, const Vec<M, T> &v) noexcept { constexpr void fill_one(std::size_t &i, const Vec<M, T> &v) noexcept
{
for (std::size_t k = 0; k < M; ++k) for (std::size_t k = 0; k < M; ++k)
(*this)[i++] = static_cast<T>(v[k]); (*this)[i++] = static_cast<T>(v[k]);
} }
}; };
template <size_t I, size_t N, class T> constexpr T &get(Vec<N, T> &v) noexcept { template<size_t I, size_t N, class T> constexpr T &get(Vec<N, T> &v) noexcept
{
static_assert(I < N); static_assert(I < N);
return v[I]; return v[I];
} }
template<size_t I, size_t N, class T> template<size_t I, size_t N, class T>
constexpr const T &get(const Vec<N, T> &v) noexcept { constexpr T const &get(Vec<N, T> const &v) noexcept
{
static_assert(I < N); static_assert(I < N);
return v[I]; return v[I];
} }
template <size_t I, size_t N, class T> template<size_t I, size_t N, class T> constexpr T &&get(Vec<N, T> &&v) noexcept
constexpr T &&get(Vec<N, T> &&v) noexcept { {
static_assert(I < N); static_assert(I < N);
return std::move(v[I]); return std::move(v[I]);
} }
template<size_t I, size_t N, class T> template<size_t I, size_t N, class T>
constexpr const T &&get(const Vec<N, T> &&v) noexcept { constexpr T const &&get(Vec<N, T> const &&v) noexcept
{
static_assert(I < N); static_assert(I < N);
return std::move(v[I]); return std::move(v[I]);
} }
@@ -416,7 +496,8 @@ using VecOrScalar = std::conditional_t<N == 1, T, Vec<N, T>>;
namespace detail { namespace detail {
consteval auto char_to_idx(char c) -> std::size_t { consteval auto char_to_idx(char c) -> std::size_t
{
if (c == 'r' || c == 'x' || c == 's' || c == 'u') if (c == 'r' || c == 'x' || c == 's' || c == 'u')
return 0; return 0;
else if (c == 'g' || c == 'y' || c == 't' || c == 'v') else if (c == 'g' || c == 'y' || c == 't' || c == 'v')
@@ -428,7 +509,8 @@ consteval auto char_to_idx(char c) -> std::size_t {
return static_cast<std::size_t>(-1); return static_cast<std::size_t>(-1);
} }
constexpr auto is_valid(char c) -> bool { constexpr auto is_valid(char c) -> bool
{
switch (c) { switch (c) {
case 'r': case 'r':
case 'g': case 'g':
@@ -453,10 +535,11 @@ constexpr auto is_valid(char c) -> bool {
template<detail::FixedString S, std::size_t N, typename T, std::size_t... I> template<detail::FixedString S, std::size_t N, typename T, std::size_t... I>
constexpr auto swizzle_impl(Vec<N, T> const &v, std::index_sequence<I...>) constexpr auto swizzle_impl(Vec<N, T> const &v, std::index_sequence<I...>)
-> VecOrScalar<S.size, T> { -> VecOrScalar<S.size, T>
{
static_assert(((is_valid(S[I])) && ...), "Invalid swizzle component"); static_assert(((is_valid(S[I])) && ...), "Invalid swizzle component");
static_assert(((char_to_idx(S[I]) < N) && ...), static_assert(
"Pattern index out of bounds"); ((char_to_idx(S[I]) < N) && ...), "Pattern index out of bounds");
VecOrScalar<S.size, T> out {}; VecOrScalar<S.size, T> out {};
std::size_t i = 0; std::size_t i = 0;
((out[i++] = v[char_to_idx(S[I])]), ...); ((out[i++] = v[char_to_idx(S[I])]), ...);
@@ -474,14 +557,15 @@ concept SwizzleInBounds = []<std::size_t... I>(std::index_sequence<I...>) {
}(std::make_index_sequence<S.size> {}); }(std::make_index_sequence<S.size> {});
template<FixedString S, std::size_t N> template<FixedString S, std::size_t N>
concept ValidSwizzle = concept ValidSwizzle
(S.size > 0) && SwizzleCharsOK<S> && SwizzleInBounds<S, N>; = (S.size > 0) && SwizzleCharsOK<S> && SwizzleInBounds<S, N>;
} // namespace detail } // namespace detail
template<detail::FixedString S, std::size_t N, typename T> template<detail::FixedString S, std::size_t N, typename T>
requires detail::ValidSwizzle<S, N> requires detail::ValidSwizzle<S, N>
constexpr auto swizzle(Vec<N, T> const &v) -> VecOrScalar<S.size, T> { constexpr auto swizzle(Vec<N, T> const &v) -> VecOrScalar<S.size, T>
{
return detail::swizzle_impl<S>(v, std::make_index_sequence<S.size> {}); return detail::swizzle_impl<S>(v, std::make_index_sequence<S.size> {});
} }
@@ -493,38 +577,41 @@ using Vec2d = Vec<2, double>;
using Vec3d = Vec<3, double>; using Vec3d = Vec<3, double>;
using Vec4d = Vec<4, double>; using Vec4d = Vec<4, double>;
template <class T> constexpr auto deg(T const value) -> T { template<class T> constexpr auto deg(T const value) -> T
{
if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) {
return value; return value;
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID == } else if constexpr (detail::SMATH_ANGLE_UNIT_ID
detail::AngularUnit::Radians) { == detail::AngularUnit::Radians) {
return value * static_cast<T>(std::numbers::pi / 180.0); return value * static_cast<T>(std::numbers::pi / 180.0);
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID == } else if constexpr (detail::SMATH_ANGLE_UNIT_ID
detail::AngularUnit::Turns) { == detail::AngularUnit::Turns) {
return value / static_cast<T>(360.0); return value / static_cast<T>(360.0);
} }
} }
template <class T> constexpr auto rad(T const value) -> T { template<class T> constexpr auto rad(T const value) -> T
{
if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) {
return value * static_cast<T>(180.0 / std::numbers::pi); return value * static_cast<T>(180.0 / std::numbers::pi);
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID == } else if constexpr (detail::SMATH_ANGLE_UNIT_ID
detail::AngularUnit::Radians) { == detail::AngularUnit::Radians) {
return value; return value;
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID == } else if constexpr (detail::SMATH_ANGLE_UNIT_ID
detail::AngularUnit::Turns) { == detail::AngularUnit::Turns) {
return value / (static_cast<T>(2.0) * static_cast<T>(std::numbers::pi)); return value / (static_cast<T>(2.0) * static_cast<T>(std::numbers::pi));
} }
} }
template <class T> constexpr auto turns(T const value) -> T { template<class T> constexpr auto turns(T const value) -> T
{
if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) {
return value * static_cast<T>(360.0); return value * static_cast<T>(360.0);
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID == } else if constexpr (detail::SMATH_ANGLE_UNIT_ID
detail::AngularUnit::Radians) { == detail::AngularUnit::Radians) {
return value * (static_cast<T>(2.0) * static_cast<T>(std::numbers::pi)); return value * (static_cast<T>(2.0) * static_cast<T>(std::numbers::pi));
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID == } else if constexpr (detail::SMATH_ANGLE_UNIT_ID
detail::AngularUnit::Turns) { == detail::AngularUnit::Turns) {
return value; return value;
} }
} }
@@ -542,39 +629,100 @@ template <class T> struct Quaternion : Vec<4, T> {
constexpr T &z() noexcept { return Base::z(); } constexpr T &z() noexcept { return Base::z(); }
constexpr T &w() noexcept { return Base::w(); } constexpr T &w() noexcept { return Base::w(); }
constexpr auto operator*(Quaternion const &rhs) const noexcept -> Quaternion { constexpr auto operator*(Quaternion const &rhs) const noexcept -> Quaternion
{
Quaternion r; Quaternion r;
auto const &a = *this; auto const &a = *this;
r.x() = r.x() = a.w() * rhs.x() + a.x() * rhs.w() + a.y() * rhs.z()
a.w() * rhs.x() + a.x() * rhs.w() + a.y() * rhs.z() - a.z() * rhs.y(); - a.z() * rhs.y();
r.y() = r.y() = a.w() * rhs.y() - a.x() * rhs.z() + a.y() * rhs.w()
a.w() * rhs.y() - a.x() * rhs.z() + a.y() * rhs.w() + a.z() * rhs.x(); + a.z() * rhs.x();
r.z() = r.z() = a.w() * rhs.z() + a.x() * rhs.y() - a.y() * rhs.x()
a.w() * rhs.z() + a.x() * rhs.y() - a.y() * rhs.x() + a.z() * rhs.w(); + a.z() * rhs.w();
r.w() = r.w() = a.w() * rhs.w() - a.x() * rhs.x() - a.y() * rhs.y()
a.w() * rhs.w() - a.x() * rhs.x() - a.y() * rhs.y() - a.z() * rhs.z(); - a.z() * rhs.z();
return r; return r;
} }
}; };
template<class T>
requires std::is_floating_point_v<T>
[[nodiscard]] constexpr auto pack_unorm4x8(Vec<4, T> const &v) -> std::uint32_t
{
std::uint32_t r = detail::pack_unorm8(v[0]);
std::uint32_t g = detail::pack_unorm8(v[1]);
std::uint32_t b = detail::pack_unorm8(v[2]);
std::uint32_t a = detail::pack_unorm8(v[3]);
return (r) | (g << 8) | (b << 16) | (a << 24);
}
template<class T>
requires std::is_floating_point_v<T>
[[nodiscard]] constexpr auto pack_snorm4x8(Vec<4, T> const &v) -> std::uint32_t
{
std::uint32_t r = static_cast<std::uint8_t>(detail::pack_snorm8(v[0]));
std::uint32_t g = static_cast<std::uint8_t>(detail::pack_snorm8(v[1]));
std::uint32_t b = static_cast<std::uint8_t>(detail::pack_snorm8(v[2]));
std::uint32_t a = static_cast<std::uint8_t>(detail::pack_snorm8(v[3]));
return (r) | (g << 8) | (b << 16) | (a << 24);
}
template<class T = float>
requires std::is_floating_point_v<T>
[[nodiscard]] constexpr auto unpack_unorm4x8(std::uint32_t packed) -> Vec<4, T>
{
std::uint8_t r = static_cast<std::uint8_t>(packed & 0xFFu);
std::uint8_t g = static_cast<std::uint8_t>((packed >> 8) & 0xFFu);
std::uint8_t b = static_cast<std::uint8_t>((packed >> 16) & 0xFFu);
std::uint8_t a = static_cast<std::uint8_t>((packed >> 24) & 0xFFu);
return {
detail::unpack_unorm8<T>(r),
detail::unpack_unorm8<T>(g),
detail::unpack_unorm8<T>(b),
detail::unpack_unorm8<T>(a),
};
}
template<class T = float>
requires std::is_floating_point_v<T>
[[nodiscard]] constexpr auto unpack_snorm4x8(std::uint32_t packed) -> Vec<4, T>
{
std::int8_t r = static_cast<std::int8_t>(packed & 0xFFu);
std::int8_t g = static_cast<std::int8_t>((packed >> 8) & 0xFFu);
std::int8_t b = static_cast<std::int8_t>((packed >> 16) & 0xFFu);
std::int8_t a = static_cast<std::int8_t>((packed >> 24) & 0xFFu);
return {
detail::unpack_snorm8<T>(r),
detail::unpack_snorm8<T>(g),
detail::unpack_snorm8<T>(b),
detail::unpack_snorm8<T>(a),
};
}
template<std::size_t R, std::size_t C, typename T = float> template<std::size_t R, std::size_t C, typename T = float>
requires std::is_arithmetic_v<T> requires std::is_arithmetic_v<T> struct Mat : std::array<Vec<R, T>, C> {
struct Mat : std::array<Vec<R, T>, C> {
using Base = std::array<Vec<R, T>, C>; using Base = std::array<Vec<R, T>, C>;
using Base::operator[]; using Base::operator[];
constexpr auto operator[](std::size_t const row, std::size_t const column) constexpr auto operator[](std::size_t const row, std::size_t const column)
-> T & { -> T &
{
return col(column)[row]; return col(column)[row];
} }
constexpr auto operator[](std::size_t const row, constexpr auto operator[](
std::size_t const column) const -> T const & { std::size_t const row, std::size_t const column) const -> T const &
{
return col(column)[row]; return col(column)[row];
} }
constexpr Mat() noexcept { constexpr Mat() noexcept
{
for (auto &col : *this) for (auto &col : *this)
col = Vec<R, T> {}; col = Vec<R, T> {};
} }
@@ -589,99 +737,119 @@ struct Mat : std::array<Vec<R, T>, C> {
} }
template<typename... Cols> template<typename... Cols>
requires(sizeof...(Cols) == C && requires(sizeof...(Cols) == C
(std::same_as<std::remove_cvref_t<Cols>, Vec<R, T>> && ...)) && (std::same_as<std::remove_cvref_t<Cols>, Vec<R, T>> && ...))
constexpr Mat(Cols const &...cols) noexcept : Base{cols...} {} constexpr Mat(Cols const &...cols) noexcept
: Base { cols... }
{
}
constexpr auto col(std::size_t j) noexcept -> Vec<R, T> & { constexpr auto col(std::size_t j) noexcept -> Vec<R, T> &
{
return (*this)[j]; return (*this)[j];
} }
constexpr auto col(std::size_t j) const noexcept -> Vec<R, T> const & { constexpr auto col(std::size_t j) const noexcept -> Vec<R, T> const &
{
return (*this)[j]; return (*this)[j];
} }
constexpr auto operator()(std::size_t row, std::size_t col) noexcept -> T & { constexpr auto operator()(std::size_t row, std::size_t col) noexcept -> T &
{
return (*this)[col][row]; return (*this)[col][row];
} }
constexpr auto operator()(std::size_t row, std::size_t col) const noexcept constexpr auto operator()(std::size_t row, std::size_t col) const noexcept
-> T const & { -> T const &
{
return (*this)[col][row]; return (*this)[col][row];
} }
constexpr auto operator-() const noexcept -> Mat { constexpr auto operator-() const noexcept -> Mat
{
Mat r {}; Mat r {};
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
r[c] = -(*this)[c]; r[c] = -(*this)[c];
return r; return r;
} }
constexpr auto operator+=(Mat const &rhs) noexcept -> Mat & { constexpr auto operator+=(Mat const &rhs) noexcept -> Mat &
{
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
(*this)[c] += rhs[c]; (*this)[c] += rhs[c];
return *this; return *this;
} }
constexpr auto operator-=(Mat const &rhs) noexcept -> Mat & { constexpr auto operator-=(Mat const &rhs) noexcept -> Mat &
{
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
(*this)[c] -= rhs[c]; (*this)[c] -= rhs[c];
return *this; return *this;
} }
friend constexpr auto operator+(Mat lhs, Mat const &rhs) noexcept -> Mat { friend constexpr auto operator+(Mat lhs, Mat const &rhs) noexcept -> Mat
{
lhs += rhs; lhs += rhs;
return lhs; return lhs;
} }
friend constexpr auto operator-(Mat lhs, Mat const &rhs) noexcept -> Mat { friend constexpr auto operator-(Mat lhs, Mat const &rhs) noexcept -> Mat
{
lhs -= rhs; lhs -= rhs;
return lhs; return lhs;
} }
constexpr auto operator*=(T const &s) noexcept -> Mat & { constexpr auto operator*=(T const &s) noexcept -> Mat &
{
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
(*this)[c] *= s; (*this)[c] *= s;
return *this; return *this;
} }
constexpr auto operator/=(T const &s) noexcept -> Mat & { constexpr auto operator/=(T const &s) noexcept -> Mat &
{
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
(*this)[c] /= s; (*this)[c] /= s;
return *this; return *this;
} }
friend constexpr auto operator*(Mat lhs, T const &s) noexcept -> Mat { friend constexpr auto operator*(Mat lhs, T const &s) noexcept -> Mat
{
lhs *= s; lhs *= s;
return lhs; return lhs;
} }
friend constexpr auto operator*(T const &s, Mat rhs) noexcept -> Mat { friend constexpr auto operator*(T const &s, Mat rhs) noexcept -> Mat
{
rhs *= s; rhs *= s;
return rhs; return rhs;
} }
friend constexpr auto operator/(Mat lhs, T const &s) noexcept -> Mat { friend constexpr auto operator/(Mat lhs, T const &s) noexcept -> Mat
{
lhs /= s; lhs /= s;
return lhs; return lhs;
} }
[[nodiscard]] constexpr auto operator==(Mat const &rhs) const noexcept [[nodiscard]] constexpr auto operator==(Mat const &rhs) const noexcept
-> bool { -> bool
{
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
if (!((*this)[c] == rhs[c])) if (!((*this)[c] == rhs[c]))
return false; return false;
return true; return true;
} }
[[nodiscard]] constexpr auto operator!=(Mat const &rhs) const noexcept [[nodiscard]] constexpr auto operator!=(Mat const &rhs) const noexcept
-> bool { -> bool
{
return !(*this == rhs); return !(*this == rhs);
} }
static constexpr T EPS_DEFAULT = T(1e-6); static constexpr T EPS_DEFAULT = T(1e-6);
template<class U = T> template<class U = T>
requires std::is_floating_point_v<U> requires std::is_floating_point_v<U>
[[nodiscard]] constexpr auto approx_equal(Mat const &rhs, [[nodiscard]] constexpr auto approx_equal(
U eps = EPS_DEFAULT) const noexcept Mat const &rhs, U eps = EPS_DEFAULT) const noexcept -> bool
-> bool { {
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
if (!(*this)[c].approx_equal(rhs[c], eps)) if (!(*this)[c].approx_equal(rhs[c], eps))
return false; return false;
return true; return true;
} }
[[nodiscard]] constexpr auto transposed() const noexcept -> Mat<C, R, T> { [[nodiscard]] constexpr auto transposed() const noexcept -> Mat<C, R, T>
{
Mat<C, R, T> r {}; Mat<C, R, T> r {};
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
for (std::size_t r_idx = 0; r_idx < R; ++r_idx) for (std::size_t r_idx = 0; r_idx < R; ++r_idx)
@@ -708,8 +876,9 @@ using Mat3d = Mat<3, 3, double>;
using Mat4d = Mat<4, 4, double>; using Mat4d = Mat<4, 4, double>;
template<std::size_t R, std::size_t C, typename T> template<std::size_t R, std::size_t C, typename T>
[[nodiscard]] constexpr Vec<R, T> operator*(Mat<R, C, T> const &m, [[nodiscard]] constexpr Vec<R, T> operator*(
Vec<C, T> const &v) noexcept { Mat<R, C, T> const &m, Vec<C, T> const &v) noexcept
{
Vec<R, T> out {}; Vec<R, T> out {};
for (std::size_t c = 0; c < C; ++c) for (std::size_t c = 0; c < C; ++c)
out += m.col(c) * v[c]; out += m.col(c) * v[c];
@@ -718,8 +887,9 @@ template <std::size_t R, std::size_t C, typename T>
// Matrix * Matrix // Matrix * Matrix
template<std::size_t R, std::size_t C, std::size_t K, typename T> template<std::size_t R, std::size_t C, std::size_t K, typename T>
[[nodiscard]] constexpr Mat<R, K, T> operator*(Mat<R, C, T> const &a, [[nodiscard]] constexpr Mat<R, K, T> operator*(
Mat<C, K, T> const &b) noexcept { Mat<R, C, T> const &a, Mat<C, K, T> const &b) noexcept
{
Mat<R, K, T> out {}; Mat<R, K, T> out {};
for (std::size_t k = 0; k < K; ++k) { for (std::size_t k = 0; k < K; ++k) {
for (std::size_t r = 0; r < R; ++r) { for (std::size_t r = 0; r < R; ++r) {
@@ -735,14 +905,16 @@ template <std::size_t R, std::size_t C, std::size_t K, typename T>
// Mat3 transformations // Mat3 transformations
template<typename T> template<typename T>
[[nodiscard]] inline auto translate(Mat<3, 3, T> const &m, Vec<2, T> const &v) [[nodiscard]] inline auto translate(Mat<3, 3, T> const &m, Vec<2, T> const &v)
-> Mat<3, 3, T> { -> Mat<3, 3, T>
{
Mat<3, 3, T> res { m }; Mat<3, 3, T> res { m };
res[2] = m[0] * v[0] + m[1] * v[1] + m[2]; res[2] = m[0] * v[0] + m[1] * v[1] + m[2];
return res; return res;
} }
template<typename T> template<typename T>
[[nodiscard]] inline auto translate(Vec<2, T> const &v) -> Mat<3, 3, T> { [[nodiscard]] inline auto translate(Vec<2, T> const &v) -> Mat<3, 3, T>
{
Mat<3, 3, T> res { 1 }; Mat<3, 3, T> res { 1 };
res[2].x() = v.x(); res[2].x() = v.x();
res[2].y() = v.y(); res[2].y() = v.y();
@@ -751,7 +923,8 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto rotate(Mat<3, 3, T> const &m, T const angle) [[nodiscard]] inline auto rotate(Mat<3, 3, T> const &m, T const angle)
-> Mat<3, 3, T> { -> Mat<3, 3, T>
{
Mat<3, 3, T> res; Mat<3, 3, T> res;
T const c { std::cos(angle) }; T const c { std::cos(angle) };
@@ -766,7 +939,8 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto scale(Mat<3, 3, T> const &m, Vec<2, T> const &v) [[nodiscard]] inline auto scale(Mat<3, 3, T> const &m, Vec<2, T> const &v)
-> Mat<3, 3, T> { -> Mat<3, 3, T>
{
Mat<3, 3, T> res; Mat<3, 3, T> res;
res[0] = m[0] * v[0]; res[0] = m[0] * v[0];
@@ -778,7 +952,8 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_x(Mat<3, 3, T> const &m, T const v) [[nodiscard]] inline auto shear_x(Mat<3, 3, T> const &m, T const v)
-> Mat<3, 3, T> { -> Mat<3, 3, T>
{
Mat<3, 3, T> res { 1 }; Mat<3, 3, T> res { 1 };
res[1][0] = v; res[1][0] = v;
return m * res; return m * res;
@@ -786,7 +961,8 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_y(Mat<3, 3, T> const &m, T const v) [[nodiscard]] inline auto shear_y(Mat<3, 3, T> const &m, T const v)
-> Mat<3, 3, T> { -> Mat<3, 3, T>
{
Mat<3, 3, T> res { 1 }; Mat<3, 3, T> res { 1 };
res[0][1] = v; res[0][1] = v;
return m * res; return m * res;
@@ -795,15 +971,16 @@ template <typename T>
// Mat4 transformations // Mat4 transformations
template<typename T> template<typename T>
[[nodiscard]] inline auto translate(Mat<4, 4, T> const &m, Vec<3, T> const &v) [[nodiscard]] inline auto translate(Mat<4, 4, T> const &m, Vec<3, T> const &v)
-> Mat<4, 4, T> { -> Mat<4, 4, T>
{
Mat<4, 4, T> res { m }; Mat<4, 4, T> res { m };
res[3] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3]; res[3] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3];
return res; return res;
} }
template<typename T> template<typename T>
[[nodiscard]] inline auto translate(Vec<3, T> const &v) -> Mat<4, 4, T> { [[nodiscard]] inline auto translate(Vec<3, T> const &v) -> Mat<4, 4, T>
{
Mat<4, 4, T> res { 1 }; Mat<4, 4, T> res { 1 };
res[3].x() = v.x(); res[3].x() = v.x();
res[3].y() = v.y(); res[3].y() = v.y();
@@ -813,7 +990,8 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto rotate(Mat<4, 4, T> const &m, T const angle) [[nodiscard]] inline auto rotate(Mat<4, 4, T> const &m, T const angle)
-> Mat<4, 4, T> { -> Mat<4, 4, T>
{
Mat<4, 4, T> res; Mat<4, 4, T> res;
T const c { std::cos(angle) }; T const c { std::cos(angle) };
@@ -829,7 +1007,8 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto scale(Mat<4, 4, T> const &m, Vec<3, T> const &v) [[nodiscard]] inline auto scale(Mat<4, 4, T> const &m, Vec<3, T> const &v)
-> Mat<4, 4, T> { -> Mat<4, 4, T>
{
Mat<4, 4, T> res; Mat<4, 4, T> res;
res[0] = m[0] * v[0]; res[0] = m[0] * v[0];
@@ -842,7 +1021,8 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_x(Mat<4, 4, T> const &m, T const v) [[nodiscard]] inline auto shear_x(Mat<4, 4, T> const &m, T const v)
-> Mat<4, 4, T> { -> Mat<4, 4, T>
{
Mat<4, 4, T> res { 1 }; Mat<4, 4, T> res { 1 };
res[0, 1] = v; res[0, 1] = v;
return m * res; return m * res;
@@ -850,7 +1030,8 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_y(Mat<4, 4, T> const &m, T const v) [[nodiscard]] inline auto shear_y(Mat<4, 4, T> const &m, T const v)
-> Mat<4, 4, T> { -> Mat<4, 4, T>
{
Mat<4, 4, T> res { 1 }; Mat<4, 4, T> res { 1 };
res[1, 0] = v; res[1, 0] = v;
return m * res; return m * res;
@@ -858,17 +1039,18 @@ template <typename T>
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_z(Mat<4, 4, T> const &m, T const v) [[nodiscard]] inline auto shear_z(Mat<4, 4, T> const &m, T const v)
-> Mat<4, 4, T> { -> Mat<4, 4, T>
{
Mat<4, 4, T> res { 1 }; Mat<4, 4, T> res { 1 };
res[2, 0] = v; res[2, 0] = v;
return m * res; return m * res;
} }
template<typename T> template<typename T>
[[nodiscard]] inline auto [[nodiscard]] inline auto matrix_ortho3d(T const left, T const right,
matrix_ortho3d(T const left, T const right, T const bottom, T const top, T const bottom, T const top, T const near, T const far,
T const near, T const far, bool const flip_z_axis = true) bool const flip_z_axis = true) -> Mat<4, 4, T>
-> Mat<4, 4, T> { {
Mat<4, 4, T> res {}; Mat<4, 4, T> res {};
res[0, 0] = 2 / (right - left); res[0, 0] = 2 / (right - left);
@@ -887,8 +1069,9 @@ matrix_ortho3d(T const left, T const right, T const bottom, T const top,
} }
template<typename T> template<typename T>
inline auto matrix_perspective(T fovy, T aspect, T znear, T zfar, inline auto matrix_perspective(
bool flip_z_axis = false) -> Mat<4, 4, T> { T fovy, T aspect, T znear, T zfar, bool flip_z_axis = false) -> Mat<4, 4, T>
{
Mat<4, 4, T> m {}; Mat<4, 4, T> m {};
T const f { T(1) / std::tan(fovy / T(2)) }; T const f { T(1) / std::tan(fovy / T(2)) };
@@ -912,9 +1095,10 @@ inline auto matrix_perspective(T fovy, T aspect, T znear, T zfar,
} }
template<typename T> template<typename T>
[[nodiscard]] inline auto [[nodiscard]] inline auto matrix_look_at(Vec<3, T> const eye,
matrix_look_at(Vec<3, T> const eye, Vec<3, T> const center, Vec<3, T> const up, Vec<3, T> const center, Vec<3, T> const up, bool flip_z_axis = false)
bool flip_z_axis = false) -> Mat<4, 4, T> { -> Mat<4, 4, T>
{
auto f = (center - eye).normalized(); auto f = (center - eye).normalized();
auto s = f.cross(up).normalized(); auto s = f.cross(up).normalized();
auto u = s.cross(f); auto u = s.cross(f);
@@ -937,9 +1121,9 @@ matrix_look_at(Vec<3, T> const eye, Vec<3, T> const center, Vec<3, T> const up,
} }
template<typename T> template<typename T>
[[nodiscard]] inline auto [[nodiscard]] inline auto matrix_infinite_perspective(T const fovy,
matrix_infinite_perspective(T const fovy, T const aspect, T const znear, T const aspect, T const znear, bool flip_z_axis = false) -> Mat<4, 4, T>
bool flip_z_axis = false) -> Mat<4, 4, T> { {
Mat<4, 4, T> m {}; Mat<4, 4, T> m {};
T const f = 1 / std::tan(fovy / T(2)); T const f = 1 / std::tan(fovy / T(2));
@@ -964,12 +1148,14 @@ matrix_infinite_perspective(T const fovy, T const aspect, T const znear,
template<std::size_t N, typename T> template<std::size_t N, typename T>
requires std::formattable<T, char> requires std::formattable<T, char>
struct std::formatter<smath::Vec<N, T>> : std::formatter<T> { struct std::formatter<smath::Vec<N, T>> : std::formatter<T> {
constexpr auto parse(std::format_parse_context &ctx) { constexpr auto parse(std::format_parse_context &ctx)
{
return std::formatter<T>::parse(ctx); return std::formatter<T>::parse(ctx);
} }
template<typename Ctx> template<typename Ctx>
auto format(smath::Vec<N, T> const &v, Ctx &ctx) const { auto format(smath::Vec<N, T> const &v, Ctx &ctx) const
{
auto out = ctx.out(); auto out = ctx.out();
*out++ = '{'; *out++ = '{';
for (std::size_t i = 0; i < N; ++i) { for (std::size_t i = 0; i < N; ++i) {