Compare commits

...

4 Commits

Author SHA1 Message Date
d3511a9b52 Forgot to change something in the CI oops
Signed-off-by: Slendi <slendi@socopon.com>
2025-12-12 01:07:10 +02:00
eed719674f Add CI
Signed-off-by: Slendi <slendi@socopon.com>
2025-12-12 01:05:57 +02:00
5f0badfe64 Fix packaging for Nix
Signed-off-by: Slendi <slendi@socopon.com>
2025-12-12 01:05:14 +02:00
1a42238a41 Formatting + new packing related functions
Signed-off-by: Slendi <slendi@socopon.com>
2025-12-11 12:55:04 +02:00
5 changed files with 980 additions and 740 deletions

29
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Build project
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-22.04
permissions:
id-token: write
contents: read
steps:
- name: git checkout
uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Building default package
run: nix build .#default
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: result
path: result

View File

@@ -67,6 +67,9 @@ endif()
if(BUILD_TESTS)
enable_testing()
find_package(GTest QUIET)
if(NOT GTest_FOUND)
include(FetchContent)
FetchContent_Declare(
googletest
@@ -74,6 +77,7 @@ if(BUILD_TESTS)
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
endif()
file(GLOB TEST_SOURCES "${CMAKE_SOURCE_DIR}/tests/*.cpp")
@@ -86,3 +90,4 @@ if(BUILD_TESTS)
include(GoogleTest)
gtest_discover_tests(smath_tests)
endif()

View File

@@ -1,4 +1,19 @@
# smath
Single-file linear algebra math library for C++23.
Single-file, header-only linear algebra math library for C++23.
## Features
- Generic `Vec<N, T>` class with useful aliases `Vec2/Vec3/Vec4` and friendly accessors (`x/y/z/w`, `r/g/b/a`). They support approx-equal and tuple/structured bindings.
- `std::format` support.
- Compile-time swizzles via `swizzle<"...">`.
- Generic matrix `Mat` class with useful aliases `Mat2/Mat3/Mat4`.
- `Quaternion<T>` built on `Vec4`.
- Angle helpers `rad/deg/turns` respecting a configurable base unit via the macro `SMATH_ANGLE_UNIT`.
- Optional implicit conversions.
- Packing utilities for normalized RGBA (`pack_unorm4x8`, `unpack_snorm4x8`, etc.).
## License
This library is licensed under the Apache License 2.0. See the (LICENSE.txt)[LICENSE.txt] file for more details.

View File

@@ -36,7 +36,11 @@
src = ./.;
nativeBuildInputs = [ pkgs.copyPkgconfigItems ];
nativeBuildInputs = with pkgs; [
cmake
gtest
copyPkgconfigItems
];
pkgconfigItems = [
(pkgs.makePkgconfigItem rec {
@@ -51,17 +55,18 @@
})
];
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out/include
cp include/*.hpp $out/include/
cp ../include/smath.hpp $out/include/
runHook postInstall
'';
dontBuild = false;
doCheck = true;
meta = with pkgs.lib; {
description = desc;
description = "Single-file linear algebra math library for C++23.";
homepage = "https://github.com/slendidev/smath";
license = licenses.asl20;
platforms = platforms.all;

View File

@@ -21,9 +21,11 @@
#pragma once
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <format>
#include <numbers>
#include <optional>
@@ -36,15 +38,15 @@
namespace smath {
template<std::size_t N, typename T>
requires std::is_arithmetic_v<T>
struct Vec;
requires std::is_arithmetic_v<T> 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) {
consteval bool streq(const char *a, const char *b)
{
for (;; ++a, ++b) {
if (*a != *b)
return false;
@@ -59,7 +61,8 @@ enum class AngularUnit {
Turns,
};
consteval std::optional<AngularUnit> parse_unit(const char *s) {
consteval std::optional<AngularUnit> parse_unit(char const *s)
{
if (streq(s, "rad"))
return AngularUnit::Radians;
if (streq(s, "deg"))
@@ -76,7 +79,8 @@ static_assert(SMATH_ANGLE_UNIT_ID != std::nullopt,
template<std::size_t N> struct FixedString {
char data[N] {};
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)
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>
inline constexpr bool is_Vec_v = is_Vec<std::remove_cvref_t<X>>::value;
template<class X>
inline constexpr bool is_scalar_v =
std::is_arithmetic_v<std::remove_cvref_t<X>>;
inline constexpr bool is_scalar_v
= std::is_arithmetic_v<std::remove_cvref_t<X>>;
template<class X> struct Vec_size;
template<std::size_t M, class U>
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
template<std::size_t N, typename T = float>
requires std::is_arithmetic_v<T>
struct Vec : std::array<T, N> {
requires std::is_arithmetic_v<T> struct Vec : std::array<T, N> {
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>)
return detail::Vec_size<std::remove_cvref_t<X>>::value;
else if constexpr (detail::is_scalar_v<X>)
@@ -107,33 +155,38 @@ private:
else
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);
}
public:
// Constructors
constexpr Vec() noexcept {
constexpr Vec() noexcept
{
for (auto &v : *this)
v = T(0);
}
explicit constexpr Vec(T const &s) noexcept {
explicit constexpr Vec(T const &s) noexcept
{
for (auto &v : *this)
v = s;
}
template<typename... Args>
requires((detail::is_scalar_v<Args> || detail::is_Vec_v<Args>) && ...) &&
(total_extent<Args...>() == N) &&
(!(sizeof...(Args) == 1 && (detail::is_Vec_v<Args> && ...)))
constexpr Vec(Args &&...args) noexcept {
requires((detail::is_scalar_v<Args> || detail::is_Vec_v<Args>) && ...)
&& (total_extent<Args...>() == N)
&& (!(sizeof...(Args) == 1 && (detail::is_Vec_v<Args> && ...)))
constexpr Vec(Args &&...args) noexcept
{
std::size_t i = 0;
(fill_one(i, std::forward<Args>(args)), ...);
}
// 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) \
constexpr auto component() noexcept -> T &requires(N >= req) { \
return (*this)[idx]; \
@@ -163,17 +216,20 @@ public:
#undef VEC_ACC
template<class... Args, std::size_t... Is>
constexpr void unpack_impl(std::index_sequence<Is...>,
Args &...args) noexcept {
constexpr void unpack_impl(
std::index_sequence<Is...>, Args &...args) noexcept
{
((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...);
}
// Unary
constexpr auto operator-() noexcept -> Vec {
constexpr auto operator-() noexcept -> Vec
{
Vec r {};
for (std::size_t i = 0; i < N; ++i)
r[i] = -(*this)[i];
@@ -181,16 +237,20 @@ public:
}
// 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;
}
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;
}
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;
}
friend constexpr auto operator/(T s, Vec const &v) noexcept -> Vec {
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];
@@ -199,14 +259,16 @@ public:
// Members
#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 {}; \
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 { \
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; \
@@ -219,12 +281,14 @@ public:
VEC_OP(/)
#undef VEC_OP
#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) \
(*this)[i] sym## = rhs[i]; \
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) \
(*this)[i] sym## = s; \
return *this; \
@@ -235,14 +299,16 @@ public:
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)
if ((*this)[i] != v[i])
return false;
return true;
}
constexpr auto operator!=(Vec const &v) const noexcept -> bool {
constexpr auto operator!=(Vec const &v) const noexcept -> bool
{
return !(*this == v);
}
@@ -262,13 +328,15 @@ public:
template<typename U = T>
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();
return (m > eps) ? (*this) / m : Vec {};
}
template<typename U = T>
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);
}
@@ -288,7 +356,8 @@ public:
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;
for (std::size_t i = 0; i < N; ++i) {
res += (*this)[i] * other[i];
@@ -299,8 +368,9 @@ public:
static constexpr T EPS_DEFAULT = T(1e-6);
template<class U = T>
requires std::is_floating_point_v<U>
[[nodiscard]] constexpr auto
approx_equal(Vec const &rhs, U eps = EPS_DEFAULT) const noexcept {
[[nodiscard]] constexpr auto approx_equal(
Vec const &rhs, U eps = EPS_DEFAULT) const noexcept
{
using F = std::conditional_t<std::is_floating_point_v<U>, U, double>;
for (size_t i = 0; i < N; ++i)
if (std::abs(F((*this)[i] - rhs[i])) > F(eps))
@@ -320,8 +390,8 @@ public:
}
template<typename U = T>
requires(N == 3)
constexpr auto cross(const Vec &r) const noexcept -> Vec {
requires(N == 3) constexpr auto cross(Vec const &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] };
@@ -344,15 +414,16 @@ public:
template<class U>
requires(std::is_arithmetic_v<U> && N >= 1)
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)
this->operator[](i) = static_cast<T>(other[i]);
}
template<class U>
requires(std::is_arithmetic_v<U> && N >= 1)
constexpr explicit(!std::is_convertible_v<T, U>)
operator Vec<N, U>() const noexcept {
requires(std::is_arithmetic_v<U> && N >= 1) constexpr explicit(
!std::is_convertible_v<T, U>) operator Vec<N, U>() const noexcept
{
Vec<N, U> r {};
for (std::size_t i = 0; i < N; ++i)
r[i] = static_cast<U>((*this)[i]);
@@ -361,51 +432,60 @@ public:
template<class U>
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)
(*this)[i] = static_cast<T>(rhs[i]);
return *this;
}
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;
}
#ifdef SMATH_IMPLICIT_CONVERSIONS
template<class U>
requires std::is_arithmetic_v<U> && (!std::is_same_v<U, T>)
constexpr void fill_one(std::size_t &i, const U &v) noexcept {
requires std::is_arithmetic_v<U>
&& (!std::is_same_v<U, T>)constexpr void fill_one(
std::size_t &i, const U &v) noexcept
{
(*this)[i++] = static_cast<T>(v);
}
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)
(*this)[i++] = static_cast<T>(v[k]);
}
#endif // SMATH_IMPLICIT_CONVERSIONS
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)
(*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);
return v[I];
}
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);
return v[I];
}
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);
return std::move(v[I]);
}
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);
return std::move(v[I]);
}
@@ -416,7 +496,8 @@ using VecOrScalar = std::conditional_t<N == 1, T, Vec<N, T>>;
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')
return 0;
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);
}
constexpr auto is_valid(char c) -> bool {
constexpr auto is_valid(char c) -> bool
{
switch (c) {
case 'r':
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>
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(((char_to_idx(S[I]) < N) && ...),
"Pattern index out of bounds");
static_assert(
((char_to_idx(S[I]) < N) && ...), "Pattern index out of bounds");
VecOrScalar<S.size, T> out {};
std::size_t i = 0;
((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> {});
template<FixedString S, std::size_t N>
concept ValidSwizzle =
(S.size > 0) && SwizzleCharsOK<S> && SwizzleInBounds<S, N>;
concept ValidSwizzle
= (S.size > 0) && SwizzleCharsOK<S> && SwizzleInBounds<S, N>;
} // namespace detail
template<detail::FixedString S, std::size_t N, typename T>
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> {});
}
@@ -493,38 +577,41 @@ using Vec2d = Vec<2, double>;
using Vec3d = Vec<3, 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) {
return value;
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID ==
detail::AngularUnit::Radians) {
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID
== detail::AngularUnit::Radians) {
return value * static_cast<T>(std::numbers::pi / 180.0);
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID ==
detail::AngularUnit::Turns) {
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID
== detail::AngularUnit::Turns) {
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) {
return value * static_cast<T>(180.0 / std::numbers::pi);
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID ==
detail::AngularUnit::Radians) {
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID
== detail::AngularUnit::Radians) {
return value;
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID ==
detail::AngularUnit::Turns) {
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID
== detail::AngularUnit::Turns) {
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) {
return value * static_cast<T>(360.0);
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID ==
detail::AngularUnit::Radians) {
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID
== detail::AngularUnit::Radians) {
return value * (static_cast<T>(2.0) * static_cast<T>(std::numbers::pi));
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID ==
detail::AngularUnit::Turns) {
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID
== detail::AngularUnit::Turns) {
return value;
}
}
@@ -542,39 +629,100 @@ template <class T> struct Quaternion : Vec<4, T> {
constexpr T &z() noexcept { return Base::z(); }
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;
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();
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<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>
requires std::is_arithmetic_v<T>
struct Mat : std::array<Vec<R, T>, C> {
requires std::is_arithmetic_v<T> struct Mat : std::array<Vec<R, T>, C> {
using Base = std::array<Vec<R, T>, C>;
using Base::operator[];
constexpr auto operator[](std::size_t const row, std::size_t const column)
-> T & {
-> T &
{
return col(column)[row];
}
constexpr auto operator[](std::size_t const row,
std::size_t const column) const -> T const & {
constexpr auto operator[](
std::size_t const row, std::size_t const column) const -> T const &
{
return col(column)[row];
}
constexpr Mat() noexcept {
constexpr Mat() noexcept
{
for (auto &col : *this)
col = Vec<R, T> {};
}
@@ -589,99 +737,119 @@ struct Mat : std::array<Vec<R, T>, C> {
}
template<typename... Cols>
requires(sizeof...(Cols) == C &&
(std::same_as<std::remove_cvref_t<Cols>, Vec<R, T>> && ...))
constexpr Mat(Cols const &...cols) noexcept : Base{cols...} {}
requires(sizeof...(Cols) == C
&& (std::same_as<std::remove_cvref_t<Cols>, Vec<R, T>> && ...))
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];
}
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];
}
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];
}
constexpr auto operator()(std::size_t row, std::size_t col) const noexcept
-> T const & {
-> T const &
{
return (*this)[col][row];
}
constexpr auto operator-() const noexcept -> Mat {
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 & {
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 & {
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 {
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 {
friend constexpr auto operator-(Mat lhs, Mat const &rhs) noexcept -> Mat
{
lhs -= rhs;
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)
(*this)[c] *= s;
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)
(*this)[c] /= s;
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;
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;
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;
return lhs;
}
[[nodiscard]] constexpr auto operator==(Mat const &rhs) const noexcept
-> bool {
-> 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 {
-> bool
{
return !(*this == rhs);
}
static constexpr T EPS_DEFAULT = T(1e-6);
template<class U = T>
requires std::is_floating_point_v<U>
[[nodiscard]] constexpr auto approx_equal(Mat const &rhs,
U eps = EPS_DEFAULT) const noexcept
-> bool {
[[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<C, R, T> {
[[nodiscard]] constexpr auto transposed() const noexcept -> Mat<C, R, T>
{
Mat<C, R, T> r {};
for (std::size_t c = 0; c < C; ++c)
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>;
template<std::size_t R, std::size_t C, typename T>
[[nodiscard]] constexpr Vec<R, T> operator*(Mat<R, C, T> const &m,
Vec<C, T> const &v) noexcept {
[[nodiscard]] constexpr Vec<R, T> operator*(
Mat<R, C, T> const &m, Vec<C, T> const &v) noexcept
{
Vec<R, T> out {};
for (std::size_t c = 0; c < C; ++c)
out += m.col(c) * v[c];
@@ -718,8 +887,9 @@ template <std::size_t R, std::size_t C, typename T>
// Matrix * Matrix
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,
Mat<C, K, T> const &b) noexcept {
[[nodiscard]] constexpr Mat<R, K, T> operator*(
Mat<R, C, T> const &a, Mat<C, K, T> const &b) noexcept
{
Mat<R, K, T> out {};
for (std::size_t k = 0; k < K; ++k) {
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
template<typename T>
[[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 };
res[2] = m[0] * v[0] + m[1] * v[1] + m[2];
return res;
}
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 };
res[2].x() = v.x();
res[2].y() = v.y();
@@ -751,7 +923,8 @@ template <typename T>
template<typename T>
[[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;
T const c { std::cos(angle) };
@@ -766,7 +939,8 @@ template <typename T>
template<typename T>
[[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;
res[0] = m[0] * v[0];
@@ -778,7 +952,8 @@ template <typename T>
template<typename T>
[[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 };
res[1][0] = v;
return m * res;
@@ -786,7 +961,8 @@ template <typename T>
template<typename T>
[[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 };
res[0][1] = v;
return m * res;
@@ -795,15 +971,16 @@ template <typename T>
// Mat4 transformations
template<typename T>
[[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 };
res[3] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3];
return res;
}
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 };
res[3].x() = v.x();
res[3].y() = v.y();
@@ -813,7 +990,8 @@ template <typename T>
template<typename T>
[[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;
T const c { std::cos(angle) };
@@ -829,7 +1007,8 @@ template <typename T>
template<typename T>
[[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;
res[0] = m[0] * v[0];
@@ -842,7 +1021,8 @@ template <typename T>
template<typename T>
[[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 };
res[0, 1] = v;
return m * res;
@@ -850,7 +1030,8 @@ template <typename T>
template<typename T>
[[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 };
res[1, 0] = v;
return m * res;
@@ -858,17 +1039,18 @@ template <typename T>
template<typename T>
[[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 };
res[2, 0] = v;
return m * res;
}
template<typename T>
[[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> {
[[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);
@@ -887,8 +1069,9 @@ matrix_ortho3d(T const left, T const right, T const bottom, T const top,
}
template<typename T>
inline auto matrix_perspective(T fovy, T aspect, T znear, T zfar,
bool flip_z_axis = false) -> Mat<4, 4, T> {
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)) };
@@ -912,9 +1095,10 @@ inline auto matrix_perspective(T fovy, T aspect, T znear, T zfar,
}
template<typename T>
[[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> {
[[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);
@@ -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>
[[nodiscard]] inline auto
matrix_infinite_perspective(T const fovy, T const aspect, T const znear,
bool flip_z_axis = false) -> Mat<4, 4, T> {
[[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));
@@ -964,12 +1148,14 @@ matrix_infinite_perspective(T const fovy, T const aspect, T const znear,
template<std::size_t N, typename T>
requires std::formattable<T, char>
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);
}
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();
*out++ = '{';
for (std::size_t i = 0; i < N; ++i) {