Compare commits

..

91 Commits

Author SHA1 Message Date
91c2e0d74b Merge pull request #159 from orange-cpp/feature/color_update
Feature/color update
2026-03-01 13:53:18 +03:00
52e9b906ff added const 2026-03-01 13:32:13 +03:00
cc6d625c2d added more formaters 2026-03-01 13:30:32 +03:00
5eaec70846 fixed tests 2026-03-01 13:22:15 +03:00
2063c4d33a updated color 2026-03-01 13:15:09 +03:00
60bf8ca30f moved file 2026-03-01 13:00:24 +03:00
6fca106edc Merge pull request #158 from orange-cpp/feature/quaternions
added files
2026-03-01 09:04:18 +03:00
78cb644920 added files 2026-03-01 08:23:26 +03:00
646a920e4c fixed potential deadlock 2026-02-27 08:47:46 +03:00
52687a70c7 fixed formating 2026-02-27 07:41:05 +03:00
a9eff7d320 Merge pull request #157 from orange-cpp/feature/mesh_improvement
Feature/mesh improvement
2026-02-26 16:39:21 +03:00
211e4c3d9b optimization 2026-02-26 16:19:54 +03:00
74dc2234f7 fixed collider when rotated 2026-02-26 16:17:41 +03:00
7ebbed6763 added funding
edit
2026-02-23 07:18:25 +03:00
e271bccaf5 added codeowners 2026-02-23 06:45:43 +03:00
50765f69c5 removed unused var 2026-02-23 04:36:48 +03:00
1169534133 fix 2026-02-23 04:32:13 +03:00
783501aab9 Enhance installation guide with prebuilt binaries section
Updated vcpkg section and added instructions for using prebuilt binaries from GitHub Releases.
2026-02-21 10:00:19 +03:00
1a79566731 Merge pull request #156 from orange-cpp/copilot/add-automatic-binary-attach
Add release workflow with binary uploads for all CI platforms
2026-02-21 09:31:12 +03:00
copilot-swe-agent[bot]
9183406e7a Add all CI pipeline platforms to release workflow (Linux x86, iOS, FreeBSD, Android, WebAssembly, MinGW)
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-21 04:55:36 +00:00
copilot-swe-agent[bot]
b592af91a9 Add release workflow to automatically attach binaries to new releases
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-21 04:45:20 +00:00
copilot-swe-agent[bot]
af9c043e95 Initial plan 2026-02-21 04:41:46 +00:00
2aaa8633b9 updated tag 2026-02-20 09:07:33 +03:00
0d1cc2088f Merge pull request #155 from orange-cpp/feaute/examples-folders
retructurized examples
2026-02-20 08:55:20 +03:00
260b3b97f9 fix 2026-02-20 08:53:03 +03:00
e0904690e6 fixed formating 2026-02-20 08:52:26 +03:00
7021d9d365 fixed formating 2026-02-20 08:46:43 +03:00
862d52593a retructurized examples 2026-02-20 07:48:19 +03:00
97a3111791 fixed code style 2026-02-20 04:02:26 +03:00
a9bec00973 Merge pull request #154 from luadebug/example1
Example with opengl triangle
2026-02-20 03:37:58 +03:00
Saikari
66debb46fa Refactor to use Color class and Triangle structure for better clarity in drawing functions 2026-02-20 03:01:34 +03:00
Saikari
b8323d3bc0 simplify complexity 2026-02-20 02:56:27 +03:00
Saikari
f363fa6f1a Fix text 2026-02-20 02:53:20 +03:00
Saikari
0546272493 Use dot not quad 2026-02-20 02:50:55 +03:00
Saikari
58801bfab3 Add one more example 2026-02-20 02:43:27 +03:00
4567cfa318 updated read me 2026-02-19 12:03:19 +03:00
1db9340dbf updated read me 2026-02-19 08:42:58 +03:00
7b9a181b0e Merge pull request #153 from orange-cpp/feature/cryengine-support
Feature/cryengine support
2026-02-19 08:34:33 +03:00
0876af3c14 added warning back 2026-02-19 08:34:21 +03:00
e46a369ddc added more tests 2026-02-19 08:06:34 +03:00
630ffa69f6 clang format 2026-02-19 08:00:23 +03:00
609970cdfe fix 2026-02-19 07:57:04 +03:00
e935155022 improved tests 2026-02-19 07:47:23 +03:00
800082e4b3 added mesh 2026-02-19 06:35:08 +03:00
02d909f77d added mesh trait 2026-02-19 01:06:08 +03:00
49319a1c7c added more files 2026-02-19 01:05:09 +03:00
d0f3e5059a added formulas 2026-02-19 01:05:09 +03:00
c9874f30d0 add 2026-02-19 01:05:09 +03:00
3080faeaa9 hotfix: projectile prediction 2026-02-19 01:01:38 +03:00
af36c909a4 improved simple game 2026-02-18 07:38:04 +03:00
1041256fac Revert "removed useless include"
This reverts commit 88012e2524.
2026-02-18 06:07:27 +03:00
88012e2524 removed useless include 2026-02-18 05:53:16 +03:00
4561dbe0e7 fix 2026-02-18 05:51:46 +03:00
b50f759675 more template stuff 2026-02-18 05:51:18 +03:00
85b672fdf3 Merge pull request #152 from orange-cpp/feature/camera_improvement
added more methods
2026-02-17 07:57:35 +03:00
360734f252 added more methods 2026-02-17 07:39:45 +03:00
1e215f3ff5 added more lfs file extensions 2026-02-15 16:43:08 +03:00
ca5f8cd328 moved another file to lfs 2026-02-15 15:49:51 +03:00
5e40a09b2e Merge pull request #151 from orange-cpp/feature/lfs
update
2026-02-15 15:38:19 +03:00
87e291d3b9 update 2026-02-15 15:35:58 +03:00
af44c918a2 removed psd file 2026-02-15 15:24:10 +03:00
0774a27d20 added site doc folder to gitignore 2026-02-14 20:55:48 +03:00
e212f66b85 added vscode config 2026-02-12 23:13:37 +03:00
6d0109edbc fix 2026-02-08 20:57:10 +03:00
58f1045e88 Merge pull request #148 from orange-cpp/feature/engine-units-to-metric
Feature/engine units to metric
2026-02-08 03:28:54 +03:00
fbd2543644 added tests 2026-02-08 03:15:21 +03:00
815b42159a added frostbite tests 2026-02-08 03:03:23 +03:00
dfc564b30d added for other engines 2026-02-08 02:58:59 +03:00
58a592a806 improved naming 2026-02-08 02:51:48 +03:00
4c92dbe1ed added for source 2026-02-08 02:43:10 +03:00
0145e0455c Merge pull request #146 from orange-cpp/feature/line_tracer_template
improvement
2026-02-06 00:14:15 +03:00
e91a2d7853 fixed warnings 2026-02-06 00:02:00 +03:00
9dc9b2cca2 added noexcept 2026-02-05 23:45:41 +03:00
268eff0f3f simplified shit 2026-02-05 23:43:17 +03:00
092b23993b added constexpr 2026-02-05 23:38:51 +03:00
1c844fe488 removed uselss c++ file 2026-02-05 23:31:14 +03:00
6490bf13ed improvement 2026-02-05 23:27:31 +03:00
e328f58a8e Merge pull request #145 from orange-cpp/feature/macho_improvement
Feature/macho improvement
2026-02-04 19:27:44 +03:00
41d09c5699 fixed bug 2026-02-04 19:10:06 +03:00
d14e77883a removed nesting 2026-02-04 18:35:04 +03:00
ca39dc20c1 removed nesting 2026-02-04 18:33:05 +03:00
02810fb0f3 fixed codestyle 2026-02-04 18:30:45 +03:00
3aa1e23025 added resharper ignore segment 2026-02-04 18:29:55 +03:00
Copilot
53fdff64e2 Add Mach-O pattern scanner (#144)
* Initial plan

* Add Mach-O pattern scanner implementation and unit tests

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

* Add Mach-O pattern scanner

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

* Remove CodeQL build artifacts from PR

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-04 17:30:20 +03:00
9a1a622dad Merge pull request #143 from luadebug/opengl
fix `pixi run examples` for linux
2026-01-30 16:00:50 +03:00
Saikari
4630bd6dcf do similiar for aarch64 linux 2026-01-30 05:13:27 +03:00
Saikari
7d25101344 add pixi.lock to gitignore 2026-01-30 05:06:14 +03:00
Saikari
e48532bcb9 fix pixi run examples for linux 2026-01-30 05:04:50 +03:00
Saikari
32b3113556 Add initial project configuration and build scripts for omath library
- Created pixi.toml for project metadata and dependencies management.
- Added formatting script (fmt.cmake) to ensure consistent CMake file formatting.
- Implemented benchmark execution script (run.benchmark.cmake) to run benchmark tests.
- Developed example execution script (run.examples.cmake) to run example applications.
- Created unit test execution script (run.unit.tests.cmake) to run unit tests.

removed lock file
2026-01-30 02:36:34 +03:00
7bff17e46c updated tag 2026-01-29 20:09:36 +03:00
e12c6b1824 removed unity build completely 2026-01-29 20:06:18 +03:00
85 changed files with 5466 additions and 517 deletions

25
.cmake-format Normal file
View File

@@ -0,0 +1,25 @@
format:
line_width: 100
tab_size: 4
use_tabchars: false
max_subgroups_hwrap: 3
max_pargs_hwrap: 5
separate_ctrl_name_with_space: false
separate_fn_name_with_space: false
dangle_parens: false
dangle_align: child
line_ending: unix
command_case: canonical
keyword_case: upper
enable_sort: true
autosort: true
markup:
bullet_char: "*"
enum_char: .
enable_markup: false
additional_commands:
target_link_libraries:
kwargs:
PUBLIC: "*"
SHARED: "*"
PRIVATE: "*"

95
.gitattributes vendored
View File

@@ -1,5 +1,98 @@
# SCM syntax highlighting & preventing 3-way merges
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
# Images
*.png filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text *.jpeg filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
*.bmp filter=lfs diff=lfs merge=lfs -text
*.heic filter=lfs diff=lfs merge=lfs -text
*.heif filter=lfs diff=lfs merge=lfs -text
*.avif filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.xcf filter=lfs diff=lfs merge=lfs -text
*.kra filter=lfs diff=lfs merge=lfs -text
*.sketch filter=lfs diff=lfs merge=lfs -text
# Video
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
*.m4v filter=lfs diff=lfs merge=lfs -text
*.mkv filter=lfs diff=lfs merge=lfs -text
*.avi filter=lfs diff=lfs merge=lfs -text
*.wmv filter=lfs diff=lfs merge=lfs -text
*.flv filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
*.mpeg filter=lfs diff=lfs merge=lfs -text
*.mpg filter=lfs diff=lfs merge=lfs -text
# Audio
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.flac filter=lfs diff=lfs merge=lfs -text
*.aac filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.m4a filter=lfs diff=lfs merge=lfs -text
# 3D / CAD / DCC
*.blend filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text
*.obj filter=lfs diff=lfs merge=lfs -text
*.stl filter=lfs diff=lfs merge=lfs -text
*.glb filter=lfs diff=lfs merge=lfs -text
*.gltf filter=lfs diff=lfs merge=lfs -text
*.usd filter=lfs diff=lfs merge=lfs -text
*.usdz filter=lfs diff=lfs merge=lfs -text
*.max filter=lfs diff=lfs merge=lfs -text
*.c4d filter=lfs diff=lfs merge=lfs -text
*.dwg filter=lfs diff=lfs merge=lfs -text
*.dxf filter=lfs diff=lfs merge=lfs -text
*.step filter=lfs diff=lfs merge=lfs -text
*.stp filter=lfs diff=lfs merge=lfs -text
*.iges filter=lfs diff=lfs merge=lfs -text
*.igs filter=lfs diff=lfs merge=lfs -text
# Documents / publishing binaries
*.pdf filter=lfs diff=lfs merge=lfs -text
*.indd filter=lfs diff=lfs merge=lfs -text
*.idml filter=lfs diff=lfs merge=lfs -text
*.ppt filter=lfs diff=lfs merge=lfs -text
*.pptx filter=lfs diff=lfs merge=lfs -text
*.key filter=lfs diff=lfs merge=lfs -text
*.doc filter=lfs diff=lfs merge=lfs -text
*.docx filter=lfs diff=lfs merge=lfs -text
# Archives / packaged artifacts
*.zip filter=lfs diff=lfs merge=lfs -text
*.7z filter=lfs diff=lfs merge=lfs -text
*.rar filter=lfs diff=lfs merge=lfs -text
*.tar filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
# Fonts
*.ttf filter=lfs diff=lfs merge=lfs -text
*.otf filter=lfs diff=lfs merge=lfs -text
*.woff filter=lfs diff=lfs merge=lfs -text
*.woff2 filter=lfs diff=lfs merge=lfs -text
# ML / data artifacts
*.onnx filter=lfs diff=lfs merge=lfs -text
*.pt filter=lfs diff=lfs merge=lfs -text
*.pth filter=lfs diff=lfs merge=lfs -text
*.ckpt filter=lfs diff=lfs merge=lfs -text
*.pb filter=lfs diff=lfs merge=lfs -text
*.tflite filter=lfs diff=lfs merge=lfs -text
*.h5 filter=lfs diff=lfs merge=lfs -text
*.hdf5 filter=lfs diff=lfs merge=lfs -text
*.parquet filter=lfs diff=lfs merge=lfs -text
*.feather filter=lfs diff=lfs merge=lfs -text
*.arrow filter=lfs diff=lfs merge=lfs -text
*.npy filter=lfs diff=lfs merge=lfs -text
*.npz filter=lfs diff=lfs merge=lfs -text
*.pickle filter=lfs diff=lfs merge=lfs -text
*.pkl filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text *.psd filter=lfs diff=lfs merge=lfs -text

4
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
# These are supported funding model platforms
open_collective: libomathorg
github: orange-cpp

BIN
.github/psd/omath.psd LFS vendored

Binary file not shown.

618
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,618 @@
name: Release
on:
release:
types: [published]
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
jobs:
##############################################################################
# 1) Linux Clang / Ninja
##############################################################################
linux-release:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- name: Linux (Clang) (x64-linux)
triplet: x64-linux
runner: ubuntu-latest
preset: linux-release-vcpkg
archive: omath-linux-x64
install_cmd: |
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
sudo add-apt-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main"
sudo apt-get update
sudo apt-get install -y git build-essential cmake ninja-build \
zip unzip curl pkg-config ca-certificates \
clang-21 lld-21 libc++-21-dev libc++abi-21-dev
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-21 100
- name: Linux (Clang) (x86-linux)
triplet: x86-linux
runner: ubuntu-latest
preset: linux-release-vcpkg-x86
archive: omath-linux-x86
install_cmd: |
# Add LLVM 21 repository
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
sudo add-apt-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main"
# Add GCC Toolchain PPA
sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu plucky main universe"
# Enable i386 architecture
sudo dpkg --add-architecture i386
sudo apt-get update
# Install Clang 21
sudo apt-get install -y git build-essential cmake ninja-build \
zip unzip curl pkg-config ca-certificates \
clang-21 lld-21 libc++-21-dev libc++abi-21-dev
sudo apt-get install -y -t plucky binutils
# Install GCC 15 with multilib support
sudo apt-get install -y gcc-15-multilib g++-15-multilib
# Set up alternatives for Clang
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-21 100
# Set up alternatives for GCC
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 100
- name: Linux (Clang) (arm64-linux)
triplet: arm64-linux
runner: ubuntu-24.04-arm
preset: linux-release-vcpkg-arm64
archive: omath-linux-arm64
install_cmd: |
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
sudo add-apt-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main"
sudo apt-get update
sudo apt-get install -y git build-essential cmake ninja-build \
zip unzip curl pkg-config ca-certificates \
clang-21 lld-21 libc++-21-dev libc++abi-21-dev
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-21 100
fail-fast: false
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Install basic tool-chain
shell: bash
run: ${{ matrix.install_cmd }}
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset ${{ matrix.preset }} \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
-DOMATH_BUILD_TESTS=OFF \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui;avx2"
- name: Build
shell: bash
run: cmake --build cmake-build/build/${{ matrix.preset }} --target omath
- name: Install
shell: bash
run: cmake --install cmake-build/build/${{ matrix.preset }} --prefix staging
- name: Package
shell: bash
run: tar -czf ${{ matrix.archive }}.tar.gz -C staging .
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh release upload "${{ github.event.release.tag_name }}" "${{ matrix.archive }}.tar.gz" --clobber
##############################################################################
# 2) Windows MSVC / Ninja
##############################################################################
windows-release:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- name: Windows (MSVC) (x64-windows)
runner: windows-latest
arch: amd64
preset: windows-release-vcpkg
triplet: x64-windows
archive: omath-windows-x64
- name: Windows (MSVC) (x86-windows)
runner: windows-latest
arch: amd64_x86
preset: windows-release-vcpkg-x86
triplet: x86-windows
archive: omath-windows-x86
- name: Windows (MSVC) (arm64-windows)
runner: windows-11-arm
arch: arm64
preset: windows-release-vcpkg-arm64
triplet: arm64-windows
archive: omath-windows-arm64
fail-fast: false
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v4
- name: Set up MSVC developer command-prompt
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.arch }}
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset ${{ matrix.preset }} \
-DOMATH_BUILD_TESTS=OFF \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui;avx2"
- name: Build
shell: bash
run: cmake --build cmake-build/build/${{ matrix.preset }} --target omath
- name: Install
shell: bash
run: cmake --install cmake-build/build/${{ matrix.preset }} --prefix staging
- name: Package
shell: pwsh
run: Compress-Archive -Path staging\* -DestinationPath "${{ matrix.archive }}.zip"
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh release upload "${{ github.event.release.tag_name }}" "${{ matrix.archive }}.zip" --clobber
##############################################################################
# 3) macOS AppleClang / Ninja
##############################################################################
macos-release:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- name: macOS (AppleClang) (arm64-osx)
runner: macos-latest
preset: darwin-release-vcpkg
triplet: arm64-osx
archive: omath-macos-arm64
- name: macOS (AppleClang) (x64-osx)
runner: macos-15-intel
preset: darwin-release-vcpkg-x64
triplet: x64-osx
archive: omath-macos-x64
fail-fast: false
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Install basic tool-chain with Homebrew
shell: bash
run: |
brew install cmake ninja
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset ${{ matrix.preset }} \
-DOMATH_BUILD_TESTS=OFF \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui;avx2"
- name: Build
shell: bash
run: cmake --build cmake-build/build/${{ matrix.preset }} --target omath
- name: Install
shell: bash
run: cmake --install cmake-build/build/${{ matrix.preset }} --prefix staging
- name: Package
shell: bash
run: tar -czf ${{ matrix.archive }}.tar.gz -C staging .
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh release upload "${{ github.event.release.tag_name }}" "${{ matrix.archive }}.tar.gz" --clobber
##############################################################################
# 4) iOS AppleClang / Xcode / arm64-ios
##############################################################################
ios-release:
name: iOS (AppleClang) (${{ matrix.triplet }})
runs-on: macOS-latest
strategy:
matrix:
include:
- triplet: arm64-ios
preset: ios-release-vcpkg
archive: omath-ios-arm64
fail-fast: false
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Install CMake tooling
shell: bash
run: |
brew install cmake ninja
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset ${{ matrix.preset }} \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
-DOMATH_BUILD_TESTS=OFF \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui"
- name: Build
shell: bash
run: |
cmake --build cmake-build/build/${{ matrix.preset }} --config Release --target omath
- name: Install
shell: bash
run: cmake --install cmake-build/build/${{ matrix.preset }} --prefix staging
- name: Package
shell: bash
run: tar -czf ${{ matrix.archive }}.tar.gz -C staging .
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh release upload "${{ github.event.release.tag_name }}" "${{ matrix.archive }}.tar.gz" --clobber
##############################################################################
# 5) FreeBSD Clang / Ninja
##############################################################################
freebsd-release:
name: FreeBSD (Clang) (${{ matrix.triplet }})
runs-on: ubuntu-latest
strategy:
matrix:
include:
- triplet: x64-freebsd
preset: freebsd-release-vcpkg
arch: x86-64
archive: omath-freebsd-x64
fail-fast: false
env:
VCPKG_ROOT: ${{ github.workspace }}/tmp/vcpkg
steps:
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build and Package
uses: cross-platform-actions/action@v0.31.0
with:
operating_system: freebsd
architecture: ${{ matrix.arch }}
version: '15.0'
memory: '12G'
cpu_count: 4
run: |
sudo pkg install -y git curl zip unzip gmake llvm gsed bash perl5 openssl 7-zip coreutils cmake ninja pkgconf patchelf
git config --global --add safe.directory `pwd`
# Build vcpkg in /tmp to avoid sshfs timestamp sync issues
export VCPKG_ROOT=/tmp/vcpkg
rm -rf "$VCPKG_ROOT"
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
cd -
export VCPKG_FORCE_SYSTEM_BINARIES=0
cmake --preset ${{ matrix.preset }} \
-DOMATH_BUILD_TESTS=OFF \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui;avx2" \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported"
cmake --build cmake-build/build/${{ matrix.preset }} --target omath
cmake --install cmake-build/build/${{ matrix.preset }} --prefix staging
tar -czf ${{ matrix.archive }}.tar.gz -C staging .
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh release upload "${{ github.event.release.tag_name }}" "${{ matrix.archive }}.tar.gz" --clobber
##############################################################################
# 6) Android NDK Clang / Ninja
##############################################################################
android-release:
name: Android NDK (${{ matrix.triplet }})
runs-on: ubuntu-latest
strategy:
matrix:
include:
- triplet: arm-neon-android
preset: android-arm-neon-release-vcpkg
archive: omath-android-arm-neon
- triplet: arm64-android
preset: android-arm64-release-vcpkg
archive: omath-android-arm64
- triplet: x64-android
preset: android-x64-release-vcpkg
archive: omath-android-x64
- triplet: x86-android
preset: android-x86-release-vcpkg
archive: omath-android-x86
fail-fast: false
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
ANDROID_NDK_HOME: ${{ github.workspace }}/android-ndk
steps:
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Android NDK
shell: bash
run: |
NDK_VERSION="r28b"
NDK_ZIP="android-ndk-${NDK_VERSION}-linux.zip"
wget -q "https://dl.google.com/android/repository/${NDK_ZIP}"
unzip -q "${NDK_ZIP}" -d "${{ github.workspace }}"
mv "${{ github.workspace }}/android-ndk-${NDK_VERSION}" "$ANDROID_NDK_HOME"
rm "${NDK_ZIP}"
echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}" >> $GITHUB_ENV
- name: Install basic tool-chain
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ninja-build cmake
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset ${{ matrix.preset }} \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
-DOMATH_BUILD_TESTS=OFF \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui"
- name: Build
shell: bash
run: |
cmake --build cmake-build/build/${{ matrix.preset }} --target omath
- name: Install
shell: bash
run: cmake --install cmake-build/build/${{ matrix.preset }} --prefix staging
- name: Package
shell: bash
run: tar -czf ${{ matrix.archive }}.tar.gz -C staging .
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh release upload "${{ github.event.release.tag_name }}" "${{ matrix.archive }}.tar.gz" --clobber
##############################################################################
# 7) WebAssembly (Emscripten) Clang / Ninja / wasm32-emscripten
##############################################################################
wasm-release:
name: WebAssembly (Emscripten) (${{ matrix.triplet }})
runs-on: ubuntu-latest
strategy:
matrix:
include:
- triplet: wasm32-emscripten
preset: wasm-release-vcpkg
archive: omath-wasm32
fail-fast: false
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install basic tool-chain
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ninja-build
- name: Setup Emscripten
uses: mymindstorm/setup-emsdk@v14
with:
version: 'latest'
- name: Verify Emscripten
shell: bash
run: |
echo "EMSDK=$EMSDK"
emcc --version
ls -la "$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset ${{ matrix.preset }} \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
-DOMATH_BUILD_TESTS=OFF \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui"
- name: Build
shell: bash
run: |
cmake --build cmake-build/build/${{ matrix.preset }} --target omath
- name: Install
shell: bash
run: cmake --install cmake-build/build/${{ matrix.preset }} --prefix staging
- name: Package
shell: bash
run: tar -czf ${{ matrix.archive }}.tar.gz -C staging .
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh release upload "${{ github.event.release.tag_name }}" "${{ matrix.archive }}.tar.gz" --clobber
##############################################################################
# 8) Windows MSYS2 MinGW GCC / Ninja
##############################################################################
mingw-release:
name: ${{ matrix.name }}
runs-on: windows-latest
strategy:
matrix:
include:
- name: MINGW64 (MSYS2) (x64-mingw-dynamic)
msystem: MINGW64
pkg_prefix: mingw-w64-x86_64
preset: mingw-release-vcpkg
archive: omath-mingw64-x64
- name: UCRT64 (MSYS2) (x64-mingw-dynamic)
msystem: UCRT64
pkg_prefix: mingw-w64-ucrt-x86_64
preset: mingw-release-vcpkg
archive: omath-ucrt64-x64
- name: MINGW32 (MSYS2) (x86-mingw-dynamic)
msystem: MINGW32
pkg_prefix: mingw-w64-i686
preset: mingw32-release-vcpkg
archive: omath-mingw32-x86
fail-fast: false
defaults:
run:
shell: msys2 {0}
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.msystem }}
update: true
install: >-
${{ matrix.pkg_prefix }}-toolchain
${{ matrix.pkg_prefix }}-cmake
${{ matrix.pkg_prefix }}-ninja
${{ matrix.pkg_prefix }}-pkg-config
git
base-devel
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up vcpkg
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
run: |
cmake --preset ${{ matrix.preset }} \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
-DOMATH_BUILD_TESTS=OFF \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui"
- name: Build
run: |
cmake --build cmake-build/build/${{ matrix.preset }} --target omath
- name: Install
run: cmake --install cmake-build/build/${{ matrix.preset }} --prefix staging
- name: Package
shell: pwsh
run: Compress-Archive -Path staging\* -DestinationPath "${{ matrix.archive }}.zip"
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh release upload "${{ github.event.release.tag_name }}" "${{ matrix.archive }}.zip" --clobber

7
.gitignore vendored
View File

