1
0
mirror of https://github.com/slendidev/smath.git synced 2026-03-17 02:26:50 +02:00

Compare commits

...

8 Commits

Author SHA1 Message Date
3b9adb0ddc Update README with logo
Add logo
2026-03-11 20:15:23 +02:00
889a42f9b5 Update README
Fix typo, add badges and add quick start section

Signed-off-by: Slendi <slendi@socopon.com>
2026-03-11 20:06:29 +02:00
8dc5ef8f24 Add helper Quaternion methods
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-11 09:35:27 +02:00
0b685eb30b Fix more math
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-10 16:15:49 +02:00
aa8e3b1637 Fixes
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-10 15:33:04 +02:00
830c64b25a Add modules support
Signed-off-by: Slendi <slendi@socopon.com>
2025-12-28 00:24:52 +02:00
a6fe9c2f2c Add const accessors to Quaternion
Signed-off-by: Slendi <slendi@socopon.com>
2025-12-27 21:43:35 +02:00
75d5a197a9 Quick fix for deg/rad/turns functions
Signed-off-by: Slendi <slendi@socopon.com>
2025-12-27 21:36:38 +02:00
5 changed files with 202 additions and 54 deletions

View File

@@ -4,8 +4,14 @@ project(SmathExamples LANGUAGES CXX VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(BUILD_EXAMPLES "Build example programs" ON) option(SMATH_BUILD_EXAMPLES "Build example programs" ON)
option(BUILD_TESTS "Build unit tests" ON) option(SMATH_BUILD_TESTS "Build unit tests" ON)
option(SMATH_BUILD_MODULES "Enable C++20 modules support" OFF)
if(SMATH_BUILD_MODULES)
message(STATUS "Building smath C++ module")
add_subdirectory(src/modules)
endif()
add_library(smath INTERFACE) add_library(smath INTERFACE)
target_include_directories(smath target_include_directories(smath
@@ -55,7 +61,7 @@ install(FILES
DESTINATION lib/cmake/smath DESTINATION lib/cmake/smath
) )
if(BUILD_EXAMPLES) if(SMATH_BUILD_EXAMPLES)
file(GLOB EXAMPLE_SOURCES "${CMAKE_SOURCE_DIR}/examples/*.cpp") file(GLOB EXAMPLE_SOURCES "${CMAKE_SOURCE_DIR}/examples/*.cpp")
foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES}) foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES})
get_filename_component(EXAMPLE_NAME ${EXAMPLE_FILE} NAME_WE) get_filename_component(EXAMPLE_NAME ${EXAMPLE_FILE} NAME_WE)
@@ -64,7 +70,7 @@ if(BUILD_EXAMPLES)
endforeach() endforeach()
endif() endif()
if(BUILD_TESTS) if(SMATH_BUILD_TESTS)
enable_testing() enable_testing()
find_package(GTest QUIET) find_package(GTest QUIET)

View File

@@ -1,6 +1,43 @@
# smath <p align="center">
<img alt="smath logo" src="https://github.com/user-attachments/assets/d6f2ef4b-eca0-4004-9099-37423528bcba" />
<br><br>
<a href="https://github.com/slendidev/smath/actions/workflows/build.yml">
<img src="https://github.com/slendidev/smath/actions/workflows/build.yml/badge.svg" alt="Build">
</a>
<a href="LICENSE.txt">
<img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License: Apache-2.0">
</a>
<a href="https://en.cppreference.com/w/cpp/23">
<img src="https://img.shields.io/badge/C%2B%2B-23-00599C.svg" alt="C++23">
</a>
</p>
Single-file, header-only linear algebra math library for C++23. ## Quick Start
Create `main.cpp`:
```cpp
#include <print>
#include <smath.hpp>
int main() {
using namespace smath;
Vec3 a{1.0f, 2.0f, 3.0f};
Vec3 b{3.0f, 2.0f, 1.0f};
std::println("a + b = {}", a + b);
std::println("dot(a, b) = {}", a.dot(b));
std::println("normalized(a) = {}", a.normalized());
}
```
Build and run:
```bash
g++ -std=c++23 -Iinclude main.cpp -o quickstart
./quickstart
```
## Features ## Features
@@ -12,8 +49,8 @@ Single-file, header-only linear algebra math library for C++23.
- Angle helpers `rad/deg/turns` respecting a configurable base unit via the macro `SMATH_ANGLE_UNIT`. - Angle helpers `rad/deg/turns` respecting a configurable base unit via the macro `SMATH_ANGLE_UNIT`.
- Optional implicit conversions. - Optional implicit conversions.
- Packing utilities for normalized RGBA (`pack_unorm4x8`, `unpack_snorm4x8`, etc.). - Packing utilities for normalized RGBA (`pack_unorm4x8`, `unpack_snorm4x8`, etc.).
- C++20 modules support.
## License ## License
This library is licensed under the Apache License 2.0. See the (LICENSE.txt)[LICENSE.txt] file for more details. This library is licensed under the Apache License 2.0. See the [LICENSE.txt](LICENSE.txt) file for more details.

