Compare commits

...

4 Commits

Author SHA1 Message Date
271f04581c Quick style fixes
Signed-off-by: Slendi <slendi@socopon.com>
2025-11-14 16:29:56 +02:00
26a0a8d046 Remove some methods if Vec doesn't use floats
Signed-off-by: Slendi <slendi@socopon.com>
2025-11-14 16:17:57 +02:00
446ab9c679 Add angle conversion functions
Signed-off-by: Slendi <slendi@socopon.com>
2025-11-14 16:03:17 +02:00
df49368e9a direnv
Signed-off-by: Slendi <slendi@socopon.com>
2025-11-14 15:21:17 +02:00
5 changed files with 156 additions and 25 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
[Bb]uild* [Bb]uild*
.cache .cache
result result
.direnv

View File

@@ -1,8 +1,5 @@
let
desc = "Single-file linear algebra math library for C++23.";
in
{ {
description = desc; description = "Single-file linear algebra math library for C++23.";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@@ -50,17 +47,17 @@ in
prefix = "${placeholder "out"}"; prefix = "${placeholder "out"}";
includedir = "${prefix}/include"; includedir = "${prefix}/include";
}; };
description = desc; description = "Single-file linear algebra math library for C++23.";
}) })
]; ];
dontBuild = true; dontBuild = true;
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
mkdir -p $out/include mkdir -p $out/include
cp include/*.hpp $out/include/ cp include/*.hpp $out/include/
runHook postInstall runHook postInstall
''; '';
meta = with pkgs.lib; { meta = with pkgs.lib; {

View File

@@ -25,8 +25,14 @@
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <format> #include <format>
#include <numbers>
#include <optional>
#include <type_traits> #include <type_traits>
#ifndef SMATH_ANGLE_UNIT
#define SMATH_ANGLE_UNIT rad
#endif // SMATH_ANGLE_UNIT
namespace smath { namespace smath {
template <std::size_t N, typename T> template <std::size_t N, typename T>
@@ -35,6 +41,38 @@ struct Vec;
namespace detail { 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<AngularUnit> 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 <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;
@@ -124,6 +162,14 @@ public:
VEC_ACC(v, 2, 1) VEC_ACC(v, 2, 1)
#undef VEC_ACC #undef VEC_ACC
// 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 // 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;
@@ -179,34 +225,60 @@ public:
VEC_OP_ASSIGN(/) VEC_OP_ASSIGN(/)
#undef VEC_OP_ASSIGN #undef VEC_OP_ASSIGN
constexpr auto magnitude() const noexcept -> T { 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>
{
T total = 0; T total = 0;
for (auto const &v : *this) for (auto const &v : *this)
total += v * v; total += v * v;
return std::sqrt(total); return std::sqrt(total);
} }
constexpr auto length() const noexcept -> T { return this->magnitude(); } constexpr auto length() const noexcept -> T
requires std::is_floating_point_v<T>
{
return this->magnitude();
}
constexpr Vec normalized_safe(T eps = eps_default) const noexcept { template <typename U = T>
requires std::is_floating_point_v<U>
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{};
} }
constexpr Vec normalize_safe(T eps = eps_default) const noexcept { template <typename U = T>
requires std::is_floating_point_v<U>
constexpr auto normalize_safe(U eps = EPS_DEFAULT) const noexcept -> Vec {
return normalized_safe(eps); return normalized_safe(eps);
} }
[[nodiscard]] constexpr auto normalized() noexcept -> Vec<N, T> const { [[nodiscard]] constexpr auto normalized() noexcept -> Vec<N, T>
requires std::is_floating_point_v<T>
{
return (*this) / this->magnitude(); return (*this) / this->magnitude();
} }
[[nodiscard]] constexpr auto normalize() noexcept -> Vec<N, T> const { [[nodiscard]] constexpr auto normalize() noexcept -> Vec<N, T>
requires std::is_floating_point_v<T>
{
return this->normalized(); return this->normalized();
} }
[[nodiscard]] constexpr auto unit() noexcept -> Vec<N, T> const { [[nodiscard]] constexpr auto unit() noexcept -> Vec<N, T>
requires std::is_floating_point_v<T>
{
return this->normalized(); return this->normalized();
} }
[[nodiscard]] constexpr auto dot(Vec<N, T> const &other) const noexcept [[nodiscard]] constexpr auto dot(Vec<N, T> const &other) const noexcept -> T {
-> T const {
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];
@@ -214,10 +286,11 @@ public:
return res; return res;
} }
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>
[[nodiscard]] constexpr auto [[nodiscard]] constexpr auto
approx_equal(Vec const &rhs, U eps = eps_default) const noexcept { approx_equal(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))
@@ -225,7 +298,10 @@ public:
return true; return true;
} }
template <class U = T> constexpr auto magnitude_promoted() const noexcept { template <class U = T>
constexpr auto magnitude_promoted() const noexcept
requires std::is_floating_point_v<T>
{
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>;
F s = 0; F s = 0;
for (auto v : *this) for (auto v : *this)
@@ -235,17 +311,21 @@ public:
template <typename U = T> template <typename U = T>
requires(N == 3) requires(N == 3)
constexpr Vec cross(const Vec &r) const noexcept { 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]};
} }
constexpr T distance(Vec const &r) const noexcept { constexpr auto distance(Vec const &r) const noexcept -> T
requires std::is_floating_point_v<T>
{
return (*this - r).magnitude(); return (*this - r).magnitude();
} }
constexpr Vec project_onto(Vec const &n) const noexcept { constexpr auto project_onto(Vec const &n) const noexcept -> Vec
requires std::is_floating_point_v<T>
{
auto d = this->dot(n); auto d = this->dot(n);
auto nn = n.dot(n); auto nn = n.dot(n);
return (nn ? (d / nn) * n : Vec()); return (nn ? (d / nn) * n : Vec());
@@ -271,7 +351,7 @@ 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 Vec &operator=(Vec<N, U> const &rhs) noexcept { 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;
@@ -401,6 +481,42 @@ 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 {
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<T>(std::numbers::pi / 180.0);
} 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 {
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) {
return value;
} 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 {
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) {
return value * (static_cast<T>(2.0) * static_cast<T>(std::numbers::pi));
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID ==
detail::AngularUnit::Turns) {
return value;
}
}
} // namespace smath } // namespace smath
template <std::size_t N, typename T> template <std::size_t N, typename T>
@@ -435,4 +551,5 @@ struct tuple_element<I, smath::Vec<N, T>> {
static_assert(I < N); static_assert(I < N);
using type = T; using type = T;
}; };
} // namespace std } // namespace std

15
tests/angles.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include <gtest/gtest.h>
#include <smath.hpp>
TEST(AngleReturnRadians, DegInput) {
EXPECT_NEAR(smath::deg(180.0), std::numbers::pi, 1e-12);
}
TEST(AngleReturnRadians, RadInput) {
EXPECT_DOUBLE_EQ(smath::rad(std::numbers::pi), std::numbers::pi);
}
TEST(AngleReturnRadians, TurnsInput) {
EXPECT_NEAR(smath::turns(0.5), std::numbers::pi, 1e-12);
}