@@ -6,3 +6,10 @@
/build/ /build/
/clang-coverage/ /clang-coverage/
*.gcov *.gcov
*.bin
# pixi lock
pixi.lock
# pixi environments
.pixi/*
!.pixi/config.toml
/site/

5
.idea/editor.xml generated
View File

@@ -17,7 +17,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="HINT" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
@@ -56,6 +56,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
@@ -109,7 +110,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />

24
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": " Unit Tests (Debug)",
"type": "lldb",
"request": "launch",
"program": "${workspaceRoot}/out/Debug/unit_tests",
"args": [],
"cwd": "${workspaceRoot}"
},
{
"name": " Unit Tests (Release)",
"type": "lldb",
"request": "launch",
"program": "${workspaceRoot}/out/Release/unit_tests",
"args": [],
"cwd": "${workspaceRoot}"
}
]
}

View File

@@ -9,49 +9,56 @@ include(CheckCXXCompilerFlag)
include(cmake/Coverage.cmake) include(cmake/Coverage.cmake)
include(cmake/Valgrind.cmake) include(cmake/Valgrind.cmake)
if (MSVC) if(MSVC)
check_cxx_compiler_flag("/arch:AVX2" COMPILER_SUPPORTS_AVX2) check_cxx_compiler_flag("/arch:AVX2" COMPILER_SUPPORTS_AVX2)
else () else()
check_cxx_compiler_flag("-mavx2" COMPILER_SUPPORTS_AVX2) check_cxx_compiler_flag("-mavx2" COMPILER_SUPPORTS_AVX2)
endif () endif()
option(OMATH_BUILD_TESTS "Build unit tests" OFF) option(OMATH_BUILD_TESTS "Build unit tests" OFF)
option(OMATH_BUILD_BENCHMARK "Build benchmarks" OFF) option(OMATH_BUILD_BENCHMARK "Build benchmarks" OFF)
option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_THREAT_WARNING_AS_ERROR
"Set highest level of warnings and force compiler to treat them as errors" ON)
option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF)
option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ${COMPILER_SUPPORTS_AVX2}) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ${COMPILER_SUPPORTS_AVX2})
option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF) option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF)
option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF) option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF)
option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF)
option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON) option(OMATH_ENABLE_LEGACY
option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" OFF) "Will enable legacy classes that MUST be used ONLY for backward compatibility" ON)
option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" ON) option(OMATH_SUPRESS_SAFETY_CHECKS
"Supress some safety checks in release build to improve general performance" ON)
option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF) option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF)
option(OMATH_ENABLE_FORCE_INLINE "Will for compiler to make some functions to be force inlined no matter what" ON) option(OMATH_ENABLE_FORCE_INLINE
if (VCPKG_MANIFEST_FEATURES) "Will for compiler to make some functions to be force inlined no matter what" ON)
foreach (omath_feature IN LISTS VCPKG_MANIFEST_FEATURES) if(VCPKG_MANIFEST_FEATURES)
if (omath_feature STREQUAL "imgui") foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
if(omath_feature STREQUAL "imgui")
set(OMATH_IMGUI_INTEGRATION ON) set(OMATH_IMGUI_INTEGRATION ON)
elseif (omath_feature STREQUAL "avx2") elseif(omath_feature STREQUAL "avx2")
set(OMATH_USE_AVX2 ${COMPILER_SUPPORTS_AVX2}) set(OMATH_USE_AVX2 ${COMPILER_SUPPORTS_AVX2})
elseif (omath_feature STREQUAL "tests") elseif(omath_feature STREQUAL "tests")
set(OMATH_BUILD_TESTS ON) set(OMATH_BUILD_TESTS ON)
elseif (omath_feature STREQUAL "benchmark") elseif(omath_feature STREQUAL "benchmark")
set(OMATH_BUILD_BENCHMARK ON) set(OMATH_BUILD_BENCHMARK ON)
elseif (omath_feature STREQUAL "examples") elseif(omath_feature STREQUAL "examples")
set(OMATH_BUILD_EXAMPLES ON) set(OMATH_BUILD_EXAMPLES ON)
endif () endif()
endforeach () endforeach()
endif () endif()
if (OMATH_USE_AVX2 AND NOT COMPILER_SUPPORTS_AVX2) if(OMATH_USE_AVX2 AND NOT COMPILER_SUPPORTS_AVX2)
message(WARNING "OMATH_USE_AVX2 requested, but compiler/target does not support AVX2. Disabling.") message(
WARNING "OMATH_USE_AVX2 requested, but compiler/target does not support AVX2. Disabling.")
set(OMATH_USE_AVX2 OFF CACHE BOOL "Omath will use AVX2 to boost performance" FORCE) set(OMATH_USE_AVX2 OFF CACHE BOOL "Omath will use AVX2 to boost performance" FORCE)
endif () endif()
if (${PROJECT_IS_TOP_LEVEL}) if(${PROJECT_IS_TOP_LEVEL})
message(STATUS "[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}, compiler ${CMAKE_CXX_COMPILER_ID}") message(
STATUS
"[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}, compiler ${CMAKE_CXX_COMPILER_ID}"
)
message(STATUS "[${PROJECT_NAME}]: Warnings as errors ${OMATH_THREAT_WARNING_AS_ERROR}") message(STATUS "[${PROJECT_NAME}]: Warnings as errors ${OMATH_THREAT_WARNING_AS_ERROR}")
message(STATUS "[${PROJECT_NAME}]: Build unit tests ${OMATH_BUILD_TESTS}") message(STATUS "[${PROJECT_NAME}]: Build unit tests ${OMATH_BUILD_TESTS}")
message(STATUS "[${PROJECT_NAME}]: Build benchmark ${OMATH_BUILD_BENCHMARK}") message(STATUS "[${PROJECT_NAME}]: Build benchmark ${OMATH_BUILD_BENCHMARK}")
@@ -65,84 +72,78 @@ if (${PROJECT_IS_TOP_LEVEL})
message(STATUS "[${PROJECT_NAME}]: Building using vcpkg ${OMATH_BUILD_VIA_VCPKG}") message(STATUS "[${PROJECT_NAME}]: Building using vcpkg ${OMATH_BUILD_VIA_VCPKG}")
message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}") message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}")
message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}") message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}")
endif () endif()
file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
file(GLOB_RECURSE OMATH_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp") file(GLOB_RECURSE OMATH_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
if (OMATH_BUILD_AS_SHARED_LIBRARY) if(OMATH_BUILD_AS_SHARED_LIBRARY)
add_library(${PROJECT_NAME} SHARED ${OMATH_SOURCES} ${OMATH_HEADERS}) add_library(${PROJECT_NAME} SHARED ${OMATH_SOURCES} ${OMATH_HEADERS})
else () else()
add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS}) add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS})
endif () endif()
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}") target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}")
if (OMATH_IMGUI_INTEGRATION) if(OMATH_IMGUI_INTEGRATION)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_IMGUI_INTEGRATION) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_IMGUI_INTEGRATION)
# IMGUI is being linked as submodule # IMGUI is being linked as submodule
if (TARGET imgui) if(TARGET imgui)
target_link_libraries(${PROJECT_NAME} PUBLIC imgui) target_link_libraries(${PROJECT_NAME} PUBLIC imgui)
install(TARGETS imgui install(
TARGETS imgui
EXPORT omathTargets EXPORT omathTargets
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
RUNTIME DESTINATION bin) RUNTIME DESTINATION bin)
else () else()
# Assume that IMGUI linked via VCPKG. # Assume that IMGUI linked via VCPKG.
find_package(imgui CONFIG REQUIRED) find_package(imgui CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC imgui::imgui) target_link_libraries(${PROJECT_NAME} PUBLIC imgui::imgui)
endif () endif()
endif () endif()
if (OMATH_USE_AVX2) if(OMATH_USE_AVX2)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_USE_AVX2) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_USE_AVX2)
endif () endif()
if (OMATH_SUPRESS_SAFETY_CHECKS) if(OMATH_SUPRESS_SAFETY_CHECKS)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_SUPRESS_SAFETY_CHECKS) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_SUPRESS_SAFETY_CHECKS)
endif () endif()
if (OMATH_ENABLE_LEGACY) if(OMATH_ENABLE_LEGACY)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY)
endif () endif()
if (OMATH_ENABLE_FORCE_INLINE) if(OMATH_ENABLE_FORCE_INLINE)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_FORCE_INLINE) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_FORCE_INLINE)
endif () endif()
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ${PROJECT_NAME}
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
CXX_STANDARD 23 CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON) CXX_STANDARD_REQUIRED ON)
if (OMATH_USE_UNITY_BUILD) if(OMATH_STATIC_MSVC_RUNTIME_LIBRARY)
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY
UNITY_BUILD ON "MultiThreaded$<$<CONFIG:Debug>:Debug>")
UNITY_BUILD_BATCH_SIZE 20) endif()
endif ()
if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY) if(OMATH_USE_AVX2)
set_target_properties(${PROJECT_NAME} PROPERTIES if(MSVC)
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
)
endif ()
if (OMATH_USE_AVX2)
if (MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC /arch:AVX2) target_compile_options(${PROJECT_NAME} PUBLIC /arch:AVX2)
elseif (EMSCRIPTEN) elseif(EMSCRIPTEN)
target_compile_options(${PROJECT_NAME} PUBLIC -msimd128 -mavx2) target_compile_options(${PROJECT_NAME} PUBLIC -msimd128 -mavx2)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(${PROJECT_NAME} PUBLIC -mfma -mavx2) target_compile_options(${PROJECT_NAME} PUBLIC -mfma -mavx2)
endif () endif()
endif () endif()
if(EMSCRIPTEN) if(EMSCRIPTEN)
target_compile_options(${PROJECT_NAME} PUBLIC -fexceptions) target_compile_options(${PROJECT_NAME} PUBLIC -fexceptions)
@@ -151,79 +152,79 @@ endif()
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
if(OMATH_BUILD_TESTS)
if (OMATH_BUILD_TESTS)
add_subdirectory(tests) add_subdirectory(tests)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS)
if(OMATH_ENABLE_COVERAGE) if(OMATH_ENABLE_COVERAGE)
omath_setup_coverage(${PROJECT_NAME}) omath_setup_coverage(${PROJECT_NAME})
endif() endif()
endif () endif()
if (OMATH_BUILD_BENCHMARK) if(OMATH_BUILD_BENCHMARK)
add_subdirectory(benchmark) add_subdirectory(benchmark)
endif () endif()
if (OMATH_BUILD_EXAMPLES) if(OMATH_BUILD_EXAMPLES)
add_subdirectory(examples) add_subdirectory(examples)
endif () endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR)
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX) target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX)
elseif (OMATH_THREAT_WARNING_AS_ERROR) elseif(OMATH_THREAT_WARNING_AS_ERROR)
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror) target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror)
endif () endif()
# Windows SDK redefine min/max via preprocessor and break std::min and std::max # Windows SDK redefine min/max via preprocessor and break std::min and std::max
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_definitions(${PROJECT_NAME} INTERFACE NOMINMAX) target_compile_definitions(${PROJECT_NAME} INTERFACE NOMINMAX)
endif () endif()
target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # Use this path when building the project
$<INSTALL_INTERFACE:include> # Use this path when the project is installed
)
target_include_directories(
${PROJECT_NAME}
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # Use this path
# when building
# the project
$<INSTALL_INTERFACE:include> # Use this path when the project is
# installed
)
# Installation rules # Installation rules
# Install the library # Install the library
install(TARGETS ${PROJECT_NAME} install(
TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets EXPORT ${PROJECT_NAME}Targets
ARCHIVE DESTINATION lib COMPONENT ${PROJECT_NAME} # For static libraries ARCHIVE DESTINATION lib COMPONENT ${PROJECT_NAME} # For static libraries
LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries
RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on Windows) RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on
) # Windows)
)
# Install headers as part of omath_component # Install headers as part of omath_component
install(DIRECTORY include/ DESTINATION include COMPONENT ${PROJECT_NAME}) install(DIRECTORY include/ DESTINATION include COMPONENT ${PROJECT_NAME})
# Export omath target for CMake find_package support, also under omath_component # Export omath target for CMake find_package support, also under omath_component
install(EXPORT ${PROJECT_NAME}Targets install(
EXPORT ${PROJECT_NAME}Targets
FILE ${PROJECT_NAME}Targets.cmake FILE ${PROJECT_NAME}Targets.cmake
NAMESPACE ${PROJECT_NAME}:: NAMESPACE ${PROJECT_NAME}::
DESTINATION lib/cmake/${PROJECT_NAME} COMPONENT ${PROJECT_NAME} DESTINATION lib/cmake/${PROJECT_NAME}
) COMPONENT ${PROJECT_NAME})
# Generate the omathConfigVersion.cmake file # Generate the omathConfigVersion.cmake file
write_basic_package_version_file( write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion)
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
# Generate the omathConfig.cmake file from the template (which is in the cmake/ folder) # Generate the omathConfig.cmake file from the template (which is in the cmake/
# folder)
configure_package_config_file( configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/omathConfig.cmake.in" # Path to the .in file "${CMAKE_CURRENT_SOURCE_DIR}/cmake/omathConfig.cmake.in" # Path to the .in
"${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" # Output path for the generated file # file
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME} "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" # Output path for the
) # generated file
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME})
# Install the generated config files # Install the generated config files
install(FILES install(FILES "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake" "${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
DESTINATION lib/cmake/${PROJECT_NAME} DESTINATION lib/cmake/${PROJECT_NAME})
)

37
CODEOWNERS Normal file
View File

@@ -0,0 +1,37 @@
## List of maintainers for the omath library
## This file purpose is to give newcomers to the project the responsible
## developers when submitting a pull request on GitHub, or opening a bug
## report in issues.
## This file will notably establish who is responsible for a specific
## area of omath. Being a maintainer means the following:
## - that person has good knownledge in the area
## - that person is able to enforce consistency in the area
## - that person may be available for giving help in the area
## - that person has push access on the repository
## Being a maintainer does not mean the following:
## - that person is dedicated to the area
## - that person is working full-time on the area/on omath
## - that person is paid
## - that person is always available
# omath core source code
/source @orange-cpp
/include @orange-cpp
# Tests and becnchmarks
/benchmark @orange-cpp
/tests @orange-cpp @luadebug
# Examples and documentation
/examples @luadebug @orange-cpp
/docs @orange-cpp
# Misc like formating
/scripts @luadebug
/pixi @luadebug
# CI/CD
/.github/workflows @luadbg @orange-cpp

View File

@@ -1,6 +1,6 @@
# 📥Installation Guide # 📥Installation Guide
## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg ## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg (recomended)
**Note**: Support vcpkg for package management **Note**: Support vcpkg for package management
1. Install [vcpkg](https://github.com/microsoft/vcpkg) 1. Install [vcpkg](https://github.com/microsoft/vcpkg)
2. Run the following command to install the orange-math package: 2. Run the following command to install the orange-math package:
@@ -28,6 +28,46 @@ target("...")
add_packages("omath") add_packages("omath")
``` ```
## <img width="28px" src="https://github.githubassets.com/favicons/favicon.svg" /> Using prebuilt binaries (GitHub Releases)
**Note**: This is the fastest option if you dont want to build from source.
1. **Go to the Releases page**
- Open the projects GitHub **Releases** page and choose the latest version.
2. **Download the correct asset for your platform**
- Pick the archive that matches your OS and architecture (for example: Windows x64 / Linux x64 / macOS arm64).
3. **Extract the archive**
- You should end up with something like:
- `include/` (headers)
- `lib/` or `bin/` (library files / DLLs)
- sometimes `cmake/` (CMake package config)
4. **Use it in your project**
### Option A: CMake package (recommended if the release includes CMake config files)
If the extracted folder contains something like `lib/cmake/omath` or `cmake/omath`, you can point CMake to it:
```cmake
# Example: set this to the extracted prebuilt folder
list(APPEND CMAKE_PREFIX_PATH "path/to/omath-prebuilt")
find_package(omath CONFIG REQUIRED)
target_link_libraries(main PRIVATE omath::omath)
```
### Option B: Manual include + link (works with any layout)
If theres no CMake package config, link it manually:
```cmake
target_include_directories(main PRIVATE "path/to/omath-prebuilt/include")
# Choose ONE depending on what you downloaded:
# - Static library: .lib / .a
# - Shared library: .dll + .lib import (Windows), .so (Linux), .dylib (macOS)
target_link_directories(main PRIVATE "path/to/omath-prebuilt/lib")
target_link_libraries(main PRIVATE omath) # or the actual library filename
```
## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake ## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake
1. **Preparation** 1. **Preparation**

View File

@@ -80,9 +80,10 @@ if (auto screen = camera.world_to_screen(world_position)) {
- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces. - **Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! - **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
- **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL**. - **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**.
- **Cross platform**: Supports Windows, MacOS and Linux. - **Cross platform**: Supports Windows, MacOS and Linux.
- **Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps. - **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps.
- **Battle tested**: It's already used by some big players on the market like wraith.su and bluedream.ltd
<div align = center> <div align = center>
# Gallery # Gallery

View File

@@ -1 +1 @@
4.6.0 5.0.0

View File

@@ -1,22 +1,23 @@
project(omath_benchmark) project(omath_benchmark)
file(GLOB_RECURSE OMATH_BENCHMARK_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") file(GLOB_RECURSE OMATH_BENCHMARK_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
add_executable(${PROJECT_NAME} ${OMATH_BENCHMARK_SOURCES}) add_executable(${PROJECT_NAME} ${OMATH_BENCHMARK_SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ${PROJECT_NAME}
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
CXX_STANDARD 23 CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON) CXX_STANDARD_REQUIRED ON)
if (TARGET benchmark::benchmark) # Benchmark is being linked as submodule if(TARGET benchmark::benchmark) # Benchmark is being linked as submodule
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath) target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath)
else() else()
find_package(benchmark CONFIG REQUIRED) # Benchmark is being linked as vcpkg package find_package(benchmark CONFIG REQUIRED) # Benchmark is being linked as vcpkg
# package
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath) target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath)
endif () endif()
if(OMATH_ENABLE_VALGRIND) if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(${PROJECT_NAME}) omath_setup_valgrind(${PROJECT_NAME})

View File

@@ -7,38 +7,23 @@ function(omath_setup_coverage TARGET_NAME)
endif() endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC)
target_compile_options(${TARGET_NAME} PRIVATE target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping
-fprofile-instr-generate /Zi)
-fcoverage-mapping target_link_options(
/Zi ${TARGET_NAME}
) PRIVATE
target_link_options(${TARGET_NAME} PRIVATE
-fprofile-instr-generate -fprofile-instr-generate
-fcoverage-mapping -fcoverage-mapping
/DEBUG:FULL /DEBUG:FULL
/INCREMENTAL:NO /INCREMENTAL:NO
/PROFILE /PROFILE)
)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
target_compile_options(${TARGET_NAME} PRIVATE target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping
-fprofile-instr-generate -g -O0)
-fcoverage-mapping target_link_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping)
-g
-O0
)
target_link_options(${TARGET_NAME} PRIVATE
-fprofile-instr-generate
-fcoverage-mapping
)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${TARGET_NAME} PRIVATE target_compile_options(${TARGET_NAME} PRIVATE --coverage -g -O0)
--coverage target_link_options(${TARGET_NAME} PRIVATE --coverage)
-g
-O0
)
target_link_options(${TARGET_NAME} PRIVATE
--coverage
)
endif() endif()
if(TARGET coverage) if(TARGET coverage)
@@ -47,40 +32,36 @@ function(omath_setup_coverage TARGET_NAME)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC)
message(STATUS "MSVC detected: Use VS Code Coverage from CI workflow") message(STATUS "MSVC detected: Use VS Code Coverage from CI workflow")
add_custom_target(coverage add_custom_target(
coverage
DEPENDS unit_tests DEPENDS unit_tests
COMMAND $<TARGET_FILE:unit_tests> COMMAND $<TARGET_FILE:unit_tests>
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Running tests for coverage (use VS Code Coverage from CI)" COMMENT "Running tests for coverage (use VS Code Coverage from CI)")
)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
add_custom_target(coverage add_custom_target(
coverage
DEPENDS unit_tests DEPENDS unit_tests
COMMAND bash "${CMAKE_SOURCE_DIR}/scripts/coverage-llvm.sh" COMMAND bash "${CMAKE_SOURCE_DIR}/scripts/coverage-llvm.sh" "${CMAKE_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" "$<TARGET_FILE:unit_tests>" "${CMAKE_BINARY_DIR}/coverage"
"${CMAKE_BINARY_DIR}"
"$<TARGET_FILE:unit_tests>"
"${CMAKE_BINARY_DIR}/coverage"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Running LLVM coverage" COMMENT "Running LLVM coverage")
)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
add_custom_target(coverage add_custom_target(
coverage
DEPENDS unit_tests DEPENDS unit_tests
COMMAND $<TARGET_FILE:unit_tests> || true COMMAND $<TARGET_FILE:unit_tests> || true
COMMAND lcov --capture --directory "${CMAKE_BINARY_DIR}" COMMAND lcov --capture --directory "${CMAKE_BINARY_DIR}" --output-file
--output-file "${CMAKE_BINARY_DIR}/coverage.info" "${CMAKE_BINARY_DIR}/coverage.info" --ignore-errors mismatch,gcov
--ignore-errors mismatch,gcov COMMAND
COMMAND lcov --remove "${CMAKE_BINARY_DIR}/coverage.info" lcov --remove "${CMAKE_BINARY_DIR}/coverage.info" "*/tests/*" "*/gtest/*"
"*/tests/*" "*/gtest/*" "*/googletest/*" "*/_deps/*" "/usr/*" "*/googletest/*" "*/_deps/*" "/usr/*" --output-file
--output-file "${CMAKE_BINARY_DIR}/coverage.info" "${CMAKE_BINARY_DIR}/coverage.info" --ignore-errors unused
--ignore-errors unused COMMAND genhtml "${CMAKE_BINARY_DIR}/coverage.info" --output-directory
COMMAND genhtml "${CMAKE_BINARY_DIR}/coverage.info" "${CMAKE_BINARY_DIR}/coverage"
--output-directory "${CMAKE_BINARY_DIR}/coverage"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Running lcov/genhtml" COMMENT "Running lcov/genhtml")
)
endif() endif()
endfunction() endfunction()

View File

@@ -22,23 +22,19 @@ function(omath_setup_valgrind TARGET_NAME)
return() return()
endif() endif()
set(VALGRIND_FLAGS set(VALGRIND_FLAGS --leak-check=full --show-leak-kinds=all --track-origins=yes
--leak-check=full --error-exitcode=99)
--show-leak-kinds=all
--track-origins=yes
--error-exitcode=99
)
set(VALGRIND_TARGET "valgrind_${TARGET_NAME}") set(VALGRIND_TARGET "valgrind_${TARGET_NAME}")
if(NOT TARGET ${VALGRIND_TARGET}) if(NOT TARGET ${VALGRIND_TARGET})
add_custom_target(${VALGRIND_TARGET} add_custom_target(
${VALGRIND_TARGET}
DEPENDS ${TARGET_NAME} DEPENDS ${TARGET_NAME}
COMMAND ${VALGRIND_EXECUTABLE} ${VALGRIND_FLAGS} $<TARGET_FILE:${TARGET_NAME}> COMMAND ${VALGRIND_EXECUTABLE} ${VALGRIND_FLAGS} $<TARGET_FILE:${TARGET_NAME}>
WORKING_DIRECTORY $<TARGET_FILE_DIR:${TARGET_NAME}> WORKING_DIRECTORY $<TARGET_FILE_DIR:${TARGET_NAME}>
COMMENT "Running Valgrind memory check on ${TARGET_NAME}..." COMMENT "Running Valgrind memory check on ${TARGET_NAME}..."
USES_TERMINAL USES_TERMINAL)
)
add_dependencies(valgrind_all ${VALGRIND_TARGET}) add_dependencies(valgrind_all ${VALGRIND_TARGET})
endif() endif()

View File

@@ -1,34 +1,7 @@
project(examples) add_subdirectory(example_barycentric)
add_subdirectory(example_glfw3)
add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp) add_subdirectory(example_proj_mat_builder)
set_target_properties(example_projection_matrix_builder PROPERTIES add_subdirectory(example_signature_scan)
CXX_STANDARD 26
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
)
target_link_libraries(example_projection_matrix_builder PRIVATE omath::omath)
add_executable(example_signature_scan example_signature_scan.cpp)
set_target_properties(example_signature_scan PROPERTIES
CXX_STANDARD 26
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
)
target_link_libraries(example_signature_scan PRIVATE omath::omath)
add_executable(example_glfw3 example_glfw3.cpp)
set_target_properties(example_glfw3 PROPERTIES CXX_STANDARD 26
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
)
find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw)
if(OMATH_ENABLE_VALGRIND) if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(example_projection_matrix_builder) omath_setup_valgrind(example_projection_matrix_builder)

View File

@@ -0,0 +1,14 @@
project(example_barycentric)
add_executable(${PROJECT_NAME} example_barycentric.cpp)
set_target_properties(
${PROJECT_NAME}
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(OpenGL)
find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath GLEW::GLEW glfw OpenGL::GL)

View File

@@ -0,0 +1,427 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <cmath>
#include <iostream>
#include <omath/omath.hpp>
#include <vector>
using omath::Color;
using omath::Triangle;
using omath::Vector3;
static const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in float aPointSize;
layout (location = 3) in float aIsLine;
out vec3 vColor;
out float vIsLine;
void main() {
gl_Position = vec4(aPos, 1.0);
vColor = aColor;
gl_PointSize = aPointSize;
vIsLine = aIsLine;
}
)";
static const char* fragmentShaderSource = R"(
#version 330 core
in vec3 vColor;
in float vIsLine;
out vec4 FragColor;
void main() {
if (vIsLine < 0.5) {
// Calculate distance from center of the point
vec2 coord = gl_PointCoord - vec2(0.5);
if(length(coord) > 0.5)
discard;
}
FragColor = vec4(vColor, 1.0);
}
)";
GLuint compileShader(GLenum type, const char* src)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, nullptr);
glCompileShader(shader);
GLint ok;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok)
{
char log[1024];
glGetShaderInfoLog(shader, sizeof(log), nullptr, log);
std::cerr << "Shader error: " << log << std::endl;
}
return shader;
}
void drawChar(char c, float x, float y, float scale, const Color& color, std::vector<float>& lines)
{
float w = 0.5f * scale;
float h = 1.0f * scale;
auto add = [&](float x1, float y1, float x2, float y2)
{
lines.push_back(x + x1 * w);
lines.push_back(y + y1 * h);
lines.push_back(0.0f);
lines.push_back(color.x);
lines.push_back(color.y);
lines.push_back(color.z);
lines.push_back(1.0f); // size
lines.push_back(1.0f); // isLine
lines.push_back(x + x2 * w);
lines.push_back(y + y2 * h);
lines.push_back(0.0f);
lines.push_back(color.x);
lines.push_back(color.y);
lines.push_back(color.z);
lines.push_back(1.0f); // size
lines.push_back(1.0f); // isLine
};
switch (c)
{
case '0':
add(0, 0, 1, 0);
add(1, 0, 1, 1);
add(1, 1, 0, 1);
add(0, 1, 0, 0);
break;
case '1':
add(0.5f, 0, 0.5f, 1);
add(0.25f, 0.75f, 0.5f, 1);
add(0.25f, 0, 0.75f, 0);
break;
case '2':
add(0, 1, 1, 1);
add(1, 1, 1, 0.5f);
add(1, 0.5f, 0, 0.5f);
add(0, 0.5f, 0, 0);
add(0, 0, 1, 0);
break;
case '3':
add(0, 1, 1, 1);
add(1, 1, 1, 0);
add(1, 0, 0, 0);
add(0, 0.5f, 1, 0.5f);
break;
case '4':
add(0, 1, 0, 0.5f);
add(0, 0.5f, 1, 0.5f);
add(1, 1, 1, 0);
break;
case '5':
add(1, 1, 0, 1);
add(0, 1, 0, 0.5f);
add(0, 0.5f, 1, 0.5f);
add(1, 0.5f, 1, 0);
add(1, 0, 0, 0);
break;
case '6':
add(1, 1, 0, 1);
add(0, 1, 0, 0);
add(0, 0, 1, 0);
add(1, 0, 1, 0.5f);
add(1, 0.5f, 0, 0.5f);
break;
case '7':
add(0, 1, 1, 1);
add(1, 1, 0.5f, 0);
break;
case '8':
add(0, 0, 1, 0);
add(1, 0, 1, 1);
add(1, 1, 0, 1);
add(0, 1, 0, 0);
add(0, 0.5f, 1, 0.5f);
break;
case '9':
add(1, 0.5f, 0, 0.5f);
add(0, 0.5f, 0, 1);
add(0, 1, 1, 1);
add(1, 1, 1, 0);
add(1, 0, 0, 0);
break;
case '.':
add(0.4f, 0, 0.6f, 0);
add(0.6f, 0, 0.6f, 0.2f);
add(0.6f, 0.2f, 0.4f, 0.2f);
add(0.4f, 0.2f, 0.4f, 0);
break;
}
}
void drawText(const std::string& text, float x, float y, float scale, const Color& color, std::vector<float>& lines)
{
float cursor = x;
for (char c : text)
{
drawChar(c, cursor, y, scale, color, lines);
cursor += (c == '.' ? 0.3f : 0.7f) * scale;
}
}
GLFWwindow* initWindow(int width, int height, const char* title)
{
if (!glfwInit())
{
std::cerr << "Failed to initialize GLFW\n";
return nullptr;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(width, height, title, NULL, NULL);
if (!window)
{
std::cerr << "Failed to create GLFW window\n";
glfwTerminate();
return nullptr;
}
glfwMakeContextCurrent(window);
// Check if context is valid using standard GL
const GLubyte* renderer = glGetString(GL_RENDERER);
const GLubyte* version = glGetString(GL_VERSION);
if (renderer && version)
{
std::cout << "Renderer: " << renderer << "\n";
std::cout << "OpenGL version supported: " << version << "\n";
}
else
{
std::cerr << "Failed to get GL_RENDERER or GL_VERSION. Context might be invalid.\n";
}
glewExperimental = GL_TRUE;
GLenum glewErr = glewInit();
if (glewErr != GLEW_OK)
{
// Ignore GLEW_ERROR_NO_GLX_DISPLAY if we have a valid context (e.g. Wayland)
if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY && renderer)
{
std::cerr << "GLEW warning: " << glewGetErrorString(glewErr) << " (Ignored because context seems valid)\n";
}
else
{
std::cerr << "Failed to initialize GLEW: " << glewGetErrorString(glewErr) << "\n";
glfwTerminate();
return nullptr;
}
}
return window;
}
GLuint createShaderProgram()
{
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vs);
glAttachShader(shaderProgram, fs);
glLinkProgram(shaderProgram);
return shaderProgram;
}
void generatePointCloud(std::vector<float>& pointCloud, const Triangle<Vector3<float>>& triangle)
{
const auto& A = triangle.m_vertex1;
const auto& B = triangle.m_vertex2;
const auto& C = triangle.m_vertex3;
// Iterating over barycentric coordinates (u, v, w) from 0.0 to 1.0
for (float u = 0.0f; u <= 1.0f; u += 0.015f)
{
for (float v = 0.0f; v <= 1.0f - u; v += 0.015f)
{
float w = 1.0f - u - v;
if (w >= 0.0f && w <= 1.0f)
{
Vector3<float> P = A * u + B * v + C * w;
pointCloud.push_back(P.x);
pointCloud.push_back(P.y);
pointCloud.push_back(P.z);
pointCloud.push_back(u);
pointCloud.push_back(v);
pointCloud.push_back(w);
pointCloud.push_back(2.0f); // size
pointCloud.push_back(0.0f); // isLine
}
}
}
}
void setupBuffers(GLuint& VAO_cloud, GLuint& VBO_cloud, const std::vector<float>& pointCloud, GLuint& VAO_dyn,
GLuint& VBO_dyn)
{
glGenVertexArrays(1, &VAO_cloud);
glGenBuffers(1, &VBO_cloud);
glBindVertexArray(VAO_cloud);
glBindBuffer(GL_ARRAY_BUFFER, VBO_cloud);
glBufferData(GL_ARRAY_BUFFER, pointCloud.size() * sizeof(float), pointCloud.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(7 * sizeof(float)));
glEnableVertexAttribArray(3);
glGenVertexArrays(1, &VAO_dyn);
glGenBuffers(1, &VBO_dyn);
glBindVertexArray(VAO_dyn);
glBindBuffer(GL_ARRAY_BUFFER, VBO_dyn);
glBufferData(GL_ARRAY_BUFFER, 1000 * 8 * sizeof(float), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(7 * sizeof(float)));
glEnableVertexAttribArray(3);
}
void updateDynamicData(std::vector<float>& dynData, float u, float v, float w, const Vector3<float>& P,
const Triangle<Vector3<float>>& triangle)
{
const auto& A = triangle.m_vertex1;
const auto& B = triangle.m_vertex2;
const auto& C = triangle.m_vertex3;
float sizeA = 10.0f + u * 30.0f;
float sizeB = 10.0f + v * 30.0f;
float sizeC = 10.0f + w * 30.0f;
float sizeP = 12.0f;
dynData = {// Lines from P to A, B, C
P.x, P.y, P.z, u, v, w, 1.0f, 1.0f, A.x, A.y, A.z, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
P.x, P.y, P.z, u, v, w, 1.0f, 1.0f, B.x, B.y, B.z, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
P.x, P.y, P.z, u, v, w, 1.0f, 1.0f, C.x, C.y, C.z, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
// The animated dot itself (White)
P.x, P.y, P.z, 1.0f, 1.0f, 1.0f, sizeP, 0.0f,
// The 3 corner dots
A.x, A.y, A.z, 1.0f, 0.0f, 0.0f, sizeA, 0.0f, B.x, B.y, B.z, 0.0f, 1.0f, 0.0f, sizeB, 0.0f, C.x, C.y,
C.z, 0.0f, 0.0f, 1.0f, sizeC, 0.0f};
char bufA[16], bufB[16], bufC[16];
snprintf(bufA, sizeof(bufA), "%.2f", u);
snprintf(bufB, sizeof(bufB), "%.2f", v);
snprintf(bufC, sizeof(bufC), "%.2f", w);
// Keep text at a fixed distance from the dots
float distA = 0.13f;
float distB = 0.13f;
float distC = 0.13f;
drawText(bufA, A.x - 0.05f, A.y + distA, 0.1f, Color(1, 0, 0, 1), dynData);
drawText(bufB, B.x - 0.15f - distB, B.y - 0.05f - distB, 0.1f, Color(0, 1, 0, 1), dynData);
drawText(bufC, C.x + 0.05f + distC, C.y - 0.05f - distC, 0.1f, Color(0, 0, 1, 1), dynData);
}
int main()
{
GLFWwindow* window = initWindow(800, 800, "Barycentric Coordinates");
if (!window)
return -1;
GLuint shaderProgram = createShaderProgram();
// Triangle vertices as shown in the picture (Red, Green, Blue)
// Scaled down slightly to leave room for text
Triangle<Vector3<float>> triangle(Vector3<float>(0.0f, 0.6f, 0.0f), // Red dot (top)
Vector3<float>(-0.6f, -0.6f, 0.0f), // Green dot (bottom left)
Vector3<float>(0.6f, -0.6f, 0.0f) // Blue dot (bottom right)
);
std::vector<float> pointCloud;
generatePointCloud(pointCloud, triangle);
GLuint VAO_cloud, VBO_cloud, VAO_dyn, VBO_dyn;
setupBuffers(VAO_cloud, VBO_cloud, pointCloud, VAO_dyn, VBO_dyn);
glEnable(GL_PROGRAM_POINT_SIZE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
while (!glfwWindowShouldClose(window))
{
glClearColor(0.02f, 0.02f, 0.02f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
// Draw the point cloud (the iterated points)
glBindVertexArray(VAO_cloud);
glDrawArrays(GL_POINTS, 0, pointCloud.size() / 8);
// Animate the white dot to simulate dragging
float t = glfwGetTime();
float u = (std::sin(t * 1.5f) * 0.5f + 0.5f);
float v = (std::cos(t * 1.1f) * 0.5f + 0.5f);
if (u + v > 1.0f)
{
u = 1.0f - u;
v = 1.0f - v;
}
float w = 1.0f - u - v;
if (w > 1.0f)
{
float diff = w - 1.0f;
w = 1.0f;
u += diff / 2.0f;
v += diff / 2.0f;
}
else if (w < 0.0f)
{
float diff = -w;
w = 0.0f;
u -= diff / 2.0f;
v -= diff / 2.0f;
}
Vector3<float> P = triangle.m_vertex1 * u + triangle.m_vertex2 * v + triangle.m_vertex3 * w;
std::vector<float> dynData;
updateDynamicData(dynData, u, v, w, P, triangle);
glBindVertexArray(VAO_dyn);
glBindBuffer(GL_ARRAY_BUFFER, VBO_dyn);
glBufferSubData(GL_ARRAY_BUFFER, 0, dynData.size() * sizeof(float), dynData.data());
// Draw lines
glDrawArrays(GL_LINES, 0, 6);
// Draw text lines
int numTextVertices = (dynData.size() / 8) - 10;
if (numTextVertices > 0)
{
glDrawArrays(GL_LINES, 10, numTextVertices);
}
// Draw dots
glDrawArrays(GL_POINTS, 6, 4);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}

View File

@@ -0,0 +1,14 @@
project(example_glfw3)
add_executable(${PROJECT_NAME} example_glfw3.cpp)
set_target_properties(
${PROJECT_NAME}
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(OpenGL)
find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath GLEW::GLEW glfw OpenGL::GL)

View File

@@ -120,9 +120,14 @@ int main()
return -1; return -1;
} }
std::cout << "GLFW Version: " << glfwGetVersionString() << "\n";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Force GLX context creation API to ensure compatibility with GLEW
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
#ifdef __APPLE__ #ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif #endif
@@ -141,16 +146,37 @@ int main()
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// Check if context is valid using standard GL
const GLubyte* renderer = glGetString(GL_RENDERER);
const GLubyte* version = glGetString(GL_VERSION);
if (renderer && version)
{
std::cout << "Renderer: " << renderer << "\n";
std::cout << "OpenGL version supported: " << version << "\n";
}
else
{
std::cerr << "Failed to get GL_RENDERER or GL_VERSION. Context might be invalid.\n";
}
// ---------- GLEW init ---------- // ---------- GLEW init ----------
glewExperimental = GL_TRUE; glewExperimental = GL_TRUE;
GLenum glewErr = glewInit(); GLenum glewErr = glewInit();
if (glewErr != GLEW_OK) if (glewErr != GLEW_OK)
{
// Ignore NO_GLX_DISPLAY if we have a valid context
if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY && renderer)
{
std::cerr << "GLEW warning: " << glewGetErrorString(glewErr) << " (Ignored because context seems valid)\n";
}
else
{ {
std::cerr << "Failed to initialize GLEW: " << reinterpret_cast<const char*>(glewGetErrorString(glewErr)) std::cerr << "Failed to initialize GLEW: " << reinterpret_cast<const char*>(glewGetErrorString(glewErr))
<< "\n"; << "\n";
glfwTerminate(); glfwTerminate();
return -1; return -1;
} }
}
// ---------- GL state ---------- // ---------- GL state ----------
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
@@ -280,11 +306,65 @@ int main()
static float old_frame_time = glfwGetTime(); static float old_frame_time = glfwGetTime();
float lastX = 640.0f / 2.0f;
float lastY = 480.0f / 2.0f;
bool firstMouse = true;
bool mouse_capture = false;
float mouseSensitivity = 0.1f;
// ---------- Main loop ---------- // ---------- Main loop ----------
static double old_mouse_time = glfwGetTime();
while (!glfwWindowShouldClose(window)) while (!glfwWindowShouldClose(window))
{ {
glfwPollEvents(); glfwPollEvents();
omath::Vector3<float> move_dir;
if (glfwGetKey(window, GLFW_KEY_W))
move_dir += camera.get_forward();
if (glfwGetKey(window, GLFW_KEY_A))
move_dir -= camera.get_right();
if (glfwGetKey(window, GLFW_KEY_S))
move_dir -= camera.get_forward();
if (glfwGetKey(window, GLFW_KEY_D))
move_dir += camera.get_right();
if (glfwGetKey(window, GLFW_KEY_SPACE))
move_dir += camera.get_up();
if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL))
move_dir -= camera.get_up();
auto delta = glfwGetTime() - old_mouse_time;
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) && delta > 0.4)
{
old_mouse_time = glfwGetTime();
mouse_capture = !mouse_capture;
glfwSetInputMode(window, GLFW_CURSOR, mouse_capture ? GLFW_CURSOR_HIDDEN : GLFW_CURSOR_NORMAL);
}
if (mouse_capture)
{
int x, y;
glfwGetWindowSize(window, &x, &y);
camera.set_origin(camera.get_origin() + (move_dir.normalized() * 0.4f));
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
float xoffset = (float)xpos - x / 2.f;
float yoffset = y / 2.f - ypos ; // reversed: y-coordinates go bottom->top for pitch
xoffset *= mouseSensitivity;
yoffset *= mouseSensitivity;
auto new_angles = camera.get_view_angles();
new_angles.pitch += decltype(new_angles.pitch)::from_degrees(yoffset);
new_angles.yaw -= decltype(new_angles.yaw)::from_degrees(xoffset);
camera.set_view_angles(new_angles);
glfwSetCursorPos(window, x / 2., y / 2);
}
float currentTime = glfwGetTime(); float currentTime = glfwGetTime();
float deltaTime = currentTime - old_frame_time; float deltaTime = currentTime - old_frame_time;
old_frame_time = currentTime; old_frame_time = currentTime;

View File

@@ -0,0 +1,10 @@
project(example_projection_matrix_builder)
add_executable(${PROJECT_NAME} example_proj_mat_builder.cpp)
set_target_properties(
${PROJECT_NAME}
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath)

View File

@@ -0,0 +1,10 @@
project(example_signature_scan)
add_executable(${PROJECT_NAME} example_signature_scan.cpp)
set_target_properties(
${PROJECT_NAME}
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath)

View File

@@ -13,11 +13,11 @@
namespace omath::collision namespace omath::collision
{ {
template<class V> template<class V, class FloatingType>
concept EpaVector = requires(const V& a, const V& b, float s) { concept EpaVector = requires(const V& a, const V& b, FloatingType s) {
{ a - b } -> std::same_as<V>; { a - b } -> std::same_as<V>;
{ a.cross(b) } -> std::same_as<V>; { a.cross(b) } -> std::same_as<V>;
{ a.dot(b) } -> std::same_as<float>; { a.dot(b) } -> std::same_as<FloatingType>;
{ -a } -> std::same_as<V>; { -a } -> std::same_as<V>;
{ a * s } -> std::same_as<V>; { a * s } -> std::same_as<V>;
{ a / s } -> std::same_as<V>; { a / s } -> std::same_as<V>;
@@ -28,13 +28,18 @@ namespace omath::collision
{ {
public: public:
using VectorType = ColliderInterfaceType::VectorType; using VectorType = ColliderInterfaceType::VectorType;
static_assert(EpaVector<VectorType>, "VertexType must satisfy EpaVector concept"); static_assert(EpaVector<VectorType, typename VectorType::ContainedType>,
"VertexType must satisfy EpaVector concept");
private:
using FloatingType = VectorType::ContainedType;
public:
struct Result final struct Result final
{ {
VectorType normal{}; // from A to B VectorType normal{}; // from A to B
VectorType penetration_vector; VectorType penetration_vector;
float depth{0.0f}; FloatingType depth{0.0};
int iterations{0}; int iterations{0};
int num_vertices{0}; int num_vertices{0};
int num_faces{0}; int num_faces{0};
@@ -43,7 +48,7 @@ namespace omath::collision
struct Params final struct Params final
{ {
int max_iterations{64}; int max_iterations{64};
float tolerance{1e-4f}; // absolute tolerance on distance growth FloatingType tolerance{1e-4}; // absolute tolerance on distance growth
}; };
// Precondition: simplex.size()==4 and contains the origin. // Precondition: simplex.size()==4 and contains the origin.
[[nodiscard]] [[nodiscard]]
@@ -75,13 +80,13 @@ namespace omath::collision
if (heap.empty()) if (heap.empty())
break; break;
//FIXME: STORE REF VALUE, DO NOT USE // FIXME: STORE REF VALUE, DO NOT USE
// AFTER IF STATEMENT BLOCK // AFTER IF STATEMENT BLOCK
const Face& face = faces[heap.top().idx]; const Face& face = faces[heap.top().idx];
// Get the furthest point in face normal direction // Get the furthest point in face normal direction
const VectorType p = support_point(a, b, face.n); const VectorType p = support_point(a, b, face.n);
const float p_dist = face.n.dot(p); const auto p_dist = face.n.dot(p);
// Converged if we cant push the face closer than tolerance // Converged if we cant push the face closer than tolerance
if (p_dist - face.d <= params.tolerance) if (p_dist - face.d <= params.tolerance)
@@ -136,7 +141,7 @@ namespace omath::collision
{ {
int i0, i1, i2; int i0, i1, i2;
VectorType n; // unit outward normal VectorType n; // unit outward normal
float d; // n · v0 (>=0 ideally because origin is inside) FloatingType d; // n · v0 (>=0 ideally because origin is inside)
}; };
struct Edge final struct Edge final
@@ -146,7 +151,7 @@ namespace omath::collision
struct HeapItem final struct HeapItem final
{ {
float d; FloatingType d;
int idx; int idx;
}; };
struct HeapCmp final struct HeapCmp final
@@ -178,7 +183,7 @@ namespace omath::collision
static bool visible_from(const Face& f, const VectorType& p) static bool visible_from(const Face& f, const VectorType& p)
{ {
// positive if p is in front of the face // positive if p is in front of the face
return f.n.dot(p) - f.d > 1e-7f; return f.n.dot(p) - f.d > static_cast<FloatingType>(1e-7);
} }
static void add_edge_boundary(std::pmr::vector<Edge>& boundary, int a, int b) static void add_edge_boundary(std::pmr::vector<Edge>& boundary, int a, int b)
@@ -198,19 +203,20 @@ namespace omath::collision
const VectorType& a1 = vertexes[i1]; const VectorType& a1 = vertexes[i1];
const VectorType& a2 = vertexes[i2]; const VectorType& a2 = vertexes[i2];
VectorType n = (a1 - a0).cross(a2 - a0); VectorType n = (a1 - a0).cross(a2 - a0);
if (n.dot(n) <= 1e-30f) if (n.dot(n) <= static_cast<FloatingType>(1e-30))
{ {
n = any_perp_vec(a1 - a0); // degenerate guard n = any_perp_vec(a1 - a0); // degenerate guard
} }
// Ensure normal points outward (away from origin): require n·a0 >= 0 // Ensure normal points outward (away from origin): require n·a0 >= 0
if (n.dot(a0) < 0.0f) if (n.dot(a0) < static_cast<FloatingType>(0.0))
{ {
std::swap(i1, i2); std::swap(i1, i2);
n = -n; n = -n;
} }
const float inv_len = 1.0f / std::sqrt(std::max(n.dot(n), 1e-30f)); const auto inv_len =
static_cast<FloatingType>(1.0) / std::sqrt(std::max(n.dot(n), static_cast<FloatingType>(1e-30)));
n = n * inv_len; n = n * inv_len;
const float d = n.dot(a0); const auto d = n.dot(a0);
return {i0, i1, i2, n, d}; return {i0, i1, i2, n, d};
} }
@@ -223,7 +229,7 @@ namespace omath::collision
template<class V> template<class V>
[[nodiscard]] [[nodiscard]]
static constexpr bool near_zero_vec(const V& v, const float eps = 1e-7f) static constexpr bool near_zero_vec(const V& v, const FloatingType eps = 1e-7f)
{ {
return v.dot(v) <= eps * eps; return v.dot(v) <= eps * eps;
} }

View File

@@ -14,11 +14,15 @@ namespace omath::collision
Simplex<VertexType> simplex; // valid only if hit == true and size==4 Simplex<VertexType> simplex; // valid only if hit == true and size==4
}; };
struct GjkSettings final
{
float epsilon = 1e-6f;
std::size_t max_iterations = 64;
};
template<class ColliderInterfaceType> template<class ColliderInterfaceType>
class GjkAlgorithm final class GjkAlgorithm final
{ {
using VectorType = ColliderInterfaceType::VectorType; using VectorType = ColliderInterfaceType::VectorType;
public: public:
[[nodiscard]] [[nodiscard]]
static VectorType find_support_vertex(const ColliderInterfaceType& collider_a, static VectorType find_support_vertex(const ColliderInterfaceType& collider_a,
@@ -36,7 +40,8 @@ namespace omath::collision
[[nodiscard]] [[nodiscard]]
static GjkHitInfo<VectorType> is_collide_with_simplex_info(const ColliderInterfaceType& collider_a, static GjkHitInfo<VectorType> is_collide_with_simplex_info(const ColliderInterfaceType& collider_a,
const ColliderInterfaceType& collider_b) const ColliderInterfaceType& collider_b,
const GjkSettings& settings = {})
{ {
auto support = find_support_vertex(collider_a, collider_b, VectorType{1, 0, 0}); auto support = find_support_vertex(collider_a, collider_b, VectorType{1, 0, 0});
@@ -45,11 +50,11 @@ namespace omath::collision
auto direction = -support; auto direction = -support;
while (true) for (std::size_t iteration = 0; iteration < settings.max_iterations; ++iteration)
{ {
support = find_support_vertex(collider_a, collider_b, direction); support = find_support_vertex(collider_a, collider_b, direction);
if (support.dot(direction) <= 0.f) if (support.dot(direction) <= settings.epsilon)
return {false, simplex}; return {false, simplex};
simplex.push_front(support); simplex.push_front(support);
@@ -57,6 +62,7 @@ namespace omath::collision
if (simplex.handle(direction)) if (simplex.handle(direction))
return {true, simplex}; return {true, simplex};
} }
return {false, simplex};
} }
}; };
} // namespace omath::collision } // namespace omath::collision

View File

@@ -8,43 +8,96 @@
namespace omath::collision namespace omath::collision
{ {
class Ray template<class T = Vector3<float>>
class Ray final
{ {
public: public:
Vector3<float> start; using VectorType = T;
Vector3<float> end; VectorType start;
VectorType end;
bool infinite_length = false; bool infinite_length = false;
[[nodiscard]] [[nodiscard]]
Vector3<float> direction_vector() const noexcept; constexpr VectorType direction_vector() const noexcept
{
return end - start;
}
[[nodiscard]] [[nodiscard]]
Vector3<float> direction_vector_normalized() const noexcept; constexpr VectorType direction_vector_normalized() const noexcept
};
class LineTracer
{ {
return direction_vector().normalized();
}
};
template<class RayType = Ray<>>
class LineTracer final
{
using TriangleType = Triangle<typename RayType::VectorType>;
public: public:
LineTracer() = delete; LineTracer() = delete;
[[nodiscard]] [[nodiscard]]
static bool can_trace_line(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept; constexpr static bool can_trace_line(const RayType& ray, const TriangleType& triangle) noexcept
{
return get_ray_hit_point(ray, triangle) == ray.end;
}
// Realization of MöllerTrumbore intersection algorithm // Realization of MöllerTrumbore intersection algorithm
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
[[nodiscard]] [[nodiscard]]
static Vector3<float> get_ray_hit_point(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept; constexpr static auto get_ray_hit_point(const RayType& ray, const TriangleType& triangle) noexcept
{
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
const auto side_a = triangle.side_a_vector();
const auto side_b = triangle.side_b_vector();
const auto ray_dir = ray.direction_vector();
const auto p = ray_dir.cross(side_b);
const auto det = side_a.dot(p);
if (std::abs(det) < k_epsilon)
return ray.end;
const auto inv_det = 1 / det;
const auto t = ray.start - triangle.m_vertex2;
const auto u = t.dot(p) * inv_det;
if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon))
return ray.end;
const auto q = t.cross(side_a);
// ReSharper disable once CppTooWideScopeInitStatement
const auto v = ray_dir.dot(q) * inv_det;
if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon))
return ray.end;
const auto t_hit = side_b.dot(q) * inv_det;
if (ray.infinite_length && t_hit <= k_epsilon)
return ray.end;
if (t_hit <= k_epsilon || t_hit > 1 - k_epsilon)
return ray.end;
return ray.start + ray_dir * t_hit;
}
template<class MeshType> template<class MeshType>
[[nodiscard]] [[nodiscard]]
static Vector3<float> get_ray_hit_point(const Ray& ray, const MeshType& mesh) noexcept constexpr static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept
{ {
Vector3<float> mesh_hit = ray.end; auto mesh_hit = ray.end;
auto begin = mesh.m_element_buffer_object.cbegin(); const auto begin = mesh.m_element_buffer_object.cbegin();
auto end = mesh.m_element_buffer_object.cend(); const auto end = mesh.m_element_buffer_object.cend();
for (auto current = begin; current < end; current = std::next(current)) for (auto current = begin; current < end; current = std::next(current))
{ {
auto face = mesh.make_face_in_world_space(current); const auto face = mesh.make_face_in_world_space(current);
auto ray_stop_point = get_ray_hit_point(ray, face); auto ray_stop_point = get_ray_hit_point(ray, face);
if (ray_stop_point.distance_to(ray.start) < mesh_hit.distance_to(ray.start)) if (ray_stop_point.distance_to(ray.start) < mesh_hit.distance_to(ray.start))

View File

@@ -46,9 +46,26 @@ namespace omath::collision
[[nodiscard]] [[nodiscard]]
const VertexType& find_furthest_vertex(const VectorType& direction) const const VertexType& find_furthest_vertex(const VectorType& direction) const
{ {
return *std::ranges::max_element( // The support query arrives in world space, but vertex positions are stored
m_mesh.m_vertex_buffer, [&direction](const auto& first, const auto& second) // in local space. We need argmax_v { world(v) · d }.
{ return first.position.dot(direction) < second.position.dot(direction); }); //
// world(v) = M·v (ignoring translation, which is constant across vertices)
// world(v) · d = v · Mᵀ·d
//
// So we transform the direction to local space once — O(1) — then compare
// raw local positions, which is far cheaper than calling
// vertex_position_to_world_space (full 4×4 multiply) for every vertex.
//
// d_local = upper-left 3×3 of M, transposed, times d_world:
// d_local[j] = sum_i M.at(i,j) * d[i] (i.e. column j of M dotted with d)
const auto& m = m_mesh.get_to_world_matrix();
const VectorType d_local = {
m[0, 0] * direction.x + m[1, 0] * direction.y + m[2, 0] * direction.z,
m[0, 1] * direction.x + m[1, 1] * direction.y + m[2, 1] * direction.z,
m[0, 2] * direction.x + m[1, 2] * direction.y + m[2, 2] * direction.z,
};
return *std::ranges::max_element(m_mesh.m_vertex_buffer, [&d_local](const auto& first, const auto& second)
{ return first.position.dot(d_local) < second.position.dot(d_local); });
} }
MeshType m_mesh; MeshType m_mesh;
}; };

View File

@@ -130,7 +130,7 @@ namespace omath::collision
template<class V> template<class V>
[[nodiscard]] [[nodiscard]]
static constexpr bool near_zero(const V& v, const float eps = 1e-7f) static constexpr bool near_zero(const V& v, const float eps = 1e-7f) noexcept
{ {
return v.dot(v) <= eps * eps; return v.dot(v) <= eps * eps;
} }
@@ -146,7 +146,7 @@ namespace omath::collision
} }
[[nodiscard]] [[nodiscard]]
constexpr bool handle_line(VectorType& direction) constexpr bool handle_line(VectorType& direction) noexcept
{ {
const auto& a = m_points[0]; const auto& a = m_points[0];
const auto& b = m_points[1]; const auto& b = m_points[1];
@@ -158,21 +158,11 @@ namespace omath::collision
{ {
// ReSharper disable once CppTooWideScopeInitStatement // ReSharper disable once CppTooWideScopeInitStatement
auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos
if (near_zero(n)) direction = near_zero(n) ? any_perp(ab) : n.cross(ab);
{ return false;
// collinear: origin lies on ray AB (often on segment), pick any perp to escape
direction = any_perp(ab);
} }
else
{
direction = n.cross(ab);
}
}
else
{
*this = {a}; *this = {a};
direction = ao; direction = ao;
}
return false; return false;
} }

View File

@@ -62,20 +62,13 @@ namespace omath::detail
return splitmix64(base_seed() + 0xD1B54A32D192ED03ull * (Stream + 1)); return splitmix64(base_seed() + 0xD1B54A32D192ED03ull * (Stream + 1));
} }
[[nodiscard]]
consteval std::uint64_t bounded_u64(const std::uint64_t x, const std::uint64_t bound)
{
return (x * bound) >> 64;
}
template<std::int64_t Lo, std::int64_t Hi, std::uint64_t Stream> template<std::int64_t Lo, std::int64_t Hi, std::uint64_t Stream>
[[nodiscard]] [[nodiscard]]
consteval std::int64_t rand_uint8_t() consteval std::int64_t rand_uint8_t()
{ {
static_assert(Lo <= Hi); static_assert(Lo <= Hi);
const std::uint64_t span = static_cast<std::uint64_t>(Hi - Lo) + 1ull;
const std::uint64_t r = rand_u64<Stream>(); const std::uint64_t r = rand_u64<Stream>();
return static_cast<std::int64_t>(bounded_u64(r, span)) + Lo; return static_cast<std::int64_t>(r) + Lo;
} }
[[nodiscard]] [[nodiscard]]
consteval std::uint64_t rand_u64(const std::uint64_t seed, const std::uint64_t i) consteval std::uint64_t rand_u64(const std::uint64_t seed, const std::uint64_t i)

View File

@@ -0,0 +1,13 @@
//
// Created by Vlad on 3/22/2025.
//
#pragma once
#include "omath/engines/cry_engine/constants.hpp"
#include "omath/projection/camera.hpp"
#include "traits/camera_trait.hpp"
namespace omath::cry_engine
{
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>;
} // namespace omath::cry_engine

View File

@@ -0,0 +1,25 @@
//
// Created by Vlad on 10/21/2025.
//
#pragma once
#include "omath/linear_algebra/mat.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <omath/trigonometry/angle.hpp>
#include <omath/trigonometry/view_angles.hpp>
namespace omath::cry_engine
{
constexpr Vector3<float> k_abs_up = {0, 0, 1};
constexpr Vector3<float> k_abs_right = {1, 0, 0};
constexpr Vector3<float> k_abs_forward = {0, 1, 0};
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
} // namespace omath::cry_engine

View File

@@ -0,0 +1,74 @@
//
// Created by Vlad on 3/22/2025.
//
#pragma once
#include "omath/engines/cry_engine/constants.hpp"
namespace omath::cry_engine
{
[[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
[[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
[[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::cry_engine

View File

@@ -0,0 +1,12 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "constants.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "traits/mesh_trait.hpp"
namespace omath::cry_engine
{
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
}

View File

@@ -0,0 +1,24 @@
//
// Created by Vlad on 8/10/2025.
//
#pragma once
#include "omath/engines/cry_engine/formulas.hpp"
#include "omath/projection/camera.hpp"
namespace omath::cry_engine
{
class CameraTrait final
{
public:
[[nodiscard]]
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept;
[[nodiscard]]
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
float near, float far) noexcept;
};
} // namespace omath::cry_engine

View File

@@ -0,0 +1,19 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include <omath/engines/cry_engine/constants.hpp>
#include <omath/engines/cry_engine/formulas.hpp>
namespace omath::cry_engine
{
class MeshTrait final
{
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
{
return cry_engine::rotation_matrix(rotation);
}
};
} // namespace omath::cry_engine

View File

@@ -0,0 +1,76 @@
//
// Created by Vlad on 8/6/2025.
//
#pragma once
#include "omath/engines/cry_engine/formulas.hpp"
#include "omath/projectile_prediction/projectile.hpp"
#include "omath/projectile_prediction/target.hpp"
#include <optional>
namespace omath::cry_engine
{
class PredEngineTrait final
{
public:
constexpr static Vector3<float> predict_projectile_position(const projectile_prediction::Projectile& projectile,
const float pitch, const float yaw,
const float time, const float gravity) noexcept
{
auto current_pos = projectile.m_origin
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;
current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
return current_pos;
}
[[nodiscard]]
static constexpr Vector3<float> predict_target_position(const projectile_prediction::Target& target,
const float time, const float gravity) noexcept
{
auto predicted = target.m_origin + target.m_velocity * time;
if (target.m_is_airborne)
predicted.z -= gravity * (time * time) * 0.5f;
return predicted;
}
[[nodiscard]]
static float calc_vector_2d_distance(const Vector3<float>& delta) noexcept
{
return std::sqrt(delta.x * delta.x + delta.y * delta.y);
}
[[nodiscard]]
constexpr static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
{
return vec.z;
}
[[nodiscard]]
static Vector3<float> calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
Vector3<float> predicted_target_position,
const std::optional<float> projectile_pitch) noexcept
{
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value()));
return {predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height};
}
// Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be:
// 89 look up, -89 look down
[[nodiscard]]
static float calc_direct_pitch_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto direction = (view_to - origin).normalized();
return angles::radians_to_degrees(std::asin(direction.z));
}
[[nodiscard]]
static float calc_direct_yaw_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto direction = (view_to - origin).normalized();
return angles::radians_to_degrees(-std::atan2(direction.x, direction.y));
};
};
} // namespace omath::cry_engine

View File

@@ -23,4 +23,52 @@ namespace omath::frostbite_engine
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
} // namespace omath::unity_engine
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::frostbite_engine

View File

@@ -55,7 +55,7 @@ namespace omath::frostbite_engine
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin); const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value())); const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value()));
return {predicted_target_position.x, predicted_target_position.y + height, projectile.m_origin.z}; return {predicted_target_position.x, projectile.m_origin.y + height, predicted_target_position.z};
} }
// Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be: // Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be:
// 89 look up, -89 look down // 89 look up, -89 look down

View File

@@ -23,4 +23,54 @@ namespace omath::iw_engine
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
constexpr auto centimeter_in_unit = static_cast<FloatingType>(2.54);
return units * centimeter_in_unit;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units_to_centimeters(units) / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
constexpr auto centimeter_in_unit = static_cast<FloatingType>(2.54);
return centimeters / centimeter_in_unit;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return centimeters_to_units(meters * static_cast<FloatingType>(100));
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::iw_engine } // namespace omath::iw_engine

View File

@@ -8,5 +8,5 @@
namespace omath::opengl_engine namespace omath::opengl_engine
{ {
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>; using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, true>;
} // namespace omath::opengl_engine } // namespace omath::opengl_engine

View File

@@ -4,7 +4,6 @@
#pragma once #pragma once
#include "omath/engines/opengl_engine/constants.hpp" #include "omath/engines/opengl_engine/constants.hpp"
namespace omath::opengl_engine namespace omath::opengl_engine
{ {
[[nodiscard]] [[nodiscard]]
@@ -23,4 +22,52 @@ namespace omath::opengl_engine
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::opengl_engine } // namespace omath::opengl_engine

View File

@@ -55,7 +55,7 @@ namespace omath::opengl_engine
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin); const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value())); const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value()));
return {predicted_target_position.x, predicted_target_position.y + height, projectile.m_origin.z}; return {predicted_target_position.x, projectile.m_origin.y + height, predicted_target_position.z};
} }
// Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be: // Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be:
// 89 look up, -89 look down // 89 look up, -89 look down

View File

@@ -22,4 +22,54 @@ namespace omath::source_engine
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
constexpr auto centimeter_in_unit = static_cast<FloatingType>(2.54);
return units * centimeter_in_unit;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units_to_centimeters(units) / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
constexpr auto centimeter_in_unit = static_cast<FloatingType>(2.54);
return centimeters / centimeter_in_unit;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return centimeters_to_units(meters * static_cast<FloatingType>(100));
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::source_engine } // namespace omath::source_engine

View File

@@ -23,4 +23,52 @@ namespace omath::unity_engine
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::unity_engine } // namespace omath::unity_engine

View File

@@ -55,7 +55,7 @@ namespace omath::unity_engine
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin); const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value())); const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value()));
return {predicted_target_position.x, predicted_target_position.y + height, projectile.m_origin.z}; return {predicted_target_position.x, projectile.m_origin.y + height, predicted_target_position.z};
} }
// Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be: // Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be:
// 89 look up, -89 look down // 89 look up, -89 look down

View File

@@ -23,4 +23,52 @@ namespace omath::unreal_engine
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::unreal_engine } // namespace omath::unreal_engine

View File

@@ -0,0 +1,219 @@
//
// Created by vlad on 3/1/2026.
//
#pragma once
#include "omath/linear_algebra/mat.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
#include <cmath>
#include <format>
namespace omath
{
template<class Type>
requires std::is_arithmetic_v<Type>
class Quaternion
{
public:
using ContainedType = Type;
Type x = static_cast<Type>(0);
Type y = static_cast<Type>(0);
Type z = static_cast<Type>(0);
Type w = static_cast<Type>(1); // identity quaternion
constexpr Quaternion() noexcept = default;
constexpr Quaternion(const Type& x, const Type& y, const Type& z, const Type& w) noexcept
: x(x), y(y), z(z), w(w)
{
}
// Factory: build from a normalized axis and an angle in radians
[[nodiscard]]
static Quaternion from_axis_angle(const Vector3<Type>& axis, const Type& angle_rad) noexcept
{
const Type half = angle_rad / static_cast<Type>(2);
const Type s = std::sin(half);
return {axis.x * s, axis.y * s, axis.z * s, std::cos(half)};
}
[[nodiscard]] constexpr bool operator==(const Quaternion& other) const noexcept
{
return x == other.x && y == other.y && z == other.z && w == other.w;
}
[[nodiscard]] constexpr bool operator!=(const Quaternion& other) const noexcept
{
return !(*this == other);
}
// Hamilton product: this * other
[[nodiscard]] constexpr Quaternion operator*(const Quaternion& other) const noexcept
{
return {
w * other.x + x * other.w + y * other.z - z * other.y,
w * other.y - x * other.z + y * other.w + z * other.x,
w * other.z + x * other.y - y * other.x + z * other.w,
w * other.w - x * other.x - y * other.y - z * other.z,
};
}
constexpr Quaternion& operator*=(const Quaternion& other) noexcept
{
return *this = *this * other;
}
[[nodiscard]] constexpr Quaternion operator*(const Type& scalar) const noexcept
{
return {x * scalar, y * scalar, z * scalar, w * scalar};
}
constexpr Quaternion& operator*=(const Type& scalar) noexcept
{
x *= scalar;
y *= scalar;
z *= scalar;
w *= scalar;
return *this;
}
[[nodiscard]] constexpr Quaternion operator+(const Quaternion& other) const noexcept
{
return {x + other.x, y + other.y, z + other.z, w + other.w};
}
constexpr Quaternion& operator+=(const Quaternion& other) noexcept
{
x += other.x;
y += other.y;
z += other.z;
w += other.w;
return *this;
}
[[nodiscard]] constexpr Quaternion operator-() const noexcept
{
return {-x, -y, -z, -w};
}
// Conjugate: negates the vector part (x, y, z)
[[nodiscard]] constexpr Quaternion conjugate() const noexcept
{
return {-x, -y, -z, w};
}
[[nodiscard]] constexpr Type dot(const Quaternion& other) const noexcept
{
return x * other.x + y * other.y + z * other.z + w * other.w;
}
[[nodiscard]] constexpr Type length_sqr() const noexcept
{
return x * x + y * y + z * z + w * w;
}
#ifndef _MSC_VER
[[nodiscard]] constexpr Type length() const noexcept
{
return std::sqrt(length_sqr());
}
[[nodiscard]] constexpr Quaternion normalized() const noexcept
{
const Type len = length();
return len != static_cast<Type>(0) ? *this * (static_cast<Type>(1) / len) : *this;
}
#else
[[nodiscard]] Type length() const noexcept
{
return std::sqrt(length_sqr());
}
[[nodiscard]] Quaternion normalized() const noexcept
{
const Type len = length();
return len != static_cast<Type>(0) ? *this * (static_cast<Type>(1) / len) : *this;
}
#endif
// Inverse: q* / |q|^2 (for unit quaternions inverse == conjugate)
[[nodiscard]] constexpr Quaternion inverse() const noexcept
{
return conjugate() * (static_cast<Type>(1) / length_sqr());
}
// Rotate a 3D vector: v' = q * pure(v) * q^-1
// Computed via Rodrigues' formula to avoid full quaternion product overhead
[[nodiscard]] constexpr Vector3<Type> rotate(const Vector3<Type>& v) const noexcept
{
const Vector3<Type> q_vec{x, y, z};
const Vector3<Type> cross = q_vec.cross(v);
return v + cross * (static_cast<Type>(2) * w) + q_vec.cross(cross) * static_cast<Type>(2);
}
// 3x3 rotation matrix from this (unit) quaternion
[[nodiscard]] constexpr Mat<3, 3, Type> to_rotation_matrix3() const noexcept
{
const Type xx = x * x, yy = y * y, zz = z * z;
const Type xy = x * y, xz = x * z, yz = y * z;
const Type wx = w * x, wy = w * y, wz = w * z;
const Type one = static_cast<Type>(1);
const Type two = static_cast<Type>(2);
return {
{one - two * (yy + zz), two * (xy - wz), two * (xz + wy) },
{two * (xy + wz), one - two * (xx + zz), two * (yz - wx) },
{two * (xz - wy), two * (yz + wx), one - two * (xx + yy)},
};
}
// 4x4 rotation matrix (with homogeneous row/column)
[[nodiscard]] constexpr Mat<4, 4, Type> to_rotation_matrix4() const noexcept
{
const Type xx = x * x, yy = y * y, zz = z * z;
const Type xy = x * y, xz = x * z, yz = y * z;
const Type wx = w * x, wy = w * y, wz = w * z;
const Type one = static_cast<Type>(1);
const Type two = static_cast<Type>(2);
const Type zero = static_cast<Type>(0);
return {
{one - two * (yy + zz), two * (xy - wz), two * (xz + wy), zero},
{two * (xy + wz), one - two * (xx + zz), two * (yz - wx), zero},
{two * (xz - wy), two * (yz + wx), one - two * (xx + yy), zero},
{zero, zero, zero, one },
};
}
[[nodiscard]] constexpr std::array<Type, 4> as_array() const noexcept
{
return {x, y, z, w};
}
};
} // namespace omath
template<class Type>
struct std::formatter<omath::Quaternion<Type>> // NOLINT(*-dcl58-cpp)
{
[[nodiscard]]
static constexpr auto parse(std::format_parse_context& ctx)
{
return ctx.begin();
}
template<class FormatContext>
[[nodiscard]]
static auto format(const omath::Quaternion<Type>& q, FormatContext& ctx)
{
if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
return std::format_to(ctx.out(), "[{}, {}, {}, {}]", q.x, q.y, q.z, q.w);
if constexpr (std::is_same_v<typename FormatContext::char_type, wchar_t>)
return std::format_to(ctx.out(), L"[{}, {}, {}, {}]", q.x, q.y, q.z, q.w);
if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>)
return std::format_to(ctx.out(), u8"[{}, {}, {}, {}]", q.x, q.y, q.z, q.w);
}
};

View File

@@ -220,6 +220,12 @@ namespace omath
{ {
return std::make_tuple(x, y); return std::make_tuple(x, y);
} }
[[nodiscard]]
constexpr std::array<Type, 2> as_array() const noexcept
{
return {x, y};
}
#ifdef OMATH_IMGUI_INTEGRATION #ifdef OMATH_IMGUI_INTEGRATION
[[nodiscard]] [[nodiscard]]
constexpr ImVec2 to_im_vec2() const noexcept constexpr ImVec2 to_im_vec2() const noexcept

View File

@@ -4,8 +4,8 @@
#pragma once #pragma once
#include "omath/trigonometry/angle.hpp"
#include "omath/linear_algebra/vector2.hpp" #include "omath/linear_algebra/vector2.hpp"
#include "omath/trigonometry/angle.hpp"
#include <cstdint> #include <cstdint>
#include <expected> #include <expected>
#include <functional> #include <functional>
@@ -233,7 +233,8 @@ namespace omath
return Angle<float, 0.f, 180.f, AngleFlags::Clamped>::from_radians(std::acos(dot(other) / bottom)); return Angle<float, 0.f, 180.f, AngleFlags::Clamped>::from_radians(std::acos(dot(other) / bottom));
} }
[[nodiscard]] bool is_perpendicular(const Vector3& other, Type epsilon = static_cast<Type>(0.0001)) const noexcept [[nodiscard]] bool is_perpendicular(const Vector3& other,
Type epsilon = static_cast<Type>(0.0001)) const noexcept
{ {
if (const auto angle = angle_between(other)) if (const auto angle = angle_between(other))
return std::abs(angle->as_degrees() - static_cast<Type>(90)) <= epsilon; return std::abs(angle->as_degrees() - static_cast<Type>(90)) <= epsilon;
@@ -274,6 +275,12 @@ namespace omath
{ {
return length() >= other.length(); return length() >= other.length();
} }
[[nodiscard]]
constexpr std::array<Type, 3> as_array() const noexcept
{
return {this->x, this->y, z};
}
}; };
} // namespace omath } // namespace omath

View File

@@ -3,8 +3,8 @@
// //
#pragma once #pragma once
#include <algorithm>
#include "omath/linear_algebra/vector3.hpp" #include "omath/linear_algebra/vector3.hpp"
#include <algorithm>
namespace omath namespace omath
{ {
@@ -183,6 +183,12 @@ namespace omath
return length() >= other.length(); return length() >= other.length();
} }
[[nodiscard]]
constexpr std::array<Type, 4> as_array() const noexcept
{
return {this->x, this->y, this->z, w};
}
#ifdef OMATH_IMGUI_INTEGRATION #ifdef OMATH_IMGUI_INTEGRATION
[[nodiscard]] [[nodiscard]]
constexpr ImVec4 to_im_vec4() const noexcept constexpr ImVec4 to_im_vec4() const noexcept
@@ -200,7 +206,7 @@ namespace omath
return {static_cast<Type>(other.x), static_cast<Type>(other.y), static_cast<Type>(other.z)}; return {static_cast<Type>(other.x), static_cast<Type>(other.y), static_cast<Type>(other.z)};
} }
#endif #endif
}; };
} // namespace omath } // namespace omath
template<> struct std::hash<omath::Vector4<float>> template<> struct std::hash<omath::Vector4<float>>

View File

@@ -17,6 +17,9 @@
// Matrix classes // Matrix classes
#include "omath/linear_algebra/mat.hpp" #include "omath/linear_algebra/mat.hpp"
// Quaternion
#include "omath/linear_algebra/quaternion.hpp"
// Color functionality // Color functionality
#include "omath/utility/color.hpp" #include "omath/utility/color.hpp"

View File

@@ -52,7 +52,7 @@ namespace omath::projection
requires noexcept(T::calc_projection_matrix(fov, viewport, znear, zfar)); requires noexcept(T::calc_projection_matrix(fov, viewport, znear, zfar));
}; };
template<class Mat4X4Type, class ViewAnglesType, class TraitClass> template<class Mat4X4Type, class ViewAnglesType, class TraitClass, bool inverted_z = false>
requires CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType> requires CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType>
class Camera final class Camera final
{ {
@@ -80,59 +80,96 @@ namespace omath::projection
{ {
m_view_angles = TraitClass::calc_look_at_angle(m_origin, target); m_view_angles = TraitClass::calc_look_at_angle(m_origin, target);
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_view_matrix = std::nullopt;
} }
protected: [[nodiscard]]
[[nodiscard]] Mat4X4Type calc_view_projection_matrix() const noexcept Vector3<float> get_forward() const noexcept
{ {
return TraitClass::calc_projection_matrix(m_field_of_view, m_view_port, m_near_plane_distance, const auto& view_matrix = get_view_matrix();
m_far_plane_distance)
* TraitClass::calc_view_matrix(m_view_angles, m_origin); if constexpr (inverted_z)
return -Vector3<float>{view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]};
return {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]};
}
[[nodiscard]]
Vector3<float> get_right() const noexcept
{
const auto& view_matrix = get_view_matrix();
return {view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]};
}
[[nodiscard]]
Vector3<float> get_up() const noexcept
{
const auto& view_matrix = get_view_matrix();
return {view_matrix[1, 0], view_matrix[1, 1], view_matrix[1, 2]};
} }
public:
[[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const noexcept [[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const noexcept
{ {
if (!m_view_projection_matrix.has_value()) if (!m_view_projection_matrix.has_value())
m_view_projection_matrix = calc_view_projection_matrix(); m_view_projection_matrix = get_projection_matrix() * get_view_matrix();
return m_view_projection_matrix.value(); return m_view_projection_matrix.value();
} }
[[nodiscard]] const Mat4X4Type& get_view_matrix() const noexcept
{
if (!m_view_matrix.has_value())
m_view_matrix = TraitClass::calc_view_matrix(m_view_angles, m_origin);
return m_view_matrix.value();
}
[[nodiscard]] const Mat4X4Type& get_projection_matrix() const noexcept
{
if (!m_projection_matrix.has_value())
m_projection_matrix = TraitClass::calc_projection_matrix(m_field_of_view, m_view_port,
m_near_plane_distance, m_far_plane_distance);
return m_projection_matrix.value();
}
void set_field_of_view(const FieldOfView& fov) noexcept void set_field_of_view(const FieldOfView& fov) noexcept
{ {
m_field_of_view = fov; m_field_of_view = fov;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_projection_matrix = std::nullopt;
} }
void set_near_plane(const float near) noexcept void set_near_plane(const float near) noexcept
{ {
m_near_plane_distance = near; m_near_plane_distance = near;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_projection_matrix = std::nullopt;
} }
void set_far_plane(const float far) noexcept void set_far_plane(const float far) noexcept
{ {
m_far_plane_distance = far; m_far_plane_distance = far;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_projection_matrix = std::nullopt;
} }
void set_view_angles(const ViewAnglesType& view_angles) noexcept void set_view_angles(const ViewAnglesType& view_angles) noexcept
{ {
m_view_angles = view_angles; m_view_angles = view_angles;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_view_matrix = std::nullopt;
} }
void set_origin(const Vector3<float>& origin) noexcept void set_origin(const Vector3<float>& origin) noexcept
{ {
m_origin = origin; m_origin = origin;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_view_matrix = std::nullopt;
} }
void set_view_port(const ViewPort& view_port) noexcept void set_view_port(const ViewPort& view_port) noexcept
{ {
m_view_port = view_port; m_view_port = view_port;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_projection_matrix = std::nullopt;
} }
[[nodiscard]] const FieldOfView& get_field_of_view() const noexcept [[nodiscard]] const FieldOfView& get_field_of_view() const noexcept
@@ -283,7 +320,8 @@ namespace omath::projection
Angle<float, 0.f, 180.f, AngleFlags::Clamped> m_field_of_view; Angle<float, 0.f, 180.f, AngleFlags::Clamped> m_field_of_view;
mutable std::optional<Mat4X4Type> m_view_projection_matrix; mutable std::optional<Mat4X4Type> m_view_projection_matrix;
mutable std::optional<Mat4X4Type> m_projection_matrix;
mutable std::optional<Mat4X4Type> m_view_matrix;
float m_far_plane_distance; float m_far_plane_distance;
float m_near_plane_distance; float m_near_plane_distance;

View File

@@ -16,19 +16,28 @@ namespace omath
float value{}; float value{};
}; };
class Color final : public Vector4<float> class Color final
{ {
Vector4<float> m_value;
public: public:
constexpr Color(const float r, const float g, const float b, const float a) noexcept: Vector4(r, g, b, a) constexpr const Vector4<float>& value() const
{ {
clamp(0.f, 1.f); return m_value;
}
constexpr Color(const float r, const float g, const float b, const float a) noexcept: m_value(r, g, b, a)
{
m_value.clamp(0.f, 1.f);
} }
constexpr explicit Color(const Vector4<float>& value) : m_value(value)
{
m_value.clamp(0.f, 1.f);
}
constexpr explicit Color() noexcept = default; constexpr explicit Color() noexcept = default;
[[nodiscard]] [[nodiscard]]
constexpr static Color from_rgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) noexcept constexpr static Color from_rgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) noexcept
{ {
return Color{Vector4(r, g, b, a) / 255.f}; return Color(Vector4<float>(r, g, b, a) / 255.f);
} }
[[nodiscard]] [[nodiscard]]
@@ -82,9 +91,9 @@ namespace omath
{ {
Hsv hsv_data; Hsv hsv_data;
const float& red = x; const float& red = m_value.x;
const float& green = y; const float& green = m_value.y;
const float& blue = z; const float& blue = m_value.z;
const float max = std::max({red, green, blue}); const float max = std::max({red, green, blue});
const float min = std::min({red, green, blue}); const float min = std::min({red, green, blue});
@@ -109,11 +118,6 @@ namespace omath
return hsv_data; return hsv_data;
} }
constexpr explicit Color(const Vector4& vec) noexcept: Vector4(vec)
{
clamp(0.f, 1.f);
}
constexpr void set_hue(const float hue) noexcept constexpr void set_hue(const float hue) noexcept
{ {
auto hsv = to_hsv(); auto hsv = to_hsv();
@@ -141,7 +145,7 @@ namespace omath
constexpr Color blend(const Color& other, float ratio) const noexcept constexpr Color blend(const Color& other, float ratio) const noexcept
{ {
ratio = std::clamp(ratio, 0.f, 1.f); ratio = std::clamp(ratio, 0.f, 1.f);
return Color(*this * (1.f - ratio) + other * ratio); return Color(this->m_value * (1.f - ratio) + other.m_value * ratio);
} }
[[nodiscard]] static constexpr Color red() [[nodiscard]] static constexpr Color red()
@@ -160,16 +164,26 @@ namespace omath
[[nodiscard]] [[nodiscard]]
ImColor to_im_color() const noexcept ImColor to_im_color() const noexcept
{ {
return {to_im_vec4()}; return {m_value.to_im_vec4()};
} }
#endif #endif
[[nodiscard]] std::string to_string() const noexcept [[nodiscard]] std::string to_string() const noexcept
{ {
return std::format("[r:{}, g:{}, b:{}, a:{}]", return std::format("[r:{}, g:{}, b:{}, a:{}]",
static_cast<int>(x * 255.f), static_cast<int>(m_value.x * 255.f),
static_cast<int>(y * 255.f), static_cast<int>(m_value.y * 255.f),
static_cast<int>(z * 255.f), static_cast<int>(m_value.z * 255.f),
static_cast<int>(w * 255.f)); static_cast<int>(m_value.w * 255.f));
}
[[nodiscard]] std::string to_rgbf_string() const noexcept
{
return std::format("[r:{}, g:{}, b:{}, a:{}]",
m_value.x, m_value.y, m_value.z, m_value.w);
}
[[nodiscard]] std::string to_hsv_string() const noexcept
{
const auto [hue, saturation, value] = to_hsv();
return std::format("[h:{}, s:{}, v:{}]", hue, saturation, value);
} }
[[nodiscard]] std::wstring to_wstring() const noexcept [[nodiscard]] std::wstring to_wstring() const noexcept
{ {
@@ -188,23 +202,55 @@ namespace omath
template<> template<>
struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp) struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp)
{ {
[[nodiscard]] enum class ColorFormat { rgb, rgbf, hsv };
static constexpr auto parse(const std::format_parse_context& ctx) ColorFormat color_format = ColorFormat::rgb;
constexpr auto parse(std::format_parse_context& ctx)
{ {
return ctx.begin(); const auto it = ctx.begin();
const auto end = ctx.end();
if (it == end || *it == '}')
return it;
const std::string_view spec(it, end);
if (spec.starts_with("rgbf"))
{
color_format = ColorFormat::rgbf;
return it + 4;
}
if (spec.starts_with("rgb"))
{
color_format = ColorFormat::rgb;
return it + 3;
}
if (spec.starts_with("hsv"))
{
color_format = ColorFormat::hsv;
return it + 3;
}
throw std::format_error("Invalid format specifier for omath::Color. Use rgb, rgbf, or hsv.");
} }
template<class FormatContext> template<class FormatContext>
[[nodiscard]] auto format(const omath::Color& col, FormatContext& ctx) const
static auto format(const omath::Color& col, FormatContext& ctx)
{ {
if constexpr (std::is_same_v<typename FormatContext::char_type, char>) std::string str;
return std::format_to(ctx.out(), "{}", col.to_string()); switch (color_format)
if constexpr (std::is_same_v<typename FormatContext::char_type, wchar_t>) {
return std::format_to(ctx.out(), L"{}", col.to_wstring()); case ColorFormat::rgb: str = col.to_string(); break;
case ColorFormat::rgbf: str = col.to_rgbf_string(); break;
case ColorFormat::hsv: str = col.to_hsv_string(); break;
}
if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
return std::format_to(ctx.out(), "{}", str);
if constexpr (std::is_same_v<typename FormatContext::char_type, wchar_t>)
return std::format_to(ctx.out(), L"{}", std::wstring(str.cbegin(), str.cend()));
if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>) if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>)
return std::format_to(ctx.out(), u8"{}", col.to_u8string()); return std::format_to(ctx.out(), u8"{}", std::u8string(str.cbegin(), str.cend()));
std::unreachable(); std::unreachable();
} }

View File

@@ -0,0 +1,25 @@
//
// Created by Copilot on 04.02.2026.
//
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
class MachOPatternScanner final
{
public:
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
};
} // namespace omath

69
pixi.toml Normal file
View File

@@ -0,0 +1,69 @@
[workspace]
name = "omath"
version = "5.7.0"
description = "Cross-platform modern general purpose math library written in C++23 that suitable for cheat/game development."
authors = [
"orange-cpp <orange_github@proton.me>"
]
license = "Zlib"
license-file = "LICENSE"
readme = "README.md"
documentation = "http://libomath.org"
repository = "https://github.com/orange-cpp/omath"
channels = ["conda-forge"]
platforms = ["win-64", "linux-64", "linux-aarch64", "osx-64", "osx-arm64"]
[tasks]
format = { cwd = "pixi", cmd = "cmake -P fmt.cmake" }
configure = { cmd = "cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DOMATH_USE_AVX2=OFF -DOMATH_IMGUI_INTEGRATION=ON -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=ON -DOMATH_BUILD_EXAMPLES=ON .", depends-on = ["format"] }
build = { cmd = "cmake --build build --config Debug -j", depends-on = ["configure"] }
examples = { cwd = "pixi", cmd = "cmake -DCMAKE_BUILD_TYPE=Debug -P run.examples.cmake", depends-on = ["build"] }
tests = { cwd = "pixi", cmd = "cmake -DCMAKE_BUILD_TYPE=Debug -P run.unit.tests.cmake", depends-on = ["build"] }
benchmark = { cwd = "pixi", cmd = "cmake -DCMAKE_BUILD_TYPE=Debug -P run.benchmark.cmake", depends-on = ["build"] }
[dependencies]
benchmark = ">=1.9.5,<2"
ccache = ">=4.12.2,<5"
cmake = ">=4.2.3,<5"
cmake-format = ">=0.6.13,<0.7"
cxx-compiler = ">=1.11.0,<2"
imgui = ">=1.92.3,<2"
gtest = ">=1.17.0,<2"
glew = ">=2.3.0,<3"
glfw = ">=3.4,<4"
ninja = ">=1.13.2,<2"
[target.linux-64.dependencies]
mesa-libgl-devel-cos7-x86_64 = ">=18.3.4,<19"
xorg-x11-server-xvfb-cos7-x86_64 = ">=1.20.4,<2"
[target.linux-64.activation.env]
__GLX_VENDOR_LIBRARY_NAME = "mesa"
EGL_PLATFORM = "x11"
GLFW_PLATFORM = "x11"
[target.linux-64.tasks]
examples = { cwd = "pixi", cmd = "xvfb-run -a -s '-screen 0 1024x768x24 +extension GLX +render' cmake -DCMAKE_BUILD_TYPE=Debug -P run.examples.cmake", depends-on = ["build"] }
[target.win-64.dependencies]
mesa-libgl-devel-cos7-x86_64 = ">=18.3.4,<19"
[target.osx-64.dependencies]
mesa-libgl-devel-cos7-x86_64 = ">=18.3.4,<19"
[target.osx-arm64.dependencies]
mesa-libgl-devel-cos7-aarch64 = ">=18.3.4,<19"
[target.linux-aarch64.dependencies]
mesa-libgl-devel-cos7-aarch64 = ">=18.3.4,<19"
xorg-x11-server-xvfb-cos7-aarch64 = ">=1.20.4,<2"
[target.linux-aarch64.activation.env]
__GLX_VENDOR_LIBRARY_NAME = "mesa"
EGL_PLATFORM = "x11"
GLFW_PLATFORM = "x11"
[target.linux-aarch64.tasks]
examples = { cwd = "pixi", cmd = "xvfb-run -a -s '-screen 0 1024x768x24 +extension GLX +render' cmake -DCMAKE_BUILD_TYPE=Debug -P run.examples.cmake", depends-on = ["build"] }

36
pixi/fmt.cmake Normal file
View File

@@ -0,0 +1,36 @@
# cmake/Format.cmake
# Find cmake-format executable
find_program(CMAKE_FORMAT_EXECUTABLE NAMES cmake-format)
if(NOT CMAKE_FORMAT_EXECUTABLE)
message(FATAL_ERROR "cmake-format not found. Please install it first.")
endif()
# Get the project root directory (assuming this script is in cmake/
# subdirectory)
get_filename_component(PROJECT_ROOT "../${CMAKE_CURRENT_LIST_DIR}" ABSOLUTE)
# Iterate over all files in the project root
file(GLOB_RECURSE ALL_FILES "${PROJECT_ROOT}/*")
foreach(FILE ${ALL_FILES})
# Basic ignores for common directories to avoid formatting external/build
# files Note: We check for substrings in the full path
if("${FILE}" MATCHES "/\\.git/"
OR "${FILE}" MATCHES "/build/"
OR "${FILE}" MATCHES "/cmake-build/"
OR "${FILE}" MATCHES "/\\.pixi/"
OR "${FILE}" MATCHES "/vcpkg_installed/")
continue()
endif()
get_filename_component(FILENAME "${FILE}" NAME)
# Check if file ends with .cmake or matches exactly to CMakeLists.txt
if("${FILENAME}" STREQUAL "CMakeLists.txt" OR "${FILENAME}" MATCHES "\\.cmake$")
message(STATUS "Formatting ${FILE}")
execute_process(COMMAND ${CMAKE_FORMAT_EXECUTABLE} --config-files
"${PROJECT_ROOT}/.cmake-format" -i "${FILE}")
endif()
endforeach()

63
pixi/run.benchmark.cmake Normal file
View File

@@ -0,0 +1,63 @@
# cmake/run.examples.cmake
# Get the project root directory (assuming this script is in cmake/ subdirectory)
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
# Default to Debug if CMAKE_BUILD_TYPE is not specified
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
else()
message(STATUS "CMAKE_BUILD_TYPE is set to: ${CMAKE_BUILD_TYPE}")
endif()
# Define the directory where executables are located
# Based on CMakeLists.txt: "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
set(EXAMPLES_BIN_DIR "${PROJECT_ROOT}/out/${CMAKE_BUILD_TYPE}")
if(NOT EXISTS "${EXAMPLES_BIN_DIR}")
message(
FATAL_ERROR
"Examples binary directory not found: ${EXAMPLES_BIN_DIR}. Please build the project first."
)
endif()
message(STATUS "Looking for benchmark executables in: ${EXAMPLES_BIN_DIR}")
# Find all files starting with "omath_benchmark"
file(GLOB EXAMPLE_FILES "${EXAMPLES_BIN_DIR}/omath_benchmark*")
foreach(EXAMPLE_PATH ${EXAMPLE_FILES})
# Skip directories
if(IS_DIRECTORY "${EXAMPLE_PATH}")
continue()
endif()
get_filename_component(FILENAME "${EXAMPLE_PATH}" NAME)
get_filename_component(EXTENSION "${EXAMPLE_PATH}" EXT)
# Filter out potential debug symbols or non-executable artifacts
if(EXTENSION STREQUAL ".pdb" OR EXTENSION STREQUAL ".ilk" OR EXTENSION STREQUAL ".obj")
continue()
endif()
# On Windows, we expect .exe
if(MSVC AND NOT EXTENSION STREQUAL ".exe")
continue()
endif()
# On Linux/macOS, check permissions or just try to run it.
message(STATUS "-------------------------------------------------")
message(STATUS "Running benchmark: ${FILENAME}")
message(STATUS "-------------------------------------------------")
execute_process(COMMAND "${EXAMPLE_PATH}" WORKING_DIRECTORY "${PROJECT_ROOT}"
RESULT_VARIABLE EXIT_CODE)
if(NOT EXIT_CODE EQUAL 0)
message(WARNING "Benchmark ${FILENAME} exited with error code: ${EXIT_CODE}")
else()
message(STATUS "Benchmark ${FILENAME} completed successfully.")
endif()
endforeach()

63
pixi/run.examples.cmake Normal file
View File

@@ -0,0 +1,63 @@
# cmake/run.examples.cmake
# Get the project root directory (assuming this script is in cmake/ subdirectory)
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
# Default to Debug if CMAKE_BUILD_TYPE is not specified
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
else()
message(STATUS "CMAKE_BUILD_TYPE is set to: ${CMAKE_BUILD_TYPE}")
endif()
# Define the directory where executables are located
# Based on CMakeLists.txt: "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
set(EXAMPLES_BIN_DIR "${PROJECT_ROOT}/out/${CMAKE_BUILD_TYPE}")
if(NOT EXISTS "${EXAMPLES_BIN_DIR}")
message(
FATAL_ERROR
"Examples binary directory not found: ${EXAMPLES_BIN_DIR}. Please build the project first."
)
endif()
message(STATUS "Looking for example executables in: ${EXAMPLES_BIN_DIR}")
# Find all files starting with "example_"
file(GLOB EXAMPLE_FILES "${EXAMPLES_BIN_DIR}/example_*")
foreach(EXAMPLE_PATH ${EXAMPLE_FILES})
# Skip directories
if(IS_DIRECTORY "${EXAMPLE_PATH}")
continue()
endif()
get_filename_component(FILENAME "${EXAMPLE_PATH}" NAME)
get_filename_component(EXTENSION "${EXAMPLE_PATH}" EXT)
# Filter out potential debug symbols or non-executable artifacts
if(EXTENSION STREQUAL ".pdb" OR EXTENSION STREQUAL ".ilk" OR EXTENSION STREQUAL ".obj")
continue()
endif()
# On Windows, we expect .exe
if(MSVC AND NOT EXTENSION STREQUAL ".exe")
continue()
endif()
# On Linux/macOS, check permissions or just try to run it.
message(STATUS "-------------------------------------------------")
message(STATUS "Running example: ${FILENAME}")
message(STATUS "-------------------------------------------------")
execute_process(COMMAND "${EXAMPLE_PATH}" WORKING_DIRECTORY "${PROJECT_ROOT}"
RESULT_VARIABLE EXIT_CODE)
if(NOT EXIT_CODE EQUAL 0)
message(WARNING "Example ${FILENAME} exited with error code: ${EXIT_CODE}")
else()
message(STATUS "Example ${FILENAME} completed successfully.")
endif()
endforeach()

63
pixi/run.unit.tests.cmake Normal file
View File

@@ -0,0 +1,63 @@
# cmake/run.examples.cmake
# Get the project root directory (assuming this script is in cmake/ subdirectory)
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
# Default to Debug if CMAKE_BUILD_TYPE is not specified
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
else()
message(STATUS "CMAKE_BUILD_TYPE is set to: ${CMAKE_BUILD_TYPE}")
endif()
# Define the directory where executables are located
# Based on CMakeLists.txt: "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
set(EXAMPLES_BIN_DIR "${PROJECT_ROOT}/out/${CMAKE_BUILD_TYPE}")
if(NOT EXISTS "${EXAMPLES_BIN_DIR}")
message(
FATAL_ERROR
"Examples binary directory not found: ${EXAMPLES_BIN_DIR}. Please build the project first."
)
endif()
message(STATUS "Looking for unit test executables in: ${EXAMPLES_BIN_DIR}")
# Find all files starting with "unit_tests"
file(GLOB EXAMPLE_FILES "${EXAMPLES_BIN_DIR}/unit_tests*")
foreach(EXAMPLE_PATH ${EXAMPLE_FILES})
# Skip directories
if(IS_DIRECTORY "${EXAMPLE_PATH}")
continue()
endif()
get_filename_component(FILENAME "${EXAMPLE_PATH}" NAME)
get_filename_component(EXTENSION "${EXAMPLE_PATH}" EXT)
# Filter out potential debug symbols or non-executable artifacts
if(EXTENSION STREQUAL ".pdb" OR EXTENSION STREQUAL ".ilk" OR EXTENSION STREQUAL ".obj")
continue()
endif()
# On Windows, we expect .exe
if(MSVC AND NOT EXTENSION STREQUAL ".exe")
continue()
endif()
# On Linux/macOS, check permissions or just try to run it.
message(STATUS "-------------------------------------------------")
message(STATUS "Running unit_tests: ${FILENAME}")
message(STATUS "-------------------------------------------------")
execute_process(COMMAND "${EXAMPLE_PATH}" WORKING_DIRECTORY "${PROJECT_ROOT}"
RESULT_VARIABLE EXIT_CODE)
if(NOT EXIT_CODE EQUAL 0)
message(WARNING "Example ${FILENAME} exited with error code: ${EXIT_CODE}")
else()
message(STATUS "Example ${FILENAME} completed successfully.")
endif()
endforeach()

15
scripts/cmake-format.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
# Format all CMakeLists.txt and *.cmake files in the repo (excluding common build dirs)
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
find . \
-path "./build" -prune -o \
-path "./cmake-build*" -prune -o \
-path "./out" -prune -o \
-path "./.git" -prune -o \
\( -name "CMakeLists.txt" -o -name "*.cmake" \) -print0 \
| xargs -0 cmake-format -i

View File

@@ -1,61 +0,0 @@
//
// Created by Orange on 11/13/2024.
//
#include "omath/collision/line_tracer.hpp"
namespace omath::collision
{
bool LineTracer::can_trace_line(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept
{
return get_ray_hit_point(ray, triangle) == ray.end;
}
Vector3<float> Ray::direction_vector() const noexcept
{
return end - start;
}
Vector3<float> Ray::direction_vector_normalized() const noexcept
{
return direction_vector().normalized();
}
Vector3<float> LineTracer::get_ray_hit_point(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept
{
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
const auto side_a = triangle.side_a_vector();
const auto side_b = triangle.side_b_vector();
const auto ray_dir = ray.direction_vector();
const auto p = ray_dir.cross(side_b);
const auto det = side_a.dot(p);
if (std::abs(det) < k_epsilon)
return ray.end;
const auto inv_det = 1.0f / det;
const auto t = ray.start - triangle.m_vertex2;
const auto u = t.dot(p) * inv_det;
if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon))
return ray.end;
const auto q = t.cross(side_a);
// ReSharper disable once CppTooWideScopeInitStatement
const auto v = ray_dir.dot(q) * inv_det;
if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon))
return ray.end;
const auto t_hit = side_b.dot(q) * inv_det;
if (ray.infinite_length && t_hit <= k_epsilon)
return ray.end;
if (t_hit <= k_epsilon || t_hit > 1.0f - k_epsilon)
return ray.end;
return ray.start + ray_dir * t_hit;
}
} // namespace omath::collision

View File

@@ -0,0 +1,42 @@
//
// Created by Vlad on 3/22/2025.
//
#include "omath/engines/cry_engine/formulas.hpp"
namespace omath::cry_engine
{
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_forward);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_up);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.yaw)
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.roll)
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far) noexcept
{
return mat_perspective_left_handed(field_of_view, aspect_ratio, near, far);
}
} // namespace omath::unity_engine

View File

@@ -0,0 +1,26 @@
//
// Created by Vlad on 8/11/2025.
//
#include "omath/engines/cry_engine/traits/camera_trait.hpp"
namespace omath::cry_engine
{
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(std::asin(direction.z)),
YawAngle::from_radians(-std::atan2(direction.x, direction.y)), RollAngle::from_radians(0.f)};
}
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return cry_engine::calc_view_matrix(angles, cam_origin);
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far);
}
} // namespace omath::unity_engine

View File

@@ -0,0 +1,349 @@
//
// Created by Copilot on 04.02.2026.
//
#include "omath/utility/macho_pattern_scan.hpp"
#include "omath/utility/pattern_scan.hpp"
#include <cstring>
#include <fstream>
#include <variant>
#include <vector>
#pragma pack(push, 1)
namespace
{
// Mach-O magic numbers
constexpr std::uint32_t mh_magic_32 = 0xFEEDFACE;
constexpr std::uint32_t mh_magic_64 = 0xFEEDFACF;
constexpr std::uint32_t mh_cigam_32 = 0xCEFAEDFE; // Byte-swapped 32-bit
constexpr std::uint32_t mh_cigam_64 = 0xCFFAEDFE; // Byte-swapped 64-bit
// Load command types
constexpr std::uint32_t lc_segment = 0x1;
constexpr std::uint32_t lc_segment_64 = 0x19;
// ReSharper disable CppDeclaratorNeverUsed
// Mach-O header for 32-bit
struct MachHeader32 final
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
};
// Mach-O header for 64-bit
struct MachHeader64 final
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
std::uint32_t reserved;
};
// Load command header
struct LoadCommand final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
};
// Segment command for 32-bit
struct SegmentCommand32 final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint32_t vmaddr;
std::uint32_t vmsize;
std::uint32_t fileoff;
std::uint32_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
// Segment command for 64-bit
struct SegmentCommand64 final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint64_t vmaddr;
std::uint64_t vmsize;
std::uint64_t fileoff;
std::uint64_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
// Section for 32-bit
struct Section32 final
{
char sectname[16];
char segname[16];
std::uint32_t addr;
std::uint32_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
};
// Section for 64-bit
struct Section64 final
{
char sectname[16];
char segname[16];
std::uint64_t addr;
std::uint64_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
std::uint32_t reserved3;
};
// ReSharper enable CppDeclaratorNeverUsed
#pragma pack(pop)
enum class MachOArch : std::int8_t
{
x32,
x64,
};
struct ExtractedSection final
{
std::uintptr_t virtual_base_addr{};
std::uintptr_t raw_base_addr{};
std::vector<std::byte> data;
};
[[nodiscard]]
std::optional<MachOArch> get_macho_arch(std::fstream& file)
{
std::uint32_t magic{};
const std::streampos backup_pos = file.tellg();
file.seekg(0, std::ios_base::beg);
file.read(reinterpret_cast<char*>(&magic), sizeof(magic));
file.seekg(backup_pos, std::ios_base::beg);
if (magic == mh_magic_64 || magic == mh_cigam_64)
return MachOArch::x64;
if (magic == mh_magic_32 || magic == mh_cigam_32)
return MachOArch::x32;
return std::nullopt;
}
[[nodiscard]]
bool is_macho_file(std::fstream& file)
{
return get_macho_arch(file).has_value();
}
[[nodiscard]]
std::string_view get_section_name(const char* sectname)
{
// Mach-O section names are fixed 16-byte arrays, not necessarily null-terminated
return std::string_view(sectname, std::min(std::strlen(sectname), std::size_t{16}));
}
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
std::optional<ExtractedSection> extract_section_impl(std::fstream& file, const std::string_view& section_name)
{
HeaderType header{};
file.seekg(0, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&header), sizeof(header))) [[unlikely]]
return std::nullopt;
std::streamoff cmd_offset = sizeof(header);
for (std::uint32_t i = 0; i < header.ncmds; ++i)
{
LoadCommand lc{};
file.seekg(cmd_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&lc), sizeof(lc))) [[unlikely]]
return std::nullopt;
if (lc.cmd != segment_cmd)
{
cmd_offset += static_cast<std::streamoff>(lc.cmdsize);
continue;
}
SegmentType segment{};
file.seekg(cmd_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&segment), sizeof(segment))) [[unlikely]]
return std::nullopt;
if (!segment.nsects)
{
cmd_offset += static_cast<std::streamoff>(lc.cmdsize);
continue;
}
std::streamoff sect_offset = cmd_offset + static_cast<std::streamoff>(sizeof(segment));
for (std::uint32_t j = 0; j < segment.nsects; ++j)
{
SectionType section{};
file.seekg(sect_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&section), sizeof(section))) [[unlikely]]
return std::nullopt;
if (get_section_name(section.sectname) != section_name)
{
sect_offset += static_cast<std::streamoff>(sizeof(section));
continue;
}
ExtractedSection out;
out.virtual_base_addr = static_cast<std::uintptr_t>(section.addr);
out.raw_base_addr = static_cast<std::uintptr_t>(section.offset);
out.data.resize(static_cast<std::size_t>(section.size));
file.seekg(static_cast<std::streamoff>(section.offset), std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(out.data.data()), static_cast<std::streamsize>(out.data.size())))
[[unlikely]]
return std::nullopt;
return out;
}
}
return std::nullopt;
}
[[nodiscard]]
std::optional<ExtractedSection> get_macho_section_by_name(const std::filesystem::path& path,
const std::string_view& section_name)
{
std::fstream file(path, std::ios::binary | std::ios::in);
if (!file.is_open()) [[unlikely]]
return std::nullopt;
if (!is_macho_file(file)) [[unlikely]]
return std::nullopt;
const auto arch = get_macho_arch(file);
if (!arch.has_value()) [[unlikely]]
return std::nullopt;
if (arch.value() == MachOArch::x64)
return extract_section_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(file, section_name);
return extract_section_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(file, section_name);
}
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
const std::string_view target_section_name)
{
const auto* header = reinterpret_cast<const HeaderType*>(base);
std::size_t cmd_offset = sizeof(HeaderType);
for (std::uint32_t i = 0; i < header->ncmds; ++i)
{
const auto* lc = reinterpret_cast<const LoadCommand*>(base + cmd_offset);
if (lc->cmd != segment_cmd)
{
cmd_offset += lc->cmdsize;
continue;
}
const auto* segment = reinterpret_cast<const SegmentType*>(base + cmd_offset);
std::size_t sect_offset = cmd_offset + sizeof(SegmentType);
for (std::uint32_t j = 0; j < segment->nsects; ++j)
{
const auto* section = reinterpret_cast<const SectionType*>(base + sect_offset);
if (get_section_name(section->sectname) != target_section_name && section->size > 0)
{
sect_offset += sizeof(SectionType);
continue;
}
const auto* section_begin = base + static_cast<std::size_t>(section->addr);
const auto* section_end = section_begin + static_cast<std::size_t>(section->size);
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (scan_result != section_end)
return reinterpret_cast<std::uintptr_t>(scan_result);
}
}
return std::nullopt;
}
} // namespace
namespace omath
{
std::optional<std::uintptr_t>
MachOPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
// Read magic to determine architecture
std::uint32_t magic{};
std::memcpy(&magic, base, sizeof(magic));
if (magic == mh_magic_64 || magic == mh_cigam_64)
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, pattern,
target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, pattern,
target_section_name);
return std::nullopt;
}
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
const auto macho_section = get_macho_section_by_name(path_to_file, target_section_name);
if (!macho_section.has_value()) [[unlikely]]
return std::nullopt;
const auto scan_result =
PatternScanner::scan_for_pattern(macho_section->data.cbegin(), macho_section->data.cend(), pattern);
if (scan_result == macho_section->data.cend())
return std::nullopt;
const auto offset = std::distance(macho_section->data.begin(), scan_result);
return SectionScanResult{.virtual_base_addr = macho_section->virtual_base_addr,
.raw_base_addr = macho_section->raw_base_addr,
.target_offset = offset};
}
} // namespace omath

View File

@@ -237,7 +237,7 @@ namespace
variant); variant);
} }
struct ExtractedSection struct ExtractedSection final
{ {
std::uintptr_t virtual_base_addr; std::uintptr_t virtual_base_addr;
std::uintptr_t raw_base_addr; std::uintptr_t raw_base_addr;

View File

@@ -7,14 +7,15 @@ include(GoogleTest)
file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES}) add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ${PROJECT_NAME}
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
CXX_STANDARD 23 CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON) CXX_STANDARD_REQUIRED ON)
if (TARGET gtest) # GTest is being linked as submodule if(TARGET gtest) # GTest is being linked as submodule
target_link_libraries(${PROJECT_NAME} PRIVATE gtest gtest_main omath::omath) target_link_libraries(${PROJECT_NAME} PRIVATE gtest gtest_main omath::omath)
else() # GTest is being linked as vcpkg package else() # GTest is being linked as vcpkg package
find_package(GTest CONFIG REQUIRED) find_package(GTest CONFIG REQUIRED)
@@ -30,7 +31,8 @@ if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(${PROJECT_NAME}) omath_setup_valgrind(${PROJECT_NAME})
endif() endif()
# Skip test discovery for Android/iOS builds or when cross-compiling - binaries cannot run on host # Skip test discovery for Android/iOS builds or when cross-compiling - binaries
if (NOT (ANDROID OR IOS OR EMSCRIPTEN)) # cannot run on host
if(NOT (ANDROID OR IOS OR EMSCRIPTEN))
gtest_discover_tests(${PROJECT_NAME}) gtest_discover_tests(${PROJECT_NAME})
endif() endif()

View File

@@ -0,0 +1,240 @@
//
// Created by Vladislav on 19.02.2026.
//
#include <gtest/gtest.h>
#include <omath/engines/cry_engine/camera.hpp>
#include <omath/engines/cry_engine/constants.hpp>
#include <omath/engines/cry_engine/formulas.hpp>
#include <random>
#include <ranges>
using namespace omath;
TEST(unit_test_cry_engine, look_at_forward)
{
const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = cry_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_forward.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_cry_engine, look_at_right)
{
const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = cry_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_cry_engine, look_at_up)
{
const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = cry_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_cry_engine, look_at_back)
{
const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, -cry_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = cry_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-cry_engine::k_abs_forward).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_cry_engine, look_at_left)
{
const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, -cry_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = cry_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-cry_engine::k_abs_right).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_cry_engine, look_at_down)
{
const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, -cry_engine::k_abs_up);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = cry_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-cry_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_cry_engine, RightVector)
{
const auto right = omath::cry_engine::right_vector({});
EXPECT_EQ(right, omath::cry_engine::k_abs_right);
}
TEST(unit_test_cry_engine, UpVector)
{
const auto up = omath::cry_engine::up_vector({});
EXPECT_EQ(up, omath::cry_engine::k_abs_up);
}
TEST(unit_test_cry_engine, ProjectTargetMovedFromCamera)
{
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
const auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.01f, 1000.f);
for (float distance = 0.02f; distance < 100.f; distance += 0.01f)
{
const auto projected = cam.world_to_screen({0, distance, 0});
EXPECT_TRUE(projected.has_value());
if (!projected.has_value())
continue;
EXPECT_NEAR(projected->x, 640, 0.00001f);
EXPECT_NEAR(projected->y, 360, 0.00001f);
}
}
TEST(unit_test_cry_engine, CameraSetAndGetFov)
{
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f);
EXPECT_EQ(cam.get_field_of_view().as_degrees(), 90.f);
cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f));
EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f);
}
TEST(unit_test_cry_engine, CameraSetAndGetOrigin)
{
auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f);
EXPECT_EQ(cam.get_origin(), omath::Vector3<float>{});
cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f));
EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f);
}
TEST(unit_test_cry_engine, loook_at_random_all_axis)
{
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
std::uniform_real_distribution<float> dist(-1000.f, 1000.f);
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f);
std::size_t failed_points = 0;
for (int i = 0; i < 1000; i++)
{
const auto position_to_look = omath::Vector3<float>{dist(gen), dist(gen), dist(gen)};
if (cam.get_origin().distance_to(position_to_look) < 10)
continue;
cam.look_at(position_to_look);
auto projected_pos = cam.world_to_view_port(position_to_look);
EXPECT_TRUE(projected_pos.has_value());
if (!projected_pos)
continue;
if (std::abs(projected_pos->x - 0.f) >= 0.0001f || std::abs(projected_pos->y - 0.f) >= 0.0001f)
failed_points++;
}
EXPECT_LE(failed_points, 100);
}
TEST(unit_test_cry_engine, loook_at_random_x_axis)
{
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
std::uniform_real_distribution<float> dist(-1000.f, 1000.f);
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f);
std::size_t failed_points = 0;
for (int i = 0; i < 1000; i++)
{
const auto position_to_look = omath::Vector3<float>{dist(gen), 0.f, 0.f};
if (cam.get_origin().distance_to(position_to_look) < 10)
continue;
cam.look_at(position_to_look);
auto projected_pos = cam.world_to_view_port(position_to_look);
EXPECT_TRUE(projected_pos.has_value());
if (!projected_pos)
continue;
if (std::abs(projected_pos->x - 0.f) >= 0.001f || std::abs(projected_pos->y - 0.f) >= 0.001f)
failed_points++;
}
EXPECT_LE(failed_points, 100);
}
TEST(unit_test_cry_engine, loook_at_random_y_axis)
{
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
std::uniform_real_distribution<float> dist(-1000.f, 1000.f);
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f);
std::size_t failed_points = 0;
for (int i = 0; i < 1000; i++)
{
const auto position_to_look = omath::Vector3<float>{0.f, dist(gen), 0.f};
if (cam.get_origin().distance_to(position_to_look) < 10)
continue;
cam.look_at(position_to_look);
auto projected_pos = cam.world_to_view_port(position_to_look);
EXPECT_TRUE(projected_pos.has_value());
if (!projected_pos)
continue;
if (std::abs(projected_pos->x - 0.f) >= 0.01f || std::abs(projected_pos->y - 0.f) >= 0.01f)
failed_points++;
}
EXPECT_LE(failed_points, 100);
}
TEST(unit_test_cry_engine, loook_at_random_z_axis)
{
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
std::uniform_real_distribution<float> dist(-1000.f, 1000.f);
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f);
std::size_t failed_points = 0;
for (int i = 0; i < 1000; i++)
{
const auto position_to_look = omath::Vector3<float>{0.f, 0.f, dist(gen)};
if (cam.get_origin().distance_to(position_to_look) < 10)
continue;
cam.look_at(position_to_look);
auto projected_pos = cam.world_to_view_port(position_to_look);
EXPECT_TRUE(projected_pos.has_value());
if (!projected_pos)
continue;
if (std::abs(projected_pos->x - 0.f) >= 0.01f || std::abs(projected_pos->y - 0.f) >= 0.01f)
failed_points++;
}
EXPECT_LE(failed_points, 100);
}

View File

@@ -7,7 +7,127 @@
#include <omath/engines/frostbite_engine/formulas.hpp> #include <omath/engines/frostbite_engine/formulas.hpp>
#include <print> #include <print>
#include <random> #include <random>
#include <ranges>
TEST(unit_test_frostbite_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_frostbite_engine, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_frostbite_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_frostbite_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_frostbite_engine, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_frostbite_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
const auto cm_f = omath::frostbite_engine::units_to_centimeters(units_f);
const auto units_f_back = omath::frostbite_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
const auto cm_d = omath::frostbite_engine::units_to_centimeters(units_d);
const auto units_d_back = omath::frostbite_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::frostbite_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::frostbite_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::frostbite_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::frostbite_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::frostbite_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::frostbite_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::frostbite_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::frostbite_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_frostbite_engine, ConversionChainConsistency)
{
const double units = 424242.42;
const auto cm_direct = omath::frostbite_engine::units_to_centimeters(units);
const auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
const auto km_direct = omath::frostbite_engine::units_to_kilometers(units);
const auto km_via_meters = omath::frostbite_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_frostbite_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::frostbite_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_frostbite_engine, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::frostbite_engine::units_to_centimeters(units);
constexpr double m = omath::frostbite_engine::units_to_meters(units);
constexpr double km = omath::frostbite_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_frostbite_engine, ForwardVector) TEST(unit_test_frostbite_engine, ForwardVector)
{ {
const auto forward = omath::frostbite_engine::forward_vector({}); const auto forward = omath::frostbite_engine::forward_vector({});
@@ -234,3 +354,54 @@ TEST(unit_test_frostbite_engine, loook_at_random_z_axis)
} }
EXPECT_LE(failed_points, 100); EXPECT_LE(failed_points, 100);
} }
TEST(unit_test_frostbite_engine, look_at_right)
{
const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, omath::frostbite_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::frostbite_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), omath::frostbite_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_frostbite_engine, look_at_up)
{
const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, omath::frostbite_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::frostbite_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), omath::frostbite_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_frostbite_engine, look_at_back)
{
const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, -omath::frostbite_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::frostbite_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_forward).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_frostbite_engine, look_at_left)
{
const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, -omath::frostbite_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::frostbite_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_right).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_frostbite_engine, look_at_down)
{
const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, -omath::frostbite_engine::k_abs_up);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::frostbite_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}

View File

@@ -6,6 +6,7 @@
#include <omath/engines/iw_engine/constants.hpp> #include <omath/engines/iw_engine/constants.hpp>
#include <omath/engines/iw_engine/formulas.hpp> #include <omath/engines/iw_engine/formulas.hpp>
#include <random> #include <random>
#include <ranges>
TEST(unit_test_iw_engine, ForwardVector) TEST(unit_test_iw_engine, ForwardVector)
{ {
@@ -224,3 +225,59 @@ TEST(unit_test_iw_engine, loook_at_random_z_axis)
} }
EXPECT_LE(failed_points, 100); EXPECT_LE(failed_points, 100);
} }
TEST(unit_test_iw_engine, look_at_forward)
{
const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, omath::iw_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::iw_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::iw_engine::k_abs_forward.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_iw_engine, look_at_right)
{
const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, omath::iw_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::iw_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::iw_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_iw_engine, look_at_up)
{
const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, omath::iw_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::iw_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::iw_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_iw_engine, look_at_back)
{
const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, -omath::iw_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::iw_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::iw_engine::k_abs_forward).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_iw_engine, look_at_left)
{
const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, -omath::iw_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::iw_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::iw_engine::k_abs_right).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_iw_engine, look_at_down)
{
const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, -omath::iw_engine::k_abs_up);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::iw_engine::forward_vector(angles);
EXPECT_NEAR(dir_vector.z, -0.99984f, 0.0001f);
EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f);
EXPECT_NEAR(dir_vector.y, 0.f, 0.001f);
}

View File

@@ -6,7 +6,127 @@
#include <omath/engines/opengl_engine/constants.hpp> #include <omath/engines/opengl_engine/constants.hpp>
#include <omath/engines/opengl_engine/formulas.hpp> #include <omath/engines/opengl_engine/formulas.hpp>
#include <random> #include <random>
#include <ranges>
TEST(unit_test_opengl, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_opengl, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_opengl, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_opengl, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_opengl, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_opengl, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_opengl, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
const auto cm_f = omath::opengl_engine::units_to_centimeters(units_f);
const auto units_f_back = omath::opengl_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
const auto cm_d = omath::opengl_engine::units_to_centimeters(units_d);
const auto units_d_back = omath::opengl_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_opengl, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
const auto m_f = omath::opengl_engine::units_to_meters(units_f);
const auto units_f_back = omath::opengl_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
const auto m_d = omath::opengl_engine::units_to_meters(units_d);
const auto units_d_back = omath::opengl_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_opengl, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
const auto km_f = omath::opengl_engine::units_to_kilometers(units_f);
const auto units_f_back = omath::opengl_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
const auto km_d = omath::opengl_engine::units_to_kilometers(units_d);
const auto units_d_back = omath::opengl_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_opengl, ConversionChainConsistency)
{
const double units = 424242.42;
const auto cm_direct = omath::opengl_engine::units_to_centimeters(units);
const auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
const auto km_direct = omath::opengl_engine::units_to_kilometers(units);
const auto km_via_meters = omath::opengl_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_opengl, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::opengl_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_opengl, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::opengl_engine::units_to_centimeters(units);
constexpr double m = omath::opengl_engine::units_to_meters(units);
constexpr double km = omath::opengl_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_opengl, ForwardVector) TEST(unit_test_opengl, ForwardVector)
{ {
const auto forward = omath::opengl_engine::forward_vector({}); const auto forward = omath::opengl_engine::forward_vector({});
@@ -219,3 +339,59 @@ TEST(unit_test_opengl_engine, loook_at_random_z_axis)
} }
EXPECT_LE(failed_points, 100); EXPECT_LE(failed_points, 100);
} }
TEST(unit_test_opengl_engine, look_at_forward)
{
const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, omath::opengl_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::opengl_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::opengl_engine::k_abs_forward.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_opengl_engine, look_at_right)
{
const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, omath::opengl_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::opengl_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::opengl_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_opengl_engine, look_at_up)
{
const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, omath::opengl_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::opengl_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::opengl_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_opengl_engine, look_at_back)
{
const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, -omath::opengl_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::opengl_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_forward).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_opengl_engine, look_at_left)
{
const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, -omath::opengl_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::opengl_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_right).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_opengl_engine, look_at_down)
{
const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, -omath::opengl_engine::k_abs_up);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::opengl_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}

View File

@@ -6,6 +6,130 @@
#include <omath/engines/source_engine/constants.hpp> #include <omath/engines/source_engine/constants.hpp>
#include <omath/engines/source_engine/formulas.hpp> #include <omath/engines/source_engine/formulas.hpp>
#include <random> #include <random>
#include <ranges>
TEST(unit_test_source_engine_units, HammerUnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(1.0f), 2.54f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(10.0f), 25.4f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(-2.0f), -5.08f);
}
TEST(unit_test_source_engine_units, HammerUnitsToMeters_BasicValues)
{
EXPECT_NEAR(omath::source_engine::units_to_meters(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(1.0), 0.0254, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(100.0), 2.54, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(-4.0), -0.1016, 1e-12);
}
TEST(unit_test_source_engine_units, HammerUnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::source_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(1.0), 0.0000254, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(1000.0), 0.0254, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(-10.0), -0.000254, 1e-15);
}
TEST(unit_test_source_engine_units, CentimetersToHammerUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::source_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(2.54f), 1.0f, 1e-6f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(25.4f), 10.0f, 1e-5f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(-5.08f), -2.0f, 1e-6f);
}
TEST(unit_test_source_engine_units, MetersToHammerUnits_BasicValues)
{
EXPECT_NEAR(omath::source_engine::meters_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::source_engine::meters_to_units(0.0254), 1.0, 1e-12);
EXPECT_NEAR(omath::source_engine::meters_to_units(2.54), 100.0, 1e-10);
EXPECT_NEAR(omath::source_engine::meters_to_units(-0.0508), -2.0, 1e-12);
}
TEST(unit_test_source_engine_units, KilometersToHammerUnits_BasicValues)
{
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.0), 0.0, 1e-9);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.0000254), 1.0, 1e-9);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.00254), 100.0, 1e-7);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(-0.0000508), -2.0, 1e-9);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToCentimetersToHammer)
{
constexpr float hu_f = 123.456f;
constexpr auto cm_f = omath::source_engine::units_to_centimeters(hu_f);
constexpr auto hu_f_back = omath::source_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-5f);
constexpr double hu_d = -98765.4321;
constexpr auto cm_d = omath::source_engine::units_to_centimeters(hu_d);
constexpr auto hu_d_back = omath::source_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-10);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToMetersToHammer)
{
constexpr float hu_f = 2500.25f;
constexpr auto m_f = omath::source_engine::units_to_meters(hu_f);
constexpr auto hu_f_back = omath::source_engine::meters_to_units(m_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-4f);
constexpr double hu_d = -42000.125;
constexpr auto m_d = omath::source_engine::units_to_meters(hu_d);
constexpr auto hu_d_back = omath::source_engine::meters_to_units(m_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-10);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToKilometersToHammer)
{
constexpr float hu_f = 100000.0f;
constexpr auto km_f = omath::source_engine::units_to_kilometers(hu_f);
constexpr auto hu_f_back = omath::source_engine::kilometers_to_units(km_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-2f); // looser due to float scaling
constexpr double hu_d = -1234567.89;
constexpr auto km_d = omath::source_engine::units_to_kilometers(hu_d);
constexpr auto hu_d_back = omath::source_engine::kilometers_to_units(km_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-7);
}
TEST(unit_test_source_engine_units, ConversionChainConsistency)
{
// hu -> cm -> m -> km should match direct helpers
constexpr auto hu = 54321.123;
constexpr auto cm = omath::source_engine::units_to_centimeters(hu);
constexpr auto m_via_cm = cm / 100.0;
constexpr auto km_via_cm = m_via_cm / 1000.0;
constexpr auto m_direct = omath::source_engine::units_to_meters(hu);
constexpr auto km_direct = omath::source_engine::units_to_kilometers(hu);
EXPECT_NEAR(m_direct, m_via_cm, 1e-12);
EXPECT_NEAR(km_direct, km_via_cm, 1e-15);
}
TEST(unit_test_source_engine_units, SupportsFloatAndDoubleTypes)
{
static_assert(std::is_same_v<decltype(omath::source_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::source_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::source_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::source_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_source_engine_units, ConstexprEvaluation)
{
constexpr double hu = 10.0;
constexpr double cm = omath::source_engine::units_to_centimeters(hu);
constexpr double m = omath::source_engine::units_to_meters(hu);
constexpr double km = omath::source_engine::units_to_kilometers(hu);
static_assert(cm == 25.4, "constexpr hu->cm failed");
static_assert(m == 0.254, "constexpr hu->m failed");
static_assert(km == 0.000254, "constexpr hu->km failed");
}
TEST(unit_test_source_engine, ForwardVector) TEST(unit_test_source_engine, ForwardVector)
{ {
@@ -243,3 +367,59 @@ TEST(unit_test_source_engine, loook_at_random_z_axis)
} }
EXPECT_LE(failed_points, 100); EXPECT_LE(failed_points, 100);
} }
TEST(unit_test_source_engine, look_at_forward)
{
const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, omath::source_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::source_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::source_engine::k_abs_forward.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_source_engine, look_at_right)
{
const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, omath::source_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::source_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::source_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_source_engine, look_at_up)
{
const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, omath::source_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::source_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::source_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_source_engine, look_at_back)
{
const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, -omath::source_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::source_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::source_engine::k_abs_forward).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_source_engine, look_at_left)
{
const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, -omath::source_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::source_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::source_engine::k_abs_right).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_source_engine, look_at_down)
{
const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, -omath::source_engine::k_abs_up);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::source_engine::forward_vector(angles);
EXPECT_NEAR(dir_vector.z, -0.99984f, 0.0001f);
EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f);
EXPECT_NEAR(dir_vector.y, 0.f, 0.001f);
}

View File

@@ -7,6 +7,127 @@
#include <omath/engines/unity_engine/formulas.hpp> #include <omath/engines/unity_engine/formulas.hpp>
#include <print> #include <print>
#include <random> #include <random>
#include <ranges>
TEST(unit_test_unity_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_unity_engine, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_unity_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_unity_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_unity_engine, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_unity_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
constexpr auto cm_f = omath::unity_engine::units_to_centimeters(units_f);
constexpr auto units_f_back = omath::unity_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
constexpr auto cm_d = omath::unity_engine::units_to_centimeters(units_d);
constexpr auto units_d_back = omath::unity_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::unity_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::unity_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::unity_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::unity_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::unity_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::unity_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::unity_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::unity_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_unity_engine, ConversionChainConsistency)
{
constexpr double units = 424242.42;
constexpr auto cm_direct = omath::unity_engine::units_to_centimeters(units);
constexpr auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
constexpr auto km_direct = omath::unity_engine::units_to_kilometers(units);
constexpr auto km_via_meters = omath::unity_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_unity_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::unity_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unity_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::unity_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unity_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_unity_engine, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::unity_engine::units_to_centimeters(units);
constexpr double m = omath::unity_engine::units_to_meters(units);
constexpr double km = omath::unity_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_unity_engine, ForwardVector) TEST(unit_test_unity_engine, ForwardVector)
{ {
@@ -87,7 +208,8 @@ TEST(unit_test_unity_engine, Project)
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f); constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f); const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f);
const auto proj = cam.world_to_screen<omath::unity_engine::Camera::ScreenStart::BOTTOM_LEFT_CORNER>({10.f, 3, 10.f}); const auto proj =
cam.world_to_screen<omath::unity_engine::Camera::ScreenStart::BOTTOM_LEFT_CORNER>({10.f, 3, 10.f});
EXPECT_NEAR(proj->x, 1263.538, 0.001f); EXPECT_NEAR(proj->x, 1263.538, 0.001f);
EXPECT_NEAR(proj->y, 547.061f, 0.001f); EXPECT_NEAR(proj->y, 547.061f, 0.001f);
@@ -234,3 +356,64 @@ TEST(unit_test_unity_engine, loook_at_random_z_axis)
} }
EXPECT_LE(failed_points, 100); EXPECT_LE(failed_points, 100);
} }
TEST(unit_test_unity_engine, look_at_forward)
{
const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, omath::unity_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unity_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), omath::unity_engine::k_abs_forward.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unity_engine, look_at_right)
{
const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, omath::unity_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unity_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), omath::unity_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unity_engine, look_at_up)
{
const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, omath::unity_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unity_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), omath::unity_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unity_engine, look_at_back)
{
const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, -omath::unity_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unity_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_forward).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unity_engine, look_at_left)
{
const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, -omath::unity_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unity_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_right).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unity_engine, look_at_down)
{
const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, -omath::unity_engine::k_abs_up);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unity_engine::forward_vector(angles);
for (const auto& [result, etalon] :
std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}

View File

@@ -7,6 +7,7 @@
#include <omath/engines/unreal_engine/formulas.hpp> #include <omath/engines/unreal_engine/formulas.hpp>
#include <print> #include <print>
#include <random> #include <random>
#include <ranges>
TEST(unit_test_unreal_engine, ForwardVector) TEST(unit_test_unreal_engine, ForwardVector)
{ {
@@ -239,3 +240,181 @@ TEST(unit_test_unreal_engine, loook_at_random_z_axis)
} }
EXPECT_LE(failed_points, 100); EXPECT_LE(failed_points, 100);
} }
TEST(unit_test_unreal_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(1.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(250.0f), 250.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(-42.5f), -42.5f);
}
TEST(unit_test_unreal_engine, UnitsToMeters_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::units_to_meters(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(1.0), 0.01, 1e-15);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(100.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(-250.0), -2.5, 1e-12);
}
TEST(unit_test_unreal_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(0.0), 0.0, 1e-18);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(1.0), 0.00001, 1e-18);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(100000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(-250000.0), -2.5, 1e-12);
}
TEST(unit_test_unreal_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(1.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(250.0f), 250.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(-42.5f), -42.5f);
}
TEST(unit_test_unreal_engine, MetersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::meters_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(0.01), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(1.0), 100.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(-2.5), -250.0, 1e-9);
}
TEST(unit_test_unreal_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(0.0), 0.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(0.00001), 1.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(1.0), 100000.0, 1e-6);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(-2.5), -250000.0, 1e-3);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
constexpr auto cm_f = omath::unreal_engine::units_to_centimeters(units_f);
constexpr auto units_f_back = omath::unreal_engine::centimeters_to_units(cm_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -987654.321;
constexpr auto cm_d = omath::unreal_engine::units_to_centimeters(units_d);
constexpr auto units_d_back = omath::unreal_engine::centimeters_to_units(cm_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::unreal_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::unreal_engine::meters_to_units(m_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::unreal_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::unreal_engine::meters_to_units(m_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::unreal_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::unreal_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::unreal_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::unreal_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_unreal_engine, ConversionChainConsistency)
{
constexpr double units = 424242.42;
constexpr auto cm_direct = omath::unreal_engine::units_to_centimeters(units);
constexpr auto cm_expected = units; // 1 uu == 1 cm
EXPECT_NEAR(cm_direct, cm_expected, 1e-12);
constexpr auto m_direct = omath::unreal_engine::units_to_meters(units);
constexpr auto m_via_cm = cm_direct / 100.0;
EXPECT_NEAR(m_direct, m_via_cm, 1e-12);
constexpr auto km_direct = omath::unreal_engine::units_to_kilometers(units);
constexpr auto km_via_m = m_direct / 1000.0;
EXPECT_NEAR(km_direct, km_via_m, 1e-15);
}
TEST(unit_test_unreal_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::unreal_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_unreal_engine, ConstexprConversions)
{
constexpr double units = 100000.0;
constexpr double cm = omath::unreal_engine::units_to_centimeters(units);
constexpr double m = omath::unreal_engine::units_to_meters(units);
constexpr double km = omath::unreal_engine::units_to_kilometers(units);
static_assert(cm == 100000.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_unreal_engine, look_at_forward)
{
const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, omath::unreal_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unreal_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::unreal_engine::k_abs_forward.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unreal_engine, look_at_right)
{
const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, omath::unreal_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unreal_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::unreal_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unreal_engine, look_at_up)
{
const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, omath::unreal_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unreal_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::unreal_engine::k_abs_right.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unreal_engine, look_at_back)
{
const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, -omath::unreal_engine::k_abs_forward);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unreal_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_forward).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unreal_engine, look_at_left)
{
const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, -omath::unreal_engine::k_abs_right);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unreal_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_right).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unreal_engine, look_at_down)
{
const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, -omath::unreal_engine::k_abs_up);
// ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = omath::unreal_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}

View File

@@ -26,38 +26,38 @@ protected:
TEST_F(UnitTestColorGrouped, Constructor_Float) TEST_F(UnitTestColorGrouped, Constructor_Float)
{ {
constexpr Color color(0.5f, 0.5f, 0.5f, 1.0f); constexpr Color color(0.5f, 0.5f, 0.5f, 1.0f);
EXPECT_FLOAT_EQ(color.x, 0.5f); EXPECT_FLOAT_EQ(color.value().x, 0.5f);
EXPECT_FLOAT_EQ(color.y, 0.5f); EXPECT_FLOAT_EQ(color.value().y, 0.5f);
EXPECT_FLOAT_EQ(color.z, 0.5f); EXPECT_FLOAT_EQ(color.value().z, 0.5f);
EXPECT_FLOAT_EQ(color.w, 1.0f); EXPECT_FLOAT_EQ(color.value().w, 1.0f);
} }
TEST_F(UnitTestColorGrouped, Constructor_Vector4) TEST_F(UnitTestColorGrouped, Constructor_Vector4)
{ {
constexpr omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f); constexpr omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f);
constexpr Color color(vec); constexpr Color color(vec);
EXPECT_FLOAT_EQ(color.x, 0.2f); EXPECT_FLOAT_EQ(color.value().x, 0.2f);
EXPECT_FLOAT_EQ(color.y, 0.4f); EXPECT_FLOAT_EQ(color.value().y, 0.4f);
EXPECT_FLOAT_EQ(color.z, 0.6f); EXPECT_FLOAT_EQ(color.value().z, 0.6f);
EXPECT_FLOAT_EQ(color.w, 0.8f); EXPECT_FLOAT_EQ(color.value().w, 0.8f);
} }
TEST_F(UnitTestColorGrouped, FromRGBA) TEST_F(UnitTestColorGrouped, FromRGBA)
{ {
constexpr Color color = Color::from_rgba(128, 64, 32, 255); constexpr Color color = Color::from_rgba(128, 64, 32, 255);
EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f); EXPECT_FLOAT_EQ(color.value().x, 128.0f / 255.0f);
EXPECT_FLOAT_EQ(color.y, 64.0f / 255.0f); EXPECT_FLOAT_EQ(color.value().y, 64.0f / 255.0f);
EXPECT_FLOAT_EQ(color.z, 32.0f / 255.0f); EXPECT_FLOAT_EQ(color.value().z, 32.0f / 255.0f);
EXPECT_FLOAT_EQ(color.w, 1.0f); EXPECT_FLOAT_EQ(color.value().w, 1.0f);
} }
TEST_F(UnitTestColorGrouped, FromHSV) TEST_F(UnitTestColorGrouped, FromHSV)
{ {
constexpr Color color = Color::from_hsv(0.0f, 1.0f, 1.0f); // Red in HSV constexpr Color color = Color::from_hsv(0.0f, 1.0f, 1.0f); // Red in HSV
EXPECT_FLOAT_EQ(color.x, 1.0f); EXPECT_FLOAT_EQ(color.value().x, 1.0f);
EXPECT_FLOAT_EQ(color.y, 0.0f); EXPECT_FLOAT_EQ(color.value().y, 0.0f);
EXPECT_FLOAT_EQ(color.z, 0.0f); EXPECT_FLOAT_EQ(color.value().z, 0.0f);
EXPECT_FLOAT_EQ(color.w, 1.0f); EXPECT_FLOAT_EQ(color.value().w, 1.0f);
} }
TEST_F(UnitTestColorGrouped, ToHSV) TEST_F(UnitTestColorGrouped, ToHSV)
@@ -71,10 +71,10 @@ TEST_F(UnitTestColorGrouped, ToHSV)
TEST_F(UnitTestColorGrouped, Blend) TEST_F(UnitTestColorGrouped, Blend)
{ {
const Color blended = color1.blend(color2, 0.5f); const Color blended = color1.blend(color2, 0.5f);
EXPECT_FLOAT_EQ(blended.x, 0.5f); EXPECT_FLOAT_EQ(blended.value().x, 0.5f);
EXPECT_FLOAT_EQ(blended.y, 0.5f); EXPECT_FLOAT_EQ(blended.value().y, 0.5f);
EXPECT_FLOAT_EQ(blended.z, 0.0f); EXPECT_FLOAT_EQ(blended.value().z, 0.0f);
EXPECT_FLOAT_EQ(blended.w, 1.0f); EXPECT_FLOAT_EQ(blended.value().w, 1.0f);
} }
TEST_F(UnitTestColorGrouped, PredefinedColors) TEST_F(UnitTestColorGrouped, PredefinedColors)
@@ -83,20 +83,20 @@ TEST_F(UnitTestColorGrouped, PredefinedColors)
constexpr Color green = Color::green(); constexpr Color green = Color::green();
constexpr Color blue = Color::blue(); constexpr Color blue = Color::blue();
EXPECT_FLOAT_EQ(red.x, 1.0f); EXPECT_FLOAT_EQ(red.value().x, 1.0f);
EXPECT_FLOAT_EQ(red.y, 0.0f); EXPECT_FLOAT_EQ(red.value().y, 0.0f);
EXPECT_FLOAT_EQ(red.z, 0.0f); EXPECT_FLOAT_EQ(red.value().z, 0.0f);
EXPECT_FLOAT_EQ(red.w, 1.0f); EXPECT_FLOAT_EQ(red.value().w, 1.0f);
EXPECT_FLOAT_EQ(green.x, 0.0f); EXPECT_FLOAT_EQ(green.value().x, 0.0f);
EXPECT_FLOAT_EQ(green.y, 1.0f); EXPECT_FLOAT_EQ(green.value().y, 1.0f);
EXPECT_FLOAT_EQ(green.z, 0.0f); EXPECT_FLOAT_EQ(green.value().z, 0.0f);
EXPECT_FLOAT_EQ(green.w, 1.0f); EXPECT_FLOAT_EQ(green.value().w, 1.0f);
EXPECT_FLOAT_EQ(blue.x, 0.0f); EXPECT_FLOAT_EQ(blue.value().x, 0.0f);
EXPECT_FLOAT_EQ(blue.y, 0.0f); EXPECT_FLOAT_EQ(blue.value().y, 0.0f);
EXPECT_FLOAT_EQ(blue.z, 1.0f); EXPECT_FLOAT_EQ(blue.value().z, 1.0f);
EXPECT_FLOAT_EQ(blue.w, 1.0f); EXPECT_FLOAT_EQ(blue.value().w, 1.0f);
} }
TEST_F(UnitTestColorGrouped, BlendVector3) TEST_F(UnitTestColorGrouped, BlendVector3)
@@ -104,9 +104,9 @@ TEST_F(UnitTestColorGrouped, BlendVector3)
constexpr Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red constexpr Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red
constexpr Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green constexpr Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green
constexpr Color blended = v1.blend(v2, 0.5f); constexpr Color blended = v1.blend(v2, 0.5f);
EXPECT_FLOAT_EQ(blended.x, 0.5f); EXPECT_FLOAT_EQ(blended.value().x, 0.5f);
EXPECT_FLOAT_EQ(blended.y, 0.5f); EXPECT_FLOAT_EQ(blended.value().y, 0.5f);
EXPECT_FLOAT_EQ(blended.z, 0.0f); EXPECT_FLOAT_EQ(blended.value().z, 0.0f);
} }
// From unit_test_color_extra.cpp // From unit_test_color_extra.cpp
@@ -148,37 +148,37 @@ TEST(UnitTestColorGrouped_Extra, BlendEdgeCases)
constexpr Color a = Color::red(); constexpr Color a = Color::red();
constexpr Color b = Color::blue(); constexpr Color b = Color::blue();
constexpr auto r0 = a.blend(b, 0.f); constexpr auto r0 = a.blend(b, 0.f);
EXPECT_FLOAT_EQ(r0.x, a.x); EXPECT_FLOAT_EQ(r0.value().x, a.value().x);
constexpr auto r1 = a.blend(b, 1.f); constexpr auto r1 = a.blend(b, 1.f);
EXPECT_FLOAT_EQ(r1.x, b.x); EXPECT_FLOAT_EQ(r1.value().x, b.value().x);
} }
// From unit_test_color_more.cpp // From unit_test_color_more.cpp
TEST(UnitTestColorGrouped_More, DefaultCtorIsZero) TEST(UnitTestColorGrouped_More, DefaultCtorIsZero)
{ {
constexpr Color c; constexpr Color c;
EXPECT_FLOAT_EQ(c.x, 0.0f); EXPECT_FLOAT_EQ(c.value().x, 0.0f);
EXPECT_FLOAT_EQ(c.y, 0.0f); EXPECT_FLOAT_EQ(c.value().y, 0.0f);
EXPECT_FLOAT_EQ(c.z, 0.0f); EXPECT_FLOAT_EQ(c.value().z, 0.0f);
EXPECT_FLOAT_EQ(c.w, 0.0f); EXPECT_FLOAT_EQ(c.value().w, 0.0f);
} }
TEST(UnitTestColorGrouped_More, FloatCtorAndClampForRGB) TEST(UnitTestColorGrouped_More, FloatCtorAndClampForRGB)
{ {
constexpr Color c(1.2f, -0.5f, 0.5f, 2.0f); constexpr Color c(1.2f, -0.5f, 0.5f, 2.0f);
EXPECT_FLOAT_EQ(c.x, 1.0f); EXPECT_FLOAT_EQ(c.value().x, 1.0f);
EXPECT_FLOAT_EQ(c.y, 0.0f); EXPECT_FLOAT_EQ(c.value().y, 0.0f);
EXPECT_FLOAT_EQ(c.z, 0.5f); EXPECT_FLOAT_EQ(c.value().z, 0.5f);
EXPECT_FLOAT_EQ(c.w, 2.0f); EXPECT_FLOAT_EQ(c.value().w, 2.0f);
} }
TEST(UnitTestColorGrouped_More, FromRgbaProducesScaledComponents) TEST(UnitTestColorGrouped_More, FromRgbaProducesScaledComponents)
{ {
constexpr Color c = Color::from_rgba(25u, 128u, 230u, 64u); constexpr Color c = Color::from_rgba(25u, 128u, 230u, 64u);
EXPECT_NEAR(c.x, 25.0f/255.0f, 1e-6f); EXPECT_NEAR(c.value().x, 25.0f/255.0f, 1e-6f);
EXPECT_NEAR(c.y, 128.0f/255.0f, 1e-6f); EXPECT_NEAR(c.value().y, 128.0f/255.0f, 1e-6f);
EXPECT_NEAR(c.z, 230.0f/255.0f, 1e-6f); EXPECT_NEAR(c.value().z, 230.0f/255.0f, 1e-6f);
EXPECT_NEAR(c.w, 64.0f/255.0f, 1e-6f); EXPECT_NEAR(c.value().w, 64.0f/255.0f, 1e-6f);
} }
TEST(UnitTestColorGrouped_More, BlendProducesIntermediate) TEST(UnitTestColorGrouped_More, BlendProducesIntermediate)
@@ -186,10 +186,10 @@ TEST(UnitTestColorGrouped_More, BlendProducesIntermediate)
constexpr Color c0(0.0f, 0.0f, 0.0f, 1.0f); constexpr Color c0(0.0f, 0.0f, 0.0f, 1.0f);
constexpr Color c1(1.0f, 1.0f, 1.0f, 0.0f); constexpr Color c1(1.0f, 1.0f, 1.0f, 0.0f);
constexpr Color mid = c0.blend(c1, 0.5f); constexpr Color mid = c0.blend(c1, 0.5f);
EXPECT_FLOAT_EQ(mid.x, 0.5f); EXPECT_FLOAT_EQ(mid.value().x, 0.5f);
EXPECT_FLOAT_EQ(mid.y, 0.5f); EXPECT_FLOAT_EQ(mid.value().y, 0.5f);
EXPECT_FLOAT_EQ(mid.z, 0.5f); EXPECT_FLOAT_EQ(mid.value().z, 0.5f);
EXPECT_FLOAT_EQ(mid.w, 0.5f); EXPECT_FLOAT_EQ(mid.value().w, 0.5f);
} }
TEST(UnitTestColorGrouped_More, HsvRoundTrip) TEST(UnitTestColorGrouped_More, HsvRoundTrip)
@@ -197,9 +197,9 @@ TEST(UnitTestColorGrouped_More, HsvRoundTrip)
constexpr Color red = Color::red(); constexpr Color red = Color::red();
const auto hsv = red.to_hsv(); const auto hsv = red.to_hsv();
const Color back = Color::from_hsv(hsv); const Color back = Color::from_hsv(hsv);
EXPECT_NEAR(back.x, 1.0f, 1e-6f); EXPECT_NEAR(back.value().x, 1.0f, 1e-6f);
EXPECT_NEAR(back.y, 0.0f, 1e-6f); EXPECT_NEAR(back.value().y, 0.0f, 1e-6f);
EXPECT_NEAR(back.z, 0.0f, 1e-6f); EXPECT_NEAR(back.value().z, 0.0f, 1e-6f);
} }
TEST(UnitTestColorGrouped_More, ToStringContainsComponents) TEST(UnitTestColorGrouped_More, ToStringContainsComponents)
@@ -230,18 +230,18 @@ TEST(UnitTestColorGrouped_More2, FromHsvCases)
auto check_hue = [&](float h) { auto check_hue = [&](float h) {
SCOPED_TRACE(::testing::Message() << "h=" << h); SCOPED_TRACE(::testing::Message() << "h=" << h);
Color c = Color::from_hsv(h, 1.f, 1.f); Color c = Color::from_hsv(h, 1.f, 1.f);
EXPECT_TRUE(std::isfinite(c.x)); EXPECT_TRUE(std::isfinite(c.value().x));
EXPECT_TRUE(std::isfinite(c.y)); EXPECT_TRUE(std::isfinite(c.value().y));
EXPECT_TRUE(std::isfinite(c.z)); EXPECT_TRUE(std::isfinite(c.value().z));
EXPECT_GE(c.x, -eps); EXPECT_GE(c.value().x, -eps);
EXPECT_LE(c.x, 1.f + eps); EXPECT_LE(c.value().x, 1.f + eps);
EXPECT_GE(c.y, -eps); EXPECT_GE(c.value().y, -eps);
EXPECT_LE(c.y, 1.f + eps); EXPECT_LE(c.value().y, 1.f + eps);
EXPECT_GE(c.z, -eps); EXPECT_GE(c.value().z, -eps);
EXPECT_LE(c.z, 1.f + eps); EXPECT_LE(c.value().z, 1.f + eps);
float mx = std::max({c.x, c.y, c.z}); float mx = std::max({c.value().x, c.value().y, c.value().z});
float mn = std::min({c.x, c.y, c.z}); float mn = std::min({c.value().x, c.value().y, c.value().z});
EXPECT_GE(mx, 0.999f); EXPECT_GE(mx, 0.999f);
EXPECT_LE(mn, 1e-3f + 1e-4f); EXPECT_LE(mn, 1e-3f + 1e-4f);
}; };
@@ -261,13 +261,13 @@ TEST(UnitTestColorGrouped_More2, ToHsvAndSetters)
EXPECT_NEAR(hsv.value, 0.6f, 1e-6f); EXPECT_NEAR(hsv.value, 0.6f, 1e-6f);
c.set_hue(0.0f); c.set_hue(0.0f);
EXPECT_TRUE(std::isfinite(c.x)); EXPECT_TRUE(std::isfinite(c.value().x));
c.set_saturation(0.0f); c.set_saturation(0.0f);
EXPECT_TRUE(std::isfinite(c.y)); EXPECT_TRUE(std::isfinite(c.value().y));
c.set_value(0.5f); c.set_value(0.5f);
EXPECT_TRUE(std::isfinite(c.z)); EXPECT_TRUE(std::isfinite(c.value().z));
} }
TEST(UnitTestColorGrouped_More2, BlendAndStaticColors) TEST(UnitTestColorGrouped_More2, BlendAndStaticColors)
@@ -275,14 +275,14 @@ TEST(UnitTestColorGrouped_More2, BlendAndStaticColors)
constexpr Color a = Color::red(); constexpr Color a = Color::red();
constexpr Color b = Color::blue(); constexpr Color b = Color::blue();
constexpr auto mid = a.blend(b, 0.5f); constexpr auto mid = a.blend(b, 0.5f);
EXPECT_GT(mid.x, 0.f); EXPECT_GT(mid.value().x, 0.f);
EXPECT_GT(mid.z, 0.f); EXPECT_GT(mid.value().z, 0.f);
constexpr auto all_a = a.blend(b, -1.f); constexpr auto all_a = a.blend(b, -1.f);
EXPECT_NEAR(all_a.x, a.x, 1e-6f); EXPECT_NEAR(all_a.value().x, a.value().x, 1e-6f);
constexpr auto all_b = a.blend(b, 2.f); constexpr auto all_b = a.blend(b, 2.f);
EXPECT_NEAR(all_b.z, b.z, 1e-6f); EXPECT_NEAR(all_b.value().z, b.value().z, 1e-6f);
} }
TEST(UnitTestColorGrouped_More2, FormatterUsesToString) TEST(UnitTestColorGrouped_More2, FormatterUsesToString)
@@ -291,3 +291,35 @@ TEST(UnitTestColorGrouped_More2, FormatterUsesToString)
const auto formatted = std::format("{}", c); const auto formatted = std::format("{}", c);
EXPECT_NE(formatted.find("r:10"), std::string::npos); EXPECT_NE(formatted.find("r:10"), std::string::npos);
} }
TEST(UnitTestColorGrouped_More2, FormatterRgb)
{
constexpr Color c = Color::from_rgba(255, 128, 0, 64);
const auto s = std::format("{:rgb}", c);
EXPECT_NE(s.find("r:255"), std::string::npos);
EXPECT_NE(s.find("g:128"), std::string::npos);
EXPECT_NE(s.find("b:0"), std::string::npos);
EXPECT_NE(s.find("a:64"), std::string::npos);
}
TEST(UnitTestColorGrouped_More2, FormatterRgbf)
{
constexpr Color c(0.5f, 0.25f, 1.0f, 0.75f);
const auto s = std::format("{:rgbf}", c);
EXPECT_NE(s.find("r:"), std::string::npos);
EXPECT_NE(s.find("g:"), std::string::npos);
EXPECT_NE(s.find("b:"), std::string::npos);
EXPECT_NE(s.find("a:"), std::string::npos);
// Values should be in [0,1] float range, not 0-255
EXPECT_EQ(s.find("r:127"), std::string::npos);
EXPECT_EQ(s.find("r:255"), std::string::npos);
}
TEST(UnitTestColorGrouped_More2, FormatterHsv)
{
const Color c = Color::red();
const auto s = std::format("{:hsv}", c);
EXPECT_NE(s.find("h:"), std::string::npos);
EXPECT_NE(s.find("s:"), std::string::npos);
EXPECT_NE(s.find("v:"), std::string::npos);
}

View File

@@ -47,7 +47,7 @@ namespace
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
struct TraceCase struct TraceCase
{ {
Ray ray; Ray<> ray;
bool expected_clear; // true => segment does NOT hit the triangle bool expected_clear; // true => segment does NOT hit the triangle
friend std::ostream& operator<<(std::ostream& os, const TraceCase& tc) friend std::ostream& operator<<(std::ostream& os, const TraceCase& tc)
{ {
@@ -66,7 +66,7 @@ namespace
TEST_P(CanTraceLineParam, VariousRays) TEST_P(CanTraceLineParam, VariousRays)
{ {
const auto& [ray, expected_clear] = GetParam(); const auto& [ray, expected_clear] = GetParam();
EXPECT_EQ(LineTracer::can_trace_line(ray, triangle), expected_clear); EXPECT_EQ(LineTracer<>::can_trace_line(ray, triangle), expected_clear);
} }
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
@@ -91,7 +91,7 @@ namespace
constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}};
constexpr Vec3 expected{0.3f, 0.3f, 0.f}; constexpr Vec3 expected{0.3f, 0.3f, 0.f};
const Vec3 hit = LineTracer::get_ray_hit_point(ray, triangle); const Vec3 hit = LineTracer<>::get_ray_hit_point(ray, triangle);
ASSERT_FALSE(vec_equal(hit, ray.end)); ASSERT_FALSE(vec_equal(hit, ray.end));
EXPECT_TRUE(vec_equal(hit, expected)); EXPECT_TRUE(vec_equal(hit, expected));
} }
@@ -106,7 +106,7 @@ namespace
{1001.f, 1000.f, 1000.f}, {1001.f, 1000.f, 1000.f},
{1000.f, 1001.f, 1000.f}}; {1000.f, 1001.f, 1000.f}};
EXPECT_TRUE(LineTracer::can_trace_line(short_ray, distant)); EXPECT_TRUE(LineTracer<>::can_trace_line(short_ray, distant));
} }
TEST(unit_test_unity_engine, CantHit) TEST(unit_test_unity_engine, CantHit)
@@ -115,13 +115,13 @@ namespace
constexpr Ray ray{{}, {1.0, 0, 0}, false}; constexpr Ray ray{{}, {1.0, 0, 0}, false};
EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, triangle)); EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, triangle));
} }
TEST(unit_test_unity_engine, CanHit) TEST(unit_test_unity_engine, CanHit)
{ {
constexpr omath::Triangle<Vector3<float>> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}}; constexpr omath::Triangle<Vector3<float>> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}};
constexpr Ray ray{{}, {2.1, 0, 0}, false}; constexpr Ray ray{{}, {2.1, 0, 0}, false};
EXPECT_FALSE(omath::collision::LineTracer::can_trace_line(ray, triangle)); EXPECT_FALSE(omath::collision::LineTracer<>::can_trace_line(ray, triangle));
} }
} // namespace } // namespace

View File

@@ -15,9 +15,9 @@ TEST(LineTracerTests, ParallelRayReturnsEnd)
ray.end = Vector3<float>{1.f,1.f,1.f}; ray.end = Vector3<float>{1.f,1.f,1.f};
// For a ray parallel to the triangle plane the algorithm should return ray.end // For a ray parallel to the triangle plane the algorithm should return ray.end
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end); EXPECT_TRUE(hit == ray.end);
EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, tri)); EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, tri));
} }
TEST(LineTracerTests, MissesTriangleReturnsEnd) TEST(LineTracerTests, MissesTriangleReturnsEnd)
@@ -27,7 +27,7 @@ TEST(LineTracerTests, MissesTriangleReturnsEnd)
ray.start = Vector3<float>{2.f,2.f,-1.f}; ray.start = Vector3<float>{2.f,2.f,-1.f};
ray.end = Vector3<float>{2.f,2.f,1.f}; // passes above the triangle area ray.end = Vector3<float>{2.f,2.f,1.f}; // passes above the triangle area
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end); EXPECT_TRUE(hit == ray.end);
} }
@@ -38,7 +38,7 @@ TEST(LineTracerTests, HitTriangleReturnsPointInsideSegment)
ray.start = Vector3<float>{0.25f,0.25f,-1.f}; ray.start = Vector3<float>{0.25f,0.25f,-1.f};
ray.end = Vector3<float>{0.25f,0.25f,1.f}; ray.end = Vector3<float>{0.25f,0.25f,1.f};
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
// Should return a point between start and end (z approximately 0) // Should return a point between start and end (z approximately 0)
EXPECT_NE(hit, ray.end); EXPECT_NE(hit, ray.end);
EXPECT_NEAR(hit.z, 0.f, 1e-4f); EXPECT_NEAR(hit.z, 0.f, 1e-4f);
@@ -60,6 +60,6 @@ TEST(LineTracerTests, InfiniteLengthEarlyOut)
// If t_hit <= epsilon the algorithm should return ray.end when infinite_length is true. // If t_hit <= epsilon the algorithm should return ray.end when infinite_length is true.
// Using start on the triangle plane should produce t_hit <= epsilon. // Using start on the triangle plane should produce t_hit <= epsilon.
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end); EXPECT_TRUE(hit == ray.end);
} }

View File

@@ -10,7 +10,7 @@ TEST(LineTracerExtra, MissParallel)
{ {
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false }; // parallel above triangle constexpr Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false }; // parallel above triangle
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -18,7 +18,7 @@ TEST(LineTracerExtra, HitCenter)
{ {
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.3f,0.3f,-1.f}, {0.3f,0.3f,1.f}, false }; constexpr Ray ray{ {0.3f,0.3f,-1.f}, {0.3f,0.3f,1.f}, false };
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
ASSERT_FALSE(hit == ray.end); ASSERT_FALSE(hit == ray.end);
EXPECT_NEAR(hit.x, 0.3f, 1e-6f); EXPECT_NEAR(hit.x, 0.3f, 1e-6f);
EXPECT_NEAR(hit.y, 0.3f, 1e-6f); EXPECT_NEAR(hit.y, 0.3f, 1e-6f);
@@ -30,7 +30,7 @@ TEST(LineTracerExtra, HitOnEdge)
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.0f,0.0f,1.f}, {0.0f,0.0f,0.f}, false }; constexpr Ray ray{ {0.0f,0.0f,1.f}, {0.0f,0.0f,0.f}, false };
// hitting exact vertex/edge may be considered miss; ensure function handles without crash // hitting exact vertex/edge may be considered miss; ensure function handles without crash
if (const auto hit = LineTracer::get_ray_hit_point(ray, tri); hit != ray.end) if (const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); hit != ray.end)
{ {
EXPECT_NEAR(hit.x, 0.0f, 1e-6f); EXPECT_NEAR(hit.x, 0.0f, 1e-6f);
EXPECT_NEAR(hit.y, 0.0f, 1e-6f); EXPECT_NEAR(hit.y, 0.0f, 1e-6f);
@@ -42,6 +42,6 @@ TEST(LineTracerExtra, InfiniteRayIgnoredIfBehind)
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
// Ray pointing away but infinite_length true should be ignored // Ray pointing away but infinite_length true should be ignored
constexpr Ray ray{ {0.5f,0.5f,-1.f}, {0.5f,0.5f,-2.f}, true }; constexpr Ray ray{ {0.5f,0.5f,-1.f}, {0.5f,0.5f,-2.f}, true };
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }

View File

@@ -14,7 +14,7 @@ TEST(LineTracerMore, ParallelRayReturnsEnd)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.f,0.f,1.f}; ray.end = {1.f,0.f,1.f}; Ray ray; ray.start = {0.f,0.f,1.f}; ray.end = {1.f,0.f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -24,7 +24,7 @@ TEST(LineTracerMore, UOutOfRangeReturnsEnd)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {-1.f,-1.f,-1.f}; ray.end = {-0.5f,-1.f,1.f}; Ray ray; ray.start = {-1.f,-1.f,-1.f}; ray.end = {-0.5f,-1.f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -34,7 +34,7 @@ TEST(LineTracerMore, VOutOfRangeReturnsEnd)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {2.f,2.f,-1.f}; ray.end = {2.f,2.f,1.f}; Ray ray; ray.start = {2.f,2.f,-1.f}; ray.end = {2.f,2.f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -43,7 +43,7 @@ TEST(LineTracerMore, THitTooSmallReturnsEnd)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.f,0.f,0.0000000001f}; ray.end = {0.f,0.f,1.f}; Ray ray; ray.start = {0.f,0.f,0.0000000001f}; ray.end = {0.f,0.f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -53,7 +53,7 @@ TEST(LineTracerMore, THitGreaterThanOneReturnsEnd)
// Choose a ray and compute t_hit locally to assert consistency // Choose a ray and compute t_hit locally to assert consistency
Ray ray; ray.start = {0.f,0.f,-1.f}; ray.end = {0.f,0.f,-0.5f}; Ray ray; ray.start = {0.f,0.f,-1.f}; ray.end = {0.f,0.f,-0.5f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
constexpr float k_epsilon = std::numeric_limits<float>::epsilon(); constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
constexpr auto side_a = tri.side_a_vector(); constexpr auto side_a = tri.side_a_vector();
@@ -87,7 +87,7 @@ TEST(LineTracerMore, InfiniteLengthWithSmallTHitReturnsEnd)
// Create triangle slightly behind so t_hit <= eps // Create triangle slightly behind so t_hit <= eps
tri = tri2; tri = tri2;
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -96,7 +96,7 @@ TEST(LineTracerMore, SuccessfulHitReturnsPoint)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.1f,0.1f,-1.f}; ray.end = {0.1f,0.1f,1.f}; Ray ray; ray.start = {0.1f,0.1f,-1.f}; ray.end = {0.1f,0.1f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_NE(hit, ray.end); EXPECT_NE(hit, ray.end);
// Hit should be on plane z=0 and near x=0.1,y=0.1 // Hit should be on plane z=0 and near x=0.1,y=0.1
EXPECT_NEAR(hit.z, 0.f, 1e-6f); EXPECT_NEAR(hit.z, 0.f, 1e-6f);

View File

@@ -14,7 +14,7 @@ TEST(LineTracerMore2, UGreaterThanOneReturnsEnd)
// choose ray so barycentric u > 1 // choose ray so barycentric u > 1
Ray ray; ray.start = {2.f, -1.f, -1.f}; ray.end = {2.f, -1.f, 1.f}; Ray ray; ray.start = {2.f, -1.f, -1.f}; ray.end = {2.f, -1.f, 1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -24,7 +24,7 @@ TEST(LineTracerMore2, VGreaterThanOneReturnsEnd)
// choose ray so barycentric v > 1 // choose ray so barycentric v > 1
Ray ray; ray.start = {-1.f, 2.f, -1.f}; ray.end = {-1.f, 2.f, 1.f}; Ray ray; ray.start = {-1.f, 2.f, -1.f}; ray.end = {-1.f, 2.f, 1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -34,7 +34,7 @@ TEST(LineTracerMore2, UPlusVGreaterThanOneReturnsEnd)
// Ray aimed so u+v > 1 (outside triangle region) // Ray aimed so u+v > 1 (outside triangle region)
Ray ray; ray.start = {1.f, 1.f, -1.f}; ray.end = {1.f, 1.f, 1.f}; Ray ray; ray.start = {1.f, 1.f, -1.f}; ray.end = {1.f, 1.f, 1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -52,6 +52,6 @@ TEST(LineTracerMore2, ZeroLengthRayHandled)
Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,0.f}; Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,0.f};
// Zero-length ray: direction length == 0; algorithm should handle without crash // Zero-length ray: direction length == 0; algorithm should handle without crash
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }

View File

@@ -0,0 +1,358 @@
//
// Created by Copilot on 04.02.2026.
//
// Unit tests for MachOPatternScanner
#include <gtest/gtest.h>
#include <omath/utility/macho_pattern_scan.hpp>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <vector>
using namespace omath;
namespace
{
// Mach-O magic numbers
constexpr std::uint32_t mh_magic_64 = 0xFEEDFACF;
constexpr std::uint32_t mh_magic_32 = 0xFEEDFACE;
constexpr std::uint32_t lc_segment = 0x1;
constexpr std::uint32_t lc_segment_64 = 0x19;
constexpr std::string_view segment_name = "__TEXT";
constexpr std::string_view section_name = "__text";
#pragma pack(push, 1)
struct MachHeader64
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
std::uint32_t reserved;
};
struct MachHeader32
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
};
struct SegmentCommand64
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint64_t vmaddr;
std::uint64_t vmsize;
std::uint64_t fileoff;
std::uint64_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
struct SegmentCommand32
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint32_t vmaddr;
std::uint32_t vmsize;
std::uint32_t fileoff;
std::uint32_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
struct Section64
{
char sectname[16];
char segname[16];
std::uint64_t addr;
std::uint64_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
std::uint32_t reserved3;
};
struct Section32
{
char sectname[16];
char segname[16];
std::uint32_t addr;
std::uint32_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
};
#pragma pack(pop)
// Helper function to create a minimal 64-bit Mach-O file with a __text section
bool write_minimal_macho64_file(const std::string& path, const std::vector<std::uint8_t>& section_bytes)
{
std::ofstream f(path, std::ios::binary);
if (!f.is_open())
return false;
// Calculate sizes
constexpr std::size_t header_size = sizeof(MachHeader64);
constexpr std::size_t segment_size = sizeof(SegmentCommand64);
constexpr std::size_t section_size = sizeof(Section64);
constexpr std::size_t load_cmd_size = segment_size + section_size;
// Section data will start after headers
const std::size_t section_offset = header_size + load_cmd_size;
// Create Mach-O header
MachHeader64 header{};
header.magic = mh_magic_64;
header.cputype = 0x01000007; // CPU_TYPE_X86_64
header.cpusubtype = 0x3; // CPU_SUBTYPE_X86_64_ALL
header.filetype = 0x2; // MH_EXECUTE
header.ncmds = 1;
header.sizeofcmds = static_cast<std::uint32_t>(load_cmd_size);
header.flags = 0;
header.reserved = 0;
f.write(reinterpret_cast<const char*>(&header), sizeof(header));
// Create segment command
SegmentCommand64 segment{};
segment.cmd = lc_segment_64;
segment.cmdsize = static_cast<std::uint32_t>(load_cmd_size);
std::ranges::copy(segment_name, segment.segname);
segment.vmaddr = 0x100000000;
segment.vmsize = section_bytes.size();
segment.fileoff = section_offset;
segment.filesize = section_bytes.size();
segment.maxprot = 7; // VM_PROT_ALL
segment.initprot = 5; // VM_PROT_READ | VM_PROT_EXECUTE
segment.nsects = 1;
segment.flags = 0;
f.write(reinterpret_cast<const char*>(&segment), sizeof(segment));
// Create section
Section64 section{};
std::ranges::copy(section_name, section.sectname);
std::ranges::copy(segment_name, segment.segname);
section.addr = 0x100000000;
section.size = section_bytes.size();
section.offset = static_cast<std::uint32_t>(section_offset);
section.align = 0;
section.reloff = 0;
section.nreloc = 0;
section.flags = 0;
section.reserved1 = 0;
section.reserved2 = 0;
section.reserved3 = 0;
f.write(reinterpret_cast<const char*>(&section), sizeof(section));
// Write section data
f.write(reinterpret_cast<const char*>(section_bytes.data()), static_cast<std::streamsize>(section_bytes.size()));
f.close();
return true;
}
// Helper function to create a minimal 32-bit Mach-O file with a __text section
bool write_minimal_macho32_file(const std::string& path, const std::vector<std::uint8_t>& section_bytes)
{
std::ofstream f(path, std::ios::binary);
if (!f.is_open())
return false;
// Calculate sizes
constexpr std::size_t header_size = sizeof(MachHeader32);
constexpr std::size_t segment_size = sizeof(SegmentCommand32);
constexpr std::size_t section_size = sizeof(Section32);
constexpr std::size_t load_cmd_size = segment_size + section_size;
// Section data will start after headers
constexpr std::size_t section_offset = header_size + load_cmd_size;
// Create Mach-O header
MachHeader32 header{};
header.magic = mh_magic_32;
header.cputype = 0x7; // CPU_TYPE_X86
header.cpusubtype = 0x3; // CPU_SUBTYPE_X86_ALL
header.filetype = 0x2; // MH_EXECUTE
header.ncmds = 1;
header.sizeofcmds = static_cast<std::uint32_t>(load_cmd_size);
header.flags = 0;
f.write(reinterpret_cast<const char*>(&header), sizeof(header));
// Create segment command
SegmentCommand32 segment{};
segment.cmd = lc_segment;
segment.cmdsize = static_cast<std::uint32_t>(load_cmd_size);
std::ranges::copy(segment_name, segment.segname);
segment.vmaddr = 0x1000;
segment.vmsize = static_cast<std::uint32_t>(section_bytes.size());
segment.fileoff = static_cast<std::uint32_t>(section_offset);
segment.filesize = static_cast<std::uint32_t>(section_bytes.size());
segment.maxprot = 7; // VM_PROT_ALL
segment.initprot = 5; // VM_PROT_READ | VM_PROT_EXECUTE
segment.nsects = 1;
segment.flags = 0;
f.write(reinterpret_cast<const char*>(&segment), sizeof(segment));
// Create section
Section32 section{};
std::ranges::copy(section_name, section.sectname);
std::ranges::copy(segment_name, segment.segname);
section.addr = 0x1000;
section.size = static_cast<std::uint32_t>(section_bytes.size());
section.offset = static_cast<std::uint32_t>(section_offset);
section.align = 0;
section.reloff = 0;
section.nreloc = 0;
section.flags = 0;
section.reserved1 = 0;
section.reserved2 = 0;
f.write(reinterpret_cast<const char*>(&section), sizeof(section));
// Write section data
f.write(reinterpret_cast<const char*>(section_bytes.data()), static_cast<std::streamsize>(section_bytes.size()));
f.close();
return true;
}
} // namespace
// Test scanning for a pattern that exists in a 64-bit Mach-O file
TEST(unit_test_macho_pattern_scan_file, ScanFindsPattern64)
{
constexpr std::string_view path = "./test_minimal_macho64.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5, 0x90, 0x90}; // push rbp; mov rbp, rsp; nop; nop
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 0);
}
}
// Test scanning for a pattern that exists in a 32-bit Mach-O file
TEST(unit_test_macho_pattern_scan_file, ScanFindsPattern32)
{
constexpr std::string_view path = "./test_minimal_macho32.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x89, 0xE5, 0x90, 0x90}; // push ebp; mov ebp, esp; nop; nop
ASSERT_TRUE(write_minimal_macho32_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 0);
}
}
// Test scanning for a pattern that does not exist
TEST(unit_test_macho_pattern_scan_file, ScanMissingPattern)
{
constexpr std::string_view path = "./test_minimal_macho_missing.bin";
const std::vector<std::uint8_t> bytes = {0x00, 0x01, 0x02, 0x03};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "FF EE DD", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning for a pattern at a non-zero offset
TEST(unit_test_macho_pattern_scan_file, ScanPatternAtOffset)
{
constexpr std::string_view path = "./test_minimal_macho_offset.bin";
const std::vector<std::uint8_t> bytes = {0x90, 0x90, 0x90, 0x55, 0x48, 0x89, 0xE5}; // nops then pattern
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 3);
}
}
// Test scanning with wildcards
TEST(unit_test_macho_pattern_scan_file, ScanWithWildcard)
{
constexpr std::string_view path = "./test_minimal_macho_wildcard.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5, 0x90};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 ? 89 E5", "__text");
EXPECT_TRUE(res.has_value());
}
// Test scanning a non-existent file
TEST(unit_test_macho_pattern_scan_file, ScanNonExistentFile)
{
const auto res = MachOPatternScanner::scan_for_pattern_in_file("/non/existent/file.bin", "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning an invalid (non-Mach-O) file
TEST(unit_test_macho_pattern_scan_file, ScanInvalidFile)
{
constexpr std::string_view path = "./test_invalid_macho.bin";
std::ofstream f(path.data(), std::ios::binary);
const std::vector<std::uint8_t> garbage = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
f.write(reinterpret_cast<const char*>(garbage.data()), static_cast<std::streamsize>(garbage.size()));
f.close();
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning for a non-existent section
TEST(unit_test_macho_pattern_scan_file, ScanNonExistentSection)
{
constexpr std::string_view path = "./test_minimal_macho_nosect.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48", "__nonexistent");
EXPECT_FALSE(res.has_value());
}
// Test scanning with null module base address
TEST(unit_test_macho_pattern_scan_loaded, ScanNullModule)
{
const auto res = MachOPatternScanner::scan_for_pattern_in_loaded_module(nullptr, "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning in loaded module with invalid magic
TEST(unit_test_macho_pattern_scan_loaded, ScanInvalidMagic)
{
std::vector<std::uint8_t> invalid_data(256, 0x00);
const auto res = MachOPatternScanner::scan_for_pattern_in_loaded_module(invalid_data.data(), "55 48", "__text");
EXPECT_FALSE(res.has_value());
}

View File

@@ -12,5 +12,5 @@ TEST(test, test)
{0.f, 30.f, 0.f}, {}, omath::opengl_engine::k_abs_forward, omath::opengl_engine::k_abs_right); {0.f, 30.f, 0.f}, {}, omath::opengl_engine::k_abs_forward, omath::opengl_engine::k_abs_right);
omath::collision::Ray ray{.start = {0, 0, 0}, .end = {-100, 0, 0}}; omath::collision::Ray ray{.start = {0, 0, 0}, .end = {-100, 0, 0}};
std::ignore = omath::collision::LineTracer::get_ray_hit_point(ray, result); std::ignore = omath::collision::LineTracer<>::get_ray_hit_point(ray, result);
} }

View File

@@ -0,0 +1,402 @@
//
// Created by vlad on 3/1/2026.
//
#include <omath/linear_algebra/quaternion.hpp>
#include <cmath>
#include <gtest/gtest.h>
#include <numbers>
using namespace omath;
static constexpr float kEps = 1e-5f;
// ── Helpers ──────────────────────────────────────────────────────────────────
static void expect_quat_near(const Quaternion<float>& a, const Quaternion<float>& b, float eps = kEps)
{
EXPECT_NEAR(a.x, b.x, eps);
EXPECT_NEAR(a.y, b.y, eps);
EXPECT_NEAR(a.z, b.z, eps);
EXPECT_NEAR(a.w, b.w, eps);
}
static void expect_vec3_near(const Vector3<float>& a, const Vector3<float>& b, float eps = kEps)
{
EXPECT_NEAR(a.x, b.x, eps);
EXPECT_NEAR(a.y, b.y, eps);
EXPECT_NEAR(a.z, b.z, eps);
}
// ── Constructors ─────────────────────────────────────────────────────────────
TEST(Quaternion, DefaultConstructorIsIdentity)
{
constexpr Quaternion<float> q;
EXPECT_FLOAT_EQ(q.x, 0.f);
EXPECT_FLOAT_EQ(q.y, 0.f);
EXPECT_FLOAT_EQ(q.z, 0.f);
EXPECT_FLOAT_EQ(q.w, 1.f);
}
TEST(Quaternion, ValueConstructor)
{
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
EXPECT_FLOAT_EQ(q.x, 1.f);
EXPECT_FLOAT_EQ(q.y, 2.f);
EXPECT_FLOAT_EQ(q.z, 3.f);
EXPECT_FLOAT_EQ(q.w, 4.f);
}
TEST(Quaternion, DoubleInstantiation)
{
constexpr Quaternion<double> q{0.0, 0.0, 0.0, 1.0};
EXPECT_DOUBLE_EQ(q.w, 1.0);
}
// ── Equality ─────────────────────────────────────────────────────────────────
TEST(Quaternion, EqualityOperators)
{
constexpr Quaternion<float> a{1.f, 2.f, 3.f, 4.f};
constexpr Quaternion<float> b{1.f, 2.f, 3.f, 4.f};
constexpr Quaternion<float> c{1.f, 2.f, 3.f, 5.f};
EXPECT_TRUE(a == b);
EXPECT_FALSE(a == c);
EXPECT_FALSE(a != b);
EXPECT_TRUE(a != c);
}
// ── Arithmetic ───────────────────────────────────────────────────────────────
TEST(Quaternion, ScalarMultiply)
{
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
constexpr auto r = q * 2.f;
EXPECT_FLOAT_EQ(r.x, 2.f);
EXPECT_FLOAT_EQ(r.y, 4.f);
EXPECT_FLOAT_EQ(r.z, 6.f);
EXPECT_FLOAT_EQ(r.w, 8.f);
}
TEST(Quaternion, ScalarMultiplyAssign)
{
Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
q *= 3.f;
EXPECT_FLOAT_EQ(q.x, 3.f);
EXPECT_FLOAT_EQ(q.y, 6.f);
EXPECT_FLOAT_EQ(q.z, 9.f);
EXPECT_FLOAT_EQ(q.w, 12.f);
}
TEST(Quaternion, Addition)
{
constexpr Quaternion<float> a{1.f, 2.f, 3.f, 4.f};
constexpr Quaternion<float> b{4.f, 3.f, 2.f, 1.f};
constexpr auto r = a + b;
EXPECT_FLOAT_EQ(r.x, 5.f);
EXPECT_FLOAT_EQ(r.y, 5.f);
EXPECT_FLOAT_EQ(r.z, 5.f);
EXPECT_FLOAT_EQ(r.w, 5.f);
}
TEST(Quaternion, AdditionAssign)
{
Quaternion<float> a{1.f, 0.f, 0.f, 0.f};
const Quaternion<float> b{0.f, 1.f, 0.f, 0.f};
a += b;
EXPECT_FLOAT_EQ(a.x, 1.f);
EXPECT_FLOAT_EQ(a.y, 1.f);
}
TEST(Quaternion, UnaryNegation)
{
constexpr Quaternion<float> q{1.f, -2.f, 3.f, -4.f};
constexpr auto r = -q;
EXPECT_FLOAT_EQ(r.x, -1.f);
EXPECT_FLOAT_EQ(r.y, 2.f);
EXPECT_FLOAT_EQ(r.z, -3.f);
EXPECT_FLOAT_EQ(r.w, 4.f);
}
// ── Hamilton product ──────────────────────────────────────────────────────────
TEST(Quaternion, MultiplyByIdentityIsNoop)
{
constexpr Quaternion<float> identity;
constexpr Quaternion<float> q{0.5f, 0.5f, 0.5f, 0.5f};
expect_quat_near(q * identity, q);
expect_quat_near(identity * q, q);
}
TEST(Quaternion, MultiplyAssign)
{
constexpr Quaternion<float> identity;
Quaternion<float> q{0.5f, 0.5f, 0.5f, 0.5f};
q *= identity;
expect_quat_near(q, {0.5f, 0.5f, 0.5f, 0.5f});
}
TEST(Quaternion, MultiplyKnownResult)
{
// i * j = k → (1,0,0,0) * (0,1,0,0) = (0,0,1,0)
constexpr Quaternion<float> i{1.f, 0.f, 0.f, 0.f};
constexpr Quaternion<float> j{0.f, 1.f, 0.f, 0.f};
constexpr auto k = i * j;
EXPECT_FLOAT_EQ(k.x, 0.f);
EXPECT_FLOAT_EQ(k.y, 0.f);
EXPECT_FLOAT_EQ(k.z, 1.f);
EXPECT_FLOAT_EQ(k.w, 0.f);
}
TEST(Quaternion, MultiplyByInverseGivesIdentity)
{
const Quaternion<float> q = Quaternion<float>::from_axis_angle({0.f, 0.f, 1.f},
std::numbers::pi_v<float> / 3.f);
const auto result = q * q.inverse();
expect_quat_near(result, Quaternion<float>{});
}
// ── Conjugate ────────────────────────────────────────────────────────────────
TEST(Quaternion, Conjugate)
{
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
constexpr auto c = q.conjugate();
EXPECT_FLOAT_EQ(c.x, -1.f);
EXPECT_FLOAT_EQ(c.y, -2.f);
EXPECT_FLOAT_EQ(c.z, -3.f);
EXPECT_FLOAT_EQ(c.w, 4.f);
}
TEST(Quaternion, ConjugateOfIdentityIsIdentity)
{
constexpr Quaternion<float> id;
constexpr auto c = id.conjugate();
EXPECT_FLOAT_EQ(c.x, 0.f);
EXPECT_FLOAT_EQ(c.y, 0.f);
EXPECT_FLOAT_EQ(c.z, 0.f);
EXPECT_FLOAT_EQ(c.w, 1.f);
}
// ── Dot / length ─────────────────────────────────────────────────────────────
TEST(Quaternion, Dot)
{
constexpr Quaternion<float> a{1.f, 0.f, 0.f, 0.f};
constexpr Quaternion<float> b{0.f, 1.f, 0.f, 0.f};
EXPECT_FLOAT_EQ(a.dot(b), 0.f);
EXPECT_FLOAT_EQ(a.dot(a), 1.f);
}
TEST(Quaternion, LengthSqrIdentity)
{
constexpr Quaternion<float> id;
EXPECT_FLOAT_EQ(id.length_sqr(), 1.f);
}
TEST(Quaternion, LengthSqrGeneral)
{
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
EXPECT_FLOAT_EQ(q.length_sqr(), 30.f);
}
TEST(Quaternion, LengthIdentity)
{
const Quaternion<float> id;
EXPECT_NEAR(id.length(), 1.f, kEps);
}
TEST(Quaternion, Normalized)
{
const Quaternion<float> q{1.f, 1.f, 1.f, 1.f};
const auto n = q.normalized();
EXPECT_NEAR(n.length(), 1.f, kEps);
EXPECT_NEAR(n.x, 0.5f, kEps);
EXPECT_NEAR(n.y, 0.5f, kEps);
EXPECT_NEAR(n.z, 0.5f, kEps);
EXPECT_NEAR(n.w, 0.5f, kEps);
}
TEST(Quaternion, NormalizedOfZeroLengthReturnsSelf)
{
// length_sqr = 0 would be UB, but zero-vector part + zero w is degenerate;
// we just verify the guard branch (divides by zero) doesn't crash by
// keeping length > 0 via the default constructor path.
const Quaternion<float> unit;
const auto n = unit.normalized();
expect_quat_near(n, unit);
}
// ── Inverse ───────────────────────────────────────────────────────────────────
TEST(Quaternion, InverseOfUnitIsConjugate)
{
const Quaternion<float> q = Quaternion<float>::from_axis_angle({1.f, 0.f, 0.f},
std::numbers::pi_v<float> / 4.f);
const auto inv = q.inverse();
const auto conj = q.conjugate();
expect_quat_near(inv, conj);
}
// ── from_axis_angle ──────────────────────────────────────────────────────────
TEST(Quaternion, FromAxisAngleZeroAngleIsIdentity)
{
const auto q = Quaternion<float>::from_axis_angle({1.f, 0.f, 0.f}, 0.f);
EXPECT_NEAR(q.x, 0.f, kEps);
EXPECT_NEAR(q.y, 0.f, kEps);
EXPECT_NEAR(q.z, 0.f, kEps);
EXPECT_NEAR(q.w, 1.f, kEps);
}
TEST(Quaternion, FromAxisAngle90DegZ)
{
const float half_pi = std::numbers::pi_v<float> / 2.f;
const auto q = Quaternion<float>::from_axis_angle({0.f, 0.f, 1.f}, half_pi);
const float s = std::sin(half_pi / 2.f);
const float c = std::cos(half_pi / 2.f);
EXPECT_NEAR(q.x, 0.f, kEps);
EXPECT_NEAR(q.y, 0.f, kEps);
EXPECT_NEAR(q.z, s, kEps);
EXPECT_NEAR(q.w, c, kEps);
}
// ── rotate ───────────────────────────────────────────────────────────────────
TEST(Quaternion, RotateByIdentityIsNoop)
{
constexpr Quaternion<float> id;
constexpr Vector3<float> v{1.f, 2.f, 3.f};
const auto r = id.rotate(v);
expect_vec3_near(r, v);
}
TEST(Quaternion, Rotate90DegAroundZ)
{
// Rotating (1,0,0) by 90° around Z should give (0,1,0)
const auto q = Quaternion<float>::from_axis_angle({0.f, 0.f, 1.f}, std::numbers::pi_v<float> / 2.f);
const auto r = q.rotate({1.f, 0.f, 0.f});
expect_vec3_near(r, {0.f, 1.f, 0.f});
}
TEST(Quaternion, Rotate180DegAroundY)
{
// Rotating (1,0,0) by 180° around Y should give (-1,0,0)
const auto q = Quaternion<float>::from_axis_angle({0.f, 1.f, 0.f}, std::numbers::pi_v<float>);
const auto r = q.rotate({1.f, 0.f, 0.f});
expect_vec3_near(r, {-1.f, 0.f, 0.f});
}
TEST(Quaternion, Rotate90DegAroundX)
{
// Rotating (0,1,0) by 90° around X should give (0,0,1)
const auto q = Quaternion<float>::from_axis_angle({1.f, 0.f, 0.f}, std::numbers::pi_v<float> / 2.f);
const auto r = q.rotate({0.f, 1.f, 0.f});
expect_vec3_near(r, {0.f, 0.f, 1.f});
}
// ── to_rotation_matrix3 ───────────────────────────────────────────────────────
TEST(Quaternion, RotationMatrix3FromIdentityIsIdentityMatrix)
{
constexpr Quaternion<float> id;
constexpr auto m = id.to_rotation_matrix3();
for (size_t i = 0; i < 3; ++i)
for (size_t j = 0; j < 3; ++j)
EXPECT_NEAR(m.at(i, j), i == j ? 1.f : 0.f, kEps);
}
TEST(Quaternion, RotationMatrix3From90DegZ)
{
// Expected: | 0 -1 0 |
// | 1 0 0 |
// | 0 0 1 |
const auto q = Quaternion<float>::from_axis_angle({0.f, 0.f, 1.f}, std::numbers::pi_v<float> / 2.f);
const auto m = q.to_rotation_matrix3();
EXPECT_NEAR(m.at(0, 0), 0.f, kEps);
EXPECT_NEAR(m.at(0, 1), -1.f, kEps);
EXPECT_NEAR(m.at(0, 2), 0.f, kEps);
EXPECT_NEAR(m.at(1, 0), 1.f, kEps);
EXPECT_NEAR(m.at(1, 1), 0.f, kEps);
EXPECT_NEAR(m.at(1, 2), 0.f, kEps);
EXPECT_NEAR(m.at(2, 0), 0.f, kEps);
EXPECT_NEAR(m.at(2, 1), 0.f, kEps);
EXPECT_NEAR(m.at(2, 2), 1.f, kEps);
}
TEST(Quaternion, RotationMatrix3ConsistentWithRotate)
{
// Matrix-vector multiply must agree with the rotate() method
const auto q = Quaternion<float>::from_axis_angle({1.f, 1.f, 0.f}, std::numbers::pi_v<float> / 3.f);
const Vector3<float> v{2.f, -1.f, 0.5f};
const auto rotated = q.rotate(v);
const auto m = q.to_rotation_matrix3();
// manual mat-vec multiply (row-major)
const float rx = m.at(0, 0) * v.x + m.at(0, 1) * v.y + m.at(0, 2) * v.z;
const float ry = m.at(1, 0) * v.x + m.at(1, 1) * v.y + m.at(1, 2) * v.z;
const float rz = m.at(2, 0) * v.x + m.at(2, 1) * v.y + m.at(2, 2) * v.z;
EXPECT_NEAR(rotated.x, rx, kEps);
EXPECT_NEAR(rotated.y, ry, kEps);
EXPECT_NEAR(rotated.z, rz, kEps);
}
// ── to_rotation_matrix4 ───────────────────────────────────────────────────────
TEST(Quaternion, RotationMatrix4FromIdentityIsIdentityMatrix)
{
constexpr Quaternion<float> id;
constexpr auto m = id.to_rotation_matrix4();
for (size_t i = 0; i < 4; ++i)
for (size_t j = 0; j < 4; ++j)
EXPECT_NEAR(m.at(i, j), i == j ? 1.f : 0.f, kEps);
}
TEST(Quaternion, RotationMatrix4HomogeneousRowAndColumn)
{
const auto q = Quaternion<float>::from_axis_angle({1.f, 0.f, 0.f}, std::numbers::pi_v<float> / 5.f);
const auto m = q.to_rotation_matrix4();
// Last row and last column must be (0,0,0,1)
for (size_t i = 0; i < 3; ++i)
{
EXPECT_NEAR(m.at(3, i), 0.f, kEps);
EXPECT_NEAR(m.at(i, 3), 0.f, kEps);
}
EXPECT_NEAR(m.at(3, 3), 1.f, kEps);
}
TEST(Quaternion, RotationMatrix4Upper3x3MatchesMatrix3)
{
const auto q = Quaternion<float>::from_axis_angle({0.f, 1.f, 0.f}, std::numbers::pi_v<float> / 7.f);
const auto m3 = q.to_rotation_matrix3();
const auto m4 = q.to_rotation_matrix4();
for (size_t i = 0; i < 3; ++i)
for (size_t j = 0; j < 3; ++j)
EXPECT_NEAR(m4.at(i, j), m3.at(i, j), kEps);
}
// ── as_array ──────────────────────────────────────────────────────────────────
TEST(Quaternion, AsArray)
{
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
constexpr auto arr = q.as_array();
EXPECT_FLOAT_EQ(arr[0], 1.f);
EXPECT_FLOAT_EQ(arr[1], 2.f);
EXPECT_FLOAT_EQ(arr[2], 3.f);
EXPECT_FLOAT_EQ(arr[3], 4.f);
}
// ── std::formatter ────────────────────────────────────────────────────────────
TEST(Quaternion, Formatter)
{
const Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
const auto s = std::format("{}", q);
EXPECT_EQ(s, "[1, 2, 3, 4]");
}

View File

@@ -27,10 +27,11 @@
] ]
}, },
"examples": { "examples": {
"description": "Build benchmarks", "description": "Build examples",
"dependencies": [ "dependencies": [
"glfw3", "glfw3",
"glew" "glew",
"opengl"
] ]
}, },
"imgui": { "imgui": {