/* * smath - Single-file linear algebra math library for C++23. * * Copyright 2025 Slendi * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * You can define the following macros to change functionality: * - SMATH_IMPLICIT_CONVERSIONS */ #pragma once #include #include #include #include #include #include #include #ifndef SMATH_ANGLE_UNIT #define SMATH_ANGLE_UNIT rad #endif // SMATH_ANGLE_UNIT namespace smath { template requires std::is_arithmetic_v struct Vec; namespace detail { #define SMATH_STR(x) #x #define SMATH_XSTR(x) SMATH_STR(x) consteval bool streq(const char *a, const char *b) { for (;; ++a, ++b) { if (*a != *b) return false; if (*a == '\0') return true; } } enum class AngularUnit { Radians, Degrees, Turns, }; consteval std::optional parse_unit(const char *s) { if (streq(s, "rad")) return AngularUnit::Radians; if (streq(s, "deg")) return AngularUnit::Degrees; if (streq(s, "turns")) return AngularUnit::Turns; return std::nullopt; } constexpr auto SMATH_ANGLE_UNIT_ID = parse_unit(SMATH_XSTR(SMATH_ANGLE_UNIT)); static_assert(SMATH_ANGLE_UNIT_ID != std::nullopt, "Invalid SMATH_ANGLE_UNIT. Should be rad, deg, or turns."); template struct FixedString { char data[N]{}; static constexpr std::size_t size = N - 1; constexpr FixedString(char const (&s)[N]) { for (std::size_t i = 0; i < N; ++i) data[i] = s[i]; } constexpr char operator[](std::size_t i) const { return data[i]; } }; template struct is_Vec : std::false_type {}; template struct is_Vec> : std::true_type {}; template inline constexpr bool is_Vec_v = is_Vec>::value; template inline constexpr bool is_scalar_v = std::is_arithmetic_v>; template struct Vec_size; template struct Vec_size> : std::integral_constant {}; } // namespace detail template requires std::is_arithmetic_v struct Vec : std::array { private: template static consteval std::size_t extent() { if constexpr (detail::is_Vec_v) return detail::Vec_size>::value; else if constexpr (detail::is_scalar_v) return 1; else return 0; // Should be unreachable } template static consteval std::size_t total_extent() { return (extent() + ... + 0); } public: // Constructors constexpr Vec() noexcept { for (auto &v : *this) v = T(0); } explicit constexpr Vec(T const &s) noexcept { for (auto &v : *this) v = s; } template requires((detail::is_scalar_v || detail::is_Vec_v) && ...) && (total_extent() == N) && (!(sizeof...(Args) == 1 && (detail::is_Vec_v && ...))) constexpr Vec(Args &&...args) noexcept { std::size_t i = 0; (fill_one(i, std::forward(args)), ...); } // Member accesses // NOTE: This can (probably) be improved with C++26 reflection in the future. #define VEC_ACC(component, req, idx) \ constexpr auto component() noexcept -> T &requires(N >= req) { \ return (*this)[idx]; \ } constexpr auto component() const->T const & \ requires(N >= req) \ { \ return (*this)[idx]; \ } VEC_ACC(r, 1, 0) VEC_ACC(g, 2, 1) VEC_ACC(b, 3, 2) VEC_ACC(a, 4, 3) VEC_ACC(x, 1, 0) VEC_ACC(y, 2, 1) VEC_ACC(z, 3, 2) VEC_ACC(w, 4, 3) VEC_ACC(s, 1, 0) VEC_ACC(t, 2, 1) VEC_ACC(p, 3, 2) VEC_ACC(q, 4, 3) VEC_ACC(u, 1, 0) VEC_ACC(v, 2, 1) #undef VEC_ACC template constexpr void unpack_impl(std::index_sequence, Args &...args) noexcept { ((args = (*this)[Is]), ...); } template constexpr void unpack(Args &...args) noexcept { unpack_impl(std::index_sequence_for{}, args...); } // Unary constexpr auto operator-() noexcept -> Vec { Vec r{}; for (std::size_t i = 0; i < N; ++i) r[i] = -(*this)[i]; return r; } // RHS operations friend constexpr auto operator+(T s, Vec const &v) noexcept -> Vec { return v + s; } friend constexpr auto operator-(T s, Vec const &v) noexcept -> Vec { return Vec(s) - v; } friend constexpr auto operator*(T s, Vec const &v) noexcept -> Vec { return v * s; } friend constexpr auto operator/(T s, Vec const &v) noexcept -> Vec { Vec r{}; for (std::size_t i = 0; i < N; ++i) r[i] = s / v[i]; return r; } // Members #define VEC_OP(op) \ constexpr auto operator op(Vec const &rhs) const noexcept -> Vec { \ Vec result{}; \ for (std::size_t i = 0; i < N; ++i) { \ result[i] = (*this)[i] op rhs[i]; \ } \ return result; \ } \ constexpr auto operator op(T const &rhs) const noexcept -> Vec { \ Vec result{}; \ for (std::size_t i = 0; i < N; ++i) { \ result[i] = (*this)[i] op rhs; \ } \ return result; \ } VEC_OP(+) VEC_OP(-) VEC_OP(*) VEC_OP(/) #undef VEC_OP #define VEC_OP_ASSIGN(sym) \ constexpr Vec &operator sym##=(Vec const &rhs) noexcept { \ for (std::size_t i = 0; i < N; ++i) \ (*this)[i] sym## = rhs[i]; \ return *this; \ } \ constexpr Vec &operator sym##=(T const &s) noexcept { \ for (std::size_t i = 0; i < N; ++i) \ (*this)[i] sym## = s; \ return *this; \ } VEC_OP_ASSIGN(+) VEC_OP_ASSIGN(-) VEC_OP_ASSIGN(*) VEC_OP_ASSIGN(/) #undef VEC_OP_ASSIGN constexpr auto operator==(Vec const &v) const noexcept -> bool { for (std::size_t i = 0; i < N; ++i) if ((*this)[i] != v[i]) return false; return true; } constexpr auto operator!=(Vec const &v) const noexcept -> bool { return !(*this == v); } constexpr auto magnitude() const noexcept -> T requires std::is_floating_point_v { T total = 0; for (auto const &v : *this) total += v * v; return std::sqrt(total); } constexpr auto length() const noexcept -> T requires std::is_floating_point_v { return this->magnitude(); } template requires std::is_floating_point_v constexpr auto normalized_safe(U eps = EPS_DEFAULT) const noexcept -> Vec { auto m = magnitude(); return (m > eps) ? (*this) / m : Vec{}; } template requires std::is_floating_point_v constexpr auto normalize_safe(U eps = EPS_DEFAULT) const noexcept -> Vec { return normalized_safe(eps); } [[nodiscard]] constexpr auto normalized() noexcept -> Vec requires std::is_floating_point_v { return (*this) / this->magnitude(); } [[nodiscard]] constexpr auto normalize() noexcept -> Vec requires std::is_floating_point_v { return this->normalized(); } [[nodiscard]] constexpr auto unit() noexcept -> Vec requires std::is_floating_point_v { return this->normalized(); } [[nodiscard]] constexpr auto dot(Vec const &other) const noexcept -> T { T res = 0; for (std::size_t i = 0; i < N; ++i) { res += (*this)[i] * other[i]; } return res; } static constexpr T EPS_DEFAULT = T(1e-6); template requires std::is_floating_point_v [[nodiscard]] constexpr auto approx_equal(Vec const &rhs, U eps = EPS_DEFAULT) const noexcept { using F = std::conditional_t, U, double>; for (size_t i = 0; i < N; ++i) if (std::abs(F((*this)[i] - rhs[i])) > F(eps)) return false; return true; } template constexpr auto magnitude_promoted() const noexcept requires std::is_floating_point_v { using F = std::conditional_t, U, double>; F s = 0; for (auto v : *this) s += F(v) * F(v); return std::sqrt(s); } template requires(N == 3) constexpr auto cross(const Vec &r) const noexcept -> Vec { return {(*this)[1] * r[2] - (*this)[2] * r[1], (*this)[2] * r[0] - (*this)[0] * r[2], (*this)[0] * r[1] - (*this)[1] * r[0]}; } constexpr auto distance(Vec const &r) const noexcept -> T requires std::is_floating_point_v { return (*this - r).magnitude(); } constexpr auto project_onto(Vec const &n) const noexcept -> Vec requires std::is_floating_point_v { auto d = this->dot(n); auto nn = n.dot(n); return (nn ? (d / nn) * n : Vec()); } template requires(std::is_arithmetic_v && N >= 1) constexpr explicit(!std::is_convertible_v) Vec(Vec const &other) noexcept { for (std::size_t i = 0; i < N; ++i) this->operator[](i) = static_cast(other[i]); } template requires(std::is_arithmetic_v && N >= 1) constexpr explicit(!std::is_convertible_v) operator Vec() const noexcept { Vec r{}; for (std::size_t i = 0; i < N; ++i) r[i] = static_cast((*this)[i]); return r; } template requires(std::is_arithmetic_v && !std::is_same_v) constexpr auto operator=(Vec const &rhs) noexcept -> Vec & { for (std::size_t i = 0; i < N; ++i) (*this)[i] = static_cast(rhs[i]); return *this; } private: constexpr void fill_one(std::size_t &i, const T &v) noexcept { (*this)[i++] = v; } #ifdef SMATH_IMPLICIT_CONVERSIONS template requires std::is_arithmetic_v && (!std::is_same_v) constexpr void fill_one(std::size_t &i, const U &v) noexcept { (*this)[i++] = static_cast(v); } template constexpr void fill_one(std::size_t &i, const Vec &v) noexcept { for (std::size_t k = 0; k < M; ++k) (*this)[i++] = static_cast(v[k]); } #endif // SMATH_IMPLICIT_CONVERSIONS template constexpr void fill_one(std::size_t &i, const Vec &v) noexcept { for (std::size_t k = 0; k < M; ++k) (*this)[i++] = static_cast(v[k]); } }; template constexpr T &get(Vec &v) noexcept { static_assert(I < N); return v[I]; } template constexpr const T &get(const Vec &v) noexcept { static_assert(I < N); return v[I]; } template constexpr T &&get(Vec &&v) noexcept { static_assert(I < N); return std::move(v[I]); } template constexpr const T &&get(const Vec &&v) noexcept { static_assert(I < N); return std::move(v[I]); } template requires std::is_arithmetic_v using VecOrScalar = std::conditional_t>; namespace detail { consteval auto char_to_idx(char c) -> std::size_t { if (c == 'r' || c == 'x' || c == 's' || c == 'u') return 0; else if (c == 'g' || c == 'y' || c == 't' || c == 'v') return 1; else if (c == 'b' || c == 'z' || c == 'p') return 2; else if (c == 'a' || c == 'w' || c == 'q') return 3; return static_cast(-1); } constexpr auto is_valid(char c) -> bool { switch (c) { case 'r': case 'g': case 'b': case 'a': case 'x': case 'y': case 'z': case 'w': case 's': case 't': case 'p': case 'q': case 'u': case 'v': return true; default: return false; } return false; } template constexpr auto swizzle_impl(Vec const &v, std::index_sequence) -> VecOrScalar { static_assert(((is_valid(S[I])) && ...), "Invalid swizzle component"); static_assert(((char_to_idx(S[I]) < N) && ...), "Pattern index out of bounds"); VecOrScalar out{}; std::size_t i = 0; ((out[i++] = v[char_to_idx(S[I])]), ...); return out; } template concept SwizzleCharsOK = [](std::index_sequence) { return ((is_valid(S[I])) && ...); }(std::make_index_sequence{}); template concept SwizzleInBounds = [](std::index_sequence) { return ((char_to_idx(S[I]) < N) && ...); }(std::make_index_sequence{}); template concept ValidSwizzle = (S.size > 0) && SwizzleCharsOK && SwizzleInBounds; } // namespace detail template requires detail::ValidSwizzle constexpr auto swizzle(Vec const &v) -> VecOrScalar { return detail::swizzle_impl(v, std::make_index_sequence{}); } using Vec2 = Vec<2>; using Vec3 = Vec<3>; using Vec4 = Vec<4>; using Vec2d = Vec<2, double>; using Vec3d = Vec<3, double>; using Vec4d = Vec<4, double>; template constexpr auto deg(T const value) -> T { if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { return value; } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Radians) { return value * static_cast(std::numbers::pi / 180.0); } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Turns) { return value / static_cast(360.0); } } template constexpr auto rad(T const value) -> T { if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { return value * static_cast(180.0 / std::numbers::pi); } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Radians) { return value; } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Turns) { return value / (static_cast(2.0) * static_cast(std::numbers::pi)); } } template constexpr auto turns(T const value) -> T { if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { return value * static_cast(360.0); } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Radians) { return value * (static_cast(2.0) * static_cast(std::numbers::pi)); } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Turns) { return value; } } template struct Quaternion : Vec<4, T> { using Base = Vec<4, T>; using Base::Base; using Base::operator=; constexpr Base &vec() noexcept { return *this; } constexpr Base const &vec() const noexcept { return *this; } constexpr T &x() noexcept { return Base::x(); } constexpr T &y() noexcept { return Base::y(); } constexpr T &z() noexcept { return Base::z(); } constexpr T &w() noexcept { return Base::w(); } constexpr auto operator*(Quaternion const &rhs) const noexcept -> Quaternion { Quaternion r; auto const &a = *this; r.x() = a.w() * rhs.x() + a.x() * rhs.w() + a.y() * rhs.z() - a.z() * rhs.y(); r.y() = a.w() * rhs.y() - a.x() * rhs.z() + a.y() * rhs.w() + a.z() * rhs.x(); r.z() = a.w() * rhs.z() + a.x() * rhs.y() - a.y() * rhs.x() + a.z() * rhs.w(); r.w() = a.w() * rhs.w() - a.x() * rhs.x() - a.y() * rhs.y() - a.z() * rhs.z(); return r; } }; template requires std::is_arithmetic_v struct Mat : std::array, C> { using Base = std::array, C>; using Base::operator[]; constexpr auto operator[](std::size_t const row, std::size_t const column) -> T & { return col(column)[row]; } constexpr auto operator[](std::size_t const row, std::size_t const column) const -> T const & { return col(column)[row]; } constexpr Mat() noexcept { for (auto &col : *this) col = Vec{}; } constexpr explicit Mat(T const &diag) noexcept requires(R == C) { for (std::size_t c = 0; c < C; ++c) { (*this)[c] = Vec{}; (*this)[c][c] = diag; } } template requires(sizeof...(Cols) == C && (std::same_as, Vec> && ...)) constexpr Mat(Cols const &...cols) noexcept : Base{cols...} {} constexpr auto col(std::size_t j) noexcept -> Vec & { return (*this)[j]; } constexpr auto col(std::size_t j) const noexcept -> Vec const & { return (*this)[j]; } constexpr auto operator()(std::size_t row, std::size_t col) noexcept -> T & { return (*this)[col][row]; } constexpr auto operator()(std::size_t row, std::size_t col) const noexcept -> T const & { return (*this)[col][row]; } constexpr auto operator-() const noexcept -> Mat { Mat r{}; for (std::size_t c = 0; c < C; ++c) r[c] = -(*this)[c]; return r; } constexpr auto operator+=(Mat const &rhs) noexcept -> Mat & { for (std::size_t c = 0; c < C; ++c) (*this)[c] += rhs[c]; return *this; } constexpr auto operator-=(Mat const &rhs) noexcept -> Mat & { for (std::size_t c = 0; c < C; ++c) (*this)[c] -= rhs[c]; return *this; } friend constexpr auto operator+(Mat lhs, Mat const &rhs) noexcept -> Mat { lhs += rhs; return lhs; } friend constexpr auto operator-(Mat lhs, Mat const &rhs) noexcept -> Mat { lhs -= rhs; return lhs; } constexpr auto operator*=(T const &s) noexcept -> Mat & { for (std::size_t c = 0; c < C; ++c) (*this)[c] *= s; return *this; } constexpr auto operator/=(T const &s) noexcept -> Mat & { for (std::size_t c = 0; c < C; ++c) (*this)[c] /= s; return *this; } friend constexpr auto operator*(Mat lhs, T const &s) noexcept -> Mat { lhs *= s; return lhs; } friend constexpr auto operator*(T const &s, Mat rhs) noexcept -> Mat { rhs *= s; return rhs; } friend constexpr auto operator/(Mat lhs, T const &s) noexcept -> Mat { lhs /= s; return lhs; } [[nodiscard]] constexpr auto operator==(Mat const &rhs) const noexcept -> bool { for (std::size_t c = 0; c < C; ++c) if (!((*this)[c] == rhs[c])) return false; return true; } [[nodiscard]] constexpr auto operator!=(Mat const &rhs) const noexcept -> bool { return !(*this == rhs); } static constexpr T EPS_DEFAULT = T(1e-6); template requires std::is_floating_point_v [[nodiscard]] constexpr auto approx_equal(Mat const &rhs, U eps = EPS_DEFAULT) const noexcept -> bool { for (std::size_t c = 0; c < C; ++c) if (!(*this)[c].approx_equal(rhs[c], eps)) return false; return true; } [[nodiscard]] constexpr auto transposed() const noexcept -> Mat { Mat r{}; for (std::size_t c = 0; c < C; ++c) for (std::size_t r_idx = 0; r_idx < R; ++r_idx) r(r_idx, c) = (*this)(c, r_idx); return r; } [[nodiscard]] static constexpr auto identity() noexcept -> Mat requires(R == C) { Mat m{}; for (std::size_t i = 0; i < R; ++i) m(i, i) = T(1); return m; } }; using Mat2 = Mat<2, 2>; using Mat3 = Mat<3, 3>; using Mat4 = Mat<4, 4>; using Mat2d = Mat<2, 2, double>; using Mat3d = Mat<3, 3, double>; using Mat4d = Mat<4, 4, double>; template [[nodiscard]] constexpr Vec operator*(Mat const &m, Vec const &v) noexcept { Vec out{}; for (std::size_t c = 0; c < C; ++c) out += m.col(c) * v[c]; return out; } // Matrix * Matrix template [[nodiscard]] constexpr Mat operator*(Mat const &a, Mat const &b) noexcept { Mat out{}; for (std::size_t k = 0; k < K; ++k) { for (std::size_t r = 0; r < R; ++r) { T sum = T(0); for (std::size_t c = 0; c < C; ++c) sum += a(r, c) * b(c, k); out(r, k) = sum; } } return out; } // Mat3 transformations template [[nodiscard]] inline auto translate(Mat<3, 3, T> const &m, Vec<2, T> const &v) -> Mat<3, 3, T> { Mat<3, 3, T> res{m}; res[2] = m[0] * v[0] + m[1] * v[1] + m[2]; return res; } template [[nodiscard]] inline auto translate(Vec<2, T> const &v) -> Mat<3, 3, T> { Mat<3, 3, T> res{1}; res[2].x() = v.x(); res[2].y() = v.y(); return res; } template [[nodiscard]] inline auto rotate(Mat<3, 3, T> const &m, T const angle) -> Mat<3, 3, T> { Mat<3, 3, T> res; T const c{std::cos(angle)}; T const s{std::sin(angle)}; res[0] = m[0] * c + m[1] * s; res[1] = m[0] * -s + m[1] * c; res[2] = m[2]; return res; } template [[nodiscard]] inline auto scale(Mat<3, 3, T> const &m, Vec<2, T> const &v) -> Mat<3, 3, T> { Mat<3, 3, T> res; res[0] = m[0] * v[0]; res[1] = m[1] * v[1]; res[2] = m[2]; return res; } template [[nodiscard]] inline auto shear_x(Mat<3, 3, T> const &m, T const v) -> Mat<3, 3, T> { Mat<3, 3, T> res{1}; res[1][0] = v; return m * res; } template [[nodiscard]] inline auto shear_y(Mat<3, 3, T> const &m, T const v) -> Mat<3, 3, T> { Mat<3, 3, T> res{1}; res[0][1] = v; return m * res; } // Mat4 transformations template [[nodiscard]] inline auto translate(Mat<4, 4, T> const &m, Vec<3, T> const &v) -> Mat<4, 4, T> { Mat<4, 4, T> res{m}; res[3] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3]; return res; } template [[nodiscard]] inline auto translate(Vec<3, T> const &v) -> Mat<4, 4, T> { Mat<4, 4, T> res{1}; res[3].x() = v.x(); res[3].y() = v.y(); res[3].z() = v.z(); return res; } template [[nodiscard]] inline auto rotate(Mat<4, 4, T> const &m, T const angle) -> Mat<4, 4, T> { Mat<4, 4, T> res; T const c{std::cos(angle)}; T const s{std::sin(angle)}; res[0] = m[0] * c + m[1] * s; res[1] = m[0] * -s + m[1] * c; res[2] = m[2]; res[3] = m[3]; return res; } template [[nodiscard]] inline auto scale(Mat<4, 4, T> const &m, Vec<3, T> const &v) -> Mat<4, 4, T> { Mat<4, 4, T> res; res[0] = m[0] * v[0]; res[1] = m[1] * v[1]; res[2] = m[2] * v[2]; res[3] = m[3]; return res; } template [[nodiscard]] inline auto shear_x(Mat<4, 4, T> const &m, T const v) -> Mat<4, 4, T> { Mat<4, 4, T> res{1}; res[0, 1] = v; return m * res; } template [[nodiscard]] inline auto shear_y(Mat<4, 4, T> const &m, T const v) -> Mat<4, 4, T> { Mat<4, 4, T> res{1}; res[1, 0] = v; return m * res; } template [[nodiscard]] inline auto shear_z(Mat<4, 4, T> const &m, T const v) -> Mat<4, 4, T> { Mat<4, 4, T> res{1}; res[2, 0] = v; return m * res; } template [[nodiscard]] inline auto matrix_ortho3d(T const left, T const right, T const bottom, T const top, T const near, T const far, bool const flip_z_axis = true) -> Mat<4, 4, T> { Mat<4, 4, T> res{}; res[0, 0] = 2 / (right - left); res[1, 1] = 2 / (top - bottom); res[2, 2] = -2 / (far - near); res[0, 3] = -(right + left) / (right - left); res[1, 3] = -(top + bottom) / (top - bottom); res[2, 3] = -(far + near) / (far - near); res[3, 3] = 1; if (flip_z_axis) { res[2] = -res[2]; } return res; } template inline auto matrix_perspective(T fovy, T aspect, T znear, T zfar, bool flip_z_axis = false) -> Mat<4, 4, T> { Mat<4, 4, T> m{}; T const f{T(1) / std::tan(fovy / T(2))}; m[0, 0] = f / aspect; m[1, 1] = f; if (!flip_z_axis) { m[2, 2] = -(zfar + znear) / (zfar - znear); m[2, 3] = -(T(2) * zfar * znear) / (zfar - znear); m[3, 2] = -1; m[3, 3] = 0; } else { m[2, 2] = (zfar + znear) / (zfar - znear); m[2, 3] = (T(2) * zfar * znear) / (zfar - znear); m[3, 2] = 1; m[3, 3] = 0; } return m; } template [[nodiscard]] inline auto matrix_look_at(Vec<3, T> const eye, Vec<3, T> const center, Vec<3, T> const up, bool flip_z_axis = false) -> Mat<4, 4, T> { auto f = (center - eye).normalized(); auto s = f.cross(up).normalized(); auto u = s.cross(f); if (!flip_z_axis) { return Mat<4, 4, T>{ Vec<4, T>{s.x(), s.y(), s.z(), 0}, Vec<4, T>{u.x(), u.y(), u.z(), 0}, Vec<4, T>{-f.x(), -f.y(), -f.z(), 0}, Vec<4, T>{-s.dot(eye), -u.dot(eye), f.dot(eye), 1}, }; } else { return Mat<4, 4, T>{ Vec<4, T>{s.x(), s.y(), s.z(), 0}, Vec<4, T>{u.x(), u.y(), u.z(), 0}, Vec<4, T>{f.x(), f.y(), f.z(), 0}, Vec<4, T>{-s.dot(eye), -u.dot(eye), -f.dot(eye), 1}, }; } } template [[nodiscard]] inline auto matrix_infinite_perspective(T const fovy, T const aspect, T const znear, bool flip_z_axis = false) -> Mat<4, 4, T> { Mat<4, 4, T> m{}; T const f = 1 / std::tan(fovy / T(2)); m(0, 0) = f / aspect; m(1, 1) = f; if (!flip_z_axis) { m(2, 2) = -1; m(2, 3) = -T(2) * znear; m(3, 2) = -1; } else { m(2, 2) = 1; m(2, 3) = T(2) * znear; m(3, 2) = 1; } return m; } } // namespace smath template requires std::formattable struct std::formatter> : std::formatter { constexpr auto parse(std::format_parse_context &ctx) { return std::formatter::parse(ctx); } template auto format(smath::Vec const &v, Ctx &ctx) const { auto out = ctx.out(); *out++ = '{'; for (std::size_t i = 0; i < N; ++i) { if (i) { *out++ = ','; *out++ = ' '; } out = std::formatter::format(v[i], ctx); } *out++ = '}'; return out; } }; namespace std { template struct tuple_size> : std::integral_constant {}; template struct tuple_element> { static_assert(I < N); using type = T; }; } // namespace std