mirror of
https://github.com/slendidev/smath.git
synced 2025-12-10 12:59:51 +02:00
Compare commits
4 Commits
9f70b6c72b
...
271f04581c
| Author | SHA1 | Date | |
|---|---|---|---|
| 271f04581c | |||
| 26a0a8d046 | |||
| 446ab9c679 | |||
| df49368e9a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
[Bb]uild*
|
||||
.cache
|
||||
result
|
||||
.direnv
|
||||
|
||||
15
flake.nix
15
flake.nix
@@ -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 = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
@@ -50,17 +47,17 @@ in
|
||||
prefix = "${placeholder "out"}";
|
||||
includedir = "${prefix}/include";
|
||||
};
|
||||
description = desc;
|
||||
description = "Single-file linear algebra math library for C++23.";
|
||||
})
|
||||
];
|
||||
|
||||
dontBuild = true;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/include
|
||||
cp include/*.hpp $out/include/
|
||||
runHook postInstall
|
||||
runHook preInstall
|
||||
mkdir -p $out/include
|
||||
cp include/*.hpp $out/include/
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
|
||||
@@ -25,8 +25,14 @@
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
#include <numbers>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef SMATH_ANGLE_UNIT
|
||||
#define SMATH_ANGLE_UNIT rad
|
||||
#endif // SMATH_ANGLE_UNIT
|
||||
|
||||
namespace smath {
|
||||
|
||||
template <std::size_t N, typename T>
|
||||
@@ -35,6 +41,38 @@ 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<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 {
|
||||
char data[N]{};
|
||||
static constexpr std::size_t size = N - 1;
|
||||
@@ -124,6 +162,14 @@ public:
|
||||
VEC_ACC(v, 2, 1)
|
||||
#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
|
||||
friend constexpr auto operator+(T s, Vec const &v) noexcept -> Vec {
|
||||
return v + s;
|
||||
@@ -179,34 +225,60 @@ public:
|
||||
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;
|
||||
for (auto const &v : *this)
|
||||
total += v * v;
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
[[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();
|
||||
}
|
||||
[[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();
|
||||
}
|
||||
[[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();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto dot(Vec<N, T> const &other) const noexcept
|
||||
-> T const {
|
||||
[[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];
|
||||
@@ -214,10 +286,11 @@ public:
|
||||
return res;
|
||||
}
|
||||
|
||||
static constexpr T eps_default = T(1e-6);
|
||||
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 {
|
||||
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))
|
||||
@@ -225,7 +298,10 @@ public:
|
||||
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>;
|
||||
F s = 0;
|
||||
for (auto v : *this)
|
||||
@@ -235,17 +311,21 @@ public:
|
||||
|
||||
template <typename U = T>
|
||||
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],
|
||||
(*this)[2] * r[0] - (*this)[0] * r[2],
|
||||
(*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();
|
||||
}
|
||||
|
||||
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 nn = n.dot(n);
|
||||
return (nn ? (d / nn) * n : Vec());
|
||||
@@ -271,7 +351,7 @@ public:
|
||||
|
||||
template <class U>
|
||||
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)
|
||||
(*this)[i] = static_cast<T>(rhs[i]);
|
||||
return *this;
|
||||
@@ -401,6 +481,42 @@ 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 {
|
||||
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
|
||||
|
||||
template <std::size_t N, typename T>
|
||||
@@ -435,4 +551,5 @@ struct tuple_element<I, smath::Vec<N, T>> {
|
||||
static_assert(I < N);
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
15
tests/angles.cpp
Normal file
15
tests/angles.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user