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

Compare commits

...

13 Commits

Author SHA1 Message Date
79dc2d43e6 ci: Try another approach to docs workflow
I hate CI.

Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 02:44:52 +02:00
cc08dd190c ci: add docs dir creation
Please work

Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 02:40:33 +02:00
87a12de2bf ci: add doxygen workflow
Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 02:38:43 +02:00
6079705bb6 Add Doxygen documentation
Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 02:35:04 +02:00
48eff7d784 Document SMATH_ANGLE_UNIT in smath.hpp's top comment
Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 01:42:20 +02:00
224b924772 Add optional interop headers
Those headers are used for interoping with various popular C++
libraries. For now, those libraries are Eigen, GLM, HandmadeMath,
Raylib, and SFML. Some other ones may be added in the future.

Those are disabled by default, as most projects I would imagine would
not use them. But in case you need any of them for special reasons like
I did, they are there.

Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 01:34:35 +02:00
2a161e36ab Move smath.hpp to smath/smath.hpp
This is done in preparation for optional interop headers for different
libraries.

Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 00:40:20 +02:00
6f9e8814b1 ci: print build logs
Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 00:17:19 +02:00
4f71b3ac90 Add .clang-format and format codebase
Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 00:13:58 +02:00
01538457a5 Add interop API
Signed-off-by: Slendi <slendi@socopon.com>
2026-03-12 00:13:58 +02:00
c4109f0ce3 bump cmake project version
Signed-off-by: Slendi <slendi@socopon.com>
2026-03-11 20:24:45 +02:00
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
22 changed files with 4564 additions and 418 deletions

51
.clang-format Normal file
View File

@@ -0,0 +1,51 @@
---
Language: Cpp
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: ForIndentation
ColumnLimit: 80
AlignAfterOpenBracket: DontAlign
AlignOperands: false
BreakBeforeBinaryOperators: All
ContinuationIndentWidth: 4
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: Never
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterUnion: true
BeforeCatch: false
BeforeElse: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortBlocksOnASingleLine: Empty
SpaceInEmptyBlock: true
BinPackArguments: false
BinPackParameters: false
Cpp11BracedListStyle: false
SpaceBeforeCpp11BracedList: true
IndentRequiresClause: false
RequiresClausePosition: OwnLine
PointerAlignment: Right
ReferenceAlignment: Right
IndentAccessModifiers: false
AccessModifierOffset: -4
SortIncludes: CaseSensitive
SpaceAfterTemplateKeyword: false
AlignEscapedNewlines: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
...

View File

@@ -21,7 +21,7 @@ jobs:
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@main
- name: Building default package - name: Building default package
run: nix build .#default run: nix build .#default --print-build-logs
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:

