mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-13 15:03:27 +00:00
Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 510e3d7d1c | |||
|
|
72404f2159 | ||
|
|
0e792c9b9c | ||
|
|
7e9151084e | ||
|
|
43c8f2e6a5 | ||
| b0fd8d42f4 | |||
| b5229f72d5 | |||
| d5233dd00b | |||
| bd1d437d7d | |||
| f67ad8ef42 | |||
| acc24f8438 | |||
| 8bccbdb620 | |||
| 91a96fb97a | |||
| 5fcac74a25 | |||
| d2cf62bdbe | |||
| 2a2832c75f | |||
| 26fda5402e | |||
| 906ddd75d4 | |||
| 68505a77ae | |||
| d6746f6243 | |||
| ee2f084e0b | |||
| 9bd42d9c8c | |||
| 47d82fe083 | |||
|
|
217fc108c2 | ||
|
|
9c1c4fe6f3 | ||
| 958156e6b7 | |||
|
|
450f3a3ab0 | ||
|
|
8159686658 | ||
| 9fc31d03e5 | |||
| 6017d40579 | |||
| 618d4aa1c0 | |||
| 8366c48965 | |||
| d2e418c50b | |||
| eae10d92ca | |||
| 3f940c8e35 | |||
| 0846022f8a | |||
| 8812abdf33 | |||
| d56d05f01e | |||
| aabdebbbbe | |||
| 2b75b33d60 | |||
| 9a3f5abb7c | |||
| be1049db93 | |||
| 525b273a84 | |||
| 77adc0ea8a | |||
| 83f17892d6 | |||
| 26fd8e158a | |||
| 2af77d30d4 | |||
| 368272b1e8 | |||
|
|
0ca471ed4f | ||
|
|
f0145ec68e | ||
| 771e1e68fe | |||
| ffcf448a07 | |||
| 00fcdc86bc | |||
| 2c11c5ce1a | |||
| fdb2ad099a | |||
| 88ce5e6b8c | |||
| 29da13d244 | |||
| d16984a8b2 | |||
| d935caf1a4 | |||
| 897484bea1 | |||
| a03620c18f | |||
|
|
4a8e7e85ce | ||
| 1499ac3213 | |||
|
|
f3a6a1a3ae | ||
| c312ccad0c | |||
| 939be67643 | |||
| 43a063807d | |||
| 4fd7f8efa6 | |||
| 52ca23383d | |||
| ce21c217f1 | |||
| 09b64cc702 | |||
| d085681efe | |||
|
|
2f7746caeb | ||
| 94ee8751af | |||
|
|
82b9b671f6 | ||
| 082b5f69b8 | |||
|
|
735a565446 | ||
| 852bf5c56f | |||
|
|
de5c8bc84d | ||
| 35d9de1550 | |||
|
|
201d8f5547 | ||
| 5a7b9d2338 | |||
| 3d67827704 | |||
| 45a37eb413 | |||
| 90c4ea2036 | |||
| e10cbf9356 | |||
| 4ad44badb9 | |||
| adce4a808a | |||
| 257b06c552 | |||
| a94c78f834 | |||
| b6ac0a1d61 | |||
| f3b74fe433 | |||
| 2ddf29b158 | |||
| bf30957acf | |||
| c9ac61935e | |||
| 60a3a42140 | |||
| 17e21cde4b | |||
| 7fb5ea47dd | |||
| d7189eb7d4 | |||
| ff35571231 | |||
| 3744a6cdec | |||
| 0fd9a5aed8 | |||
| 3dd792c2d5 | |||
| 584969da44 | |||
| acf36c3e04 | |||
| 27c1d147c5 | |||
| 3831bc0999 | |||
| d23bc3204d | |||
| c158f08430 | |||
| e05eba42c3 | |||
| e97be8c142 | |||
| e97d097b2b | |||
| 58aa03c4a9 | |||
| e1399d1814 | |||
| 1964d3d36f | |||
| d7a009eb67 | |||
| 0e03805439 | |||
| eafefb40ec | |||
| 9e4c778e8f | |||
| 0788fd6122 | |||
| 3685f13344 | |||
| d4d8f70fff | |||
| 918858e255 | |||
| 1aff083ef3 | |||
| 6414922884 | |||
| 57ba809076 | |||
| 6fd3a695cf | |||
| f6857cac90 | |||
| b994e47357 | |||
| 82b21d0458 | |||
| daa1abc047 | |||
| 3a66b66c6a | |||
| 6c89c72041 | |||
| e54d5e7388 | |||
| 9a89e2467e | |||
| 48bf06f69c | |||
| 8feddf872a | |||
| 99ebdeb188 | |||
| ba267cbcb8 | |||
| b98093b244 | |||
| 58392144ca | |||
| f1394a24e5 | |||
| e396e00016 | |||
| 0dc4890107 | |||
| dd8c41b19f | |||
| 0283935918 | |||
| 12f888b8d4 | |||
| a5b24f90dc | |||
|
|
df4947ceb3 | ||
|
|
190a8bf91e | ||
|
|
d118e88f6b | ||
| 1553139a80 | |||
| 798caa2b0d | |||
| 88d4447b20 | |||
| ee458a24f7 | |||
| fa91f21e39 | |||
| 873bdd2036 | |||
| 20aecac2ae | |||
| 09fd92ccad | |||
| 2b21caf58f | |||
| 40e26be72e | |||
| 2699053102 | |||
| 06b597f37c | |||
| 6d3b543648 | |||
| c515dc89a9 | |||
| 66919af46a | |||
| 28ef194586 | |||
| 05bc7577b5 | |||
| 9efabd8fb2 | |||
| 8f225c5f8e | |||
| 44682b6f2c | |||
| a6cf043d79 | |||
| 3968d13a19 | |||
| a3f45628aa | |||
| 6da44a5a51 | |||
| e5d8e1c953 | |||
| e2378bfa8b | |||
| ca3dab855b | |||
| 79482d56d1 | |||
| c4024663bb | |||
| b2a512eafe | |||
| 7b0e2127dc | |||
| 0b663b73d5 | |||
| afc0720f08 | |||
| 015fc9b1e7 | |||
| 62d1a615ae | |||
| 043b5c588d | |||
| cd18b088cb | |||
| ebfdd0b70e | |||
| e7b82f441c | |||
| 6e59957247 | |||
| 1e540862a0 | |||
| 31cc1116ae | |||
| 338246a618 | |||
| 10ebf6ed04 | |||
| ec9a15927a | |||
| 1a0e55b4cf | |||
| b48160e1b7 | |||
| d4a16a94e6 | |||
| 07c0eebf21 | |||
| 9ed18f27c3 | |||
|
|
86b1e8a00d |
25
.cmake-format
Normal file
25
.cmake-format
Normal 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: "*"
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# SCM syntax highlighting & preventing 3-way merges
|
||||
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
|
||||
773
.github/workflows/cmake-multi-platform.yml
vendored
773
.github/workflows/cmake-multi-platform.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Omath CI (Arch Linux / Windows)
|
||||
name: Omath CI
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -10,24 +10,82 @@ concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
##############################################################################
|
||||
# 1) ARCH LINUX – Clang / Ninja
|
||||
# 1) Linux – Clang / Ninja
|
||||
##############################################################################
|
||||
jobs:
|
||||
arch-build-and-test:
|
||||
name: Arch Linux (Clang)
|
||||
runs-on: ubuntu-latest
|
||||
container: archlinux:latest
|
||||
linux-build-and-test:
|
||||
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
|
||||
coverage: true
|
||||
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 \
|
||||
llvm-21
|
||||
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
|
||||
coverage: false
|
||||
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
|
||||
coverage: false
|
||||
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 with pacman
|
||||
- name: Install basic tool-chain
|
||||
shell: bash
|
||||
run: |
|
||||
pacman -Sy --noconfirm archlinux-keyring
|
||||
pacman -Syu --noconfirm --needed \
|
||||
git base-devel clang cmake ninja zip unzip fmt
|
||||
run: ${{ matrix.install_cmd }}
|
||||
|
||||
- name: Checkout repository (with sub-modules)
|
||||
uses: actions/checkout@v4
|
||||
@@ -38,29 +96,82 @@ jobs:
|
||||
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 linux-release-vcpkg -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
run: |
|
||||
cmake --preset ${{ matrix.preset }} \
|
||||
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
|
||||
-DOMATH_BUILD_TESTS=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || 'OFF' }} \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cmake --build cmake-build/build/linux-release-vcpkg --target unit_tests omath
|
||||
run: cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||
|
||||
- name: Run unit_tests
|
||||
shell: bash
|
||||
run: ./out/Release/unit_tests
|
||||
|
||||
- name: Run Coverage
|
||||
if: ${{ matrix.coverage == true }}
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get install lcov
|
||||
chmod +x scripts/coverage-llvm.sh
|
||||
./scripts/coverage-llvm.sh \
|
||||
"${{ github.workspace }}" \
|
||||
"cmake-build/build/${{ matrix.preset }}" \
|
||||
"./out/Release/unit_tests" \
|
||||
"cmake-build/build/${{ matrix.preset }}/coverage"
|
||||
|
||||
- name: Upload Coverage Report
|
||||
if: ${{ matrix.coverage == true }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report-linux
|
||||
path: cmake-build/build/${{ matrix.preset }}/coverage/
|
||||
|
||||
##############################################################################
|
||||
# 2) Windows – MSVC / Ninja
|
||||
##############################################################################
|
||||
- name: Upload logs on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-build-logs-${{ matrix.triplet }}
|
||||
path: |
|
||||
cmake-build/build/${{ matrix.preset }}/**/*.log
|
||||
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
|
||||
|
||||
##############################################################################
|
||||
# 2) Windows – MSVC / Ninja
|
||||
##############################################################################
|
||||
windows-build-and-test:
|
||||
name: Windows (MSVC)
|
||||
runs-on: windows-latest
|
||||
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
|
||||
- name: Windows (MSVC) (x86-windows)
|
||||
runner: windows-latest
|
||||
arch: amd64_x86
|
||||
preset: windows-release-vcpkg-x86
|
||||
triplet: x86-windows
|
||||
- name: Windows (MSVC) (arm64-windows)
|
||||
runner: windows-11-arm
|
||||
arch: arm64
|
||||
preset: windows-release-vcpkg-arm64
|
||||
triplet: arm64-windows
|
||||
fail-fast: false
|
||||
env:
|
||||
OMATH_BUILD_VIA_VCPKG: ON
|
||||
|
||||
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
|
||||
steps:
|
||||
- name: Checkout repository (with sub-modules)
|
||||
uses: actions/checkout@v4
|
||||
@@ -72,15 +183,631 @@ jobs:
|
||||
|
||||
- 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 windows-release-vcpkg -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
run: |
|
||||
cmake --preset ${{ matrix.preset }} \
|
||||
-DOMATH_BUILD_TESTS=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DOMATH_ENABLE_COVERAGE=OFF \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cmake --build cmake-build/build/windows-release-vcpkg --target unit_tests omath
|
||||
run: cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||
|
||||
- name: Run unit_tests.exe
|
||||
shell: bash
|
||||
run: ./out/Release/unit_tests.exe
|
||||
|
||||
##########################################################################
|
||||
# Coverage (x64-windows only)
|
||||
##########################################################################
|
||||
- name: Install LLVM
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
run: |
|
||||
choco install llvm -y
|
||||
|
||||
- name: Clean Build Directory for Coverage
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$buildDir = "cmake-build/build/${{ matrix.preset }}"
|
||||
if (Test-Path $buildDir) {
|
||||
Write-Host "Cleaning build directory to prevent compiler conflict: $buildDir"
|
||||
Remove-Item -Path $buildDir -Recurse -Force
|
||||
}
|
||||
|
||||
- name: Build Debug for Coverage
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --preset ${{ matrix.preset }} \
|
||||
-DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" \
|
||||
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" \
|
||||
-DCMAKE_LINKER="C:/Program Files/LLVM/bin/lld-link.exe" \
|
||||
-DOMATH_BUILD_TESTS=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DOMATH_ENABLE_COVERAGE=ON \
|
||||
-DOMATH_THREAT_WARNING_AS_ERROR=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
cmake --build cmake-build/build/${{ matrix.preset }} --config Debug --target unit_tests omath
|
||||
|
||||
- name: Run Tests (Generates .profraw)
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
shell: pwsh
|
||||
env:
|
||||
LLVM_PROFILE_FILE: "cmake-build/build/${{ matrix.preset }}/unit_tests.profraw"
|
||||
run: |
|
||||
./out/Debug/unit_tests.exe
|
||||
|
||||
- name: Process Coverage (llvm-profdata & llvm-cov)
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$BUILD_DIR = "cmake-build/build/${{ matrix.preset }}"
|
||||
$EXE_PATH = "./out/Debug/unit_tests.exe"
|
||||
|
||||
# 1. Merge raw profile data (essential step)
|
||||
& "C:/Program Files/LLVM/bin/llvm-profdata.exe" merge `
|
||||
-sparse "$BUILD_DIR/unit_tests.profraw" `
|
||||
-o "$BUILD_DIR/unit_tests.profdata"
|
||||
|
||||
# 2. Export to LCOV format
|
||||
# NOTE: We explicitly ignore vcpkg_installed and system headers
|
||||
& "C:/Program Files/LLVM/bin/llvm-cov.exe" export "$EXE_PATH" `
|
||||
-instr-profile="$BUILD_DIR/unit_tests.profdata" `
|
||||
-format=lcov `
|
||||
-ignore-filename-regex="vcpkg_installed|external|tests" `
|
||||
> "$BUILD_DIR/lcov.info"
|
||||
|
||||
if (Test-Path "$BUILD_DIR/lcov.info") {
|
||||
Write-Host "✅ LCOV info created at $BUILD_DIR/lcov.info"
|
||||
} else {
|
||||
Write-Error "Failed to create LCOV info"
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Install LCOV (for genhtml)
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
run: choco install lcov -y
|
||||
|
||||
- name: Generate HTML Report
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
shell: bash
|
||||
run: |
|
||||
BUILD_DIR="cmake-build/build/${{ matrix.preset }}"
|
||||
LCOV_INFO="${BUILD_DIR}/lcov.info"
|
||||
HTML_DIR="${BUILD_DIR}/coverage-html"
|
||||
|
||||
# Fix paths for genhtml (Perl hates backslashes)
|
||||
sed -i 's|\\|/|g' "${LCOV_INFO}"
|
||||
|
||||
# Locate genhtml provided by 'choco install lcov'
|
||||
# It is typically in ProgramData/chocolatey/lib/lcov/tools/bin
|
||||
GENHTML=$(find /c/ProgramData/chocolatey -name genhtml -print -quit)
|
||||
|
||||
if [ -z "$GENHTML" ]; then
|
||||
echo "Error: genhtml executable not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using genhtml: $GENHTML"
|
||||
mkdir -p "$HTML_DIR"
|
||||
|
||||
# Run genhtml
|
||||
# Added --demangle-cpp if your version supports it, otherwise remove it
|
||||
perl "$GENHTML" \
|
||||
"${LCOV_INFO}" \
|
||||
--output-directory "$HTML_DIR" \
|
||||
--title "OMath Coverage Report" \
|
||||
--legend \
|
||||
--show-details \
|
||||
--branch-coverage \
|
||||
--ignore-errors source
|
||||
|
||||
echo "✅ LCOV HTML report generated at $HTML_DIR"
|
||||
|
||||
- name: Upload Coverage (HTML Report)
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-html-windows
|
||||
path: cmake-build/build/${{ matrix.preset }}/coverage-html/
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-build-logs-${{ matrix.triplet }}
|
||||
path: |
|
||||
cmake-build/build/${{ matrix.preset }}/**/*.log
|
||||
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
|
||||
|
||||
##############################################################################
|
||||
# 3) macOS – AppleClang / Ninja
|
||||
##############################################################################
|
||||
macosx-build-and-test:
|
||||
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
|
||||
coverage: true
|
||||
- name: macOS (AppleClang) (x64-osx)
|
||||
runner: macos-15-intel
|
||||
preset: darwin-release-vcpkg-x64
|
||||
triplet: x64-osx
|
||||
coverage: false
|
||||
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=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || 'OFF' }} \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||
|
||||
- name: Run unit_tests
|
||||
shell: bash
|
||||
run: ./out/Release/unit_tests
|
||||
|
||||
- name: Run Coverage
|
||||
if: ${{ matrix.coverage == true }}
|
||||
shell: bash
|
||||
run: |
|
||||
brew install lcov
|
||||
chmod +x scripts/coverage-llvm.sh
|
||||
./scripts/coverage-llvm.sh \
|
||||
"${{ github.workspace }}" \
|
||||
"cmake-build/build/${{ matrix.preset }}" \
|
||||
"./out/Release/unit_tests" \
|
||||
"cmake-build/build/${{ matrix.preset }}/coverage"
|
||||
|
||||
- name: Upload Coverage Report
|
||||
if: ${{ matrix.coverage == true }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report-macos
|
||||
path: cmake-build/build/${{ matrix.preset }}/coverage/
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-build-logs-${{ matrix.triplet }}
|
||||
path: |
|
||||
cmake-build/build/${{ matrix.preset }}/**/*.log
|
||||
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
|
||||
|
||||
##############################################################################
|
||||
# 4) iOS – AppleClang / Xcode / arm64-ios
|
||||
##############################################################################
|
||||
ios-build:
|
||||
name: iOS (AppleClang) (${{ matrix.triplet }})
|
||||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- triplet: arm64-ios
|
||||
preset: ios-release-vcpkg
|
||||
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=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build cmake-build/build/${{ matrix.preset }} --config Release --target unit_tests omath
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ios-build-logs-${{ matrix.triplet }}
|
||||
path: |
|
||||
cmake-build/build/${{ matrix.preset }}/**/*.log
|
||||
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
|
||||
|
||||
##############################################################################
|
||||
# 5) FreeBSD – Clang / Ninja
|
||||
##############################################################################
|
||||
freebsd-build-and-test:
|
||||
name: FreeBSD (Clang) (${{ matrix.triplet }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- triplet: x64-freebsd
|
||||
preset: freebsd-release-vcpkg
|
||||
arch: x86-64
|
||||
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 Test
|
||||
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=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests" \
|
||||
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported"
|
||||
cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||
./out/Release/unit_tests
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: freebsd-build-logs-${{ matrix.triplet }}
|
||||
path: |
|
||||
cmake-build/build/${{ matrix.preset }}/**/*.log
|
||||
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
|
||||
|
||||
##############################################################################
|
||||
# 6) Android NDK – Clang / Ninja
|
||||
##############################################################################
|
||||
android-build:
|
||||
name: Android NDK (${{ matrix.triplet }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- triplet: arm-neon-android
|
||||
preset: android-arm-neon-release-vcpkg
|
||||
- triplet: arm64-android
|
||||
preset: android-arm64-release-vcpkg
|
||||
- triplet: x64-android
|
||||
preset: android-x64-release-vcpkg
|
||||
- triplet: x86-android
|
||||
preset: android-x86-release-vcpkg
|
||||
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=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: android-build-logs-${{ matrix.triplet }}
|
||||
path: |
|
||||
cmake-build/build/${{ matrix.preset }}/**/*.log
|
||||
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
|
||||
|
||||
##############################################################################
|
||||
# 7) WebAssembly (Emscripten) – Clang / Ninja / wasm32-emscripten
|
||||
##############################################################################
|
||||
wasm-build-and-test:
|
||||
name: WebAssembly (Emscripten) (${{ matrix.triplet }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- triplet: wasm32-emscripten
|
||||
preset: wasm-release-vcpkg
|
||||
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
|
||||
# Verify toolchain file exists
|
||||
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=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wasm-build-logs-${{ matrix.triplet }}
|
||||
path: |
|
||||
cmake-build/build/${{ matrix.preset }}/**/*.log
|
||||
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Run WASM Unit Tests
|
||||
run: node out/Release/unit_tests.js
|
||||
|
||||
##############################################################################
|
||||
# 8) Windows MSYS2 MinGW – GCC / Ninja
|
||||
##############################################################################
|
||||
mingw-build-and-test:
|
||||
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
|
||||
- name: UCRT64 (MSYS2) (x64-mingw-dynamic)
|
||||
msystem: UCRT64
|
||||
pkg_prefix: mingw-w64-ucrt-x86_64
|
||||
preset: mingw-release-vcpkg
|
||||
- name: MINGW32 (MSYS2) (x86-mingw-dynamic)
|
||||
msystem: MINGW32
|
||||
pkg_prefix: mingw-w64-i686
|
||||
preset: mingw32-release-vcpkg
|
||||
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=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;tests"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||
|
||||
- name: Run unit_tests.exe
|
||||
run: |
|
||||
./out/Release/unit_tests.exe
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mingw-build-logs-${{ matrix.msystem }}
|
||||
path: |
|
||||
cmake-build/build/${{ matrix.preset }}/**/*.log
|
||||
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
|
||||
|
||||
|
||||
##############################################################################
|
||||
# 9) Valgrind Memory Check
|
||||
##############################################################################
|
||||
valgrind-memory-check:
|
||||
name: Valgrind Analysis (All Targets)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [linux-build-and-test]
|
||||
env:
|
||||
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
|
||||
steps:
|
||||
- name: Install toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
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 \
|
||||
llvm-21 valgrind libxmu-dev libxi-dev libgl-dev libxinerama-dev libxcursor-dev xorg-dev libglu1-mesa-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: 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 linux-release-vcpkg \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DOMATH_BUILD_EXAMPLES=OFF \
|
||||
-DOMATH_BUILD_TESTS=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=ON \
|
||||
-DOMATH_ENABLE_VALGRIND=ON \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;benchmark"
|
||||
|
||||
- name: Build All Targets
|
||||
shell: bash
|
||||
run: cmake --build cmake-build/build/linux-release-vcpkg
|
||||
|
||||
- name: Run Valgrind (All Registered Targets)
|
||||
shell: bash
|
||||
working-directory: cmake-build/build/linux-release-vcpkg
|
||||
run: |
|
||||
cmake --build . --target valgrind_all
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -2,4 +2,13 @@
|
||||
/out
|
||||
*.DS_Store
|
||||
/extlibs/vcpkg
|
||||
.idea/workspace.xml
|
||||
.idea/workspace.xml
|
||||
/build/
|
||||
/clang-coverage/
|
||||
*.gcov
|
||||
*.bin
|
||||
# pixi lock
|
||||
pixi.lock
|
||||
# pixi environments
|
||||
.pixi/*
|
||||
!.pixi/config.toml
|
||||
|
||||
4
.idea/editor.xml
generated
4
.idea/editor.xml
generated
@@ -201,7 +201,7 @@
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
@@ -215,7 +215,7 @@
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
|
||||
2
.idea/omath.iml
generated
2
.idea/omath.iml
generated
@@ -1,2 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
||||
<module classpath="CIDR" type="CPP_MODULE" version="4" />
|
||||
230
CMakeLists.txt
230
CMakeLists.txt
@@ -6,47 +6,59 @@ project(omath VERSION ${OMATH_VERSION} LANGUAGES CXX)
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
if (MSVC)
|
||||
include(cmake/Coverage.cmake)
|
||||
include(cmake/Valgrind.cmake)
|
||||
|
||||
if(MSVC)
|
||||
check_cxx_compiler_flag("/arch:AVX2" COMPILER_SUPPORTS_AVX2)
|
||||
else ()
|
||||
else()
|
||||
check_cxx_compiler_flag("-mavx2" COMPILER_SUPPORTS_AVX2)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
option(OMATH_BUILD_TESTS "Build unit tests" 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_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_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_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON)
|
||||
option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" OFF)
|
||||
option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" ON)
|
||||
|
||||
|
||||
if (VCPKG_MANIFEST_FEATURES)
|
||||
foreach (omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
|
||||
if (omath_feature STREQUAL "imgui")
|
||||
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_FORCE_INLINE
|
||||
"Will for compiler to make some functions to be force inlined no matter what" ON)
|
||||
if(VCPKG_MANIFEST_FEATURES)
|
||||
foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
|
||||
if(omath_feature STREQUAL "imgui")
|
||||
set(OMATH_IMGUI_INTEGRATION ON)
|
||||
elseif (omath_feature STREQUAL "avx2")
|
||||
elseif(omath_feature STREQUAL "avx2")
|
||||
set(OMATH_USE_AVX2 ${COMPILER_SUPPORTS_AVX2})
|
||||
elseif (omath_feature STREQUAL "tests")
|
||||
elseif(omath_feature STREQUAL "tests")
|
||||
set(OMATH_BUILD_TESTS ON)
|
||||
elseif (omath_feature STREQUAL "benchmark")
|
||||
elseif(omath_feature STREQUAL "benchmark")
|
||||
set(OMATH_BUILD_BENCHMARK ON)
|
||||
endif ()
|
||||
elseif(omath_feature STREQUAL "examples")
|
||||
set(OMATH_BUILD_EXAMPLES ON)
|
||||
endif()
|
||||
|
||||
endforeach ()
|
||||
endif ()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if (OMATH_USE_AVX2 AND NOT COMPILER_SUPPORTS_AVX2)
|
||||
message(WARNING "OMATH_USE_AVX2 requested, but compiler/target does not support AVX2. Disabling.")
|
||||
if(OMATH_USE_AVX2 AND NOT COMPILER_SUPPORTS_AVX2)
|
||||
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)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
if (${PROJECT_IS_TOP_LEVEL})
|
||||
message(STATUS "[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}, compiler ${CMAKE_CXX_COMPILER_ID}")
|
||||
if(${PROJECT_IS_TOP_LEVEL})
|
||||
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}]: Build unit tests ${OMATH_BUILD_TESTS}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Build benchmark ${OMATH_BUILD_BENCHMARK}")
|
||||
@@ -58,64 +70,65 @@ if (${PROJECT_IS_TOP_LEVEL})
|
||||
message(STATUS "[${PROJECT_NAME}]: ImGUI integration feature status ${OMATH_IMGUI_INTEGRATION}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Legacy features support ${OMATH_ENABLE_LEGACY}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Building using vcpkg ${OMATH_BUILD_VIA_VCPKG}")
|
||||
endif ()
|
||||
message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}")
|
||||
endif()
|
||||
|
||||
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")
|
||||
|
||||
if (OMATH_BUILD_AS_SHARED_LIBRARY)
|
||||
if(OMATH_BUILD_AS_SHARED_LIBRARY)
|
||||
add_library(${PROJECT_NAME} SHARED ${OMATH_SOURCES} ${OMATH_HEADERS})
|
||||
else ()
|
||||
else()
|
||||
add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS})
|
||||
endif ()
|
||||
|
||||
endif()
|
||||
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
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)
|
||||
|
||||
# IMGUI is being linked as submodule
|
||||
if (TARGET imgui)
|
||||
if(TARGET imgui)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC imgui)
|
||||
install(TARGETS imgui
|
||||
EXPORT omathTargets
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin)
|
||||
else ()
|
||||
install(
|
||||
TARGETS imgui
|
||||
EXPORT omathTargets
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin)
|
||||
else()
|
||||
# Assume that IMGUI linked via VCPKG.
|
||||
find_package(imgui CONFIG REQUIRED)
|
||||
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)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
if (OMATH_SUPRESS_SAFETY_CHECKS)
|
||||
if(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)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED ON)
|
||||
if(OMATH_ENABLE_FORCE_INLINE)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_FORCE_INLINE)
|
||||
endif()
|
||||
|
||||
if (OMATH_USE_UNITY_BUILD)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
UNITY_BUILD ON
|
||||
UNITY_BUILD_BATCH_SIZE 20)
|
||||
endif ()
|
||||
set_target_properties(
|
||||
${PROJECT_NAME}
|
||||
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
@@ -123,87 +136,96 @@ if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY)
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (OMATH_USE_AVX2)
|
||||
if (MSVC)
|
||||
if(OMATH_USE_AVX2)
|
||||
if(MSVC)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC /arch:AVX2)
|
||||
elseif (EMSCRIPTEN)
|
||||
elseif(EMSCRIPTEN)
|
||||
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)
|
||||
endif ()
|
||||
endif ()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -fexceptions)
|
||||
target_link_options(${PROJECT_NAME} PUBLIC -fexceptions)
|
||||
endif()
|
||||
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
|
||||
|
||||
if (OMATH_BUILD_TESTS)
|
||||
if(OMATH_BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS)
|
||||
endif ()
|
||||
if(OMATH_ENABLE_COVERAGE)
|
||||
omath_setup_coverage(${PROJECT_NAME})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (OMATH_BUILD_BENCHMARK)
|
||||
if(OMATH_BUILD_BENCHMARK)
|
||||
add_subdirectory(benchmark)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
if (OMATH_BUILD_EXAMPLES)
|
||||
if(OMATH_BUILD_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)
|
||||
elseif (OMATH_THREAT_WARNING_AS_ERROR)
|
||||
elseif(OMATH_THREAT_WARNING_AS_ERROR)
|
||||
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
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_compile_definitions(${PROJECT_NAME} INTERFACE NOMINMAX)
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
|
||||
# Installation rules
|
||||
|
||||
# Install the library
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT ${PROJECT_NAME}Targets
|
||||
ARCHIVE DESTINATION lib COMPONENT ${PROJECT_NAME} # For static libraries
|
||||
LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries
|
||||
RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on Windows)
|
||||
)
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME}
|
||||
EXPORT ${PROJECT_NAME}Targets
|
||||
ARCHIVE DESTINATION lib COMPONENT ${PROJECT_NAME} # For static libraries
|
||||
LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries
|
||||
RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on
|
||||
# Windows)
|
||||
)
|
||||
|
||||
# Install headers as part of omath_component
|
||||
install(DIRECTORY include/ DESTINATION include COMPONENT ${PROJECT_NAME})
|
||||
|
||||
# Export omath target for CMake find_package support, also under omath_component
|
||||
install(EXPORT ${PROJECT_NAME}Targets
|
||||
FILE ${PROJECT_NAME}Targets.cmake
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION lib/cmake/${PROJECT_NAME} COMPONENT ${PROJECT_NAME}
|
||||
)
|
||||
|
||||
install(
|
||||
EXPORT ${PROJECT_NAME}Targets
|
||||
FILE ${PROJECT_NAME}Targets.cmake
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION lib/cmake/${PROJECT_NAME}
|
||||
COMPONENT ${PROJECT_NAME})
|
||||
|
||||
# Generate the omathConfigVersion.cmake file
|
||||
write_basic_package_version_file(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
|
||||
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(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/omathConfig.cmake.in" # Path to the .in file
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" # Output path for the generated file
|
||||
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}
|
||||
)
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/omathConfig.cmake.in" # Path to the .in
|
||||
# file
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" # Output path for the
|
||||
# generated file
|
||||
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME})
|
||||
|
||||
# Install the generated config files
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
|
||||
DESTINATION lib/cmake/${PROJECT_NAME}
|
||||
)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
|
||||
DESTINATION lib/cmake/${PROJECT_NAME})
|
||||
|
||||
@@ -1,15 +1,49 @@
|
||||
{
|
||||
"version": 3,
|
||||
"version": 6,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 25,
|
||||
"patch": 0
|
||||
},
|
||||
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "windows-base",
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
|
||||
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
|
||||
"installDir": "${sourceDir}/cmake-build/install/${presetName}"
|
||||
},
|
||||
{
|
||||
"name": "vcpkg-base",
|
||||
"hidden": true,
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_COMPILER": "cl.exe",
|
||||
"CMAKE_MAKE_PROGRAM": "Ninja"
|
||||
"OMATH_BUILD_VIA_VCPKG": "ON",
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"VCPKG_INSTALLED_DIR": "${sourceDir}/cmake-build/vcpkg_installed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"hidden": true,
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release",
|
||||
"hidden": true,
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "windows-base",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_COMPILER": "cl.exe"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
@@ -18,59 +52,88 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-base-vcpkg",
|
||||
"name": "windows-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": "windows-base",
|
||||
"inherits": ["windows-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"OMATH_BUILD_VIA_VCPKG": "ON",
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"VCPKG_INSTALLED_DIR": "${sourceDir}/cmake-build/vcpkg_installed",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2"
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-debug",
|
||||
"displayName": "Debug",
|
||||
"inherits": "windows-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-debug-vcpkg",
|
||||
"displayName": "Debug",
|
||||
"inherits": "windows-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-release-vcpkg",
|
||||
"displayName": "Release",
|
||||
"inherits": "windows-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"OMATH_BUILD_VIA_VCPKG": "ON"
|
||||
}
|
||||
"displayName": "Windows Debug",
|
||||
"inherits": ["windows-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "windows-release",
|
||||
"displayName": "Release",
|
||||
"inherits": "windows-base",
|
||||
"displayName": "Windows Release",
|
||||
"inherits": ["windows-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "windows-debug-vcpkg",
|
||||
"displayName": "Windows Debug (vcpkg)",
|
||||
"inherits": ["windows-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "windows-release-vcpkg",
|
||||
"displayName": "Windows Release (vcpkg)",
|
||||
"inherits": ["windows-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "windows-x86-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["windows-base", "vcpkg-base"],
|
||||
"architecture": {
|
||||
"value": "x86",
|
||||
"strategy": "external"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
"VCPKG_TARGET_TRIPLET": "x86-windows",
|
||||
"VCPKG_HOST_TRIPLET": "x64-windows",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-debug-vcpkg-x86",
|
||||
"displayName": "Windows x86 Debug (vcpkg)",
|
||||
"inherits": ["windows-x86-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "windows-release-vcpkg-x86",
|
||||
"displayName": "Windows x86 Release (vcpkg)",
|
||||
"inherits": ["windows-x86-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "windows-arm64-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["windows-base", "vcpkg-base"],
|
||||
"architecture": {
|
||||
"value": "arm64",
|
||||
"strategy": "external"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"VCPKG_TARGET_TRIPLET": "arm64-windows",
|
||||
"VCPKG_HOST_TRIPLET": "arm64-windows",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;examples"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-debug-vcpkg-arm64",
|
||||
"displayName": "Windows ARM64 Debug (vcpkg)",
|
||||
"inherits": ["windows-arm64-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "windows-release-vcpkg-arm64",
|
||||
"displayName": "Windows ARM64 Release (vcpkg)",
|
||||
"inherits": ["windows-arm64-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "linux-base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
|
||||
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_MAKE_PROGRAM": "ninja"
|
||||
},
|
||||
"inherits": "base",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
@@ -78,54 +141,86 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-base-vcpkg",
|
||||
"name": "linux-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": "linux-base",
|
||||
"inherits": ["linux-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"OMATH_BUILD_VIA_VCPKG": "ON",
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"VCPKG_INSTALLED_DIR": "${sourceDir}/cmake-build/vcpkg_installed",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-debug",
|
||||
"displayName": "Linux Debug",
|
||||
"inherits": "linux-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-debug-vcpkg",
|
||||
"displayName": "Linux Debug",
|
||||
"inherits": "linux-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
"inherits": ["linux-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "linux-release",
|
||||
"displayName": "Linux Release",
|
||||
"inherits": "linux-debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
"inherits": ["linux-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "linux-debug-vcpkg",
|
||||
"displayName": "Linux Debug (vcpkg)",
|
||||
"inherits": ["linux-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "linux-release-vcpkg",
|
||||
"displayName": "Linux Release",
|
||||
"inherits": "linux-base-vcpkg",
|
||||
"displayName": "Linux Release (vcpkg)",
|
||||
"inherits": ["linux-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "linux-x86-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["linux-base", "vcpkg-base"],
|
||||
"architecture": {
|
||||
"value": "x86",
|
||||
"strategy": "external"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
"CMAKE_C_FLAGS": "-m32",
|
||||
"CMAKE_CXX_FLAGS": "-m32",
|
||||
"VCPKG_TARGET_TRIPLET": "x86-linux",
|
||||
"VCPKG_HOST_TRIPLET": "x64-linux",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-debug-vcpkg-x86",
|
||||
"displayName": "Linux x86 Debug (vcpkg)",
|
||||
"inherits": ["linux-x86-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "linux-release-vcpkg-x86",
|
||||
"displayName": "Linux x86 Release (vcpkg)",
|
||||
"inherits": ["linux-x86-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "linux-arm64-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["linux-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"VCPKG_TARGET_TRIPLET": "arm64-linux",
|
||||
"VCPKG_HOST_TRIPLET": "arm64-linux",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-debug-vcpkg-arm64",
|
||||
"displayName": "Linux ARM64 Debug (vcpkg)",
|
||||
"inherits": ["linux-arm64-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "linux-release-vcpkg-arm64",
|
||||
"displayName": "Linux ARM64 Release (vcpkg)",
|
||||
"inherits": ["linux-arm64-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "darwin-base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
|
||||
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_COMPILER": "clang++"
|
||||
},
|
||||
@@ -136,36 +231,456 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-debug",
|
||||
"displayName": "Darwin Debug",
|
||||
"inherits": "darwin-base",
|
||||
"name": "darwin-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["darwin-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-debug-vcpkg",
|
||||
"displayName": "Darwin Debug",
|
||||
"inherits": "darwin-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
"name": "darwin-debug",
|
||||
"displayName": "macOS Debug",
|
||||
"inherits": ["darwin-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "darwin-release",
|
||||
"displayName": "Darwin Release",
|
||||
"inherits": "darwin-debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
"displayName": "macOS Release",
|
||||
"inherits": ["darwin-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "darwin-debug-vcpkg",
|
||||
"displayName": "macOS Debug (vcpkg)",
|
||||
"inherits": ["darwin-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "darwin-release-vcpkg",
|
||||
"displayName": "Darwin Release",
|
||||
"inherits": "darwin-debug",
|
||||
"displayName": "macOS Release (vcpkg)",
|
||||
"inherits": ["darwin-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "darwin-x64-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["darwin-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
"CMAKE_OSX_ARCHITECTURES": "x86_64",
|
||||
"VCPKG_TARGET_TRIPLET": "x64-osx",
|
||||
"VCPKG_HOST_TRIPLET": "x64-osx",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-debug-vcpkg-x64",
|
||||
"displayName": "macOS x64 Debug (vcpkg)",
|
||||
"inherits": ["darwin-x64-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "darwin-release-vcpkg-x64",
|
||||
"displayName": "macOS x64 Release (vcpkg)",
|
||||
"inherits": ["darwin-x64-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "ios-base",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_SYSTEM_NAME": "iOS",
|
||||
"CMAKE_OSX_DEPLOYMENT_TARGET": "18.5",
|
||||
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED": "NO",
|
||||
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED": "NO"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Darwin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ios-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["ios-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"VCPKG_TARGET_TRIPLET": "arm64-ios",
|
||||
"VCPKG_HOST_TRIPLET": "arm64-osx",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ios-debug-vcpkg",
|
||||
"displayName": "iOS Debug (vcpkg)",
|
||||
"inherits": ["ios-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "ios-release-vcpkg",
|
||||
"displayName": "iOS Release (vcpkg)",
|
||||
"inherits": ["ios-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "freebsd-base",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "FreeBSD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "freebsd-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["freebsd-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "freebsd-debug",
|
||||
"displayName": "FreeBSD Debug",
|
||||
"inherits": ["freebsd-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "freebsd-release",
|
||||
"displayName": "FreeBSD Release",
|
||||
"inherits": ["freebsd-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "freebsd-debug-vcpkg",
|
||||
"displayName": "FreeBSD Debug (vcpkg)",
|
||||
"inherits": ["freebsd-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "freebsd-release-vcpkg",
|
||||
"displayName": "FreeBSD Release (vcpkg)",
|
||||
"inherits": ["freebsd-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "android-base",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_SYSTEM_NAME": "Android",
|
||||
"CMAKE_SYSTEM_VERSION": "24",
|
||||
"CMAKE_ANDROID_NDK": "$env{ANDROID_NDK_HOME}",
|
||||
"CMAKE_ANDROID_STL_TYPE": "c++_static"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["android-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "android-arm64-base",
|
||||
"hidden": true,
|
||||
"inherits": "android-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_ANDROID_ARCH_ABI": "arm64-v8a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-arm64-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": "android-vcpkg-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_ANDROID_ARCH_ABI": "arm64-v8a",
|
||||
"VCPKG_TARGET_TRIPLET": "arm64-android"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-arm64-debug",
|
||||
"displayName": "Android arm64-v8a Debug",
|
||||
"inherits": ["android-arm64-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "android-arm64-release",
|
||||
"displayName": "Android arm64-v8a Release",
|
||||
"inherits": ["android-arm64-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "android-arm64-debug-vcpkg",
|
||||
"displayName": "Android arm64-v8a Debug (vcpkg)",
|
||||
"inherits": ["android-arm64-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "android-arm64-release-vcpkg",
|
||||
"displayName": "Android arm64-v8a Release (vcpkg)",
|
||||
"inherits": ["android-arm64-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "android-arm-neon-base",
|
||||
"hidden": true,
|
||||
"inherits": "android-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_ANDROID_ARCH_ABI": "armeabi-v7a",
|
||||
"CMAKE_ANDROID_ARM_NEON": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-arm-neon-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": "android-vcpkg-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_ANDROID_ARCH_ABI": "armeabi-v7a",
|
||||
"CMAKE_ANDROID_ARM_NEON": "ON",
|
||||
"VCPKG_TARGET_TRIPLET": "arm-neon-android"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-arm-neon-debug",
|
||||
"displayName": "Android armeabi-v7a NEON Debug",
|
||||
"inherits": ["android-arm-neon-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "android-arm-neon-release",
|
||||
"displayName": "Android armeabi-v7a NEON Release",
|
||||
"inherits": ["android-arm-neon-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "android-arm-neon-debug-vcpkg",
|
||||
"displayName": "Android armeabi-v7a NEON Debug (vcpkg)",
|
||||
"inherits": ["android-arm-neon-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "android-arm-neon-release-vcpkg",
|
||||
"displayName": "Android armeabi-v7a NEON Release (vcpkg)",
|
||||
"inherits": ["android-arm-neon-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "android-x64-base",
|
||||
"hidden": true,
|
||||
"inherits": "android-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_ANDROID_ARCH_ABI": "x86_64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-x64-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": "android-vcpkg-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_ANDROID_ARCH_ABI": "x86_64",
|
||||
"VCPKG_TARGET_TRIPLET": "x64-android"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-x64-debug",
|
||||
"displayName": "Android x86_64 Debug",
|
||||
"inherits": ["android-x64-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "android-x64-release",
|
||||
"displayName": "Android x86_64 Release",
|
||||
"inherits": ["android-x64-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "android-x64-debug-vcpkg",
|
||||
"displayName": "Android x86_64 Debug (vcpkg)",
|
||||
"inherits": ["android-x64-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "android-x64-release-vcpkg",
|
||||
"displayName": "Android x86_64 Release (vcpkg)",
|
||||
"inherits": ["android-x64-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "android-x86-base",
|
||||
"hidden": true,
|
||||
"inherits": "android-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_ANDROID_ARCH_ABI": "x86"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-x86-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": "android-vcpkg-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_ANDROID_ARCH_ABI": "x86",
|
||||
"VCPKG_TARGET_TRIPLET": "x86-android"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-x86-debug",
|
||||
"displayName": "Android x86 Debug",
|
||||
"inherits": ["android-x86-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "android-x86-release",
|
||||
"displayName": "Android x86 Release",
|
||||
"inherits": ["android-x86-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "android-x86-debug-vcpkg",
|
||||
"displayName": "Android x86 Debug (vcpkg)",
|
||||
"inherits": ["android-x86-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "android-x86-release-vcpkg",
|
||||
"displayName": "Android x86 Release (vcpkg)",
|
||||
"inherits": ["android-x86-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "android-debug",
|
||||
"displayName": "Android Debug (default: arm64)",
|
||||
"inherits": "android-arm64-debug"
|
||||
},
|
||||
{
|
||||
"name": "android-release",
|
||||
"displayName": "Android Release (default: arm64)",
|
||||
"inherits": "android-arm64-release"
|
||||
},
|
||||
{
|
||||
"name": "android-debug-vcpkg",
|
||||
"displayName": "Android Debug (default: arm64, vcpkg)",
|
||||
"inherits": "android-arm64-debug-vcpkg"
|
||||
},
|
||||
{
|
||||
"name": "android-release-vcpkg",
|
||||
"displayName": "Android Release (default: arm64, vcpkg)",
|
||||
"inherits": "android-arm64-release-vcpkg"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "wasm-base",
|
||||
"hidden": true,
|
||||
"inherits": "base"
|
||||
},
|
||||
{
|
||||
"name": "wasm-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["wasm-base", "vcpkg-base"],
|
||||
"cacheVariables": {
|
||||
"VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "$env{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
|
||||
"VCPKG_TARGET_TRIPLET": "wasm32-emscripten",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wasm-debug-vcpkg",
|
||||
"displayName": "WebAssembly Debug (vcpkg)",
|
||||
"inherits": ["wasm-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "wasm-release-vcpkg",
|
||||
"displayName": "WebAssembly Release (vcpkg)",
|
||||
"inherits": ["wasm-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "mingw-base",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "gcc",
|
||||
"CMAKE_CXX_COMPILER": "g++"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mingw-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["mingw-base", "vcpkg-base"],
|
||||
"environment": {
|
||||
"VCPKG_DEFAULT_HOST_TRIPLET": "x64-mingw-dynamic"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"VCPKG_TARGET_TRIPLET": "x64-mingw-dynamic",
|
||||
"VCPKG_HOST_TRIPLET": "x64-mingw-dynamic",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mingw-debug",
|
||||
"displayName": "MinGW x64 Debug",
|
||||
"inherits": ["mingw-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "mingw-release",
|
||||
"displayName": "MinGW x64 Release",
|
||||
"inherits": ["mingw-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "mingw-debug-vcpkg",
|
||||
"displayName": "MinGW x64 Debug (vcpkg)",
|
||||
"inherits": ["mingw-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "mingw-release-vcpkg",
|
||||
"displayName": "MinGW x64 Release (vcpkg)",
|
||||
"inherits": ["mingw-vcpkg-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "mingw-ucrt-release-vcpkg",
|
||||
"displayName": "MinGW UCRT64 Release (vcpkg)",
|
||||
"inherits": ["mingw-vcpkg-base", "release"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "mingw32-base",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "gcc",
|
||||
"CMAKE_CXX_COMPILER": "g++"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mingw32-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": ["mingw32-base", "vcpkg-base"],
|
||||
"environment": {
|
||||
"VCPKG_DEFAULT_HOST_TRIPLET": "x86-mingw-dynamic"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"VCPKG_TARGET_TRIPLET": "x86-mingw-dynamic",
|
||||
"VCPKG_HOST_TRIPLET": "x86-mingw-dynamic",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mingw32-debug",
|
||||
"displayName": "MinGW x86 Debug",
|
||||
"inherits": ["mingw32-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "mingw32-release",
|
||||
"displayName": "MinGW x86 Release",
|
||||
"inherits": ["mingw32-base", "release"]
|
||||
},
|
||||
{
|
||||
"name": "mingw32-debug-vcpkg",
|
||||
"displayName": "MinGW x86 Debug (vcpkg)",
|
||||
"inherits": ["mingw32-vcpkg-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "mingw32-release-vcpkg",
|
||||
"displayName": "MinGW x86 Release (vcpkg)",
|
||||
"inherits": ["mingw32-vcpkg-base", "release"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,201 @@
|
||||
## 🎯 Goal
|
||||
# UNIVERSAL DECLARATION OF CODE OF CONDUCT
|
||||
_Declaration of Community Rights and Responsibilities_
|
||||
|
||||
My goal is to provide a space where it is safe for everyone to contribute to,
|
||||
and get support for, open-source software in a respectful and cooperative
|
||||
manner.
|
||||
## Preamble
|
||||
|
||||
I value all contributions and want to make this project and its
|
||||
surrounding community a place for everyone.
|
||||
Whereas the Orange++ community is founded on cooperation, mutual respect and support for the development of open-source software;
|
||||
|
||||
As members, contributors, and everyone else who may participate in the
|
||||
development, I strive to keep the entire experience civil.
|
||||
Whereas it is essential that all participants can contribute and seek assistance in an environment that is safe, inclusive and free from discrimination and harassment;
|
||||
|
||||
## 📜 Standards
|
||||
Whereas the dignity and equality of all participants, regardless of their traits or background, must be respected and protected;
|
||||
|
||||
Our community standards exist in order to make sure everyone feels comfortable
|
||||
contributing to the project(s) together.
|
||||
Now, therefore, this Community Code of Conduct is proclaimed as a common standard of behaviour for all members, contributors and participants in projects led by Orange++ and its official communities.
|
||||
|
||||
Our standards are:
|
||||
- Do not harass, attack, or in any other way discriminate against anyone, including
|
||||
for their protected traits, including, but not limited to, sex, religion, race,
|
||||
appearance, gender, identity, nationality, sexuality, etc.
|
||||
- Do not go off-topic, do not post spam.
|
||||
- Treat everyone with respect.
|
||||
---
|
||||
|
||||
Examples of breaking each rule respectively include:
|
||||
- Harassment, bullying or inappropriate jokes about another person.
|
||||
- Posting distasteful imagery, trolling, or posting things unrelated to the topic at hand.
|
||||
- Treating someone as worse because of their lack of understanding of an issue.
|
||||
## Article 1
|
||||
|
||||
## ⚡ Enforcement
|
||||
This Code of Conduct establishes standards of behaviour intended to:
|
||||
|
||||
Enforcement of this CoC is done by Orange++ and/or other core contributors.
|
||||
1. Provide a safe and welcoming environment for all participants.
|
||||
2. Encourage respectful and constructive collaboration.
|
||||
3. Prevent harassment, discrimination, and other harmful conduct.
|
||||
|
||||
I, as the core developer, will strive my best to keep this community civil and
|
||||
following the standards outlined above.
|
||||
All individuals who participate in Orange++ projects or official communities, whether online or offline, are expected to adhere to this Code of Conduct.
|
||||
|
||||
### 🚩 Reporting incidents
|
||||
---
|
||||
|
||||
If you believe an incident of breaking these standards has occurred, but nobody has
|
||||
taken appropriate action, you can privately contact the people responsible for dealing
|
||||
with such incidents in multiple ways:
|
||||
## Article 2
|
||||
|
||||
All participants are equal in dignity and rights within the community.
|
||||
|
||||
No person shall be harassed, attacked, or discriminated against on the basis of protected or personal traits, including but not limited to:
|
||||
|
||||
- sex;
|
||||
- religion or belief;
|
||||
- race or ethnicity;
|
||||
- appearance;
|
||||
- gender or gender identity;
|
||||
- nationality;
|
||||
- sexual orientation;
|
||||
- or any other similar characteristic.
|
||||
|
||||
Treating someone as lesser or unworthy because of their knowledge, experience, or level of understanding of an issue is incompatible with this Code.
|
||||
|
||||
---
|
||||
|
||||
## Article 3
|
||||
|
||||
Participants shall treat one another with respect at all times.
|
||||
|
||||
Participants shall:
|
||||
|
||||
1. Engage in discussion in good faith and assume good intent where reasonable.
|
||||
2. Provide feedback and criticism in a constructive and considerate manner.
|
||||
3. Recognize that people have different backgrounds, perspectives, and levels of expertise.
|
||||
|
||||
Examples of conduct contrary to this Article include, but are not limited to:
|
||||
|
||||
- harassment, bullying, personal attacks or degrading comments;
|
||||
- inappropriate or offensive jokes or remarks about another person;
|
||||
- persistent disruption of discussions or activities.
|
||||
|
||||
---
|
||||
|
||||
## Article 4
|
||||
|
||||
Participants shall remain on topic and avoid posting spam or irrelevant material.
|
||||
|
||||
Content that is distasteful, deliberately inflammatory, or unrelated to the project or discussion at hand is prohibited.
|
||||
|
||||
Examples of prohibited conduct under this Article include:
|
||||
|
||||
- posting trolling or inflammatory messages;
|
||||
- sharing disturbing or inappropriate imagery unrelated to the topic;
|
||||
- repeatedly derailing conversations away from their intended purpose.
|
||||
|
||||
---
|
||||
|
||||
## Article 5
|
||||
|
||||
The following standards shall guide all participation in Orange++ projects and official communities:
|
||||
|
||||
1. Do not harass, attack, or discriminate against any person.
|
||||
2. Do not go off-topic and do not post spam.
|
||||
3. Treat all participants with respect.
|
||||
|
||||
These standards apply equally to maintainers, contributors, and all other participants, regardless of status or seniority.
|
||||
|
||||
---
|
||||
|
||||
## Article 6
|
||||
|
||||
Enforcement of this Code of Conduct is carried out by Orange++ and/or other core contributors (hereinafter “members”).
|
||||
|
||||
Members shall strive to:
|
||||
|
||||
1. Act fairly, consistently, and transparently.
|
||||
2. Consider the context and severity of each incident.
|
||||
3. Maintain a civil and welcoming environment for the community as a whole.
|
||||
|
||||
Where appropriate, members may consult individuals with relevant lived experience, particularly when an incident concerns a marginalized group, while preserving confidentiality as required by this Code.
|
||||
|
||||
---
|
||||
|
||||
## Article 7
|
||||
|
||||
Any participant who believes that a breach of this Code of Conduct has occurred and has not been appropriately addressed may report the incident privately.
|
||||
|
||||
Reports may be submitted through any of the following channels:
|
||||
|
||||
**E-mail**
|
||||
|
||||
***E-Mail***
|
||||
- `orange-cpp@yandex.ru`
|
||||
|
||||
***Discord***
|
||||
**Discord**
|
||||
|
||||
- `@orange_cpp`
|
||||
|
||||
***Telegram***
|
||||
**Telegram**
|
||||
|
||||
- `@orange_cpp`
|
||||
|
||||
I guarantee your privacy and will not share those reports with anyone.
|
||||
The reporting party’s privacy shall be respected, and reports shall not be shared beyond those responsible for handling them, except where required by law or with the explicit consent of the reporting party.
|
||||
|
||||
## ⚖️ Enforcement Strategy
|
||||
---
|
||||
|
||||
Depending on the severity of the infraction, any action from the list below may be applied.
|
||||
Please keep in mind cases are reviewed on a per-case basis and members are the ultimate
|
||||
deciding factor in the type of punishment.
|
||||
## Article 8
|
||||
|
||||
If the matter benefited from an outside opinion, a member might reach for more opinions
|
||||
from people unrelated, however, the final decision regarding the action
|
||||
to be taken is still up to the member.
|
||||
Depending on the nature and severity of the infraction, and taking into account past behaviour, members may apply one or more of the following measures.
|
||||
|
||||
For example, if the matter at hand regards a representative of a marginalized group or minority,
|
||||
the member might ask for a first-hand opinion from another representative of such group.
|
||||
**1. Correction / Edit**
|
||||
|
||||
### ✏️ Correction/Edit
|
||||
Where a message is misleading, poorly worded, or likely to cause misunderstanding, members may:
|
||||
|
||||
If your message is found to be misleading or poorly worded, a member might
|
||||
edit your message.
|
||||
1. Request that the author clarify or correct the message; or
|
||||
2. Edit the message where the platform permits and such action is appropriate and transparent.
|
||||
|
||||
### ⚠️ Warning/Deletion
|
||||
**2. Warning / Deletion**
|
||||
|
||||
If your message is found inappropriate, a member might give you a public or private warning,
|
||||
and/or delete your message.
|
||||
Where a message is inappropriate or in breach of the standards, members may:
|
||||
|
||||
### 🔇 Mute
|
||||
1. Issue a public or private warning; and/or
|
||||
2. Delete the message.
|
||||
|
||||
If your message is disruptive, or you have been repeatedly violating the standards,
|
||||
a member might mute (or temporarily ban) you.
|
||||
**3. Mute / Temporary Ban**
|
||||
|
||||
### ⛔ Ban
|
||||
Where a participant is repeatedly violating the standards, or where their behaviour is significantly disruptive, members may:
|
||||
|
||||
If your message is hateful, very disruptive, or other, less serious infractions are repeated
|
||||
ignoring previous punishments, a member might ban you permanently.
|
||||
1. Temporarily mute the participant; or
|
||||
2. Temporarily suspend or ban the participant from the community.
|
||||
|
||||
## 🔎 Scope
|
||||
**4. Permanent Ban**
|
||||
|
||||
This CoC shall apply to all projects ran under the Orange++ lead and all _official_ communities
|
||||
outside of GitHub.
|
||||
Where a message is hateful or severely disruptive, or where less serious infractions are repeated despite prior measures, members may permanently ban the participant.
|
||||
|
||||
However, it is worth noting that official communities outside of GitHub might have their own,
|
||||
additional sets of rules.
|
||||
Each case shall be considered individually. The final decision regarding the appropriate measure lies with the members responsible for enforcement.
|
||||
|
||||
---
|
||||
|
||||
## Article 9
|
||||
|
||||
Reports of misconduct and information regarding enforcement actions shall be handled with care and confidentiality.
|
||||
|
||||
The personal data of reporters, witnesses, and involved parties shall not be disclosed to third parties, except:
|
||||
|
||||
1. Where such disclosure is required by law; or
|
||||
2. Where explicit consent has been given by the person concerned.
|
||||
|
||||
The maintainer guarantees that every report will be treated with discretion and respect.
|
||||
|
||||
---
|
||||
|
||||
## Article 10
|
||||
|
||||
This Code of Conduct applies to:
|
||||
|
||||
1. All projects led under the Orange++ name or leadership; and
|
||||
2. All official communities associated with Orange++ outside of GitHub.
|
||||
|
||||
Official communities outside of GitHub may maintain additional rules specific to their platform. In case of overlap, participants are expected to follow:
|
||||
|
||||
1. The rules of the platform or community; and
|
||||
2. This Code of Conduct, insofar as it is applicable.
|
||||
|
||||
---
|
||||
|
||||
## Article 11
|
||||
|
||||
This Code of Conduct shall be interpreted in a manner consistent with its purpose: to promote a safe, respectful and inclusive community.
|
||||
|
||||
The maintainer and core contributors may review and revise this Code of Conduct periodically in light of community needs and experience.
|
||||
|
||||
Significant changes should be communicated to the community in a timely and clear manner.
|
||||
|
||||
---
|
||||
|
||||
## Article 12
|
||||
|
||||
Nothing in this Community Code of Conduct may be interpreted as granting to any maintainer, member, contributor, or participant any right to engage in any activity or to perform any act that aims at undermining, limiting, or destroying the rights, protections, and standards set forth herein.
|
||||
|
||||
No rule, policy, custom, or decision within the Orange++ projects or their official communities may be invoked to justify harassment, discrimination, retaliation, or any other conduct contrary to this Code of Conduct.
|
||||
|
||||
44
LICENSE
44
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (C) 2024-2025 Orange++ <orange_github@proton.me>
|
||||
Copyright (C) 2023-2026 Orange++ orange_github@proton.me
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
@@ -15,45 +15,3 @@ freely, subject to the following restrictions:
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
4. If you are an employee, contractor, volunteer, representative,
|
||||
or have any other affiliation (past or present)
|
||||
with any of the following entities:
|
||||
* "Advertising Placement Services" LLC
|
||||
* "NEW SOLUTIONS VERTICALS" LLC
|
||||
* "Autoexpert" LLC
|
||||
* "Creditit" LLC
|
||||
* "Yandex.Taxi" LLC
|
||||
* "Yandex.Eda" LLC
|
||||
* "Yandex.Lavka" LLC
|
||||
* "Yandex.Telecom" LLC
|
||||
* "Yandex.Cloud" LLC
|
||||
* "Micromobility" LLC
|
||||
* "MM-Tech" LLC
|
||||
* "Carsharing" LLC
|
||||
* "Yandex.Drive" LLC
|
||||
* "EDADIL PROMO" LLC
|
||||
* "Kinopoisk" LLC
|
||||
* "Yandex.Music" LLC
|
||||
* "Refueling (Yandex.Zapravki)" LLC
|
||||
* "Yandex.Pay" LLC
|
||||
* "Financial and Payment Technologies" LLC
|
||||
* "Yandex.Delivery" LLC
|
||||
* "Delivery Club" LLC
|
||||
* "Yandex.Check" LLC
|
||||
* "SMB-Service" LLC
|
||||
* "ADV-TECH" LLC
|
||||
* "Yandex Fantech" LLC
|
||||
* "Yandex Smena" LLC
|
||||
* "Market.Operations" LLC
|
||||
* "Yandex.Market" LLC
|
||||
* "ID Tech" LLC
|
||||
* "Yandex.Crowd" LLC
|
||||
* "Yandex" LLC
|
||||
* "Rutube" LLC
|
||||
* "Kaspersky" LLC
|
||||
Or if you represent or are associated with any legal, organizational, or
|
||||
professional entity providing services to or on behalf of the aforementioned entities:
|
||||
You are expressly forbidden from accessing, using, modifying, distributing, or
|
||||
interacting with the Software and its source code in any form. You must immediately
|
||||
delete or destroy any physical or digital copies of the Software and/or
|
||||
its source code, including any derivative works, tools, or information obtained from the Software.
|
||||
|
||||
54
README.md
54
README.md
@@ -2,9 +2,10 @@
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://www.codefactor.io/repository/github/orange-cpp/omath)
|
||||

|
||||
[](https://repology.org/project/orange-math/versions)
|
||||
@@ -43,7 +44,7 @@ It provides the latest features, is highly customizable, has all for cheat devel
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## 🚀 Quick Example
|
||||
## Quick Example
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
@@ -68,20 +69,20 @@ if (auto screen = camera.world_to_screen(world_position)) {
|
||||
}
|
||||
```
|
||||
|
||||
**[➡️ See more examples and tutorials][TUTORIALS]**
|
||||
**[See more examples and tutorials][TUTORIALS]**
|
||||
|
||||
# ✨ Features
|
||||
- **🚀 Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
|
||||
- **🎯 Versatility**: Includes a wide array of mathematical functions and algorithms.
|
||||
- **✅ Ease of Use**: Simplified interface for convenient integration into various projects.
|
||||
- **🎮 Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
|
||||
- **📐 3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
|
||||
- **💥 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
|
||||
- **🔧 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**.
|
||||
- **🌍 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.
|
||||
# Features
|
||||
- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
|
||||
- **Versatility**: Includes a wide array of mathematical functions and algorithms.
|
||||
- **Ease of Use**: Simplified interface for convenient integration into various projects.
|
||||
- **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
|
||||
- **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
|
||||
- **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
|
||||
- **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**.
|
||||
- **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.
|
||||
<div align = center>
|
||||
|
||||
# Gallery
|
||||
@@ -106,28 +107,32 @@ if (auto screen = camera.world_to_screen(world_position)) {
|
||||
|
||||
![TF2 Preview]
|
||||
|
||||
<br>
|
||||
|
||||
![OpenGL Preview]
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
||||
## 📚 Documentation
|
||||
## Documentation
|
||||
|
||||
- **[Getting Started Guide](https://libomath.org/getting_started/)** - Installation and first steps
|
||||
- **[API Overview](https://libomath.org/api_overview/)** - Complete API reference
|
||||
- **[Tutorials](https://libomath.org/tutorials/)** - Step-by-step guides
|
||||
- **[FAQ](https://libomath.org/faq/)** - Common questions and answers
|
||||
- **[Troubleshooting](https://libomath.org/troubleshooting/)** - Solutions to common issues
|
||||
- **[Best Practices](https://libomath.org/best_practices/)** - Guidelines for effective usage
|
||||
- **[Getting Started Guide](http://libomath.org/getting_started/)** - Installation and first steps
|
||||
- **[API Overview](http://libomath.org/api_overview/)** - Complete API reference
|
||||
- **[Tutorials](http://libomath.org/tutorials/)** - Step-by-step guides
|
||||
- **[FAQ](http://libomath.org/faq/)** - Common questions and answers
|
||||
- **[Troubleshooting](http://libomath.org/troubleshooting/)** - Solutions to common issues
|
||||
- **[Best Practices](http://libomath.org/best_practices/)** - Guidelines for effective usage
|
||||
|
||||
## 🤝 Community & Support
|
||||
## Community & Support
|
||||
|
||||
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
|
||||
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
|
||||
- **Issues**: [Report bugs or request features](https://github.com/orange-cpp/omath/issues)
|
||||
- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines
|
||||
|
||||
# 💘 Acknowledgments
|
||||
# Acknowledgments
|
||||
- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors)
|
||||
|
||||
<!----------------------------------{ Images }--------------------------------->
|
||||
@@ -135,6 +140,7 @@ if (auto screen = camera.world_to_screen(world_position)) {
|
||||
[BO2 Preview]: docs/images/showcase/cod_bo2.png
|
||||
[CS2 Preview]: docs/images/showcase/cs2.jpeg
|
||||
[TF2 Preview]: docs/images/showcase/tf2.jpg
|
||||
[OpenGL Preview]: docs/images/showcase/opengl.png
|
||||
<!----------------------------------{ Buttons }--------------------------------->
|
||||
[QUICKSTART]: docs/getting_started.md
|
||||
[INSTALL]: INSTALL.md
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
project(omath_benchmark)
|
||||
|
||||
|
||||
file(GLOB_RECURSE OMATH_BENCHMARK_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
|
||||
add_executable(${PROJECT_NAME} ${OMATH_BENCHMARK_SOURCES})
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
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}"
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED ON)
|
||||
set_target_properties(
|
||||
${PROJECT_NAME}
|
||||
PROPERTIES 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}"
|
||||
CXX_STANDARD 23
|
||||
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)
|
||||
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)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
if(OMATH_ENABLE_VALGRIND)
|
||||
omath_setup_valgrind(${PROJECT_NAME})
|
||||
endif()
|
||||
|
||||
67
cmake/Coverage.cmake
Normal file
67
cmake/Coverage.cmake
Normal file
@@ -0,0 +1,67 @@
|
||||
# cmake/Coverage.cmake
|
||||
include_guard(GLOBAL)
|
||||
|
||||
function(omath_setup_coverage TARGET_NAME)
|
||||
if(ANDROID OR IOS OR EMSCRIPTEN)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC)
|
||||
target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping
|
||||
/Zi)
|
||||
target_link_options(
|
||||
${TARGET_NAME}
|
||||
PRIVATE
|
||||
-fprofile-instr-generate
|
||||
-fcoverage-mapping
|
||||
/DEBUG:FULL
|
||||
/INCREMENTAL:NO
|
||||
/PROFILE)
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
|
||||
target_compile_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")
|
||||
target_compile_options(${TARGET_NAME} PRIVATE --coverage -g -O0)
|
||||
target_link_options(${TARGET_NAME} PRIVATE --coverage)
|
||||
endif()
|
||||
|
||||
if(TARGET coverage)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC)
|
||||
message(STATUS "MSVC detected: Use VS Code Coverage from CI workflow")
|
||||
add_custom_target(
|
||||
coverage
|
||||
DEPENDS unit_tests
|
||||
COMMAND $<TARGET_FILE:unit_tests>
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Running tests for coverage (use VS Code Coverage from CI)")
|
||||
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
|
||||
add_custom_target(
|
||||
coverage
|
||||
DEPENDS unit_tests
|
||||
COMMAND bash "${CMAKE_SOURCE_DIR}/scripts/coverage-llvm.sh" "${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_BINARY_DIR}" "$<TARGET_FILE:unit_tests>" "${CMAKE_BINARY_DIR}/coverage"
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Running LLVM coverage")
|
||||
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
add_custom_target(
|
||||
coverage
|
||||
DEPENDS unit_tests
|
||||
COMMAND $<TARGET_FILE:unit_tests> || true
|
||||
COMMAND lcov --capture --directory "${CMAKE_BINARY_DIR}" --output-file
|
||||
"${CMAKE_BINARY_DIR}/coverage.info" --ignore-errors mismatch,gcov
|
||||
COMMAND
|
||||
lcov --remove "${CMAKE_BINARY_DIR}/coverage.info" "*/tests/*" "*/gtest/*"
|
||||
"*/googletest/*" "*/_deps/*" "/usr/*" --output-file
|
||||
"${CMAKE_BINARY_DIR}/coverage.info" --ignore-errors unused
|
||||
COMMAND genhtml "${CMAKE_BINARY_DIR}/coverage.info" --output-directory
|
||||
"${CMAKE_BINARY_DIR}/coverage"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
COMMENT "Running lcov/genhtml")
|
||||
endif()
|
||||
endfunction()
|
||||
41
cmake/Valgrind.cmake
Normal file
41
cmake/Valgrind.cmake
Normal file
@@ -0,0 +1,41 @@
|
||||
# cmake/Valgrind.cmake
|
||||
|
||||
if(DEFINED __OMATH_VALGRIND_INCLUDED)
|
||||
return()
|
||||
endif()
|
||||
set(__OMATH_VALGRIND_INCLUDED TRUE)
|
||||
|
||||
find_program(VALGRIND_EXECUTABLE valgrind)
|
||||
option(OMATH_ENABLE_VALGRIND "Enable Valgrind target for memory checking" ON)
|
||||
|
||||
if(OMATH_ENABLE_VALGRIND AND NOT TARGET valgrind_all)
|
||||
add_custom_target(valgrind_all)
|
||||
endif()
|
||||
|
||||
function(omath_setup_valgrind TARGET_NAME)
|
||||
if(NOT OMATH_ENABLE_VALGRIND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT VALGRIND_EXECUTABLE)
|
||||
message(WARNING "OMATH_ENABLE_VALGRIND is ON, but 'valgrind' executable was not found.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(VALGRIND_FLAGS --leak-check=full --show-leak-kinds=all --track-origins=yes
|
||||
--error-exitcode=99)
|
||||
|
||||
set(VALGRIND_TARGET "valgrind_${TARGET_NAME}")
|
||||
|
||||
if(NOT TARGET ${VALGRIND_TARGET})
|
||||
add_custom_target(
|
||||
${VALGRIND_TARGET}
|
||||
DEPENDS ${TARGET_NAME}
|
||||
COMMAND ${VALGRIND_EXECUTABLE} ${VALGRIND_FLAGS} $<TARGET_FILE:${TARGET_NAME}>
|
||||
WORKING_DIRECTORY $<TARGET_FILE_DIR:${TARGET_NAME}>
|
||||
COMMENT "Running Valgrind memory check on ${TARGET_NAME}..."
|
||||
USES_TERMINAL)
|
||||
|
||||
add_dependencies(valgrind_all ${VALGRIND_TARGET})
|
||||
endif()
|
||||
endfunction()
|
||||
465
docs/3d_primitives/mesh.md
Normal file
465
docs/3d_primitives/mesh.md
Normal file
@@ -0,0 +1,465 @@
|
||||
# `omath::primitives::Mesh` — 3D mesh with transformation support
|
||||
|
||||
> Header: `omath/3d_primitives/mesh.hpp`
|
||||
> Namespace: `omath::primitives`
|
||||
> Depends on: `omath::Vector3<T>`, `omath::Mat4X4`, `omath::Triangle<Vector3<T>>`
|
||||
> Purpose: represent and transform 3D meshes in different engine coordinate systems
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`Mesh` represents a 3D polygonal mesh with vertex data and transformation capabilities. It stores:
|
||||
* **Vertex buffer (VBO)** — array of 3D vertex positions
|
||||
* **Index buffer (VAO)** — array of triangular faces (indices into VBO)
|
||||
* **Transformation** — position, rotation, and scale with caching
|
||||
|
||||
The mesh supports transformation from local space to world space using engine-specific coordinate systems through the `MeshTrait` template parameter.
|
||||
|
||||
---
|
||||
|
||||
## Template Declaration
|
||||
|
||||
```cpp
|
||||
template<class Mat4X4, class RotationAngles, class MeshTypeTrait, class Type = float>
|
||||
class Mesh final;
|
||||
```
|
||||
|
||||
### Template Parameters
|
||||
|
||||
* `Mat4X4` — Matrix type for transformations (typically `omath::Mat4X4`)
|
||||
* `RotationAngles` — Rotation representation (e.g., `ViewAngles` with pitch/yaw/roll)
|
||||
* `MeshTypeTrait` — Engine-specific transformation trait (see [Engine Traits](#engine-traits))
|
||||
* `Type` — Scalar type for vertex coordinates (default `float`)
|
||||
|
||||
---
|
||||
|
||||
## Type Aliases
|
||||
|
||||
```cpp
|
||||
using NumericType = Type;
|
||||
```
|
||||
|
||||
Common engine-specific aliases:
|
||||
|
||||
```cpp
|
||||
// Source Engine
|
||||
using Mesh = omath::primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
|
||||
// Unity Engine
|
||||
using Mesh = omath::primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
|
||||
// Unreal Engine
|
||||
using Mesh = omath::primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
|
||||
// Frostbite, IW Engine, OpenGL similar...
|
||||
```
|
||||
|
||||
Use the pre-defined type aliases in engine namespaces:
|
||||
```cpp
|
||||
using namespace omath::source_engine;
|
||||
Mesh my_mesh = /* ... */; // Uses SourceEngine::Mesh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Members
|
||||
|
||||
### Vertex Data
|
||||
|
||||
```cpp
|
||||
std::vector<Vector3<NumericType>> m_vertex_buffer; // VBO: vertex positions
|
||||
std::vector<Vector3<std::size_t>> m_vertex_array_object; // VAO: face indices
|
||||
```
|
||||
|
||||
* `m_vertex_buffer` — array of vertex positions in **local space**
|
||||
* `m_vertex_array_object` — array of triangular faces, each containing 3 indices into `m_vertex_buffer`
|
||||
|
||||
**Public access**: These members are public for direct manipulation when needed.
|
||||
|
||||
---
|
||||
|
||||
## Constructor
|
||||
|
||||
```cpp
|
||||
Mesh(std::vector<Vector3<NumericType>> vbo,
|
||||
std::vector<Vector3<std::size_t>> vao,
|
||||
Vector3<NumericType> scale = {1, 1, 1});
|
||||
```
|
||||
|
||||
Creates a mesh from vertex and index data.
|
||||
|
||||
**Parameters**:
|
||||
* `vbo` — vertex buffer (moved into mesh)
|
||||
* `vao` — index buffer / vertex array object (moved into mesh)
|
||||
* `scale` — initial scale (default `{1, 1, 1}`)
|
||||
|
||||
**Example**:
|
||||
```cpp
|
||||
std::vector<Vector3<float>> vertices = {
|
||||
{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}
|
||||
};
|
||||
|
||||
std::vector<Vector3<std::size_t>> faces = {
|
||||
{0, 1, 2}, // Triangle 1
|
||||
{0, 1, 3}, // Triangle 2
|
||||
{0, 2, 3}, // Triangle 3
|
||||
{1, 2, 3} // Triangle 4
|
||||
};
|
||||
|
||||
using namespace omath::source_engine;
|
||||
Mesh tetrahedron(std::move(vertices), std::move(faces));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Transformation Methods
|
||||
|
||||
### Setting Transform Components
|
||||
|
||||
```cpp
|
||||
void set_origin(const Vector3<NumericType>& new_origin);
|
||||
void set_scale(const Vector3<NumericType>& new_scale);
|
||||
void set_rotation(const RotationAngles& new_rotation_angles);
|
||||
```
|
||||
|
||||
Update the mesh's transformation. **Side effect**: invalidates the cached transformation matrix, which will be recomputed on the next `get_to_world_matrix()` call.
|
||||
|
||||
**Example**:
|
||||
```cpp
|
||||
mesh.set_origin({10, 0, 5});
|
||||
mesh.set_scale({2, 2, 2});
|
||||
|
||||
ViewAngles angles;
|
||||
angles.pitch = PitchAngle::from_degrees(45.0f);
|
||||
angles.yaw = YawAngle::from_degrees(30.0f);
|
||||
mesh.set_rotation(angles);
|
||||
```
|
||||
|
||||
### Getting Transform Components
|
||||
|
||||
```cpp
|
||||
[[nodiscard]] const Vector3<NumericType>& get_origin() const;
|
||||
[[nodiscard]] const Vector3<NumericType>& get_scale() const;
|
||||
[[nodiscard]] const RotationAngles& get_rotation_angles() const;
|
||||
```
|
||||
|
||||
Retrieve current transformation components.
|
||||
|
||||
### Transformation Matrix
|
||||
|
||||
```cpp
|
||||
[[nodiscard]] const Mat4X4& get_to_world_matrix() const;
|
||||
```
|
||||
|
||||
Returns the cached local-to-world transformation matrix. The matrix is computed lazily on first access after any transformation change:
|
||||
|
||||
```
|
||||
M = Translation(origin) × Scale(scale) × Rotation(angles)
|
||||
```
|
||||
|
||||
The rotation matrix is computed using the engine-specific `MeshTrait::rotation_matrix()` method.
|
||||
|
||||
**Caching**: The matrix is stored in a `mutable std::optional` and recomputed only when invalidated by `set_*` methods.
|
||||
|
||||
---
|
||||
|
||||
## Vertex Transformation
|
||||
|
||||
### `vertex_to_world_space`
|
||||
|
||||
```cpp
|
||||
[[nodiscard]]
|
||||
Vector3<float> vertex_to_world_space(const Vector3<float>& vertex) const;
|
||||
```
|
||||
|
||||
Transforms a vertex from local space to world space by multiplying with the transformation matrix.
|
||||
|
||||
**Algorithm**:
|
||||
1. Convert vertex to column matrix: `[x, y, z, 1]ᵀ`
|
||||
2. Multiply by transformation matrix: `M × vertex`
|
||||
3. Extract the resulting 3D position
|
||||
|
||||
**Usage**:
|
||||
```cpp
|
||||
Vector3<float> local_vertex{1, 0, 0};
|
||||
Vector3<float> world_vertex = mesh.vertex_to_world_space(local_vertex);
|
||||
```
|
||||
|
||||
**Note**: This is used internally by `MeshCollider` to provide world-space support functions for GJK/EPA.
|
||||
|
||||
---
|
||||
|
||||
## Face Transformation
|
||||
|
||||
### `make_face_in_world_space`
|
||||
|
||||
```cpp
|
||||
[[nodiscard]]
|
||||
Triangle<Vector3<float>> make_face_in_world_space(
|
||||
const std::vector<Vector3<std::size_t>>::const_iterator vao_iterator
|
||||
) const;
|
||||
```
|
||||
|
||||
Creates a triangle in world space from a face index iterator.
|
||||
|
||||
**Parameters**:
|
||||
* `vao_iterator` — iterator to an element in `m_vertex_array_object`
|
||||
|
||||
**Returns**: `Triangle` with all three vertices transformed to world space.
|
||||
|
||||
**Example**:
|
||||
```cpp
|
||||
for (auto it = mesh.m_vertex_array_object.begin();
|
||||
it != mesh.m_vertex_array_object.end();
|
||||
++it) {
|
||||
Triangle<Vector3<float>> world_triangle = mesh.make_face_in_world_space(it);
|
||||
// Render or process the triangle
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Creating a Box Mesh
|
||||
|
||||
```cpp
|
||||
using namespace omath::source_engine;
|
||||
|
||||
std::vector<Vector3<float>> box_vbo = {
|
||||
// Bottom face
|
||||
{-0.5f, -0.5f, 0.0f}, { 0.5f, -0.5f, 0.0f},
|
||||
{ 0.5f, 0.5f, 0.0f}, {-0.5f, 0.5f, 0.0f},
|
||||
// Top face
|
||||
{-0.5f, -0.5f, 1.0f}, { 0.5f, -0.5f, 1.0f},
|
||||
{ 0.5f, 0.5f, 1.0f}, {-0.5f, 0.5f, 1.0f}
|
||||
};
|
||||
|
||||
std::vector<Vector3<std::size_t>> box_vao = {
|
||||
// Bottom
|
||||
{0, 1, 2}, {0, 2, 3},
|
||||
// Top
|
||||
{4, 6, 5}, {4, 7, 6},
|
||||
// Sides
|
||||
{0, 4, 5}, {0, 5, 1},
|
||||
{1, 5, 6}, {1, 6, 2},
|
||||
{2, 6, 7}, {2, 7, 3},
|
||||
{3, 7, 4}, {3, 4, 0}
|
||||
};
|
||||
|
||||
Mesh box(std::move(box_vbo), std::move(box_vao));
|
||||
box.set_origin({0, 0, 50});
|
||||
box.set_scale({10, 10, 10});
|
||||
```
|
||||
|
||||
### Transforming Mesh Over Time
|
||||
|
||||
```cpp
|
||||
void update_mesh(Mesh& mesh, float delta_time) {
|
||||
// Rotate mesh
|
||||
auto rotation = mesh.get_rotation_angles();
|
||||
rotation.yaw = YawAngle::from_degrees(
|
||||
rotation.yaw.as_degrees() + 45.0f * delta_time
|
||||
);
|
||||
mesh.set_rotation(rotation);
|
||||
|
||||
// Oscillate position
|
||||
auto origin = mesh.get_origin();
|
||||
origin.z = 50.0f + 10.0f * std::sin(current_time * 2.0f);
|
||||
mesh.set_origin(origin);
|
||||
}
|
||||
```
|
||||
|
||||
### Collision Detection
|
||||
|
||||
```cpp
|
||||
using namespace omath::collision;
|
||||
using namespace omath::source_engine;
|
||||
|
||||
Mesh mesh_a(vbo_a, vao_a);
|
||||
mesh_a.set_origin({0, 0, 0});
|
||||
|
||||
Mesh mesh_b(vbo_b, vao_b);
|
||||
mesh_b.set_origin({5, 0, 0});
|
||||
|
||||
MeshCollider collider_a(std::move(mesh_a));
|
||||
MeshCollider collider_b(std::move(mesh_b));
|
||||
|
||||
auto result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
|
||||
collider_a, collider_b
|
||||
);
|
||||
```
|
||||
|
||||
### Rendering Transformed Triangles
|
||||
|
||||
```cpp
|
||||
void render_mesh(const Mesh& mesh) {
|
||||
for (auto it = mesh.m_vertex_array_object.begin();
|
||||
it != mesh.m_vertex_array_object.end();
|
||||
++it) {
|
||||
|
||||
Triangle<Vector3<float>> tri = mesh.make_face_in_world_space(it);
|
||||
|
||||
// Draw triangle with your renderer
|
||||
draw_triangle(tri.m_vertex1, tri.m_vertex2, tri.m_vertex3);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Engine Traits
|
||||
|
||||
Each game engine has a corresponding `MeshTrait` that provides the `rotation_matrix` function:
|
||||
|
||||
```cpp
|
||||
class MeshTrait final {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
};
|
||||
```
|
||||
|
||||
### Available Engines
|
||||
|
||||
| Engine | Namespace | Header |
|
||||
|--------|-----------|--------|
|
||||
| Source Engine | `omath::source_engine` | `engines/source_engine/mesh.hpp` |
|
||||
| Unity | `omath::unity_engine` | `engines/unity_engine/mesh.hpp` |
|
||||
| Unreal | `omath::unreal_engine` | `engines/unreal_engine/mesh.hpp` |
|
||||
| Frostbite | `omath::frostbite_engine` | `engines/frostbite_engine/mesh.hpp` |
|
||||
| IW Engine | `omath::iw_engine` | `engines/iw_engine/mesh.hpp` |
|
||||
| OpenGL | `omath::opengl_engine` | `engines/opengl_engine/mesh.hpp` |
|
||||
|
||||
**Example** (Source Engine):
|
||||
```cpp
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// Uses source_engine::MeshTrait automatically
|
||||
Mesh my_mesh(vertices, indices);
|
||||
```
|
||||
|
||||
See [MeshTrait Documentation](#mesh-trait-documentation) for engine-specific details.
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Matrix Caching
|
||||
|
||||
The transformation matrix is computed lazily and cached:
|
||||
* **First access**: O(matrix multiply) ≈ 64 float operations
|
||||
* **Subsequent access**: O(1) — returns cached matrix
|
||||
* **Cache invalidation**: Any `set_*` call invalidates the cache
|
||||
|
||||
**Best practice**: Batch transformation updates before accessing the matrix:
|
||||
```cpp
|
||||
// Good: single matrix recomputation
|
||||
mesh.set_origin(new_origin);
|
||||
mesh.set_rotation(new_rotation);
|
||||
mesh.set_scale(new_scale);
|
||||
auto matrix = mesh.get_to_world_matrix(); // Computes once
|
||||
|
||||
// Bad: three matrix recomputations
|
||||
mesh.set_origin(new_origin);
|
||||
auto m1 = mesh.get_to_world_matrix(); // Compute
|
||||
mesh.set_rotation(new_rotation);
|
||||
auto m2 = mesh.get_to_world_matrix(); // Compute again
|
||||
mesh.set_scale(new_scale);
|
||||
auto m3 = mesh.get_to_world_matrix(); // Compute again
|
||||
```
|
||||
|
||||
### Memory Layout
|
||||
|
||||
* **VBO**: Contiguous `std::vector` for cache-friendly access
|
||||
* **VAO**: Contiguous indices for cache-friendly face iteration
|
||||
* **Matrix**: Cached in `std::optional` (no allocation)
|
||||
|
||||
### Transformation Cost
|
||||
|
||||
* `vertex_to_world_space`: ~15-20 FLOPs per vertex (4×4 matrix multiply)
|
||||
* `make_face_in_world_space`: ~60 FLOPs (3 vertices)
|
||||
|
||||
For high-frequency transformations, consider:
|
||||
* Caching transformed vertices if the mesh doesn't change
|
||||
* Using simpler proxy geometry for collision
|
||||
* Batching transformations
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System Details
|
||||
|
||||
Different engines use different coordinate systems:
|
||||
|
||||
| Engine | Up Axis | Forward Axis | Handedness |
|
||||
|--------|---------|--------------|------------|
|
||||
| Source | +Z | +Y | Right |
|
||||
| Unity | +Y | +Z | Left |
|
||||
| Unreal | +Z | +X | Left |
|
||||
| Frostbite | +Y | +Z | Right |
|
||||
| IW Engine | +Z | +Y | Right |
|
||||
| OpenGL | +Y | +Z | Right |
|
||||
|
||||
The `MeshTrait::rotation_matrix` function accounts for these differences, ensuring correct transformations in each engine's space.
|
||||
|
||||
---
|
||||
|
||||
## Limitations & Edge Cases
|
||||
|
||||
### Empty Mesh
|
||||
|
||||
A mesh with no vertices or faces is valid but not useful:
|
||||
```cpp
|
||||
Mesh empty_mesh({}, {}); // Valid but meaningless
|
||||
```
|
||||
|
||||
For collision detection, ensure `m_vertex_buffer` is non-empty.
|
||||
|
||||
### Index Validity
|
||||
|
||||
No bounds checking is performed on indices in `m_vertex_array_object`. Ensure all indices are valid:
|
||||
```cpp
|
||||
assert(face.x < mesh.m_vertex_buffer.size());
|
||||
assert(face.y < mesh.m_vertex_buffer.size());
|
||||
assert(face.z < mesh.m_vertex_buffer.size());
|
||||
```
|
||||
|
||||
### Degenerate Triangles
|
||||
|
||||
Faces with duplicate indices or collinear vertices will produce degenerate triangles. The mesh doesn't validate this; users must ensure clean geometry.
|
||||
|
||||
### Thread Safety
|
||||
|
||||
* **Read-only**: Safe to read from multiple threads (including const methods)
|
||||
* **Modification**: Not thread-safe; synchronize `set_*` calls externally
|
||||
* **Matrix cache**: Uses `mutable` member; not thread-safe even for const methods
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [MeshCollider Documentation](../collision/mesh_collider.md) - Collision wrapper for meshes
|
||||
- [GJK Algorithm Documentation](../collision/gjk_algorithm.md) - Uses mesh for collision detection
|
||||
- [EPA Algorithm Documentation](../collision/epa_algorithm.md) - Penetration depth with meshes
|
||||
- [Triangle Documentation](../linear_algebra/triangle.md) - Triangle primitive
|
||||
- [Mat4X4 Documentation](../linear_algebra/mat.md) - Transformation matrices
|
||||
- [Box Documentation](box.md) - Box primitive
|
||||
- [Plane Documentation](plane.md) - Plane primitive
|
||||
|
||||
---
|
||||
|
||||
## Mesh Trait Documentation
|
||||
|
||||
For engine-specific `MeshTrait` details, see:
|
||||
|
||||
- [Source Engine MeshTrait](../engines/source_engine/mesh_trait.md)
|
||||
- [Unity Engine MeshTrait](../engines/unity_engine/mesh_trait.md)
|
||||
- [Unreal Engine MeshTrait](../engines/unreal_engine/mesh_trait.md)
|
||||
- [Frostbite Engine MeshTrait](../engines/frostbite/mesh_trait.md)
|
||||
- [IW Engine MeshTrait](../engines/iw_engine/mesh_trait.md)
|
||||
- [OpenGL Engine MeshTrait](../engines/opengl_engine/mesh_trait.md)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
@@ -11,10 +11,10 @@ OMath is organized into several logical modules:
|
||||
### Core Mathematics
|
||||
- **Linear Algebra** - Vectors, matrices, triangles
|
||||
- **Trigonometry** - Angles, view angles, trigonometric functions
|
||||
- **3D Primitives** - Boxes, planes, geometric shapes
|
||||
- **3D Primitives** - Boxes, planes, meshes, geometric shapes
|
||||
|
||||
### Game Development
|
||||
- **Collision Detection** - Ray tracing, intersection tests
|
||||
- **Collision Detection** - Ray tracing, GJK/EPA algorithms, mesh collision, intersection tests
|
||||
- **Projectile Prediction** - Ballistics and aim-assist calculations
|
||||
- **Projection** - Camera systems and world-to-screen transformations
|
||||
- **Pathfinding** - A* algorithm, navigation meshes
|
||||
@@ -131,6 +131,41 @@ omath::opengl_engine::Camera // OpenGL
|
||||
|
||||
## Collision Detection
|
||||
|
||||
### GJK/EPA Algorithms
|
||||
|
||||
Advanced convex shape collision detection using the Gilbert-Johnson-Keerthi and Expanding Polytope algorithms:
|
||||
|
||||
```cpp
|
||||
namespace omath::collision {
|
||||
template<class ColliderType>
|
||||
class GjkAlgorithm;
|
||||
|
||||
template<class ColliderType>
|
||||
class Epa;
|
||||
}
|
||||
```
|
||||
|
||||
**GJK (Gilbert-Johnson-Keerthi):**
|
||||
* Detects collision between two convex shapes
|
||||
* Returns a 4-point simplex when collision is detected
|
||||
* O(k) complexity where k is typically < 20 iterations
|
||||
* Works with any collider implementing `find_abs_furthest_vertex()`
|
||||
|
||||
**EPA (Expanding Polytope Algorithm):**
|
||||
* Computes penetration depth and separation normal
|
||||
* Takes GJK's output simplex as input
|
||||
* Provides contact information for physics simulation
|
||||
* Configurable iteration limit and convergence tolerance
|
||||
|
||||
**Supporting Types:**
|
||||
|
||||
| Type | Description | Key Features |
|
||||
|------|-------------|--------------|
|
||||
| `Simplex<VectorType>` | 1-4 point geometric simplex | Fixed capacity, GJK iteration support |
|
||||
| `MeshCollider<MeshType>` | Convex mesh collider | Support function for GJK/EPA |
|
||||
| `GjkHitInfo<VertexType>` | Collision result | Hit flag and simplex |
|
||||
| `Epa::Result` | Penetration info | Depth, normal, iteration count |
|
||||
|
||||
### LineTracer
|
||||
|
||||
Ray-casting and line tracing utilities:
|
||||
@@ -142,7 +177,7 @@ namespace omath::collision {
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Ray-triangle intersection
|
||||
- Ray-triangle intersection (Möller-Trumbore algorithm)
|
||||
- Ray-plane intersection
|
||||
- Ray-box intersection
|
||||
- Distance calculations
|
||||
@@ -154,6 +189,14 @@ namespace omath::collision {
|
||||
|------|-------------|-------------|
|
||||
| `Plane` | Infinite plane | `intersects_ray()`, `distance_to_point()` |
|
||||
| `Box` | Axis-aligned bounding box | `contains()`, `intersects()` |
|
||||
| `Mesh` | Polygonal mesh with transforms | `vertex_to_world_space()`, `make_face_in_world_space()` |
|
||||
|
||||
**Mesh Features:**
|
||||
* Vertex buffer (VBO) and index buffer (VAO/EBO) storage
|
||||
* Position, rotation, and scale transformations
|
||||
* Cached transformation matrix
|
||||
* Engine-specific coordinate system support
|
||||
* Compatible with `MeshCollider` for collision detection
|
||||
|
||||
---
|
||||
|
||||
@@ -241,6 +284,13 @@ Implements camera math for an engine:
|
||||
- `calc_view_matrix()` - Build view matrix from angles and position
|
||||
- `calc_projection_matrix()` - Build projection matrix from FOV and viewport
|
||||
|
||||
### MeshTrait
|
||||
|
||||
Provides mesh transformation for an engine:
|
||||
- `rotation_matrix()` - Build rotation matrix from engine-specific angles
|
||||
- Handles coordinate system differences (Y-up vs Z-up, left/right-handed)
|
||||
- Used by `Mesh` class for local-to-world transformations
|
||||
|
||||
### PredEngineTrait
|
||||
|
||||
Provides physics/ballistics specific to an engine:
|
||||
@@ -251,18 +301,18 @@ Provides physics/ballistics specific to an engine:
|
||||
|
||||
### Available Traits
|
||||
|
||||
| Engine | Camera Trait | Pred Engine Trait | Constants | Formulas |
|
||||
|--------|--------------|-------------------|-----------|----------|
|
||||
| Source Engine | ✓ | ✓ | ✓ | ✓ |
|
||||
| Unity Engine | ✓ | ✓ | ✓ | ✓ |
|
||||
| Unreal Engine | ✓ | ✓ | ✓ | ✓ |
|
||||
| Frostbite | ✓ | ✓ | ✓ | ✓ |
|
||||
| IW Engine | ✓ | ✓ | ✓ | ✓ |
|
||||
| OpenGL | ✓ | ✓ | ✓ | ✓ |
|
||||
| Engine | Camera Trait | Mesh Trait | Pred Engine Trait | Constants | Formulas |
|
||||
|--------|--------------|------------|-------------------|-----------|----------|
|
||||
| Source Engine | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Unity Engine | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Unreal Engine | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Frostbite | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| IW Engine | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| OpenGL | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
**Documentation:**
|
||||
- See `docs/engines/<engine_name>/` for detailed per-engine docs
|
||||
- Each engine has separate docs for camera_trait, pred_engine_trait, constants, and formulas
|
||||
- Each engine has separate docs for camera_trait, mesh_trait, pred_engine_trait, constants, and formulas
|
||||
|
||||
---
|
||||
|
||||
@@ -524,4 +574,4 @@ UnityCamera camera{pos, SourceAngles{}}; // Wrong!
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
*Last updated: 13 Nov 2025*
|
||||
|
||||
322
docs/collision/epa_algorithm.md
Normal file
322
docs/collision/epa_algorithm.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# `omath::collision::Epa` — Expanding Polytope Algorithm for penetration depth
|
||||
|
||||
> Header: `omath/collision/epa_algorithm.hpp`
|
||||
> Namespace: `omath::collision`
|
||||
> Depends on: `Simplex<VertexType>`, collider types with `find_abs_furthest_vertex` method
|
||||
> Algorithm: **EPA** (Expanding Polytope Algorithm) for penetration depth and contact normal
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The **EPA (Expanding Polytope Algorithm)** calculates the **penetration depth** and **separation normal** between two intersecting convex shapes. It is typically used as a follow-up to the GJK algorithm after a collision has been detected.
|
||||
|
||||
EPA takes a 4-point simplex containing the origin (from GJK) and iteratively expands it to find the point on the Minkowski difference closest to the origin. This point gives both:
|
||||
* **Depth**: minimum translation distance to separate the shapes
|
||||
* **Normal**: direction of separation (pointing from shape B to shape A)
|
||||
|
||||
`Epa` is a template class working with any collider type that implements the support function interface.
|
||||
|
||||
---
|
||||
|
||||
## `Epa::Result`
|
||||
|
||||
```cpp
|
||||
struct Result final {
|
||||
bool success{false}; // true if EPA converged
|
||||
Vertex normal{}; // outward normal (from B to A)
|
||||
float depth{0.0f}; // penetration depth
|
||||
int iterations{0}; // number of iterations performed
|
||||
int num_vertices{0}; // final polytope vertex count
|
||||
int num_faces{0}; // final polytope face count
|
||||
};
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
* `success` — `true` if EPA successfully computed depth and normal; `false` if it failed to converge
|
||||
* `normal` — unit vector pointing from shape B toward shape A (separation direction)
|
||||
* `depth` — minimum distance to move shape A along `normal` to separate the shapes
|
||||
* `iterations` — actual iteration count (useful for performance tuning)
|
||||
* `num_vertices`, `num_faces` — final polytope size (for diagnostics)
|
||||
|
||||
---
|
||||
|
||||
## `Epa::Params`
|
||||
|
||||
```cpp
|
||||
struct Params final {
|
||||
int max_iterations{64}; // maximum iterations before giving up
|
||||
float tolerance{1e-4f}; // absolute tolerance on distance growth
|
||||
};
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
* `max_iterations` — safety limit to prevent infinite loops (default 64)
|
||||
* `tolerance` — convergence threshold: stop when distance grows less than this (default 1e-4)
|
||||
|
||||
---
|
||||
|
||||
## `Epa` Template Class
|
||||
|
||||
```cpp
|
||||
template<class ColliderType>
|
||||
class Epa final {
|
||||
public:
|
||||
using Vertex = typename ColliderType::VertexType;
|
||||
static_assert(EpaVector<Vertex>, "VertexType must satisfy EpaVector concept");
|
||||
|
||||
// Solve for penetration depth and normal
|
||||
[[nodiscard]]
|
||||
static Result solve(
|
||||
const ColliderType& a,
|
||||
const ColliderType& b,
|
||||
const Simplex<Vertex>& simplex,
|
||||
const Params params = {}
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Precondition
|
||||
|
||||
The `simplex` parameter must:
|
||||
* Have exactly 4 points (`simplex.size() == 4`)
|
||||
* Contain the origin (i.e., be a valid GJK result with `hit == true`)
|
||||
|
||||
Violating this precondition leads to undefined behavior.
|
||||
|
||||
---
|
||||
|
||||
## Collider Requirements
|
||||
|
||||
Any type used as `ColliderType` must provide:
|
||||
|
||||
```cpp
|
||||
// Type alias for vertex type (typically Vector3<float>)
|
||||
using VertexType = /* ... */;
|
||||
|
||||
// Find the farthest point in world space along the given direction
|
||||
[[nodiscard]]
|
||||
VertexType find_abs_furthest_vertex(const VertexType& direction) const;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Algorithm Details
|
||||
|
||||
### Expanding Polytope
|
||||
|
||||
EPA maintains a convex polytope (polyhedron) in Minkowski difference space `A - B`. Starting from the 4-point tetrahedron (simplex from GJK), it repeatedly:
|
||||
|
||||
1. **Find closest face** to the origin
|
||||
2. **Support query** in the direction of the face normal
|
||||
3. **Expand polytope** by adding the new support point
|
||||
4. **Update faces** to maintain convexity
|
||||
|
||||
The algorithm terminates when:
|
||||
* **Convergence**: the distance from origin to polytope stops growing (within tolerance)
|
||||
* **Max iterations**: safety limit reached
|
||||
* **Failure cases**: degenerate polytope or numerical issues
|
||||
|
||||
### Minkowski Difference
|
||||
|
||||
Like GJK, EPA operates in Minkowski difference space where `point = a - b` for points in shapes A and B. The closest point on this polytope to the origin gives the minimum separation.
|
||||
|
||||
### Face Winding
|
||||
|
||||
Faces are stored with outward-pointing normals. The algorithm uses a priority queue to efficiently find the face closest to the origin.
|
||||
|
||||
---
|
||||
|
||||
## Vertex Type Requirements
|
||||
|
||||
The `VertexType` must satisfy the `EpaVector` concept:
|
||||
|
||||
```cpp
|
||||
template<class V>
|
||||
concept EpaVector = requires(const V& a, const V& b, float s) {
|
||||
{ a - b } -> std::same_as<V>;
|
||||
{ a.cross(b) } -> std::same_as<V>;
|
||||
{ a.dot(b) } -> std::same_as<float>;
|
||||
{ -a } -> std::same_as<V>;
|
||||
{ a * s } -> std::same_as<V>;
|
||||
{ a / s } -> std::same_as<V>;
|
||||
};
|
||||
```
|
||||
|
||||
`omath::Vector3<float>` satisfies this concept.
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic EPA Usage
|
||||
|
||||
```cpp
|
||||
using namespace omath::collision;
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// First, run GJK to detect collision
|
||||
MeshCollider<Mesh> collider_a(mesh_a);
|
||||
MeshCollider<Mesh> collider_b(mesh_b);
|
||||
|
||||
auto gjk_result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
|
||||
collider_a,
|
||||
collider_b
|
||||
);
|
||||
|
||||
if (gjk_result.hit) {
|
||||
// Collision detected, use EPA to get penetration info
|
||||
auto epa_result = Epa<MeshCollider<Mesh>>::solve(
|
||||
collider_a,
|
||||
collider_b,
|
||||
gjk_result.simplex
|
||||
);
|
||||
|
||||
if (epa_result.success) {
|
||||
std::cout << "Penetration depth: " << epa_result.depth << "\n";
|
||||
std::cout << "Separation normal: "
|
||||
<< "(" << epa_result.normal.x << ", "
|
||||
<< epa_result.normal.y << ", "
|
||||
<< epa_result.normal.z << ")\n";
|
||||
|
||||
// Apply separation: move A away from B
|
||||
Vector3<float> correction = epa_result.normal * epa_result.depth;
|
||||
mesh_a.set_origin(mesh_a.get_origin() + correction);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Parameters
|
||||
|
||||
```cpp
|
||||
// Use custom convergence settings
|
||||
Epa<Collider>::Params params;
|
||||
params.max_iterations = 128; // Allow more iterations for complex shapes
|
||||
params.tolerance = 1e-5f; // Tighter tolerance for more accuracy
|
||||
|
||||
auto result = Epa<Collider>::solve(a, b, simplex, params);
|
||||
```
|
||||
|
||||
### Physics Integration
|
||||
|
||||
```cpp
|
||||
void resolve_collision(PhysicsBody& body_a, PhysicsBody& body_b) {
|
||||
auto gjk_result = GjkAlgorithm<Collider>::check_collision(
|
||||
body_a.collider, body_b.collider
|
||||
);
|
||||
|
||||
if (!gjk_result.hit)
|
||||
return; // No collision
|
||||
|
||||
auto epa_result = Epa<Collider>::solve(
|
||||
body_a.collider,
|
||||
body_b.collider,
|
||||
gjk_result.simplex
|
||||
);
|
||||
|
||||
if (epa_result.success) {
|
||||
// Separate bodies
|
||||
float mass_sum = body_a.mass + body_b.mass;
|
||||
float ratio_a = body_b.mass / mass_sum;
|
||||
float ratio_b = body_a.mass / mass_sum;
|
||||
|
||||
body_a.position += epa_result.normal * (epa_result.depth * ratio_a);
|
||||
body_b.position -= epa_result.normal * (epa_result.depth * ratio_b);
|
||||
|
||||
// Apply collision response
|
||||
apply_impulse(body_a, body_b, epa_result.normal);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
* **Time complexity**: O(k × f) where k is iterations and f is faces per iteration (typically f grows slowly)
|
||||
* **Space complexity**: O(n) where n is the number of polytope vertices (typically < 100)
|
||||
* **Typical iterations**: 4-20 for most collisions
|
||||
* **Worst case**: 64 iterations (configurable limit)
|
||||
|
||||
### Performance Tips
|
||||
|
||||
1. **Adjust max_iterations**: Balance accuracy vs. performance for your use case
|
||||
2. **Tolerance tuning**: Larger tolerance = faster convergence but less accurate
|
||||
3. **Shape complexity**: Simpler shapes (fewer faces) converge faster
|
||||
4. **Deep penetrations**: Require more iterations; consider broad-phase separation
|
||||
|
||||
---
|
||||
|
||||
## Limitations & Edge Cases
|
||||
|
||||
* **Requires valid simplex**: Must be called with a 4-point simplex containing the origin (from successful GJK)
|
||||
* **Convex shapes only**: Like GJK, EPA only works with convex colliders
|
||||
* **Convergence failure**: Can fail to converge for degenerate or very thin shapes (check `result.success`)
|
||||
* **Numerical precision**: Extreme scale differences or very small shapes may cause issues
|
||||
* **Deep penetration**: Very deep intersections may require many iterations or fail to converge
|
||||
|
||||
### Error Handling
|
||||
|
||||
```cpp
|
||||
auto result = Epa<Collider>::solve(a, b, simplex);
|
||||
|
||||
if (!result.success) {
|
||||
// EPA failed to converge
|
||||
// Fallback options:
|
||||
// 1. Use a default separation (e.g., axis between centers)
|
||||
// 2. Increase max_iterations and retry
|
||||
// 3. Log a warning and skip this collision
|
||||
std::cerr << "EPA failed after " << result.iterations << " iterations\n";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Theory & Background
|
||||
|
||||
### Why EPA after GJK?
|
||||
|
||||
GJK determines **if** shapes intersect but doesn't compute penetration depth. EPA extends GJK's final simplex to find the exact depth and normal needed for:
|
||||
* **Collision response** — separating objects realistically
|
||||
* **Contact manifolds** — generating contact points for physics
|
||||
* **Constraint solving** — iterative physics solvers
|
||||
|
||||
### Comparison with SAT
|
||||
|
||||
| Feature | EPA | SAT (Separating Axis Theorem) |
|
||||
|---------|-----|-------------------------------|
|
||||
| Works with | Any convex shape | Polytopes (faces/edges) |
|
||||
| Penetration depth | Yes | Yes |
|
||||
| Complexity | Iterative | Per-axis projection |
|
||||
| Best for | General convex | Boxes, prisms |
|
||||
| Typical speed | Moderate | Fast (few axes) |
|
||||
|
||||
EPA is more general; SAT is faster for axis-aligned shapes.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The EPA implementation in OMath:
|
||||
* Uses a **priority queue** to efficiently find the closest face
|
||||
* Maintains face winding for consistent normals
|
||||
* Handles **edge cases**: degenerate faces, numerical instability
|
||||
* Prevents infinite loops with iteration limits
|
||||
* Returns detailed diagnostics (iteration count, polytope size)
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [GJK Algorithm Documentation](gjk_algorithm.md) - Collision detection (required before EPA)
|
||||
- [Simplex Documentation](simplex.md) - Input simplex structure
|
||||
- [MeshCollider Documentation](mesh_collider.md) - Mesh-based collider
|
||||
- [Mesh Documentation](../3d_primitives/mesh.md) - Mesh primitive
|
||||
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
|
||||
- [API Overview](../api_overview.md) - High-level API reference
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
216
docs/collision/gjk_algorithm.md
Normal file
216
docs/collision/gjk_algorithm.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# `omath::collision::GjkAlgorithm` — Gilbert-Johnson-Keerthi collision detection
|
||||
|
||||
> Header: `omath/collision/gjk_algorithm.hpp`
|
||||
> Namespace: `omath::collision`
|
||||
> Depends on: `Simplex<VertexType>`, collider types with `find_abs_furthest_vertex` method
|
||||
> Algorithm: **GJK** (Gilbert-Johnson-Keerthi) for convex shape collision detection
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The **GJK algorithm** determines whether two convex shapes intersect by iteratively constructing a simplex in Minkowski difference space. The algorithm is widely used in physics engines and collision detection systems due to its efficiency and robustness.
|
||||
|
||||
`GjkAlgorithm` is a template class that works with any collider type implementing the required support function interface:
|
||||
|
||||
* `find_abs_furthest_vertex(direction)` — returns the farthest point in the collider along the given direction.
|
||||
|
||||
The algorithm returns a `GjkHitInfo` containing:
|
||||
* `hit` — boolean indicating whether the shapes intersect
|
||||
* `simplex` — a 4-point simplex containing the origin (valid only when `hit == true`)
|
||||
|
||||
---
|
||||
|
||||
## `GjkHitInfo`
|
||||
|
||||
```cpp
|
||||
template<class VertexType>
|
||||
struct GjkHitInfo final {
|
||||
bool hit{false}; // true if collision detected
|
||||
Simplex<VertexType> simplex; // 4-point simplex (valid only if hit == true)
|
||||
};
|
||||
```
|
||||
|
||||
The `simplex` field is only meaningful when `hit == true` and contains 4 points. This simplex can be passed to the EPA algorithm for penetration depth calculation.
|
||||
|
||||
---
|
||||
|
||||
## `GjkAlgorithm`
|
||||
|
||||
```cpp
|
||||
template<class ColliderType>
|
||||
class GjkAlgorithm final {
|
||||
using VertexType = typename ColliderType::VertexType;
|
||||
|
||||
public:
|
||||
// Find support vertex in Minkowski difference
|
||||
[[nodiscard]]
|
||||
static VertexType find_support_vertex(
|
||||
const ColliderType& collider_a,
|
||||
const ColliderType& collider_b,
|
||||
const VertexType& direction
|
||||
);
|
||||
|
||||
// Check if two convex shapes intersect
|
||||
[[nodiscard]]
|
||||
static GjkHitInfo<VertexType> check_collision(
|
||||
const ColliderType& collider_a,
|
||||
const ColliderType& collider_b
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Collider Requirements
|
||||
|
||||
Any type used as `ColliderType` must provide:
|
||||
|
||||
```cpp
|
||||
// Type alias for vertex type (typically Vector3<float>)
|
||||
using VertexType = /* ... */;
|
||||
|
||||
// Find the farthest point in world space along the given direction
|
||||
[[nodiscard]]
|
||||
VertexType find_abs_furthest_vertex(const VertexType& direction) const;
|
||||
```
|
||||
|
||||
Common collider types:
|
||||
* `MeshCollider<MeshType>` — for arbitrary triangle meshes
|
||||
* Custom colliders for spheres, boxes, capsules, etc.
|
||||
|
||||
---
|
||||
|
||||
## Algorithm Details
|
||||
|
||||
### Minkowski Difference
|
||||
|
||||
GJK operates in the **Minkowski difference** space `A - B`, where a point in this space represents the difference between points in shapes A and B. The shapes intersect if and only if the origin lies within this Minkowski difference.
|
||||
|
||||
### Support Function
|
||||
|
||||
The support function finds the point in the Minkowski difference farthest along a given direction:
|
||||
|
||||
```cpp
|
||||
support(A, B, dir) = A.furthest(dir) - B.furthest(-dir)
|
||||
```
|
||||
|
||||
This is computed by `find_support_vertex`.
|
||||
|
||||
### Simplex Iteration
|
||||
|
||||
The algorithm builds a simplex incrementally:
|
||||
1. Start with an initial direction (typically vector between shape centers)
|
||||
2. Add support vertices in directions that move the simplex toward the origin
|
||||
3. Simplify the simplex to keep only points closest to the origin
|
||||
4. Repeat until either:
|
||||
* Origin is contained (collision detected, returns 4-point simplex)
|
||||
* No progress can be made (no collision)
|
||||
|
||||
Maximum 64 iterations are performed to prevent infinite loops in edge cases.
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Collision Check
|
||||
|
||||
```cpp
|
||||
using namespace omath::collision;
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// Create mesh colliders
|
||||
Mesh mesh_a = /* ... */;
|
||||
Mesh mesh_b = /* ... */;
|
||||
|
||||
MeshCollider collider_a(mesh_a);
|
||||
MeshCollider collider_b(mesh_b);
|
||||
|
||||
// Check for collision
|
||||
auto result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
|
||||
collider_a,
|
||||
collider_b
|
||||
);
|
||||
|
||||
if (result.hit) {
|
||||
std::cout << "Collision detected!\n";
|
||||
// Can pass result.simplex to EPA for penetration depth
|
||||
}
|
||||
```
|
||||
|
||||
### Combined with EPA
|
||||
|
||||
```cpp
|
||||
auto gjk_result = GjkAlgorithm<Collider>::check_collision(a, b);
|
||||
|
||||
if (gjk_result.hit) {
|
||||
// Get penetration depth and normal using EPA
|
||||
auto epa_result = Epa<Collider>::solve(
|
||||
a, b, gjk_result.simplex
|
||||
);
|
||||
|
||||
if (epa_result.success) {
|
||||
std::cout << "Penetration depth: " << epa_result.depth << "\n";
|
||||
std::cout << "Separation normal: " << epa_result.normal << "\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
* **Time complexity**: O(k) where k is the number of iterations (typically < 20 for most cases)
|
||||
* **Space complexity**: O(1) — only stores a 4-point simplex
|
||||
* **Best case**: 4-8 iterations for well-separated objects
|
||||
* **Worst case**: 64 iterations (hard limit)
|
||||
* **Cache efficient**: operates on small fixed-size data structures
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
1. **Initial direction**: Use vector between shape centers for faster convergence
|
||||
2. **Early exit**: GJK quickly rejects non-intersecting shapes
|
||||
3. **Warm starting**: Reuse previous simplex for continuous collision detection
|
||||
4. **Broad phase**: Use spatial partitioning before GJK (AABB trees, grids)
|
||||
|
||||
---
|
||||
|
||||
## Limitations & Edge Cases
|
||||
|
||||
* **Convex shapes only**: GJK only works with convex colliders. For concave shapes, decompose into convex parts or use a mesh collider wrapper.
|
||||
* **Degenerate simplices**: The algorithm handles degenerate cases, but numerical precision can cause issues with very thin or flat shapes.
|
||||
* **Iteration limit**: Hard limit of 64 iterations prevents infinite loops but may miss collisions in extreme cases.
|
||||
* **Zero-length directions**: The simplex update logic guards against zero-length vectors, returning safe fallbacks.
|
||||
|
||||
---
|
||||
|
||||
## Vertex Type Requirements
|
||||
|
||||
The `VertexType` must satisfy the `GjkVector` concept (defined in `simplex.hpp`):
|
||||
|
||||
```cpp
|
||||
template<class V>
|
||||
concept GjkVector = requires(const V& a, const V& b) {
|
||||
{ -a } -> std::same_as<V>;
|
||||
{ a - b } -> std::same_as<V>;
|
||||
{ a.cross(b) } -> std::same_as<V>;
|
||||
{ a.point_to_same_direction(b) } -> std::same_as<bool>;
|
||||
};
|
||||
```
|
||||
|
||||
`omath::Vector3<float>` satisfies this concept.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [EPA Algorithm Documentation](epa_algorithm.md) - Penetration depth calculation
|
||||
- [Simplex Documentation](simplex.md) - Simplex data structure
|
||||
- [MeshCollider Documentation](mesh_collider.md) - Mesh-based collider
|
||||
- [Mesh Documentation](../3d_primitives/mesh.md) - Mesh primitive
|
||||
- [LineTracer Documentation](line_tracer.md) - Ray-triangle intersection
|
||||
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
371
docs/collision/mesh_collider.md
Normal file
371
docs/collision/mesh_collider.md
Normal file
@@ -0,0 +1,371 @@
|
||||
# `omath::collision::MeshCollider` — Convex hull collider for meshes
|
||||
|
||||
> Header: `omath/collision/mesh_collider.hpp`
|
||||
> Namespace: `omath::collision`
|
||||
> Depends on: `omath::primitives::Mesh`, `omath::Vector3<T>`
|
||||
> Purpose: wrap a mesh to provide collision detection support for GJK/EPA
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`MeshCollider` wraps a `Mesh` object to provide the **support function** interface required by the GJK and EPA collision detection algorithms. The support function finds the vertex of the mesh farthest along a given direction, which is essential for constructing Minkowski difference simplices.
|
||||
|
||||
**Important**: `MeshCollider` assumes the mesh represents a **convex hull**. For non-convex shapes, you must either:
|
||||
* Decompose into convex parts
|
||||
* Use the convex hull of the mesh
|
||||
* Use a different collision detection algorithm
|
||||
|
||||
---
|
||||
|
||||
## Template Declaration
|
||||
|
||||
```cpp
|
||||
template<class MeshType>
|
||||
class MeshCollider;
|
||||
```
|
||||
|
||||
### MeshType Requirements
|
||||
|
||||
The `MeshType` must be an instantiation of `omath::primitives::Mesh` or provide:
|
||||
|
||||
```cpp
|
||||
struct MeshType {
|
||||
using NumericType = /* float, double, etc. */;
|
||||
|
||||
std::vector<Vector3<NumericType>> m_vertex_buffer;
|
||||
|
||||
// Transform vertex from local to world space
|
||||
Vector3<NumericType> vertex_to_world_space(const Vector3<NumericType>&) const;
|
||||
};
|
||||
```
|
||||
|
||||
Common types:
|
||||
* `omath::source_engine::Mesh`
|
||||
* `omath::unity_engine::Mesh`
|
||||
* `omath::unreal_engine::Mesh`
|
||||
* `omath::frostbite_engine::Mesh`
|
||||
* `omath::iw_engine::Mesh`
|
||||
* `omath::opengl_engine::Mesh`
|
||||
|
||||
---
|
||||
|
||||
## Type Aliases
|
||||
|
||||
```cpp
|
||||
using NumericType = typename MeshType::NumericType;
|
||||
using VertexType = Vector3<NumericType>;
|
||||
```
|
||||
|
||||
* `NumericType` — scalar type (typically `float`)
|
||||
* `VertexType` — 3D vector type for vertices
|
||||
|
||||
---
|
||||
|
||||
## Constructor
|
||||
|
||||
```cpp
|
||||
explicit MeshCollider(MeshType mesh);
|
||||
```
|
||||
|
||||
Creates a collider from a mesh. The mesh is **moved** into the collider, so pass by value:
|
||||
|
||||
```cpp
|
||||
omath::source_engine::Mesh my_mesh = /* ... */;
|
||||
MeshCollider collider(std::move(my_mesh));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Methods
|
||||
|
||||
### `find_furthest_vertex`
|
||||
|
||||
```cpp
|
||||
[[nodiscard]]
|
||||
const VertexType& find_furthest_vertex(const VertexType& direction) const;
|
||||
```
|
||||
|
||||
Finds the vertex in the mesh's **local space** that has the maximum dot product with `direction`.
|
||||
|
||||
**Algorithm**: Linear search through all vertices (O(n) where n is vertex count).
|
||||
|
||||
**Returns**: Const reference to the vertex in `m_vertex_buffer`.
|
||||
|
||||
---
|
||||
|
||||
### `find_abs_furthest_vertex`
|
||||
|
||||
```cpp
|
||||
[[nodiscard]]
|
||||
VertexType find_abs_furthest_vertex(const VertexType& direction) const;
|
||||
```
|
||||
|
||||
Finds the vertex farthest along `direction` and transforms it to **world space**. This is the primary method used by GJK/EPA.
|
||||
|
||||
**Steps**:
|
||||
1. Find furthest vertex in local space using `find_furthest_vertex`
|
||||
2. Transform to world space using `mesh.vertex_to_world_space()`
|
||||
|
||||
**Returns**: Vertex position in world coordinates.
|
||||
|
||||
**Usage in GJK**:
|
||||
```cpp
|
||||
// GJK support function for Minkowski difference
|
||||
VertexType support = collider_a.find_abs_furthest_vertex(direction)
|
||||
- collider_b.find_abs_furthest_vertex(-direction);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Collision Detection
|
||||
|
||||
```cpp
|
||||
using namespace omath::collision;
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// Create meshes with vertex data
|
||||
std::vector<Vector3<float>> vbo_a = {
|
||||
{-1, -1, -1}, {1, -1, -1}, {1, 1, -1}, {-1, 1, -1},
|
||||
{-1, -1, 1}, {1, -1, 1}, {1, 1, 1}, {-1, 1, 1}
|
||||
};
|
||||
std::vector<Vector3<std::size_t>> vao_a = /* face indices */;
|
||||
|
||||
Mesh mesh_a(vbo_a, vao_a);
|
||||
mesh_a.set_origin({0, 0, 0});
|
||||
|
||||
Mesh mesh_b(vbo_b, vao_b);
|
||||
mesh_b.set_origin({5, 0, 0}); // Positioned away from mesh_a
|
||||
|
||||
// Wrap in colliders
|
||||
MeshCollider<Mesh> collider_a(std::move(mesh_a));
|
||||
MeshCollider<Mesh> collider_b(std::move(mesh_b));
|
||||
|
||||
// Run GJK
|
||||
auto result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
|
||||
collider_a, collider_b
|
||||
);
|
||||
|
||||
if (result.hit) {
|
||||
std::cout << "Collision detected!\n";
|
||||
}
|
||||
```
|
||||
|
||||
### With EPA for Penetration Depth
|
||||
|
||||
```cpp
|
||||
auto gjk_result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
|
||||
collider_a, collider_b
|
||||
);
|
||||
|
||||
if (gjk_result.hit) {
|
||||
auto epa_result = Epa<MeshCollider<Mesh>>::solve(
|
||||
collider_a, collider_b, gjk_result.simplex
|
||||
);
|
||||
|
||||
if (epa_result.success) {
|
||||
std::cout << "Penetration: " << epa_result.depth << " units\n";
|
||||
std::cout << "Normal: " << epa_result.normal << "\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Mesh Creation
|
||||
|
||||
```cpp
|
||||
// Create a simple box mesh
|
||||
std::vector<Vector3<float>> box_vertices = {
|
||||
{-0.5f, -0.5f, -0.5f}, { 0.5f, -0.5f, -0.5f},
|
||||
{ 0.5f, 0.5f, -0.5f}, {-0.5f, 0.5f, -0.5f},
|
||||
{-0.5f, -0.5f, 0.5f}, { 0.5f, -0.5f, 0.5f},
|
||||
{ 0.5f, 0.5f, 0.5f}, {-0.5f, 0.5f, 0.5f}
|
||||
};
|
||||
|
||||
std::vector<Vector3<std::size_t>> box_indices = {
|
||||
{0, 1, 2}, {0, 2, 3}, // Front face
|
||||
{4, 6, 5}, {4, 7, 6}, // Back face
|
||||
{0, 4, 5}, {0, 5, 1}, // Bottom face
|
||||
{2, 6, 7}, {2, 7, 3}, // Top face
|
||||
{0, 3, 7}, {0, 7, 4}, // Left face
|
||||
{1, 5, 6}, {1, 6, 2} // Right face
|
||||
};
|
||||
|
||||
using namespace omath::source_engine;
|
||||
Mesh box_mesh(box_vertices, box_indices);
|
||||
box_mesh.set_origin({10, 0, 0});
|
||||
box_mesh.set_scale({2, 2, 2});
|
||||
|
||||
MeshCollider<Mesh> box_collider(std::move(box_mesh));
|
||||
```
|
||||
|
||||
### Oriented Collision
|
||||
|
||||
```cpp
|
||||
// Create rotated mesh
|
||||
Mesh mesh(vertices, indices);
|
||||
mesh.set_origin({5, 5, 5});
|
||||
mesh.set_scale({1, 1, 1});
|
||||
|
||||
// Set rotation (engine-specific angles)
|
||||
ViewAngles rotation;
|
||||
rotation.pitch = PitchAngle::from_degrees(45.0f);
|
||||
rotation.yaw = YawAngle::from_degrees(30.0f);
|
||||
mesh.set_rotation(rotation);
|
||||
|
||||
// Collider automatically handles transformation
|
||||
MeshCollider<Mesh> collider(std::move(mesh));
|
||||
|
||||
// Support function returns world-space vertices
|
||||
auto support = collider.find_abs_furthest_vertex({0, 1, 0});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Linear Search
|
||||
|
||||
`find_furthest_vertex` performs a **linear search** through all vertices:
|
||||
* **Time complexity**: O(n) per support query
|
||||
* **GJK iterations**: ~10-20 support queries per collision test
|
||||
* **Total cost**: O(k × n) where k is GJK iterations
|
||||
|
||||
For meshes with many vertices (>1000), consider:
|
||||
* Using simpler proxy geometry (bounding box, convex hull with fewer vertices)
|
||||
* Pre-computing hierarchical structures
|
||||
* Using specialized collision shapes when possible
|
||||
|
||||
### Caching Opportunities
|
||||
|
||||
The implementation uses `std::ranges::max_element`, which is cache-friendly for contiguous vertex buffers. For optimal performance:
|
||||
* Store vertices contiguously in memory
|
||||
* Avoid pointer-based or scattered vertex storage
|
||||
* Consider SoA (Structure of Arrays) layout for SIMD optimization
|
||||
|
||||
### World Space Transformation
|
||||
|
||||
The `vertex_to_world_space` call involves matrix multiplication:
|
||||
* **Cost**: ~15-20 floating-point operations per vertex
|
||||
* **Optimization**: The mesh caches its transformation matrix
|
||||
* **Update cost**: Only recomputed when origin/rotation/scale changes
|
||||
|
||||
---
|
||||
|
||||
## Limitations & Edge Cases
|
||||
|
||||
### Convex Hull Requirement
|
||||
|
||||
**Critical**: GJK/EPA only work with **convex shapes**. If your mesh is concave:
|
||||
|
||||
#### Option 1: Convex Decomposition
|
||||
```cpp
|
||||
// Decompose concave mesh into convex parts
|
||||
std::vector<Mesh> convex_parts = decompose_mesh(concave_mesh);
|
||||
|
||||
for (const auto& part : convex_parts) {
|
||||
MeshCollider collider(part);
|
||||
// Test each part separately
|
||||
}
|
||||
```
|
||||
|
||||
#### Option 2: Use Convex Hull
|
||||
```cpp
|
||||
// Compute convex hull of vertices
|
||||
auto hull_vertices = compute_convex_hull(mesh.m_vertex_buffer);
|
||||
Mesh hull_mesh(hull_vertices, hull_indices);
|
||||
MeshCollider collider(std::move(hull_mesh));
|
||||
```
|
||||
|
||||
#### Option 3: Different Algorithm
|
||||
Use triangle-based collision (e.g., LineTracer) for true concave support.
|
||||
|
||||
### Empty Mesh
|
||||
|
||||
Behavior is undefined if `m_vertex_buffer` is empty. Always ensure:
|
||||
```cpp
|
||||
assert(!mesh.m_vertex_buffer.empty());
|
||||
MeshCollider collider(std::move(mesh));
|
||||
```
|
||||
|
||||
### Degenerate Meshes
|
||||
|
||||
* **Single vertex**: Treated as a point (degenerates to sphere collision)
|
||||
* **Two vertices**: Line segment (may cause GJK issues)
|
||||
* **Coplanar vertices**: Flat mesh; EPA may have convergence issues
|
||||
|
||||
**Recommendation**: Use at least 4 non-coplanar vertices for robustness.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate Systems
|
||||
|
||||
`MeshCollider` supports different engine coordinate systems through the `MeshTrait`:
|
||||
|
||||
| Engine | Up Axis | Handedness | Rotation Order |
|
||||
|--------|---------|------------|----------------|
|
||||
| Source Engine | Z | Right-handed | Pitch/Yaw/Roll |
|
||||
| Unity | Y | Left-handed | Pitch/Yaw/Roll |
|
||||
| Unreal | Z | Left-handed | Roll/Pitch/Yaw |
|
||||
| Frostbite | Y | Right-handed | Pitch/Yaw/Roll |
|
||||
| IW Engine | Z | Right-handed | Pitch/Yaw/Roll |
|
||||
| OpenGL | Y | Right-handed | Pitch/Yaw/Roll |
|
||||
|
||||
The `vertex_to_world_space` method handles these differences transparently.
|
||||
|
||||
---
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Support Function
|
||||
|
||||
For specialized collision shapes, implement a custom collider:
|
||||
|
||||
```cpp
|
||||
class SphereCollider {
|
||||
public:
|
||||
using VertexType = Vector3<float>;
|
||||
|
||||
Vector3<float> center;
|
||||
float radius;
|
||||
|
||||
VertexType find_abs_furthest_vertex(const VertexType& direction) const {
|
||||
auto normalized = direction.normalized();
|
||||
return center + normalized * radius;
|
||||
}
|
||||
};
|
||||
|
||||
// Use with GJK/EPA
|
||||
auto result = GjkAlgorithm<SphereCollider>::check_collision(sphere_a, sphere_b);
|
||||
```
|
||||
|
||||
### Debugging Support Queries
|
||||
|
||||
```cpp
|
||||
class DebugMeshCollider : public MeshCollider<Mesh> {
|
||||
public:
|
||||
using MeshCollider::MeshCollider;
|
||||
|
||||
VertexType find_abs_furthest_vertex(const VertexType& direction) const {
|
||||
auto result = MeshCollider::find_abs_furthest_vertex(direction);
|
||||
std::cout << "Support query: direction=" << direction
|
||||
<< " -> vertex=" << result << "\n";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [GJK Algorithm Documentation](gjk_algorithm.md) - Uses `MeshCollider` for collision detection
|
||||
- [EPA Algorithm Documentation](epa_algorithm.md) - Uses `MeshCollider` for penetration depth
|
||||
- [Simplex Documentation](simplex.md) - Data structure used by GJK
|
||||
- [Mesh Documentation](../3d_primitives/mesh.md) - Underlying mesh primitive
|
||||
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
327
docs/collision/simplex.md
Normal file
327
docs/collision/simplex.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# `omath::collision::Simplex` — Fixed-capacity simplex for GJK/EPA
|
||||
|
||||
> Header: `omath/collision/simplex.hpp`
|
||||
> Namespace: `omath::collision`
|
||||
> Depends on: `Vector3<float>` (or any type satisfying `GjkVector` concept)
|
||||
> Purpose: store and manipulate simplices in GJK and EPA algorithms
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`Simplex` is a lightweight container for up to 4 points, used internally by the GJK and EPA collision detection algorithms. A simplex in this context is a geometric shape defined by 1 to 4 vertices:
|
||||
|
||||
* **1 point** — a single vertex
|
||||
* **2 points** — a line segment
|
||||
* **3 points** — a triangle
|
||||
* **4 points** — a tetrahedron
|
||||
|
||||
The GJK algorithm builds simplices incrementally to detect collisions, and EPA extends a 4-point simplex to compute penetration depth.
|
||||
|
||||
---
|
||||
|
||||
## Template & Concepts
|
||||
|
||||
```cpp
|
||||
template<GjkVector VectorType = Vector3<float>>
|
||||
class Simplex final;
|
||||
```
|
||||
|
||||
### `GjkVector` Concept
|
||||
|
||||
The vertex type must satisfy:
|
||||
|
||||
```cpp
|
||||
template<class V>
|
||||
concept GjkVector = requires(const V& a, const V& b) {
|
||||
{ -a } -> std::same_as<V>;
|
||||
{ a - b } -> std::same_as<V>;
|
||||
{ a.cross(b) } -> std::same_as<V>;
|
||||
{ a.point_to_same_direction(b) } -> std::same_as<bool>;
|
||||
};
|
||||
```
|
||||
|
||||
`omath::Vector3<float>` satisfies this concept and is the default.
|
||||
|
||||
---
|
||||
|
||||
## Constructors & Assignment
|
||||
|
||||
```cpp
|
||||
constexpr Simplex() = default;
|
||||
|
||||
constexpr Simplex& operator=(std::initializer_list<VectorType> list) noexcept;
|
||||
```
|
||||
|
||||
### Initialization
|
||||
|
||||
```cpp
|
||||
// Empty simplex
|
||||
Simplex<Vector3<float>> s;
|
||||
|
||||
// Initialize with points
|
||||
Simplex<Vector3<float>> s2;
|
||||
s2 = {v1, v2, v3}; // 3-point simplex (triangle)
|
||||
```
|
||||
|
||||
**Constraint**: Maximum 4 points. Passing more triggers an assertion in debug builds.
|
||||
|
||||
---
|
||||
|
||||
## Core Methods
|
||||
|
||||
### Adding Points
|
||||
|
||||
```cpp
|
||||
constexpr void push_front(const VectorType& p) noexcept;
|
||||
```
|
||||
|
||||
Inserts a point at the **front** (index 0), shifting existing points back. If the simplex is already at capacity (4 points), the last point is discarded.
|
||||
|
||||
**Usage pattern in GJK**:
|
||||
```cpp
|
||||
simplex.push_front(new_support_point);
|
||||
// Now simplex[0] is the newest point
|
||||
```
|
||||
|
||||
### Size & Capacity
|
||||
|
||||
```cpp
|
||||
[[nodiscard]] constexpr std::size_t size() const noexcept;
|
||||
[[nodiscard]] static constexpr std::size_t capacity = 4;
|
||||
```
|
||||
|
||||
* `size()` — current number of points (0-4)
|
||||
* `capacity` — maximum points (always 4)
|
||||
|
||||
### Element Access
|
||||
|
||||
```cpp
|
||||
[[nodiscard]] constexpr VectorType& operator[](std::size_t index) noexcept;
|
||||
[[nodiscard]] constexpr const VectorType& operator[](std::size_t index) const noexcept;
|
||||
```
|
||||
|
||||
Access points by index. **No bounds checking** — index must be `< size()`.
|
||||
|
||||
```cpp
|
||||
if (simplex.size() >= 2) {
|
||||
auto edge = simplex[1] - simplex[0];
|
||||
}
|
||||
```
|
||||
|
||||
### Iterators
|
||||
|
||||
```cpp
|
||||
[[nodiscard]] constexpr auto begin() noexcept;
|
||||
[[nodiscard]] constexpr auto end() noexcept;
|
||||
[[nodiscard]] constexpr auto begin() const noexcept;
|
||||
[[nodiscard]] constexpr auto end() const noexcept;
|
||||
```
|
||||
|
||||
Standard iterator support for range-based loops:
|
||||
|
||||
```cpp
|
||||
for (const auto& vertex : simplex) {
|
||||
std::cout << vertex << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GJK-Specific Methods
|
||||
|
||||
These methods implement the core logic for simplifying simplices in the GJK algorithm.
|
||||
|
||||
### `contains_origin`
|
||||
|
||||
```cpp
|
||||
[[nodiscard]] constexpr bool contains_origin() noexcept;
|
||||
```
|
||||
|
||||
Determines if the origin lies within the current simplex. This is the **core GJK test**: if true, the shapes intersect.
|
||||
|
||||
* For a **1-point** simplex, always returns `false` (can't contain origin)
|
||||
* For a **2-point** simplex (line), checks if origin projects onto the segment
|
||||
* For a **3-point** simplex (triangle), checks if origin projects onto the triangle
|
||||
* For a **4-point** simplex (tetrahedron), checks if origin is inside
|
||||
|
||||
**Side effect**: Simplifies the simplex by removing points not needed to maintain proximity to the origin. After calling, `size()` may have decreased.
|
||||
|
||||
**Return value**:
|
||||
* `true` — origin is contained (collision detected)
|
||||
* `false` — origin not contained; simplex has been simplified toward origin
|
||||
|
||||
### `next_direction`
|
||||
|
||||
```cpp
|
||||
[[nodiscard]] constexpr VectorType next_direction() const noexcept;
|
||||
```
|
||||
|
||||
Computes the next search direction for GJK. This is the direction from the simplex toward the origin, used to query the next support point.
|
||||
|
||||
* Must be called **after** `contains_origin()` returns `false`
|
||||
* Behavior is **undefined** if called when `size() == 0` or when origin is already contained
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### GJK Iteration (Simplified)
|
||||
|
||||
```cpp
|
||||
Simplex<Vector3<float>> simplex;
|
||||
Vector3<float> direction{1, 0, 0}; // Initial search direction
|
||||
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
// Get support point in current direction
|
||||
auto support = find_support_vertex(collider_a, collider_b, direction);
|
||||
|
||||
// Check if we made progress
|
||||
if (support.dot(direction) <= 0)
|
||||
break; // No collision possible
|
||||
|
||||
simplex.push_front(support);
|
||||
|
||||
// Check if simplex contains origin
|
||||
if (simplex.contains_origin()) {
|
||||
// Collision detected!
|
||||
assert(simplex.size() == 4);
|
||||
return GjkHitInfo{true, simplex};
|
||||
}
|
||||
|
||||
// Get next search direction
|
||||
direction = simplex.next_direction();
|
||||
}
|
||||
|
||||
// No collision
|
||||
return GjkHitInfo{false, {}};
|
||||
```
|
||||
|
||||
### Manual Simplex Construction
|
||||
|
||||
```cpp
|
||||
using Vec3 = Vector3<float>;
|
||||
|
||||
Simplex<Vec3> simplex;
|
||||
simplex = {
|
||||
Vec3{0.0f, 0.0f, 0.0f},
|
||||
Vec3{1.0f, 0.0f, 0.0f},
|
||||
Vec3{0.0f, 1.0f, 0.0f},
|
||||
Vec3{0.0f, 0.0f, 1.0f}
|
||||
};
|
||||
|
||||
assert(simplex.size() == 4);
|
||||
|
||||
// Check if origin is inside this tetrahedron
|
||||
bool has_collision = simplex.contains_origin();
|
||||
```
|
||||
|
||||
### Iterating Over Points
|
||||
|
||||
```cpp
|
||||
void print_simplex(const Simplex<Vector3<float>>& s) {
|
||||
std::cout << "Simplex with " << s.size() << " points:\n";
|
||||
for (std::size_t i = 0; i < s.size(); ++i) {
|
||||
const auto& p = s[i];
|
||||
std::cout << " [" << i << "] = ("
|
||||
<< p.x << ", " << p.y << ", " << p.z << ")\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Simplex Simplification
|
||||
|
||||
The `contains_origin()` method implements different tests based on simplex size:
|
||||
|
||||
#### Line Segment (2 points)
|
||||
|
||||
Checks if origin projects onto segment `[A, B]`:
|
||||
* If yes, keeps both points
|
||||
* If no, keeps only the closer point
|
||||
|
||||
#### Triangle (3 points)
|
||||
|
||||
Tests the origin against the triangle plane and edges using cross products. Simplifies to:
|
||||
* The full triangle if origin projects onto its surface
|
||||
* An edge if origin is closest to that edge
|
||||
* A single vertex otherwise
|
||||
|
||||
#### Tetrahedron (4 points)
|
||||
|
||||
Tests origin against all four faces:
|
||||
* If origin is inside, returns `true` (collision)
|
||||
* If outside, reduces to the face/edge/vertex closest to origin
|
||||
|
||||
### Direction Calculation
|
||||
|
||||
The `next_direction()` method computes:
|
||||
* For **line**: perpendicular from line toward origin
|
||||
* For **triangle**: perpendicular from triangle toward origin
|
||||
* Implementation uses cross products and projections to avoid sqrt when possible
|
||||
|
||||
---
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
* **Storage**: Fixed 4 × `sizeof(VectorType)` + size counter
|
||||
* **Push front**: O(n) where n is current size (max 4, so effectively O(1))
|
||||
* **Contains origin**: O(1) for each case (line, triangle, tetrahedron)
|
||||
* **Next direction**: O(1) — simple cross products and subtractions
|
||||
* **No heap allocations**: All storage is inline
|
||||
|
||||
**constexpr**: All methods are `constexpr`, enabling compile-time usage where feasible.
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Constraints
|
||||
|
||||
### Degenerate Simplices
|
||||
|
||||
* **Zero-length edges**: Can occur if support points coincide. The algorithm handles this by checking `point_to_same_direction` before divisions.
|
||||
* **Collinear points**: Triangle simplification detects and handles collinear cases by reducing to a line.
|
||||
* **Flat tetrahedron**: If the 4th point is coplanar with the first 3, the origin containment test may have reduced precision.
|
||||
|
||||
### Assertions
|
||||
|
||||
* **Capacity**: `operator=` asserts `list.size() <= 4` in debug builds
|
||||
* **Index bounds**: No bounds checking in release builds — ensure `index < size()`
|
||||
|
||||
### Thread Safety
|
||||
|
||||
* **Read-only**: Safe to read from multiple threads
|
||||
* **Modification**: Not thread-safe; synchronize writes externally
|
||||
|
||||
---
|
||||
|
||||
## Relationship to GJK & EPA
|
||||
|
||||
### In GJK
|
||||
|
||||
* Starts empty or with an initial point
|
||||
* Grows via `push_front` as support points are added
|
||||
* Shrinks via `contains_origin` as it's simplified
|
||||
* Once it reaches 4 points and contains origin, GJK succeeds
|
||||
|
||||
### In EPA
|
||||
|
||||
* Takes a 4-point simplex from GJK as input
|
||||
* Uses the tetrahedron as the initial polytope
|
||||
* Does not directly use the `Simplex` class for expansion (EPA maintains a more complex polytope structure)
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [GJK Algorithm Documentation](gjk_algorithm.md) - Uses `Simplex` for collision detection
|
||||
- [EPA Algorithm Documentation](epa_algorithm.md) - Takes 4-point `Simplex` as input
|
||||
- [MeshCollider Documentation](mesh_collider.md) - Provides support function for GJK/EPA
|
||||
- [Vector3 Documentation](../linear_algebra/vector3.md) - Default vertex type
|
||||
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Collision tutorial
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
119
docs/engines/frostbite/mesh_trait.md
Normal file
119
docs/engines/frostbite/mesh_trait.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# `omath::frostbite_engine::MeshTrait` — mesh transformation trait for Frostbite Engine
|
||||
|
||||
> Header: `omath/engines/frostbite_engine/traits/mesh_trait.hpp`
|
||||
> Namespace: `omath::frostbite_engine`
|
||||
> Purpose: provide Frostbite Engine-specific rotation matrix computation for `omath::primitives::Mesh`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Frostbite's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System
|
||||
|
||||
**Frostbite Engine** uses:
|
||||
* **Up axis**: +Y
|
||||
* **Forward axis**: +Z
|
||||
* **Right axis**: +X
|
||||
* **Handedness**: Right-handed
|
||||
* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z)
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::frostbite_engine {
|
||||
|
||||
class MeshTrait final {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
};
|
||||
|
||||
} // namespace omath::frostbite_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method: `rotation_matrix`
|
||||
|
||||
```cpp
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
```
|
||||
|
||||
Computes a 4×4 rotation matrix from Frostbite-style Euler angles.
|
||||
|
||||
**Parameters**:
|
||||
* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles
|
||||
|
||||
**Returns**: 4×4 rotation matrix suitable for mesh transformation
|
||||
|
||||
**Implementation**: Delegates to `frostbite_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### With Mesh
|
||||
|
||||
```cpp
|
||||
using namespace omath::frostbite_engine;
|
||||
|
||||
// Create mesh (MeshTrait is used automatically)
|
||||
Mesh my_mesh(vertices, indices);
|
||||
|
||||
// Set rotation using ViewAngles
|
||||
ViewAngles angles;
|
||||
angles.pitch = PitchAngle::from_degrees(30.0f);
|
||||
angles.yaw = YawAngle::from_degrees(45.0f);
|
||||
angles.roll = RollAngle::from_degrees(0.0f);
|
||||
|
||||
my_mesh.set_rotation(angles);
|
||||
|
||||
// The rotation matrix is computed using MeshTrait::rotation_matrix
|
||||
auto matrix = my_mesh.get_to_world_matrix();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rotation Conventions
|
||||
|
||||
Frostbite uses a right-handed Y-up coordinate system:
|
||||
|
||||
1. **Pitch** (rotation around X-axis / right axis)
|
||||
* Positive pitch looks upward (+Y direction)
|
||||
* Range: typically [-89°, 89°]
|
||||
|
||||
2. **Yaw** (rotation around Y-axis / up axis)
|
||||
* Positive yaw rotates counterclockwise when viewed from above (right-handed)
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
3. **Roll** (rotation around Z-axis / forward axis)
|
||||
* Positive roll tilts right
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
---
|
||||
|
||||
## Type Alias
|
||||
|
||||
```cpp
|
||||
namespace omath::frostbite_engine {
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
|
||||
- [Formulas Documentation](formulas.md) - Frostbite rotation formula
|
||||
- [CameraTrait Documentation](camera_trait.md) - Camera trait
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
119
docs/engines/iw_engine/mesh_trait.md
Normal file
119
docs/engines/iw_engine/mesh_trait.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# `omath::iw_engine::MeshTrait` — mesh transformation trait for IW Engine
|
||||
|
||||
> Header: `omath/engines/iw_engine/traits/mesh_trait.hpp`
|
||||
> Namespace: `omath::iw_engine`
|
||||
> Purpose: provide IW Engine-specific rotation matrix computation for `omath::primitives::Mesh`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in IW Engine's (Infinity Ward) coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System
|
||||
|
||||
**IW Engine** (Call of Duty) uses:
|
||||
* **Up axis**: +Z
|
||||
* **Forward axis**: +Y
|
||||
* **Right axis**: +X
|
||||
* **Handedness**: Right-handed
|
||||
* **Rotation order**: Pitch (X) → Yaw (Z) → Roll (Y)
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::iw_engine {
|
||||
|
||||
class MeshTrait final {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
};
|
||||
|
||||
} // namespace omath::iw_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method: `rotation_matrix`
|
||||
|
||||
```cpp
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
```
|
||||
|
||||
Computes a 4×4 rotation matrix from IW Engine-style Euler angles.
|
||||
|
||||
**Parameters**:
|
||||
* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles
|
||||
|
||||
**Returns**: 4×4 rotation matrix suitable for mesh transformation
|
||||
|
||||
**Implementation**: Delegates to `iw_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### With Mesh
|
||||
|
||||
```cpp
|
||||
using namespace omath::iw_engine;
|
||||
|
||||
// Create mesh (MeshTrait is used automatically)
|
||||
Mesh my_mesh(vertices, indices);
|
||||
|
||||
// Set rotation using ViewAngles
|
||||
ViewAngles angles;
|
||||
angles.pitch = PitchAngle::from_degrees(30.0f);
|
||||
angles.yaw = YawAngle::from_degrees(45.0f);
|
||||
angles.roll = RollAngle::from_degrees(0.0f);
|
||||
|
||||
my_mesh.set_rotation(angles);
|
||||
|
||||
// The rotation matrix is computed using MeshTrait::rotation_matrix
|
||||
auto matrix = my_mesh.get_to_world_matrix();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rotation Conventions
|
||||
|
||||
IW Engine uses a right-handed Z-up coordinate system (similar to Source Engine):
|
||||
|
||||
1. **Pitch** (rotation around X-axis / right axis)
|
||||
* Positive pitch looks upward (+Z direction)
|
||||
* Range: typically [-89°, 89°]
|
||||
|
||||
2. **Yaw** (rotation around Z-axis / up axis)
|
||||
* Positive yaw rotates counterclockwise when viewed from above
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
3. **Roll** (rotation around Y-axis / forward axis)
|
||||
* Positive roll tilts right
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
---
|
||||
|
||||
## Type Alias
|
||||
|
||||
```cpp
|
||||
namespace omath::iw_engine {
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
|
||||
- [Formulas Documentation](formulas.md) - IW Engine rotation formula
|
||||
- [CameraTrait Documentation](camera_trait.md) - Camera trait
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
121
docs/engines/opengl_engine/mesh_trait.md
Normal file
121
docs/engines/opengl_engine/mesh_trait.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# `omath::opengl_engine::MeshTrait` — mesh transformation trait for OpenGL
|
||||
|
||||
> Header: `omath/engines/opengl_engine/traits/mesh_trait.hpp`
|
||||
> Namespace: `omath::opengl_engine`
|
||||
> Purpose: provide OpenGL-specific rotation matrix computation for `omath::primitives::Mesh`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in OpenGL's canonical coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System
|
||||
|
||||
**OpenGL** (canonical) uses:
|
||||
* **Up axis**: +Y
|
||||
* **Forward axis**: +Z (toward viewer)
|
||||
* **Right axis**: +X
|
||||
* **Handedness**: Right-handed
|
||||
* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z)
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::opengl_engine {
|
||||
|
||||
class MeshTrait final {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
};
|
||||
|
||||
} // namespace omath::opengl_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method: `rotation_matrix`
|
||||
|
||||
```cpp
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
```
|
||||
|
||||
Computes a 4×4 rotation matrix from OpenGL-style Euler angles.
|
||||
|
||||
**Parameters**:
|
||||
* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles
|
||||
|
||||
**Returns**: 4×4 rotation matrix suitable for mesh transformation
|
||||
|
||||
**Implementation**: Delegates to `opengl_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### With Mesh
|
||||
|
||||
```cpp
|
||||
using namespace omath::opengl_engine;
|
||||
|
||||
// Create mesh (MeshTrait is used automatically)
|
||||
Mesh my_mesh(vertices, indices);
|
||||
|
||||
// Set rotation using ViewAngles
|
||||
ViewAngles angles;
|
||||
angles.pitch = PitchAngle::from_degrees(30.0f);
|
||||
angles.yaw = YawAngle::from_degrees(45.0f);
|
||||
angles.roll = RollAngle::from_degrees(0.0f);
|
||||
|
||||
my_mesh.set_rotation(angles);
|
||||
|
||||
// The rotation matrix is computed using MeshTrait::rotation_matrix
|
||||
auto matrix = my_mesh.get_to_world_matrix();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rotation Conventions
|
||||
|
||||
OpenGL uses a right-handed Y-up coordinate system:
|
||||
|
||||
1. **Pitch** (rotation around X-axis / right axis)
|
||||
* Positive pitch looks upward (+Y direction)
|
||||
* Range: typically [-89°, 89°]
|
||||
|
||||
2. **Yaw** (rotation around Y-axis / up axis)
|
||||
* Positive yaw rotates counterclockwise when viewed from above (right-handed)
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
3. **Roll** (rotation around Z-axis / depth axis)
|
||||
* Positive roll tilts right
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
**Note**: In OpenGL, +Z points toward the viewer in view space, but away from the viewer in world space.
|
||||
|
||||
---
|
||||
|
||||
## Type Alias
|
||||
|
||||
```cpp
|
||||
namespace omath::opengl_engine {
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
|
||||
- [Formulas Documentation](formulas.md) - OpenGL rotation formula
|
||||
- [CameraTrait Documentation](camera_trait.md) - Camera trait
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
182
docs/engines/source_engine/mesh_trait.md
Normal file
182
docs/engines/source_engine/mesh_trait.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# `omath::source_engine::MeshTrait` — mesh transformation trait for Source Engine
|
||||
|
||||
> Header: `omath/engines/source_engine/traits/mesh_trait.hpp`
|
||||
> Namespace: `omath::source_engine`
|
||||
> Purpose: provide Source Engine-specific rotation matrix computation for `omath::primitives::Mesh`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Source Engine's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System
|
||||
|
||||
**Source Engine** uses:
|
||||
* **Up axis**: +Z
|
||||
* **Forward axis**: +Y
|
||||
* **Right axis**: +X
|
||||
* **Handedness**: Right-handed
|
||||
* **Rotation order**: Pitch (X) → Yaw (Z) → Roll (Y)
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::source_engine {
|
||||
|
||||
class MeshTrait final {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
};
|
||||
|
||||
} // namespace omath::source_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method: `rotation_matrix`
|
||||
|
||||
```cpp
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
```
|
||||
|
||||
Computes a 4×4 rotation matrix from Source Engine-style Euler angles.
|
||||
|
||||
**Parameters**:
|
||||
* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles
|
||||
|
||||
**Returns**: 4×4 rotation matrix suitable for mesh transformation
|
||||
|
||||
**Implementation**: Delegates to `source_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### With Mesh
|
||||
|
||||
```cpp
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// Create mesh (MeshTrait is used automatically)
|
||||
Mesh my_mesh(vertices, indices);
|
||||
|
||||
// Set rotation using ViewAngles
|
||||
ViewAngles angles;
|
||||
angles.pitch = PitchAngle::from_degrees(30.0f);
|
||||
angles.yaw = YawAngle::from_degrees(45.0f);
|
||||
angles.roll = RollAngle::from_degrees(0.0f);
|
||||
|
||||
my_mesh.set_rotation(angles);
|
||||
|
||||
// The rotation matrix is computed using MeshTrait::rotation_matrix
|
||||
auto matrix = my_mesh.get_to_world_matrix();
|
||||
```
|
||||
|
||||
### Direct Usage
|
||||
|
||||
```cpp
|
||||
using namespace omath::source_engine;
|
||||
|
||||
ViewAngles angles;
|
||||
angles.pitch = PitchAngle::from_degrees(45.0f);
|
||||
angles.yaw = YawAngle::from_degrees(90.0f);
|
||||
angles.roll = RollAngle::from_degrees(0.0f);
|
||||
|
||||
Mat4X4 rot_matrix = MeshTrait::rotation_matrix(angles);
|
||||
|
||||
// Use the matrix directly
|
||||
Vector3<float> local_point{1, 0, 0};
|
||||
auto rotated = rot_matrix * mat_column_from_vector(local_point);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rotation Conventions
|
||||
|
||||
The rotation matrix is built following Source Engine's conventions:
|
||||
|
||||
1. **Pitch** (rotation around X-axis / right axis)
|
||||
* Positive pitch looks upward (+Z direction)
|
||||
* Range: typically [-89°, 89°]
|
||||
|
||||
2. **Yaw** (rotation around Z-axis / up axis)
|
||||
* Positive yaw rotates counterclockwise when viewed from above
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
3. **Roll** (rotation around Y-axis / forward axis)
|
||||
* Positive roll tilts right
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
**Composition**: The matrices are combined in the order Pitch × Yaw × Roll, producing a rotation that:
|
||||
* First applies roll around the forward axis
|
||||
* Then applies yaw around the up axis
|
||||
* Finally applies pitch around the right axis
|
||||
|
||||
This matches Source Engine's internal rotation order.
|
||||
|
||||
---
|
||||
|
||||
## Related Functions
|
||||
|
||||
The trait delegates to the formula defined in `formulas.hpp`:
|
||||
|
||||
```cpp
|
||||
[[nodiscard]]
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||
```
|
||||
|
||||
See [Formulas Documentation](formulas.md) for details on the rotation matrix computation.
|
||||
|
||||
---
|
||||
|
||||
## Type Alias
|
||||
|
||||
The Source Engine mesh type is pre-defined:
|
||||
|
||||
```cpp
|
||||
namespace omath::source_engine {
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||
```
|
||||
|
||||
Use this alias to ensure correct trait usage:
|
||||
|
||||
```cpp
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// Correct: uses Source Engine trait
|
||||
Mesh my_mesh(vbo, vao);
|
||||
|
||||
// Avoid: manually specifying template parameters
|
||||
primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float> verbose_mesh(vbo, vao);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
* **Angle ranges**: Ensure angles are within valid ranges (pitch: [-89°, 89°], yaw/roll: [-180°, 180°])
|
||||
* **Performance**: Matrix computation is O(1) with ~64 floating-point operations
|
||||
* **Caching**: The mesh caches the transformation matrix; recomputed only when rotation changes
|
||||
* **Compatibility**: Works with all Source Engine games (CS:GO, TF2, Portal, Half-Life 2, etc.)
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive using this trait
|
||||
- [MeshCollider Documentation](../../collision/mesh_collider.md) - Collision wrapper for meshes
|
||||
- [Formulas Documentation](formulas.md) - Source Engine rotation formula
|
||||
- [CameraTrait Documentation](camera_trait.md) - Camera transformation trait
|
||||
- [Constants Documentation](constants.md) - Source Engine constants
|
||||
- [API Overview](../../api_overview.md) - High-level API reference
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
119
docs/engines/unity_engine/mesh_trait.md
Normal file
119
docs/engines/unity_engine/mesh_trait.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# `omath::unity_engine::MeshTrait` — mesh transformation trait for Unity Engine
|
||||
|
||||
> Header: `omath/engines/unity_engine/traits/mesh_trait.hpp`
|
||||
> Namespace: `omath::unity_engine`
|
||||
> Purpose: provide Unity Engine-specific rotation matrix computation for `omath::primitives::Mesh`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Unity's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System
|
||||
|
||||
**Unity Engine** uses:
|
||||
* **Up axis**: +Y
|
||||
* **Forward axis**: +Z
|
||||
* **Right axis**: +X
|
||||
* **Handedness**: Left-handed
|
||||
* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z)
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::unity_engine {
|
||||
|
||||
class MeshTrait final {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
};
|
||||
|
||||
} // namespace omath::unity_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method: `rotation_matrix`
|
||||
|
||||
```cpp
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
```
|
||||
|
||||
Computes a 4×4 rotation matrix from Unity-style Euler angles.
|
||||
|
||||
**Parameters**:
|
||||
* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles
|
||||
|
||||
**Returns**: 4×4 rotation matrix suitable for mesh transformation
|
||||
|
||||
**Implementation**: Delegates to `unity_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### With Mesh
|
||||
|
||||
```cpp
|
||||
using namespace omath::unity_engine;
|
||||
|
||||
// Create mesh (MeshTrait is used automatically)
|
||||
Mesh my_mesh(vertices, indices);
|
||||
|
||||
// Set rotation using ViewAngles
|
||||
ViewAngles angles;
|
||||
angles.pitch = PitchAngle::from_degrees(30.0f);
|
||||
angles.yaw = YawAngle::from_degrees(45.0f);
|
||||
angles.roll = RollAngle::from_degrees(0.0f);
|
||||
|
||||
my_mesh.set_rotation(angles);
|
||||
|
||||
// The rotation matrix is computed using MeshTrait::rotation_matrix
|
||||
auto matrix = my_mesh.get_to_world_matrix();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rotation Conventions
|
||||
|
||||
Unity uses a left-handed coordinate system with Y-up:
|
||||
|
||||
1. **Pitch** (rotation around X-axis / right axis)
|
||||
* Positive pitch looks upward (+Y direction)
|
||||
* Range: typically [-89°, 89°]
|
||||
|
||||
2. **Yaw** (rotation around Y-axis / up axis)
|
||||
* Positive yaw rotates clockwise when viewed from above (left-handed)
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
3. **Roll** (rotation around Z-axis / forward axis)
|
||||
* Positive roll tilts right
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
---
|
||||
|
||||
## Type Alias
|
||||
|
||||
```cpp
|
||||
namespace omath::unity_engine {
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
|
||||
- [Formulas Documentation](formulas.md) - Unity rotation formula
|
||||
- [CameraTrait Documentation](camera_trait.md) - Camera trait
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
121
docs/engines/unreal_engine/mesh_trait.md
Normal file
121
docs/engines/unreal_engine/mesh_trait.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# `omath::unreal_engine::MeshTrait` — mesh transformation trait for Unreal Engine
|
||||
|
||||
> Header: `omath/engines/unreal_engine/traits/mesh_trait.hpp`
|
||||
> Namespace: `omath::unreal_engine`
|
||||
> Purpose: provide Unreal Engine-specific rotation matrix computation for `omath::primitives::Mesh`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Unreal Engine's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System
|
||||
|
||||
**Unreal Engine** uses:
|
||||
* **Up axis**: +Z
|
||||
* **Forward axis**: +X
|
||||
* **Right axis**: +Y
|
||||
* **Handedness**: Left-handed
|
||||
* **Rotation order**: Roll (Y) → Pitch (X) → Yaw (Z)
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::unreal_engine {
|
||||
|
||||
class MeshTrait final {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
};
|
||||
|
||||
} // namespace omath::unreal_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method: `rotation_matrix`
|
||||
|
||||
```cpp
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
|
||||
```
|
||||
|
||||
Computes a 4×4 rotation matrix from Unreal-style Euler angles.
|
||||
|
||||
**Parameters**:
|
||||
* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles
|
||||
|
||||
**Returns**: 4×4 rotation matrix suitable for mesh transformation
|
||||
|
||||
**Implementation**: Delegates to `unreal_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### With Mesh
|
||||
|
||||
```cpp
|
||||
using namespace omath::unreal_engine;
|
||||
|
||||
// Create mesh (MeshTrait is used automatically)
|
||||
Mesh my_mesh(vertices, indices);
|
||||
|
||||
// Set rotation using ViewAngles
|
||||
ViewAngles angles;
|
||||
angles.pitch = PitchAngle::from_degrees(30.0f);
|
||||
angles.yaw = YawAngle::from_degrees(45.0f);
|
||||
angles.roll = RollAngle::from_degrees(0.0f);
|
||||
|
||||
my_mesh.set_rotation(angles);
|
||||
|
||||
// The rotation matrix is computed using MeshTrait::rotation_matrix
|
||||
auto matrix = my_mesh.get_to_world_matrix();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rotation Conventions
|
||||
|
||||
Unreal uses a left-handed Z-up coordinate system:
|
||||
|
||||
1. **Roll** (rotation around Y-axis / right axis)
|
||||
* Positive roll rotates forward axis upward
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
2. **Pitch** (rotation around X-axis / forward axis)
|
||||
* Positive pitch looks upward
|
||||
* Range: typically [-89°, 89°]
|
||||
|
||||
3. **Yaw** (rotation around Z-axis / up axis)
|
||||
* Positive yaw rotates clockwise when viewed from above (left-handed)
|
||||
* Range: [-180°, 180°]
|
||||
|
||||
**Note**: Unreal applies rotations in Roll-Pitch-Yaw order, different from most other engines.
|
||||
|
||||
---
|
||||
|
||||
## Type Alias
|
||||
|
||||
```cpp
|
||||
namespace omath::unreal_engine {
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
|
||||
- [Formulas Documentation](formulas.md) - Unreal rotation formula
|
||||
- [CameraTrait Documentation](camera_trait.md) - Camera trait
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 13 Nov 2025*
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 137 KiB |
BIN
docs/images/showcase/opengl.png
Normal file
BIN
docs/images/showcase/opengl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 224 KiB |
65
docs/styles/links.css
Normal file
65
docs/styles/links.css
Normal file
@@ -0,0 +1,65 @@
|
||||
/* Normal links */
|
||||
a {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
|
||||
/* On hover/focus */
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: #ff9900; /* a slightly different orange, optional */
|
||||
}
|
||||
/* Navbar background */
|
||||
.navbar,
|
||||
.navbar-default,
|
||||
.navbar-inverse {
|
||||
background-color: #a26228 !important; /* your orange */
|
||||
border-color: #ff6600 !important;
|
||||
}
|
||||
|
||||
/* Navbar brand + links */
|
||||
.navbar .navbar-brand,
|
||||
.navbar .navbar-nav > li > a {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Active and hover states */
|
||||
.navbar .navbar-nav > .active > a,
|
||||
.navbar .navbar-nav > .active > a:focus,
|
||||
.navbar .navbar-nav > .active > a:hover,
|
||||
.navbar .navbar-nav > li > a:hover,
|
||||
.navbar .navbar-nav > li > a:focus {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
/* === DROPDOWN MENU BACKGROUND === */
|
||||
.navbar .dropdown-menu {
|
||||
border-color: #ff6600 !important;
|
||||
}
|
||||
|
||||
/* Caret icon (the little triangle) */
|
||||
.navbar .dropdown-toggle .caret {
|
||||
border-top-color: #ffffff !important;
|
||||
border-bottom-color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* === BOOTSTRAP 3 STYLE ITEMS (mkdocs + bootswatch darkly often use this) === */
|
||||
.navbar .dropdown-menu > li > a {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.navbar .dropdown-menu > li > a:hover,
|
||||
.navbar .dropdown-menu > li > a:focus {
|
||||
background-color: #e65c00 !important; /* darker orange on hover */
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* === BOOTSTRAP 4+ STYLE ITEMS (if your theme uses .dropdown-item) === */
|
||||
.navbar .dropdown-item {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.navbar .dropdown-item:hover,
|
||||
.navbar .dropdown-item:focus {
|
||||
background-color: #e65c00 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
@@ -1,4 +1,38 @@
|
||||
project(examples)
|
||||
|
||||
add_executable(ExampleProjectionMatrixBuilder example_proj_mat_builder.cpp)
|
||||
target_link_libraries(ExampleProjectionMatrixBuilder PRIVATE omath::omath)
|
||||
add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp)
|
||||
set_target_properties(
|
||||
example_projection_matrix_builder
|
||||
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(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 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(example_signature_scan PRIVATE omath::omath)
|
||||
|
||||
add_executable(example_glfw3 example_glfw3.cpp)
|
||||
set_target_properties(
|
||||
example_glfw3
|
||||
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(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw OpenGL::OpenGL)
|
||||
|
||||
if(OMATH_ENABLE_VALGRIND)
|
||||
omath_setup_valgrind(example_projection_matrix_builder)
|
||||
omath_setup_valgrind(example_signature_scan)
|
||||
omath_setup_valgrind(example_glfw3)
|
||||
endif()
|
||||
|
||||
359
examples/example_glfw3.cpp
Normal file
359
examples/example_glfw3.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
// main.cpp
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
// --- OpenGL / windowing ---
|
||||
#include <GL/glew.h> // GLEW must come before GLFW
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
// --- your math / engine stuff ---
|
||||
#include "omath/3d_primitives/mesh.hpp"
|
||||
#include "omath/engines/opengl_engine/camera.hpp"
|
||||
#include "omath/engines/opengl_engine/constants.hpp"
|
||||
#include "omath/engines/opengl_engine/mesh.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
|
||||
using omath::Vector3;
|
||||
|
||||
// ---------------- TYPE ALIASES (ADAPT TO YOUR LIB) ----------------
|
||||
|
||||
// Your 4x4 matrix type
|
||||
using Mat4x4 = omath::opengl_engine::Mat4X4;
|
||||
|
||||
// Rotation angles for the Mesh
|
||||
using RotationAngles = omath::opengl_engine::ViewAngles;
|
||||
|
||||
// For brevity, alias the templates instantiated with your types
|
||||
using VertexType = omath::primitives::Vertex<Vector3<float>>;
|
||||
using CubeMesh = omath::opengl_engine::Mesh;
|
||||
using MyCamera = omath::opengl_engine::Camera;
|
||||
|
||||
// ---------------- SHADERS ----------------
|
||||
|
||||
static const char* vertexShaderSource = R"(
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aNormal;
|
||||
layout (location = 2) in vec3 aUv;
|
||||
|
||||
uniform mat4 uMVP;
|
||||
uniform mat4 uModel;
|
||||
|
||||
out vec3 vNormal;
|
||||
out vec3 vUv;
|
||||
|
||||
void main() {
|
||||
vNormal = aNormal;
|
||||
vUv = aUv;
|
||||
gl_Position = uMVP * uModel * vec4(aPos, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* fragmentShaderSource = R"(
|
||||
#version 330 core
|
||||
in vec3 vNormal;
|
||||
in vec3 vUv;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main() {
|
||||
vec3 baseColor = normalize(abs(vNormal));
|
||||
FragColor = vec4(baseColor, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
GLuint compileShader(GLenum type, const char* src)
|
||||
{
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &src, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint ok = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
|
||||
if (!ok)
|
||||
{
|
||||
char log[1024];
|
||||
glGetShaderInfoLog(shader, sizeof(log), nullptr, log);
|
||||
std::cerr << "Shader compile error: " << log << std::endl;
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
GLuint createShaderProgram()
|
||||
{
|
||||
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
|
||||
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
|
||||
|
||||
GLuint prog = glCreateProgram();
|
||||
glAttachShader(prog, vs);
|
||||
glAttachShader(prog, fs);
|
||||
glLinkProgram(prog);
|
||||
|
||||
GLint ok = GL_FALSE;
|
||||
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
|
||||
if (!ok)
|
||||
{
|
||||
char log[1024];
|
||||
glGetProgramInfoLog(prog, sizeof(log), nullptr, log);
|
||||
std::cerr << "Program link error: " << log << std::endl;
|
||||
}
|
||||
|
||||
glDeleteShader(vs);
|
||||
glDeleteShader(fs);
|
||||
return prog;
|
||||
}
|
||||
|
||||
void framebuffer_size_callback(GLFWwindow* /*window*/, int w, int h)
|
||||
{
|
||||
glViewport(0, 0, w, h);
|
||||
}
|
||||
|
||||
// ---------------- MAIN ----------------
|
||||
|
||||
int main()
|
||||
{
|
||||
// ---------- GLFW init ----------
|
||||
if (!glfwInit())
|
||||
{
|
||||
std::cerr << "Failed to init GLFW\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << "GLFW Version: " << glfwGetVersionString() << "\n";
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
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__
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
#endif
|
||||
|
||||
constexpr int SCR_WIDTH = 800;
|
||||
constexpr int SCR_HEIGHT = 600;
|
||||
|
||||
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "omath cube + camera (GLEW)", nullptr, nullptr);
|
||||
if (!window)
|
||||
{
|
||||
std::cerr << "Failed to create GLFW window\n";
|
||||
glfwTerminate();
|
||||
return -1;
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
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 ----------
|
||||
glewExperimental = GL_TRUE;
|
||||
GLenum glewErr = glewInit();
|
||||
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))
|
||||
<< "\n";
|
||||
glfwTerminate();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- GL state ----------
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
// Face culling
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK); // cull back faces
|
||||
glFrontFace(GL_CCW); // counter-clockwise is front
|
||||
|
||||
// ---------- Build Cube Mesh (CPU side) ----------
|
||||
std::vector<VertexType> vbo;
|
||||
vbo.reserve(8);
|
||||
|
||||
Vector3<float> p000{-0.5f, -0.5f, -0.5f};
|
||||
Vector3<float> p001{-0.5f, -0.5f, 0.5f};
|
||||
Vector3<float> p010{-0.5f, 0.5f, -0.5f};
|
||||
Vector3<float> p011{-0.5f, 0.5f, 0.5f};
|
||||
Vector3<float> p100{0.5f, -0.5f, -0.5f};
|
||||
Vector3<float> p101{0.5f, -0.5f, 0.5f};
|
||||
Vector3<float> p110{0.5f, 0.5f, -0.5f};
|
||||
Vector3<float> p111{0.5f, 0.5f, 0.5f};
|
||||
|
||||
VertexType v0{p000, Vector3<float>{-1, -1, -1}, omath::Vector2<float>{0, 0}};
|
||||
VertexType v1{p001, Vector3<float>{-1, -1, 1}, omath::Vector2<float>{0, 1}};
|
||||
VertexType v2{p010, Vector3<float>{-1, 1, -1}, omath::Vector2<float>{1, 0}};
|
||||
VertexType v3{p011, Vector3<float>{-1, 1, 1}, omath::Vector2<float>{1, 1}};
|
||||
VertexType v4{p100, Vector3<float>{1, -1, -1}, omath::Vector2<float>{0, 0}};
|
||||
VertexType v5{p101, Vector3<float>{1, -1, 1}, omath::Vector2<float>{0, 1}};
|
||||
VertexType v6{p110, Vector3<float>{1, 1, -1}, omath::Vector2<float>{1, 0}};
|
||||
VertexType v7{p111, Vector3<float>{1, 1, 1}, omath::Vector2<float>{1, 1}};
|
||||
|
||||
vbo.push_back(v0); // 0
|
||||
vbo.push_back(v1); // 1
|
||||
vbo.push_back(v2); // 2
|
||||
vbo.push_back(v3); // 3
|
||||
vbo.push_back(v4); // 4
|
||||
vbo.push_back(v5); // 5
|
||||
vbo.push_back(v6); // 6
|
||||
vbo.push_back(v7); // 7
|
||||
|
||||
using Idx = Vector3<std::uint32_t>;
|
||||
std::vector<Idx> ebo;
|
||||
ebo.reserve(12);
|
||||
|
||||
// front (z+)
|
||||
ebo.emplace_back(1, 5, 7);
|
||||
ebo.emplace_back(1, 7, 3);
|
||||
|
||||
// back (z-)
|
||||
ebo.emplace_back(0, 2, 6);
|
||||
ebo.emplace_back(0, 6, 4);
|
||||
|
||||
// left (x-)
|
||||
ebo.emplace_back(0, 1, 3);
|
||||
ebo.emplace_back(0, 3, 2);
|
||||
|
||||
// right (x+)
|
||||
ebo.emplace_back(4, 6, 7);
|
||||
ebo.emplace_back(4, 7, 5);
|
||||
|
||||
// bottom (y-)
|
||||
ebo.emplace_back(0, 4, 5);
|
||||
ebo.emplace_back(0, 5, 1);
|
||||
|
||||
// top (y+)
|
||||
ebo.emplace_back(2, 3, 7);
|
||||
ebo.emplace_back(2, 7, 6);
|
||||
|
||||
CubeMesh cube{std::move(vbo), std::move(ebo)};
|
||||
cube.set_origin({0.f, 0.f, 0.f});
|
||||
cube.set_scale({2.f, 2.f, 2.f});
|
||||
cube.set_rotation(RotationAngles{});
|
||||
|
||||
// ---------- OpenGL buffers ----------
|
||||
GLuint VAO = 0, VBO = 0, EBO_GL = 0;
|
||||
glGenVertexArrays(1, &VAO);
|
||||
glGenBuffers(1, &VBO);
|
||||
glGenBuffers(1, &EBO_GL);
|
||||
|
||||
glBindVertexArray(VAO);
|
||||
|
||||
// upload vertex buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, cube.m_vertex_buffer.size() * sizeof(VertexType), cube.m_vertex_buffer.data(),
|
||||
GL_STATIC_DRAW);
|
||||
|
||||
// flatten EBO to GL indices
|
||||
std::vector<GLuint> flatIndices;
|
||||
flatIndices.reserve(cube.m_element_buffer_object.size() * 3);
|
||||
for (const auto& tri : cube.m_element_buffer_object)
|
||||
{
|
||||
flatIndices.push_back(tri.x);
|
||||
flatIndices.push_back(tri.y);
|
||||
flatIndices.push_back(tri.z);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO_GL);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, flatIndices.size() * sizeof(GLuint), flatIndices.data(), GL_STATIC_DRAW);
|
||||
|
||||
// vertex layout: position / normal / uv (each Vector3<float>)
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, position));
|
||||
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, normal));
|
||||
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, uv));
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
// ---------- Camera setup ----------
|
||||
omath::projection::ViewPort viewPort{static_cast<float>(SCR_WIDTH), static_cast<float>(SCR_HEIGHT)};
|
||||
|
||||
Vector3<float> camPos{0.f, 0.f, 3.f};
|
||||
|
||||
float nearPlane = 0.1f;
|
||||
float farPlane = 100.f;
|
||||
auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
|
||||
MyCamera camera{camPos, {}, viewPort, fov, nearPlane, farPlane};
|
||||
|
||||
// ---------- Shader ----------
|
||||
GLuint shaderProgram = createShaderProgram();
|
||||
GLint uMvpLoc = glGetUniformLocation(shaderProgram, "uMVP");
|
||||
GLint uModel = glGetUniformLocation(shaderProgram, "uModel");
|
||||
|
||||
static float old_frame_time = glfwGetTime();
|
||||
|
||||
// ---------- Main loop ----------
|
||||
while (!glfwWindowShouldClose(window))
|
||||
{
|
||||
glfwPollEvents();
|
||||
|
||||
float currentTime = glfwGetTime();
|
||||
float deltaTime = currentTime - old_frame_time;
|
||||
old_frame_time = currentTime;
|
||||
|
||||
int fbW = 0, fbH = 0;
|
||||
glfwGetFramebufferSize(window, &fbW, &fbH);
|
||||
glViewport(0, 0, fbW, fbH);
|
||||
|
||||
viewPort.m_width = static_cast<float>(fbW);
|
||||
viewPort.m_height = static_cast<float>(fbH);
|
||||
camera.set_view_port(viewPort);
|
||||
|
||||
glClearColor(0.1f, 0.15f, 0.2f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
RotationAngles rot = cube.get_rotation_angles();
|
||||
rot.yaw += omath::opengl_engine::YawAngle ::from_degrees(40.f * deltaTime);
|
||||
rot.roll += omath::opengl_engine::RollAngle::from_degrees(40.f * deltaTime);
|
||||
|
||||
if (rot.pitch.as_degrees() == 90.f)
|
||||
rot.pitch = omath::opengl_engine::PitchAngle::from_degrees(-90.f);
|
||||
rot.pitch += omath::opengl_engine::PitchAngle::from_degrees(40.f * deltaTime);
|
||||
cube.set_rotation(rot);
|
||||
|
||||
const Mat4x4& viewProj = camera.get_view_projection_matrix();
|
||||
const auto& model = cube.get_to_world_matrix();
|
||||
|
||||
glUseProgram(shaderProgram);
|
||||
|
||||
// Send matrices to GPU
|
||||
const float* mvpPtr = viewProj.raw_array().data();
|
||||
const float* modelPtr = model.raw_array().data();
|
||||
|
||||
glUniformMatrix4fv(uMvpLoc, 1, GL_FALSE, mvpPtr);
|
||||
glUniformMatrix4fv(uModel, 1, GL_FALSE, modelPtr);
|
||||
|
||||
glBindVertexArray(VAO);
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(flatIndices.size()), GL_UNSIGNED_INT, nullptr);
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
// ---------- Cleanup ----------
|
||||
glDeleteVertexArrays(1, &VAO);
|
||||
glDeleteBuffers(1, &VBO);
|
||||
glDeleteBuffers(1, &EBO_GL);
|
||||
glDeleteProgram(shaderProgram);
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
return 0;
|
||||
}
|
||||
39
examples/example_signature_scan.cpp
Normal file
39
examples/example_signature_scan.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Created by Vlad on 11/8/2025.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
#include <print>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::println("OMATH Signature Scanner");
|
||||
|
||||
std::print("Enter path to PE file: ");
|
||||
std::string file_path;
|
||||
std::getline(std::cin, file_path); // allows spaces
|
||||
|
||||
std::print("Enter target section: ");
|
||||
std::string section;
|
||||
std::getline(std::cin, section);
|
||||
|
||||
std::print("Enter signature: ");
|
||||
std::string signature;
|
||||
std::getline(std::cin, signature);
|
||||
|
||||
std::println("[LOG] Performing scan....");
|
||||
|
||||
const auto result = omath::PePatternScanner::scan_for_pattern_in_file(file_path, signature, section);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
std::println("[ERROR] Scan failed or signature was not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::println("Found at virtual 0x{:x} , raw 0x{:x}", result->virtual_base_addr + result->target_offset,
|
||||
result->raw_base_addr + result->target_offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -3,14 +3,60 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/engines/opengl_engine/camera.hpp"
|
||||
#include "omath/engines/opengl_engine/traits/mesh_trait.hpp"
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
|
||||
namespace omath::primitives
|
||||
{
|
||||
template<class BoxMeshType>
|
||||
[[nodiscard]]
|
||||
std::array<Triangle<Vector3<float>>, 12> create_box(const Vector3<float>& top, const Vector3<float>& bottom,
|
||||
const Vector3<float>& dir_forward, const Vector3<float>& dir_right,
|
||||
float ratio = 4.f) noexcept;
|
||||
}
|
||||
BoxMeshType create_box(const Vector3<float>& top, const Vector3<float>& bottom, const Vector3<float>& dir_forward,
|
||||
const Vector3<float>& dir_right, const float ratio = 4.f) noexcept
|
||||
{
|
||||
const auto height = top.distance_to(bottom);
|
||||
const auto side_size = height / ratio;
|
||||
|
||||
// corner layout (0‑3 bottom, 4‑7 top)
|
||||
std::array<Vector3<float>, 8> p;
|
||||
p[0] = bottom + (dir_forward + dir_right) * side_size; // front‑right‑bottom
|
||||
p[1] = bottom + (dir_forward - dir_right) * side_size; // front‑left‑bottom
|
||||
p[2] = bottom + (-dir_forward + dir_right) * side_size; // back‑right‑bottom
|
||||
p[3] = bottom + (-dir_forward - dir_right) * side_size; // back‑left‑bottom
|
||||
p[4] = top + (dir_forward + dir_right) * side_size; // front‑right‑top
|
||||
p[5] = top + (dir_forward - dir_right) * side_size; // front‑left‑top
|
||||
p[6] = top + (-dir_forward + dir_right) * side_size; // back‑right‑top
|
||||
p[7] = top + (-dir_forward - dir_right) * side_size; // back‑left‑top
|
||||
|
||||
std::array<Vector3<std::uint32_t>, 12> poly;
|
||||
|
||||
// bottom face (+Y up ⇒ wind CW when viewed from above)
|
||||
poly[0] = {0, 2, 3};
|
||||
poly[1] = {0, 3, 1};
|
||||
|
||||
// top face
|
||||
poly[2] = {4, 7, 6};
|
||||
poly[3] = {4, 5, 7};
|
||||
|
||||
// front face
|
||||
poly[4] = {0, 5, 1};
|
||||
poly[5] = {0, 4, 5};
|
||||
|
||||
// right face
|
||||
poly[6] = {0, 6, 2};
|
||||
poly[7] = {0, 4, 6};
|
||||
|
||||
// back face
|
||||
poly[8] = {2, 7, 3};
|
||||
poly[9] = {2, 6, 7};
|
||||
|
||||
// left face
|
||||
poly[10] = {1, 7, 5};
|
||||
poly[11] = {1, 3, 7};
|
||||
|
||||
return BoxMeshType{std::move(p), std::move(poly)};
|
||||
}
|
||||
} // namespace omath::primitives
|
||||
|
||||
134
include/omath/3d_primitives/mesh.hpp
Normal file
134
include/omath/3d_primitives/mesh.hpp
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include <omath/linear_algebra/mat.hpp>
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace omath::primitives
|
||||
{
|
||||
template<class VecType = Vector3<float>, class UvT = Vector2<float>>
|
||||
struct Vertex final
|
||||
{
|
||||
using VectorType = VecType;
|
||||
using UvType = UvT;
|
||||
VectorType position;
|
||||
VectorType normal;
|
||||
UvType uv;
|
||||
};
|
||||
|
||||
template<typename T> concept HasPosition = requires(T vertex) { vertex.position; };
|
||||
template<typename T> concept HasNormal = requires(T vertex) { vertex.normal; };
|
||||
template<typename T> concept HasUv = requires(T vertex) { vertex.uv; };
|
||||
|
||||
template<class Mat4X4, class RotationAngles, class MeshTypeTrait, class VertType = Vertex<>,
|
||||
class VboType = std::vector<VertType>, class EboType = std::vector<Vector3<std::uint32_t>>>
|
||||
class Mesh final
|
||||
{
|
||||
public:
|
||||
using VectorType = VertType::VectorType;
|
||||
using VertexType = VboType::value_type;
|
||||
|
||||
private:
|
||||
using Vbo = VboType;
|
||||
using Ebo = EboType;
|
||||
|
||||
public:
|
||||
Vbo m_vertex_buffer;
|
||||
Ebo m_element_buffer_object;
|
||||
|
||||
Mesh(Vbo vbo, Ebo vao,
|
||||
const VectorType scale =
|
||||
{
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
})
|
||||
: m_vertex_buffer(std::move(vbo)), m_element_buffer_object(std::move(vao)), m_scale(std::move(scale))
|
||||
{
|
||||
}
|
||||
void set_origin(const VectorType& new_origin)
|
||||
{
|
||||
m_origin = new_origin;
|
||||
m_to_world_matrix = std::nullopt;
|
||||
}
|
||||
|
||||
void set_scale(const VectorType& new_scale)
|
||||
{
|
||||
m_scale = new_scale;
|
||||
m_to_world_matrix = std::nullopt;
|
||||
}
|
||||
|
||||
void set_rotation(const RotationAngles& new_rotation_angles)
|
||||
{
|
||||
m_rotation_angles = new_rotation_angles;
|
||||
m_to_world_matrix = std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const VectorType& get_origin() const
|
||||
{
|
||||
return m_origin;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const VectorType& get_scale() const
|
||||
{
|
||||
return m_scale;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const RotationAngles& get_rotation_angles() const
|
||||
{
|
||||
return m_rotation_angles;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const Mat4X4& get_to_world_matrix() const
|
||||
{
|
||||
if (m_to_world_matrix)
|
||||
return m_to_world_matrix.value();
|
||||
m_to_world_matrix = mat_translation<float, Mat4X4::get_store_ordering()>(m_origin)
|
||||
* MeshTypeTrait::rotation_matrix(m_rotation_angles)
|
||||
* mat_scale<float, Mat4X4::get_store_ordering()>(m_scale);
|
||||
|
||||
return m_to_world_matrix.value();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
VectorType vertex_position_to_world_space(const Vector3<float>& vertex_position) const
|
||||
{
|
||||
auto abs_vec = get_to_world_matrix()
|
||||
* mat_column_from_vector<typename Mat4X4::ContainedType, Mat4X4::get_store_ordering()>(
|
||||
vertex_position);
|
||||
|
||||
return {abs_vec.at(0, 0), abs_vec.at(1, 0), abs_vec.at(2, 0)};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Triangle<VectorType> make_face_in_world_space(const Ebo::const_iterator vao_iterator) const
|
||||
{
|
||||
if constexpr (HasPosition<VertexType>)
|
||||
{
|
||||
return {vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->x).position),
|
||||
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->y).position),
|
||||
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->z).position)};
|
||||
}
|
||||
return {vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->x)),
|
||||
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->y)),
|
||||
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->z))};
|
||||
}
|
||||
|
||||
private:
|
||||
VectorType m_origin;
|
||||
VectorType m_scale;
|
||||
|
||||
RotationAngles m_rotation_angles;
|
||||
|
||||
mutable std::optional<Mat4X4> m_to_world_matrix;
|
||||
};
|
||||
} // namespace omath::primitives
|
||||
@@ -3,14 +3,30 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/engines/opengl_engine/camera.hpp"
|
||||
#include "omath/engines/opengl_engine/mesh.hpp"
|
||||
#include "omath/engines/opengl_engine/traits/mesh_trait.hpp"
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
|
||||
namespace omath::primitives
|
||||
{
|
||||
template<class PlaneMeshType>
|
||||
[[nodiscard]]
|
||||
std::array<Triangle<Vector3<float>>, 2> create_plane(const Vector3<float>& vertex_a,
|
||||
const Vector3<float>& vertex_b,
|
||||
const Vector3<float>& direction, float size) noexcept;
|
||||
}
|
||||
PlaneMeshType create_plane(const Vector3<float>& vertex_a, const Vector3<float>& vertex_b,
|
||||
const Vector3<float>& direction, const float size) noexcept
|
||||
{
|
||||
const auto second_vertex_a = vertex_a + direction * size;
|
||||
const auto second_vertex_b = vertex_b + direction * size;
|
||||
|
||||
std::array<Vector3<float>, 4> grid = {vertex_a, vertex_b, second_vertex_a, second_vertex_b};
|
||||
|
||||
std::array<Vector3<std::uint32_t>, 2> poly;
|
||||
poly[0] = {1, 1, 2};
|
||||
poly[1] = {0, 1, 3};
|
||||
|
||||
return PlaneMeshType(std::move(grid), std::move(poly));
|
||||
}
|
||||
} // namespace omath::primitives
|
||||
|
||||
23
include/omath/collision/collider_interface.hpp
Normal file
23
include/omath/collision/collider_interface.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by Vladislav on 06.12.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
template<class VecType = Vector3<float>>
|
||||
class ColliderInterface
|
||||
{
|
||||
public:
|
||||
using VectorType = VecType;
|
||||
virtual ~ColliderInterface() = default;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual VectorType find_abs_furthest_vertex_position(const VectorType& direction) const = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual const VectorType& get_origin() const = 0;
|
||||
virtual void set_origin(const VectorType& new_origin) = 0;
|
||||
};
|
||||
}
|
||||
303
include/omath/collision/epa_algorithm.hpp
Normal file
303
include/omath/collision/epa_algorithm.hpp
Normal file
@@ -0,0 +1,303 @@
|
||||
#pragma once
|
||||
#include "simplex.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
template<class V>
|
||||
concept EpaVector = requires(const V& a, const V& b, float s) {
|
||||
{ a - b } -> std::same_as<V>;
|
||||
{ a.cross(b) } -> std::same_as<V>;
|
||||
{ a.dot(b) } -> std::same_as<float>;
|
||||
{ -a } -> std::same_as<V>;
|
||||
{ a * s } -> std::same_as<V>;
|
||||
{ a / s } -> std::same_as<V>;
|
||||
};
|
||||
|
||||
template<class ColliderInterfaceType>
|
||||
class Epa final
|
||||
{
|
||||
public:
|
||||
using VectorType = ColliderInterfaceType::VectorType;
|
||||
static_assert(EpaVector<VectorType>, "VertexType must satisfy EpaVector concept");
|
||||
|
||||
struct Result final
|
||||
{
|
||||
VectorType normal{}; // from A to B
|
||||
VectorType penetration_vector;
|
||||
float depth{0.0f};
|
||||
int iterations{0};
|
||||
int num_vertices{0};
|
||||
int num_faces{0};
|
||||
};
|
||||
|
||||
struct Params final
|
||||
{
|
||||
int max_iterations{64};
|
||||
float tolerance{1e-4f}; // absolute tolerance on distance growth
|
||||
};
|
||||
// Precondition: simplex.size()==4 and contains the origin.
|
||||
[[nodiscard]]
|
||||
static std::optional<Result> solve(const ColliderInterfaceType& a, const ColliderInterfaceType& b,
|
||||
const Simplex<VectorType>& simplex, const Params params = {},
|
||||
std::pmr::memory_resource& mem_resource = *std::pmr::get_default_resource())
|
||||
{
|
||||
// --- Build initial polytope from simplex (4 points) ---
|
||||
std::pmr::vector<VectorType> vertexes = build_initial_polytope_from_simplex(simplex, mem_resource);
|
||||
|
||||
// Initial tetra faces (windings corrected in make_face)
|
||||
std::pmr::vector<Face> faces = create_initial_tetra_faces(mem_resource, vertexes);
|
||||
|
||||
auto heap = rebuild_heap(faces, mem_resource);
|
||||
|
||||
Result out{};
|
||||
|
||||
for (int it = 0; it < params.max_iterations; ++it)
|
||||
{
|
||||
// If heap might be stale after face edits, rebuild lazily.
|
||||
if (heap.empty())
|
||||
break;
|
||||
// Rebuild when the "closest" face changed (simple cheap guard)
|
||||
// (We could keep face handles; this is fine for small Ns.)
|
||||
|
||||
if (const auto top = heap.top(); faces[top.idx].d != top.d)
|
||||
heap = rebuild_heap(faces, mem_resource);
|
||||
|
||||
if (heap.empty())
|
||||
break;
|
||||
|
||||
//FIXME: STORE REF VALUE, DO NOT USE
|
||||
// AFTER IF STATEMENT BLOCK
|
||||
const Face& face = faces[heap.top().idx];
|
||||
|
||||
// Get the furthest point in face normal direction
|
||||
const VectorType p = support_point(a, b, face.n);
|
||||
const float p_dist = face.n.dot(p);
|
||||
|
||||
// Converged if we can’t push the face closer than tolerance
|
||||
if (p_dist - face.d <= params.tolerance)
|
||||
{
|
||||
out.normal = face.n;
|
||||
out.depth = face.d; // along unit normal
|
||||
out.iterations = it + 1;
|
||||
out.num_vertices = static_cast<int>(vertexes.size());
|
||||
out.num_faces = static_cast<int>(faces.size());
|
||||
|
||||
out.penetration_vector = out.normal * out.depth;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Add new vertex
|
||||
const int new_idx = static_cast<int>(vertexes.size());
|
||||
vertexes.emplace_back(p);
|
||||
|
||||
const auto [to_delete, boundary] = mark_visible_and_collect_horizon(faces, p);
|
||||
|
||||
erase_marked(faces, to_delete);
|
||||
|
||||
// Stitch new faces around the horizon
|
||||
for (const auto& e : boundary)
|
||||
faces.emplace_back(make_face(vertexes, e.a, e.b, new_idx));
|
||||
|
||||
// Rebuild heap after topology change
|
||||
heap = rebuild_heap(faces, mem_resource);
|
||||
|
||||
if (!std::isfinite(vertexes.back().dot(vertexes.back())))
|
||||
break; // safety
|
||||
out.iterations = it + 1;
|
||||
}
|
||||
|
||||
if (faces.empty())
|
||||
return std::nullopt;
|
||||
|
||||
const auto best = *std::ranges::min_element(faces, [](const auto& first, const auto& second)
|
||||
{ return first.d < second.d; });
|
||||
out.normal = best.n;
|
||||
out.depth = best.d;
|
||||
out.num_vertices = static_cast<int>(vertexes.size());
|
||||
out.num_faces = static_cast<int>(faces.size());
|
||||
|
||||
out.penetration_vector = out.normal * out.depth;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Face final
|
||||
{
|
||||
int i0, i1, i2;
|
||||
VectorType n; // unit outward normal
|
||||
float d; // n · v0 (>=0 ideally because origin is inside)
|
||||
};
|
||||
|
||||
struct Edge final
|
||||
{
|
||||
int a, b;
|
||||
};
|
||||
|
||||
struct HeapItem final
|
||||
{
|
||||
float d;
|
||||
int idx;
|
||||
};
|
||||
struct HeapCmp final
|
||||
{
|
||||
[[nodiscard]]
|
||||
static bool operator()(const HeapItem& lhs, const HeapItem& rhs) noexcept
|
||||
{
|
||||
return lhs.d > rhs.d; // min-heap by distance
|
||||
}
|
||||
};
|
||||
|
||||
using Heap = std::priority_queue<HeapItem, std::pmr::vector<HeapItem>, HeapCmp>;
|
||||
|
||||
[[nodiscard]]
|
||||
static Heap rebuild_heap(const std::pmr::vector<Face>& faces, auto& memory_resource)
|
||||
{
|
||||
std::pmr::vector<HeapItem> storage{&memory_resource};
|
||||
storage.reserve(faces.size()); // optional but recommended
|
||||
|
||||
Heap h{HeapCmp{}, std::move(storage)};
|
||||
|
||||
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
|
||||
h.emplace(faces[i].d, i);
|
||||
|
||||
return h; // allocator is preserved
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static bool visible_from(const Face& f, const VectorType& p)
|
||||
{
|
||||
// positive if p is in front of the face
|
||||
return f.n.dot(p) - f.d > 1e-7f;
|
||||
}
|
||||
|
||||
static void add_edge_boundary(std::pmr::vector<Edge>& boundary, int a, int b)
|
||||
{
|
||||
// Keep edges that appear only once; erase if opposite already present
|
||||
auto itb = std::ranges::find_if(boundary, [&](const Edge& e) { return e.a == b && e.b == a; });
|
||||
if (itb != boundary.end())
|
||||
boundary.erase(itb); // internal edge cancels out
|
||||
else
|
||||
boundary.emplace_back(a, b); // horizon edge (directed)
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static Face make_face(const std::pmr::vector<VectorType>& vertexes, int i0, int i1, int i2)
|
||||
{
|
||||
const VectorType& a0 = vertexes[i0];
|
||||
const VectorType& a1 = vertexes[i1];
|
||||
const VectorType& a2 = vertexes[i2];
|
||||
VectorType n = (a1 - a0).cross(a2 - a0);
|
||||
if (n.dot(n) <= 1e-30f)
|
||||
{
|
||||
n = any_perp_vec(a1 - a0); // degenerate guard
|
||||
}
|
||||
// Ensure normal points outward (away from origin): require n·a0 >= 0
|
||||
if (n.dot(a0) < 0.0f)
|
||||
{
|
||||
std::swap(i1, i2);
|
||||
n = -n;
|
||||
}
|
||||
const float inv_len = 1.0f / std::sqrt(std::max(n.dot(n), 1e-30f));
|
||||
n = n * inv_len;
|
||||
const float d = n.dot(a0);
|
||||
return {i0, i1, i2, n, d};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static VectorType support_point(const ColliderInterfaceType& a, const ColliderInterfaceType& b,
|
||||
const VectorType& dir)
|
||||
{
|
||||
return a.find_abs_furthest_vertex_position(dir) - b.find_abs_furthest_vertex_position(-dir);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
[[nodiscard]]
|
||||
static constexpr bool near_zero_vec(const V& v, const float eps = 1e-7f)
|
||||
{
|
||||
return v.dot(v) <= eps * eps;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
[[nodiscard]]
|
||||
static constexpr V any_perp_vec(const V& v)
|
||||
{
|
||||
for (const auto& dir : {V{1, 0, 0}, V{0, 1, 0}, V{0, 0, 1}})
|
||||
if (const auto d = v.cross(dir); !near_zero_vec(d))
|
||||
return d;
|
||||
return V{1, 0, 0};
|
||||
}
|
||||
[[nodiscard]]
|
||||
static std::pmr::vector<Face> create_initial_tetra_faces(std::pmr::memory_resource& mem_resource,
|
||||
const std::pmr::vector<VectorType>& vertexes)
|
||||
{
|
||||
std::pmr::vector<Face> faces{&mem_resource};
|
||||
faces.reserve(4);
|
||||
faces.emplace_back(make_face(vertexes, 0, 1, 2));
|
||||
faces.emplace_back(make_face(vertexes, 0, 2, 3));
|
||||
faces.emplace_back(make_face(vertexes, 0, 3, 1));
|
||||
faces.emplace_back(make_face(vertexes, 1, 3, 2));
|
||||
return faces;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::pmr::vector<VectorType> build_initial_polytope_from_simplex(const Simplex<VectorType>& simplex,
|
||||
std::pmr::memory_resource& mem_resource)
|
||||
{
|
||||
std::pmr::vector<VectorType> vertexes{&mem_resource};
|
||||
vertexes.reserve(simplex.size());
|
||||
|
||||
for (std::size_t i = 0; i < simplex.size(); ++i)
|
||||
vertexes.emplace_back(simplex[i]);
|
||||
|
||||
return vertexes;
|
||||
}
|
||||
static void erase_marked(std::pmr::vector<Face>& faces, const std::pmr::vector<bool>& to_delete)
|
||||
{
|
||||
auto* mr = faces.get_allocator().resource(); // keep same resource
|
||||
std::pmr::vector<Face> kept{mr};
|
||||
kept.reserve(faces.size());
|
||||
|
||||
for (std::size_t i = 0; i < faces.size(); ++i)
|
||||
if (!to_delete[i])
|
||||
kept.emplace_back(faces[i]);
|
||||
|
||||
faces.swap(kept);
|
||||
}
|
||||
struct Horizon
|
||||
{
|
||||
std::pmr::vector<bool> to_delete;
|
||||
std::pmr::vector<Edge> boundary;
|
||||
};
|
||||
|
||||
static Horizon mark_visible_and_collect_horizon(const std::pmr::vector<Face>& faces, const VectorType& p)
|
||||
{
|
||||
auto* mr = faces.get_allocator().resource();
|
||||
|
||||
Horizon horizon{std::pmr::vector<bool>(faces.size(), false, mr), std::pmr::vector<Edge>(mr)};
|
||||
horizon.boundary.reserve(faces.size());
|
||||
|
||||
for (std::size_t i = 0; i < faces.size(); ++i)
|
||||
if (visible_from(faces[i], p))
|
||||
{
|
||||
const auto& rf = faces[i];
|
||||
horizon.to_delete[i] = true;
|
||||
add_edge_boundary(horizon.boundary, rf.i0, rf.i1);
|
||||
add_edge_boundary(horizon.boundary, rf.i1, rf.i2);
|
||||
add_edge_boundary(horizon.boundary, rf.i2, rf.i0);
|
||||
}
|
||||
|
||||
return horizon;
|
||||
}
|
||||
};
|
||||
} // namespace omath::collision
|
||||
62
include/omath/collision/gjk_algorithm.hpp
Normal file
62
include/omath/collision/gjk_algorithm.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Created by Vlad on 11/9/2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "simplex.hpp"
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
template<class VertexType>
|
||||
struct GjkHitInfo final
|
||||
{
|
||||
bool hit{false};
|
||||
Simplex<VertexType> simplex; // valid only if hit == true and size==4
|
||||
};
|
||||
|
||||
template<class ColliderInterfaceType>
|
||||
class GjkAlgorithm final
|
||||
{
|
||||
using VectorType = ColliderInterfaceType::VectorType;
|
||||
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static VectorType find_support_vertex(const ColliderInterfaceType& collider_a,
|
||||
const ColliderInterfaceType& collider_b, const VectorType& direction)
|
||||
{
|
||||
return collider_a.find_abs_furthest_vertex_position(direction)
|
||||
- collider_b.find_abs_furthest_vertex_position(-direction);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static bool is_collide(const ColliderInterfaceType& collider_a, const ColliderInterfaceType& collider_b)
|
||||
{
|
||||
return is_collide_with_simplex_info(collider_a, collider_b).hit;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static GjkHitInfo<VectorType> is_collide_with_simplex_info(const ColliderInterfaceType& collider_a,
|
||||
const ColliderInterfaceType& collider_b)
|
||||
{
|
||||
auto support = find_support_vertex(collider_a, collider_b, VectorType{1, 0, 0});
|
||||
|
||||
Simplex<VectorType> simplex;
|
||||
simplex.push_front(support);
|
||||
|
||||
auto direction = -support;
|
||||
|
||||
while (true)
|
||||
{
|
||||
support = find_support_vertex(collider_a, collider_b, direction);
|
||||
|
||||
if (support.dot(direction) <= 0.f)
|
||||
return {false, simplex};
|
||||
|
||||
simplex.push_front(support);
|
||||
|
||||
if (simplex.handle(direction))
|
||||
return {true, simplex};
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace omath::collision
|
||||
@@ -33,5 +33,25 @@ namespace omath::collision
|
||||
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
|
||||
[[nodiscard]]
|
||||
static Vector3<float> get_ray_hit_point(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept;
|
||||
|
||||
template<class MeshType>
|
||||
[[nodiscard]]
|
||||
static Vector3<float> get_ray_hit_point(const Ray& ray, const MeshType& mesh) noexcept
|
||||
{
|
||||
Vector3<float> mesh_hit = ray.end;
|
||||
|
||||
auto begin = mesh.m_element_buffer_object.cbegin();
|
||||
auto end = mesh.m_element_buffer_object.cend();
|
||||
for (auto current = begin; current < end; current = std::next(current))
|
||||
{
|
||||
auto face = mesh.make_face_in_world_space(current);
|
||||
|
||||
auto ray_stop_point = get_ray_hit_point(ray, face);
|
||||
if (ray_stop_point.distance_to(ray.start) < mesh_hit.distance_to(ray.start))
|
||||
mesh_hit = ray_stop_point;
|
||||
}
|
||||
|
||||
return mesh_hit;
|
||||
}
|
||||
};
|
||||
} // namespace omath::collision
|
||||
|
||||
55
include/omath/collision/mesh_collider.hpp
Normal file
55
include/omath/collision/mesh_collider.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Created by Vlad on 11/9/2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "collider_interface.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
|
||||
#ifdef OMATH_BUILD_TESTS
|
||||
// ReSharper disable once CppInconsistentNaming
|
||||
class UnitTestColider_FindFurthestVertex_Test;
|
||||
#endif
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
template<class MeshType>
|
||||
class MeshCollider final : public ColliderInterface<typename MeshType::VertexType::VectorType>
|
||||
{
|
||||
#ifdef OMATH_BUILD_TESTS
|
||||
friend UnitTestColider_FindFurthestVertex_Test;
|
||||
#endif
|
||||
public:
|
||||
using VertexType = MeshType::VertexType;
|
||||
using VectorType = MeshType::VertexType::VectorType;
|
||||
explicit MeshCollider(MeshType mesh): m_mesh(std::move(mesh))
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
VectorType find_abs_furthest_vertex_position(const VectorType& direction) const override
|
||||
{
|
||||
return m_mesh.vertex_position_to_world_space(find_furthest_vertex(direction).position);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const VectorType& get_origin() const override
|
||||
{
|
||||
return m_mesh.get_origin();
|
||||
}
|
||||
void set_origin(const VectorType& new_origin) override
|
||||
{
|
||||
m_mesh.set_origin(new_origin);
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]]
|
||||
const VertexType& find_furthest_vertex(const VectorType& direction) const
|
||||
{
|
||||
return *std::ranges::max_element(
|
||||
m_mesh.m_vertex_buffer, [&direction](const auto& first, const auto& second)
|
||||
{ return first.position.dot(direction) < second.position.dot(direction); });
|
||||
}
|
||||
MeshType m_mesh;
|
||||
};
|
||||
} // namespace omath::collision
|
||||
260
include/omath/collision/simplex.hpp
Normal file
260
include/omath/collision/simplex.hpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
|
||||
// Minimal structural contract for the vector type used by GJK.
|
||||
template<class V>
|
||||
concept GjkVector = requires(const V& a, const V& b) {
|
||||
{ -a } -> std::same_as<V>;
|
||||
{ a - b } -> std::same_as<V>;
|
||||
{ a.cross(b) } -> std::same_as<V>;
|
||||
{ a.point_to_same_direction(b) } -> std::same_as<bool>;
|
||||
};
|
||||
|
||||
template<GjkVector VectorType = Vector3<float>>
|
||||
class Simplex final
|
||||
{
|
||||
std::array<VectorType, 4> m_points{};
|
||||
std::size_t m_size{0};
|
||||
|
||||
public:
|
||||
static constexpr std::size_t capacity = 4;
|
||||
|
||||
constexpr Simplex() = default;
|
||||
|
||||
constexpr Simplex& operator=(std::initializer_list<VectorType> list) noexcept
|
||||
{
|
||||
assert(list.size() <= capacity && "Simplex can have at most 4 points");
|
||||
m_size = 0;
|
||||
for (const auto& p : list)
|
||||
m_points[m_size++] = p;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr void push_front(const VectorType& p) noexcept
|
||||
{
|
||||
const std::size_t limit = (m_size < capacity) ? m_size : capacity - 1;
|
||||
for (std::size_t i = limit; i > 0; --i)
|
||||
m_points[i] = m_points[i - 1];
|
||||
m_points[0] = p;
|
||||
if (m_size < capacity)
|
||||
++m_size;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const VectorType& operator[](std::size_t i) const noexcept
|
||||
{
|
||||
return m_points[i];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr VectorType& operator[](std::size_t i) noexcept
|
||||
{
|
||||
return m_points[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::size_t size() const noexcept
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool empty() const noexcept
|
||||
{
|
||||
return m_size == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const VectorType& front() const noexcept
|
||||
{
|
||||
return m_points[0];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const VectorType& back() const noexcept
|
||||
{
|
||||
return m_points[m_size - 1];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const VectorType* data() const noexcept
|
||||
{
|
||||
return m_points.data();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto begin() const noexcept
|
||||
{
|
||||
return m_points.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto end() const noexcept
|
||||
{
|
||||
return m_points.begin() + m_size;
|
||||
}
|
||||
|
||||
constexpr void clear() noexcept
|
||||
{
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
// GJK step: updates simplex + next search direction.
|
||||
// Returns true iff the origin lies inside the tetrahedron.
|
||||
[[nodiscard]] constexpr bool handle(VectorType& direction) noexcept
|
||||
{
|
||||
switch (m_size)
|
||||
{
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
return handle_point(direction);
|
||||
case 2:
|
||||
return handle_line(direction);
|
||||
case 3:
|
||||
return handle_triangle(direction);
|
||||
case 4:
|
||||
return handle_tetrahedron(direction);
|
||||
default:
|
||||
std::unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] constexpr bool handle_point(VectorType& direction) noexcept
|
||||
{
|
||||
const auto& a = m_points[0];
|
||||
direction = -a;
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
[[nodiscard]]
|
||||
static constexpr bool near_zero(const V& v, const float eps = 1e-7f)
|
||||
{
|
||||
return v.dot(v) <= eps * eps;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
[[nodiscard]]
|
||||
static constexpr V any_perp(const V& v)
|
||||
{
|
||||
for (const auto& dir : {V{1, 0, 0}, {0, 1, 0}, {0, 0, 1}})
|
||||
if (const auto d = v.cross(dir); !near_zero(d))
|
||||
return d;
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool handle_line(VectorType& direction)
|
||||
{
|
||||
const auto& a = m_points[0];
|
||||
const auto& b = m_points[1];
|
||||
|
||||
const auto ab = b - a;
|
||||
const auto ao = -a;
|
||||
|
||||
if (ab.point_to_same_direction(ao))
|
||||
{
|
||||
// ReSharper disable once CppTooWideScopeInitStatement
|
||||
auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos
|
||||
if (near_zero(n))
|
||||
{
|
||||
// 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};
|
||||
direction = ao;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool handle_triangle(VectorType& direction) noexcept
|
||||
{
|
||||
const auto& a = m_points[0];
|
||||
const auto& b = m_points[1];
|
||||
const auto& c = m_points[2];
|
||||
|
||||
const auto ab = b - a;
|
||||
const auto ac = c - a;
|
||||
const auto ao = -a;
|
||||
|
||||
const auto abc = ab.cross(ac);
|
||||
|
||||
// Region AC
|
||||
if (abc.cross(ac).point_to_same_direction(ao))
|
||||
{
|
||||
if (ac.point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, c};
|
||||
direction = ac.cross(ao).cross(ac);
|
||||
return false;
|
||||
}
|
||||
*this = {a, b};
|
||||
return handle_line(direction);
|
||||
}
|
||||
|
||||
// Region AB
|
||||
if (ab.cross(abc).point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, b};
|
||||
return handle_line(direction);
|
||||
}
|
||||
|
||||
// Above or below triangle
|
||||
if (abc.point_to_same_direction(ao))
|
||||
{
|
||||
direction = abc;
|
||||
}
|
||||
else
|
||||
{
|
||||
*this = {a, c, b}; // flip winding
|
||||
direction = -abc;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool handle_tetrahedron(VectorType& direction) noexcept
|
||||
{
|
||||
const auto& a = m_points[0];
|
||||
const auto& b = m_points[1];
|
||||
const auto& c = m_points[2];
|
||||
const auto& d = m_points[3];
|
||||
|
||||
const auto ab = b - a;
|
||||
const auto ac = c - a;
|
||||
const auto ad = d - a;
|
||||
const auto ao = -a;
|
||||
|
||||
const auto abc = ab.cross(ac);
|
||||
const auto acd = ac.cross(ad);
|
||||
const auto adb = ad.cross(ab);
|
||||
|
||||
if (abc.point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, b, c};
|
||||
return handle_triangle(direction);
|
||||
}
|
||||
if (acd.point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, c, d};
|
||||
return handle_triangle(direction);
|
||||
}
|
||||
if (adb.point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, d, b};
|
||||
return handle_triangle(direction);
|
||||
}
|
||||
// Origin inside tetrahedron
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace omath::collision
|
||||
201
include/omath/containers/encrypted_variable.hpp
Normal file
201
include/omath/containers/encrypted_variable.hpp
Normal file
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// Created by Vladislav on 04.01.2026.
|
||||
//
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#ifdef OMATH_ENABLE_FORCE_INLINE
|
||||
#ifdef _MSC_VER
|
||||
#define OMATH_FORCE_INLINE __forceinline
|
||||
#else
|
||||
#define OMATH_FORCE_INLINE __attribute__((always_inline)) inline
|
||||
#endif
|
||||
#else
|
||||
#define OMATH_FORCE_INLINE
|
||||
#endif
|
||||
|
||||
namespace omath::detail
|
||||
{
|
||||
[[nodiscard]]
|
||||
consteval std::uint64_t fnv1a_64(const char* s)
|
||||
{
|
||||
std::uint64_t h = 14695981039346656037ull;
|
||||
while (*s)
|
||||
{
|
||||
h ^= static_cast<unsigned char>(*s++);
|
||||
h *= 1099511628211ull;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// SplitMix64 mixer (good quality for seeding / scrambling)
|
||||
[[nodiscard]]
|
||||
consteval std::uint64_t splitmix64(std::uint64_t x)
|
||||
{
|
||||
x += 0x9E3779B97F4A7C15ull;
|
||||
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ull;
|
||||
x = (x ^ (x >> 27)) * 0x94D049BB133111EBull;
|
||||
return x ^ (x >> 31);
|
||||
}
|
||||
|
||||
// Choose your policy:
|
||||
// - If you want reproducible builds, REMOVE __DATE__/__TIME__.
|
||||
// - If you want "different each build", keep them.
|
||||
[[nodiscard]]
|
||||
consteval std::uint64_t base_seed()
|
||||
{
|
||||
std::uint64_t h = 0;
|
||||
h ^= fnv1a_64(__FILE__);
|
||||
h ^= splitmix64(fnv1a_64(__DATE__));
|
||||
h ^= splitmix64(fnv1a_64(__TIME__));
|
||||
return splitmix64(h);
|
||||
}
|
||||
|
||||
// Produce a "random" 64-bit value for a given stream index (compile-time)
|
||||
template<std::uint64_t Stream>
|
||||
[[nodiscard]]
|
||||
consteval std::uint64_t rand_u64()
|
||||
{
|
||||
// Stream is usually __COUNTER__ so each call site differs
|
||||
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>
|
||||
[[nodiscard]]
|
||||
consteval std::int64_t rand_uint8_t()
|
||||
{
|
||||
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>();
|
||||
return static_cast<std::int64_t>(bounded_u64(r, span)) + Lo;
|
||||
}
|
||||
[[nodiscard]]
|
||||
consteval std::uint64_t rand_u64(const std::uint64_t seed, const std::uint64_t i)
|
||||
{
|
||||
return splitmix64(seed + 0xD1B54A32D192ED03ull * (i + 1ull));
|
||||
}
|
||||
|
||||
// Convert to int (uses low 32 bits; you can also use high bits if you prefer)
|
||||
[[nodiscard]]
|
||||
consteval std::uint8_t rand_uint8t(const std::uint64_t seed, const std::uint64_t i)
|
||||
{
|
||||
return static_cast<std::uint8_t>(rand_u64(seed, i)); // narrowing is fine/deterministic
|
||||
}
|
||||
template<std::size_t N, std::uint64_t Seed, std::size_t... I>
|
||||
[[nodiscard]]
|
||||
consteval std::array<std::uint8_t, N> make_array_impl(std::index_sequence<I...>)
|
||||
{
|
||||
return {rand_uint8t(Seed, static_cast<std::uint64_t>(I))...};
|
||||
}
|
||||
|
||||
template<std::size_t N, std::uint64_t Seed>
|
||||
[[nodiscard]]
|
||||
consteval std::array<std::uint8_t, N> make_array()
|
||||
{
|
||||
return make_array_impl<N, Seed>(std::make_index_sequence<N>{});
|
||||
}
|
||||
} // namespace omath::detail
|
||||
|
||||
namespace omath
|
||||
{
|
||||
template<class T>
|
||||
class VarAnchor;
|
||||
|
||||
template<class T, std::size_t key_size, std::array<std::uint8_t, key_size> key>
|
||||
class EncryptedVariable final
|
||||
{
|
||||
using value_type = std::remove_cvref_t<T>;
|
||||
|
||||
bool m_is_encrypted{};
|
||||
value_type m_data{};
|
||||
|
||||
OMATH_FORCE_INLINE constexpr void xor_contained_var_by_key()
|
||||
{
|
||||
// Safe, keeps const-correctness, and avoids reinterpret_cast issues
|
||||
auto bytes = std::as_writable_bytes(std::span<value_type, 1>{&m_data, 1});
|
||||
|
||||
for (std::size_t i = 0; i < bytes.size(); ++i)
|
||||
{
|
||||
const std::uint8_t k = static_cast<std::uint8_t>(key[i % key_size] + (i * key_size));
|
||||
bytes[i] ^= static_cast<std::byte>(k);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
OMATH_FORCE_INLINE constexpr explicit EncryptedVariable(const value_type& data)
|
||||
: m_is_encrypted(false), m_data(data)
|
||||
{
|
||||
encrypt();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool is_encrypted() const
|
||||
{
|
||||
return m_is_encrypted;
|
||||
}
|
||||
|
||||
OMATH_FORCE_INLINE constexpr void decrypt()
|
||||
{
|
||||
if (!m_is_encrypted)
|
||||
return;
|
||||
xor_contained_var_by_key();
|
||||
m_is_encrypted = false;
|
||||
}
|
||||
|
||||
OMATH_FORCE_INLINE constexpr void encrypt()
|
||||
{
|
||||
if (m_is_encrypted)
|
||||
return;
|
||||
xor_contained_var_by_key();
|
||||
m_is_encrypted = true;
|
||||
}
|
||||
|
||||
[[nodiscard]] OMATH_FORCE_INLINE constexpr value_type& value()
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
[[nodiscard]] OMATH_FORCE_INLINE constexpr const value_type& value() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
constexpr OMATH_FORCE_INLINE ~EncryptedVariable()
|
||||
{
|
||||
decrypt();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr OMATH_FORCE_INLINE auto drop_anchor()
|
||||
{
|
||||
return VarAnchor{*this};
|
||||
}
|
||||
};
|
||||
|
||||
template<class EncryptedVarType>
|
||||
class VarAnchor final
|
||||
{
|
||||
public:
|
||||
// ReSharper disable once CppNonExplicitConvertingConstructor
|
||||
OMATH_FORCE_INLINE constexpr VarAnchor(EncryptedVarType& var): m_var(var)
|
||||
{
|
||||
m_var.decrypt();
|
||||
}
|
||||
OMATH_FORCE_INLINE constexpr ~VarAnchor()
|
||||
{
|
||||
m_var.encrypt();
|
||||
}
|
||||
|
||||
private:
|
||||
EncryptedVarType& m_var;
|
||||
};
|
||||
} // namespace omath
|
||||
|
||||
#define OMATH_CT_RAND_ARRAY_BYTE(N) \
|
||||
(::omath::detail::make_array<(N), (::omath::detail::base_seed() ^ static_cast<std::uint64_t>(__COUNTER__))>())
|
||||
#define OMATH_DEF_CRYPT_VAR(TYPE, KEY_SIZE) omath::EncryptedVariable<TYPE, KEY_SIZE, OMATH_CT_RAND_ARRAY_BYTE(KEY_SIZE)>
|
||||
12
include/omath/engines/frostbite_engine/mesh.hpp
Normal file
12
include/omath/engines/frostbite_engine/mesh.hpp
Normal 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::frostbite_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
}
|
||||
17
include/omath/engines/frostbite_engine/primitives.hpp
Normal file
17
include/omath/engines/frostbite_engine/primitives.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by Vladislav on 27.01.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/engines/frostbite_engine/traits/mesh_trait.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
|
||||
std::array<Vector3<std::uint32_t>, 12>>;
|
||||
|
||||
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
|
||||
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
|
||||
} // namespace omath::frostbite_engine
|
||||
19
include/omath/engines/frostbite_engine/traits/mesh_trait.hpp
Normal file
19
include/omath/engines/frostbite_engine/traits/mesh_trait.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include <omath/engines/frostbite_engine/constants.hpp>
|
||||
#include <omath/engines/frostbite_engine/formulas.hpp>
|
||||
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
class MeshTrait final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
|
||||
{
|
||||
return frostbite_engine::rotation_matrix(rotation);
|
||||
}
|
||||
};
|
||||
} // namespace omath::frostbite_engine
|
||||
12
include/omath/engines/iw_engine/mesh.hpp
Normal file
12
include/omath/engines/iw_engine/mesh.hpp
Normal 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::iw_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
}
|
||||
17
include/omath/engines/iw_engine/primitives.hpp
Normal file
17
include/omath/engines/iw_engine/primitives.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by Vladislav on 27.01.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/engines/iw_engine/traits/mesh_trait.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
namespace omath::iw_engine
|
||||
{
|
||||
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
|
||||
std::array<Vector3<std::uint32_t>, 12>>;
|
||||
|
||||
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
|
||||
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
|
||||
} // namespace omath::iw_engine
|
||||
19
include/omath/engines/iw_engine/traits/mesh_trait.hpp
Normal file
19
include/omath/engines/iw_engine/traits/mesh_trait.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include <omath/engines/iw_engine/constants.hpp>
|
||||
#include <omath/engines/iw_engine/formulas.hpp>
|
||||
|
||||
namespace omath::iw_engine
|
||||
{
|
||||
class MeshTrait final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
|
||||
{
|
||||
return iw_engine::rotation_matrix(rotation);
|
||||
}
|
||||
};
|
||||
} // namespace omath::iw_engine
|
||||
12
include/omath/engines/opengl_engine/mesh.hpp
Normal file
12
include/omath/engines/opengl_engine/mesh.hpp
Normal 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::opengl_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
}
|
||||
17
include/omath/engines/opengl_engine/primitives.hpp
Normal file
17
include/omath/engines/opengl_engine/primitives.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by Vladislav on 27.01.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/engines/opengl_engine/traits/mesh_trait.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
namespace omath::opengl_engine
|
||||
{
|
||||
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
|
||||
std::array<Vector3<std::uint32_t>, 12>>;
|
||||
|
||||
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
|
||||
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
|
||||
} // namespace omath::opengl_engine
|
||||
19
include/omath/engines/opengl_engine/traits/mesh_trait.hpp
Normal file
19
include/omath/engines/opengl_engine/traits/mesh_trait.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include <omath/engines/opengl_engine/constants.hpp>
|
||||
#include <omath/engines/opengl_engine/formulas.hpp>
|
||||
|
||||
namespace omath::opengl_engine
|
||||
{
|
||||
class MeshTrait final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
|
||||
{
|
||||
return opengl_engine::rotation_matrix(rotation);
|
||||
}
|
||||
};
|
||||
} // namespace omath::opengl_engine
|
||||
13
include/omath/engines/source_engine/collider.hpp
Normal file
13
include/omath/engines/source_engine/collider.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/3d_primitives/mesh.hpp"
|
||||
#include "omath/collision/mesh_collider.hpp"
|
||||
|
||||
namespace omath::source_engine
|
||||
{
|
||||
using MeshCollider = collision::MeshCollider<Mesh>;
|
||||
}
|
||||
12
include/omath/engines/source_engine/mesh.hpp
Normal file
12
include/omath/engines/source_engine/mesh.hpp
Normal 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::source_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
}
|
||||
17
include/omath/engines/source_engine/primitives.hpp
Normal file
17
include/omath/engines/source_engine/primitives.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by Vladislav on 27.01.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/engines/source_engine/traits/mesh_trait.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
namespace omath::source_engine
|
||||
{
|
||||
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
|
||||
std::array<Vector3<std::uint32_t>, 12>>;
|
||||
|
||||
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
|
||||
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
|
||||
} // namespace omath::source_engine
|
||||
19
include/omath/engines/source_engine/traits/mesh_trait.hpp
Normal file
19
include/omath/engines/source_engine/traits/mesh_trait.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include <omath/engines/source_engine/constants.hpp>
|
||||
#include <omath/engines/source_engine/formulas.hpp>
|
||||
|
||||
namespace omath::source_engine
|
||||
{
|
||||
class MeshTrait final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
|
||||
{
|
||||
return source_engine::rotation_matrix(rotation);
|
||||
}
|
||||
};
|
||||
} // namespace omath::source_engine
|
||||
@@ -9,5 +9,5 @@
|
||||
|
||||
namespace omath::unity_engine
|
||||
{
|
||||
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>;
|
||||
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>;
|
||||
} // namespace omath::unity_engine
|
||||
12
include/omath/engines/unity_engine/mesh.hpp
Normal file
12
include/omath/engines/unity_engine/mesh.hpp
Normal 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::unity_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||
17
include/omath/engines/unity_engine/primitives.hpp
Normal file
17
include/omath/engines/unity_engine/primitives.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by Vladislav on 27.01.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/engines/unity_engine/traits/mesh_trait.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
namespace omath::unity_engine
|
||||
{
|
||||
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
|
||||
std::array<Vector3<std::uint32_t>, 12>>;
|
||||
|
||||
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
|
||||
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
|
||||
} // namespace omath::unity_engine
|
||||
19
include/omath/engines/unity_engine/traits/mesh_trait.hpp
Normal file
19
include/omath/engines/unity_engine/traits/mesh_trait.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include <omath/engines/unity_engine/constants.hpp>
|
||||
#include <omath/engines/unity_engine/formulas.hpp>
|
||||
|
||||
namespace omath::unity_engine
|
||||
{
|
||||
class MeshTrait final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
|
||||
{
|
||||
return unity_engine::rotation_matrix(rotation);
|
||||
}
|
||||
};
|
||||
} // namespace omath::unity_engine
|
||||
12
include/omath/engines/unreal_engine/mesh.hpp
Normal file
12
include/omath/engines/unreal_engine/mesh.hpp
Normal 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::unreal_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||
17
include/omath/engines/unreal_engine/primitives.hpp
Normal file
17
include/omath/engines/unreal_engine/primitives.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by Vladislav on 27.01.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh.hpp"
|
||||
#include "omath/engines/unreal_engine/traits/mesh_trait.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
namespace omath::unreal_engine
|
||||
{
|
||||
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
|
||||
std::array<Vector3<std::uint32_t>, 12>>;
|
||||
|
||||
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
|
||||
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
|
||||
} // namespace omath::unreal_engine
|
||||
19
include/omath/engines/unreal_engine/traits/mesh_trait.hpp
Normal file
19
include/omath/engines/unreal_engine/traits/mesh_trait.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include <omath/engines/unreal_engine/constants.hpp>
|
||||
#include <omath/engines/unreal_engine/formulas.hpp>
|
||||
|
||||
namespace omath::unreal_engine
|
||||
{
|
||||
class MeshTrait final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
|
||||
{
|
||||
return unreal_engine::rotation_matrix(rotation);
|
||||
}
|
||||
};
|
||||
} // namespace omath::unreal_engine
|
||||
@@ -17,6 +17,13 @@
|
||||
|
||||
#undef near
|
||||
#undef far
|
||||
// Undefine FreeBSD/BSD system macros that conflict with method names
|
||||
#ifdef minor
|
||||
#undef minor
|
||||
#endif
|
||||
#ifdef major
|
||||
#undef major
|
||||
#endif
|
||||
namespace omath
|
||||
{
|
||||
struct MatSize
|
||||
@@ -46,7 +53,7 @@ namespace omath
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr static MatStoreType get_store_ordering() noexcept
|
||||
consteval static MatStoreType get_store_ordering() noexcept
|
||||
{
|
||||
return StoreType;
|
||||
}
|
||||
@@ -373,7 +380,7 @@ namespace omath
|
||||
{
|
||||
const auto det = determinant();
|
||||
|
||||
if (det == 0)
|
||||
if (std::abs(det) < std::numeric_limits<Type>::epsilon())
|
||||
return std::nullopt;
|
||||
|
||||
const auto transposed_mat = transposed();
|
||||
@@ -586,6 +593,17 @@ namespace omath
|
||||
{0, 0, 0, 1},
|
||||
};
|
||||
}
|
||||
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
|
||||
[[nodiscard]]
|
||||
constexpr Mat<4, 4, Type, St> mat_scale(const Vector3<Type>& scale) noexcept
|
||||
{
|
||||
return {
|
||||
{scale.x, 0, 0, 0},
|
||||
{0, scale.y, 0, 0},
|
||||
{0, 0, scale.z, 0},
|
||||
{0, 0, 0, 1},
|
||||
};
|
||||
}
|
||||
|
||||
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
|
||||
[[nodiscard]]
|
||||
|
||||
@@ -26,12 +26,12 @@ namespace omath
|
||||
{
|
||||
}
|
||||
|
||||
Vector3<float> m_vertex1;
|
||||
Vector3<float> m_vertex2;
|
||||
Vector3<float> m_vertex3;
|
||||
Vector m_vertex1;
|
||||
Vector m_vertex2;
|
||||
Vector m_vertex3;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Vector3<float> calculate_normal() const
|
||||
constexpr Vector calculate_normal() const
|
||||
{
|
||||
const auto b = side_b_vector();
|
||||
const auto a = side_a_vector();
|
||||
@@ -40,25 +40,25 @@ namespace omath
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
float side_a_length() const
|
||||
Vector::ContainedType side_a_length() const
|
||||
{
|
||||
return m_vertex1.distance_to(m_vertex2);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
float side_b_length() const
|
||||
Vector::ContainedType side_b_length() const
|
||||
{
|
||||
return m_vertex3.distance_to(m_vertex2);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Vector3<float> side_a_vector() const
|
||||
constexpr Vector side_a_vector() const
|
||||
{
|
||||
return m_vertex1 - m_vertex2;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr float hypot() const
|
||||
constexpr Vector::ContainedType hypot() const
|
||||
{
|
||||
return m_vertex1.distance_to(m_vertex3);
|
||||
}
|
||||
@@ -72,12 +72,12 @@ namespace omath
|
||||
return std::abs(side_a * side_a + side_b * side_b - hypot_value * hypot_value) <= 0.0001f;
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr Vector3<float> side_b_vector() const
|
||||
constexpr Vector side_b_vector() const
|
||||
{
|
||||
return m_vertex3 - m_vertex2;
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr Vector3<float> mid_point() const
|
||||
constexpr Vector mid_point() const
|
||||
{
|
||||
return (m_vertex1 + m_vertex2 + m_vertex3) / 3;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace omath
|
||||
class Vector2
|
||||
{
|
||||
public:
|
||||
using ContainedType = Type;
|
||||
Type x = static_cast<Type>(0);
|
||||
Type y = static_cast<Type>(0);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace omath
|
||||
class Vector3 : public Vector2<Type>
|
||||
{
|
||||
public:
|
||||
using ContainedType = Type;
|
||||
Type z = static_cast<Type>(0);
|
||||
constexpr Vector3(const Type& x, const Type& y, const Type& z) noexcept: Vector2<Type>(x, y), z(z)
|
||||
{
|
||||
@@ -216,6 +217,11 @@ namespace omath
|
||||
return sum_2d() + z;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool point_to_same_direction(const Vector3& other) const
|
||||
{
|
||||
return dot(other) > static_cast<Type>(0);
|
||||
}
|
||||
[[nodiscard]] std::expected<Angle<float, 0.f, 180.f, AngleFlags::Clamped>, Vector3Error>
|
||||
angle_between(const Vector3& other) const noexcept
|
||||
{
|
||||
@@ -227,10 +233,10 @@ namespace omath
|
||||
return Angle<float, 0.f, 180.f, AngleFlags::Clamped>::from_radians(std::acos(dot(other) / bottom));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_perpendicular(const Vector3& other) 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))
|
||||
return angle->as_degrees() == static_cast<Type>(90);
|
||||
return std::abs(angle->as_degrees() - static_cast<Type>(90)) <= epsilon;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace omath
|
||||
class Vector4 : public Vector3<Type>
|
||||
{
|
||||
public:
|
||||
using ContainedType = Type;
|
||||
Type w;
|
||||
|
||||
constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w): Vector3<Type>(x, y, z), w(w)
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
|
||||
// Collision detection
|
||||
#include "omath/collision/line_tracer.hpp"
|
||||
|
||||
#include "omath/collision/gjk_algorithm.hpp"
|
||||
#include "omath/collision/epa_algorithm.hpp"
|
||||
// Pathfinding algorithms
|
||||
#include "omath/pathfinding/a_star.hpp"
|
||||
#include "omath/pathfinding/navigation_mesh.hpp"
|
||||
|
||||
@@ -5,15 +5,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/projection/error_codes.hpp"
|
||||
#include <omath/trigonometry/angle.hpp>
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <omath/trigonometry/angle.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef OMATH_BUILD_TESTS
|
||||
// ReSharper disable once CppInconsistentNaming
|
||||
// ReSharper disable CppInconsistentNaming
|
||||
class UnitTestProjection_Projection_Test;
|
||||
class UnitTestProjection_ScreenToNdcTopLeft_Test;
|
||||
class UnitTestProjection_ScreenToNdcBottomLeft_Test;
|
||||
// ReSharper restore CppInconsistentNaming
|
||||
|
||||
#endif
|
||||
|
||||
namespace omath::projection
|
||||
@@ -52,6 +58,8 @@ namespace omath::projection
|
||||
{
|
||||
#ifdef OMATH_BUILD_TESTS
|
||||
friend UnitTestProjection_Projection_Test;
|
||||
friend UnitTestProjection_ScreenToNdcTopLeft_Test;
|
||||
friend UnitTestProjection_ScreenToNdcBottomLeft_Test;
|
||||
#endif
|
||||
public:
|
||||
enum class ScreenStart
|
||||
@@ -152,7 +160,6 @@ namespace omath::projection
|
||||
return m_origin;
|
||||
}
|
||||
|
||||
|
||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||
[[nodiscard]] std::expected<Vector3<float>, Error>
|
||||
world_to_screen(const Vector3<float>& world_position) const noexcept
|
||||
@@ -170,16 +177,64 @@ namespace omath::projection
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_culled_by_frustum(const Triangle<Vector3<float>>& triangle) const noexcept
|
||||
{
|
||||
// Transform to clip space (before perspective divide)
|
||||
auto to_clip = [this](const Vector3<float>& point)
|
||||
{
|
||||
auto clip = get_view_projection_matrix()
|
||||
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(point);
|
||||
return std::array<float, 4>{
|
||||
clip.at(0, 0), // x
|
||||
clip.at(1, 0), // y
|
||||
clip.at(2, 0), // z
|
||||
clip.at(3, 0) // w
|
||||
};
|
||||
};
|
||||
|
||||
const auto c0 = to_clip(triangle.m_vertex1);
|
||||
const auto c1 = to_clip(triangle.m_vertex2);
|
||||
const auto c2 = to_clip(triangle.m_vertex3);
|
||||
|
||||
// If all vertices are behind the camera (w <= 0), trivially reject
|
||||
if (c0[3] <= 0.f && c1[3] <= 0.f && c2[3] <= 0.f)
|
||||
return true;
|
||||
|
||||
// Helper: all three vertices outside the same clip plane
|
||||
auto all_outside_plane = [](const int axis, const std::array<float, 4>& a, const std::array<float, 4>& b,
|
||||
const std::array<float, 4>& c, const bool positive_side)
|
||||
{
|
||||
if (positive_side)
|
||||
return a[axis] > a[3] && b[axis] > b[3] && c[axis] > c[3];
|
||||
return a[axis] < -a[3] && b[axis] < -b[3] && c[axis] < -c[3];
|
||||
};
|
||||
|
||||
// Clip volume in clip space (OpenGL-style):
|
||||
// -w <= x <= w
|
||||
// -w <= y <= w
|
||||
// -w <= z <= w
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (all_outside_plane(i, c0, c1, c2, false))
|
||||
return true; // x < -w (left)
|
||||
if (all_outside_plane(i, c0, c1, c2, true))
|
||||
return true; // x > w (right)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<Vector3<float>, Error>
|
||||
world_to_view_port(const Vector3<float>& world_position) const noexcept
|
||||
{
|
||||
auto projected = get_view_projection_matrix()
|
||||
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(world_position);
|
||||
|
||||
if (projected.at(3, 0) == 0.0f)
|
||||
const auto& w = projected.at(3, 0);
|
||||
if (w <= std::numeric_limits<float>::epsilon())
|
||||
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
||||
|
||||
projected /= projected.at(3, 0);
|
||||
projected /= w;
|
||||
|
||||
if (is_ndc_out_of_bounds(projected))
|
||||
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
||||
@@ -197,26 +252,30 @@ namespace omath::projection
|
||||
auto inverted_projection =
|
||||
inv_view_proj.value() * mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(ndc);
|
||||
|
||||
if (!inverted_projection.at(3, 0))
|
||||
const auto& w = inverted_projection.at(3, 0);
|
||||
|
||||
if (std::abs(w) < std::numeric_limits<float>::epsilon())
|
||||
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
||||
|
||||
inverted_projection /= inverted_projection.at(3, 0);
|
||||
inverted_projection /= w;
|
||||
|
||||
return Vector3<float>{inverted_projection.at(0, 0), inverted_projection.at(1, 0),
|
||||
inverted_projection.at(2, 0)};
|
||||
}
|
||||
|
||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||
[[nodiscard]]
|
||||
std::expected<Vector3<float>, Error> screen_to_world(const Vector3<float>& screen_pos) const noexcept
|
||||
{
|
||||
return view_port_to_screen(screen_to_ndc(screen_pos));
|
||||
return view_port_to_screen(screen_to_ndc<screen_start>(screen_pos));
|
||||
}
|
||||
|
||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||
[[nodiscard]]
|
||||
std::expected<Vector3<float>, Error> screen_to_world(const Vector2<float>& screen_pos) const noexcept
|
||||
{
|
||||
const auto& [x, y] = screen_pos;
|
||||
return screen_to_world({x, y, 1.f});
|
||||
return screen_to_world<screen_start>({x, y, 1.f});
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -235,7 +294,9 @@ namespace omath::projection
|
||||
template<class Type>
|
||||
[[nodiscard]] constexpr static bool is_ndc_out_of_bounds(const Type& ndc) noexcept
|
||||
{
|
||||
return std::ranges::any_of(ndc.raw_array(), [](const auto& val) { return val < -1 || val > 1; });
|
||||
constexpr auto eps = std::numeric_limits<float>::epsilon();
|
||||
return std::ranges::any_of(ndc.raw_array(),
|
||||
[](const auto& val) { return val < -1.0f - eps || val > 1.0f + eps; });
|
||||
}
|
||||
|
||||
// NDC REPRESENTATION:
|
||||
@@ -286,10 +347,17 @@ namespace omath::projection
|
||||
return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (ndc.y / 2.f + 0.5f) * m_view_port.m_height, ndc.z};
|
||||
}
|
||||
|
||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||
[[nodiscard]] Vector3<float> screen_to_ndc(const Vector3<float>& screen_pos) const noexcept
|
||||
{
|
||||
return {screen_pos.x / m_view_port.m_width * 2.f - 1.f, 1.f - screen_pos.y / m_view_port.m_height * 2.f,
|
||||
screen_pos.z};
|
||||
if constexpr (screen_start == ScreenStart::TOP_LEFT_CORNER)
|
||||
return {screen_pos.x / m_view_port.m_width * 2.f - 1.f, 1.f - screen_pos.y / m_view_port.m_height * 2.f,
|
||||
screen_pos.z};
|
||||
else if constexpr (screen_start == ScreenStart::BOTTOM_LEFT_CORNER)
|
||||
return {screen_pos.x / m_view_port.m_width * 2.f - 1.f,
|
||||
(screen_pos.y / m_view_port.m_height - 0.5f) * 2.f, screen_pos.z};
|
||||
else
|
||||
std::unreachable();
|
||||
}
|
||||
};
|
||||
} // namespace omath::projection
|
||||
|
||||
@@ -23,27 +23,26 @@ namespace omath::rev_eng
|
||||
return *reinterpret_cast<Type*>(reinterpret_cast<std::uintptr_t>(this) + offset);
|
||||
}
|
||||
|
||||
template<std::size_t id, class ReturnType, class... Args>
|
||||
ReturnType call_virtual_method(Args&&... arg_list)
|
||||
template<std::size_t id, class ReturnType>
|
||||
ReturnType call_virtual_method(auto... arg_list)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
||||
#else
|
||||
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
|
||||
#endif
|
||||
return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, std::forward<Args>(arg_list)...);
|
||||
return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, arg_list...);
|
||||
}
|
||||
template<std::size_t id, class ReturnType, class... Args>
|
||||
ReturnType call_virtual_method(Args&&... arg_list) const
|
||||
template<std::size_t id, class ReturnType>
|
||||
ReturnType call_virtual_method(auto... arg_list) const
|
||||
{
|
||||
using This = std::remove_cv_t<std::remove_pointer_t<decltype(this)>>;
|
||||
#ifdef _MSC_VER
|
||||
using VirtualMethodType = ReturnType(__thiscall*)(const void*, decltype(arg_list)...);
|
||||
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
||||
#else
|
||||
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
|
||||
#endif
|
||||
return (*reinterpret_cast<VirtualMethodType**>(const_cast<This*>(this)))[id](
|
||||
const_cast<void*>(static_cast<const void*>(this)), std::forward<Args>(arg_list)...);
|
||||
return (*static_cast<VirtualMethodType**>((void*)(this)))[id](
|
||||
const_cast<void*>(static_cast<const void*>(this)), arg_list...);
|
||||
}
|
||||
};
|
||||
} // namespace omath::rev_eng
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace omath
|
||||
{
|
||||
struct Hsv
|
||||
struct Hsv final
|
||||
{
|
||||
float hue{};
|
||||
float saturation{};
|
||||
@@ -46,27 +46,26 @@ namespace omath
|
||||
|
||||
switch (i % 6)
|
||||
{
|
||||
case 0:
|
||||
r = value, g = t, b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q, g = value, b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p, g = value, b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p, g = q, b = value;
|
||||
break;
|
||||
case 4:
|
||||
r = t, g = p, b = value;
|
||||
break;
|
||||
case 5:
|
||||
r = value, g = p, b = q;
|
||||
break;
|
||||
|
||||
default:
|
||||
return {0.f, 0.f, 0.f, 0.f};
|
||||
case 0:
|
||||
r = value, g = t, b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q, g = value, b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p, g = value, b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p, g = q, b = value;
|
||||
break;
|
||||
case 4:
|
||||
r = t, g = p, b = value;
|
||||
break;
|
||||
case 5:
|
||||
r = value, g = p, b = q;
|
||||
break;
|
||||
default:
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
return {r, g, b, 1.f};
|
||||
@@ -190,7 +189,7 @@ template<>
|
||||
struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp)
|
||||
{
|
||||
[[nodiscard]]
|
||||
static constexpr auto parse(std::format_parse_context& ctx)
|
||||
static constexpr auto parse(const std::format_parse_context& ctx)
|
||||
{
|
||||
return ctx.begin();
|
||||
}
|
||||
@@ -207,6 +206,6 @@ struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp)
|
||||
if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>)
|
||||
return std::format_to(ctx.out(), u8"{}", col.to_u8string());
|
||||
|
||||
return std::unreachable();
|
||||
std::unreachable();
|
||||
}
|
||||
};
|
||||
25
include/omath/utility/elf_pattern_scan.hpp
Normal file
25
include/omath/utility/elf_pattern_scan.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by Vladislav on 30.12.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include "section_scan_result.hpp"
|
||||
namespace omath
|
||||
{
|
||||
class ElfPatternScanner 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
|
||||
@@ -51,9 +51,13 @@ namespace omath
|
||||
|
||||
const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end));
|
||||
|
||||
const std::ptrdiff_t scan_size = whole_range_size - static_cast<std::ptrdiff_t>(pattern.size());
|
||||
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern->size());
|
||||
const std::ptrdiff_t scan_size = whole_range_size - pattern_size;
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < scan_size; i++)
|
||||
if (scan_size < 0)
|
||||
return end;
|
||||
|
||||
for (std::ptrdiff_t i = 0; i <= scan_size; i++)
|
||||
{
|
||||
bool found = true;
|
||||
|
||||
|
||||
@@ -7,23 +7,20 @@
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include "section_scan_result.hpp"
|
||||
namespace omath
|
||||
{
|
||||
struct PeSectionScanResult
|
||||
{
|
||||
std::uint64_t virtual_base_addr;
|
||||
std::uint64_t raw_base_addr;
|
||||
std::ptrdiff_t target_offset;
|
||||
};
|
||||
|
||||
class PePatternScanner 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);
|
||||
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<PeSectionScanResult>
|
||||
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");
|
||||
};
|
||||
|
||||
16
include/omath/utility/section_scan_result.hpp
Normal file
16
include/omath/utility/section_scan_result.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by Vladislav on 01.01.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
namespace omath
|
||||
{
|
||||
struct SectionScanResult final
|
||||
{
|
||||
std::uintptr_t virtual_base_addr;
|
||||
std::uintptr_t raw_base_addr;
|
||||
std::ptrdiff_t target_offset;
|
||||
};
|
||||
}
|
||||
@@ -3,4 +3,5 @@ theme:
|
||||
name: darkly
|
||||
extra_css:
|
||||
- styles/center.css
|
||||
- styles/custom-header.css
|
||||
- styles/custom-header.css
|
||||
- styles/links.css
|
||||
69
pixi.toml
Normal file
69
pixi.toml
Normal 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
36
pixi/fmt.cmake
Normal 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
63
pixi/run.benchmark.cmake
Normal 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
63
pixi/run.examples.cmake
Normal 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
63
pixi/run.unit.tests.cmake
Normal 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()
|
||||
169
scripts/coverage-llvm.sh
Executable file
169
scripts/coverage-llvm.sh
Executable file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/coverage-llvm.sh
|
||||
# LLVM coverage script that generates LCOV-style reports
|
||||
|
||||
set -e
|
||||
|
||||
SOURCE_DIR="${1:-.}"
|
||||
BINARY_DIR="${2:-cmake-build/build}"
|
||||
TEST_BINARY="${3:-}"
|
||||
OUTPUT_DIR="${4:-${BINARY_DIR}/coverage}"
|
||||
|
||||
echo "[*] Source dir: ${SOURCE_DIR}"
|
||||
echo "[*] Binary dir: ${BINARY_DIR}"
|
||||
echo "[*] Output dir: ${OUTPUT_DIR}"
|
||||
|
||||
# Find llvm tools - handle versioned names (Linux) and xcrun (macOS)
|
||||
find_llvm_tool() {
|
||||
local tool_name="$1"
|
||||
|
||||
# macOS: use xcrun
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
if xcrun --find "${tool_name}" &>/dev/null; then
|
||||
echo "xcrun ${tool_name}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try versioned names (Linux with LLVM 21, 20, 19, etc.)
|
||||
for version in 21 20 19 18 17 ""; do
|
||||
local versioned_name="${tool_name}${version:+-$version}"
|
||||
if command -v "${versioned_name}" &>/dev/null; then
|
||||
echo "${versioned_name}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
return 1
|
||||
}
|
||||
|
||||
LLVM_PROFDATA=$(find_llvm_tool "llvm-profdata")
|
||||
LLVM_COV=$(find_llvm_tool "llvm-cov")
|
||||
|
||||
if [[ -z "${LLVM_PROFDATA}" ]] || [[ -z "${LLVM_COV}" ]]; then
|
||||
echo "Error: llvm-profdata or llvm-cov not found" >&2
|
||||
echo "On Linux, install llvm or clang package" >&2
|
||||
echo "On macOS, Xcode command line tools should provide these" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[*] Using: ${LLVM_PROFDATA}"
|
||||
echo "[*] Using: ${LLVM_COV}"
|
||||
|
||||
# Find test binary
|
||||
if [[ -z "${TEST_BINARY}" ]]; then
|
||||
for path in \
|
||||
"${SOURCE_DIR}/out/Debug/unit_tests" \
|
||||
"${SOURCE_DIR}/out/Release/unit_tests" \
|
||||
"${BINARY_DIR}/unit_tests" \
|
||||
"${BINARY_DIR}/tests/unit_tests"; do
|
||||
if [[ -x "${path}" ]]; then
|
||||
TEST_BINARY="${path}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "${TEST_BINARY}" ]] || [[ ! -x "${TEST_BINARY}" ]]; then
|
||||
echo "Error: unit_tests binary not found" >&2
|
||||
echo "Searched in: out/Debug, out/Release, ${BINARY_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[*] Test binary: ${TEST_BINARY}"
|
||||
|
||||
# Clean previous coverage data
|
||||
rm -rf "${OUTPUT_DIR}"
|
||||
rm -f "${BINARY_DIR}"/*.profraw "${BINARY_DIR}"/*.profdata
|
||||
mkdir -p "${OUTPUT_DIR}"
|
||||
|
||||
# Run tests with profiling enabled
|
||||
PROFILE_FILE="${BINARY_DIR}/default_%p.profraw"
|
||||
echo "[*] Running tests with LLVM_PROFILE_FILE=${PROFILE_FILE}"
|
||||
|
||||
export LLVM_PROFILE_FILE="${PROFILE_FILE}"
|
||||
"${TEST_BINARY}" || echo "[!] Some tests failed, continuing with coverage..."
|
||||
|
||||
# Find all generated .profraw files
|
||||
PROFRAW_FILES=$(find "${BINARY_DIR}" -name "*.profraw" -type f 2>/dev/null)
|
||||
if [[ -z "${PROFRAW_FILES}" ]]; then
|
||||
# Also check current directory
|
||||
PROFRAW_FILES=$(find . -maxdepth 3 -name "*.profraw" -type f 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [[ -z "${PROFRAW_FILES}" ]]; then
|
||||
echo "Error: No .profraw files generated" >&2
|
||||
echo "Make sure the binary was built with -fprofile-instr-generate -fcoverage-mapping" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[*] Found profraw files:"
|
||||
echo "${PROFRAW_FILES}"
|
||||
|
||||
# Merge profiles
|
||||
PROFDATA_FILE="${BINARY_DIR}/coverage.profdata"
|
||||
echo "[*] Merging profiles into ${PROFDATA_FILE}"
|
||||
${LLVM_PROFDATA} merge -sparse ${PROFRAW_FILES} -o "${PROFDATA_FILE}"
|
||||
|
||||
# Generate text summary
|
||||
echo "[*] Coverage Summary:"
|
||||
${LLVM_COV} report "${TEST_BINARY}" \
|
||||
-instr-profile="${PROFDATA_FILE}" \
|
||||
-ignore-filename-regex="tests/.*" \
|
||||
-ignore-filename-regex="googletest/.*" \
|
||||
-ignore-filename-regex="gtest/.*" \
|
||||
-ignore-filename-regex="_deps/.*" \
|
||||
-ignore-filename-regex="vcpkg_installed/.*"
|
||||
|
||||
# Export lcov format (for tools like codecov)
|
||||
LCOV_FILE="${OUTPUT_DIR}/coverage.lcov"
|
||||
echo "[*] Exporting LCOV format to ${LCOV_FILE}"
|
||||
${LLVM_COV} export "${TEST_BINARY}" \
|
||||
-instr-profile="${PROFDATA_FILE}" \
|
||||
-format=lcov \
|
||||
-ignore-filename-regex="tests/.*" \
|
||||
-ignore-filename-regex="googletest/.*" \
|
||||
-ignore-filename-regex="gtest/.*" \
|
||||
-ignore-filename-regex="_deps/.*" \
|
||||
-ignore-filename-regex="vcpkg_installed/.*" \
|
||||
> "${LCOV_FILE}" || true
|
||||
|
||||
# Generate LCOV-style HTML report using genhtml
|
||||
if command -v genhtml >/dev/null 2>&1; then
|
||||
echo "[*] Generating LCOV-style HTML report using genhtml"
|
||||
genhtml "${LCOV_FILE}" \
|
||||
--ignore-errors inconsistent,corrupt \
|
||||
--output-directory "${OUTPUT_DIR}" \
|
||||
--title "Omath Coverage Report" \
|
||||
--show-details \
|
||||
--legend \
|
||||
--demangle-cpp \
|
||||
--num-spaces 4 \
|
||||
--sort \
|
||||
--function-coverage \
|
||||
--branch-coverage
|
||||
|
||||
echo "[*] LCOV-style HTML report generated at: ${OUTPUT_DIR}/index.html"
|
||||
else
|
||||
echo "[!] genhtml not found. Installing lcov package..."
|
||||
echo "[!] On Ubuntu/Debian: sudo apt-get install lcov"
|
||||
echo "[!] On macOS: brew install lcov"
|
||||
echo "[!] Falling back to LLVM HTML report..."
|
||||
|
||||
# Fall back to LLVM HTML report
|
||||
${LLVM_COV} show "${TEST_BINARY}" \
|
||||
-instr-profile="${PROFDATA_FILE}" \
|
||||
-format=html \
|
||||
-output-dir="${OUTPUT_DIR}" \
|
||||
-show-line-counts-or-regions \
|
||||
-show-instantiations=false \
|
||||
-ignore-filename-regex="tests/.*" \
|
||||
-ignore-filename-regex="googletest/.*" \
|
||||
-ignore-filename-regex="gtest/.*" \
|
||||
-ignore-filename-regex="_deps/.*" \
|
||||
-ignore-filename-regex="vcpkg_installed/.*"
|
||||
fi
|
||||
|
||||
echo "[*] Coverage report generated at: ${OUTPUT_DIR}/index.html"
|
||||
echo "[*] LCOV file at: ${LCOV_FILE}"
|
||||
8
scripts/coverage.bat.in
Normal file
8
scripts/coverage.bat.in
Normal file
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
REM scripts/coverage.bat.in
|
||||
REM Simple wrapper to run coverage.ps1
|
||||
|
||||
set SOURCE_DIR=@CMAKE_SOURCE_DIR@
|
||||
set BINARY_DIR=@CMAKE_BINARY_DIR@
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File "%BINARY_DIR%\scripts\coverage.ps1" -SourceDir "%SOURCE_DIR%" -BinaryDir "%BINARY_DIR%" %*
|
||||
132
scripts/coverage.ps1.in
Normal file
132
scripts/coverage.ps1.in
Normal file
@@ -0,0 +1,132 @@
|
||||
# scripts/coverage.ps1.in
|
||||
# Windows coverage script using OpenCppCoverage
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$SourceDir,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$BinaryDir,
|
||||
|
||||
[string]$TestBinary = "",
|
||||
[switch]$Cobertura,
|
||||
[switch]$Html
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# CMake-injected variables
|
||||
$LCOV_IGNORE_ERRORS = '@LCOV_IGNORE_ERRORS@'
|
||||
|
||||
# Resolve paths
|
||||
$SourceDir = Resolve-Path $SourceDir
|
||||
$BinaryDir = Resolve-Path $BinaryDir
|
||||
|
||||
Write-Host "[*] Source directory: $SourceDir" -ForegroundColor Cyan
|
||||
Write-Host "[*] Binary directory: $BinaryDir" -ForegroundColor Cyan
|
||||
|
||||
# Find test binary
|
||||
if (-not $TestBinary) {
|
||||
$searchPaths = @(
|
||||
"$BinaryDir\Debug\unit_tests.exe",
|
||||
"$BinaryDir\Release\unit_tests.exe",
|
||||
"$BinaryDir\unit_tests.exe",
|
||||
"$SourceDir\out\Debug\unit_tests.exe",
|
||||
"$SourceDir\out\Release\unit_tests.exe"
|
||||
)
|
||||
|
||||
foreach ($path in $searchPaths) {
|
||||
if (Test-Path $path) {
|
||||
$TestBinary = $path
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $TestBinary -or -not (Test-Path $TestBinary)) {
|
||||
Write-Error "unit_tests.exe not found. Searched: $($searchPaths -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$TestBinary = Resolve-Path $TestBinary
|
||||
Write-Host "[*] Test binary: $TestBinary" -ForegroundColor Cyan
|
||||
|
||||
# Check for OpenCppCoverage
|
||||
$opencppcov = Get-Command "OpenCppCoverage" -ErrorAction SilentlyContinue
|
||||
if (-not $opencppcov) {
|
||||
# Try common installation paths
|
||||
$possiblePaths = @(
|
||||
"$env:ProgramFiles\OpenCppCoverage\OpenCppCoverage.exe",
|
||||
"${env:ProgramFiles(x86)}\OpenCppCoverage\OpenCppCoverage.exe",
|
||||
"$env:LOCALAPPDATA\Programs\OpenCppCoverage\OpenCppCoverage.exe"
|
||||
)
|
||||
|
||||
foreach ($path in $possiblePaths) {
|
||||
if (Test-Path $path) {
|
||||
$opencppcov = Get-Item $path
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $opencppcov) {
|
||||
Write-Host @"
|
||||
OpenCppCoverage not found!
|
||||
|
||||
Install it from: https://github.com/OpenCppCoverage/OpenCppCoverage/releases
|
||||
|
||||
Or via Chocolatey:
|
||||
choco install opencppcoverage
|
||||
|
||||
Or via winget:
|
||||
winget install OpenCppCoverage.OpenCppCoverage
|
||||
"@ -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$OpenCppCoveragePath = if ($opencppcov.Source) { $opencppcov.Source } else { $opencppcov.FullName }
|
||||
Write-Host "[*] Using OpenCppCoverage: $OpenCppCoveragePath" -ForegroundColor Cyan
|
||||
|
||||
# Create output directory
|
||||
$CoverageDir = Join-Path $BinaryDir "coverage"
|
||||
if (-not (Test-Path $CoverageDir)) {
|
||||
New-Item -ItemType Directory -Path $CoverageDir | Out-Null
|
||||
}
|
||||
|
||||
# Build OpenCppCoverage arguments
|
||||
$coverageArgs = @(
|
||||
"--sources", "$SourceDir\include",
|
||||
"--sources", "$SourceDir\source",
|
||||
"--excluded_sources", "*\tests\*",
|
||||
"--excluded_sources", "*\googletest\*",
|
||||
"--excluded_sources", "*\gtest\*",
|
||||
"--excluded_sources", "*\_deps\*",
|
||||
"--excluded_sources", "*\vcpkg_installed\*",
|
||||
"--export_type", "html:$CoverageDir",
|
||||
"--export_type", "cobertura:$CoverageDir\coverage.xml",
|
||||
"--cover_children",
|
||||
"--"
|
||||
)
|
||||
|
||||
Write-Host "[*] Running OpenCppCoverage..." -ForegroundColor Cyan
|
||||
Write-Host " Command: $OpenCppCoveragePath $($coverageArgs -join ' ') $TestBinary"
|
||||
|
||||
& $OpenCppCoveragePath @coverageArgs $TestBinary
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "OpenCppCoverage exited with code $LASTEXITCODE (tests may have failed)"
|
||||
}
|
||||
|
||||
# Check outputs
|
||||
$htmlIndex = Join-Path $CoverageDir "index.html"
|
||||
$coberturaXml = Join-Path $CoverageDir "coverage.xml"
|
||||
|
||||
if (Test-Path $htmlIndex) {
|
||||
Write-Host "[*] HTML coverage report: $htmlIndex" -ForegroundColor Green
|
||||
}
|
||||
|
||||
if (Test-Path $coberturaXml) {
|
||||
Write-Host "[*] Cobertura XML report: $coberturaXml" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host "[*] Coverage collection complete!" -ForegroundColor Green
|
||||
77
scripts/valgrind.sh
Executable file
77
scripts/valgrind.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
|
||||
# =============================================================================
|
||||
# Local Reproduction of "Valgrind Analysis" GitHub Action
|
||||
# =============================================================================
|
||||
|
||||
# Stop on error, undefined variables, or pipe failures
|
||||
set -euo pipefail
|
||||
|
||||
# --- 1. Environment Setup ---
|
||||
|
||||
# Determine VCPKG_ROOT
|
||||
# If VCPKG_ROOT is not set in your shell, we check if a local folder exists.
|
||||
if [[ -z "${VCPKG_ROOT:-}" ]]; then
|
||||
if [[ -d "./vcpkg" ]]; then
|
||||
export VCPKG_ROOT="$(pwd)/vcpkg"
|
||||
echo "Found local vcpkg at: $VCPKG_ROOT"
|
||||
|
||||
# Bootstrap vcpkg if the executable doesn't exist
|
||||
if [[ ! -f "$VCPKG_ROOT/vcpkg" ]]; then
|
||||
echo "Bootstrapping vcpkg..."
|
||||
"$VCPKG_ROOT/bootstrap-vcpkg.sh"
|
||||
fi
|
||||
else
|
||||
echo "Error: VCPKG_ROOT is not set and ./vcpkg directory not found."
|
||||
echo "Please install vcpkg or set the VCPKG_ROOT environment variable."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Using existing VCPKG_ROOT: $VCPKG_ROOT"
|
||||
fi
|
||||
|
||||
# Set the build directory matching the YAML's preset expectation
|
||||
# Assuming the preset writes to: cmake-build/build/linux-release-vcpkg
|
||||
BUILD_DIR="cmake-build/build/linux-release-vcpkg"
|
||||
|
||||
# Check if Valgrind is installed
|
||||
if ! command -v valgrind &> /dev/null; then
|
||||
echo "Error: valgrind is not installed. Please install it (e.g., sudo apt install valgrind)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "----------------------------------------------------"
|
||||
echo "Starting Configuration (Debug Build with Valgrind)..."
|
||||
echo "----------------------------------------------------"
|
||||
|
||||
# --- 2. Configure (CMake) ---
|
||||
# We force CMAKE_BUILD_TYPE=Debug even though the preset says 'release'
|
||||
# to ensure Valgrind has access to debug symbols (line numbers).
|
||||
|
||||
cmake --preset linux-release-vcpkg \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DOMATH_BUILD_EXAMPLES=OFF \
|
||||
-DOMATH_BUILD_TESTS=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=ON \
|
||||
-DOMATH_ENABLE_VALGRIND=ON \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;benchmark"
|
||||
|
||||
echo "----------------------------------------------------"
|
||||
echo "Building Targets..."
|
||||
echo "----------------------------------------------------"
|
||||
|
||||
# --- 3. Build ---
|
||||
# Using the specific build directory defined by the preset structure
|
||||
cmake --build "$BUILD_DIR"
|
||||
|
||||
echo "----------------------------------------------------"
|
||||
echo "Running Valgrind Analysis..."
|
||||
echo "----------------------------------------------------"
|
||||
|
||||
# --- 4. Run Valgrind ---
|
||||
# Runs the specific custom target defined in your CMakeLists.txt
|
||||
cmake --build "$BUILD_DIR" --target valgrind_all
|
||||
|
||||
echo "----------------------------------------------------"
|
||||
echo "Valgrind Analysis Complete."
|
||||
echo "----------------------------------------------------"
|
||||
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// Created by Vlad on 4/18/2025.
|
||||
//
|
||||
#include "omath/3d_primitives/box.hpp"
|
||||
|
||||
namespace omath::primitives
|
||||
{
|
||||
std::array<Triangle<Vector3<float>>, 12> create_box(const Vector3<float>& top, const Vector3<float>& bottom,
|
||||
const Vector3<float>& dir_forward,
|
||||
const Vector3<float>& dir_right, const float ratio) noexcept
|
||||
{
|
||||
const auto height = top.distance_to(bottom);
|
||||
const auto side_size = height / ratio;
|
||||
|
||||
// corner layout (0‑3 bottom, 4‑7 top)
|
||||
std::array<Vector3<float>, 8> p;
|
||||
p[0] = bottom + (dir_forward + dir_right) * side_size; // front‑right‑bottom
|
||||
p[1] = bottom + (dir_forward - dir_right) * side_size; // front‑left‑bottom
|
||||
p[2] = bottom + (-dir_forward + dir_right) * side_size; // back‑right‑bottom
|
||||
p[3] = bottom + (-dir_forward - dir_right) * side_size; // back‑left‑bottom
|
||||
p[4] = top + (dir_forward + dir_right) * side_size; // front‑right‑top
|
||||
p[5] = top + (dir_forward - dir_right) * side_size; // front‑left‑top
|
||||
p[6] = top + (-dir_forward + dir_right) * side_size; // back‑right‑top
|
||||
p[7] = top + (-dir_forward - dir_right) * side_size; // back‑left‑top
|
||||
|
||||
std::array<Triangle<Vector3<float>>, 12> poly;
|
||||
|
||||
// bottom face (+Y up ⇒ wind CW when viewed from above)
|
||||
poly[0] = {p[0], p[2], p[3]};
|
||||
poly[1] = {p[0], p[3], p[1]};
|
||||
|
||||
// top face
|
||||
poly[2] = {p[4], p[7], p[6]};
|
||||
poly[3] = {p[4], p[5], p[7]};
|
||||
|
||||
// front face
|
||||
poly[4] = {p[0], p[5], p[1]};
|
||||
poly[5] = {p[0], p[4], p[5]};
|
||||
|
||||
// right face
|
||||
poly[6] = {p[0], p[6], p[2]};
|
||||
poly[7] = {p[0], p[4], p[6]};
|
||||
|
||||
// back face
|
||||
poly[8] = {p[2], p[7], p[3]};
|
||||
poly[9] = {p[2], p[6], p[7]};
|
||||
|
||||
// left face
|
||||
poly[10] = {p[1], p[7], p[5]};
|
||||
poly[11] = {p[1], p[3], p[7]};
|
||||
|
||||
return poly;
|
||||
}
|
||||
} // namespace omath::primitives
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// Created by Vlad on 8/28/2025.
|
||||
//
|
||||
#include "omath/3d_primitives/plane.hpp"
|
||||
|
||||
namespace omath::primitives
|
||||
{
|
||||
std::array<Triangle<Vector3<float>>, 2> create_plane(const Vector3<float>& vertex_a,
|
||||
const Vector3<float>& vertex_b,
|
||||
const Vector3<float>& direction, const float size) noexcept
|
||||
{
|
||||
const auto second_vertex_a = vertex_a + direction * size;
|
||||
return std::array
|
||||
{
|
||||
Triangle{second_vertex_a, vertex_a, vertex_b},
|
||||
Triangle{second_vertex_a, vertex_b + direction * size, vertex_b}
|
||||
};
|
||||
}
|
||||
} // namespace omath::primitives
|
||||
@@ -37,13 +37,6 @@ namespace omath::unreal_engine
|
||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
||||
const float far) noexcept
|
||||
{
|
||||
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f);
|
||||
|
||||
return {
|
||||
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
|
||||
{0, 1.f / (fov_half_tan), 0, 0},
|
||||
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
|
||||
{0, 0, -1.f, 0},
|
||||
};
|
||||
return mat_perspective_left_handed(field_of_view, aspect_ratio, near, far);
|
||||
}
|
||||
} // namespace omath::unreal_engine
|
||||
|
||||
325
source/utility/elf_pattern_scan.cpp
Normal file
325
source/utility/elf_pattern_scan.cpp
Normal file
@@ -0,0 +1,325 @@
|
||||
//
|
||||
// Created by Vladislav on 30.12.2025.
|
||||
//
|
||||
#include "omath/utility/pattern_scan.hpp"
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <omath/utility/elf_pattern_scan.hpp>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
namespace
|
||||
{
|
||||
// Common
|
||||
constexpr uint8_t ei_nident = 16;
|
||||
constexpr uint8_t ei_class = 4;
|
||||
|
||||
constexpr uint8_t elfclass32 = 1;
|
||||
constexpr uint8_t elfclass64 = 2;
|
||||
// ReSharper disable CppDeclaratorNeverUsed
|
||||
struct Elf32Ehdr final
|
||||
{
|
||||
unsigned char e_ident[ei_nident];
|
||||
uint16_t e_type;
|
||||
uint16_t e_machine;
|
||||
uint32_t e_version;
|
||||
uint32_t e_entry;
|
||||
uint32_t e_phoff;
|
||||
uint32_t e_shoff;
|
||||
uint32_t e_flags;
|
||||
uint16_t e_ehsize;
|
||||
uint16_t e_phentsize;
|
||||
uint16_t e_phnum;
|
||||
uint16_t e_shentsize;
|
||||
uint16_t e_shnum;
|
||||
uint16_t e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf64Ehdr final
|
||||
{
|
||||
unsigned char e_ident[ei_nident];
|
||||
uint16_t e_type;
|
||||
uint16_t e_machine;
|
||||
uint32_t e_version;
|
||||
uint64_t e_entry;
|
||||
uint64_t e_phoff;
|
||||
uint64_t e_shoff;
|
||||
uint32_t e_flags;
|
||||
uint16_t e_ehsize;
|
||||
uint16_t e_phentsize;
|
||||
uint16_t e_phnum;
|
||||
uint16_t e_shentsize;
|
||||
uint16_t e_shnum;
|
||||
uint16_t e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf32Shdr final
|
||||
{
|
||||
uint32_t sh_name;
|
||||
uint32_t sh_type;
|
||||
uint32_t sh_flags;
|
||||
uint32_t sh_addr;
|
||||
uint32_t sh_offset;
|
||||
uint32_t sh_size;
|
||||
uint32_t sh_link;
|
||||
uint32_t sh_info;
|
||||
uint32_t sh_addralign;
|
||||
uint32_t sh_entsize;
|
||||
};
|
||||
|
||||
struct Elf64Shdr final
|
||||
{
|
||||
uint32_t sh_name;
|
||||
uint32_t sh_type;
|
||||
uint64_t sh_flags;
|
||||
uint64_t sh_addr;
|
||||
uint64_t sh_offset;
|
||||
uint64_t sh_size;
|
||||
uint32_t sh_link;
|
||||
uint32_t sh_info;
|
||||
uint64_t sh_addralign;
|
||||
uint64_t sh_entsize;
|
||||
};
|
||||
// ReSharper restore CppDeclaratorNeverUsed
|
||||
#pragma pack(pop)
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class FileArch : std::int8_t
|
||||
{
|
||||
x32,
|
||||
x64,
|
||||
};
|
||||
template<FileArch arch>
|
||||
struct ElfHeaders
|
||||
{
|
||||
using FileHeader = std::conditional_t<arch == FileArch::x64, Elf64Ehdr, Elf32Ehdr>;
|
||||
using SectionHeader = std::conditional_t<arch == FileArch::x64, Elf64Shdr, Elf32Shdr>;
|
||||
FileHeader file_header;
|
||||
SectionHeader section_header;
|
||||
};
|
||||
[[nodiscard]]
|
||||
bool not_elf_file(std::fstream& file)
|
||||
{
|
||||
constexpr std::string_view valid_elf_signature = "\x7F"
|
||||
"ELF";
|
||||
std::array<char, valid_elf_signature.size() + 1> elf_signature{};
|
||||
const std::streampos back_up_pose = file.tellg();
|
||||
|
||||
file.seekg(0, std::ios_base::beg);
|
||||
file.read(elf_signature.data(), 4);
|
||||
file.seekg(back_up_pose, std::ios_base::beg);
|
||||
|
||||
return std::string_view{elf_signature.data(), 4} != valid_elf_signature;
|
||||
}
|
||||
[[nodiscard]]
|
||||
std::optional<FileArch> get_file_arch(std::fstream& file)
|
||||
{
|
||||
std::array<char, ei_nident> e_ident{};
|
||||
const std::streampos back_up_pose = file.tellg();
|
||||
|
||||
file.seekg(0, std::ios_base::beg);
|
||||
file.read(e_ident.data(), e_ident.size());
|
||||
file.seekg(back_up_pose, std::ios_base::beg);
|
||||
|
||||
if (e_ident[ei_class] == elfclass64)
|
||||
return FileArch::x64;
|
||||
|
||||
if (e_ident[ei_class] == elfclass32)
|
||||
return FileArch::x32;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
struct ExtractedSection final
|
||||
{
|
||||
std::uintptr_t virtual_base_addr{};
|
||||
std::uintptr_t raw_base_addr{};
|
||||
std::vector<std::byte> data;
|
||||
};
|
||||
[[maybe_unused]]
|
||||
std::optional<ExtractedSection> get_elf_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 (not_elf_file(file)) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
const auto architecture = get_file_arch(file);
|
||||
|
||||
if (!architecture.has_value()) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
std::variant<ElfHeaders<FileArch::x64>, ElfHeaders<FileArch::x32>> elf_headers;
|
||||
if (architecture.value() == FileArch::x64)
|
||||
elf_headers = ElfHeaders<FileArch::x64>{};
|
||||
else if (architecture.value() == FileArch::x32)
|
||||
elf_headers = ElfHeaders<FileArch::x32>{};
|
||||
|
||||
return std::visit(
|
||||
[&](auto& header) -> std::optional<ExtractedSection>
|
||||
{
|
||||
auto& [file_header, section_header] = header;
|
||||
file.seekg(0, std::ios_base::beg);
|
||||
if (!file.read(reinterpret_cast<char*>(&file_header), sizeof(file_header))) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
const std::streamoff shstr_off =
|
||||
static_cast<std::streamoff>(file_header.e_shoff)
|
||||
+ static_cast<std::streamoff>(file_header.e_shstrndx) * sizeof(section_header);
|
||||
file.seekg(shstr_off, std::ios_base::beg);
|
||||
|
||||
if (!file.read(reinterpret_cast<char*>(§ion_header), sizeof(section_header))) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<char> shstrtab(static_cast<std::size_t>(section_header.sh_size));
|
||||
|
||||
file.seekg(section_header.sh_offset, std::ios_base::beg);
|
||||
|
||||
if (!file.read(shstrtab.data(), static_cast<std::streamsize>(shstrtab.size()))) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
for (std::uint16_t i = 0; i < file_header.e_shnum; ++i)
|
||||
{
|
||||
decltype(section_header) current_section{};
|
||||
const std::streamoff off = static_cast<std::streamoff>(file_header.e_shoff)
|
||||
+ static_cast<std::streamoff>(i) * sizeof(current_section);
|
||||
|
||||
file.seekg(off, std::ios_base::beg);
|
||||
if (!file.read(reinterpret_cast<char*>(¤t_section), sizeof(current_section))) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
if (current_section.sh_name >= shstrtab.size()) [[unlikely]]
|
||||
continue;
|
||||
|
||||
// ReSharper disable once CppTooWideScopeInitStatement
|
||||
const std::string_view name = &shstrtab[current_section.sh_name];
|
||||
if (section_name != name)
|
||||
continue;
|
||||
|
||||
ExtractedSection out;
|
||||
|
||||
out.virtual_base_addr = static_cast<std::uintptr_t>(current_section.sh_addr);
|
||||
out.raw_base_addr = static_cast<std::uintptr_t>(current_section.sh_offset);
|
||||
out.data.resize(static_cast<std::size_t>(current_section.sh_size));
|
||||
|
||||
file.seekg(static_cast<std::streamoff>(out.raw_base_addr), 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;
|
||||
},
|
||||
elf_headers);
|
||||
}
|
||||
|
||||
template<class FileHeader, class SectionHeader>
|
||||
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* file_header = reinterpret_cast<const FileHeader*>(base);
|
||||
|
||||
const auto shoff = static_cast<std::size_t>(file_header->e_shoff);
|
||||
const auto shnum = static_cast<std::size_t>(file_header->e_shnum);
|
||||
const auto shstrnd = static_cast<std::size_t>(file_header->e_shstrndx);
|
||||
|
||||
const auto shstrtab_off = shoff + shstrnd * sizeof(SectionHeader);
|
||||
const auto* shstrtab_hdr = reinterpret_cast<const SectionHeader*>(base + shstrtab_off);
|
||||
|
||||
const auto shstrtab = reinterpret_cast<const char*>(base + static_cast<std::size_t>(shstrtab_hdr->sh_offset));
|
||||
|
||||
const auto shstrtab_size = static_cast<std::size_t>(shstrtab_hdr->sh_size);
|
||||
|
||||
for (std::size_t i = 0; i < shnum; ++i)
|
||||
{
|
||||
const auto section_off = shoff + i * sizeof(SectionHeader);
|
||||
const auto* section = reinterpret_cast<const SectionHeader*>(base + section_off);
|
||||
|
||||
if (section->sh_size == 0)
|
||||
continue;
|
||||
|
||||
if (std::cmp_greater_equal(section->sh_name, shstrtab_size))
|
||||
continue;
|
||||
|
||||
if (std::string_view{shstrtab + section->sh_name} != target_section_name)
|
||||
continue;
|
||||
|
||||
const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr);
|
||||
const auto* section_end = section_begin + static_cast<std::size_t>(section->sh_size);
|
||||
|
||||
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
|
||||
if (scan_result == section_end)
|
||||
return std::nullopt;
|
||||
|
||||
return reinterpret_cast<std::uintptr_t>(scan_result);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace
|
||||
namespace omath
|
||||
{
|
||||
std::optional<std::uintptr_t>
|
||||
ElfPatternScanner::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);
|
||||
|
||||
// Validate ELF signature.
|
||||
constexpr std::string_view valid_elf_signature = "\x7F"
|
||||
"ELF";
|
||||
if (std::string_view{reinterpret_cast<const char*>(base), valid_elf_signature.size()} != valid_elf_signature)
|
||||
return std::nullopt;
|
||||
|
||||
// Detect architecture.
|
||||
const auto ei_class_value = static_cast<uint8_t>(base[ei_class]);
|
||||
const auto arch = ei_class_value == elfclass64 ? FileArch::x64
|
||||
: ei_class_value == elfclass32 ? FileArch::x32
|
||||
: std::optional<FileArch>{};
|
||||
if (!arch.has_value()) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
if (arch == FileArch::x64)
|
||||
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address),
|
||||
pattern, target_section_name);
|
||||
if (arch == FileArch::x32)
|
||||
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address),
|
||||
pattern, target_section_name);
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
std::optional<SectionScanResult>
|
||||
ElfPatternScanner::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 pe_section = get_elf_section_by_name(path_to_file, target_section_name);
|
||||
|
||||
if (!pe_section.has_value()) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
const auto scan_result =
|
||||
PatternScanner::scan_for_pattern(pe_section->data.cbegin(), pe_section->data.cend(), pattern);
|
||||
|
||||
if (scan_result == pe_section->data.cend())
|
||||
return std::nullopt;
|
||||
const auto offset = std::distance(pe_section->data.begin(), scan_result);
|
||||
|
||||
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
|
||||
.raw_base_addr = pe_section->raw_base_addr,
|
||||
.target_offset = offset};
|
||||
}
|
||||
} // namespace omath
|
||||
@@ -237,10 +237,10 @@ namespace
|
||||
variant);
|
||||
}
|
||||
|
||||
struct ExtractedSection
|
||||
struct ExtractedSection final
|
||||
{
|
||||
std::uint64_t virtual_base_addr;
|
||||
std::uint64_t raw_base_addr;
|
||||
std::uintptr_t virtual_base_addr;
|
||||
std::uintptr_t raw_base_addr;
|
||||
std::vector<std::byte> data;
|
||||
};
|
||||
|
||||
@@ -261,7 +261,7 @@ namespace
|
||||
|
||||
const auto nt_headers = get_nt_header_from_file(file, dos_header);
|
||||
|
||||
if (!nt_headers)
|
||||
if (!nt_headers) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
if (invalid_nt_header_file(nt_headers.value())) [[unlikely]]
|
||||
@@ -290,10 +290,11 @@ namespace
|
||||
file.seekg(current_section.ptr_raw_data, std::ios::beg);
|
||||
file.read(reinterpret_cast<char*>(section_data.data()),
|
||||
static_cast<std::streamsize>(section_data.size()));
|
||||
return ExtractedSection{.virtual_base_addr = current_section.virtual_address
|
||||
+ concrete_headers.optional_header.image_base,
|
||||
.raw_base_addr = current_section.ptr_raw_data,
|
||||
.data = std::move(section_data)};
|
||||
return ExtractedSection{
|
||||
.virtual_base_addr = static_cast<std::uintptr_t>(
|
||||
current_section.virtual_address + concrete_headers.optional_header.image_base),
|
||||
.raw_base_addr = static_cast<std::uintptr_t>(current_section.ptr_raw_data),
|
||||
.data = std::move(section_data)};
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
@@ -304,46 +305,71 @@ namespace
|
||||
namespace omath
|
||||
{
|
||||
|
||||
std::optional<std::uintptr_t> PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
|
||||
const std::string_view& pattern)
|
||||
std::optional<std::uintptr_t>
|
||||
PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
|
||||
const std::string_view& pattern,
|
||||
const std::string_view& target_section_name)
|
||||
{
|
||||
const auto base_address = reinterpret_cast<std::uintptr_t>(module_base_address);
|
||||
const auto* base_bytes = static_cast<const std::byte*>(module_base_address);
|
||||
|
||||
if (!base_address)
|
||||
return std::nullopt;
|
||||
|
||||
auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address);
|
||||
const auto* dos_header = static_cast<const DosHeader*>(module_base_address);
|
||||
|
||||
if (!nt_header_variant)
|
||||
if (invalid_dos_header_file(*dos_header)) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
const auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address);
|
||||
|
||||
if (!nt_header_variant) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
return std::visit(
|
||||
[base_address, &pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
|
||||
[base_bytes, base_address, lfanew = dos_header->e_lfanew, &target_section_name,
|
||||
&pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
|
||||
{
|
||||
// Define .code segment as scan area
|
||||
const auto start = nt_header.optional_header.base_of_code;
|
||||
const auto scan_size = nt_header.optional_header.size_code;
|
||||
constexpr std::size_t signature_size = sizeof(nt_header.signature);
|
||||
const auto section_table_off = static_cast<std::size_t>(lfanew) + signature_size
|
||||
+ sizeof(FileHeader) + nt_header.file_header.size_optional_header;
|
||||
|
||||
const auto scan_range = std::span{reinterpret_cast<std::byte*>(base_address) + start, scan_size};
|
||||
const auto* section_table = reinterpret_cast<const SectionHeader*>(base_bytes + section_table_off);
|
||||
|
||||
// ReSharper disable once CppTooWideScopeInitStatement
|
||||
const auto result = PatternScanner::scan_for_pattern(scan_range, pattern);
|
||||
for (std::size_t i = 0; i < nt_header.file_header.num_sections; ++i)
|
||||
{
|
||||
const auto* section = section_table + i;
|
||||
|
||||
if (result != scan_range.end())
|
||||
return reinterpret_cast<std::uintptr_t>(&*result);
|
||||
if (std::string_view{section->name} != target_section_name || section->size_raw_data == 0)
|
||||
continue;
|
||||
|
||||
const auto section_size = section->virtual_size != 0
|
||||
? static_cast<std::size_t>(section->virtual_size)
|
||||
: static_cast<std::size_t>(section->size_raw_data);
|
||||
|
||||
const auto* section_begin =
|
||||
reinterpret_cast<std::byte*>(base_address + section->virtual_address);
|
||||
const auto scan_range = std::span{section_begin, section_size};
|
||||
|
||||
const auto result =
|
||||
PatternScanner::scan_for_pattern(scan_range.begin(), scan_range.end(), pattern);
|
||||
|
||||
if (result != scan_range.end())
|
||||
return reinterpret_cast<std::uintptr_t>(&*result);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
},
|
||||
nt_header_variant.value());
|
||||
}
|
||||
std::optional<PeSectionScanResult>
|
||||
std::optional<SectionScanResult>
|
||||
PePatternScanner::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 pe_section = extract_section_from_pe_file(path_to_file, target_section_name);
|
||||
|
||||
if (!pe_section.has_value())
|
||||
if (!pe_section.has_value()) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
const auto scan_result =
|
||||
@@ -353,8 +379,8 @@ namespace omath
|
||||
return std::nullopt;
|
||||
const auto offset = std::distance(pe_section->data.begin(), scan_result);
|
||||
|
||||
return PeSectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
|
||||
.raw_base_addr = pe_section->raw_base_addr,
|
||||
.target_offset = offset};
|
||||
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
|
||||
.raw_base_addr = pe_section->raw_base_addr,
|
||||
.target_offset = offset};
|
||||
}
|
||||
} // namespace omath
|
||||
@@ -7,19 +7,32 @@ include(GoogleTest)
|
||||
file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
|
||||
add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES})
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
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}"
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED ON)
|
||||
set_target_properties(
|
||||
${PROJECT_NAME}
|
||||
PROPERTIES 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}"
|
||||
CXX_STANDARD 23
|
||||
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)
|
||||
else() # GTest is being linked as vcpkg package
|
||||
find_package(GTest CONFIG REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE GTest::gtest GTest::gtest_main omath::omath)
|
||||
endif()
|
||||
gtest_discover_tests(${PROJECT_NAME})
|
||||
|
||||
if(OMATH_ENABLE_COVERAGE)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Coverage.cmake)
|
||||
omath_setup_coverage(${PROJECT_NAME})
|
||||
endif()
|
||||
|
||||
if(OMATH_ENABLE_VALGRIND)
|
||||
omath_setup_valgrind(${PROJECT_NAME})
|
||||
endif()
|
||||
|
||||
# Skip test discovery for Android/iOS builds or when cross-compiling - binaries
|
||||
# cannot run on host
|
||||
if(NOT (ANDROID OR IOS OR EMSCRIPTEN))
|
||||
gtest_discover_tests(${PROJECT_NAME})
|
||||
endif()
|
||||
|
||||
297
tests/engines/unit_test_traits_engines.cpp
Normal file
297
tests/engines/unit_test_traits_engines.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
// Tests for engine trait headers to improve header coverage
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/engines/frostbite_engine/traits/pred_engine_trait.hpp>
|
||||
#include <omath/engines/frostbite_engine/traits/mesh_trait.hpp>
|
||||
#include <omath/engines/frostbite_engine/traits/camera_trait.hpp>
|
||||
|
||||
#include <omath/engines/iw_engine/traits/pred_engine_trait.hpp>
|
||||
#include <omath/engines/iw_engine/traits/mesh_trait.hpp>
|
||||
#include <omath/engines/iw_engine/traits/camera_trait.hpp>
|
||||
|
||||
#include <omath/engines/opengl_engine/traits/pred_engine_trait.hpp>
|
||||
#include <omath/engines/opengl_engine/traits/mesh_trait.hpp>
|
||||
#include <omath/engines/opengl_engine/traits/camera_trait.hpp>
|
||||
|
||||
#include <omath/engines/unity_engine/traits/pred_engine_trait.hpp>
|
||||
#include <omath/engines/unity_engine/traits/mesh_trait.hpp>
|
||||
#include <omath/engines/unity_engine/traits/camera_trait.hpp>
|
||||
|
||||
#include <omath/engines/unreal_engine/traits/pred_engine_trait.hpp>
|
||||
#include <omath/engines/unreal_engine/traits/mesh_trait.hpp>
|
||||
#include <omath/engines/unreal_engine/traits/camera_trait.hpp>
|
||||
|
||||
#include <omath/projectile_prediction/projectile.hpp>
|
||||
#include <omath/projectile_prediction/target.hpp>
|
||||
#include <optional>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
// Small helper to compare matrices roughly (templated to avoid concrete typedef)
|
||||
template<typename MatT>
|
||||
static void expect_matrix_near(const MatT& a, const MatT& b, float eps = 1e-5f)
|
||||
{
|
||||
for (std::size_t r = 0; r < 4; ++r)
|
||||
for (std::size_t c = 0; c < 4; ++c)
|
||||
EXPECT_NEAR(a.at(r, c), b.at(r, c), eps);
|
||||
}
|
||||
|
||||
// Generic tests for PredEngineTrait behaviour across engines
|
||||
TEST(TraitTests, Frostbite_Pred_And_Mesh_And_Camera)
|
||||
{
|
||||
namespace e = omath::frostbite_engine;
|
||||
|
||||
projectile_prediction::Projectile p;
|
||||
p.m_origin = {0.f, 0.f, 0.f};
|
||||
p.m_launch_speed = 10.f;
|
||||
p.m_gravity_scale = 1.f;
|
||||
|
||||
const auto pos = e::PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 9.81f);
|
||||
EXPECT_NEAR(pos.x, 0.f, 1e-4f);
|
||||
EXPECT_NEAR(pos.z, 10.f, 1e-4f);
|
||||
EXPECT_NEAR(pos.y, -9.81f * 0.5f, 1e-4f);
|
||||
|
||||
projectile_prediction::Target t;
|
||||
t.m_origin = {0.f, 5.f, 0.f};
|
||||
t.m_velocity = {2.f, 0.f, 0.f};
|
||||
t.m_is_airborne = true;
|
||||
const auto pred = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred.x, 4.f, 1e-6f);
|
||||
EXPECT_NEAR(pred.y, 5.f - 9.81f * (2.f * 2.f) * 0.5f, 1e-6f);
|
||||
|
||||
// Also test non-airborne path (no gravity applied)
|
||||
t.m_is_airborne = false;
|
||||
const auto pred_ground = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred_ground.x, 4.f, 1e-6f);
|
||||
EXPECT_NEAR(pred_ground.y, 5.f, 1e-6f);
|
||||
|
||||
EXPECT_NEAR(e::PredEngineTrait::calc_vector_2d_distance({3.f, 0.f, 4.f}), 5.f, 1e-6f);
|
||||
EXPECT_NEAR(e::PredEngineTrait::get_vector_height_coordinate({1.f, 2.5f, 3.f}), 2.5f, 1e-6f);
|
||||
|
||||
std::optional<float> pitch = 45.f;
|
||||
auto vp = e::PredEngineTrait::calc_viewpoint_from_angles(p, {10.f, 0.f, 0.f}, pitch);
|
||||
EXPECT_NEAR(vp.y, 0.f + 10.f * std::tan(angles::degrees_to_radians(45.f)), 1e-6f);
|
||||
|
||||
// Direct angles
|
||||
Vector3<float> origin{0.f, 0.f, 0.f};
|
||||
Vector3<float> view_to{0.f, 1.f, 1.f};
|
||||
const auto pitch_calc = e::PredEngineTrait::calc_direct_pitch_angle(origin, view_to);
|
||||
const auto dir = (view_to - origin).normalized();
|
||||
EXPECT_NEAR(pitch_calc, angles::radians_to_degrees(std::asin(dir.y)), 1e-3f);
|
||||
|
||||
const auto yaw_calc = e::PredEngineTrait::calc_direct_yaw_angle(origin, view_to);
|
||||
EXPECT_NEAR(yaw_calc, angles::radians_to_degrees(std::atan2(dir.x, dir.z)), 1e-3f);
|
||||
|
||||
// MeshTrait simply forwards to rotation_matrix; ensure it compiles and returns something
|
||||
e::ViewAngles va;
|
||||
const auto m1 = e::MeshTrait::rotation_matrix(va);
|
||||
const auto m2 = e::rotation_matrix(va);
|
||||
expect_matrix_near(m1, m2);
|
||||
|
||||
// CameraTrait look at should be callable
|
||||
const auto angles = e::CameraTrait::calc_look_at_angle({0, 0, 0}, {0, 1, 1});
|
||||
(void)angles;
|
||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f);
|
||||
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
||||
expect_matrix_near(proj, expected);
|
||||
}
|
||||
|
||||
TEST(TraitTests, IW_Pred_And_Mesh_And_Camera)
|
||||
{
|
||||
namespace e = omath::iw_engine;
|
||||
|
||||
projectile_prediction::Projectile p;
|
||||
p.m_origin = {0.f, 0.f, 0.f};
|
||||
p.m_launch_speed = 10.f;
|
||||
p.m_gravity_scale = 1.f;
|
||||
|
||||
const auto pos = e::PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 9.81f);
|
||||
EXPECT_NEAR(pos.x, 10.f, 1e-4f);
|
||||
EXPECT_NEAR(pos.z, -9.81f * 0.5f, 1e-4f);
|
||||
|
||||
projectile_prediction::Target t;
|
||||
t.m_origin = {0.f, 0.f, 5.f};
|
||||
t.m_velocity = {0.f, 0.f, 2.f};
|
||||
t.m_is_airborne = true;
|
||||
const auto pred = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
// predicted = origin + velocity * t -> z = 5 + 2*2 = 9; then gravity applied
|
||||
EXPECT_NEAR(pred.z, 9.f - 9.81f * (2.f * 2.f) * 0.5f, 1e-6f);
|
||||
|
||||
EXPECT_NEAR(e::PredEngineTrait::calc_vector_2d_distance({3.f, 4.f, 0.f}), 5.f, 1e-6f);
|
||||
EXPECT_NEAR(e::PredEngineTrait::get_vector_height_coordinate({1.f, 2.5f, 3.f}), 3.f, 1e-6f);
|
||||
|
||||
std::optional<float> pitch = 45.f;
|
||||
auto vp = e::PredEngineTrait::calc_viewpoint_from_angles(p, {10.f, 0.f, 0.f}, pitch);
|
||||
EXPECT_NEAR(vp.z, 0.f + 10.f * std::tan(angles::degrees_to_radians(45.f)), 1e-6f);
|
||||
|
||||
Vector3<float> origin{0.f, 0.f, 0.f};
|
||||
Vector3<float> view_to{1.f, 1.f, 1.f};
|
||||
const auto pitch_calc = e::PredEngineTrait::calc_direct_pitch_angle(origin, view_to);
|
||||
const auto dist = origin.distance_to(view_to);
|
||||
EXPECT_NEAR(pitch_calc, angles::radians_to_degrees(std::asin((view_to.z - origin.z) / dist)), 1e-3f);
|
||||
|
||||
const auto yaw_calc = e::PredEngineTrait::calc_direct_yaw_angle(origin, view_to);
|
||||
const auto delta = view_to - origin;
|
||||
EXPECT_NEAR(yaw_calc, angles::radians_to_degrees(std::atan2(delta.y, delta.x)), 1e-3f);
|
||||
|
||||
e::ViewAngles va;
|
||||
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
||||
|
||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(45.f), {1920.f, 1080.f}, 0.1f, 1000.f);
|
||||
const auto expected = e::calc_perspective_projection_matrix(45.f, 1920.f / 1080.f, 0.1f, 1000.f);
|
||||
expect_matrix_near(proj, expected);
|
||||
|
||||
// non-airborne
|
||||
t.m_is_airborne = false;
|
||||
const auto pred_ground_iw = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred_ground_iw.z, 9.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(TraitTests, OpenGL_Pred_And_Mesh_And_Camera)
|
||||
{
|
||||
namespace e = omath::opengl_engine;
|
||||
|
||||
projectile_prediction::Projectile p;
|
||||
p.m_origin = {0.f, 0.f, 0.f};
|
||||
p.m_launch_speed = 10.f;
|
||||
p.m_gravity_scale = 1.f;
|
||||
|
||||
const auto pos = e::PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 9.81f);
|
||||
EXPECT_NEAR(pos.z, -10.f, 1e-4f);
|
||||
EXPECT_NEAR(pos.y, -9.81f * 0.5f, 1e-4f);
|
||||
|
||||
projectile_prediction::Target t;
|
||||
t.m_origin = {0.f, 5.f, 0.f};
|
||||
t.m_velocity = {2.f, 0.f, 0.f};
|
||||
t.m_is_airborne = true;
|
||||
const auto pred = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred.x, 4.f, 1e-6f);
|
||||
EXPECT_NEAR(pred.y, 5.f - 9.81f * (2.f * 2.f) * 0.5f, 1e-6f);
|
||||
|
||||
EXPECT_NEAR(e::PredEngineTrait::calc_vector_2d_distance({3.f, 0.f, 4.f}), 5.f, 1e-6f);
|
||||
EXPECT_NEAR(e::PredEngineTrait::get_vector_height_coordinate({1.f, 2.5f, 3.f}), 2.5f, 1e-6f);
|
||||
|
||||
std::optional<float> pitch = 45.f;
|
||||
auto vp = e::PredEngineTrait::calc_viewpoint_from_angles(p, {10.f, 0.f, 0.f}, pitch);
|
||||
EXPECT_NEAR(vp.y, 0.f + 10.f * std::tan(angles::degrees_to_radians(45.f)), 1e-6f);
|
||||
|
||||
Vector3<float> origin{0.f, 0.f, 0.f};
|
||||
Vector3<float> view_to{0.f, 1.f, 1.f};
|
||||
const auto pitch_calc = e::PredEngineTrait::calc_direct_pitch_angle(origin, view_to);
|
||||
const auto dir = (view_to - origin).normalized();
|
||||
EXPECT_NEAR(pitch_calc, angles::radians_to_degrees(std::asin(dir.y)), 1e-3f);
|
||||
|
||||
const auto yaw_calc = e::PredEngineTrait::calc_direct_yaw_angle(origin, view_to);
|
||||
EXPECT_NEAR(yaw_calc, angles::radians_to_degrees(-std::atan2(dir.x, -dir.z)), 1e-3f);
|
||||
|
||||
e::ViewAngles va;
|
||||
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
||||
|
||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f);
|
||||
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
||||
expect_matrix_near(proj, expected);
|
||||
|
||||
// non-airborne
|
||||
t.m_is_airborne = false;
|
||||
const auto pred_ground_gl = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred_ground_gl.x, 4.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(TraitTests, Unity_Pred_And_Mesh_And_Camera)
|
||||
{
|
||||
namespace e = omath::unity_engine;
|
||||
|
||||
projectile_prediction::Projectile p;
|
||||
p.m_origin = {0.f, 0.f, 0.f};
|
||||
p.m_launch_speed = 10.f;
|
||||
p.m_gravity_scale = 1.f;
|
||||
|
||||
const auto pos = e::PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 9.81f);
|
||||
EXPECT_NEAR(pos.z, 10.f, 1e-4f);
|
||||
EXPECT_NEAR(pos.y, -9.81f * 0.5f, 1e-4f);
|
||||
|
||||
projectile_prediction::Target t;
|
||||
t.m_origin = {0.f, 5.f, 0.f};
|
||||
t.m_velocity = {2.f, 0.f, 0.f};
|
||||
t.m_is_airborne = true;
|
||||
const auto pred = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred.x, 4.f, 1e-6f);
|
||||
EXPECT_NEAR(pred.y, 5.f - 9.81f * (2.f * 2.f) * 0.5f, 1e-6f);
|
||||
|
||||
EXPECT_NEAR(e::PredEngineTrait::calc_vector_2d_distance({3.f, 0.f, 4.f}), 5.f, 1e-6f);
|
||||
EXPECT_NEAR(e::PredEngineTrait::get_vector_height_coordinate({1.f, 2.5f, 3.f}), 2.5f, 1e-6f);
|
||||
|
||||
std::optional<float> pitch = 45.f;
|
||||
auto vp = e::PredEngineTrait::calc_viewpoint_from_angles(p, {10.f, 0.f, 0.f}, pitch);
|
||||
EXPECT_NEAR(vp.y, 0.f + 10.f * std::tan(angles::degrees_to_radians(45.f)), 1e-6f);
|
||||
|
||||
Vector3<float> origin{0.f, 0.f, 0.f};
|
||||
Vector3<float> view_to{0.f, 1.f, 1.f};
|
||||
const auto pitch_calc = e::PredEngineTrait::calc_direct_pitch_angle(origin, view_to);
|
||||
const auto dir = (view_to - origin).normalized();
|
||||
EXPECT_NEAR(pitch_calc, angles::radians_to_degrees(std::asin(dir.y)), 1e-3f);
|
||||
|
||||
const auto yaw_calc = e::PredEngineTrait::calc_direct_yaw_angle(origin, view_to);
|
||||
EXPECT_NEAR(yaw_calc, angles::radians_to_degrees(std::atan2(dir.x, dir.z)), 1e-3f);
|
||||
|
||||
e::ViewAngles va;
|
||||
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
||||
|
||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f);
|
||||
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
||||
expect_matrix_near(proj, expected);
|
||||
|
||||
// non-airborne
|
||||
t.m_is_airborne = false;
|
||||
const auto pred_ground_unity = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred_ground_unity.x, 4.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(TraitTests, Unreal_Pred_And_Mesh_And_Camera)
|
||||
{
|
||||
namespace e = omath::unreal_engine;
|
||||
|
||||
projectile_prediction::Projectile p;
|
||||
p.m_origin = {0.f, 0.f, 0.f};
|
||||
p.m_launch_speed = 10.f;
|
||||
p.m_gravity_scale = 1.f;
|
||||
|
||||
const auto pos = e::PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 9.81f);
|
||||
EXPECT_NEAR(pos.x, 10.f, 1e-4f);
|
||||
EXPECT_NEAR(pos.y, -9.81f * 0.5f, 1e-4f);
|
||||
|
||||
projectile_prediction::Target t;
|
||||
t.m_origin = {0.f, 5.f, 0.f};
|
||||
t.m_velocity = {2.f, 0.f, 0.f};
|
||||
t.m_is_airborne = true;
|
||||
const auto pred = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred.x, 4.f, 1e-6f);
|
||||
EXPECT_NEAR(pred.y, 5.f - 9.81f * (2.f * 2.f) * 0.5f, 1e-6f);
|
||||
|
||||
EXPECT_NEAR(e::PredEngineTrait::calc_vector_2d_distance({3.f, 0.f, 4.f}), 5.f, 1e-6f);
|
||||
EXPECT_NEAR(e::PredEngineTrait::get_vector_height_coordinate({1.f, 2.5f, 3.f}), 2.5f, 1e-6f);
|
||||
|
||||
std::optional<float> pitch = 45.f;
|
||||
auto vp = e::PredEngineTrait::calc_viewpoint_from_angles(p, {10.f, 0.f, 0.f}, pitch);
|
||||
EXPECT_NEAR(vp.z, 0.f + 10.f * std::tan(angles::degrees_to_radians(45.f)), 1e-6f);
|
||||
|
||||
Vector3<float> origin{0.f, 0.f, 0.f};
|
||||
Vector3<float> view_to{1.f, 1.f, 1.f};
|
||||
const auto pitch_calc = e::PredEngineTrait::calc_direct_pitch_angle(origin, view_to);
|
||||
const auto dir = (view_to - origin).normalized();
|
||||
EXPECT_NEAR(pitch_calc, angles::radians_to_degrees(std::asin(dir.z)), 1e-3f);
|
||||
|
||||
const auto yaw_calc = e::PredEngineTrait::calc_direct_yaw_angle(origin, view_to);
|
||||
EXPECT_NEAR(yaw_calc, angles::radians_to_degrees(std::atan2(dir.y, dir.x)), 1e-3f);
|
||||
|
||||
e::ViewAngles va;
|
||||
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
||||
|
||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f);
|
||||
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
||||
expect_matrix_near(proj, expected);
|
||||
|
||||
// non-airborne
|
||||
t.m_is_airborne = false;
|
||||
const auto pred_ground_unreal = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred_ground_unreal.x, 4.f, 1e-6f);
|
||||
}
|
||||
@@ -82,6 +82,18 @@ TEST(unit_test_unreal_engine, ProjectTargetMovedFromCamera)
|
||||
EXPECT_NEAR(projected->y, 360, 0.00001f);
|
||||
}
|
||||
}
|
||||
TEST(unit_test_unreal_engine, ProjectTargetMovedFromCameraBehind)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
|
||||
const auto cam = omath::unreal_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.01f, 10000.f);
|
||||
|
||||
for (float distance = 0.02f; distance < 9000.f; distance += 100.f)
|
||||
{
|
||||
const auto projected = cam.world_to_screen({-distance, 0, 0});
|
||||
|
||||
EXPECT_FALSE(projected.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(unit_test_unreal_engine, CameraSetAndGetFov)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,128 @@
|
||||
//
|
||||
// Created by Vlad on 18.08.2024.
|
||||
//
|
||||
// Extra unit tests for the project's A* implementation
|
||||
#include <array>
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/pathfinding/a_star.hpp>
|
||||
#include <omath/pathfinding/navigation_mesh.hpp>
|
||||
#include <utility>
|
||||
|
||||
using namespace omath;
|
||||
using namespace omath::pathfinding;
|
||||
|
||||
TEST(AStarExtra, TrivialNeighbor)
|
||||
{
|
||||
NavigationMesh nav;
|
||||
Vector3<float> v1{0.f, 0.f, 0.f};
|
||||
Vector3<float> v2{1.f, 0.f, 0.f};
|
||||
nav.m_vertex_map[v1] = {v2};
|
||||
nav.m_vertex_map[v2] = {v1};
|
||||
|
||||
const auto path = Astar::find_path(v1, v2, nav);
|
||||
ASSERT_EQ(path.size(), 1u);
|
||||
EXPECT_EQ(path.front(), v2);
|
||||
}
|
||||
|
||||
TEST(AStarExtra, StartEqualsGoal)
|
||||
{
|
||||
NavigationMesh nav;
|
||||
constexpr Vector3<float> v{1.f, 1.f, 0.f};
|
||||
nav.m_vertex_map[v] = {};
|
||||
|
||||
const auto path = Astar::find_path(v, v, nav);
|
||||
ASSERT_EQ(path.size(), 1u);
|
||||
EXPECT_EQ(path.front(), v);
|
||||
}
|
||||
|
||||
TEST(AStarExtra, BlockedNoPathBetweenTwoVertices)
|
||||
{
|
||||
NavigationMesh nav;
|
||||
constexpr Vector3<float> left{0.f, 0.f, 0.f};
|
||||
constexpr Vector3<float> right{2.f, 0.f, 0.f};
|
||||
// both vertices present but no connections
|
||||
nav.m_vertex_map[left] = {};
|
||||
nav.m_vertex_map[right] = {};
|
||||
|
||||
const auto path = Astar::find_path(left, right, nav);
|
||||
// disconnected vertices -> empty result
|
||||
EXPECT_TRUE(path.empty());
|
||||
}
|
||||
|
||||
TEST(AStarExtra, LongerPathAvoidsBlock)
|
||||
{
|
||||
NavigationMesh nav;
|
||||
// build 3x3 grid of vertices, block center (1,1)
|
||||
auto idx = [&](const int x, const int y)
|
||||
{ return Vector3<float>{static_cast<float>(x), static_cast<float>(y), 0.f}; };
|
||||
for (int y = 0; y < 3; ++y)
|
||||
{
|
||||
for (int x = 0; x < 3; ++x)
|
||||
{
|
||||
Vector3<float> v = idx(x, y);
|
||||
if (x == 1 && y == 1)
|
||||
continue; // center is omitted (blocked)
|
||||
std::vector<Vector3<float>> neigh;
|
||||
constexpr std::array<std::pair<int, int>, 4> offs{{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}};
|
||||
for (auto [dx, dy] : offs)
|
||||
{
|
||||
const int nx = x + dx, ny = y + dy;
|
||||
if (nx < 0 || nx >= 3 || ny < 0 || ny >= 3)
|
||||
continue;
|
||||
if (nx == 1 && ny == 1)
|
||||
continue; // neighbor is the blocked center
|
||||
neigh.push_back(idx(nx, ny));
|
||||
}
|
||||
nav.m_vertex_map[v] = neigh;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Vector3<float> start = idx(0, 1);
|
||||
constexpr Vector3<float> goal = idx(2, 1);
|
||||
const auto path = Astar::find_path(start, goal, nav);
|
||||
ASSERT_FALSE(path.empty());
|
||||
EXPECT_EQ(path.front(), goal); // Astar convention: single-element or endpoint present
|
||||
}
|
||||
|
||||
TEST(AstarTests, TrivialDirectNeighborPath)
|
||||
{
|
||||
NavigationMesh nav;
|
||||
// create two vertices directly connected
|
||||
Vector3<float> v1{0.f, 0.f, 0.f};
|
||||
Vector3<float> v2{1.f, 0.f, 0.f};
|
||||
nav.m_vertex_map.emplace(v1, std::vector<Vector3<float>>{v2});
|
||||
nav.m_vertex_map.emplace(v2, std::vector<Vector3<float>>{v1});
|
||||
|
||||
const auto path = Astar::find_path(v1, v2, nav);
|
||||
// Current A* implementation returns the end vertex as the reconstructed
|
||||
// path (single-element) in the simple neighbor scenario. Assert that the
|
||||
// endpoint is present and reachable.
|
||||
ASSERT_EQ(path.size(), 1u);
|
||||
EXPECT_EQ(path.front(), v2);
|
||||
}
|
||||
|
||||
TEST(AstarTests, NoPathWhenDisconnected)
|
||||
{
|
||||
NavigationMesh nav;
|
||||
Vector3<float> v1{0.f, 0.f, 0.f};
|
||||
constexpr Vector3<float> v2{10.f, 0.f, 0.f};
|
||||
// nav has only v1
|
||||
nav.m_vertex_map.emplace(v1, std::vector<Vector3<float>>{});
|
||||
|
||||
const auto path = Astar::find_path(v1, v2, nav);
|
||||
// When the nav mesh contains only the start vertex, the closest
|
||||
// vertex for both start and end will be the same vertex. In that
|
||||
// case Astar returns a single-element path with the start vertex.
|
||||
ASSERT_EQ(path.size(), 1u);
|
||||
EXPECT_EQ(path.front(), v1);
|
||||
}
|
||||
|
||||
TEST(AstarTests, EmptyNavReturnsNoPath)
|
||||
{
|
||||
const NavigationMesh nav;
|
||||
constexpr Vector3<float> v1{0.f, 0.f, 0.f};
|
||||
constexpr Vector3<float> v2{1.f, 0.f, 0.f};
|
||||
|
||||
const auto path = Astar::find_path(v1, v2, nav);
|
||||
EXPECT_TRUE(path.empty());
|
||||
}
|
||||
|
||||
TEST(unit_test_a_star, finding_right_path)
|
||||
{
|
||||
|
||||
@@ -13,11 +13,11 @@ namespace
|
||||
{
|
||||
|
||||
// Handy aliases (defaults: Type=float, [0,360], Normalized)
|
||||
using Deg = Angle<float, float(0), float(360), AngleFlags::Normalized>;
|
||||
using Pitch = Angle<float, float(-90), float(90), AngleFlags::Clamped>;
|
||||
using Turn = Angle<float, float(-180), float(180), AngleFlags::Normalized>;
|
||||
using Deg = Angle<float, static_cast<float>(0), static_cast<float>(360), AngleFlags::Normalized>;
|
||||
using Pitch = Angle<float, static_cast<float>(-90), static_cast<float>(90), AngleFlags::Clamped>;
|
||||
using Turn = Angle<float, static_cast<float>(-180), static_cast<float>(180), AngleFlags::Normalized>;
|
||||
|
||||
constexpr float kEps = 1e-5f;
|
||||
constexpr float k_eps = 1e-5f;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace
|
||||
|
||||
TEST(UnitTestAngle, DefaultConstructor_IsZeroDegrees)
|
||||
{
|
||||
Deg a; // default ctor
|
||||
constexpr Deg a; // default ctor
|
||||
EXPECT_FLOAT_EQ(*a, 0.0f);
|
||||
EXPECT_FLOAT_EQ(a.as_degrees(), 0.0f);
|
||||
}
|
||||
@@ -44,8 +44,8 @@ TEST(UnitTestAngle, FromDegrees_Normalized_WrapsBelowMin)
|
||||
|
||||
TEST(UnitTestAngle, FromDegrees_Clamped_ClampsToRange)
|
||||
{
|
||||
const Pitch hi = Pitch::from_degrees(100.0f);
|
||||
const Pitch lo = Pitch::from_degrees(-120.0f);
|
||||
constexpr Pitch hi = Pitch::from_degrees(100.0f);
|
||||
constexpr Pitch lo = Pitch::from_degrees(-120.0f);
|
||||
|
||||
EXPECT_FLOAT_EQ(hi.as_degrees(), 90.0f);
|
||||
EXPECT_FLOAT_EQ(lo.as_degrees(), -90.0f);
|
||||
@@ -80,8 +80,8 @@ TEST(UnitTestAngle, DereferenceReturnsDegrees)
|
||||
TEST(UnitTestAngle, SinCosTanCot_BasicCases)
|
||||
{
|
||||
const Deg a0 = Deg::from_degrees(0.0f);
|
||||
EXPECT_NEAR(a0.sin(), 0.0f, kEps);
|
||||
EXPECT_NEAR(a0.cos(), 1.0f, kEps);
|
||||
EXPECT_NEAR(a0.sin(), 0.0f, k_eps);
|
||||
EXPECT_NEAR(a0.cos(), 1.0f, k_eps);
|
||||
// cot(0) -> cos/sin -> div by 0: allow inf or nan
|
||||
const float cot0 = a0.cot();
|
||||
EXPECT_TRUE(std::isinf(cot0) || std::isnan(cot0));
|
||||
@@ -99,7 +99,7 @@ TEST(UnitTestAngle, Atan_IsAtanOfRadians)
|
||||
{
|
||||
// atan(as_radians). For 0° -> atan(0)=0.
|
||||
const Deg a0 = Deg::from_degrees(0.0f);
|
||||
EXPECT_NEAR(a0.atan(), 0.0f, kEps);
|
||||
EXPECT_NEAR(a0.atan(), 0.0f, k_eps);
|
||||
|
||||
const Deg a45 = Deg::from_degrees(45.0f);
|
||||
// atan(pi/4) ≈ 0.665773...
|
||||
|
||||
40
tests/general/unit_test_colider.cpp
Normal file
40
tests/general/unit_test_colider.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Created by Vlad on 11/9/2025.
|
||||
//
|
||||
#include "omath/engines/source_engine/collider.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/collision/mesh_collider.hpp>
|
||||
|
||||
TEST(UnitTestColider, CheckToWorld)
|
||||
{
|
||||
omath::source_engine::Mesh mesh = {
|
||||
std::vector<omath::primitives::Vertex<>>{
|
||||
{ { 1.f, 1.f, 1.f }, {}, {} },
|
||||
{ {-1.f, -1.f, -1.f }, {}, {} }
|
||||
},
|
||||
{}
|
||||
};
|
||||
mesh.set_origin({0, 2, 0});
|
||||
const omath::source_engine::MeshCollider collider(mesh);
|
||||
|
||||
const auto vertex = collider.find_abs_furthest_vertex_position({1.f, 0.f, 0.f});
|
||||
|
||||
EXPECT_EQ(vertex, omath::Vector3<float>(1.f, 3.f, 1.f));
|
||||
}
|
||||
|
||||
TEST(UnitTestColider, FindFurthestVertex)
|
||||
{
|
||||
const omath::source_engine::Mesh mesh = {
|
||||
{
|
||||
{ { 1.f, 1.f, 1.f }, {}, {} }, // position, normal, uv
|
||||
{ {-1.f, -1.f, -1.f }, {}, {} }
|
||||
},
|
||||
{}
|
||||
};
|
||||
const omath::source_engine::MeshCollider collider(mesh);
|
||||
const auto vertex = collider.find_furthest_vertex({1.f, 0.f, 0.f}).position;
|
||||
EXPECT_EQ(vertex, omath::Vector3<float>(1.f, 1.f, 1.f));
|
||||
}
|
||||
|
||||
|
||||
|
||||
107
tests/general/unit_test_collision_extra.cpp
Normal file
107
tests/general/unit_test_collision_extra.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// Extra collision tests: Simplex, MeshCollider, EPA
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/collision/epa_algorithm.hpp>
|
||||
#include <omath/collision/mesh_collider.hpp>
|
||||
#include <omath/collision/simplex.hpp>
|
||||
#include <omath/engines/source_engine/collider.hpp>
|
||||
|
||||
using namespace omath;
|
||||
using namespace omath::collision;
|
||||
|
||||
TEST(SimplexTest, HandleEmptySimplex)
|
||||
{
|
||||
Simplex<Vector3<float>> simplex;
|
||||
Vector3<float> direction{1, 0, 0};
|
||||
|
||||
EXPECT_EQ(simplex.size(), 0);
|
||||
EXPECT_FALSE(simplex.handle(direction));
|
||||
}
|
||||
|
||||
TEST(SimplexTest, HandleLineCollinearWithXAxis)
|
||||
{
|
||||
using Vec3 = Vector3<float>;
|
||||
Simplex<Vec3> simplex;
|
||||
|
||||
simplex.push_front(Vec3{1, 0, 0});
|
||||
simplex.push_front(Vec3{-1, 0, 0});
|
||||
|
||||
Vec3 direction{};
|
||||
std::ignore = simplex.handle(direction);
|
||||
|
||||
EXPECT_NEAR(direction.x, 0.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(CollisionExtra, SimplexLineHandle)
|
||||
{
|
||||
Simplex<Vector3<float>> s;
|
||||
s = {Vector3<float>{1.f, 0.f, 0.f}, Vector3<float>{2.f, 0.f, 0.f}};
|
||||
Vector3<float> dir{0, 0, 0};
|
||||
EXPECT_FALSE(s.handle(dir));
|
||||
// direction should not be zero
|
||||
EXPECT_GT(dir.length_sqr(), 0.0f);
|
||||
}
|
||||
|
||||
TEST(CollisionExtra, SimplexTriangleHandle)
|
||||
{
|
||||
Simplex<Vector3<float>> s;
|
||||
s = {Vector3<float>{1.f, 0.f, 0.f}, Vector3<float>{0.f, 1.f, 0.f}, Vector3<float>{0.f, 0.f, 1.f}};
|
||||
Vector3<float> dir{0, 0, 0};
|
||||
EXPECT_FALSE(s.handle(dir));
|
||||
EXPECT_GT(dir.length_sqr(), 0.0f);
|
||||
}
|
||||
|
||||
TEST(CollisionExtra, SimplexTetrahedronInside)
|
||||
{
|
||||
Simplex<Vector3<float>> s;
|
||||
// tetra that surrounds origin roughly
|
||||
s = {Vector3<float>{1.f, 0.f, 0.f}, Vector3<float>{0.f, 1.f, 0.f}, Vector3<float>{0.f, 0.f, 1.f},
|
||||
Vector3<float>{-1.f, -1.f, -1.f}};
|
||||
Vector3<float> dir{0, 0, 0};
|
||||
// if origin inside, handle returns true
|
||||
const bool inside = s.handle(dir);
|
||||
EXPECT_TRUE(inside);
|
||||
}
|
||||
|
||||
TEST(CollisionExtra, MeshColliderOriginAndFurthest)
|
||||
{
|
||||
source_engine::Mesh mesh = {
|
||||
std::vector<primitives::Vertex<>>{{{1.f, 1.f, 1.f}, {}, {}}, {{-1.f, -1.f, -1.f}, {}, {}}}, {}};
|
||||
mesh.set_origin({0, 2, 0});
|
||||
source_engine::MeshCollider collider(mesh);
|
||||
|
||||
EXPECT_EQ(collider.get_origin(), omath::Vector3<float>(0, 2, 0));
|
||||
collider.set_origin({1, 2, 3});
|
||||
EXPECT_EQ(collider.get_origin(), omath::Vector3<float>(1, 2, 3));
|
||||
|
||||
const auto v = collider.find_abs_furthest_vertex_position({1.f, 0.f, 0.f});
|
||||
// the original vertex at (1,1,1) translated by origin (1,2,3) becomes (2,3,4)
|
||||
EXPECT_EQ(v, omath::Vector3<float>(2.f, 3.f, 4.f));
|
||||
}
|
||||
|
||||
TEST(CollisionExtra, EPAConvergesOnSimpleCase)
|
||||
{
|
||||
// Build two simple colliders using simple meshes that overlap
|
||||
source_engine::Mesh meshA = {
|
||||
std::vector<primitives::Vertex<>>{{{0.f, 0.f, 0.f}, {}, {}}, {{1.f, 0.f, 0.f}, {}, {}}}, {}};
|
||||
source_engine::Mesh mesh_b = meshA;
|
||||
mesh_b.set_origin({0.5f, 0.f, 0.f}); // translate to overlap
|
||||
|
||||
source_engine::MeshCollider a(meshA);
|
||||
source_engine::MeshCollider b(mesh_b);
|
||||
|
||||
// Create a simplex that approximately contains the origin in Minkowski space
|
||||
Simplex<Vector3<float>> simplex;
|
||||
simplex = {omath::Vector3<float>{0.5f, 0.f, 0.f}, omath::Vector3<float>{-0.5f, 0.f, 0.f},
|
||||
omath::Vector3<float>{0.f, 0.5f, 0.f}, omath::Vector3<float>{0.f, -0.5f, 0.f}};
|
||||
|
||||
auto pool = std::pmr::monotonic_buffer_resource(1024);
|
||||
auto res = Epa<source_engine::MeshCollider>::solve(a, b, simplex, {}, pool);
|
||||
// EPA may or may not converge depending on numerics; ensure it returns optionally
|
||||
// but if it does, fields should be finite
|
||||
if (res.has_value())
|
||||
{
|
||||
auto r = *res;
|
||||
EXPECT_TRUE(std::isfinite(r.depth));
|
||||
EXPECT_GT(r.normal.length_sqr(), 0.0f);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
//
|
||||
// Created by Vlad on 01.09.2024.
|
||||
//
|
||||
#include <omath/utility/color.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
class unit_test_color : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
Color color1;
|
||||
Color color2;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
color1 = Color::red();
|
||||
color2 = Color::green();
|
||||
}
|
||||
};
|
||||
|
||||
// Test constructors
|
||||
TEST_F(unit_test_color, Constructor_Float)
|
||||
{
|
||||
constexpr Color color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
EXPECT_FLOAT_EQ(color.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(color.y, 0.5f);
|
||||
EXPECT_FLOAT_EQ(color.z, 0.5f);
|
||||
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||
}
|
||||
|
||||
TEST_F(unit_test_color, Constructor_Vector4)
|
||||
{
|
||||
constexpr omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f);
|
||||
constexpr Color color(vec);
|
||||
EXPECT_FLOAT_EQ(color.x, 0.2f);
|
||||
EXPECT_FLOAT_EQ(color.y, 0.4f);
|
||||
EXPECT_FLOAT_EQ(color.z, 0.6f);
|
||||
EXPECT_FLOAT_EQ(color.w, 0.8f);
|
||||
}
|
||||
|
||||
// Test static methods for color creation
|
||||
TEST_F(unit_test_color, FromRGBA)
|
||||
{
|
||||
constexpr Color color = Color::from_rgba(128, 64, 32, 255);
|
||||
EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f);
|
||||
EXPECT_FLOAT_EQ(color.y, 64.0f / 255.0f);
|
||||
EXPECT_FLOAT_EQ(color.z, 32.0f / 255.0f);
|
||||
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||
}
|
||||
|
||||
TEST_F(unit_test_color, FromHSV)
|
||||
{
|
||||
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.y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(color.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||
}
|
||||
|
||||
// Test HSV conversion
|
||||
TEST_F(unit_test_color, ToHSV)
|
||||
{
|
||||
const auto [hue, saturation, value] = color1.to_hsv(); // Red color
|
||||
EXPECT_FLOAT_EQ(hue, 0.0f);
|
||||
EXPECT_FLOAT_EQ(saturation, 1.0f);
|
||||
EXPECT_FLOAT_EQ(value, 1.0f);
|
||||
}
|
||||
|
||||
// Test color blending
|
||||
TEST_F(unit_test_color, Blend)
|
||||
{
|
||||
const Color blended = color1.blend(color2, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.y, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(blended.w, 1.0f);
|
||||
}
|
||||
|
||||
// Test predefined colors
|
||||
TEST_F(unit_test_color, PredefinedColors)
|
||||
{
|
||||
constexpr Color red = Color::red();
|
||||
constexpr Color green = Color::green();
|
||||
constexpr Color blue = Color::blue();
|
||||
|
||||
EXPECT_FLOAT_EQ(red.x, 1.0f);
|
||||
EXPECT_FLOAT_EQ(red.y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(red.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(red.w, 1.0f);
|
||||
|
||||
EXPECT_FLOAT_EQ(green.x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(green.y, 1.0f);
|
||||
EXPECT_FLOAT_EQ(green.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(green.w, 1.0f);
|
||||
|
||||
EXPECT_FLOAT_EQ(blue.x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(blue.y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(blue.z, 1.0f);
|
||||
EXPECT_FLOAT_EQ(blue.w, 1.0f);
|
||||
}
|
||||
|
||||
// Test non-member function: Blend for Vector3
|
||||
TEST_F(unit_test_color, BlendVector3)
|
||||
{
|
||||
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 blended = v1.blend(v2, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.y, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.z, 0.0f);
|
||||
}
|
||||
293
tests/general/unit_test_color_grouped.cpp
Normal file
293
tests/general/unit_test_color_grouped.cpp
Normal file
@@ -0,0 +1,293 @@
|
||||
// Combined color tests
|
||||
// This file merges multiple color-related unit test files into one grouped TU
|
||||
// to make the tests look more organized.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/color.hpp>
|
||||
#include <format>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
class UnitTestColorGrouped : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
Color color1;
|
||||
Color color2;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
color1 = Color::red();
|
||||
color2 = Color::green();
|
||||
}
|
||||
};
|
||||
|
||||
// From original unit_test_color.cpp
|
||||
TEST_F(UnitTestColorGrouped, Constructor_Float)
|
||||
{
|
||||
constexpr Color color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
EXPECT_FLOAT_EQ(color.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(color.y, 0.5f);
|
||||
EXPECT_FLOAT_EQ(color.z, 0.5f);
|
||||
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||
}
|
||||
|
||||
TEST_F(UnitTestColorGrouped, Constructor_Vector4)
|
||||
{
|
||||
constexpr omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f);
|
||||
constexpr Color color(vec);
|
||||
EXPECT_FLOAT_EQ(color.x, 0.2f);
|
||||
EXPECT_FLOAT_EQ(color.y, 0.4f);
|
||||
EXPECT_FLOAT_EQ(color.z, 0.6f);
|
||||
EXPECT_FLOAT_EQ(color.w, 0.8f);
|
||||
}
|
||||
|
||||
TEST_F(UnitTestColorGrouped, FromRGBA)
|
||||
{
|
||||
constexpr Color color = Color::from_rgba(128, 64, 32, 255);
|
||||
EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f);
|
||||
EXPECT_FLOAT_EQ(color.y, 64.0f / 255.0f);
|
||||
EXPECT_FLOAT_EQ(color.z, 32.0f / 255.0f);
|
||||
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||
}
|
||||
|
||||
TEST_F(UnitTestColorGrouped, FromHSV)
|
||||
{
|
||||
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.y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(color.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||
}
|
||||
|
||||
TEST_F(UnitTestColorGrouped, ToHSV)
|
||||
{
|
||||
const auto [hue, saturation, value] = color1.to_hsv(); // Red color
|
||||
EXPECT_FLOAT_EQ(hue, 0.0f);
|
||||
EXPECT_FLOAT_EQ(saturation, 1.0f);
|
||||
EXPECT_FLOAT_EQ(value, 1.0f);
|
||||
}
|
||||
|
||||
TEST_F(UnitTestColorGrouped, Blend)
|
||||
{
|
||||
const Color blended = color1.blend(color2, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.y, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(blended.w, 1.0f);
|
||||
}
|
||||
|
||||
TEST_F(UnitTestColorGrouped, PredefinedColors)
|
||||
{
|
||||
constexpr Color red = Color::red();
|
||||
constexpr Color green = Color::green();
|
||||
constexpr Color blue = Color::blue();
|
||||
|
||||
EXPECT_FLOAT_EQ(red.x, 1.0f);
|
||||
EXPECT_FLOAT_EQ(red.y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(red.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(red.w, 1.0f);
|
||||
|
||||
EXPECT_FLOAT_EQ(green.x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(green.y, 1.0f);
|
||||
EXPECT_FLOAT_EQ(green.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(green.w, 1.0f);
|
||||
|
||||
EXPECT_FLOAT_EQ(blue.x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(blue.y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(blue.z, 1.0f);
|
||||
EXPECT_FLOAT_EQ(blue.w, 1.0f);
|
||||
}
|
||||
|
||||
TEST_F(UnitTestColorGrouped, BlendVector3)
|
||||
{
|
||||
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 blended = v1.blend(v2, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.y, 0.5f);
|
||||
EXPECT_FLOAT_EQ(blended.z, 0.0f);
|
||||
}
|
||||
|
||||
// From unit_test_color_extra.cpp
|
||||
TEST(UnitTestColorGrouped_Extra, SetHueSaturationValue)
|
||||
{
|
||||
Color c = Color::red();
|
||||
const auto h1 = c.to_hsv();
|
||||
EXPECT_FLOAT_EQ(h1.hue, 0.f);
|
||||
|
||||
c.set_hue(0.5f);
|
||||
const auto h2 = c.to_hsv();
|
||||
EXPECT_NEAR(h2.hue, 0.5f, 1e-3f);
|
||||
|
||||
c = Color::from_hsv(0.25f, 0.8f, 0.6f);
|
||||
c.set_saturation(0.3f);
|
||||
const auto h3 = c.to_hsv();
|
||||
EXPECT_NEAR(h3.saturation, 0.3f, 1e-3f);
|
||||
|
||||
c.set_value(1.0f);
|
||||
const auto h4 = c.to_hsv();
|
||||
EXPECT_NEAR(h4.value, 1.0f, 1e-3f);
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_Extra, ToStringVariants)
|
||||
{
|
||||
constexpr Color c = Color::from_rgba(10, 20, 30, 255);
|
||||
auto s = c.to_string();
|
||||
EXPECT_NE(s.find("r:"), std::string::npos);
|
||||
|
||||
const auto ws = c.to_wstring();
|
||||
EXPECT_FALSE(ws.empty());
|
||||
|
||||
const auto u8 = c.to_u8string();
|
||||
EXPECT_FALSE(u8.empty());
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_Extra, BlendEdgeCases)
|
||||
{
|
||||
constexpr Color a = Color::red();
|
||||
constexpr Color b = Color::blue();
|
||||
constexpr auto r0 = a.blend(b, 0.f);
|
||||
EXPECT_FLOAT_EQ(r0.x, a.x);
|
||||
constexpr auto r1 = a.blend(b, 1.f);
|
||||
EXPECT_FLOAT_EQ(r1.x, b.x);
|
||||
}
|
||||
|
||||
// From unit_test_color_more.cpp
|
||||
TEST(UnitTestColorGrouped_More, DefaultCtorIsZero)
|
||||
{
|
||||
constexpr Color c;
|
||||
EXPECT_FLOAT_EQ(c.x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(c.y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(c.z, 0.0f);
|
||||
EXPECT_FLOAT_EQ(c.w, 0.0f);
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_More, FloatCtorAndClampForRGB)
|
||||
{
|
||||
constexpr Color c(1.2f, -0.5f, 0.5f, 2.0f);
|
||||
EXPECT_FLOAT_EQ(c.x, 1.0f);
|
||||
EXPECT_FLOAT_EQ(c.y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(c.z, 0.5f);
|
||||
EXPECT_FLOAT_EQ(c.w, 2.0f);
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_More, FromRgbaProducesScaledComponents)
|
||||
{
|
||||
constexpr Color c = Color::from_rgba(25u, 128u, 230u, 64u);
|
||||
EXPECT_NEAR(c.x, 25.0f/255.0f, 1e-6f);
|
||||
EXPECT_NEAR(c.y, 128.0f/255.0f, 1e-6f);
|
||||
EXPECT_NEAR(c.z, 230.0f/255.0f, 1e-6f);
|
||||
EXPECT_NEAR(c.w, 64.0f/255.0f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_More, BlendProducesIntermediate)
|
||||
{
|
||||
constexpr Color c0(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
constexpr Color c1(1.0f, 1.0f, 1.0f, 0.0f);
|
||||
constexpr Color mid = c0.blend(c1, 0.5f);
|
||||
EXPECT_FLOAT_EQ(mid.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(mid.y, 0.5f);
|
||||
EXPECT_FLOAT_EQ(mid.z, 0.5f);
|
||||
EXPECT_FLOAT_EQ(mid.w, 0.5f);
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_More, HsvRoundTrip)
|
||||
{
|
||||
constexpr Color red = Color::red();
|
||||
const auto hsv = red.to_hsv();
|
||||
const Color back = Color::from_hsv(hsv);
|
||||
EXPECT_NEAR(back.x, 1.0f, 1e-6f);
|
||||
EXPECT_NEAR(back.y, 0.0f, 1e-6f);
|
||||
EXPECT_NEAR(back.z, 0.0f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_More, ToStringContainsComponents)
|
||||
{
|
||||
constexpr Color c = Color::from_rgba(10, 20, 30, 40);
|
||||
std::string s = c.to_string();
|
||||
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);
|
||||
}
|
||||
|
||||
// From unit_test_color_more2.cpp
|
||||
TEST(UnitTestColorGrouped_More2, FromRgbaAndToString)
|
||||
{
|
||||
constexpr auto c = Color::from_rgba(255, 128, 0, 64);
|
||||
const auto s = c.to_string();
|
||||
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, FromHsvCases)
|
||||
{
|
||||
constexpr float eps = 1e-5f;
|
||||
|
||||
auto check_hue = [&](float h) {
|
||||
SCOPED_TRACE(::testing::Message() << "h=" << h);
|
||||
Color c = Color::from_hsv(h, 1.f, 1.f);
|
||||
EXPECT_TRUE(std::isfinite(c.x));
|
||||
EXPECT_TRUE(std::isfinite(c.y));
|
||||
EXPECT_TRUE(std::isfinite(c.z));
|
||||
EXPECT_GE(c.x, -eps);
|
||||
EXPECT_LE(c.x, 1.f + eps);
|
||||
EXPECT_GE(c.y, -eps);
|
||||
EXPECT_LE(c.y, 1.f + eps);
|
||||
EXPECT_GE(c.z, -eps);
|
||||
EXPECT_LE(c.z, 1.f + eps);
|
||||
|
||||
float mx = std::max({c.x, c.y, c.z});
|
||||
float mn = std::min({c.x, c.y, c.z});
|
||||
EXPECT_GE(mx, 0.999f);
|
||||
EXPECT_LE(mn, 1e-3f + 1e-4f);
|
||||
};
|
||||
|
||||
check_hue(0.f / 6.f);
|
||||
check_hue(1.f / 6.f);
|
||||
check_hue(2.f / 6.f);
|
||||
check_hue(3.f / 6.f);
|
||||
check_hue(4.f / 6.f);
|
||||
check_hue(5.f / 6.f);
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_More2, ToHsvAndSetters)
|
||||
{
|
||||
Color c{0.2f, 0.4f, 0.6f, 1.f};
|
||||
const auto hsv = c.to_hsv();
|
||||
EXPECT_NEAR(hsv.value, 0.6f, 1e-6f);
|
||||
|
||||
c.set_hue(0.0f);
|
||||
EXPECT_TRUE(std::isfinite(c.x));
|
||||
|
||||
c.set_saturation(0.0f);
|
||||
EXPECT_TRUE(std::isfinite(c.y));
|
||||
|
||||
c.set_value(0.5f);
|
||||
EXPECT_TRUE(std::isfinite(c.z));
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_More2, BlendAndStaticColors)
|
||||
{
|
||||
constexpr Color a = Color::red();
|
||||
constexpr Color b = Color::blue();
|
||||
constexpr auto mid = a.blend(b, 0.5f);
|
||||
EXPECT_GT(mid.x, 0.f);
|
||||
EXPECT_GT(mid.z, 0.f);
|
||||
|
||||
constexpr auto all_a = a.blend(b, -1.f);
|
||||
EXPECT_NEAR(all_a.x, a.x, 1e-6f);
|
||||
|
||||
constexpr auto all_b = a.blend(b, 2.f);
|
||||
EXPECT_NEAR(all_b.z, b.z, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(UnitTestColorGrouped_More2, FormatterUsesToString)
|
||||
{
|
||||
Color c = Color::from_rgba(10, 20, 30, 40);
|
||||
const auto formatted = std::format("{}", c);
|
||||
EXPECT_NE(formatted.find("r:10"), std::string::npos);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user