View File

@@ -188,9 +188,12 @@ public:
// NOTE: This can (probably) be improved with C++26 reflection in the // NOTE: This can (probably) be improved with C++26 reflection in the
// future. // 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]; \
} constexpr auto component() const->T const & \ } \
constexpr auto component() const -> T const & \
requires(N >= req) \ requires(N >= req) \
{ \ { \
return (*this)[idx]; \ return (*this)[idx]; \
@@ -281,13 +284,13 @@ 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; \
@@ -421,8 +424,9 @@ public:
} }
template<class U> template<class U>
requires(std::is_arithmetic_v<U> && N >= 1) constexpr explicit( requires(std::is_arithmetic_v<U> && N >= 1)
!std::is_convertible_v<T, U>) operator Vec<N, U>() const noexcept constexpr explicit(!std::is_convertible_v<T, U>)
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)
@@ -446,9 +450,8 @@ private:
} }
#ifdef SMATH_IMPLICIT_CONVERSIONS #ifdef SMATH_IMPLICIT_CONVERSIONS
template<class U> template<class U>
requires std::is_arithmetic_v<U> requires std::is_arithmetic_v<U> && (!std::is_same_v<U, T>)constexpr void
&& (!std::is_same_v<U, T>)constexpr void fill_one( fill_one(std::size_t &i, const U &v) noexcept
std::size_t &i, const U &v) noexcept
{ {
(*this)[i++] = static_cast<T>(v); (*this)[i++] = static_cast<T>(v);
} }
@@ -577,44 +580,54 @@ 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>
using angle_ret_t = std::conditional_t<std::is_same_v<T, float>, float, double>;
template<class T> constexpr auto deg(T value)
{ {
using R = angle_ret_t<T>;
if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) {
return value; return static_cast<R>(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 static_cast<R>(value) * static_cast<R>(std::numbers::pi / 180.0);
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID } else {
== detail::AngularUnit::Turns) { return static_cast<R>(value) / static_cast<R>(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 value)
{ {
using R = angle_ret_t<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 static_cast<R>(value) * static_cast<R>(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 static_cast<R>(value);
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID } else {
== detail::AngularUnit::Turns) { return static_cast<R>(value)
return value / (static_cast<T>(2.0) * static_cast<T>(std::numbers::pi)); / (static_cast<R>(2.0) * static_cast<R>(std::numbers::pi));
} }
} }
template<class T> constexpr auto turns(T const value) -> T template<class T> constexpr auto turns(T value)
{ {
using R = angle_ret_t<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 static_cast<R>(value) * static_cast<R>(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 static_cast<R>(value)
} else if constexpr (detail::SMATH_ANGLE_UNIT_ID * (static_cast<R>(2.0) * static_cast<R>(std::numbers::pi));
== detail::AngularUnit::Turns) { } else {
return value; return static_cast<R>(value);
} }
} }
template<std::size_t R, std::size_t C, typename T>
requires std::is_arithmetic_v<T> struct Mat;
template<class T> struct Quaternion : Vec<4, T> { template<class T> struct Quaternion : Vec<4, T> {
using Base = Vec<4, T>; using Base = Vec<4, T>;
@@ -625,9 +638,13 @@ template<class T> struct Quaternion : Vec<4, T> {
constexpr Base const &vec() const noexcept { return *this; } constexpr Base const &vec() const noexcept { return *this; }
constexpr T &x() noexcept { return Base::x(); } constexpr T &x() noexcept { return Base::x(); }
constexpr T const &x() const noexcept { return Base::x(); }
constexpr T &y() noexcept { return Base::y(); } constexpr T &y() noexcept { return Base::y(); }
constexpr T const &y() const noexcept { return Base::y(); }
constexpr T &z() noexcept { return Base::z(); } constexpr T &z() noexcept { return Base::z(); }
constexpr T const &z() const noexcept { return Base::z(); }
constexpr T &w() noexcept { return Base::w(); } constexpr T &w() noexcept { return Base::w(); }
constexpr T const &w() const noexcept { return Base::w(); }
constexpr auto operator*(Quaternion const &rhs) const noexcept -> Quaternion constexpr auto operator*(Quaternion const &rhs) const noexcept -> Quaternion
{ {
@@ -645,6 +662,42 @@ template<class T> struct Quaternion : Vec<4, T> {
return r; return r;
} }
[[nodiscard]] constexpr auto as_matrix() const noexcept -> Mat<4, 4, T>
{
auto const xx = x() * x();
auto const yy = y() * y();
auto const zz = z() * z();
auto const xy = x() * y();
auto const xz = x() * z();
auto const yz = y() * z();
auto const wx = w() * x();
auto const wy = w() * y();
auto const wz = w() * z();
return Mat<4, 4, T> {
Vec<4, T> { 1 - 2 * (yy + zz), 2 * (xy + wz), 2 * (xz - wy), 0 },
Vec<4, T> { 2 * (xy - wz), 1 - 2 * (xx + zz), 2 * (yz + wx), 0 },
Vec<4, T> { 2 * (xz + wy), 2 * (yz - wx), 1 - 2 * (xx + yy), 0 },
Vec<4, T> { 0, 0, 0, 1 },
};
}
[[nodiscard]] static constexpr auto from_axis_angle(
Vec<3, T> const &axis, T const angle) noexcept -> Quaternion
{
auto const normalized_axis { axis.normalized_safe() };
auto const half_angle { angle / static_cast<T>(2) };
auto const sine { std::sin(half_angle) };
auto const cosine { std::cos(half_angle) };
return Quaternion {
normalized_axis.x() * sine,
normalized_axis.y() * sine,
normalized_axis.z() * sine,
cosine,
};
}
}; };
template<class T> template<class T>
@@ -1096,28 +1149,18 @@ inline auto matrix_perspective(
template<typename T> template<typename T>
[[nodiscard]] inline auto matrix_look_at(Vec<3, T> const eye, [[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) Vec<3, T> const center, Vec<3, T> const up) -> Mat<4, 4, T>
-> Mat<4, 4, T>
{ {
auto f = (center - eye).normalized(); auto f = (center - eye).normalized_safe();
auto s = f.cross(up).normalized(); auto s = f.cross(up).normalized_safe();
auto u = s.cross(f); auto u = s.cross(f);
if (!flip_z_axis) { return Mat<4, 4, T> {
return Mat<4, 4, T> { Vec<4, T> { s.x(), u.x(), -f.x(), 0 },
Vec<4, T> { s.x(), s.y(), s.z(), 0 }, Vec<4, T> { s.y(), u.y(), -f.y(), 0 },
Vec<4, T> { u.x(), u.y(), u.z(), 0 }, Vec<4, T> { s.z(), u.z(), -f.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 },
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<typename T> template<typename T>

View File

@@ -0,0 +1,8 @@
add_library(smath_modules)
target_sources(smath_modules PUBLIC FILE_SET CXX_MODULES FILES smath.cppm)
target_link_libraries(smath_modules PUBLIC smath::smath)
target_compile_features(smath_modules PUBLIC cxx_std_20)

54
src/modules/smath.cppm Normal file
View File

@@ -0,0 +1,54 @@
module;
#include "smath.hpp"
export module smath;
export namespace smath {
export using ::smath::Vec;
export using ::smath::VecOrScalar;
export using ::smath::Quaternion;
export using ::smath::Mat;
export using ::smath::Vec2;
export using ::smath::Vec3;
export using ::smath::Vec4;
export using ::smath::Vec2d;
export using ::smath::Vec3d;
export using ::smath::Vec4d;
export using ::smath::Mat2;
export using ::smath::Mat3;
export using ::smath::Mat4;
export using ::smath::Mat2d;
export using ::smath::Mat3d;
export using ::smath::Mat4d;
export using ::smath::swizzle;
export using ::smath::deg;
export using ::smath::rad;
export using ::smath::turns;
export using ::smath::pack_unorm4x8;
export using ::smath::pack_snorm4x8;
export using ::smath::unpack_unorm4x8;
export using ::smath::unpack_snorm4x8;
export using ::smath::operator*;
export using ::smath::translate;
export using ::smath::rotate;
export using ::smath::scale;
export using ::smath::shear_x;
export using ::smath::shear_y;
export using ::smath::shear_z;
export using ::smath::matrix_ortho3d;
export using ::smath::matrix_perspective;
export using ::smath::matrix_infinite_perspective;
export using ::smath::matrix_look_at;
}