50
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Deploy Doxygen docs
on:
push:
branches: [master]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Doxygen
run: sudo apt-get update && sudo apt-get install -y doxygen graphviz
- name: Create output directory
run: mkdir -p build/docs
- name: Generate docs
run: doxygen docs/Doxyfile
- name: Disable Jekyll
run: touch build/docs/html/.nojekyll
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: build/docs/html
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.15)
project(SmathExamples LANGUAGES CXX VERSION 0.1.0) project(SmathExamples LANGUAGES CXX VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -7,6 +7,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(SMATH_BUILD_EXAMPLES "Build example programs" ON) option(SMATH_BUILD_EXAMPLES "Build example programs" ON)
option(SMATH_BUILD_TESTS "Build unit tests" ON) option(SMATH_BUILD_TESTS "Build unit tests" ON)
option(SMATH_BUILD_MODULES "Enable C++20 modules support" OFF) option(SMATH_BUILD_MODULES "Enable C++20 modules support" OFF)
option(SMATH_ENABLE_LEGACY_HEADER "Install legacy include <smath.hpp> shim" OFF)
option(SMATH_INSTALL_INTEROP_HEADERS "Install interop headers" OFF)
if(SMATH_BUILD_MODULES) if(SMATH_BUILD_MODULES)
message(STATUS "Building smath C++ module") message(STATUS "Building smath C++ module")
@@ -17,6 +19,7 @@ add_library(smath INTERFACE)
target_include_directories(smath target_include_directories(smath
INTERFACE INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<$<BOOL:${SMATH_ENABLE_LEGACY_HEADER}>:$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
) )
add_library(smath::smath ALIAS smath) add_library(smath::smath ALIAS smath)
@@ -25,7 +28,21 @@ include(CMakePackageConfigHelpers)
install(TARGETS smath install(TARGETS smath
EXPORT smathTargets EXPORT smathTargets
) )
install(DIRECTORY include/ DESTINATION include) install(FILES include/smath/smath.hpp DESTINATION include/smath)
if(SMATH_INSTALL_INTEROP_HEADERS)
install(DIRECTORY include/smath/interop DESTINATION include/smath)
endif()
if(SMATH_ENABLE_LEGACY_HEADER)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include")
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/smath_legacy_header.hpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/include/smath.hpp"
@ONLY
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/smath.hpp" DESTINATION include)
endif()
install(EXPORT smathTargets install(EXPORT smathTargets
NAMESPACE smath:: NAMESPACE smath::
@@ -96,4 +113,3 @@ if(SMATH_BUILD_TESTS)
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(smath_tests) gtest_discover_tests(smath_tests)
endif() endif()

View File

@@ -1,6 +1,16 @@
# smath <p align="center">
<img alt="smath logo" width="100%" src="https://github.com/user-attachments/assets/d6f2ef4b-eca0-4004-9099-37423528bcba" />
Single-file, header-only linear algebra math library for C++23. <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>
## Features ## Features
@@ -12,8 +22,51 @@ 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.
- Built-in interop adapter and optional interop headers for various libraries.
## Quick Start
Create `main.cpp`:
```cpp
#include <print>
#include <smath/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
```
## Interop Headers
smath ships optional interop adapters under `include/smath/interop/`.
When installing with CMake, interop headers are gated behind:
- `-DSMATH_INSTALL_INTEROP_HEADERS=ON`
All of them plug into the same generic API defined in `smath.hpp`:
```cpp
auto v = smath::from_external(external_value);
auto ext = smath::to_external<ExternalType>(smath_value);
```
## 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

@@ -0,0 +1,3 @@
#pragma once
#include <smath/smath.hpp>

27
docs/Doxyfile Normal file
View File

@@ -0,0 +1,27 @@
PROJECT_NAME = "smath"
OUTPUT_DIRECTORY = build/docs
CREATE_SUBDIRS = NO
INPUT = include README.md
USE_MDFILE_AS_MAINPAGE = README.md
TOC_INCLUDE_HEADINGS = 0
RECURSIVE = YES
FILE_PATTERNS = *.hpp *.h *.md
EXCLUDE = include/smath/interop
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = NO
GENERATE_HTML = YES
GENERATE_LATEX = NO
GENERATE_MAN = YES
GENERATE_TREEVIEW = YES
DISABLE_INDEX = NO
FULL_SIDEBAR = NO
HTML_COLORSTYLE = LIGHT
HTML_EXTRA_STYLESHEET = docs/vendor/doxygen-awesome.css
WARN_IF_UNDOCUMENTED = YES
QUIET = NO
EXCLUDE_SYMBOLS = std smath::detail smath::interop_adapter interop_adapter smath::from_external from_external smath::to_external to_external

3020
docs/vendor/doxygen-awesome.css vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,205 +10,219 @@
#include <Windows.h> #include <Windows.h>
#endif #endif
#include <smath.hpp> #include <smath/smath.hpp>
using smath::Vec2; using smath::Vec2;
enum Color : uint8_t { enum Color : uint8_t
CLR_NONE = 0, // default {
CLR_AXES = 90, // bright black (gray) CLR_NONE = 0, // default
CLR_A = 32, // green CLR_AXES = 90, // bright black (gray)
CLR_B = 33, // yellow CLR_A = 32, // green
CLR_P = 35, // magenta CLR_B = 33, // yellow
CLR_DOT = 36 // cyan CLR_P = 35, // magenta
CLR_DOT = 36 // cyan
}; };
struct Cell { struct Cell
char ch{' '}; {
uint8_t color{CLR_NONE}; char ch { ' ' };
int prio{0}; uint8_t color { CLR_NONE };
int prio { 0 };
}; };
struct Canvas { struct Canvas
int w, h; {
std::vector<Cell> pix; int w, h;
Canvas(int W, int H) : w(W), h(H), pix(W * H) {} std::vector<Cell> pix;
Canvas(int W, int H) : w(W), h(H), pix(W * H) { }
void put(int x, int y, char c, int prio, uint8_t color) { void put(int x, int y, char c, int prio, uint8_t color)
if (x < 0 || x >= w || y < 0 || y >= h) {
return; if (x < 0 || x >= w || y < 0 || y >= h)
Cell &cell = pix[y * w + x]; return;
if (prio >= cell.prio) { Cell &cell = pix[y * w + x];
cell.ch = c; if (prio >= cell.prio) {
cell.prio = prio; cell.ch = c;
cell.color = color; cell.prio = prio;
} cell.color = color;
} }
void hline(int y, char c, int prio, uint8_t color) { }
for (int x = 0; x < w; ++x) void hline(int y, char c, int prio, uint8_t color)
put(x, y, c, prio, color); {
} for (int x = 0; x < w; ++x)
void vline(int x, char c, int prio, uint8_t color) { put(x, y, c, prio, color);
for (int y = 0; y < h; ++y) }
put(x, y, c, prio, color); void vline(int x, char c, int prio, uint8_t color)
} {
for (int y = 0; y < h; ++y)
put(x, y, c, prio, color);
}
void line_dir(int x0, int y0, int x1, int y1, int prio, uint8_t color) { void line_dir(int x0, int y0, int x1, int y1, int prio, uint8_t color)
int dx = std::abs(x1 - x0), sx = x0 < x1 ? 1 : -1; {
int dy = -std::abs(y1 - y0), sy = y0 < y1 ? 1 : -1; int dx = std::abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int err = dx + dy; int dy = -std::abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
int px = x0, py = y0; int px = x0, py = y0;
put(px, py, '+', prio, color); put(px, py, '+', prio, color);
while (true) { while (true) {
if (px == x1 && py == y1) if (px == x1 && py == y1)
break; break;
int e2 = 2 * err; int e2 = 2 * err;
int nx = px, ny = py; int nx = px, ny = py;
if (e2 >= dy) { if (e2 >= dy) {
err += dy; err += dy;
nx += sx; nx += sx;
} }
if (e2 <= dx) { if (e2 <= dx) {
err += dx; err += dx;
ny += sy; ny += sy;
} }
int sdx = nx - px; int sdx = nx - px;
int sdy = ny - py; int sdy = ny - py;
int adx = std::abs(sdx); int adx = std::abs(sdx);
int ady = std::abs(sdy); int ady = std::abs(sdy);
char g; char g;
if (adx >= 2 * ady) if (adx >= 2 * ady)
g = '-'; g = '-';
else if (ady >= 2 * adx) else if (ady >= 2 * adx)
g = '|'; g = '|';
else else
g = ((sdx > 0 && sdy > 0) || (sdx < 0 && sdy < 0)) ? '\\' : '/'; g = ((sdx > 0 && sdy > 0) || (sdx < 0 && sdy < 0)) ? '\\' : '/';
put(nx, ny, g, prio, color); put(nx, ny, g, prio, color);
px = nx; px = nx;
py = ny; py = ny;
} }
} }
void flush() const { void flush() const
uint8_t cur = 255; {
for (int y = 0; y < h; ++y) { uint8_t cur = 255;
for (int x = 0; x < w; ++x) { for (int y = 0; y < h; ++y) {
const Cell &c = pix[y * w + x]; for (int x = 0; x < w; ++x) {
if (c.color != cur) { const Cell &c = pix[y * w + x];
if (c.color == CLR_NONE) if (c.color != cur) {
std::print("\x1b[0m"); if (c.color == CLR_NONE)
else std::print("\x1b[0m");
std::print("\x1b[{}m", c.color); else
cur = c.color; std::print("\x1b[{}m", c.color);
} cur = c.color;
std::print("{}", c.ch); }
} std::print("{}", c.ch);
std::println(""); }
} std::println("");
std::print("\x1b[0m"); }
} std::print("\x1b[0m");
}
}; };
struct Mapper { struct Mapper
int W, H; {
double scale; int W, H;
int cx, cy; double scale;
Mapper(int w, int h, double s) : W(w), H(h), scale(s), cx(w / 2), cy(h / 2) {} int cx, cy;
std::pair<int, int> map(Vec2 v) const { Mapper(int w, int h, double s) : W(w), H(h), scale(s), cx(w / 2), cy(h / 2)
int x = cx + (int)std::llround(v.x() * scale); { }
int y = cy - (int)std::llround(v.y() * scale); std::pair<int, int> map(Vec2 v) const
return {x, y}; {
} int x = cx + (int)std::llround(v.x() * scale);
int y = cy - (int)std::llround(v.y() * scale);
return { x, y };
}
}; };
int main() { int main()
{
#if defined(_WIN32) #if defined(_WIN32)
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
if (h != INVALID_HANDLE_VALUE) { if (h != INVALID_HANDLE_VALUE) {
DWORD m; DWORD m;
if (GetConsoleMode(h, &m)) if (GetConsoleMode(h, &m))
SetConsoleMode(h, m | ENABLE_VIRTUAL_TERMINAL_PROCESSING); SetConsoleMode(h, m | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
} }
#endif #endif
std::print("Enter vector a (ax ay): "); std::print("Enter vector a (ax ay): ");
double ax, ay; double ax, ay;
if (!(std::cin >> ax >> ay)) if (!(std::cin >> ax >> ay))
return 0; return 0;
std::print("Enter vector b (bx by): "); std::print("Enter vector b (bx by): ");
double bx, by; double bx, by;
if (!(std::cin >> bx >> by)) if (!(std::cin >> bx >> by))
return 0; return 0;
Vec2 a{(float)ax, (float)ay}; Vec2 a { (float)ax, (float)ay };
Vec2 b{(float)bx, (float)by}; Vec2 b { (float)bx, (float)by };
Vec2 p = a.project_onto(b); Vec2 p = a.project_onto(b);
std::println("\nResults:"); std::println("\nResults:");
std::println("a = {}", a); std::println("a = {}", a);
std::println("b = {}", b); std::println("b = {}", b);
std::println("proj_b(a) = p = {}", p); std::println("proj_b(a) = p = {}", p);
std::println("|a|={} |b|={} |p|={}", a.magnitude(), b.magnitude(), std::println(
p.magnitude()); "|a|={} |b|={} |p|={}", a.magnitude(), b.magnitude(), p.magnitude());
double dot = a.dot(b); double dot = a.dot(b);
double ang = (a.magnitude() > 0 && b.magnitude() > 0) double ang = (a.magnitude() > 0 && b.magnitude() > 0)
? std::acos(std::clamp(dot / (a.magnitude() * b.magnitude()), ? std::acos(
-1.0, 1.0)) * std::clamp(dot / (a.magnitude() * b.magnitude()), -1.0, 1.0))
180.0 / M_PI * 180.0 / M_PI
: 0.0; : 0.0;
std::println("a·b={} angle(a,b)={} deg\n", dot, ang); std::println("a·b={} angle(a,b)={} deg\n", dot, ang);
const int W = 73, H = 33; const int W = 73, H = 33;
double maxr = std::max({(double)a.magnitude(), (double)b.magnitude(), double maxr = std::max({ (double)a.magnitude(),
(double)p.magnitude(), 1.0}); (double)b.magnitude(),
double usable = 0.45 * std::min(W, H); (double)p.magnitude(),
double scale = usable / maxr; 1.0 });
double usable = 0.45 * std::min(W, H);
double scale = usable / maxr;
Canvas cvs(W, H); Canvas cvs(W, H);
Mapper mp(W, H, scale); Mapper mp(W, H, scale);
int pr_axes = 1; int pr_axes = 1;
auto [cx, cy] = mp.map({0, 0}); auto [cx, cy] = mp.map({ 0, 0 });
cvs.hline(H / 2, '-', pr_axes, CLR_AXES); cvs.hline(H / 2, '-', pr_axes, CLR_AXES);
cvs.vline(W / 2, '|', pr_axes, CLR_AXES); cvs.vline(W / 2, '|', pr_axes, CLR_AXES);
cvs.put(W / 2, H / 2, 'O', pr_axes + 1, CLR_AXES); cvs.put(W / 2, H / 2, 'O', pr_axes + 1, CLR_AXES);
auto [ox, oy] = mp.map({0, 0}); auto [ox, oy] = mp.map({ 0, 0 });
auto [ax1, ay1] = mp.map(a); auto [ax1, ay1] = mp.map(a);
auto [bx1, by1] = mp.map(b); auto [bx1, by1] = mp.map(b);
auto [px1, py1] = mp.map(p); auto [px1, py1] = mp.map(p);
int pr_a = 3, pr_b = 3, pr_p = 4; int pr_a = 3, pr_b = 3, pr_p = 4;
cvs.line_dir(ox, oy, ax1, ay1, pr_a, CLR_A); cvs.line_dir(ox, oy, ax1, ay1, pr_a, CLR_A);
cvs.line_dir(ox, oy, bx1, by1, pr_b, CLR_B); cvs.line_dir(ox, oy, bx1, by1, pr_b, CLR_B);
cvs.line_dir(ox, oy, px1, py1, pr_p, CLR_P); cvs.line_dir(ox, oy, px1, py1, pr_p, CLR_P);
if (!a.approx_equal(p) && b.magnitude() > 0) { if (!a.approx_equal(p) && b.magnitude() > 0) {
int steps = std::max(std::abs(ax1 - px1), std::abs(ay1 - py1)); int steps = std::max(std::abs(ax1 - px1), std::abs(ay1 - py1));
for (int i = 0; i <= steps; ++i) { for (int i = 0; i <= steps; ++i) {
double t = steps ? double(i) / steps : 0.0; double t = steps ? double(i) / steps : 0.0;
int x = (int)std::llround(px1 + t * (ax1 - px1)); int x = (int)std::llround(px1 + t * (ax1 - px1));
int y = (int)std::llround(py1 + t * (ay1 - py1)); int y = (int)std::llround(py1 + t * (ay1 - py1));
if (i % 2 == 0) if (i % 2 == 0)
cvs.put(x, y, '.', 2, CLR_DOT); cvs.put(x, y, '.', 2, CLR_DOT);
} }
} }
auto place_label = [&](Vec2 v, const char *txt, uint8_t c, int pr) { auto place_label = [&](Vec2 v, const char *txt, uint8_t c, int pr) {
auto tip = mp.map(v * 1.05f); auto tip = mp.map(v * 1.05f);
for (int i = 0; txt[i]; ++i) for (int i = 0; txt[i]; ++i)
cvs.put(tip.first + i, tip.second, txt[i], pr, c); cvs.put(tip.first + i, tip.second, txt[i], pr, c);
}; };
place_label(a, "A", CLR_A, pr_a + 1); place_label(a, "A", CLR_A, pr_a + 1);
place_label(b, "B", CLR_B, pr_b + 1); place_label(b, "B", CLR_B, pr_b + 1);
place_label(p, "P", CLR_P, pr_p + 1); place_label(p, "P", CLR_P, pr_p + 1);
std::println("Legend: axes(gray), a(green), b(yellow), p=proj(magenta), " std::println("Legend: axes(gray), a(green), b(yellow), p=proj(magenta), "
"dotted=cross-perp\n"); "dotted=cross-perp\n");
cvs.flush(); cvs.flush();
return 0; return 0;
} }

View File

@@ -22,22 +22,23 @@
#include <print> #include <print>
#include <random> #include <random>
#include <smath.hpp> #include <smath/smath.hpp>
auto main() -> int { auto main() -> int
using namespace smath; {
using namespace smath;
Vec2d point; Vec2d point;
std::random_device rd; std::random_device rd;
std::mt19937 rng{rd()}; std::mt19937 rng { rd() };
std::uniform_real_distribution<> dis(-5, 5); std::uniform_real_distribution<> dis(-5, 5);
int i = 0; int i = 0;
do { do {
Vec2d add{dis(rng), dis(rng)}; Vec2d add { dis(rng), dis(rng) };
auto const n = point + add; auto const n = point + add;
std::println("{}: {:.2f} + {:.2f} -> {:.2f}", i, point, add, n); std::println("{}: {:.2f} + {:.2f} -> {:.2f}", i, point, add, n);
point = n; point = n;
i++; i++;
} while (i < 15); } while (i < 15);
} }

View File

@@ -17,53 +17,54 @@
// #define SMATH_IMPLICIT_CONVERSIONS // #define SMATH_IMPLICIT_CONVERSIONS
#include <smath.hpp> #include <smath/smath.hpp>
int main() { int main()
using namespace smath; {
Vec3 v{1, 2, 3}; using namespace smath;
std::println("v: {}", v); Vec3 v { 1, 2, 3 };
auto v2 = swizzle<"zyx">(v); std::println("v: {}", v);
std::println("v2: {}", v2); auto v2 = swizzle<"zyx">(v);
std::println("+: {}", v + v2); std::println("v2: {}", v2);
std::println("-: {}", v - v2); std::println("+: {}", v + v2);
std::println("*: {}", v * v2); std::println("-: {}", v - v2);
std::println("/: {}", v / v2); std::println("*: {}", v * v2);
std::println("dot: {}", v.dot(v2)); std::println("/: {}", v / v2);
std::println("rrggbb: {}", swizzle<"rrggbb">(v)); std::println("dot: {}", v.dot(v2));
std::println("Magnitude: {}", v.magnitude()); std::println("rrggbb: {}", swizzle<"rrggbb">(v));
std::println("Normalized: {}", v.normalized()); std::println("Magnitude: {}", v.magnitude());
std::println("(alias) Unit: {}", v.unit()); std::println("Normalized: {}", v.normalized());
std::println("(alias) Normalize: {}", v.normalize()); std::println("(alias) Unit: {}", v.unit());
std::println("(alias) Length: {}", v.length()); std::println("(alias) Normalize: {}", v.normalize());
std::println("std::get<1>(v): {}", std::get<1>(v)); std::println("(alias) Length: {}", v.length());
auto [x, y, z] = v; std::println("std::get<1>(v): {}", std::get<1>(v));
std::println("Bindings: [{}, {}, {}]", x, y, z); auto [x, y, z] = v;
float x1{}, y1{}, z1{}; std::println("Bindings: [{}, {}, {}]", x, y, z);
v.unpack(x1, y1, z1); float x1 {}, y1 {}, z1 {};
std::println("Unpacked: {}, {}, {}", x1, y1, z1); v.unpack(x1, y1, z1);
std::println("Unpacked: {}, {}, {}", x1, y1, z1);
// Let's mix and match! // Let's mix and match!
Vec<6> v3(v, 7, swizzle<"zy">(v2)); Vec<6> v3(v, 7, swizzle<"zy">(v2));
std::println("{{v, 7, XZ(v2)}}: {}", v3); std::println("{{v, 7, XZ(v2)}}: {}", v3);
// Scalar operations // Scalar operations
std::println("v + 3: {}", v + 3); std::println("v + 3: {}", v + 3);
std::println("v - 3: {}", v - 3); std::println("v - 3: {}", v - 3);
std::println("v * 3: {}", v * 3); std::println("v * 3: {}", v * 3);
std::println("v / 3: {}", v / 3); std::println("v / 3: {}", v / 3);
std::println("3 + v: {}", 3 + v); std::println("3 + v: {}", 3 + v);
std::println("3 - v: {}", 3 - v); std::println("3 - v: {}", 3 - v);
std::println("3 * v: {}", 3 * v); std::println("3 * v: {}", 3 * v);
std::println("3 / v: {}", 3 / v); std::println("3 / v: {}", 3 / v);
// Casting // Casting
auto v4 = static_cast<Vec3d>(v); auto v4 = static_cast<Vec3d>(v);
#ifdef SMATH_IMPLICIT_CONVERSIONS #ifdef SMATH_IMPLICIT_CONVERSIONS
Vec3d v5 = v; Vec3d v5 = v;
#else #else
Vec3d v5 = static_cast<Vec3d>(v); Vec3d v5 = static_cast<Vec3d>(v);
#endif // SMATH_IMPLICIT_CONVERSIONS #endif // SMATH_IMPLICIT_CONVERSIONS
std::println("Are v4 and v5 same? {}", v4 == v5); std::println("Are v4 and v5 same? {}", v4 == v5);
} }

View File

@@ -27,6 +27,7 @@
clang-tools clang-tools
lldb lldb
pkg-config pkg-config
doxygen
]; ];
}; };
@@ -55,10 +56,14 @@
}) })
]; ];
extraCMakeFlags = [
"-DSMATH_INSTALL_INTEROP_HEADERS=ON"
];
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
mkdir -p $out/include mkdir -p $out/include/
cp ../include/smath.hpp $out/include/ cp -r ../include/smath/ $out/include/
runHook postInstall runHook postInstall
''; '';

View File

@@ -0,0 +1,131 @@
/*
* smath - Single-file linear algebra math library for C++23.
*
* Copyright 2025 Slendi <slendi@socopon.com>
*
* 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.
*/
#pragma once
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <smath/smath.hpp>
namespace smath
{
template<class Scalar, int N, int Options, int MaxRows, int MaxCols>
requires(std::is_arithmetic_v<Scalar> && (N > 0))
struct interop_adapter<Eigen::Matrix<Scalar, N, 1, Options, MaxRows, MaxCols>>
{
using smath_type = Vec<static_cast<std::size_t>(N), Scalar>;
static constexpr auto to_smath(
Eigen::Matrix<Scalar, N, 1, Options, MaxRows, MaxCols> const &v)
-> smath_type
{
smath_type out {};
for (std::size_t i = 0; i < static_cast<std::size_t>(N); ++i)
out[i] = v(static_cast<int>(i));
return out;
}
static constexpr auto from_smath(smath_type const &v)
-> Eigen::Matrix<Scalar, N, 1, Options, MaxRows, MaxCols>
{
Eigen::Matrix<Scalar, N, 1, Options, MaxRows, MaxCols> out;
for (std::size_t i = 0; i < static_cast<std::size_t>(N); ++i)
out(static_cast<int>(i)) = v[i];
return out;
}
};
template<class Scalar, int N, int Options, int MaxRows, int MaxCols>
requires(std::is_arithmetic_v<Scalar> && (N > 0))
struct interop_adapter<Eigen::Matrix<Scalar, 1, N, Options, MaxRows, MaxCols>>
{
using smath_type = Vec<static_cast<std::size_t>(N), Scalar>;
static constexpr auto to_smath(
Eigen::Matrix<Scalar, 1, N, Options, MaxRows, MaxCols> const &v)
-> smath_type
{
smath_type out {};
for (std::size_t i = 0; i < static_cast<std::size_t>(N); ++i)
out[i] = v(static_cast<int>(i));
return out;
}
static constexpr auto from_smath(smath_type const &v)
-> Eigen::Matrix<Scalar, 1, N, Options, MaxRows, MaxCols>
{
Eigen::Matrix<Scalar, 1, N, Options, MaxRows, MaxCols> out;
for (std::size_t i = 0; i < static_cast<std::size_t>(N); ++i)
out(static_cast<int>(i)) = v[i];
return out;
}
};
template<class Scalar, int R, int C, int Options, int MaxRows, int MaxCols>
requires(std::is_arithmetic_v<Scalar> && (R > 1) && (C > 1))
struct interop_adapter<Eigen::Matrix<Scalar, R, C, Options, MaxRows, MaxCols>>
{
using smath_type
= Mat<static_cast<std::size_t>(R), static_cast<std::size_t>(C), Scalar>;
static constexpr auto to_smath(
Eigen::Matrix<Scalar, R, C, Options, MaxRows, MaxCols> const &m)
-> smath_type
{
smath_type out {};
for (std::size_t c = 0; c < static_cast<std::size_t>(C); ++c) {
for (std::size_t r = 0; r < static_cast<std::size_t>(R); ++r)
out[r, c] = m(static_cast<int>(r), static_cast<int>(c));
}
return out;
}
static constexpr auto from_smath(smath_type const &m)
-> Eigen::Matrix<Scalar, R, C, Options, MaxRows, MaxCols>
{
Eigen::Matrix<Scalar, R, C, Options, MaxRows, MaxCols> out;
for (std::size_t c = 0; c < static_cast<std::size_t>(C); ++c) {
for (std::size_t r = 0; r < static_cast<std::size_t>(R); ++r)
out(static_cast<int>(r), static_cast<int>(c)) = m[r, c];
}
return out;
}
};
template<class Scalar, int Options>
requires std::is_arithmetic_v<Scalar>
struct interop_adapter<Eigen::Quaternion<Scalar, Options>>
{
using smath_type = Quaternion<Scalar>;
static constexpr auto to_smath(Eigen::Quaternion<Scalar, Options> const &q)
-> smath_type
{
return { q.x(), q.y(), q.z(), q.w() };
}
static constexpr auto from_smath(smath_type const &q)
-> Eigen::Quaternion<Scalar, Options>
{
return { q.w(), q.x(), q.y(), q.z() };
}
};
} // namespace smath

View File

@@ -0,0 +1,103 @@
/*
* smath - Single-file linear algebra math library for C++23.
*
* Copyright 2025 Slendi <slendi@socopon.com>
*
* 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.
*/
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <smath/smath.hpp>
namespace smath
{
template<glm::length_t L, class T, glm::qualifier Q>
requires std::is_arithmetic_v<T>
struct interop_adapter<glm::vec<L, T, Q>>
{
using smath_type = Vec<static_cast<std::size_t>(L), T>;
static constexpr auto to_smath(glm::vec<L, T, Q> const &v) -> smath_type
{
smath_type out {};
for (std::size_t i = 0; i < static_cast<std::size_t>(L); ++i)
out[i] = v[static_cast<glm::length_t>(i)];
return out;
}
static constexpr auto from_smath(smath_type const &v) -> glm::vec<L, T, Q>
{
glm::vec<L, T, Q> out {};
for (std::size_t i = 0; i < static_cast<std::size_t>(L); ++i)
out[static_cast<glm::length_t>(i)] = v[i];
return out;
}
};
template<glm::length_t C, glm::length_t R, class T, glm::qualifier Q>
requires std::is_arithmetic_v<T>
struct interop_adapter<glm::mat<C, R, T, Q>>
{
using smath_type
= Mat<static_cast<std::size_t>(R), static_cast<std::size_t>(C), T>;
static constexpr auto to_smath(glm::mat<C, R, T, Q> const &m) -> smath_type
{
smath_type out {};
for (std::size_t c = 0; c < static_cast<std::size_t>(C); ++c) {
for (std::size_t r = 0; r < static_cast<std::size_t>(R); ++r) {
out[r, c] = m[static_cast<glm::length_t>(c)]
[static_cast<glm::length_t>(r)];
}
}
return out;
}
static constexpr auto from_smath(smath_type const &m)
-> glm::mat<C, R, T, Q>
{
glm::mat<C, R, T, Q> out {};
for (std::size_t c = 0; c < static_cast<std::size_t>(C); ++c) {
for (std::size_t r = 0; r < static_cast<std::size_t>(R); ++r) {
out[static_cast<glm::length_t>(c)]
[static_cast<glm::length_t>(r)]
= m[r, c];
}
}
return out;
}
};
template<class T, glm::qualifier Q>
requires std::is_arithmetic_v<T>
struct interop_adapter<glm::qua<T, Q>>
{
using smath_type = Quaternion<T>;
static constexpr auto to_smath(glm::qua<T, Q> const &q) -> smath_type
{
return { q.x, q.y, q.z, q.w };
}
static constexpr auto from_smath(smath_type const &q) -> glm::qua<T, Q>
{
return { q.w(), q.x(), q.y(), q.z() };
}
};
} // namespace smath

View File

@@ -0,0 +1,125 @@
/*
* smath - Single-file linear algebra math library for C++23.
*
* Copyright 2025 Slendi <slendi@socopon.com>
*
* 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.
*/
#pragma once
#include <HandmadeMath.h>
#include <smath/smath.hpp>
namespace smath
{
template<> struct interop_adapter<HMM_Vec2>
{
using smath_type = Vec<2, float>;
static constexpr auto to_smath(HMM_Vec2 const &v) -> smath_type
{
return { v.X, v.Y };
}
static constexpr auto from_smath(smath_type const &v) -> HMM_Vec2
{
return { v.x(), v.y() };
}
};
template<> struct interop_adapter<HMM_Vec3>
{
using smath_type = Vec<3, float>;
static constexpr auto to_smath(HMM_Vec3 const &v) -> smath_type
{
return { v.X, v.Y, v.Z };
}
static constexpr auto from_smath(smath_type const &v) -> HMM_Vec3
{
return { v.x(), v.y(), v.z() };
}
};
template<> struct interop_adapter<HMM_Vec4>
{
using smath_type = Vec<4, float>;
static constexpr auto to_smath(HMM_Vec4 const &v) -> smath_type
{
return { v.X, v.Y, v.Z, v.W };
}
static constexpr auto from_smath(smath_type const &v) -> HMM_Vec4
{
return { v.x(), v.y(), v.z(), v.w() };
}
};
template<> struct interop_adapter<HMM_Mat4>
{
using smath_type = Mat<4, 4, float>;
static constexpr auto to_smath(HMM_Mat4 const &m) -> smath_type
{
smath_type out {};
out[0, 0] = m.Columns[0].X;
out[1, 0] = m.Columns[0].Y;
out[2, 0] = m.Columns[0].Z;
out[3, 0] = m.Columns[0].W;
out[0, 1] = m.Columns[1].X;
out[1, 1] = m.Columns[1].Y;
out[2, 1] = m.Columns[1].Z;
out[3, 1] = m.Columns[1].W;
out[0, 2] = m.Columns[2].X;
out[1, 2] = m.Columns[2].Y;
out[2, 2] = m.Columns[2].Z;
out[3, 2] = m.Columns[2].W;
out[0, 3] = m.Columns[3].X;
out[1, 3] = m.Columns[3].Y;
out[2, 3] = m.Columns[3].Z;
out[3, 3] = m.Columns[3].W;
return out;
}
static constexpr auto from_smath(smath_type const &m) -> HMM_Mat4
{
HMM_Mat4 out {};
out.Columns[0] = { m[0, 0], m[1, 0], m[2, 0], m[3, 0] };
out.Columns[1] = { m[0, 1], m[1, 1], m[2, 1], m[3, 1] };
out.Columns[2] = { m[0, 2], m[1, 2], m[2, 2], m[3, 2] };
out.Columns[3] = { m[0, 3], m[1, 3], m[2, 3], m[3, 3] };
return out;
}
};
template<> struct interop_adapter<HMM_Quat>
{
using smath_type = Quaternion<float>;
static constexpr auto to_smath(HMM_Quat const &q) -> smath_type
{
return { q.X, q.Y, q.Z, q.W };
}
static constexpr auto from_smath(smath_type const &q) -> HMM_Quat
{
return { q.x(), q.y(), q.z(), q.w() };
}
};
} // namespace smath

View File

@@ -0,0 +1,122 @@
/*
* smath - Single-file linear algebra math library for C++23.
*
* Copyright 2025 Slendi <slendi@socopon.com>
*
* 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.
*/
#pragma once
#include <raylib.h>
#include <smath/smath.hpp>
namespace smath
{
template<> struct interop_adapter<Vector2>
{
using smath_type = Vec<2, float>;
static constexpr auto to_smath(Vector2 const &v) -> smath_type
{
return { v.x, v.y };
}
static constexpr auto from_smath(smath_type const &v) -> Vector2
{
return { v.x(), v.y() };
}
};
template<> struct interop_adapter<Vector3>
{
using smath_type = Vec<3, float>;
static constexpr auto to_smath(Vector3 const &v) -> smath_type
{
return { v.x, v.y, v.z };
}
static constexpr auto from_smath(smath_type const &v) -> Vector3
{
return { v.x(), v.y(), v.z() };
}
};
template<> struct interop_adapter<Vector4>
{
using smath_type = Vec<4, float>;
static constexpr auto to_smath(Vector4 const &v) -> smath_type
{
return { v.x, v.y, v.z, v.w };
}
static constexpr auto from_smath(smath_type const &v) -> Vector4
{
return { v.x(), v.y(), v.z(), v.w() };
}
};
template<> struct interop_adapter<Matrix>
{
using smath_type = Mat<4, 4, float>;
static constexpr auto to_smath(Matrix const &m) -> smath_type
{
smath_type out {};
out[0, 0] = m.m0;
out[1, 0] = m.m1;
out[2, 0] = m.m2;
out[3, 0] = m.m3;
out[0, 1] = m.m4;
out[1, 1] = m.m5;
out[2, 1] = m.m6;
out[3, 1] = m.m7;
out[0, 2] = m.m8;
out[1, 2] = m.m9;
out[2, 2] = m.m10;
out[3, 2] = m.m11;
out[0, 3] = m.m12;
out[1, 3] = m.m13;
out[2, 3] = m.m14;
out[3, 3] = m.m15;
return out;
}
static constexpr auto from_smath(smath_type const &m) -> Matrix
{
return {
m[0, 0],
m[0, 1],
m[0, 2],
m[0, 3],
m[1, 0],
m[1, 1],
m[1, 2],
m[1, 3],
m[2, 0],
m[2, 1],
m[2, 2],
m[2, 3],
m[3, 0],
m[3, 1],
m[3, 2],
m[3, 3],
};
}
};
} // namespace smath

View File

@@ -0,0 +1,63 @@
/*
* smath - Single-file linear algebra math library for C++23.
*
* Copyright 2025 Slendi <slendi@socopon.com>
*
* 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.
*/
#pragma once
#include <SFML/System/Vector2.hpp>
#include <SFML/System/Vector3.hpp>
#include <smath/smath.hpp>
namespace smath
{
template<class T>
requires std::is_arithmetic_v<T>
struct interop_adapter<sf::Vector2<T>>
{
using smath_type = Vec<2, T>;
static constexpr auto to_smath(sf::Vector2<T> const &v) -> smath_type
{
return { v.x, v.y };
}
static constexpr auto from_smath(smath_type const &v) -> sf::Vector2<T>
{
return { v.x(), v.y() };
}
};
template<class T>
requires std::is_arithmetic_v<T>
struct interop_adapter<sf::Vector3<T>>
{
using smath_type = Vec<3, T>;
static constexpr auto to_smath(sf::Vector3<T> const &v) -> smath_type
{
return { v.x, v.y, v.z };
}
static constexpr auto from_smath(smath_type const &v) -> sf::Vector3<T>
{
return { v.x(), v.y(), v.z() };
}
};
} // namespace smath

View File

@@ -17,6 +17,7 @@
* *
* You can define the following macros to change functionality: * You can define the following macros to change functionality:
* - SMATH_IMPLICIT_CONVERSIONS * - SMATH_IMPLICIT_CONVERSIONS
* - SMATH_ANGLE_UNIT (=rad, =deg, =turns)
*/ */
#pragma once #pragma once
@@ -32,20 +33,23 @@
#include <type_traits> #include <type_traits>
#ifndef SMATH_ANGLE_UNIT #ifndef SMATH_ANGLE_UNIT
# define SMATH_ANGLE_UNIT rad #define SMATH_ANGLE_UNIT rad
#endif // SMATH_ANGLE_UNIT #endif // SMATH_ANGLE_UNIT
namespace smath { namespace smath
{
template<std::size_t N, typename T> 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 { namespace detail
{
#define SMATH_STR(x) #x #define SMATH_STR(x) #x
#define SMATH_XSTR(x) SMATH_STR(x) #define SMATH_XSTR(x) SMATH_STR(x)
consteval bool streq(const char *a, const char *b) consteval auto streq(const char *a, const char *b) -> bool
{ {
for (;; ++a, ++b) { for (;; ++a, ++b) {
if (*a != *b) if (*a != *b)
@@ -55,13 +59,14 @@ consteval bool streq(const char *a, const char *b)
} }
} }
enum class AngularUnit { enum class AngularUnit
{
Radians, Radians,
Degrees, Degrees,
Turns, Turns,
}; };
consteval std::optional<AngularUnit> parse_unit(char const *s) consteval auto parse_unit(char const *s) -> std::optional<AngularUnit>
{ {
if (streq(s, "rad")) if (streq(s, "rad"))
return AngularUnit::Radians; return AngularUnit::Radians;
@@ -76,7 +81,8 @@ constexpr auto SMATH_ANGLE_UNIT_ID = parse_unit(SMATH_XSTR(SMATH_ANGLE_UNIT));
static_assert(SMATH_ANGLE_UNIT_ID != std::nullopt, static_assert(SMATH_ANGLE_UNIT_ID != std::nullopt,
"Invalid SMATH_ANGLE_UNIT. Should be rad, deg, or turns."); "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;
constexpr FixedString(char const (&s)[N]) constexpr FixedString(char const (&s)[N])
@@ -86,8 +92,10 @@ template<std::size_t N> struct FixedString {
} }
constexpr char operator[](std::size_t i) const { return data[i]; } constexpr char operator[](std::size_t i) const { return data[i]; }
}; };
template<class X> struct is_Vec : std::false_type { }; template<class X> struct is_Vec : std::false_type
template<std::size_t M, class U> struct is_Vec<Vec<M, U>> : std::true_type { }; { };
template<std::size_t M, class U> struct is_Vec<Vec<M, U>> : std::true_type
{ };
template<class X> template<class X>
inline constexpr bool is_Vec_v = is_Vec<std::remove_cvref_t<X>>::value; inline constexpr bool is_Vec_v = is_Vec<std::remove_cvref_t<X>>::value;
template<class X> template<class X>
@@ -95,7 +103,8 @@ inline constexpr bool is_scalar_v
= std::is_arithmetic_v<std::remove_cvref_t<X>>; = std::is_arithmetic_v<std::remove_cvref_t<X>>;
template<class X> struct Vec_size; template<class X> struct Vec_size;
template<std::size_t M, class U> template<std::size_t M, class U>
struct Vec_size<Vec<M, U>> : std::integral_constant<std::size_t, M> { }; 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 template<class T> constexpr auto pack_unorm8(T v) -> std::uint8_t
{ {
@@ -144,9 +153,16 @@ template<class T> constexpr auto unpack_snorm8(std::int8_t b) -> T
} // namespace detail } // namespace detail
template<std::size_t N, typename T = float> 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>
/**
* @brief Fixed-size arithmetic vector.
* @tparam N Number of components.
* @tparam T Component type.
*/
struct Vec : std::array<T, N>
{
private: private:
template<class X> static consteval std::size_t extent() template<class X> static consteval auto extent() -> std::size_t
{ {
if constexpr (detail::is_Vec_v<X>) if constexpr (detail::is_Vec_v<X>)
return detail::Vec_size<std::remove_cvref_t<X>>::value; return detail::Vec_size<std::remove_cvref_t<X>>::value;
@@ -155,19 +171,24 @@ private:
else else
return 0; // Should be unreachable return 0; // Should be unreachable
} }
template<class... Args> static consteval std::size_t total_extent() template<class... Args> static consteval auto total_extent() -> std::size_t
{ {
return (extent<Args>() + ... + 0); return (extent<Args>() + ... + 0);
} }
public: public:
// Constructors // Constructors
/** @brief Constructs a zero-initialized vector. */
constexpr Vec() noexcept constexpr Vec() noexcept
{ {
for (auto &v : *this) for (auto &v : *this)
v = T(0); v = T(0);
} }
/**
* @brief Fills all components with the same scalar.
* @param s Scalar value assigned to every component.
*/
explicit constexpr Vec(T const &s) noexcept explicit constexpr Vec(T const &s) noexcept
{ {
for (auto &v : *this) for (auto &v : *this)
@@ -178,6 +199,10 @@ public:
requires((detail::is_scalar_v<Args> || detail::is_Vec_v<Args>) && ...) requires((detail::is_scalar_v<Args> || detail::is_Vec_v<Args>) && ...)
&& (total_extent<Args...>() == N) && (total_extent<Args...>() == N)
&& (!(sizeof...(Args) == 1 && (detail::is_Vec_v<Args> && ...))) && (!(sizeof...(Args) == 1 && (detail::is_Vec_v<Args> && ...)))
/**
* @brief Constructs from scalars and/or vectors whose flattened extent is
* N.
*/
constexpr Vec(Args &&...args) noexcept constexpr Vec(Args &&...args) noexcept
{ {
std::size_t i = 0; std::size_t i = 0;
@@ -188,12 +213,9 @@ 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 & \ constexpr auto component() noexcept -> T &requires(N >= req) { \
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]; \
@@ -225,7 +247,11 @@ public:
((args = (*this)[Is]), ...); ((args = (*this)[Is]), ...);
} }
template<class... Args> constexpr void unpack(Args &...args) noexcept template<class... Args>
/**
* @brief Unpacks components into output references.
*/
constexpr auto unpack(Args &...args) noexcept -> void
{ {
unpack_impl(std::index_sequence_for<Args...> {}, args...); unpack_impl(std::index_sequence_for<Args...> {}, args...);
} }
@@ -284,13 +310,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; \
@@ -315,6 +341,7 @@ public:
return !(*this == v); return !(*this == v);
} }
/** @brief Returns Euclidean magnitude. */
constexpr auto magnitude() const noexcept -> T constexpr auto magnitude() const noexcept -> T
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
{ {
@@ -323,6 +350,7 @@ public:
total += v * v; total += v * v;
return std::sqrt(total); return std::sqrt(total);
} }
/** @brief Alias for magnitude(). */
constexpr auto length() const noexcept -> T constexpr auto length() const noexcept -> T
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
{ {
@@ -331,6 +359,9 @@ public:
template<typename U = T> template<typename U = T>
requires std::is_floating_point_v<U> requires std::is_floating_point_v<U>
/**
* @brief Normalizes with zero fallback for very small magnitudes.
*/
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(); auto m = magnitude();
@@ -338,27 +369,32 @@ public:
} }
template<typename U = T> template<typename U = T>
requires std::is_floating_point_v<U> requires std::is_floating_point_v<U>
/** @brief Alias for normalized_safe(). */
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); return normalized_safe(eps);
} }
/** @brief Returns normalized vector; undefined for zero magnitude. */
[[nodiscard]] constexpr auto normalized() noexcept -> Vec<N, T> [[nodiscard]] constexpr auto normalized() noexcept -> Vec<N, T>
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
{ {
return (*this) / this->magnitude(); return (*this) / this->magnitude();
} }
/** @brief Alias for normalized(). */
[[nodiscard]] constexpr auto normalize() noexcept -> Vec<N, T> [[nodiscard]] constexpr auto normalize() noexcept -> Vec<N, T>
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
{ {
return this->normalized(); return this->normalized();
} }
/** @brief Alias for normalized(). */
[[nodiscard]] constexpr auto unit() noexcept -> Vec<N, T> [[nodiscard]] constexpr auto unit() noexcept -> Vec<N, T>
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
{ {
return this->normalized(); return this->normalized();
} }
/** @brief Dot product with `other`. */
[[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; T res = 0;
@@ -369,6 +405,13 @@ public:
} }
static constexpr T EPS_DEFAULT = T(1e-6); static constexpr T EPS_DEFAULT = T(1e-6);
/**
* @brief Approximate equality with per-component absolute epsilon.
* @param rhs Vec to compare against.
* @param eps Absolute tolerance per component.
* @return `true` when all components are approximately equal.
*/
template<class U = T> template<class U = T>
requires std::is_floating_point_v<U> requires std::is_floating_point_v<U>
[[nodiscard]] constexpr auto approx_equal( [[nodiscard]] constexpr auto approx_equal(
@@ -382,6 +425,7 @@ public:
} }
template<class U = T> template<class U = T>
/** @brief Magnitude computed in promoted type `U` (or `double`). */
constexpr auto magnitude_promoted() const noexcept constexpr auto magnitude_promoted() const noexcept
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
{ {
@@ -393,19 +437,25 @@ public:
} }
template<typename U = T> template<typename U = T>
requires(N == 3) constexpr auto cross(Vec const &r) const noexcept -> Vec requires(N == 3)
/** @brief 3D cross product. */
constexpr auto cross(Vec const &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],
};
} }
/** @brief Distance between this vector and `r`. */
constexpr auto distance(Vec const &r) const noexcept -> T constexpr auto distance(Vec const &r) const noexcept -> T
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
{ {
return (*this - r).magnitude(); return (*this - r).magnitude();
} }
/** @brief Projection of this vector onto `n`. */
constexpr auto project_onto(Vec const &n) const noexcept -> Vec constexpr auto project_onto(Vec const &n) const noexcept -> Vec
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
{ {
@@ -425,8 +475,8 @@ public:
template<class U> template<class U>
requires(std::is_arithmetic_v<U> && N >= 1) requires(std::is_arithmetic_v<U> && N >= 1)
constexpr explicit(!std::is_convertible_v<T, U>) constexpr explicit(
operator Vec<N, U>() const noexcept !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)
@@ -444,26 +494,26 @@ public:
} }
private: private:
constexpr void fill_one(std::size_t &i, T const &v) noexcept constexpr auto fill_one(std::size_t &i, T const &v) noexcept -> void
{ {
(*this)[i++] = v; (*this)[i++] = v;
} }
#ifdef SMATH_IMPLICIT_CONVERSIONS #ifdef SMATH_IMPLICIT_CONVERSIONS
template<class U> template<class U>
requires std::is_arithmetic_v<U> && (!std::is_same_v<U, T>)constexpr void requires std::is_arithmetic_v<U> && (!std::is_same_v<U, T>)
fill_one(std::size_t &i, const U &v) noexcept constexpr auto fill_one(std::size_t &i, const U &v) noexcept -> void
{ {
(*this)[i++] = static_cast<T>(v); (*this)[i++] = static_cast<T>(v);
} }
template<std::size_t M, class U> template<std::size_t M, class U>
constexpr void fill_one(std::size_t &i, Vec<M, U> const &v) noexcept constexpr auto fill_one(std::size_t &i, Vec<M, U> const &v) noexcept -> void
{ {
for (std::size_t k = 0; k < M; ++k) for (std::size_t k = 0; k < M; ++k)
(*this)[i++] = static_cast<T>(v[k]); (*this)[i++] = static_cast<T>(v[k]);
} }
#endif // SMATH_IMPLICIT_CONVERSIONS #endif // SMATH_IMPLICIT_CONVERSIONS
template<std::size_t M> template<std::size_t M>
constexpr void fill_one(std::size_t &i, const Vec<M, T> &v) noexcept constexpr auto fill_one(std::size_t &i, const Vec<M, T> &v) noexcept -> void
{ {
for (std::size_t k = 0; k < M; ++k) for (std::size_t k = 0; k < M; ++k)
(*this)[i++] = static_cast<T>(v[k]); (*this)[i++] = static_cast<T>(v[k]);
@@ -495,9 +545,15 @@ constexpr T const &&get(Vec<N, T> const &&v) noexcept
template<std::size_t N, typename T = float> template<std::size_t N, typename T = float>
requires std::is_arithmetic_v<T> requires std::is_arithmetic_v<T>
/**
* @brief Return type used by swizzle operations.
*
* Produces `T` when `N == 1`, otherwise `Vec<N, T>`.
*/
using VecOrScalar = std::conditional_t<N == 1, T, Vec<N, T>>; using VecOrScalar = std::conditional_t<N == 1, T, Vec<N, T>>;
namespace detail { namespace detail
{
consteval auto char_to_idx(char c) -> std::size_t consteval auto char_to_idx(char c) -> std::size_t
{ {
@@ -567,22 +623,39 @@ concept ValidSwizzle
template<detail::FixedString S, std::size_t N, typename T> template<detail::FixedString S, std::size_t N, typename T>
requires detail::ValidSwizzle<S, N> requires detail::ValidSwizzle<S, N>
/**
* @brief Compile-time swizzle selection.
* @tparam S Swizzle pattern like `"xy"`, `"rgba"`, or `"stp"`.
* @param v Source vector.
* @return Swizzled scalar/vector according to `S`.
*/
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> {}); return detail::swizzle_impl<S>(v, std::make_index_sequence<S.size> {});
} }
/** @brief 2D float vector alias. */
using Vec2 = Vec<2>; using Vec2 = Vec<2>;
/** @brief 3D float vector alias. */
using Vec3 = Vec<3>; using Vec3 = Vec<3>;
/** @brief 4D float vector alias. */
using Vec4 = Vec<4>; using Vec4 = Vec<4>;
/** @brief 2D double vector alias. */
using Vec2d = Vec<2, double>; using Vec2d = Vec<2, double>;
/** @brief 3D double vector alias. */
using Vec3d = Vec<3, double>; using Vec3d = Vec<3, double>;
/** @brief 4D double vector alias. */
using Vec4d = Vec<4, double>; using Vec4d = Vec<4, double>;
template<class T> template<class T>
using angle_ret_t = std::conditional_t<std::is_same_v<T, float>, float, double>; using angle_ret_t = std::conditional_t<std::is_same_v<T, float>, float, double>;
/**
* @brief Convert degrees to the configured angle unit.
* @param value Angle in degrees.
* @return Angle in `SMATH_ANGLE_UNIT`.
*/
template<class T> constexpr auto deg(T value) template<class T> constexpr auto deg(T value)
{ {
using R = angle_ret_t<T>; using R = angle_ret_t<T>;
@@ -597,6 +670,11 @@ template<class T> constexpr auto deg(T value)
} }
} }
/**
* @brief Convert radians to the configured angle unit.
* @param value Angle in radians.
* @return Angle in `SMATH_ANGLE_UNIT`.
*/
template<class T> constexpr auto rad(T value) template<class T> constexpr auto rad(T value)
{ {
using R = angle_ret_t<T>; using R = angle_ret_t<T>;
@@ -612,6 +690,11 @@ template<class T> constexpr auto rad(T value)
} }
} }
/**
* @brief Convert turns to the configured angle unit.
* @param value Angle in turns.
* @return Angle in `SMATH_ANGLE_UNIT`.
*/
template<class T> constexpr auto turns(T value) template<class T> constexpr auto turns(T value)
{ {
using R = angle_ret_t<T>; using R = angle_ret_t<T>;
@@ -627,14 +710,22 @@ template<class T> constexpr auto turns(T value)
} }
} }
template<std::size_t R, std::size_t C, typename T> template<std::size_t R, std::size_t C, typename T>
requires std::is_arithmetic_v<T> struct Mat; requires std::is_arithmetic_v<T>
struct Mat;
template<class T> struct Quaternion : Vec<4, T> { /**
* @brief Quaternion represented as `(x, y, z, w)`.
* @tparam T Component type.
*/
template<class T> struct Quaternion : Vec<4, T>
{
using Base = Vec<4, T>; using Base = Vec<4, T>;
using Base::Base; using Base::Base;
using Base::operator=; using Base::operator=;
/** @brief Returns this quaternion as its vector base type. */
constexpr Base &vec() noexcept { return *this; } constexpr Base &vec() noexcept { return *this; }
/** @brief Returns this quaternion as its vector base type. */
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(); }
@@ -646,6 +737,11 @@ template<class T> struct Quaternion : Vec<4, T> {
constexpr T &w() noexcept { return Base::w(); } constexpr T &w() noexcept { return Base::w(); }
constexpr T const &w() const noexcept { return Base::w(); } constexpr T const &w() const noexcept { return Base::w(); }
/**
* @brief Hamilton product.
* @param rhs Right-hand quaternion.
* @return Product quaternion.
*/
constexpr auto operator*(Quaternion const &rhs) const noexcept -> Quaternion constexpr auto operator*(Quaternion const &rhs) const noexcept -> Quaternion
{ {
Quaternion r; Quaternion r;
@@ -663,6 +759,10 @@ template<class T> struct Quaternion : Vec<4, T> {
return r; return r;
} }
/**
* @brief Converts this quaternion to a 4x4 rotation matrix.
* @return Homogeneous rotation matrix.
*/
[[nodiscard]] constexpr auto as_matrix() const noexcept -> Mat<4, 4, T> [[nodiscard]] constexpr auto as_matrix() const noexcept -> Mat<4, 4, T>
{ {
auto const xx = x() * x(); auto const xx = x() * x();
@@ -683,6 +783,12 @@ template<class T> struct Quaternion : Vec<4, T> {
}; };
} }
/**
* @brief Builds a quaternion from axis-angle input.
* @param axis Rotation axis.
* @param angle Rotation angle in configured angle units.
* @return Quaternion representing the rotation.
*/
[[nodiscard]] static constexpr auto from_axis_angle( [[nodiscard]] static constexpr auto from_axis_angle(
Vec<3, T> const &axis, T const angle) noexcept -> Quaternion Vec<3, T> const &axis, T const angle) noexcept -> Quaternion
{ {
@@ -702,6 +808,10 @@ template<class T> struct Quaternion : Vec<4, T> {
template<class T> template<class T>
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
/**
* @brief Packs `[0, 1]` RGBA values into 4x8-bit UNORM.
* @return Packed 32-bit value in RGBA byte order.
*/
[[nodiscard]] constexpr auto pack_unorm4x8(Vec<4, T> const &v) -> std::uint32_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 r = detail::pack_unorm8(v[0]);
@@ -714,6 +824,10 @@ requires std::is_floating_point_v<T>
template<class T> template<class T>
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
/**
* @brief Packs `[-1, 1]` RGBA values into 4x8-bit SNORM.
* @return Packed 32-bit value in RGBA byte order.
*/
[[nodiscard]] constexpr auto pack_snorm4x8(Vec<4, T> const &v) -> std::uint32_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 r = static_cast<std::uint8_t>(detail::pack_snorm8(v[0]));
@@ -726,6 +840,10 @@ requires std::is_floating_point_v<T>
template<class T = float> template<class T = float>
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
/**
* @brief Unpacks a 4x8-bit UNORM RGBA value.
* @return RGBA vector with components in `[0, 1]`.
*/
[[nodiscard]] constexpr auto unpack_unorm4x8(std::uint32_t packed) -> Vec<4, 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 r = static_cast<std::uint8_t>(packed & 0xFFu);
@@ -743,6 +861,10 @@ requires std::is_floating_point_v<T>
template<class T = float> template<class T = float>
requires std::is_floating_point_v<T> requires std::is_floating_point_v<T>
/**
* @brief Unpacks a 4x8-bit SNORM RGBA value.
* @return RGBA vector with components in `[-1, 1]`.
*/
[[nodiscard]] constexpr auto unpack_snorm4x8(std::uint32_t packed) -> Vec<4, 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 r = static_cast<std::int8_t>(packed & 0xFFu);
@@ -759,7 +881,15 @@ requires std::is_floating_point_v<T>
} }
template<std::size_t R, std::size_t C, typename T = float> 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>
/**
* @brief Column-major matrix with `R` rows and `C` columns.
* @tparam R Row count.
* @tparam C Column count.
* @tparam T Scalar type.
*/
struct Mat : std::array<Vec<R, T>, C>
{
using Base = std::array<Vec<R, T>, C>; using Base = std::array<Vec<R, T>, C>;
using Base::operator[]; using Base::operator[];
@@ -774,12 +904,17 @@ requires std::is_arithmetic_v<T> struct Mat : std::array<Vec<R, T>, C> {
return col(column)[row]; return col(column)[row];
} }
/** @brief Constructs a zero matrix. */
constexpr Mat() noexcept constexpr Mat() noexcept
{ {
for (auto &col : *this) for (auto &col : *this)
col = Vec<R, T> {}; col = Vec<R, T> {};
} }
/**
* @brief Constructs a diagonal matrix.
* @param diag Value used for every diagonal component.
*/
constexpr explicit Mat(T const &diag) noexcept constexpr explicit Mat(T const &diag) noexcept
requires(R == C) requires(R == C)
{ {
@@ -792,10 +927,12 @@ requires std::is_arithmetic_v<T> struct Mat : std::array<Vec<R, T>, C> {
template<typename... Cols> template<typename... Cols>
requires(sizeof...(Cols) == C requires(sizeof...(Cols) == C
&& (std::same_as<std::remove_cvref_t<Cols>, Vec<R, T>> && ...)) && (std::same_as<std::remove_cvref_t<Cols>, Vec<R, T>> && ...))
constexpr Mat(Cols const &...cols) noexcept /**
: Base { cols... } * @brief Constructs from explicit column vectors.
{ * @param cols Columns in column-major order.
} */
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> &
{ {
@@ -890,6 +1027,13 @@ requires std::is_arithmetic_v<T> struct Mat : std::array<Vec<R, T>, C> {
} }
static constexpr T EPS_DEFAULT = T(1e-6); static constexpr T EPS_DEFAULT = T(1e-6);
/**
* @brief Approximate matrix equality check.
* @param rhs Matrix to compare against.
* @param eps Absolute tolerance per component.
* @return `true` when all columns are approximately equal.
*/
template<class U = T> template<class U = T>
requires std::is_floating_point_v<U> requires std::is_floating_point_v<U>
[[nodiscard]] constexpr auto approx_equal( [[nodiscard]] constexpr auto approx_equal(
@@ -901,6 +1045,7 @@ requires std::is_arithmetic_v<T> struct Mat : std::array<Vec<R, T>, C> {
return true; return true;
} }
/** @brief Returns the transposed matrix. */
[[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 {}; Mat<C, R, T> r {};
@@ -910,6 +1055,7 @@ requires std::is_arithmetic_v<T> struct Mat : std::array<Vec<R, T>, C> {
return r; return r;
} }
/** @brief Returns an identity matrix. */
[[nodiscard]] static constexpr auto identity() noexcept -> Mat<R, C, T> [[nodiscard]] static constexpr auto identity() noexcept -> Mat<R, C, T>
requires(R == C) requires(R == C)
{ {
@@ -920,15 +1066,25 @@ requires std::is_arithmetic_v<T> struct Mat : std::array<Vec<R, T>, C> {
} }
}; };
/** @brief 2x2 float matrix alias. */
using Mat2 = Mat<2, 2>; using Mat2 = Mat<2, 2>;
/** @brief 3x3 float matrix alias. */
using Mat3 = Mat<3, 3>; using Mat3 = Mat<3, 3>;
/** @brief 4x4 float matrix alias. */
using Mat4 = Mat<4, 4>; using Mat4 = Mat<4, 4>;
/** @brief 2x2 double matrix alias. */
using Mat2d = Mat<2, 2, double>; using Mat2d = Mat<2, 2, double>;
/** @brief 3x3 double matrix alias. */
using Mat3d = Mat<3, 3, double>; using Mat3d = Mat<3, 3, double>;
/** @brief 4x4 double matrix alias. */
using Mat4d = Mat<4, 4, double>; using Mat4d = Mat<4, 4, double>;
template<std::size_t R, std::size_t C, typename T> template<std::size_t R, std::size_t C, typename T>
/**
* @brief Matrix-vector multiplication.
* @return Product vector.
*/
[[nodiscard]] constexpr Vec<R, T> operator*( [[nodiscard]] constexpr Vec<R, T> operator*(
Mat<R, C, T> const &m, Vec<C, T> const &v) noexcept Mat<R, C, T> const &m, Vec<C, T> const &v) noexcept
{ {
@@ -938,7 +1094,10 @@ template<std::size_t R, std::size_t C, typename T>
return out; return out;
} }
// Matrix * Matrix /**
* @brief Matrix-matrix multiplication.
* @return Product matrix.
*/
template<std::size_t R, std::size_t C, std::size_t K, typename T> template<std::size_t R, std::size_t C, std::size_t K, typename T>
[[nodiscard]] constexpr Mat<R, K, T> operator*( [[nodiscard]] constexpr Mat<R, K, T> operator*(
Mat<R, C, T> const &a, Mat<C, K, T> const &b) noexcept Mat<R, C, T> const &a, Mat<C, K, T> const &b) noexcept
@@ -955,7 +1114,10 @@ template<std::size_t R, std::size_t C, std::size_t K, typename T>
return out; return out;
} }
// Mat3 transformations /** @name 2D transforms (Mat3)
* @{ */
/** @brief Applies translation to an existing 3x3 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto translate(Mat<3, 3, T> const &m, Vec<2, T> const &v) [[nodiscard]] inline auto translate(Mat<3, 3, T> const &m, Vec<2, T> const &v)
-> Mat<3, 3, T> -> Mat<3, 3, T>
@@ -965,6 +1127,7 @@ template<typename T>
return res; return res;
} }
/** @brief Creates a 3x3 translation matrix. */
template<typename T> 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>
{ {
@@ -974,6 +1137,7 @@ template<typename T>
return res; return res;
} }
/** @brief Applies rotation to an existing 3x3 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto rotate(Mat<3, 3, T> const &m, T const angle) [[nodiscard]] inline auto rotate(Mat<3, 3, T> const &m, T const angle)
-> Mat<3, 3, T> -> Mat<3, 3, T>
@@ -990,6 +1154,7 @@ template<typename T>
return res; return res;
} }
/** @brief Applies scaling to an existing 3x3 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto scale(Mat<3, 3, T> const &m, Vec<2, T> const &v) [[nodiscard]] inline auto scale(Mat<3, 3, T> const &m, Vec<2, T> const &v)
-> Mat<3, 3, T> -> Mat<3, 3, T>
@@ -1003,6 +1168,7 @@ template<typename T>
return res; return res;
} }
/** @brief Applies X shear to an existing 3x3 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_x(Mat<3, 3, T> const &m, T const v) [[nodiscard]] inline auto shear_x(Mat<3, 3, T> const &m, T const v)
-> Mat<3, 3, T> -> Mat<3, 3, T>
@@ -1012,6 +1178,7 @@ template<typename T>
return m * res; return m * res;
} }
/** @brief Applies Y shear to an existing 3x3 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_y(Mat<3, 3, T> const &m, T const v) [[nodiscard]] inline auto shear_y(Mat<3, 3, T> const &m, T const v)
-> Mat<3, 3, T> -> Mat<3, 3, T>
@@ -1021,7 +1188,12 @@ template<typename T>
return m * res; return m * res;
} }
// Mat4 transformations /** @} */
/** @name 3D transforms (Mat4)
* @{ */
/** @brief Applies translation to an existing 4x4 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto translate(Mat<4, 4, T> const &m, Vec<3, T> const &v) [[nodiscard]] inline auto translate(Mat<4, 4, T> const &m, Vec<3, T> const &v)
-> Mat<4, 4, T> -> Mat<4, 4, T>
@@ -1031,6 +1203,7 @@ template<typename T>
return res; return res;
} }
/** @brief Creates a 4x4 translation matrix. */
template<typename T> 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>
{ {
@@ -1041,6 +1214,7 @@ template<typename T>
return res; return res;
} }
/** @brief Applies rotation to an existing 4x4 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto rotate(Mat<4, 4, T> const &m, T const angle) [[nodiscard]] inline auto rotate(Mat<4, 4, T> const &m, T const angle)
-> Mat<4, 4, T> -> Mat<4, 4, T>
@@ -1058,6 +1232,7 @@ template<typename T>
return res; return res;
} }
/** @brief Applies scaling to an existing 4x4 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto scale(Mat<4, 4, T> const &m, Vec<3, T> const &v) [[nodiscard]] inline auto scale(Mat<4, 4, T> const &m, Vec<3, T> const &v)
-> Mat<4, 4, T> -> Mat<4, 4, T>
@@ -1072,6 +1247,7 @@ template<typename T>
return res; return res;
} }
/** @brief Applies X shear to an existing 4x4 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_x(Mat<4, 4, T> const &m, T const v) [[nodiscard]] inline auto shear_x(Mat<4, 4, T> const &m, T const v)
-> Mat<4, 4, T> -> Mat<4, 4, T>
@@ -1081,6 +1257,7 @@ template<typename T>
return m * res; return m * res;
} }
/** @brief Applies Y shear to an existing 4x4 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_y(Mat<4, 4, T> const &m, T const v) [[nodiscard]] inline auto shear_y(Mat<4, 4, T> const &m, T const v)
-> Mat<4, 4, T> -> Mat<4, 4, T>
@@ -1090,6 +1267,7 @@ template<typename T>
return m * res; return m * res;
} }
/** @brief Applies Z shear to an existing 4x4 transform. */
template<typename T> template<typename T>
[[nodiscard]] inline auto shear_z(Mat<4, 4, T> const &m, T const v) [[nodiscard]] inline auto shear_z(Mat<4, 4, T> const &m, T const v)
-> Mat<4, 4, T> -> Mat<4, 4, T>
@@ -1099,9 +1277,24 @@ template<typename T>
return m * res; return m * res;
} }
/**
* @brief Builds an orthographic projection matrix.
* @param left Left clipping plane.
* @param right Right clipping plane.
* @param bottom Bottom clipping plane.
* @param top Top clipping plane.
* @param near Near clipping plane.
* @param far Far clipping plane.
* @param flip_z_axis Whether to flip the output Z axis convention.
* @return Orthographic projection matrix.
*/
template<typename T> template<typename T>
[[nodiscard]] inline auto matrix_ortho3d(T const left, T const right, [[nodiscard]] inline auto matrix_ortho3d(T const left,
T const bottom, T const top, T const near, T const far, 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> bool const flip_z_axis = true) -> Mat<4, 4, T>
{ {
Mat<4, 4, T> res {}; Mat<4, 4, T> res {};
@@ -1121,6 +1314,9 @@ template<typename T>
return res; return res;
} }
/**
* @brief Builds a finite perspective projection matrix.
*/
template<typename T> template<typename T>
inline auto matrix_perspective( inline auto matrix_perspective(
T fovy, T aspect, T znear, T zfar, bool flip_z_axis = false) -> Mat<4, 4, T> T fovy, T aspect, T znear, T zfar, bool flip_z_axis = false) -> Mat<4, 4, T>
@@ -1147,9 +1343,13 @@ inline auto matrix_perspective(
return m; return m;
} }
/**
* @brief Builds a right-handed look-at view matrix.
*/
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 center, Vec<3, T> const up) -> Mat<4, 4, T> Vec<3, T> const eye, Vec<3, T> const center, Vec<3, T> const up)
-> Mat<4, 4, T>
{ {
auto f = (center - eye).normalized_safe(); auto f = (center - eye).normalized_safe();
auto s = f.cross(up).normalized_safe(); auto s = f.cross(up).normalized_safe();
@@ -1163,9 +1363,13 @@ template<typename T>
}; };
} }
/**
* @brief Builds a perspective matrix with an infinite far plane.
*/
template<typename T> template<typename T>
[[nodiscard]] inline auto matrix_infinite_perspective(T const fovy, [[nodiscard]] inline auto matrix_infinite_perspective(
T const aspect, T const znear, bool flip_z_axis = false) -> Mat<4, 4, T> T const fovy, T const aspect, T const znear, bool flip_z_axis = false)
-> Mat<4, 4, T>
{ {
Mat<4, 4, T> m {}; Mat<4, 4, T> m {};
@@ -1186,11 +1390,38 @@ template<typename T>
return m; return m;
} }
/** @} */
/**
* @brief Extension point for external math-type interoperability.
*/
template<class Ext> struct interop_adapter;
/**
* @brief Converts an external value into its mapped smath type.
*/
template<class Ext>
constexpr auto from_external(Ext const &ext) ->
typename interop_adapter<Ext>::smath_type
{
return interop_adapter<Ext>::to_smath(ext);
}
/**
* @brief Converts a smath value into an external type.
*/
template<class Ext, class SMathT>
constexpr auto to_external(SMathT const &value) -> Ext
{
return interop_adapter<Ext>::from_smath(value);
}
} // namespace smath } // namespace smath
template<std::size_t N, typename T> template<std::size_t N, typename T>
requires std::formattable<T, char> requires std::formattable<T, char>
struct std::formatter<smath::Vec<N, T>> : std::formatter<T> { 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); return std::formatter<T>::parse(ctx);
@@ -1213,12 +1444,14 @@ struct std::formatter<smath::Vec<N, T>> : std::formatter<T> {
} }
}; };
namespace std { namespace std
{
template<size_t N, class T> template<size_t N, class T>
struct tuple_size<smath::Vec<N, T>> : std::integral_constant<size_t, N> { }; struct tuple_size<smath::Vec<N, T>> : std::integral_constant<size_t, N>
{ };
template<size_t I, size_t N, class T> template<size_t I, size_t N, class T> struct tuple_element<I, smath::Vec<N, T>>
struct tuple_element<I, smath::Vec<N, T>> { {
static_assert(I < N); static_assert(I < N);
using type = T; using type = T;
}; };

View File

@@ -1,6 +1,6 @@
module; module;
#include "smath.hpp" #include "smath/smath.hpp"
export module smath; export module smath;

View File

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

110
tests/interop.cpp Normal file
View File

@@ -0,0 +1,110 @@
#include <type_traits>
#include <gtest/gtest.h>
#include <smath/smath.hpp>
struct ExternalVec3f
{
float x;
float y;
float z;
};
struct ExternalMat2f
{
float m00;
float m01;
float m10;
float m11;
};
namespace smath
{
template<> struct interop_adapter<ExternalVec3f>
{
using smath_type = Vec<3, float>;
static constexpr auto to_smath(ExternalVec3f const &v) -> smath_type
{
return { v.x, v.y, v.z };
}
static constexpr auto from_smath(smath_type const &v) -> ExternalVec3f
{
return { v[0], v[1], v[2] };
}
};
template<> struct interop_adapter<ExternalMat2f>
{
using smath_type = Mat<2, 2, float>;
static constexpr auto to_smath(ExternalMat2f const &m) -> smath_type
{
smath_type out {};
out(0, 0) = m.m00;
out(0, 1) = m.m01;
out(1, 0) = m.m10;
out(1, 1) = m.m11;
return out;
}
static constexpr auto from_smath(smath_type const &m) -> ExternalMat2f
{
return { m(0, 0), m(0, 1), m(1, 0), m(1, 1) };
}
};
} // namespace smath
TEST(Interop, FromExternalVec)
{
ExternalVec3f ext { 1.0f, 2.0f, 3.0f };
auto v = smath::from_external(ext);
static_assert(std::is_same_v<decltype(v), smath::Vec<3, float>>);
EXPECT_EQ(v[0], 1.0f);
EXPECT_EQ(v[1], 2.0f);
EXPECT_EQ(v[2], 3.0f);
}
TEST(Interop, ToExternalVec)
{
smath::Vec3 v { 4.0f, 5.0f, 6.0f };
auto ext = smath::to_external<ExternalVec3f>(v);
EXPECT_EQ(ext.x, 4.0f);
EXPECT_EQ(ext.y, 5.0f);
EXPECT_EQ(ext.z, 6.0f);
}
TEST(Interop, RoundtripVec)
{
ExternalVec3f ext { 7.0f, 8.0f, 9.0f };
auto v = smath::from_external(ext);
auto ext2 = smath::to_external<ExternalVec3f>(v);
EXPECT_EQ(ext2.x, 7.0f);
EXPECT_EQ(ext2.y, 8.0f);
EXPECT_EQ(ext2.z, 9.0f);
}
TEST(Interop, MatrixConversion)
{
ExternalMat2f ext { 1.0f, 2.0f, 3.0f, 4.0f };
auto m = smath::from_external(ext);
static_assert(std::is_same_v<decltype(m), smath::Mat<2, 2, float>>);
EXPECT_EQ(m(0, 0), 1.0f);
EXPECT_EQ(m(0, 1), 2.0f);
EXPECT_EQ(m(1, 0), 3.0f);
EXPECT_EQ(m(1, 1), 4.0f);
auto ext2 = smath::to_external<ExternalMat2f>(m);
EXPECT_EQ(ext2.m00, 1.0f);
EXPECT_EQ(ext2.m01, 2.0f);
EXPECT_EQ(ext2.m10, 3.0f);
EXPECT_EQ(ext2.m11, 4.0f);
}

View File

@@ -4,176 +4,191 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <smath.hpp> #include <smath/smath.hpp>
using smath::Vec; using smath::Vec;
using smath::Vec2; using smath::Vec2;
using smath::Vec3; using smath::Vec3;
using smath::Vec4; using smath::Vec4;
template <class T> template<class T>
static void ExpectVecNear(const Vec<3, T> &a, const Vec<3, T> &b, static void ExpectVecNear(
T eps = T(1e-6)) { const Vec<3, T> &a, const Vec<3, T> &b, T eps = T(1e-6))
for (int i = 0; i < 3; ++i) {
EXPECT_NEAR(double(a[i]), double(b[i]), double(eps)); for (int i = 0; i < 3; ++i)
EXPECT_NEAR(double(a[i]), double(b[i]), double(eps));
} }
// Constructors and accessors // Constructors and accessors
TEST(Vec, DefaultZero) { TEST(Vec, DefaultZero)
Vec3 v; {
EXPECT_EQ(v[0], 0.0f); Vec3 v;
EXPECT_EQ(v[1], 0.0f); EXPECT_EQ(v[0], 0.0f);
EXPECT_EQ(v[2], 0.0f); EXPECT_EQ(v[1], 0.0f);
EXPECT_EQ(v[2], 0.0f);
} }
TEST(Vec, ScalarFillCtor) { TEST(Vec, ScalarFillCtor)
Vec4 v{2.0f}; {
EXPECT_EQ(v.x(), 2.0f); Vec4 v { 2.0f };
EXPECT_EQ(v.y(), 2.0f); EXPECT_EQ(v.x(), 2.0f);
EXPECT_EQ(v.z(), 2.0f); EXPECT_EQ(v.y(), 2.0f);
EXPECT_EQ(v.w(), 2.0f); EXPECT_EQ(v.z(), 2.0f);
EXPECT_EQ(v.w(), 2.0f);
} }
TEST(Vec, VariadicCtorScalarsAndSubvectors) { TEST(Vec, VariadicCtorScalarsAndSubvectors)
Vec2 a{1.0f, 2.0f}; {
Vec2 b{3.0f, 4.0f}; Vec2 a { 1.0f, 2.0f };
Vec4 v{a, b}; Vec2 b { 3.0f, 4.0f };
EXPECT_EQ(v.r(), 1.0f); Vec4 v { a, b };
EXPECT_EQ(v.g(), 2.0f); EXPECT_EQ(v.r(), 1.0f);
EXPECT_EQ(v.b(), 3.0f); EXPECT_EQ(v.g(), 2.0f);
EXPECT_EQ(v.a(), 4.0f); EXPECT_EQ(v.b(), 3.0f);
EXPECT_EQ(v.a(), 4.0f);
} }
TEST(Vec, NamedAccessorsAliases) { TEST(Vec, NamedAccessorsAliases)
Vec3 v{1.0f, 2.0f, 3.0f}; {
EXPECT_EQ(v.x(), v.r()); Vec3 v { 1.0f, 2.0f, 3.0f };
EXPECT_EQ(v.y(), v.g()); EXPECT_EQ(v.x(), v.r());
EXPECT_EQ(v.z(), v.b()); EXPECT_EQ(v.y(), v.g());
EXPECT_EQ(v.z(), v.b());
} }
// Arithmetic // Arithmetic
TEST(Vec, ElementwiseAndScalarOps) { TEST(Vec, ElementwiseAndScalarOps)
Vec3 a{1.0f, 2.0f, 3.0f}; {
Vec3 b{4.0f, 5.0f, 6.0f}; Vec3 a { 1.0f, 2.0f, 3.0f };
Vec3 b { 4.0f, 5.0f, 6.0f };
auto s1 = a + b; auto s1 = a + b;
EXPECT_EQ(s1[0], 5.0f); EXPECT_EQ(s1[0], 5.0f);
EXPECT_EQ(s1[1], 7.0f); EXPECT_EQ(s1[1], 7.0f);
EXPECT_EQ(s1[2], 9.0f); EXPECT_EQ(s1[2], 9.0f);
auto s2 = a * 2.0f; auto s2 = a * 2.0f;
EXPECT_EQ(s2[0], 2.0f); EXPECT_EQ(s2[0], 2.0f);
EXPECT_EQ(s2[1], 4.0f); EXPECT_EQ(s2[1], 4.0f);
EXPECT_EQ(s2[2], 6.0f); EXPECT_EQ(s2[2], 6.0f);
auto s3 = 2.0f * a; // RHS overloads auto s3 = 2.0f * a; // RHS overloads
EXPECT_EQ(s3[0], 2.0f); EXPECT_EQ(s3[0], 2.0f);
EXPECT_EQ(s3[1], 4.0f); EXPECT_EQ(s3[1], 4.0f);
EXPECT_EQ(s3[2], 6.0f); EXPECT_EQ(s3[2], 6.0f);
Vec3 c{1.0f, 2.0f, 3.0f}; Vec3 c { 1.0f, 2.0f, 3.0f };
c += Vec3{1.0f, 1.0f, 1.0f}; c += Vec3 { 1.0f, 1.0f, 1.0f };
EXPECT_EQ(c[0], 2.0f); EXPECT_EQ(c[0], 2.0f);
EXPECT_EQ(c[1], 3.0f); EXPECT_EQ(c[1], 3.0f);
EXPECT_EQ(c[2], 4.0f); EXPECT_EQ(c[2], 4.0f);
c *= 2.0f; c *= 2.0f;
EXPECT_EQ(c[0], 4.0f); EXPECT_EQ(c[0], 4.0f);
EXPECT_EQ(c[1], 6.0f); EXPECT_EQ(c[1], 6.0f);
EXPECT_EQ(c[2], 8.0f); EXPECT_EQ(c[2], 8.0f);
} }
// Length, dot, cross, normalize // Length, dot, cross, normalize
TEST(Vec, MagnitudeAndDot) { TEST(Vec, MagnitudeAndDot)
Vec3 v{3.0f, 4.0f, 12.0f}; {
EXPECT_FLOAT_EQ(v.magnitude(), 13.0f); Vec3 v { 3.0f, 4.0f, 12.0f };
EXPECT_FLOAT_EQ(v.length(), 13.0f); EXPECT_FLOAT_EQ(v.magnitude(), 13.0f);
EXPECT_FLOAT_EQ(v.length(), 13.0f);
Vec3 u{1.0f, 0.0f, 2.0f}; Vec3 u { 1.0f, 0.0f, 2.0f };
EXPECT_FLOAT_EQ(v.dot(u), 27.0f); EXPECT_FLOAT_EQ(v.dot(u), 27.0f);
} }
TEST(Vec, Cross3D) { TEST(Vec, Cross3D)
Vec3 x{1.0f, 0.0f, 0.0f}; {
Vec3 y{0.0f, 1.0f, 0.0f}; Vec3 x { 1.0f, 0.0f, 0.0f };
auto z = x.cross(y); Vec3 y { 0.0f, 1.0f, 0.0f };
EXPECT_EQ(z[0], 0.0f); auto z = x.cross(y);
EXPECT_EQ(z[1], 0.0f); EXPECT_EQ(z[0], 0.0f);
EXPECT_EQ(z[2], 1.0f); EXPECT_EQ(z[1], 0.0f);
EXPECT_EQ(z[2], 1.0f);
} }
TEST(Vec, NormalizeAndSafeNormalize) { TEST(Vec, NormalizeAndSafeNormalize)
Vec3 v{10.0f, 0.0f, 0.0f}; {
auto n = v.normalized(); Vec3 v { 10.0f, 0.0f, 0.0f };
auto ns = v.normalized_safe(); auto n = v.normalized();
ExpectVecNear(n, Vec3{1.0f, 0.0f, 0.0f}); auto ns = v.normalized_safe();
ExpectVecNear(n, Vec3 { 1.0f, 0.0f, 0.0f });
Vec3 zero{}; Vec3 zero {};
auto zs = zero.normalized_safe(); auto zs = zero.normalized_safe();
EXPECT_EQ(zs[0], 0.0f); EXPECT_EQ(zs[0], 0.0f);
EXPECT_EQ(zs[1], 0.0f); EXPECT_EQ(zs[1], 0.0f);
EXPECT_EQ(zs[2], 0.0f); EXPECT_EQ(zs[2], 0.0f);
} }
TEST(Vec, DistanceAndProjection) { TEST(Vec, DistanceAndProjection)
Vec3 a{1.0f, 2.0f, 3.0f}; {
Vec3 b{4.0f, 6.0f, 3.0f}; Vec3 a { 1.0f, 2.0f, 3.0f };
EXPECT_FLOAT_EQ(a.distance(b), 5.0f); Vec3 b { 4.0f, 6.0f, 3.0f };
EXPECT_FLOAT_EQ(a.distance(b), 5.0f);
Vec3 n{2.0f, 0.0f, 0.0f}; // onto x-axis scaled Vec3 n { 2.0f, 0.0f, 0.0f }; // onto x-axis scaled
auto p = a.project_onto(n); // (a·n)/(n·n) * n = (2)/4 * n = 0.5 * n auto p = a.project_onto(n); // (a·n)/(n·n) * n = (2)/4 * n = 0.5 * n
ExpectVecNear(p, Vec3{1.0f, 0.0f, 0.0f}); ExpectVecNear(p, Vec3 { 1.0f, 0.0f, 0.0f });
} }
// Approx equal // Approx equal
TEST(Vec, ApproxEqual) { TEST(Vec, ApproxEqual)
Vec3 a{1.0f, 2.0f, 3.0f}; {
Vec3 b{1.0f + 1e-7f, 2.0f - 1e-7f, 3.0f}; Vec3 a { 1.0f, 2.0f, 3.0f };
EXPECT_TRUE(a.approx_equal(b, 1e-6f)); Vec3 b { 1.0f + 1e-7f, 2.0f - 1e-7f, 3.0f };
EXPECT_FALSE(a.approx_equal(b, 1e-9f)); EXPECT_TRUE(a.approx_equal(b, 1e-6f));
EXPECT_FALSE(a.approx_equal(b, 1e-9f));
} }
// std::get & tuple interop // std::get & tuple interop
TEST(Vec, StdGetAndTuple) { TEST(Vec, StdGetAndTuple)
Vec3 v{7.0f, 8.0f, 9.0f}; {
static_assert(std::tuple_size_v<Vec3> == 3); Vec3 v { 7.0f, 8.0f, 9.0f };
static_assert(std::is_same_v<std::tuple_element_t<1, Vec3>, float>); static_assert(std::tuple_size_v<Vec3> == 3);
EXPECT_EQ(std::get<0>(v), 7.0f); static_assert(std::is_same_v<std::tuple_element_t<1, Vec3>, float>);
EXPECT_EQ(std::get<1>(v), 8.0f); EXPECT_EQ(std::get<0>(v), 7.0f);
EXPECT_EQ(std::get<2>(v), 9.0f); EXPECT_EQ(std::get<1>(v), 8.0f);
EXPECT_EQ(std::get<2>(v), 9.0f);
} }
// Swizzle // Swizzle
TEST(Vec, SwizzleBasic) { TEST(Vec, SwizzleBasic)
const Vec3 v{1.0f, 2.0f, 3.0f}; {
const Vec3 v { 1.0f, 2.0f, 3.0f };
auto yz = smath::swizzle<"yz">(v); auto yz = smath::swizzle<"yz">(v);
EXPECT_EQ(yz[0], 2.0f); EXPECT_EQ(yz[0], 2.0f);
EXPECT_EQ(yz[1], 3.0f); EXPECT_EQ(yz[1], 3.0f);
auto rxx = smath::swizzle<"xxy">(v); auto rxx = smath::swizzle<"xxy">(v);
EXPECT_EQ(rxx[0], 1.0f); EXPECT_EQ(rxx[0], 1.0f);
EXPECT_EQ(rxx[1], 1.0f); EXPECT_EQ(rxx[1], 1.0f);
EXPECT_EQ(rxx[2], 2.0f); EXPECT_EQ(rxx[2], 2.0f);
} }
// std::formatter // std::formatter
TEST(Vec, Formatter) { TEST(Vec, Formatter)
smath::Vec<3, int> vi{1, 2, 3}; {
std::string s = std::format("{}", vi); smath::Vec<3, int> vi { 1, 2, 3 };
EXPECT_EQ(s, "{1, 2, 3}"); std::string s = std::format("{}", vi);
EXPECT_EQ(s, "{1, 2, 3}");
} }
// Conversions // Conversions
TEST(Vec, ExplicitConversionBetweenScalarTypes) { TEST(Vec, ExplicitConversionBetweenScalarTypes)
smath::Vec<3, int> vi{1, 2, 3}; {
smath::Vec<3, float> vf{vi}; smath::Vec<3, int> vi { 1, 2, 3 };
EXPECT_EQ(vf[0], 1.0f); smath::Vec<3, float> vf { vi };
EXPECT_EQ(vf[1], 2.0f); EXPECT_EQ(vf[0], 1.0f);
EXPECT_EQ(vf[2], 3.0f); EXPECT_EQ(vf[1], 2.0f);
EXPECT_EQ(vf[2], 3.0f);
auto vi2 = static_cast<smath::Vec<3, int>>(vf); auto vi2 = static_cast<smath::Vec<3, int>>(vf);
EXPECT_EQ(vi2[0], 1); EXPECT_EQ(vi2[0], 1);
EXPECT_EQ(vi2[1], 2); EXPECT_EQ(vi2[1], 2);
EXPECT_EQ(vi2[2], 3); EXPECT_EQ(vi2[2], 3);
} }