mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-13 23:13:26 +00:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
590
.github/workflows/cmake-multi-platform.yml
vendored
590
.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/
|
||||
|
||||
- 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,25 +183,80 @@ 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
|
||||
|
||||
- name: Install OpenCppCoverage with Chocolatey
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
run: choco install opencppcoverage -y
|
||||
|
||||
- name: Build Debug for Coverage
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --preset ${{ matrix.preset }} \
|
||||
-DOMATH_BUILD_TESTS=ON \
|
||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||
-DOMATH_ENABLE_COVERAGE=ON \
|
||||
-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 Coverage
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$env:Path = "C:\Program Files\OpenCppCoverage;$env:Path"
|
||||
cmake --build cmake-build/build/${{ matrix.preset }} --target coverage --config Debug
|
||||
|
||||
- name: Upload Coverage
|
||||
if: ${{ matrix.triplet == 'x64-windows' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report-windows
|
||||
path: cmake-build/build/${{ matrix.preset }}/coverage/
|
||||
|
||||
- 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: macOS (AppleClang)
|
||||
runs-on: macOS-latest
|
||||
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:
|
||||
@@ -111,12 +277,388 @@ jobs:
|
||||
|
||||
- name: Configure (cmake --preset)
|
||||
shell: bash
|
||||
run: cmake --preset darwin-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=${{ matrix.coverage == true && 'ON' || 'OFF' }} \
|
||||
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cmake --build cmake-build/build/darwin-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: |
|
||||
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
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,4 +2,7 @@
|
||||
/out
|
||||
*.DS_Store
|
||||
/extlibs/vcpkg
|
||||
.idea/workspace.xml
|
||||
.idea/workspace.xml
|
||||
/build/
|
||||
/clang-coverage/
|
||||
*.gcov
|
||||
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" />
|
||||
@@ -5,6 +5,7 @@ project(omath VERSION ${OMATH_VERSION} LANGUAGES CXX)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(cmake/Coverage.cmake)
|
||||
|
||||
if (MSVC)
|
||||
check_cxx_compiler_flag("/arch:AVX2" COMPILER_SUPPORTS_AVX2)
|
||||
@@ -23,7 +24,7 @@ option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OF
|
||||
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)
|
||||
|
||||
option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF)
|
||||
|
||||
if (VCPKG_MANIFEST_FEATURES)
|
||||
foreach (omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
|
||||
@@ -35,6 +36,8 @@ if (VCPKG_MANIFEST_FEATURES)
|
||||
set(OMATH_BUILD_TESTS ON)
|
||||
elseif (omath_feature STREQUAL "benchmark")
|
||||
set(OMATH_BUILD_BENCHMARK ON)
|
||||
elseif (omath_feature STREQUAL "examples")
|
||||
set(OMATH_BUILD_EXAMPLES ON)
|
||||
endif ()
|
||||
|
||||
endforeach ()
|
||||
@@ -133,11 +136,19 @@ if (OMATH_USE_AVX2)
|
||||
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)
|
||||
add_subdirectory(tests)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS)
|
||||
if(OMATH_ENABLE_COVERAGE)
|
||||
omath_setup_coverage(${PROJECT_NAME})
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
if (OMATH_BUILD_BENCHMARK)
|
||||
@@ -148,6 +159,7 @@ if (OMATH_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif ()
|
||||
|
||||
|
||||
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)
|
||||
@@ -186,7 +198,6 @@ install(EXPORT ${PROJECT_NAME}Targets
|
||||
DESTINATION lib/cmake/${PROJECT_NAME} COMPONENT ${PROJECT_NAME}
|
||||
)
|
||||
|
||||
|
||||
# Generate the omathConfigVersion.cmake file
|
||||
write_basic_package_version_file(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
|
||||
|
||||
@@ -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,57 +141,88 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"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++",
|
||||
"CMAKE_MAKE_PROGRAM": "ninja"
|
||||
"CMAKE_CXX_COMPILER": "clang++"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
@@ -137,47 +231,456 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-base-vcpkg",
|
||||
"name": "darwin-vcpkg-base",
|
||||
"hidden": true,
|
||||
"inherits": "darwin-base",
|
||||
"inherits": ["darwin-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": "darwin-debug",
|
||||
"displayName": "Darwin Debug",
|
||||
"inherits": "darwin-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-debug-vcpkg",
|
||||
"displayName": "Darwin Debug",
|
||||
"inherits": "darwin-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
"displayName": "macOS Debug",
|
||||
"inherits": ["darwin-base", "debug"]
|
||||
},
|
||||
{
|
||||
"name": "darwin-release",
|
||||
"displayName": "Darwin Release",
|
||||
"inherits": "darwin-base",
|
||||
"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-base-vcpkg",
|
||||
"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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||

|
||||

|
||||

|
||||

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

|
||||
[](https://repology.org/project/orange-math/versions)
|
||||
@@ -106,6 +107,10 @@ if (auto screen = camera.world_to_screen(world_position)) {
|
||||
|
||||
![TF2 Preview]
|
||||
|
||||
<br>
|
||||
|
||||
![OpenGL Preview]
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
@@ -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
|
||||
|
||||
122
cmake/Coverage.cmake
Normal file
122
cmake/Coverage.cmake
Normal file
@@ -0,0 +1,122 @@
|
||||
# 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|AppleClang")
|
||||
# Apply to ALL configs when coverage is enabled (not just Debug)
|
||||
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
|
||||
)
|
||||
|
||||
elseif(MSVC)
|
||||
target_compile_options(${TARGET_NAME} PRIVATE
|
||||
/Zi
|
||||
/Od
|
||||
/Ob0
|
||||
)
|
||||
target_link_options(${TARGET_NAME} PRIVATE
|
||||
/DEBUG:FULL
|
||||
/INCREMENTAL:NO
|
||||
)
|
||||
endif()
|
||||
|
||||
# Create coverage target only once
|
||||
if(TARGET coverage)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(MSVC OR MINGW)
|
||||
# Windows: OpenCppCoverage
|
||||
find_program(OPENCPPCOVERAGE_EXECUTABLE
|
||||
NAMES OpenCppCoverage OpenCppCoverage.exe
|
||||
PATHS
|
||||
"$ENV{ProgramFiles}/OpenCppCoverage"
|
||||
"$ENV{ProgramW6432}/OpenCppCoverage"
|
||||
"C:/Program Files/OpenCppCoverage"
|
||||
DOC "Path to OpenCppCoverage executable"
|
||||
)
|
||||
|
||||
if(NOT OPENCPPCOVERAGE_EXECUTABLE)
|
||||
message(WARNING "OpenCppCoverage not found. Install with: choco install opencppcoverage")
|
||||
set(OPENCPPCOVERAGE_EXECUTABLE "C:/Program Files/OpenCppCoverage/OpenCppCoverage.exe")
|
||||
else()
|
||||
message(STATUS "Found OpenCppCoverage: ${OPENCPPCOVERAGE_EXECUTABLE}")
|
||||
endif()
|
||||
|
||||
file(TO_NATIVE_PATH "${CMAKE_SOURCE_DIR}" COVERAGE_ROOT_PATH)
|
||||
file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/coverage" COVERAGE_OUTPUT_PATH)
|
||||
file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/coverage.xml" COVERAGE_XML_PATH)
|
||||
file(TO_NATIVE_PATH "${OPENCPPCOVERAGE_EXECUTABLE}" OPENCPPCOVERAGE_NATIVE)
|
||||
|
||||
add_custom_target(coverage
|
||||
DEPENDS unit_tests
|
||||
COMMAND "${OPENCPPCOVERAGE_NATIVE}"
|
||||
--verbose
|
||||
--sources "${COVERAGE_ROOT_PATH}"
|
||||
--modules "${COVERAGE_ROOT_PATH}"
|
||||
--excluded_sources "*\\tests\\*"
|
||||
--excluded_sources "*\\gtest\\*"
|
||||
--excluded_sources "*\\googletest\\*"
|
||||
--excluded_sources "*\\_deps\\*"
|
||||
--excluded_sources "*\\vcpkg_installed\\*"
|
||||
--export_type "html:${COVERAGE_OUTPUT_PATH}"
|
||||
--export_type "cobertura:${COVERAGE_XML_PATH}"
|
||||
--cover_children
|
||||
-- "$<TARGET_FILE:unit_tests>"
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Running OpenCppCoverage"
|
||||
)
|
||||
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
|
||||
# Linux/macOS: LLVM coverage via script
|
||||
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")
|
||||
# GCC: lcov/gcov
|
||||
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()
|
||||
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 |
@@ -1,9 +1,32 @@
|
||||
project(examples)
|
||||
|
||||
add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp)
|
||||
set_target_properties(example_projection_matrix_builder PROPERTIES CXX_STANDARD 26)
|
||||
set_target_properties(example_projection_matrix_builder PROPERTIES
|
||||
CXX_STANDARD 26
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
)
|
||||
target_link_libraries(example_projection_matrix_builder PRIVATE omath::omath)
|
||||
|
||||
add_executable(example_signature_scan example_signature_scan.cpp)
|
||||
set_target_properties(example_signature_scan PROPERTIES CXX_STANDARD 26)
|
||||
target_link_libraries(example_signature_scan PRIVATE omath::omath)
|
||||
set_target_properties(example_signature_scan PROPERTIES
|
||||
CXX_STANDARD 26
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
)
|
||||
target_link_libraries(example_signature_scan PRIVATE omath::omath)
|
||||
|
||||
|
||||
add_executable(example_glfw3 example_glfw3.cpp)
|
||||
set_target_properties(example_glfw3 PROPERTIES CXX_STANDARD 26
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||
)
|
||||
|
||||
find_package(GLEW REQUIRED)
|
||||
find_package(glfw3 CONFIG REQUIRED)
|
||||
target_link_libraries(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw)
|
||||
339
examples/example_glfw3.cpp
Normal file
339
examples/example_glfw3.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
#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);
|
||||
|
||||
// ---------- GLEW init ----------
|
||||
glewExperimental = GL_TRUE;
|
||||
GLenum glewErr = glewInit();
|
||||
if (glewErr != GLEW_OK)
|
||||
{
|
||||
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_vertex_array_object.size() * 3);
|
||||
for (const auto& tri : cube.m_vertex_array_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;
|
||||
}
|
||||
@@ -6,35 +6,57 @@
|
||||
#include <omath/linear_algebra/mat.hpp>
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace omath::primitives
|
||||
{
|
||||
template<class Mat4X4, class RotationAngles, class MeshTypeTrait, class Type = float>
|
||||
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 Mesh final
|
||||
{
|
||||
public:
|
||||
using NumericType = Type;
|
||||
using VectorType = VertType::VectorType;
|
||||
using VertexType = VertType;
|
||||
|
||||
private:
|
||||
using Vbo = std::vector<Vector3<NumericType>>;
|
||||
using Vao = std::vector<Vector3<std::size_t>>;
|
||||
using Vbo = std::vector<VertexType>;
|
||||
using Ebo = std::vector<Vector3<std::uint32_t>>;
|
||||
|
||||
public:
|
||||
Vbo m_vertex_buffer;
|
||||
Vao m_vertex_array_object;
|
||||
Ebo m_element_buffer_object;
|
||||
|
||||
Mesh(Vbo vbo, Vao vao, const Vector3<NumericType> scale = {1, 1, 1,})
|
||||
: m_vertex_buffer(std::move(vbo)), m_vertex_array_object(std::move(vao)), m_scale(std::move(scale))
|
||||
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 Vector3<NumericType>& new_origin)
|
||||
void set_origin(const VectorType& new_origin)
|
||||
{
|
||||
m_origin = new_origin;
|
||||
m_to_world_matrix = std::nullopt;
|
||||
}
|
||||
|
||||
void set_scale(const Vector3<NumericType>& new_scale)
|
||||
void set_scale(const VectorType& new_scale)
|
||||
{
|
||||
m_scale = new_scale;
|
||||
m_to_world_matrix = std::nullopt;
|
||||
@@ -47,13 +69,13 @@ namespace omath::primitives
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const Vector3<NumericType>& get_origin() const
|
||||
const VectorType& get_origin() const
|
||||
{
|
||||
return m_origin;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const Vector3<NumericType>& get_scale() const
|
||||
const VectorType& get_scale() const
|
||||
{
|
||||
return m_scale;
|
||||
}
|
||||
@@ -69,31 +91,34 @@ namespace omath::primitives
|
||||
{
|
||||
if (m_to_world_matrix)
|
||||
return m_to_world_matrix.value();
|
||||
m_to_world_matrix =
|
||||
mat_translation(m_origin) * mat_scale(m_scale) * MeshTypeTrait::rotation_matrix(m_rotation_angles);
|
||||
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]]
|
||||
Vector3<float> vertex_to_world_space(const Vector3<float>& vertex) const
|
||||
VectorType vertex_position_to_world_space(const Vector3<float>& vertex_position) const
|
||||
requires HasPosition<VertexType>
|
||||
{
|
||||
auto abs_vec = get_to_world_matrix() * mat_column_from_vector(vertex);
|
||||
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<Vector3<float>> make_face_in_world_space(const Vao::const_iterator vao_iterator) const
|
||||
Triangle<VectorType> make_face_in_world_space(const Ebo::const_iterator vao_iterator) const
|
||||
requires HasPosition<VertexType>
|
||||
{
|
||||
return {vertex_to_world_space(m_vertex_buffer.at(vao_iterator->x)),
|
||||
vertex_to_world_space(m_vertex_buffer.at(vao_iterator->y)),
|
||||
vertex_to_world_space(m_vertex_buffer.at(vao_iterator->z))};
|
||||
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)};
|
||||
}
|
||||
|
||||
private:
|
||||
Vector3<NumericType> m_origin;
|
||||
Vector3<NumericType> m_scale;
|
||||
VectorType m_origin;
|
||||
VectorType m_scale;
|
||||
|
||||
RotationAngles m_rotation_angles;
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
#include "simplex.hpp"
|
||||
#include <algorithm> // find_if
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace omath::collision
|
||||
@@ -16,21 +19,21 @@ namespace omath::collision
|
||||
{ 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>;
|
||||
{ a / s } -> std::same_as<V>;
|
||||
};
|
||||
|
||||
template<class ColliderType>
|
||||
template<class ColliderInterfaceType>
|
||||
class Epa final
|
||||
{
|
||||
public:
|
||||
using Vertex = typename ColliderType::VertexType;
|
||||
static_assert(EpaVector<Vertex>, "VertexType must satisfy EpaVector concept");
|
||||
using VectorType = ColliderInterfaceType::VectorType;
|
||||
static_assert(EpaVector<VectorType>, "VertexType must satisfy EpaVector concept");
|
||||
|
||||
struct Result final
|
||||
{
|
||||
Vertex normal{}; // outward normal (from B to A)
|
||||
Vertex penetration_vector;
|
||||
VectorType normal{}; // from A to B
|
||||
VectorType penetration_vector;
|
||||
float depth{0.0f};
|
||||
int iterations{0};
|
||||
int num_vertices{0};
|
||||
@@ -42,27 +45,19 @@ namespace omath::collision
|
||||
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 ColliderType& a, const ColliderType& b, const Simplex<Vertex>& simplex,
|
||||
const Params params = {})
|
||||
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::vector<Vertex> vertexes;
|
||||
vertexes.reserve(64);
|
||||
for (std::size_t i = 0; i < simplex.size(); ++i)
|
||||
vertexes.push_back(simplex[i]);
|
||||
std::pmr::vector<VectorType> vertexes = build_initial_polytope_from_simplex(simplex, mem_resource);
|
||||
|
||||
// Initial tetra faces (windings corrected in make_face)
|
||||
std::vector<Face> faces;
|
||||
faces.reserve(128);
|
||||
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));
|
||||
std::pmr::vector<Face> faces = create_initial_tetra_faces(mem_resource, vertexes);
|
||||
|
||||
auto heap = rebuild_heap(faces);
|
||||
auto heap = rebuild_heap(faces, mem_resource);
|
||||
|
||||
Result out{};
|
||||
|
||||
@@ -75,104 +70,72 @@ namespace omath::collision
|
||||
// (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);
|
||||
heap = rebuild_heap(faces, mem_resource);
|
||||
|
||||
if (heap.empty())
|
||||
break;
|
||||
|
||||
const int fidx = heap.top().idx;
|
||||
const Face f = faces[fidx];
|
||||
//FIXME: STORE REF VALUE, DO NOT USE
|
||||
// AFTER IF STATEMENT BLOCK
|
||||
const Face& face = faces[heap.top().idx];
|
||||
|
||||
// Get farthest point in face normal direction
|
||||
const Vertex p = support_point(a, b, f.n);
|
||||
const float p_dist = f.n.dot(p);
|
||||
// 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 - f.d <= params.tolerance)
|
||||
if (p_dist - face.d <= params.tolerance)
|
||||
{
|
||||
out.normal = f.n;
|
||||
out.depth = f.d; // along unit normal
|
||||
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());
|
||||
|
||||
const auto centers = b.get_origin() - a.get_origin();
|
||||
const auto sign = out.normal.dot(centers) >= 0 ? 1 : -1;
|
||||
|
||||
out.penetration_vector = out.normal * out.depth * sign;
|
||||
out.penetration_vector = out.normal * out.depth;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Add new vertex
|
||||
const int new_idx = static_cast<int>(vertexes.size());
|
||||
vertexes.push_back(p);
|
||||
vertexes.emplace_back(p);
|
||||
|
||||
// Mark faces visible from p and collect their horizon
|
||||
std::vector<char> to_delete(faces.size(), 0);
|
||||
std::vector<Edge> boundary;
|
||||
boundary.reserve(faces.size() * 2);
|
||||
const auto [to_delete, boundary] = mark_visible_and_collect_horizon(faces, p);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
|
||||
{
|
||||
if (to_delete[i])
|
||||
continue;
|
||||
if (visible_from(faces[i], p))
|
||||
{
|
||||
const auto& rf = faces[i];
|
||||
to_delete[i] = 1;
|
||||
add_edge_boundary(boundary, rf.i0, rf.i1);
|
||||
add_edge_boundary(boundary, rf.i1, rf.i2);
|
||||
add_edge_boundary(boundary, rf.i2, rf.i0);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove visible faces
|
||||
std::vector<Face> new_faces;
|
||||
new_faces.reserve(faces.size() + boundary.size());
|
||||
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
|
||||
if (!to_delete[i])
|
||||
new_faces.push_back(faces[i]);
|
||||
faces.swap(new_faces);
|
||||
erase_marked(faces, to_delete);
|
||||
|
||||
// Stitch new faces around the horizon
|
||||
for (const auto& e : boundary)
|
||||
faces.push_back(make_face(vertexes, e.a, e.b, new_idx));
|
||||
faces.emplace_back(make_face(vertexes, e.a, e.b, new_idx));
|
||||
|
||||
// Rebuild heap after topology change
|
||||
heap = rebuild_heap(faces);
|
||||
heap = rebuild_heap(faces, mem_resource);
|
||||
|
||||
if (!std::isfinite(vertexes.back().dot(vertexes.back())))
|
||||
break; // safety
|
||||
out.iterations = it + 1;
|
||||
}
|
||||
|
||||
// Fallback: pick closest face as best-effort answer
|
||||
if (!faces.empty())
|
||||
{
|
||||
auto best = faces[0];
|
||||
for (const auto& f : faces)
|
||||
if (f.d < best.d)
|
||||
best = f;
|
||||
out.normal = best.n;
|
||||
out.depth = best.d;
|
||||
out.num_vertices = static_cast<int>(vertexes.size());
|
||||
out.num_faces = static_cast<int>(faces.size());
|
||||
if (faces.empty())
|
||||
return std::nullopt;
|
||||
|
||||
const auto centers = b.get_origin() - a.get_origin();
|
||||
const auto sign = out.normal.dot(centers) >= 0 ? 1 : -1;
|
||||
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 * sign;
|
||||
out.penetration_vector = out.normal * out.depth;
|
||||
|
||||
return out;
|
||||
}
|
||||
return std::nullopt;
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Face final
|
||||
{
|
||||
int i0, i1, i2;
|
||||
Vertex n; // unit outward normal
|
||||
VectorType n; // unit outward normal
|
||||
float d; // n · v0 (>=0 ideally because origin is inside)
|
||||
};
|
||||
|
||||
@@ -188,47 +151,53 @@ namespace omath::collision
|
||||
};
|
||||
struct HeapCmp final
|
||||
{
|
||||
bool operator()(const HeapItem& lhs, const HeapItem& rhs) const noexcept
|
||||
[[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::vector<HeapItem>, HeapCmp>;
|
||||
|
||||
using Heap = std::priority_queue<HeapItem, std::pmr::vector<HeapItem>, HeapCmp>;
|
||||
|
||||
[[nodiscard]]
|
||||
static Heap rebuild_heap(const std::vector<Face>& faces)
|
||||
static Heap rebuild_heap(const std::pmr::vector<Face>& faces, auto& memory_resource)
|
||||
{
|
||||
Heap h;
|
||||
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.push({faces[i].d, i});
|
||||
return h;
|
||||
h.emplace(faces[i].d, i);
|
||||
|
||||
return h; // allocator is preserved
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static bool visible_from(const Face& f, const Vertex& p)
|
||||
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;
|
||||
return f.n.dot(p) - f.d > 1e-7f;
|
||||
}
|
||||
|
||||
static void add_edge_boundary(std::vector<Edge>& boundary, int a, int b)
|
||||
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::find_if(boundary.begin(), boundary.end(), [&](const Edge& e) { return e.a == b && e.b == a; });
|
||||
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.push_back({a, b}); // horizon edge (directed)
|
||||
boundary.emplace_back(a, b); // horizon edge (directed)
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static Face make_face(const std::vector<Vertex>& vertexes, int i0, int i1, int i2)
|
||||
static Face make_face(const std::pmr::vector<VectorType>& vertexes, int i0, int i1, int i2)
|
||||
{
|
||||
const Vertex& a0 = vertexes[i0];
|
||||
const Vertex& a1 = vertexes[i1];
|
||||
const Vertex& a2 = vertexes[i2];
|
||||
Vertex n = (a1 - a0).cross(a2 - a0);
|
||||
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
|
||||
@@ -246,9 +215,10 @@ namespace omath::collision
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static Vertex support_point(const ColliderType& a, const ColliderType& b, const Vertex& dir)
|
||||
static VectorType support_point(const ColliderInterfaceType& a, const ColliderInterfaceType& b,
|
||||
const VectorType& dir)
|
||||
{
|
||||
return a.find_abs_furthest_vertex(dir) - b.find_abs_furthest_vertex(-dir);
|
||||
return a.find_abs_furthest_vertex_position(dir) - b.find_abs_furthest_vertex_position(-dir);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
@@ -267,5 +237,67 @@ namespace omath::collision
|
||||
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
|
||||
|
||||
@@ -14,32 +14,33 @@ namespace omath::collision
|
||||
Simplex<VertexType> simplex; // valid only if hit == true and size==4
|
||||
};
|
||||
|
||||
template<class ColliderType>
|
||||
template<class ColliderInterfaceType>
|
||||
class GjkAlgorithm final
|
||||
{
|
||||
using VertexType = typename ColliderType::VertexType;
|
||||
using VectorType = ColliderInterfaceType::VectorType;
|
||||
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static VertexType find_support_vertex(const ColliderType& collider_a, const ColliderType& collider_b,
|
||||
const VertexType& direction)
|
||||
static VectorType find_support_vertex(const ColliderInterfaceType& collider_a,
|
||||
const ColliderInterfaceType& collider_b, const VectorType& direction)
|
||||
{
|
||||
return collider_a.find_abs_furthest_vertex(direction) - collider_b.find_abs_furthest_vertex(-direction);
|
||||
return collider_a.find_abs_furthest_vertex_position(direction)
|
||||
- collider_b.find_abs_furthest_vertex_position(-direction);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static bool is_collide(const ColliderType& collider_a, const ColliderType& collider_b)
|
||||
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<VertexType> is_collide_with_simplex_info(const ColliderType& collider_a,
|
||||
const ColliderType& collider_b)
|
||||
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, {1, 0, 0});
|
||||
auto support = find_support_vertex(collider_a, collider_b, VectorType{1, 0, 0});
|
||||
|
||||
Simplex<VertexType> simplex;
|
||||
Simplex<VectorType> simplex;
|
||||
simplex.push_front(support);
|
||||
|
||||
auto direction = -support;
|
||||
|
||||
@@ -3,40 +3,53 @@
|
||||
//
|
||||
|
||||
#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
|
||||
class MeshCollider final : public ColliderInterface<typename MeshType::VertexType::VectorType>
|
||||
{
|
||||
#ifdef OMATH_BUILD_TESTS
|
||||
friend UnitTestColider_FindFurthestVertex_Test;
|
||||
#endif
|
||||
public:
|
||||
using NumericType = typename MeshType::NumericType;
|
||||
|
||||
using VertexType = Vector3<NumericType>;
|
||||
using VertexType = MeshType::VertexType;
|
||||
using VectorType = MeshType::VertexType::VectorType;
|
||||
explicit MeshCollider(MeshType mesh): m_mesh(std::move(mesh))
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const VertexType& find_furthest_vertex(const VertexType& direction) const
|
||||
VectorType find_abs_furthest_vertex_position(const VectorType& direction) const override
|
||||
{
|
||||
return *std::ranges::max_element(m_mesh.m_vertex_buffer, [&direction](const auto& first, const auto& second)
|
||||
{ return first.dot(direction) < second.dot(direction); });
|
||||
return m_mesh.vertex_position_to_world_space(find_furthest_vertex(direction).position);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
VertexType find_abs_furthest_vertex(const VertexType& direction) const
|
||||
{
|
||||
return m_mesh.vertex_to_world_space(find_furthest_vertex(direction));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const VertexType& get_origin() const
|
||||
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
|
||||
@@ -8,5 +8,5 @@
|
||||
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
}
|
||||
@@ -8,5 +8,5 @@
|
||||
|
||||
namespace omath::iw_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
}
|
||||
@@ -8,5 +8,5 @@
|
||||
|
||||
namespace omath::opengl_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
}
|
||||
@@ -8,5 +8,5 @@
|
||||
|
||||
namespace omath::source_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -233,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;
|
||||
}
|
||||
|
||||
@@ -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,8 +5,10 @@
|
||||
#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 <cmath>
|
||||
#include <expected>
|
||||
#include <omath/trigonometry/angle.hpp>
|
||||
#include <type_traits>
|
||||
@@ -175,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);
|
||||
@@ -202,10 +252,12 @@ 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)};
|
||||
@@ -242,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:
|
||||
@@ -299,7 +353,7 @@ namespace omath::projection
|
||||
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 (screen_start == ScreenStart::BOTTOM_LEFT_CORNER)
|
||||
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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -320,7 +320,7 @@ namespace omath
|
||||
return std::visit(
|
||||
[base_address, &pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
|
||||
{
|
||||
// Define .code segment as scan area
|
||||
// Define .text segment as scan area
|
||||
const auto start = nt_header.optional_header.base_of_code;
|
||||
const auto scan_size = nt_header.optional_header.size_code;
|
||||
|
||||
|
||||
@@ -14,12 +14,19 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
|
||||
|
||||
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()
|
||||
|
||||
# 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...
|
||||
|
||||
@@ -7,20 +7,32 @@
|
||||
|
||||
TEST(UnitTestColider, CheckToWorld)
|
||||
{
|
||||
omath::source_engine::Mesh mesh = {std::vector<omath::Vector3<float>>{{1.f, 1.f, 1.f}, {-1.f, -1.f, -1.f}}, {}};
|
||||
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({1.f, 0.f, 0.f});
|
||||
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}, {-1.f, -1.f, -1.f}}, {}};
|
||||
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});
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
112
tests/general/unit_test_collision_extra.cpp
Normal file
112
tests/general/unit_test_collision_extra.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
// Extra collision tests: Simplex, MeshCollider, EPA
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/collision/simplex.hpp>
|
||||
#include <omath/collision/mesh_collider.hpp>
|
||||
#include <omath/collision/epa_algorithm.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{};
|
||||
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)
|
||||
{
|
||||
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});
|
||||
omath::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
|
||||
omath::source_engine::Mesh meshA = {
|
||||
std::vector<omath::primitives::Vertex<>>{{ {0.f,0.f,0.f}, {}, {} }, { {1.f,0.f,0.f}, {}, {} } },
|
||||
{}
|
||||
};
|
||||
omath::source_engine::Mesh mesh_b = meshA;
|
||||
mesh_b.set_origin({0.5f, 0.f, 0.f}); // translate to overlap
|
||||
|
||||
omath::source_engine::MeshCollider a(meshA);
|
||||
omath::source_engine::MeshCollider b(mesh_b);
|
||||
|
||||
// Create a simplex that approximately contains the origin in Minkowski space
|
||||
Simplex<omath::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<omath::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);
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "omath/engines/source_engine/mesh.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory_resource>
|
||||
|
||||
using Mesh = omath::source_engine::Mesh;
|
||||
using Collider = omath::source_engine::MeshCollider;
|
||||
@@ -14,9 +15,17 @@ using EPA = omath::collision::Epa<Collider>;
|
||||
TEST(UnitTestEpa, TestCollisionTrue)
|
||||
{
|
||||
// Unit cube [-1,1]^3
|
||||
std::vector<omath::Vector3<float>> vbo = {{-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<omath::Vector3<std::size_t>> vao; // not needed
|
||||
std::vector<omath::primitives::Vertex<>> vbo = {
|
||||
{ {-1.f, -1.f, -1.f}, {}, {} },
|
||||
{ {-1.f, -1.f, 1.f}, {}, {} },
|
||||
{ {-1.f, 1.f, -1.f}, {}, {} },
|
||||
{ {-1.f, 1.f, 1.f}, {}, {} },
|
||||
{ { 1.f, 1.f, 1.f}, {}, {} },
|
||||
{ { 1.f, 1.f, -1.f}, {}, {} },
|
||||
{ { 1.f, -1.f, 1.f}, {}, {} },
|
||||
{ { 1.f, -1.f, -1.f}, {}, {} }
|
||||
};
|
||||
std::vector<omath::Vector3<std::uint32_t>> vao; // not needed
|
||||
|
||||
Mesh a(vbo, vao, {1, 1, 1});
|
||||
Mesh b(vbo, vao, {1, 1, 1});
|
||||
@@ -33,9 +42,10 @@ TEST(UnitTestEpa, TestCollisionTrue)
|
||||
|
||||
// EPA
|
||||
EPA::Params params;
|
||||
auto pool = std::make_shared<std::pmr::monotonic_buffer_resource>(1024);
|
||||
params.max_iterations = 64;
|
||||
params.tolerance = 1e-4f;
|
||||
auto epa = EPA::solve(A, B, gjk.simplex, params);
|
||||
auto epa = EPA::solve(A, B, gjk.simplex, params, *pool);
|
||||
ASSERT_TRUE(epa.has_value()) << "EPA should converge";
|
||||
|
||||
// Normal is unit
|
||||
@@ -50,8 +60,8 @@ TEST(UnitTestEpa, TestCollisionTrue)
|
||||
EXPECT_NEAR(epa->normal.z, 0.0f, 1e-3f);
|
||||
|
||||
// Try both signs with a tiny margin (avoid grazing contacts)
|
||||
const float margin = 1.0f + 1e-3f;
|
||||
const auto pen = epa->normal * epa->depth;
|
||||
constexpr float margin = 1.0f + 1e-3f;
|
||||
const auto pen = epa->penetration_vector;
|
||||
|
||||
Mesh b_plus = b;
|
||||
b_plus.set_origin(b_plus.get_origin() + pen * margin);
|
||||
@@ -80,9 +90,17 @@ TEST(UnitTestEpa, TestCollisionTrue)
|
||||
}
|
||||
TEST(UnitTestEpa, TestCollisionTrue2)
|
||||
{
|
||||
std::vector<omath::Vector3<float>> vbo = {{-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<omath::Vector3<std::size_t>> vao; // not needed
|
||||
std::vector<omath::primitives::Vertex<>> vbo = {
|
||||
{ { -1.f, -1.f, -1.f }, {}, {} },
|
||||
{ { -1.f, -1.f, 1.f }, {}, {} },
|
||||
{ { -1.f, 1.f, -1.f }, {}, {} },
|
||||
{ { -1.f, 1.f, 1.f }, {}, {} },
|
||||
{ { 1.f, 1.f, 1.f }, {}, {} },
|
||||
{ { 1.f, 1.f, -1.f }, {}, {} },
|
||||
{ { 1.f, -1.f, 1.f }, {}, {} },
|
||||
{ { 1.f, -1.f, -1.f }, {}, {} }
|
||||
};
|
||||
std::vector<omath::Vector3<std::uint32_t>> vao; // not needed
|
||||
|
||||
Mesh a(vbo, vao, {1, 1, 1});
|
||||
Mesh b(vbo, vao, {1, 1, 1});
|
||||
@@ -96,12 +114,12 @@ TEST(UnitTestEpa, TestCollisionTrue2)
|
||||
// --- GJK must detect collision and provide simplex ---
|
||||
auto gjk = GJK::is_collide_with_simplex_info(A, B);
|
||||
ASSERT_TRUE(gjk.hit) << "GJK should report collision for overlapping cubes";
|
||||
|
||||
// --- EPA penetration ---
|
||||
EPA::Params params;
|
||||
params.max_iterations = 64;
|
||||
params.tolerance = 1e-4f;
|
||||
auto epa = EPA::solve(A, B, gjk.simplex, params);
|
||||
auto pool = std::make_shared<std::pmr::monotonic_buffer_resource>(1024);
|
||||
auto epa = EPA::solve(A, B, gjk.simplex, params, *pool);
|
||||
ASSERT_TRUE(epa.has_value()) << "EPA should converge";
|
||||
|
||||
// Normal is unit-length
|
||||
@@ -115,12 +133,8 @@ TEST(UnitTestEpa, TestCollisionTrue2)
|
||||
EXPECT_NEAR(epa->normal.y, 0.0f, 1e-3f);
|
||||
EXPECT_NEAR(epa->normal.z, 0.0f, 1e-3f);
|
||||
|
||||
// Choose a deterministic sign: orient penetration from A toward B
|
||||
const auto centers = b.get_origin() - a.get_origin(); // (0.5, 0, 0)
|
||||
float sign = (epa->normal.dot(centers) >= 0.0f) ? +1.0f : -1.0f;
|
||||
|
||||
constexpr float margin = 1.0f + 1e-3f; // tiny slack to avoid grazing
|
||||
const auto pen = epa->normal * epa->depth * sign;
|
||||
const auto pen = epa->normal * epa->depth;
|
||||
|
||||
// Apply once: B + pen must separate; the opposite must still collide
|
||||
Mesh b_resolved = b;
|
||||
|
||||
48
tests/general/unit_test_epa_internal.cpp
Normal file
48
tests/general/unit_test_epa_internal.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "omath/collision/epa_algorithm.hpp"
|
||||
#include "omath/collision/simplex.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using Vector3f = omath::Vector3<float>;
|
||||
|
||||
// Dummy collider type that exposes VectorType and returns small offsets
|
||||
struct DummyCollider
|
||||
{
|
||||
using VectorType = Vector3f;
|
||||
[[nodiscard]]
|
||||
static VectorType find_abs_furthest_vertex_position(const VectorType& dir) noexcept
|
||||
{
|
||||
// map direction to a small point so support_point is finite
|
||||
return Vector3f{dir.x * 0.01f, dir.y * 0.01f, dir.z * 0.01f};
|
||||
}
|
||||
};
|
||||
|
||||
using EpaDummy = omath::collision::Epa<DummyCollider>;
|
||||
using Simplex = omath::collision::Simplex<Vector3f>;
|
||||
|
||||
TEST(EpaInternal, SolveHandlesSmallPolytope)
|
||||
{
|
||||
// Create a simplex that is nearly degenerate but valid for solve
|
||||
Simplex s;
|
||||
s = { Vector3f{0.01f, 0.f, 0.f}, Vector3f{0.f, 0.01f, 0.f}, Vector3f{0.f, 0.f, 0.01f}, Vector3f{-0.01f, -0.01f, -0.01f} };
|
||||
|
||||
constexpr DummyCollider a;
|
||||
constexpr DummyCollider b;
|
||||
EpaDummy::Params params;
|
||||
params.max_iterations = 16;
|
||||
params.tolerance = 1e-6f;
|
||||
|
||||
const auto result = EpaDummy::solve(a, b, s, params);
|
||||
|
||||
// Should either return a valid result or gracefully return nullopt
|
||||
if (result)
|
||||
{
|
||||
EXPECT_TRUE(std::isfinite(result->depth));
|
||||
EXPECT_TRUE(std::isfinite(result->normal.x));
|
||||
EXPECT_GT(result->num_faces, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SUCCEED() << "Epa::solve returned nullopt for small polytope (acceptable)";
|
||||
}
|
||||
}
|
||||
51
tests/general/unit_test_epa_more.cpp
Normal file
51
tests/general/unit_test_epa_more.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "omath/collision/epa_algorithm.hpp"
|
||||
#include "omath/collision/simplex.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using Vector3f = omath::Vector3<float>;
|
||||
|
||||
// Minimal collider interface matching Epa's expectations
|
||||
struct DegenerateCollider
|
||||
{
|
||||
using VectorType = Vector3f;
|
||||
// returns furthest point along dir
|
||||
VectorType find_abs_furthest_vertex_position(const VectorType& dir) const noexcept
|
||||
{
|
||||
// Always return points on a small circle in XY plane so some faces become degenerate
|
||||
if (dir.x > 0.5f) return {0.01f, 0.f, 0.f};
|
||||
if (dir.x < -0.5f) return {-0.01f, 0.f, 0.f};
|
||||
if (dir.y > 0.5f) return {0.f, 0.01f, 0.f};
|
||||
if (dir.y < -0.5f) return {0.f, -0.01f, 0.f};
|
||||
return {0.f, 0.f, 0.01f};
|
||||
}
|
||||
};
|
||||
|
||||
using Epa = omath::collision::Epa<DegenerateCollider>;
|
||||
using Simplex = omath::collision::Simplex<Vector3f>;
|
||||
|
||||
TEST(EpaExtra, DegenerateFaceHandled)
|
||||
{
|
||||
// Prepare a simplex with near-collinear points to force degenerate face handling
|
||||
Simplex s;
|
||||
s = { Vector3f{0.01f, 0.f, 0.f}, Vector3f{0.02f, 0.f, 0.f}, Vector3f{0.03f, 0.f, 0.f}, Vector3f{0.0f, 0.0f, 0.01f} };
|
||||
|
||||
constexpr DegenerateCollider a;
|
||||
constexpr DegenerateCollider b;
|
||||
Epa::Params params;
|
||||
params.max_iterations = 4;
|
||||
params.tolerance = 1e-6f;
|
||||
|
||||
const auto result = Epa::solve(a, b, s, params);
|
||||
|
||||
// The algorithm should either return a valid result or gracefully exit (not crash)
|
||||
if (result)
|
||||
{
|
||||
EXPECT_TRUE(std::isfinite(result->depth));
|
||||
EXPECT_TRUE(std::isfinite(result->normal.x));
|
||||
}
|
||||
else
|
||||
{
|
||||
SUCCEED() << "EPA returned nullopt for degenerate input (acceptable)";
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,18 @@
|
||||
namespace
|
||||
{
|
||||
const omath::source_engine::Mesh mesh = {
|
||||
{{-1.f, -1.f, -1.f},
|
||||
{-1.f, -1.f, 1.f},
|
||||
{-1.f, 1.f, -1.f},
|
||||
{-1.f, 1.f, 1.f},
|
||||
{1.f, 1.f, 1.f},
|
||||
{1.f, 1.f, -1.f},
|
||||
{1.f, -1.f, 1.f},
|
||||
{1.f, -1.f, -1.f}},
|
||||
{}};
|
||||
{
|
||||
{ {-1.f, -1.f, -1.f}, {}, {} },
|
||||
{ {-1.f, -1.f, 1.f}, {}, {} },
|
||||
{ {-1.f, 1.f, -1.f}, {}, {} },
|
||||
{ {-1.f, 1.f, 1.f}, {}, {} },
|
||||
{ { 1.f, 1.f, 1.f}, {}, {} },
|
||||
{ { 1.f, 1.f, -1.f}, {}, {} },
|
||||
{ { 1.f, -1.f, 1.f}, {}, {} },
|
||||
{ { 1.f, -1.f, -1.f}, {}, {} }
|
||||
},
|
||||
{}
|
||||
};
|
||||
}
|
||||
TEST(UnitTestGjk, TestCollisionTrue)
|
||||
{
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace
|
||||
// -----------------------------------------------------------------------------
|
||||
// Constants & helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
constexpr float kTol = 1e-5f;
|
||||
constexpr float k_tol = 1e-5f;
|
||||
|
||||
bool VecEqual(const Vec3& a, const Vec3& b, float tol = kTol)
|
||||
bool vec_equal(const Vec3& a, const Vec3& b, const float tol = k_tol)
|
||||
{
|
||||
return std::fabs(a.x - b.x) < tol &&
|
||||
std::fabs(a.y - b.y) < tol &&
|
||||
@@ -58,8 +58,8 @@ namespace
|
||||
|
||||
TEST_P(CanTraceLineParam, VariousRays)
|
||||
{
|
||||
const auto& p = GetParam();
|
||||
EXPECT_EQ(LineTracer::can_trace_line(p.ray, triangle), p.expected_clear);
|
||||
const auto& [ray, expected_clear] = GetParam();
|
||||
EXPECT_EQ(LineTracer::can_trace_line(ray, triangle), expected_clear);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
@@ -85,8 +85,8 @@ namespace
|
||||
constexpr Vec3 expected{0.3f, 0.3f, 0.f};
|
||||
|
||||
const Vec3 hit = LineTracer::get_ray_hit_point(ray, triangle);
|
||||
ASSERT_FALSE(VecEqual(hit, ray.end));
|
||||
EXPECT_TRUE(VecEqual(hit, expected));
|
||||
ASSERT_FALSE(vec_equal(hit, ray.end));
|
||||
EXPECT_TRUE(vec_equal(hit, expected));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
65
tests/general/unit_test_line_tracer.cpp
Normal file
65
tests/general/unit_test_line_tracer.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "omath/collision/line_tracer.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using omath::Vector3;
|
||||
|
||||
TEST(LineTracerTests, ParallelRayReturnsEnd)
|
||||
{
|
||||
// Triangle in XY plane
|
||||
constexpr omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
|
||||
omath::collision::Ray ray;
|
||||
ray.start = Vector3<float>{0.f,0.f,1.f};
|
||||
ray.end = Vector3<float>{1.f,1.f,2.f}; // direction parallel to plane normal (z) -> but choose parallel to plane? make direction parallel to triangle plane
|
||||
ray.end = Vector3<float>{1.f,1.f,1.f};
|
||||
|
||||
// For a ray parallel to the triangle plane the algorithm should return ray.end
|
||||
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_TRUE(hit == ray.end);
|
||||
EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, tri));
|
||||
}
|
||||
|
||||
TEST(LineTracerTests, MissesTriangleReturnsEnd)
|
||||
{
|
||||
constexpr omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
|
||||
omath::collision::Ray ray;
|
||||
ray.start = Vector3<float>{2.f,2.f,-1.f};
|
||||
ray.end = Vector3<float>{2.f,2.f,1.f}; // passes above the triangle area
|
||||
|
||||
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_TRUE(hit == ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerTests, HitTriangleReturnsPointInsideSegment)
|
||||
{
|
||||
constexpr omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {2.f,0.f,0.f}, {0.f,2.f,0.f} };
|
||||
omath::collision::Ray ray;
|
||||
ray.start = Vector3<float>{0.25f,0.25f,-1.f};
|
||||
ray.end = Vector3<float>{0.25f,0.25f,1.f};
|
||||
|
||||
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
|
||||
// Should return a point between start and end (z approximately 0)
|
||||
EXPECT_NE(hit, ray.end);
|
||||
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||
// t_hit should be between 0 and 1 along the ray direction
|
||||
const auto dir = ray.direction_vector();
|
||||
// find t such that start + dir * t == hit (only check z comp for stability)
|
||||
const float t = (hit.z - ray.start.z) / dir.z;
|
||||
EXPECT_GT(t, 0.f);
|
||||
EXPECT_LT(t, 1.f);
|
||||
}
|
||||
|
||||
TEST(LineTracerTests, InfiniteLengthEarlyOut)
|
||||
{
|
||||
constexpr omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
|
||||
omath::collision::Ray ray;
|
||||
ray.start = Vector3<float>{0.25f,0.25f,0.f};
|
||||
ray.end = Vector3<float>{0.25f,0.25f,1.f};
|
||||
ray.infinite_length = true;
|
||||
|
||||
// If t_hit <= epsilon the algorithm should return ray.end when infinite_length is true.
|
||||
// Using start on the triangle plane should produce t_hit <= epsilon.
|
||||
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_TRUE(hit == ray.end);
|
||||
}
|
||||
48
tests/general/unit_test_line_tracer_extra.cpp
Normal file
48
tests/general/unit_test_line_tracer_extra.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
// Extra LineTracer tests
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/collision/line_tracer.hpp>
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
|
||||
using namespace omath;
|
||||
using namespace omath::collision;
|
||||
|
||||
TEST(LineTracerExtra, MissParallel)
|
||||
{
|
||||
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
|
||||
constexpr Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false }; // parallel above triangle
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerExtra, HitCenter)
|
||||
{
|
||||
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
|
||||
constexpr Ray ray{ {0.3f,0.3f,-1.f}, {0.3f,0.3f,1.f}, false };
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
ASSERT_FALSE(hit == ray.end);
|
||||
EXPECT_NEAR(hit.x, 0.3f, 1e-6f);
|
||||
EXPECT_NEAR(hit.y, 0.3f, 1e-6f);
|
||||
EXPECT_NEAR(hit.z, 0.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(LineTracerExtra, HitOnEdge)
|
||||
{
|
||||
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
|
||||
constexpr Ray ray{ {0.0f,0.0f,1.f}, {0.0f,0.0f,0.f}, false };
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
// hitting exact vertex/edge may be considered miss; ensure function handles without crash
|
||||
if (hit != ray.end)
|
||||
{
|
||||
EXPECT_NEAR(hit.x, 0.0f, 1e-6f);
|
||||
EXPECT_NEAR(hit.y, 0.0f, 1e-6f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LineTracerExtra, InfiniteRayIgnoredIfBehind)
|
||||
{
|
||||
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
|
||||
// Ray pointing away but infinite_length true should be ignored
|
||||
constexpr Ray ray{ {0.5f,0.5f,-1.f}, {0.5f,0.5f,-2.f}, true };
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
105
tests/general/unit_test_line_tracer_more.cpp
Normal file
105
tests/general/unit_test_line_tracer_more.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "omath/collision/line_tracer.hpp"
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using omath::Vector3;
|
||||
using omath::collision::Ray;
|
||||
using omath::collision::LineTracer;
|
||||
using Triangle3 = omath::Triangle<Vector3<float>>;
|
||||
|
||||
TEST(LineTracerMore, ParallelRayReturnsEnd)
|
||||
{
|
||||
// Ray parallel to triangle plane: construct triangle in XY plane and ray along X axis
|
||||
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
|
||||
Ray ray; ray.start = {0.f,0.f,1.f}; ray.end = {1.f,0.f,1.f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore, UOutOfRangeReturnsEnd)
|
||||
{
|
||||
// Construct a ray that misses due to u < 0
|
||||
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
|
||||
Ray ray; ray.start = {-1.f,-1.f,-1.f}; ray.end = {-0.5f,-1.f,1.f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore, VOutOfRangeReturnsEnd)
|
||||
{
|
||||
// Construct ray that has v < 0
|
||||
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
|
||||
Ray ray; ray.start = {2.f,2.f,-1.f}; ray.end = {2.f,2.f,1.f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore, THitTooSmallReturnsEnd)
|
||||
{
|
||||
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
|
||||
Ray ray; ray.start = {0.f,0.f,0.0000000001f}; ray.end = {0.f,0.f,1.f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore, THitGreaterThanOneReturnsEnd)
|
||||
{
|
||||
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
|
||||
// Choose a ray and compute t_hit locally to assert consistency
|
||||
Ray ray; ray.start = {0.f,0.f,-1.f}; ray.end = {0.f,0.f,-0.5f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
|
||||
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
|
||||
constexpr auto side_a = tri.side_a_vector();
|
||||
constexpr auto side_b = tri.side_b_vector();
|
||||
const auto ray_dir = ray.direction_vector();
|
||||
const auto p = ray_dir.cross(side_b);
|
||||
const auto det = side_a.dot(p);
|
||||
|
||||
if (std::abs(det) < k_epsilon)
|
||||
{
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto inv_det = 1.0f / det;
|
||||
const auto tvec = ray.start - tri.m_vertex2;
|
||||
const auto q = tvec.cross(side_a);
|
||||
const auto t_hit = side_b.dot(q) * inv_det;
|
||||
|
||||
if (t_hit <= k_epsilon || t_hit > 1.0f)
|
||||
EXPECT_EQ(hit, ray.end) << "t_hit=" << t_hit << " hit=" << hit.x << "," << hit.y << "," << hit.z;
|
||||
else
|
||||
EXPECT_NE(hit, ray.end) << "t_hit=" << t_hit << " hit=" << hit.x << "," << hit.y << "," << hit.z;
|
||||
}
|
||||
|
||||
TEST(LineTracerMore, InfiniteLengthWithSmallTHitReturnsEnd)
|
||||
{
|
||||
Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
|
||||
constexpr Triangle3 tri2(Vector3<float>{0.f,0.f,-1e-8f}, Vector3<float>{1.f,0.f,-1e-8f}, Vector3<float>{0.f,1.f,-1e-8f});
|
||||
Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,1.f}; ray.infinite_length = true;
|
||||
// Create triangle slightly behind so t_hit <= eps
|
||||
tri = tri2;
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore, SuccessfulHitReturnsPoint)
|
||||
{
|
||||
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
|
||||
Ray ray; ray.start = {0.1f,0.1f,-1.f}; ray.end = {0.1f,0.1f,1.f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_NE(hit, ray.end);
|
||||
// Hit should be on plane z=0 and near x=0.1,y=0.1
|
||||
EXPECT_NEAR(hit.z, 0.f, 1e-6f);
|
||||
EXPECT_NEAR(hit.x, 0.1f, 1e-3f);
|
||||
EXPECT_NEAR(hit.y, 0.1f, 1e-3f);
|
||||
}
|
||||
57
tests/general/unit_test_line_tracer_more2.cpp
Normal file
57
tests/general/unit_test_line_tracer_more2.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "omath/collision/line_tracer.hpp"
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using omath::Vector3;
|
||||
using omath::collision::Ray;
|
||||
using omath::collision::LineTracer;
|
||||
using Triangle3 = omath::Triangle<Vector3<float>>;
|
||||
|
||||
TEST(LineTracerMore2, UGreaterThanOneReturnsEnd)
|
||||
{
|
||||
constexpr Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
|
||||
// choose ray so barycentric u > 1
|
||||
Ray ray; ray.start = {2.f, -1.f, -1.f}; ray.end = {2.f, -1.f, 1.f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore2, VGreaterThanOneReturnsEnd)
|
||||
{
|
||||
constexpr Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
|
||||
// choose ray so barycentric v > 1
|
||||
Ray ray; ray.start = {-1.f, 2.f, -1.f}; ray.end = {-1.f, 2.f, 1.f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore2, UPlusVGreaterThanOneReturnsEnd)
|
||||
{
|
||||
constexpr Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
|
||||
// Ray aimed so u+v > 1 (outside triangle region)
|
||||
Ray ray; ray.start = {1.f, 1.f, -1.f}; ray.end = {1.f, 1.f, 1.f};
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore2, DirectionVectorNormalizedProducesUnitLength)
|
||||
{
|
||||
Ray r; r.start = {0.f,0.f,0.f}; r.end = {0.f,3.f,4.f};
|
||||
const auto dir = r.direction_vector_normalized();
|
||||
const auto len = dir.length();
|
||||
EXPECT_NEAR(len, 1.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(LineTracerMore2, ZeroLengthRayHandled)
|
||||
{
|
||||
constexpr Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
|
||||
Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,0.f};
|
||||
|
||||
// Zero-length ray: direction length == 0; algorithm should handle without crash
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
57
tests/general/unit_test_linear_algebra_cover_more_ops.cpp
Normal file
57
tests/general/unit_test_linear_algebra_cover_more_ops.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// Added to increase coverage for vector3/vector4/mat headers
|
||||
#include <gtest/gtest.h>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/linear_algebra/vector4.hpp"
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(Vector3ScalarOps, InPlaceScalarOperators)
|
||||
{
|
||||
Vector3<float> v{1.f, 2.f, 3.f};
|
||||
|
||||
v += 1.f;
|
||||
EXPECT_FLOAT_EQ(v.x, 2.f);
|
||||
EXPECT_FLOAT_EQ(v.y, 3.f);
|
||||
EXPECT_FLOAT_EQ(v.z, 4.f);
|
||||
|
||||
v /= 2.f;
|
||||
EXPECT_FLOAT_EQ(v.x, 1.f);
|
||||
EXPECT_FLOAT_EQ(v.y, 1.5f);
|
||||
EXPECT_FLOAT_EQ(v.z, 2.f);
|
||||
|
||||
v -= 0.5f;
|
||||
EXPECT_FLOAT_EQ(v.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(v.y, 1.0f);
|
||||
EXPECT_FLOAT_EQ(v.z, 1.5f);
|
||||
}
|
||||
|
||||
TEST(Vector4BinaryOps, ElementWiseMulDiv)
|
||||
{
|
||||
constexpr Vector4<float> a{2.f, 4.f, 6.f, 8.f};
|
||||
constexpr Vector4<float> b{1.f, 2.f, 3.f, 4.f};
|
||||
|
||||
constexpr auto m = a * b;
|
||||
EXPECT_FLOAT_EQ(m.x, 2.f);
|
||||
EXPECT_FLOAT_EQ(m.y, 8.f);
|
||||
EXPECT_FLOAT_EQ(m.z, 18.f);
|
||||
EXPECT_FLOAT_EQ(m.w, 32.f);
|
||||
|
||||
constexpr auto d = a / b;
|
||||
EXPECT_FLOAT_EQ(d.x, 2.f);
|
||||
EXPECT_FLOAT_EQ(d.y, 2.f);
|
||||
EXPECT_FLOAT_EQ(d.z, 2.f);
|
||||
EXPECT_FLOAT_EQ(d.w, 2.f);
|
||||
}
|
||||
|
||||
TEST(MatInitExceptions, InvalidInitializerLists)
|
||||
{
|
||||
// Wrong number of rows
|
||||
EXPECT_THROW((Mat<2,2,float>{ {1.f,2.f} }), std::invalid_argument);
|
||||
|
||||
// Row with wrong number of columns
|
||||
EXPECT_THROW((Mat<2,2,float>{ {1.f,2.f}, {1.f} }), std::invalid_argument);
|
||||
}
|
||||
50
tests/general/unit_test_linear_algebra_cover_remaining.cpp
Normal file
50
tests/general/unit_test_linear_algebra_cover_remaining.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// Additional coverage tests for Vector4 and Mat
|
||||
#include <gtest/gtest.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "omath/linear_algebra/vector4.hpp"
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
|
||||
using namespace omath;
|
||||
|
||||
static void make_bad_mat_rows()
|
||||
{
|
||||
// wrong number of rows -> should throw inside initializer-list ctor
|
||||
[[maybe_unused]] const Mat<2, 2, float> m{{1.f, 2.f}};
|
||||
}
|
||||
|
||||
static void make_bad_mat_cols()
|
||||
{
|
||||
// row with wrong number of columns -> should throw
|
||||
[[maybe_unused]] const Mat<2, 2, float> m{{1.f, 2.f}, {1.f}};
|
||||
}
|
||||
|
||||
TEST(Vector4Operator, Subtraction)
|
||||
{
|
||||
constexpr Vector4<float> a{5.f, 6.f, 7.f, 8.f};
|
||||
constexpr Vector4<float> b{1.f, 2.f, 3.f, 4.f};
|
||||
|
||||
constexpr auto r = a - b;
|
||||
EXPECT_FLOAT_EQ(r.x, 4.f);
|
||||
EXPECT_FLOAT_EQ(r.y, 4.f);
|
||||
EXPECT_FLOAT_EQ(r.z, 4.f);
|
||||
EXPECT_FLOAT_EQ(r.w, 4.f);
|
||||
}
|
||||
|
||||
TEST(MatInitializerExceptions, ForcedThrowLines)
|
||||
{
|
||||
EXPECT_THROW(make_bad_mat_rows(), std::invalid_argument);
|
||||
EXPECT_THROW(make_bad_mat_cols(), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(MatSelfAssignment, CopyAndMoveSelfAssign)
|
||||
{
|
||||
Mat<2,2,float> m{{1.f,2.f},{3.f,4.f}};
|
||||
// self copy-assignment
|
||||
m = m;
|
||||
EXPECT_FLOAT_EQ(m.at(0, 0), 1.f);
|
||||
|
||||
// self move-assignment
|
||||
m = std::move(m);
|
||||
EXPECT_FLOAT_EQ(m.at(0, 0), 1.f);
|
||||
}
|
||||
64
tests/general/unit_test_linear_algebra_extra.cpp
Normal file
64
tests/general/unit_test_linear_algebra_extra.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/linear_algebra/vector2.hpp>
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
#include <omath/linear_algebra/vector4.hpp>
|
||||
#include <omath/linear_algebra/mat.hpp>
|
||||
#include <functional>
|
||||
#include <format>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(LinearAlgebraExtra, FormatterAndHashVector2)
|
||||
{
|
||||
Vector2<float> v{1.0f, 2.0f};
|
||||
const std::string s = std::format("{}", v);
|
||||
EXPECT_EQ(s, "[1, 2]");
|
||||
|
||||
const std::size_t h1 = std::hash<Vector2<float>>{}(v);
|
||||
const std::size_t h2 = std::hash<Vector2<float>>{}(Vector2<float>{1.0f, 2.0f});
|
||||
const std::size_t h3 = std::hash<Vector2<float>>{}(Vector2<float>{2.0f, 3.0f});
|
||||
|
||||
EXPECT_EQ(h1, h2);
|
||||
EXPECT_NE(h1, h3);
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraExtra, FormatterAndHashVector3)
|
||||
{
|
||||
Vector3<float> v{1.0f, 2.0f, 3.0f};
|
||||
const std::string s = std::format("{}", v);
|
||||
EXPECT_EQ(s, "[1, 2, 3]");
|
||||
|
||||
const std::size_t h1 = std::hash<Vector3<float>>{}(v);
|
||||
const std::size_t h2 = std::hash<Vector3<float>>{}(Vector3<float>{1.0f, 2.0f, 3.0f});
|
||||
EXPECT_EQ(h1, h2);
|
||||
|
||||
// point_to_same_direction
|
||||
EXPECT_TRUE((Vector3<float>{1,0,0}.point_to_same_direction(Vector3<float>{2,0,0})));
|
||||
EXPECT_FALSE((Vector3<float>{1,0,0}.point_to_same_direction(Vector3<float>{-1,0,0})));
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraExtra, FormatterAndHashVector4)
|
||||
{
|
||||
Vector4<float> v{1.0f, 2.0f, 3.0f, 4.0f};
|
||||
const std::string s = std::format("{}", v);
|
||||
EXPECT_EQ(s, "[1, 2, 3, 4]");
|
||||
|
||||
const std::size_t h1 = std::hash<Vector4<float>>{}(v);
|
||||
const std::size_t h2 = std::hash<Vector4<float>>{}(Vector4<float>{1.0f, 2.0f, 3.0f, 4.0f});
|
||||
EXPECT_EQ(h1, h2);
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraExtra, MatRawArrayAndOperators)
|
||||
{
|
||||
Mat<2,2> m{{1.0f, 2.0f},{3.0f,4.0f}};
|
||||
const auto raw = m.raw_array();
|
||||
EXPECT_EQ(raw.size(), 4);
|
||||
EXPECT_FLOAT_EQ(raw[0], 1.0f);
|
||||
EXPECT_FLOAT_EQ(raw[3], 4.0f);
|
||||
|
||||
// operator[] index access
|
||||
EXPECT_FLOAT_EQ(m.at(0,0), 1.0f);
|
||||
EXPECT_FLOAT_EQ(m.at(1,1), 4.0f);
|
||||
}
|
||||
|
||||
|
||||
56
tests/general/unit_test_linear_algebra_helpers.cpp
Normal file
56
tests/general/unit_test_linear_algebra_helpers.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/linear_algebra/vector4.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// This test file exercises the non-inlined helpers added to headers
|
||||
// (Vector3, Triangle, Vector4) to encourage symbol emission and
|
||||
// runtime execution so coverage tools can attribute hits back to the
|
||||
// header lines.
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(LinearAlgebraHelpers, Vector3NoInlineHelpersExecute)
|
||||
{
|
||||
constexpr Vector3<float> a{1.f, 2.f, 3.f};
|
||||
constexpr Vector3<float> b{4.f, 5.f, 6.f};
|
||||
|
||||
// Execute helpers that were made non-inlined
|
||||
const auto l = a.length();
|
||||
const auto ang = a.angle_between(b);
|
||||
const auto perp = a.is_perpendicular(b);
|
||||
const auto norm = a.normalized();
|
||||
|
||||
(void)l; (void)ang; (void)perp; (void)norm;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraHelpers, TriangleNoInlineHelpersExecute)
|
||||
{
|
||||
constexpr Vector3<float> v1{0.f,0.f,0.f};
|
||||
constexpr Vector3<float> v2{3.f,0.f,0.f};
|
||||
constexpr Vector3<float> v3{3.f,4.f,0.f};
|
||||
|
||||
constexpr Triangle<Vector3<float>> t{v1, v2, v3};
|
||||
|
||||
const auto n = t.calculate_normal();
|
||||
const auto a = t.side_a_length();
|
||||
const auto b = t.side_b_length();
|
||||
const auto h = t.hypot();
|
||||
const auto r = t.is_rectangular();
|
||||
|
||||
(void)n; (void)a; (void)b; (void)h; (void)r;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraHelpers, Vector4NoInlineHelpersExecute)
|
||||
{
|
||||
Vector4<float> v{1.f,2.f,3.f,4.f};
|
||||
|
||||
const auto l = v.length();
|
||||
const auto s = v.sum();
|
||||
v.clamp(-10.f, 10.f);
|
||||
|
||||
(void)l; (void)s;
|
||||
SUCCEED();
|
||||
}
|
||||
74
tests/general/unit_test_linear_algebra_instantiate.cpp
Normal file
74
tests/general/unit_test_linear_algebra_instantiate.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
// Instantiation-only tests to force out-of-line template emission
|
||||
#include <gtest/gtest.h>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/linear_algebra/vector4.hpp"
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(LinearAlgebraInstantiate, Vector3AndVector4AndMatCoverage) {
|
||||
// Vector3 usage
|
||||
Vector3<float> a{1.f, 2.f, 3.f};
|
||||
Vector3<float> b{4.f, 5.f, 6.f};
|
||||
|
||||
// call various methods
|
||||
volatile float d0 = a.distance_to_sqr(b);
|
||||
volatile float d1 = a.dot(b);
|
||||
volatile auto c = a.cross(b);
|
||||
auto tup = a.as_tuple();
|
||||
volatile bool dir = a.point_to_same_direction(b);
|
||||
|
||||
// non-inlined helpers
|
||||
volatile float ln = a.length();
|
||||
auto ang = a.angle_between(b);
|
||||
volatile bool perp = a.is_perpendicular(b, 0.1f);
|
||||
volatile auto anorm = a.normalized();
|
||||
|
||||
// formatter and hash instantiations (char only)
|
||||
(void)std::format("{}", a);
|
||||
(void)std::hash<Vector3<float>>{}(a);
|
||||
|
||||
// Vector4 usage
|
||||
Vector4<float> v4{1.f, -2.f, 3.f, -4.f};
|
||||
volatile float v4len = v4.length();
|
||||
volatile float v4sum = v4.sum();
|
||||
v4.clamp(-2.f, 2.f);
|
||||
(void)std::format("{}", v4);
|
||||
(void)std::hash<Vector4<float>>{}(v4);
|
||||
|
||||
// Mat usage: instantiate several sizes and store orders
|
||||
Mat<1,1> m1{{42.f}};
|
||||
volatile float m1det = m1.determinant();
|
||||
|
||||
Mat<2,2> m2{{{1.f,2.f},{3.f,4.f}}};
|
||||
volatile float det2 = m2.determinant();
|
||||
auto tr2 = m2.transposed();
|
||||
auto minor00 = m2.minor(0,0);
|
||||
auto algc = m2.alg_complement(0,1);
|
||||
auto rarr = m2.raw_array();
|
||||
auto inv2 = m2.inverted();
|
||||
|
||||
Mat<3,3> m3{{{1.f,2.f,3.f},{4.f,5.f,6.f},{7.f,8.f,9.f}}};
|
||||
volatile float det3 = m3.determinant();
|
||||
auto strip = m3.strip(0,0);
|
||||
auto min = m3.minor(2,2);
|
||||
|
||||
// to_string/wstring/u8string and to_screen_mat
|
||||
auto s = m2.to_string();
|
||||
auto ws = m2.to_wstring();
|
||||
auto u8s = m2.to_u8string();
|
||||
auto screen = Mat<4,4>::to_screen_mat(800.f, 600.f);
|
||||
|
||||
// call non-inlined mat helpers
|
||||
volatile auto det = m2.determinant();
|
||||
volatile auto inv = m2.inverted();
|
||||
volatile auto trans = m2.transposed();
|
||||
volatile auto raw = m2.raw_array();
|
||||
|
||||
// simple sanity checks (not strict, only to use values)
|
||||
EXPECT_EQ(std::get<0>(tup), 1.f);
|
||||
EXPECT_TRUE(det2 != 0.f || inv2 == std::nullopt);
|
||||
}
|
||||
64
tests/general/unit_test_linear_algebra_more.cpp
Normal file
64
tests/general/unit_test_linear_algebra_more.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/linear_algebra/vector4.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(LinearAlgebraMore, Vector3EdgeCases)
|
||||
{
|
||||
constexpr Vector3<float> zero{0.f,0.f,0.f};
|
||||
constexpr Vector3<float> v{1.f,0.f,0.f};
|
||||
|
||||
// angle_between should be unexpected when one vector has zero length
|
||||
const auto angle = zero.angle_between(v);
|
||||
EXPECT_FALSE(static_cast<bool>(angle));
|
||||
|
||||
// normalized of zero should return zero
|
||||
const auto nz = zero.normalized();
|
||||
EXPECT_EQ(nz.x, 0.f);
|
||||
EXPECT_EQ(nz.y, 0.f);
|
||||
EXPECT_EQ(nz.z, 0.f);
|
||||
|
||||
// perpendicular case: x-axis and y-axis
|
||||
constexpr Vector3<float> x{1.f,0.f,0.f};
|
||||
constexpr Vector3<float> y{0.f,1.f,0.f};
|
||||
EXPECT_TRUE(x.is_perpendicular(y));
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraMore, TriangleRectangularAndDegenerate)
|
||||
{
|
||||
constexpr Vector3<float> v1{0.f,0.f,0.f};
|
||||
constexpr Vector3<float> v2{3.f,0.f,0.f};
|
||||
constexpr Vector3<float> v3{3.f,4.f,0.f}; // 3-4-5 triangle, rectangular at v2
|
||||
|
||||
constexpr Triangle<Vector3<float>> t{v1,v2,v3};
|
||||
|
||||
EXPECT_NEAR(t.side_a_length(), 3.f, 1e-6f);
|
||||
EXPECT_NEAR(t.side_b_length(), 4.f, 1e-6f);
|
||||
EXPECT_NEAR(t.hypot(), 5.f, 1e-6f);
|
||||
EXPECT_TRUE(t.is_rectangular());
|
||||
|
||||
// Degenerate: all points same
|
||||
constexpr Triangle<Vector3<float>> d{v1,v1,v1};
|
||||
EXPECT_NEAR(d.side_a_length(), 0.f, 1e-6f);
|
||||
EXPECT_NEAR(d.side_b_length(), 0.f, 1e-6f);
|
||||
EXPECT_NEAR(d.hypot(), 0.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraMore, Vector4ClampAndComparisons)
|
||||
{
|
||||
Vector4<float> v{10.f, -20.f, 30.f, -40.f};
|
||||
const auto s = v.sum();
|
||||
EXPECT_NEAR(s, -20.f, 1e-6f);
|
||||
|
||||
v.clamp(-10.f, 10.f);
|
||||
EXPECT_LE(v.x, 10.f);
|
||||
EXPECT_GE(v.x, -10.f);
|
||||
EXPECT_LE(v.y, 10.f);
|
||||
EXPECT_GE(v.y, -10.f);
|
||||
|
||||
constexpr Vector4<float> a{1.f,2.f,3.f,4.f};
|
||||
constexpr Vector4<float> b{2.f,2.f,2.f,2.f};
|
||||
EXPECT_TRUE(a < b || a > b || a == b); // just exercise comparisons
|
||||
}
|
||||
87
tests/general/unit_test_linear_algebra_more2.cpp
Normal file
87
tests/general/unit_test_linear_algebra_more2.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
// Tests to exercise non-inlined helpers and remaining branches in linear algebra
|
||||
#include "gtest/gtest.h"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/linear_algebra/vector4.hpp"
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(LinearAlgebraMore2, Vector3NonInlinedHelpers)
|
||||
{
|
||||
Vector3<float> v{3.f, 4.f, 0.f};
|
||||
EXPECT_FLOAT_EQ(v.length(), 5.0f);
|
||||
|
||||
auto vn = v.normalized();
|
||||
EXPECT_NEAR(vn.length(), 1.0f, 1e-6f);
|
||||
|
||||
Vector3<float> zero{0.f,0.f,0.f};
|
||||
auto ang = v.angle_between(zero);
|
||||
EXPECT_FALSE(ang.has_value());
|
||||
|
||||
Vector3<float> a{1.f,0.f,0.f};
|
||||
Vector3<float> b{0.f,1.f,0.f};
|
||||
EXPECT_TRUE(a.is_perpendicular(b));
|
||||
EXPECT_FALSE(a.is_perpendicular(a));
|
||||
|
||||
auto tup = v.as_tuple();
|
||||
EXPECT_EQ(std::get<0>(tup), 3.f);
|
||||
EXPECT_EQ(std::get<1>(tup), 4.f);
|
||||
EXPECT_EQ(std::get<2>(tup), 0.f);
|
||||
|
||||
EXPECT_TRUE(a.point_to_same_direction(Vector3<float>{2.f,0.f,0.f}));
|
||||
|
||||
// exercise hash specialization for Vector3<float>
|
||||
std::hash<Vector3<float>> hasher;
|
||||
auto hv = hasher(v);
|
||||
(void)hv;
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraMore2, Vector4NonInlinedHelpers)
|
||||
{
|
||||
Vector4<float> v{1.f,2.f,3.f,4.f};
|
||||
EXPECT_FLOAT_EQ(v.length(), v.length());
|
||||
EXPECT_FLOAT_EQ(v.sum(), v.sum());
|
||||
|
||||
// clamp noinline should modify the vector
|
||||
v.clamp(0.f, 2.5f);
|
||||
EXPECT_GE(v.x, 0.f);
|
||||
EXPECT_LE(v.z, 2.5f);
|
||||
|
||||
constexpr Vector4<float> shorter{0.1f,0.1f,0.1f,0.1f};
|
||||
EXPECT_TRUE(shorter < v);
|
||||
EXPECT_FALSE(v < shorter);
|
||||
}
|
||||
|
||||
TEST(LinearAlgebraMore2, MatNonInlinedAndStringHelpers)
|
||||
{
|
||||
Mat<2,2,float> m{{{4.f,7.f},{2.f,6.f}}};
|
||||
EXPECT_FLOAT_EQ(m.determinant(), 10.0f);
|
||||
|
||||
auto maybe_inv = m.inverted();
|
||||
EXPECT_TRUE(maybe_inv.has_value());
|
||||
const auto& inv = maybe_inv.value();
|
||||
|
||||
// m * inv should be identity (approximately)
|
||||
auto prod = m * inv;
|
||||
EXPECT_NEAR(prod.at(0,0), 1.0f, 1e-5f);
|
||||
EXPECT_NEAR(prod.at(1,1), 1.0f, 1e-5f);
|
||||
EXPECT_NEAR(prod.at(0,1), 0.0f, 1e-5f);
|
||||
|
||||
// transposed and to_string variants
|
||||
auto t = m.transposed();
|
||||
EXPECT_EQ(t.at(0,1), m.at(1,0));
|
||||
|
||||
auto raw = m.raw_array();
|
||||
EXPECT_EQ(raw.size(), static_cast<size_t>(4));
|
||||
|
||||
auto s = m.to_string();
|
||||
EXPECT_NE(s.size(), 0u);
|
||||
auto ws = m.to_wstring();
|
||||
EXPECT_NE(ws.size(), 0u);
|
||||
auto u8_s = m.to_u8string();
|
||||
EXPECT_NE(u8_s.size(), 0u);
|
||||
|
||||
// to_screen_mat static helper
|
||||
auto screen = Mat<4,4,float>::to_screen_mat(800.f, 600.f);
|
||||
EXPECT_NEAR(screen.at(0,0), 800.f/2.f, 1e-6f);
|
||||
}
|
||||
@@ -154,12 +154,12 @@ TEST_F(UnitTestMat, AssignmentOperator_Move)
|
||||
// Test static methods
|
||||
TEST_F(UnitTestMat, StaticMethod_ToScreenMat)
|
||||
{
|
||||
Mat<4, 4> screenMat = Mat<4, 4>::to_screen_mat(800.0f, 600.0f);
|
||||
EXPECT_FLOAT_EQ(screenMat.at(0, 0), 400.0f);
|
||||
EXPECT_FLOAT_EQ(screenMat.at(1, 1), -300.0f);
|
||||
EXPECT_FLOAT_EQ(screenMat.at(3, 0), 400.0f);
|
||||
EXPECT_FLOAT_EQ(screenMat.at(3, 1), 300.0f);
|
||||
EXPECT_FLOAT_EQ(screenMat.at(3, 3), 1.0f);
|
||||
Mat<4, 4> screen_mat = Mat<4, 4>::to_screen_mat(800.0f, 600.0f);
|
||||
EXPECT_FLOAT_EQ(screen_mat.at(0, 0), 400.0f);
|
||||
EXPECT_FLOAT_EQ(screen_mat.at(1, 1), -300.0f);
|
||||
EXPECT_FLOAT_EQ(screen_mat.at(3, 0), 400.0f);
|
||||
EXPECT_FLOAT_EQ(screen_mat.at(3, 1), 300.0f);
|
||||
EXPECT_FLOAT_EQ(screen_mat.at(3, 3), 1.0f);
|
||||
}
|
||||
|
||||
|
||||
@@ -220,8 +220,8 @@ TEST(UnitTestMatStandalone, Equanity)
|
||||
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
|
||||
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
|
||||
|
||||
auto proj_left_handed = omath::mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1, 1000);
|
||||
auto proj_right_handed = omath::mat_perspective_right_handed(90.f, 16.f / 9.f, 0.1, 1000);
|
||||
const auto proj_left_handed = omath::mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1, 1000);
|
||||
const auto proj_right_handed = omath::mat_perspective_right_handed(90.f, 16.f / 9.f, 0.1, 1000);
|
||||
|
||||
auto ndc_left_handed = proj_left_handed * omath::mat_column_from_vector(left_handed);
|
||||
auto ndc_right_handed = proj_right_handed * omath::mat_column_from_vector(right_handed);
|
||||
@@ -231,3 +231,13 @@ TEST(UnitTestMatStandalone, Equanity)
|
||||
|
||||
EXPECT_EQ(ndc_left_handed, ndc_right_handed);
|
||||
}
|
||||
TEST(UnitTestMatStandalone, MatPerspectiveLeftHanded)
|
||||
{
|
||||
const auto perspective_proj = mat_perspective_left_handed(90.f, 16.f/9.f, 0.1f, 1000.f);
|
||||
auto projected = perspective_proj
|
||||
* mat_column_from_vector<float>({0, 0, 0.1001});
|
||||
|
||||
projected /= projected.at(3, 0);
|
||||
|
||||
EXPECT_TRUE(projected.at(2, 0) > -1.0f && projected.at(2, 0) < 0.f);
|
||||
}
|
||||
24
tests/general/unit_test_mat_coverage_extra.cpp
Normal file
24
tests/general/unit_test_mat_coverage_extra.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
// Added to exercise Mat initializer-list exception branches and determinant fallback
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/linear_algebra/mat.hpp>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(MatCoverageExtra, InitListRowsMismatchThrows) {
|
||||
// Rows mismatch: provide 3 rows for a 2x2 Mat
|
||||
EXPECT_THROW((Mat<2,2>{ {1,2}, {3,4}, {5,6} }), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(MatCoverageExtra, InitListColumnsMismatchThrows) {
|
||||
// Columns mismatch: second row has wrong number of columns
|
||||
EXPECT_THROW((Mat<2,2>{ {1,2}, {3} }), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(MatCoverageExtra, DeterminantFallbackIsCallable) {
|
||||
// Call determinant for 1x1 and 2x2 matrices to cover determinant paths
|
||||
const Mat<1,1> m1{{3.14f}};
|
||||
EXPECT_FLOAT_EQ(m1.determinant(), 3.14f);
|
||||
|
||||
const Mat<2,2> m2{{{1.0f,2.0f},{3.0f,4.0f}}};
|
||||
EXPECT_FLOAT_EQ(m2.determinant(), -2.0f);
|
||||
}
|
||||
21
tests/general/unit_test_mat_more.cpp
Normal file
21
tests/general/unit_test_mat_more.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
// Unit tests to exercise Mat extra branches
|
||||
#include "gtest/gtest.h"
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
|
||||
using omath::Mat;
|
||||
|
||||
TEST(MatMore, InitListAndMultiply)
|
||||
{
|
||||
Mat<3,3,float> m{{{1.f,2.f,3.f}, {0.f,1.f,4.f}, {5.f,6.f,0.f}}};
|
||||
// multiply by scalar and check element
|
||||
auto r = m * 1.f;
|
||||
EXPECT_EQ(r.at(0,0), m.at(0,0));
|
||||
EXPECT_EQ(r.at(1,2), m.at(1,2));
|
||||
}
|
||||
|
||||
TEST(MatMore, Determinant)
|
||||
{
|
||||
const Mat<2,2,double> m{{{1.0,2.0},{2.0,4.0}}}; // singular
|
||||
const double det = m.determinant();
|
||||
EXPECT_DOUBLE_EQ(det, 0.0);
|
||||
}
|
||||
33
tests/general/unit_test_navigation_mesh.cpp
Normal file
33
tests/general/unit_test_navigation_mesh.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "omath/pathfinding/navigation_mesh.hpp"
|
||||
|
||||
using namespace omath;
|
||||
using namespace omath::pathfinding;
|
||||
|
||||
TEST(NavigationMeshTests, SerializeDeserializeRoundTrip)
|
||||
{
|
||||
NavigationMesh nav;
|
||||
Vector3<float> a{0.f,0.f,0.f};
|
||||
Vector3<float> b{1.f,0.f,0.f};
|
||||
Vector3<float> c{0.f,1.f,0.f};
|
||||
|
||||
nav.m_vertex_map.emplace(a, std::vector<Vector3<float>>{b,c});
|
||||
nav.m_vertex_map.emplace(b, std::vector<Vector3<float>>{a});
|
||||
nav.m_vertex_map.emplace(c, std::vector<Vector3<float>>{a});
|
||||
|
||||
auto data = nav.serialize();
|
||||
NavigationMesh nav2;
|
||||
EXPECT_NO_THROW(nav2.deserialize(data));
|
||||
|
||||
// verify neighbors preserved
|
||||
EXPECT_EQ(nav2.m_vertex_map.size(), nav.m_vertex_map.size());
|
||||
EXPECT_EQ(nav2.get_neighbors(a).size(), 2u);
|
||||
}
|
||||
|
||||
TEST(NavigationMeshTests, GetClosestVertexWhenEmpty)
|
||||
{
|
||||
const NavigationMesh nav;
|
||||
constexpr Vector3<float> p{5.f,5.f,5.f};
|
||||
const auto res = nav.get_closest_vertex(p);
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
31
tests/general/unit_test_pattern_scan_extra.cpp
Normal file
31
tests/general/unit_test_pattern_scan_extra.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
// Extra tests for PatternScanner behavior
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/pattern_scan.hpp>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(unit_test_pattern_scan_extra, IteratorScanFound)
|
||||
{
|
||||
std::vector<std::byte> buf = {static_cast<std::byte>(0xDE), static_cast<std::byte>(0xAD),
|
||||
static_cast<std::byte>(0xBE), static_cast<std::byte>(0xEF),
|
||||
static_cast<std::byte>(0x00)};
|
||||
const auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "DE AD BE EF");
|
||||
EXPECT_NE(it, buf.end());
|
||||
EXPECT_EQ(std::distance(buf.begin(), it), 0);
|
||||
}
|
||||
|
||||
TEST(unit_test_pattern_scan_extra, IteratorScanNotFound)
|
||||
{
|
||||
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11),
|
||||
static_cast<std::byte>(0x22)};
|
||||
const auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "FF EE DD");
|
||||
EXPECT_EQ(it, buf.end());
|
||||
}
|
||||
|
||||
TEST(unit_test_pattern_scan_extra, ParseInvalidPattern)
|
||||
{
|
||||
// invalid hex token should cause the public scan to return end (no match)
|
||||
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11)};
|
||||
const auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "GG HH");
|
||||
EXPECT_EQ(it, buf.end());
|
||||
}
|
||||
11
tests/general/unit_test_pe_pattern_scan_extra.cpp
Normal file
11
tests/general/unit_test_pe_pattern_scan_extra.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
// Tests for PePatternScanner basic behavior
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_extra, MissingFileReturnsNull)
|
||||
{
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file("/non/existent/file.exe", "55 8B EC");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
114
tests/general/unit_test_pe_pattern_scan_file.cpp
Normal file
114
tests/general/unit_test_pe_pattern_scan_file.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
// Unit test for PePatternScanner::scan_for_pattern_in_file using a synthetic PE-like file
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
// Helper: write a trivial PE-like file with DOS header and a single section named .text
|
||||
static bool write_minimal_pe_file(const std::string& path, const std::vector<std::uint8_t>& section_bytes)
|
||||
{
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
if (!f.is_open()) return false;
|
||||
|
||||
// Write DOS header (e_magic = 0x5A4D, e_lfanew at offset 0x3C)
|
||||
std::vector<std::uint8_t> dos(64, 0);
|
||||
dos[0] = 'M'; dos[1] = 'Z';
|
||||
// e_lfanew -> place NT headers right after DOS (offset 0x80)
|
||||
std::uint32_t e_lfanew = 0x80;
|
||||
std::memcpy(dos.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
|
||||
f.write(reinterpret_cast<const char*>(dos.data()), dos.size());
|
||||
|
||||
// Pad up to e_lfanew
|
||||
if (f.tellp() < static_cast<std::streampos>(e_lfanew))
|
||||
{
|
||||
std::vector<char> pad(e_lfanew - static_cast<std::uint32_t>(f.tellp()), 0);
|
||||
f.write(pad.data(), pad.size());
|
||||
}
|
||||
|
||||
// NT headers signature 'PE\0\0'
|
||||
f.put('P'); f.put('E'); f.put('\0'); f.put('\0');
|
||||
|
||||
// FileHeader: machine, num_sections
|
||||
std::uint16_t machine = 0x8664; // x64
|
||||
std::uint16_t num_sections = 1;
|
||||
std::uint32_t dummy32 = 0;
|
||||
std::uint32_t dummy32b = 0;
|
||||
std::uint16_t size_optional = 0xF0; // reasonable
|
||||
std::uint16_t characteristics = 0;
|
||||
f.write(reinterpret_cast<const char*>(&machine), sizeof(machine));
|
||||
f.write(reinterpret_cast<const char*>(&num_sections), sizeof(num_sections));
|
||||
f.write(reinterpret_cast<const char*>(&dummy32), sizeof(dummy32));
|
||||
f.write(reinterpret_cast<const char*>(&dummy32b), sizeof(dummy32b));
|
||||
std::uint32_t num_symbols = 0;
|
||||
f.write(reinterpret_cast<const char*>(&num_symbols), sizeof(num_symbols));
|
||||
f.write(reinterpret_cast<const char*>(&size_optional), sizeof(size_optional));
|
||||
f.write(reinterpret_cast<const char*>(&characteristics), sizeof(characteristics));
|
||||
|
||||
// OptionalHeader (x64) minimal: magic 0x20b, image_base, size_of_code, size_of_headers
|
||||
std::uint16_t magic = 0x20b;
|
||||
f.write(reinterpret_cast<const char*>(&magic), sizeof(magic));
|
||||
// filler for rest of optional header up to size_optional
|
||||
std::vector<std::uint8_t> opt(size_optional - sizeof(magic), 0);
|
||||
// set size_code near end
|
||||
// we'll set image_base and size_code fields in reasonable positions for extractor
|
||||
// For simplicity, leave zeros; extractor primarily uses optional_header.image_base and size_code later,
|
||||
// but we will craft a SectionHeader that points to raw data we append below.
|
||||
f.write(reinterpret_cast<const char*>(opt.data()), opt.size());
|
||||
|
||||
// Section header (name 8 bytes, then remaining 36 bytes)
|
||||
char name[8] = {'.','t','e','x','t',0,0,0};
|
||||
f.write(name, 8);
|
||||
|
||||
// Write placeholder bytes for the rest of the section header and remember its start
|
||||
constexpr std::uint32_t section_header_rest = 36u;
|
||||
const std::streampos header_rest_pos = f.tellp();
|
||||
std::vector<char> placeholder(section_header_rest, 0);
|
||||
f.write(placeholder.data(), placeholder.size());
|
||||
|
||||
// Now write section raw data and remember its file offset
|
||||
const std::streampos data_pos = f.tellp();
|
||||
f.write(reinterpret_cast<const char*>(section_bytes.data()), static_cast<std::streamsize>(section_bytes.size()));
|
||||
|
||||
// Patch section header fields: virtual_size, virtual_address, size_raw_data, ptr_raw_data
|
||||
const std::uint32_t virtual_size = static_cast<std::uint32_t>(section_bytes.size());
|
||||
constexpr std::uint32_t virtual_address = 0x1000u;
|
||||
const std::uint32_t size_raw_data = static_cast<std::uint32_t>(section_bytes.size());
|
||||
const std::uint32_t ptr_raw_data = static_cast<std::uint32_t>(data_pos);
|
||||
|
||||
// Seek back to the header_rest_pos and write fields in order
|
||||
f.seekp(header_rest_pos, std::ios::beg);
|
||||
f.write(reinterpret_cast<const char*>(&virtual_size), sizeof(virtual_size));
|
||||
f.write(reinterpret_cast<const char*>(&virtual_address), sizeof(virtual_address));
|
||||
f.write(reinterpret_cast<const char*>(&size_raw_data), sizeof(size_raw_data));
|
||||
f.write(reinterpret_cast<const char*>(&ptr_raw_data), sizeof(ptr_raw_data));
|
||||
|
||||
// Seek back to end for consistency
|
||||
f.seekp(0, std::ios::end);
|
||||
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_file, ScanFindsPattern)
|
||||
{
|
||||
constexpr std::string_view path = "./test_minimal_pe.bin";
|
||||
const std::vector<std::uint8_t> bytes = {0x55, 0x8B, 0xEC, 0x90, 0x90}; // pattern at offset 0
|
||||
ASSERT_TRUE(write_minimal_pe_file(path.data(), bytes));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text");
|
||||
EXPECT_TRUE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_file, ScanMissingPattern)
|
||||
{
|
||||
constexpr std::string_view path = "./test_minimal_pe_2.bin";
|
||||
const std::vector<std::uint8_t> bytes = {0x00, 0x01, 0x02, 0x03};
|
||||
ASSERT_TRUE(write_minimal_pe_file(path.data(), bytes));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "FF EE DD", ".text");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
69
tests/general/unit_test_pe_pattern_scan_loaded.cpp
Normal file
69
tests/general/unit_test_pe_pattern_scan_loaded.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// Tests for PePatternScanner::scan_for_pattern_in_loaded_module
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
static std::vector<std::uint8_t> make_fake_module(std::uint32_t base_of_code,
|
||||
std::uint32_t size_code,
|
||||
const std::vector<std::uint8_t>& code_bytes)
|
||||
{
|
||||
constexpr std::uint32_t e_lfanew = 0x80;
|
||||
const std::uint32_t total_size = e_lfanew + 0x200 + size_code + 0x100;
|
||||
std::vector<std::uint8_t> buf(total_size, 0);
|
||||
|
||||
// DOS header: e_magic at 0, e_lfanew at offset 0x3C
|
||||
buf[0] = 0x4D; buf[1] = 0x5A; // 'M' 'Z' (little-endian 0x5A4D)
|
||||
constexpr std::uint32_t le = e_lfanew;
|
||||
std::memcpy(buf.data() + 0x3C, &le, sizeof(le));
|
||||
|
||||
// NT signature at e_lfanew
|
||||
constexpr std::uint32_t nt_sig = 0x4550; // 'PE\0\0'
|
||||
std::memcpy(buf.data() + e_lfanew, &nt_sig, sizeof(nt_sig));
|
||||
|
||||
// FileHeader is 20 bytes: we only need to ensure its size is present; leave zeros
|
||||
|
||||
// OptionalHeader magic (optional header begins at e_lfanew + 4 + sizeof(FileHeader) == e_lfanew + 24)
|
||||
constexpr std::uint16_t opt_magic = 0x020B; // x64
|
||||
std::memcpy(buf.data() + e_lfanew + 24, &opt_magic, sizeof(opt_magic));
|
||||
|
||||
// size_code is at offset 4 inside OptionalHeader -> absolute e_lfanew + 28
|
||||
std::memcpy(buf.data() + e_lfanew + 28, &size_code, sizeof(size_code));
|
||||
|
||||
// base_of_code is at offset 20 inside OptionalHeader -> absolute e_lfanew + 44
|
||||
std::memcpy(buf.data() + e_lfanew + 44, &base_of_code, sizeof(base_of_code));
|
||||
|
||||
// place code bytes at offset base_of_code
|
||||
if (base_of_code + code_bytes.size() <= buf.size())
|
||||
std::memcpy(buf.data() + base_of_code, code_bytes.data(), code_bytes.size());
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
TEST(PePatternScanLoaded, FindsPatternAtBase)
|
||||
{
|
||||
const std::vector<std::uint8_t> code = {0x90, 0x01, 0x02, 0x03, 0x04};
|
||||
auto buf = make_fake_module(0x200, static_cast<std::uint32_t>(code.size()), code);
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "90 01 02");
|
||||
ASSERT_TRUE(res.has_value());
|
||||
// address should point somewhere in our buffer; check offset
|
||||
const uintptr_t addr = res.value();
|
||||
const uintptr_t base = reinterpret_cast<uintptr_t>(buf.data());
|
||||
EXPECT_EQ(addr - base, 0x200u);
|
||||
}
|
||||
|
||||
TEST(PePatternScanLoaded, WildcardMatches)
|
||||
{
|
||||
const std::vector<std::uint8_t> code = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
auto buf = make_fake_module(0x300, static_cast<std::uint32_t>(code.size()), code);
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE ?? BE");
|
||||
ASSERT_TRUE(res.has_value());
|
||||
const uintptr_t addr = res.value();
|
||||
const uintptr_t base = reinterpret_cast<uintptr_t>(buf.data());
|
||||
EXPECT_EQ(addr - base, 0x300u);
|
||||
}
|
||||
197
tests/general/unit_test_pe_pattern_scan_more.cpp
Normal file
197
tests/general/unit_test_pe_pattern_scan_more.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
// Additional tests for PePatternScanner to exercise edge cases and loaded-module scanning
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
#include <vector>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
static bool write_bytes(const std::string& path, const std::vector<std::uint8_t>& data)
|
||||
{
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
if (!f.is_open())
|
||||
return false;
|
||||
f.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_more, InvalidDosHeader)
|
||||
{
|
||||
constexpr std::string_view path = "./test_bad_dos.bin";
|
||||
std::vector<std::uint8_t> data(128, 0);
|
||||
// write wrong magic
|
||||
data[0] = 'N';
|
||||
data[1] = 'Z';
|
||||
ASSERT_TRUE(write_bytes(path.data(), data));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_more, InvalidNtSignature)
|
||||
{
|
||||
constexpr std::string_view path = "./test_bad_nt.bin";
|
||||
std::vector<std::uint8_t> data(256, 0);
|
||||
// valid DOS header
|
||||
data[0] = 'M';
|
||||
data[1] = 'Z';
|
||||
// point e_lfanew to 0x80
|
||||
constexpr std::uint32_t e_lfanew = 0x80;
|
||||
std::memcpy(data.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
|
||||
// write garbage at e_lfanew (not 'PE\0\0')
|
||||
data[e_lfanew + 0] = 'X';
|
||||
data[e_lfanew + 1] = 'Y';
|
||||
data[e_lfanew + 2] = 'Z';
|
||||
data[e_lfanew + 3] = 'W';
|
||||
ASSERT_TRUE(write_bytes(path.data(), data));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_more, SectionNotFound)
|
||||
{
|
||||
// reuse minimal writer but with section named .data and search .text
|
||||
constexpr std::string_view path = "./test_section_not_found.bin";
|
||||
std::ofstream f(path.data(), std::ios::binary);
|
||||
ASSERT_TRUE(f.is_open());
|
||||
// DOS
|
||||
std::vector<std::uint8_t> dos(64, 0);
|
||||
dos[0] = 'M';
|
||||
dos[1] = 'Z';
|
||||
std::uint32_t e_lfanew = 0x80;
|
||||
std::memcpy(dos.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
|
||||
f.write(reinterpret_cast<char*>(dos.data()), dos.size());
|
||||
// pad
|
||||
std::vector<char> pad(e_lfanew - static_cast<std::uint32_t>(f.tellp()), 0);
|
||||
f.write(pad.data(), pad.size());
|
||||
// NT sig
|
||||
f.put('P');
|
||||
f.put('E');
|
||||
f.put('\0');
|
||||
f.put('\0');
|
||||
// FileHeader minimal
|
||||
std::uint16_t machine = 0x8664;
|
||||
std::uint16_t num_sections = 1;
|
||||
std::uint32_t z = 0;
|
||||
std::uint32_t z2 = 0;
|
||||
std::uint32_t numsym = 0;
|
||||
std::uint16_t size_opt = 0xF0;
|
||||
std::uint16_t ch = 0;
|
||||
f.write(reinterpret_cast<char*>(&machine), sizeof(machine));
|
||||
f.write(reinterpret_cast<char*>(&num_sections), sizeof(num_sections));
|
||||
f.write(reinterpret_cast<char*>(&z), sizeof(z));
|
||||
f.write(reinterpret_cast<char*>(&z2), sizeof(z2));
|
||||
f.write(reinterpret_cast<char*>(&numsym), sizeof(numsym));
|
||||
f.write(reinterpret_cast<char*>(&size_opt), sizeof(size_opt));
|
||||
f.write(reinterpret_cast<char*>(&ch), sizeof(ch));
|
||||
// Optional header magic
|
||||
std::uint16_t magic = 0x20b;
|
||||
f.write(reinterpret_cast<char*>(&magic), sizeof(magic));
|
||||
std::vector<std::uint8_t> opt(size_opt - sizeof(magic), 0);
|
||||
f.write(reinterpret_cast<char*>(opt.data()), opt.size());
|
||||
// Section header named .data
|
||||
char name[8] = {'.', 'd', 'a', 't', 'a', 0, 0, 0};
|
||||
f.write(name, 8);
|
||||
std::uint32_t vs = 4, va = 0x1000, srd = 4, prd = 0x200;
|
||||
f.write(reinterpret_cast<char*>(&vs), 4);
|
||||
f.write(reinterpret_cast<char*>(&va), 4);
|
||||
f.write(reinterpret_cast<char*>(&srd), 4);
|
||||
f.write(reinterpret_cast<char*>(&prd), 4);
|
||||
std::vector<char> rest(16, 0);
|
||||
f.write(rest.data(), rest.size());
|
||||
// section bytes
|
||||
std::vector<std::uint8_t> sec = {0x00, 0x01, 0x02, 0x03};
|
||||
f.write(reinterpret_cast<char*>(sec.data()), sec.size());
|
||||
f.close();
|
||||
|
||||
auto res = PePatternScanner::scan_for_pattern_in_file(path, "00 01", ".text");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds)
|
||||
{
|
||||
// Create an in-memory buffer that mimics loaded module layout
|
||||
// Define local header structs matching those in source
|
||||
struct DosHeader
|
||||
{
|
||||
std::uint16_t e_magic;
|
||||
std::uint16_t e_cblp;
|
||||
std::uint16_t e_cp;
|
||||
std::uint16_t e_crlc;
|
||||
std::uint16_t e_cparhdr;
|
||||
std::uint16_t e_minalloc;
|
||||
std::uint16_t e_maxalloc;
|
||||
std::uint16_t e_ss;
|
||||
std::uint16_t e_sp;
|
||||
std::uint16_t e_csum;
|
||||
std::uint16_t e_ip;
|
||||
std::uint16_t e_cs;
|
||||
std::uint16_t e_lfarlc;
|
||||
std::uint16_t e_ovno;
|
||||
std::uint16_t e_res[4];
|
||||
std::uint16_t e_oemid;
|
||||
std::uint16_t e_oeminfo;
|
||||
std::uint16_t e_res2[10];
|
||||
std::uint32_t e_lfanew;
|
||||
};
|
||||
struct FileHeader
|
||||
{
|
||||
std::uint16_t machine;
|
||||
std::uint16_t num_sections;
|
||||
std::uint32_t timedate_stamp;
|
||||
std::uint32_t ptr_symbols;
|
||||
std::uint32_t num_symbols;
|
||||
std::uint16_t size_optional_header;
|
||||
std::uint16_t characteristics;
|
||||
};
|
||||
struct OptionalHeaderX64
|
||||
{
|
||||
std::uint16_t magic;
|
||||
std::uint16_t linker_version;
|
||||
std::uint32_t size_code;
|
||||
std::uint32_t size_init_data;
|
||||
std::uint32_t size_uninit_data;
|
||||
std::uint32_t entry_point;
|
||||
std::uint32_t base_of_code;
|
||||
std::uint64_t image_base;
|
||||
std::uint32_t section_alignment;
|
||||
std::uint32_t file_alignment; /* rest omitted */
|
||||
std::uint32_t size_image;
|
||||
std::uint32_t size_headers; /* keep space */
|
||||
std::uint8_t pad[200];
|
||||
};
|
||||
struct ImageNtHeadersX64
|
||||
{
|
||||
std::uint32_t signature;
|
||||
FileHeader file_header;
|
||||
OptionalHeaderX64 optional_header;
|
||||
};
|
||||
|
||||
const std::vector<std::uint8_t> pattern_bytes = {0xDE, 0xAD, 0xBE, 0xEF, 0x90};
|
||||
constexpr std::uint32_t base_of_code = 0x200; // will place bytes at offset 0x200
|
||||
const std::uint32_t size_code = static_cast<std::uint32_t>(pattern_bytes.size());
|
||||
|
||||
const std::uint32_t bufsize = 0x400 + size_code;
|
||||
std::vector<std::uint8_t> buf(bufsize, 0);
|
||||
// DOS header
|
||||
const auto dos = reinterpret_cast<DosHeader*>(buf.data());
|
||||
dos->e_magic = 0x5A4D;
|
||||
dos->e_lfanew = 0x80;
|
||||
// NT headers
|
||||
const auto nt = reinterpret_cast<ImageNtHeadersX64*>(buf.data() + dos->e_lfanew);
|
||||
nt->signature = 0x4550; // 'PE\0\0'
|
||||
nt->file_header.machine = 0x8664;
|
||||
nt->file_header.num_sections = 1;
|
||||
nt->optional_header.magic = 0x020B; // x64
|
||||
nt->optional_header.base_of_code = base_of_code;
|
||||
nt->optional_header.size_code = size_code;
|
||||
|
||||
// place code at base_of_code
|
||||
std::memcpy(buf.data() + base_of_code, pattern_bytes.data(), pattern_bytes.size());
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD BE EF");
|
||||
EXPECT_TRUE(res.has_value());
|
||||
}
|
||||
294
tests/general/unit_test_pe_pattern_scan_more2.cpp
Normal file
294
tests/general/unit_test_pe_pattern_scan_more2.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
#include <vector>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
// Local minimal FileHeader used by tests when constructing raw NT headers
|
||||
struct TestFileHeader
|
||||
{
|
||||
std::uint16_t machine;
|
||||
std::uint16_t num_sections;
|
||||
std::uint32_t timedate_stamp;
|
||||
std::uint32_t ptr_symbols;
|
||||
std::uint32_t num_symbols;
|
||||
std::uint16_t size_optional_header;
|
||||
std::uint16_t characteristics;
|
||||
};
|
||||
|
||||
static bool write_bytes(const std::string& path, const std::vector<std::uint8_t>& data)
|
||||
{
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
if (!f.is_open())
|
||||
return false;
|
||||
f.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper: write a trivial PE-like file with DOS header and a single section named .text
|
||||
static bool write_minimal_pe_file(const std::string& path, const std::vector<std::uint8_t>& section_bytes)
|
||||
{
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
if (!f.is_open())
|
||||
return false;
|
||||
|
||||
// Write DOS header (e_magic = 0x5A4D, e_lfanew at offset 0x3C)
|
||||
std::vector<std::uint8_t> dos(64, 0);
|
||||
dos[0] = 'M';
|
||||
dos[1] = 'Z';
|
||||
std::uint32_t e_lfanew = 0x80;
|
||||
std::memcpy(dos.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
|
||||
f.write(reinterpret_cast<const char*>(dos.data()), dos.size());
|
||||
|
||||
// Pad up to e_lfanew
|
||||
if (f.tellp() < static_cast<std::streampos>(e_lfanew))
|
||||
{
|
||||
std::vector<char> pad(e_lfanew - static_cast<std::uint32_t>(f.tellp()), 0);
|
||||
f.write(pad.data(), pad.size());
|
||||
}
|
||||
|
||||
// NT headers signature 'PE\0\0'
|
||||
f.put('P');
|
||||
f.put('E');
|
||||
f.put('\0');
|
||||
f.put('\0');
|
||||
|
||||
// FileHeader minimal
|
||||
std::uint16_t machine = 0x8664; // x64
|
||||
std::uint16_t num_sections = 1;
|
||||
std::uint32_t dummy32 = 0;
|
||||
std::uint32_t dummy32b = 0;
|
||||
std::uint16_t size_optional = 0xF0;
|
||||
std::uint16_t characteristics = 0;
|
||||
f.write(reinterpret_cast<const char*>(&machine), sizeof(machine));
|
||||
f.write(reinterpret_cast<const char*>(&num_sections), sizeof(num_sections));
|
||||
f.write(reinterpret_cast<const char*>(&dummy32), sizeof(dummy32));
|
||||
f.write(reinterpret_cast<const char*>(&dummy32b), sizeof(dummy32b));
|
||||
std::uint32_t num_symbols = 0;
|
||||
f.write(reinterpret_cast<const char*>(&num_symbols), sizeof(num_symbols));
|
||||
f.write(reinterpret_cast<const char*>(&size_optional), sizeof(size_optional));
|
||||
f.write(reinterpret_cast<const char*>(&characteristics), sizeof(characteristics));
|
||||
|
||||
// OptionalHeader minimal filler
|
||||
std::uint16_t magic = 0x20b;
|
||||
f.write(reinterpret_cast<const char*>(&magic), sizeof(magic));
|
||||
std::vector<std::uint8_t> opt(size_optional - sizeof(magic), 0);
|
||||
f.write(reinterpret_cast<const char*>(opt.data()), opt.size());
|
||||
|
||||
// Section header (name 8 bytes, then remaining 36 bytes)
|
||||
char name[8] = {'.', 't', 'e', 'x', 't', 0, 0, 0};
|
||||
f.write(name, 8);
|
||||
|
||||
constexpr std::uint32_t section_header_rest = 36u;
|
||||
const std::streampos header_rest_pos = f.tellp();
|
||||
std::vector<char> placeholder(section_header_rest, 0);
|
||||
f.write(placeholder.data(), placeholder.size());
|
||||
|
||||
// Now write section raw data and remember its file offset
|
||||
const std::streampos data_pos = f.tellp();
|
||||
f.write(reinterpret_cast<const char*>(section_bytes.data()), static_cast<std::streamsize>(section_bytes.size()));
|
||||
|
||||
// Patch section header fields
|
||||
const std::uint32_t virtual_size = static_cast<std::uint32_t>(section_bytes.size());
|
||||
constexpr std::uint32_t virtual_address = 0x1000u;
|
||||
const std::uint32_t size_raw_data = static_cast<std::uint32_t>(section_bytes.size());
|
||||
const std::uint32_t ptr_raw_data = static_cast<std::uint32_t>(data_pos);
|
||||
|
||||
f.seekp(header_rest_pos, std::ios::beg);
|
||||
f.write(reinterpret_cast<const char*>(&virtual_size), sizeof(virtual_size));
|
||||
f.write(reinterpret_cast<const char*>(&virtual_address), sizeof(virtual_address));
|
||||
f.write(reinterpret_cast<const char*>(&size_raw_data), sizeof(size_raw_data));
|
||||
f.write(reinterpret_cast<const char*>(&ptr_raw_data), sizeof(ptr_raw_data));
|
||||
f.seekp(0, std::ios::end);
|
||||
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_more2, LoadedModuleNullBaseReturnsNull)
|
||||
{
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(nullptr, "DE AD");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_more2, LoadedModuleInvalidOptionalHeaderReturnsNull)
|
||||
{
|
||||
// Construct in-memory buffer with DOS header but invalid optional header magic
|
||||
std::vector<std::uint8_t> buf(0x200, 0);
|
||||
struct DosHeader
|
||||
{
|
||||
std::uint16_t e_magic;
|
||||
std::uint8_t pad[0x3A];
|
||||
std::uint32_t e_lfanew;
|
||||
};
|
||||
const auto dos = reinterpret_cast<DosHeader*>(buf.data());
|
||||
dos->e_magic = 0x5A4D;
|
||||
dos->e_lfanew = 0x80;
|
||||
|
||||
// Place an NT header with wrong optional magic at e_lfanew
|
||||
const auto nt_ptr = buf.data() + dos->e_lfanew;
|
||||
// write signature
|
||||
nt_ptr[0] = 'P';
|
||||
nt_ptr[1] = 'E';
|
||||
nt_ptr[2] = 0;
|
||||
nt_ptr[3] = 0;
|
||||
// craft FileHeader with size_optional_header large enough
|
||||
constexpr std::uint16_t size_opt = 0xE0;
|
||||
// file header starts at offset 4
|
||||
std::memcpy(nt_ptr + 4 + 12, &size_opt,
|
||||
sizeof(size_opt)); // size_optional_header located after 12 bytes into FileHeader
|
||||
// write optional header magic to be invalid value
|
||||
constexpr std::uint16_t bad_magic = 0x9999;
|
||||
std::memcpy(nt_ptr + 4 + sizeof(std::uint32_t) + sizeof(std::uint16_t) + sizeof(std::uint16_t), &bad_magic,
|
||||
sizeof(bad_magic));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_more2, FileX86OptionalHeaderScanFindsPattern)
|
||||
{
|
||||
constexpr std::string_view path = "./test_pe_x86.bin";
|
||||
const std::vector<std::uint8_t> pattern = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
|
||||
// Use helper from this file to write a consistent minimal PE file with .text section
|
||||
ASSERT_TRUE(write_minimal_pe_file(path.data(), pattern));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "DE AD BE EF", ".text");
|
||||
ASSERT_TRUE(res.has_value());
|
||||
EXPECT_GE(res->virtual_base_addr, 0u);
|
||||
EXPECT_GE(res->raw_base_addr, 0u);
|
||||
EXPECT_EQ(res->target_offset, 0);
|
||||
}
|
||||
|
||||
TEST(unit_test_pe_pattern_scan_more2, FilePatternNotFoundReturnsNull)
|
||||
{
|
||||
const std::string path = "./test_pe_no_pattern.bin";
|
||||
std::vector<std::uint8_t> data(512, 0);
|
||||
// minimal DOS/NT headers to make extract_section fail earlier or return empty data
|
||||
data[0] = 'M';
|
||||
data[1] = 'Z';
|
||||
constexpr std::uint32_t e_lfanew = 0x80;
|
||||
std::memcpy(data.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
|
||||
// NT signature
|
||||
data[e_lfanew + 0] = 'P';
|
||||
data[e_lfanew + 1] = 'E';
|
||||
data[e_lfanew + 2] = 0;
|
||||
data[e_lfanew + 3] = 0;
|
||||
// FileHeader: one section, size_optional_header set low
|
||||
constexpr std::uint16_t num_sections = 1;
|
||||
constexpr std::uint16_t size_optional_header = 0xE0;
|
||||
std::memcpy(data.data() + e_lfanew + 6, &num_sections, sizeof(num_sections));
|
||||
std::memcpy(data.data() + e_lfanew + 4 + 12, &size_optional_header, sizeof(size_optional_header));
|
||||
// Optional header magic x64
|
||||
constexpr std::uint16_t magic = 0x020B;
|
||||
std::memcpy(data.data() + e_lfanew + 4 + sizeof(TestFileHeader), &magic, sizeof(magic));
|
||||
// Section header .text with small data that does not contain the pattern
|
||||
constexpr std::size_t offset_to_segment_table = e_lfanew + 4 + sizeof(TestFileHeader) + size_optional_header;
|
||||
constexpr char name[8] = {'.', 't', 'e', 'x', 't', 0, 0, 0};
|
||||
std::memcpy(data.data() + offset_to_segment_table, name, 8);
|
||||
std::uint32_t vs = 4, va = 0x1000, srd = 4, prd = 0x200;
|
||||
std::memcpy(data.data() + offset_to_segment_table + 8, &vs, 4);
|
||||
std::memcpy(data.data() + offset_to_segment_table + 12, &va, 4);
|
||||
std::memcpy(data.data() + offset_to_segment_table + 16, &srd, 4);
|
||||
std::memcpy(data.data() + offset_to_segment_table + 20, &prd, 4);
|
||||
// write file
|
||||
ASSERT_TRUE(write_bytes(path, data));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "AA BB CC", ".text");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
// Extra tests for pe_pattern_scan edge cases (on-disk API)
|
||||
|
||||
TEST(PePatternScanMore2, PatternAtStartFound)
|
||||
{
|
||||
const std::string path = "./test_pe_more_start.bin";
|
||||
const std::vector<std::uint8_t> bytes = {0x90, 0x01, 0x02, 0x03, 0x04};
|
||||
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "90 01 02", ".text");
|
||||
EXPECT_TRUE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(PePatternScanMore2, PatternAtEndFound)
|
||||
{
|
||||
const std::string path = "./test_pe_more_end.bin";
|
||||
std::vector<std::uint8_t> bytes = {0x00, 0x11, 0x22, 0x33, 0x44};
|
||||
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "22 33 44", ".text");
|
||||
if (!res.has_value())
|
||||
{
|
||||
// Try to locate the section header and print the raw section bytes the scanner would read
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
ASSERT_TRUE(in.is_open());
|
||||
// search for ".text" name
|
||||
in.seekg(0, std::ios::beg);
|
||||
std::vector<char> filebuf((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
||||
const auto it = std::search(filebuf.begin(), filebuf.end(), std::begin(".text"), std::end(".text") - 1);
|
||||
if (it != filebuf.end())
|
||||
{
|
||||
const size_t pos = std::distance(filebuf.begin(), it);
|
||||
// after name, next fields: virtual_size (4), virtual_address(4), size_raw_data(4), ptr_raw_data(4)
|
||||
const size_t meta_off = pos + 8;
|
||||
uint32_t virtual_size{};
|
||||
uint32_t virtual_address{};
|
||||
uint32_t size_raw_data{};
|
||||
uint32_t ptr_raw_data{};
|
||||
std::memcpy(&virtual_size, filebuf.data() + meta_off, sizeof(virtual_size));
|
||||
std::memcpy(&virtual_address, filebuf.data() + meta_off + 4, sizeof(virtual_address));
|
||||
std::memcpy(&size_raw_data, filebuf.data() + meta_off + 8, sizeof(size_raw_data));
|
||||
std::memcpy(&ptr_raw_data, filebuf.data() + meta_off + 12, sizeof(ptr_raw_data));
|
||||
|
||||
std::cerr << "Parsed section header: virtual_size=" << virtual_size << " virtual_address=0x" << std::hex
|
||||
<< virtual_address << std::dec << " size_raw_data=" << size_raw_data
|
||||
<< " ptr_raw_data=" << ptr_raw_data << "\n";
|
||||
|
||||
if (ptr_raw_data + size_raw_data <= filebuf.size())
|
||||
{
|
||||
std::cerr << "Extracted section bytes:\n";
|
||||
for (size_t i = 0; i < size_raw_data; i += 16)
|
||||
{
|
||||
std::fprintf(stderr, "%04zx: ", i);
|
||||
for (size_t j = 0; j < 16 && i + j < size_raw_data; ++j)
|
||||
std::fprintf(stderr, "%02x ", static_cast<uint8_t>(filebuf[ptr_raw_data + i + j]));
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(PePatternScanMore2, WildcardMatches)
|
||||
{
|
||||
const std::string path = "./test_pe_more_wild.bin";
|
||||
const std::vector<std::uint8_t> bytes = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "DE ?? BE", ".text");
|
||||
EXPECT_TRUE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(PePatternScanMore2, PatternLongerThanBuffer)
|
||||
{
|
||||
const std::string path = "./test_pe_more_small.bin";
|
||||
const std::vector<std::uint8_t> bytes = {0xAA, 0xBB};
|
||||
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "AA BB CC", ".text");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
TEST(PePatternScanMore2, InvalidPatternParse)
|
||||
{
|
||||
const std::string path = "./test_pe_more_invalid.bin";
|
||||
const std::vector<std::uint8_t> bytes = {0x01, 0x02, 0x03};
|
||||
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
|
||||
|
||||
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "01 GG 03", ".text");
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
66
tests/general/unit_test_pred_engine_trait.cpp
Normal file
66
tests/general/unit_test_pred_engine_trait.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// Tests for PredEngineTrait
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/engines/source_engine/traits/pred_engine_trait.hpp>
|
||||
#include <omath/projectile_prediction/projectile.hpp>
|
||||
#include <omath/projectile_prediction/target.hpp>
|
||||
|
||||
using namespace omath;
|
||||
using namespace omath::source_engine;
|
||||
|
||||
TEST(PredEngineTrait, PredictProjectilePositionBasic)
|
||||
{
|
||||
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 = PredEngineTrait::predict_projectile_position(p, /*pitch*/ 0.f, /*yaw*/ 0.f, /*time*/ 1.f,
|
||||
/*gravity*/ 9.81f);
|
||||
// With zero pitch and yaw forward vector is along X; expect x ~10, z reduced by gravity*0.5
|
||||
EXPECT_NEAR(pos.x, 10.f, 1e-3f);
|
||||
EXPECT_NEAR(pos.z, -9.81f * 0.5f, 1e-3f);
|
||||
}
|
||||
|
||||
TEST(PredEngineTrait, PredictTargetPositionAirborne)
|
||||
{
|
||||
projectile_prediction::Target t;
|
||||
t.m_origin = {0.f, 0.f, 10.f};
|
||||
t.m_velocity = {1.f, 0.f, 0.f};
|
||||
t.m_is_airborne = true;
|
||||
|
||||
const auto pred = PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pred.x, 2.f, 1e-6f);
|
||||
// z should have been reduced by gravity* t^2
|
||||
EXPECT_NEAR(pred.z, 10.f - 9.81f * 4.f * 0.5f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(PredEngineTrait, CalcVector2dDistance)
|
||||
{
|
||||
constexpr Vector3<float> d{3.f, 4.f, 0.f};
|
||||
EXPECT_NEAR(PredEngineTrait::calc_vector_2d_distance(d), 5.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(PredEngineTrait, CalcViewpointFromAngles)
|
||||
{
|
||||
projectile_prediction::Projectile p;
|
||||
p.m_origin = {0.f, 0.f, 0.f};
|
||||
p.m_launch_speed = 10.f;
|
||||
|
||||
constexpr Vector3<float> predicted{10.f, 0.f, 0.f};
|
||||
constexpr std::optional<float> pitch = 45.f;
|
||||
const auto vp = PredEngineTrait::calc_viewpoint_from_angles(p, predicted, pitch);
|
||||
// For 45 degrees, height = delta2d * tan(45deg) = 10 * 1 = 10
|
||||
EXPECT_NEAR(vp.z, 10.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(PredEngineTrait, DirectAngles)
|
||||
{
|
||||
constexpr Vector3<float> origin{0.f, 0.f, 0.f};
|
||||
constexpr Vector3<float> target{0.f, 1.f, 1.f};
|
||||
// yaw should be 90 degrees (pointing along y)
|
||||
EXPECT_NEAR(PredEngineTrait::calc_direct_yaw_angle(origin, target), 90.f, 1e-3f);
|
||||
// pitch should be asin(z/distance)
|
||||
const float dist = origin.distance_to(target);
|
||||
EXPECT_NEAR(PredEngineTrait::calc_direct_pitch_angle(origin, target),
|
||||
angles::radians_to_degrees(std::asin((target.z - origin.z) / dist)), 1e-3f);
|
||||
}
|
||||
100
tests/general/unit_test_proj_pred_engine_legacy_more.cpp
Normal file
100
tests/general/unit_test_proj_pred_engine_legacy_more.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/projectile_prediction/proj_pred_engine_legacy.hpp>
|
||||
#include <omath/projectile_prediction/projectile.hpp>
|
||||
#include <omath/projectile_prediction/target.hpp>
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
|
||||
using omath::projectile_prediction::Projectile;
|
||||
using omath::projectile_prediction::Target;
|
||||
using omath::Vector3;
|
||||
|
||||
// Fake engine trait where gravity is effectively zero and projectile prediction always hits the target
|
||||
struct FakeEngineZeroGravity
|
||||
{
|
||||
static Vector3<float> predict_target_position(const Target& t, float /*time*/, float /*gravity*/) noexcept
|
||||
{
|
||||
return t.m_origin;
|
||||
}
|
||||
static Vector3<float> predict_projectile_position(const Projectile& /*p*/, float /*pitch*/, float /*yaw*/, float /*time*/, float /*gravity*/) noexcept
|
||||
{
|
||||
// Return a fixed point matching typical target used in the test
|
||||
return Vector3<float>{100.f, 0.f, 0.f};
|
||||
}
|
||||
static float calc_vector_2d_distance(const Vector3<float>& v) noexcept { return std::hypot(v.x, v.y); }
|
||||
static float get_vector_height_coordinate(const Vector3<float>& v) noexcept { return v.z; }
|
||||
static Vector3<float> calc_viewpoint_from_angles(const Projectile& /*p*/, Vector3<float> /*v*/, std::optional<float> /*maybe_pitch*/) noexcept
|
||||
{
|
||||
return Vector3<float>{1.f, 2.f, 3.f};
|
||||
}
|
||||
static float calc_direct_pitch_angle(const Vector3<float>& /*a*/, const Vector3<float>& /*b*/) noexcept { return 12.5f; }
|
||||
static float calc_direct_yaw_angle(const Vector3<float>& /*a*/, const Vector3<float>& /*b*/) noexcept { return 0.f; }
|
||||
};
|
||||
|
||||
TEST(ProjPredLegacyMore, ZeroGravityUsesDirectPitchAndReturnsViewpoint)
|
||||
{
|
||||
constexpr Projectile proj{ .m_origin = {0.f, 0.f, 0.f}, .m_launch_speed = 10.f, .m_gravity_scale = 0.f };
|
||||
constexpr Target target{ .m_origin = {100.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
|
||||
|
||||
using Engine = omath::projectile_prediction::ProjPredEngineLegacy<FakeEngineZeroGravity>;
|
||||
const Engine engine(9.8f, 0.1f, 5.f, 1e-3f);
|
||||
|
||||
const auto res = engine.maybe_calculate_aim_point(proj, target);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
const auto v = res.value();
|
||||
EXPECT_NEAR(v.x, 1.f, 1e-6f);
|
||||
EXPECT_NEAR(v.y, 2.f, 1e-6f);
|
||||
EXPECT_NEAR(v.z, 3.f, 1e-6f);
|
||||
}
|
||||
|
||||
// Fake trait producing no valid launch angle (root < 0)
|
||||
struct FakeEngineNoSolution
|
||||
{
|
||||
static Vector3<float> predict_target_position(const Target& t, float /*time*/, float /*gravity*/) noexcept { return t.m_origin; }
|
||||
static Vector3<float> predict_projectile_position(const Projectile& /*p*/, float /*pitch*/, float /*yaw*/, float /*time*/, float /*gravity*/) noexcept { return Vector3<float>{0.f,0.f,0.f}; }
|
||||
static float calc_vector_2d_distance(const Vector3<float>& /*v*/) noexcept { return 10000.f; }
|
||||
static float get_vector_height_coordinate(const Vector3<float>& /*v*/) noexcept { return 0.f; }
|
||||
static Vector3<float> calc_viewpoint_from_angles(const Projectile& /*p*/, Vector3<float> /*v*/, std::optional<float> /*maybe_pitch*/) noexcept { return Vector3<float>{}; }
|
||||
static float calc_direct_pitch_angle(const Vector3<float>& /*a*/, const Vector3<float>& /*b*/) noexcept { return 0.f; }
|
||||
static float calc_direct_yaw_angle(const Vector3<float>& /*a*/, const Vector3<float>& /*b*/) noexcept { return 0.f; }
|
||||
};
|
||||
|
||||
TEST(ProjPredLegacyMore, NoSolutionRootReturnsNullopt)
|
||||
{
|
||||
// Very slow projectile and large distance -> quadratic root negative
|
||||
constexpr Projectile proj{ .m_origin = {0.f,0.f,0.f}, .m_launch_speed = 1.f, .m_gravity_scale = 1.f };
|
||||
constexpr Target target{ .m_origin = {10000.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
|
||||
|
||||
using Engine = omath::projectile_prediction::ProjPredEngineLegacy<FakeEngineNoSolution>;
|
||||
const Engine engine(9.8f, 0.5f, 2.f, 1.f);
|
||||
|
||||
const auto res = engine.maybe_calculate_aim_point(proj, target);
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
// Fake trait where an angle exists but the projectile does not reach target (miss)
|
||||
struct FakeEngineAngleButMiss
|
||||
{
|
||||
static Vector3<float> predict_target_position(const Target& t, float /*time*/, float /*gravity*/) noexcept { return t.m_origin; }
|
||||
static Vector3<float> predict_projectile_position(const Projectile& /*p*/, float /*pitch*/, float /*yaw*/, float /*time*/, float /*gravity*/) noexcept
|
||||
{
|
||||
// always return a point far from the target
|
||||
return Vector3<float>{0.f, 0.f, 1000.f};
|
||||
}
|
||||
static float calc_vector_2d_distance(const Vector3<float>& v) noexcept { return std::hypot(v.x, v.y); }
|
||||
static float get_vector_height_coordinate(const Vector3<float>& v) noexcept { return v.z; }
|
||||
static Vector3<float> calc_viewpoint_from_angles(const Projectile& /*p*/, Vector3<float> /*v*/, std::optional<float> /*maybe_pitch*/) noexcept { return Vector3<float>{9.f,9.f,9.f}; }
|
||||
static float calc_direct_pitch_angle(const Vector3<float>& /*a*/, const Vector3<float>& /*b*/) noexcept { return 1.f; }
|
||||
static float calc_direct_yaw_angle(const Vector3<float>& /*a*/, const Vector3<float>& /*b*/) noexcept { return 0.f; }
|
||||
};
|
||||
|
||||
TEST(ProjPredLegacyMore, AngleComputedButMissReturnsNullopt)
|
||||
{
|
||||
constexpr Projectile proj{ .m_origin = {0.f,0.f,0.f}, .m_launch_speed = 100.f, .m_gravity_scale = 1.f };
|
||||
constexpr Target target{ .m_origin = {10.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
|
||||
|
||||
using Engine = omath::projectile_prediction::ProjPredEngineLegacy<FakeEngineAngleButMiss>;
|
||||
const Engine engine(9.8f, 0.1f, 1.f, 0.1f);
|
||||
|
||||
const auto res = engine.maybe_calculate_aim_point(proj, target);
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
54
tests/general/unit_test_simplex_additional.cpp
Normal file
54
tests/general/unit_test_simplex_additional.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "omath/collision/simplex.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using omath::Vector3;
|
||||
|
||||
TEST(SimplexAdditional, RegionACSelectsAC)
|
||||
{
|
||||
// Construct points that force the Region AC branch where ac points toward the origin
|
||||
Vector3<float> a{1.f, 0.f, 0.f};
|
||||
Vector3<float> b{2.f, 0.f, 0.f};
|
||||
Vector3<float> c{0.f, 1.f, 0.f};
|
||||
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s = { a, b, c };
|
||||
|
||||
omath::Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
const bool hit = s.handle(dir);
|
||||
|
||||
// Should not report a collision; simplex should reduce to {a, c}
|
||||
EXPECT_FALSE(hit);
|
||||
EXPECT_EQ(s.size(), 2u);
|
||||
EXPECT_TRUE(s[0] == a);
|
||||
EXPECT_TRUE(s[1] == c);
|
||||
// direction should be finite and non-zero
|
||||
EXPECT_TRUE(std::isfinite(dir.x));
|
||||
EXPECT_TRUE(std::isfinite(dir.y));
|
||||
EXPECT_TRUE(std::isfinite(dir.z));
|
||||
}
|
||||
|
||||
TEST(SimplexAdditional, AbcAboveSetsDirection)
|
||||
{
|
||||
// Choose triangle so abc points roughly toward the origin (abc · ao > 0)
|
||||
Vector3<float> a{-1.f, 0.f, 0.f};
|
||||
Vector3<float> b{0.f, 1.f, 0.f};
|
||||
Vector3<float> c{0.f, 0.f, 1.f};
|
||||
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s = { a, b, c };
|
||||
|
||||
omath::Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
const bool hit = s.handle(dir);
|
||||
|
||||
EXPECT_FALSE(hit);
|
||||
|
||||
const auto ab = b - a;
|
||||
const auto ac = c - a;
|
||||
const auto abc = ab.cross(ac);
|
||||
|
||||
// direction should equal abc (above triangle case)
|
||||
EXPECT_NEAR(dir.x, abc.x, 1e-6f);
|
||||
EXPECT_NEAR(dir.y, abc.y, 1e-6f);
|
||||
EXPECT_NEAR(dir.z, abc.z, 1e-6f);
|
||||
}
|
||||
174
tests/general/unit_test_simplex_more.cpp
Normal file
174
tests/general/unit_test_simplex_more.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "omath/collision/simplex.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using omath::Vector3;
|
||||
using Simplex = omath::collision::Simplex<Vector3<float>>;
|
||||
|
||||
TEST(SimplexExtra, HandleLine_CollinearProducesPerp)
|
||||
{
|
||||
// a and b placed so ab points roughly same dir as ao and are collinear
|
||||
Vector3<float> a{2.f, 0.f, 0.f};
|
||||
Vector3<float> b{1.f, 0.f, 0.f};
|
||||
|
||||
Simplex s;
|
||||
s = {a, b};
|
||||
|
||||
Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
const bool hit = s.handle(dir);
|
||||
|
||||
// Should not report collision for a line simplex
|
||||
EXPECT_FALSE(hit);
|
||||
// Direction must be finite and not zero
|
||||
EXPECT_TRUE(std::isfinite(dir.x));
|
||||
EXPECT_TRUE(std::isfinite(dir.y));
|
||||
EXPECT_TRUE(std::isfinite(dir.z));
|
||||
constexpr auto zero = Vector3<float>{0.f, 0.f, 0.f};
|
||||
EXPECT_FALSE(dir == zero);
|
||||
|
||||
// Ensure direction is (approximately) perpendicular to ab
|
||||
const auto ab = b - a;
|
||||
const float dot = dir.dot(ab);
|
||||
EXPECT_NEAR(dot, 0.0f, 1e-4f);
|
||||
}
|
||||
|
||||
TEST(SimplexExtra, HandleLine_NonCollinearProducesValidDirection)
|
||||
{
|
||||
Vector3<float> a{2.f, 0.f, 0.f};
|
||||
Vector3<float> b{1.f, 1.f, 0.f};
|
||||
|
||||
Simplex s;
|
||||
s = {a, b};
|
||||
|
||||
Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
const bool hit = s.handle(dir);
|
||||
|
||||
EXPECT_FALSE(hit);
|
||||
EXPECT_TRUE(std::isfinite(dir.x));
|
||||
EXPECT_TRUE(std::isfinite(dir.y));
|
||||
EXPECT_TRUE(std::isfinite(dir.z));
|
||||
}
|
||||
|
||||
TEST(SimplexExtra, HandleTriangle_FlipWinding)
|
||||
{
|
||||
// Construct points where triangle winding will be flipped
|
||||
Vector3<float> a{1.f, 0.f, 0.f};
|
||||
Vector3<float> b{0.f, 1.f, 0.f};
|
||||
Vector3<float> c{0.f, -1.f, 0.f};
|
||||
|
||||
Simplex s;
|
||||
s = {a, b, c};
|
||||
|
||||
Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
const bool hit = s.handle(dir);
|
||||
|
||||
EXPECT_FALSE(hit);
|
||||
EXPECT_TRUE(std::isfinite(dir.x));
|
||||
EXPECT_TRUE(std::isfinite(dir.y));
|
||||
EXPECT_TRUE(std::isfinite(dir.z));
|
||||
}
|
||||
|
||||
TEST(SimplexExtra, HandleTetrahedron_InsideReturnsTrue)
|
||||
{
|
||||
// Simple tetra that should contain the origin
|
||||
Vector3<float> a{1.f, 0.f, 0.f};
|
||||
Vector3<float> b{0.f, 1.f, 0.f};
|
||||
Vector3<float> c{0.f, 0.f, 1.f};
|
||||
Vector3<float> d{-0.2f, -0.2f, -0.2f};
|
||||
|
||||
Simplex s;
|
||||
s = {a, b, c, d};
|
||||
|
||||
Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
const bool hit = s.handle(dir);
|
||||
|
||||
// If origin is inside, handle_tetrahedron should return true
|
||||
EXPECT_TRUE(hit);
|
||||
}
|
||||
// Additional sanity tests (avoid reusing Simplex alias above to prevent ambiguity)
|
||||
TEST(SimplexMore, PushFrontAndAccess)
|
||||
{
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s.push_front(omath::Vector3<float>{1.f, 0.f, 0.f});
|
||||
s.push_front(omath::Vector3<float>{2.f, 0.f, 0.f});
|
||||
s.push_front(omath::Vector3<float>{3.f, 0.f, 0.f});
|
||||
|
||||
EXPECT_EQ(s.size(), 3u);
|
||||
constexpr omath::Vector3<float> exp_front{3.f, 0.f, 0.f};
|
||||
constexpr omath::Vector3<float> exp_back{1.f, 0.f, 0.f};
|
||||
EXPECT_TRUE(s.front() == exp_front);
|
||||
EXPECT_TRUE(s.back() == exp_back);
|
||||
const auto d = s.data();
|
||||
EXPECT_TRUE(d[0] == exp_front);
|
||||
}
|
||||
|
||||
TEST(SimplexMore, ClearAndEmpty)
|
||||
{
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s.push_front(omath::Vector3<float>{1.f, 1.f, 1.f});
|
||||
EXPECT_FALSE(s.empty());
|
||||
s.clear();
|
||||
EXPECT_TRUE(s.empty());
|
||||
}
|
||||
|
||||
TEST(SimplexMore, HandleLineCollinearProducesPerp)
|
||||
{
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s = {omath::Vector3<float>{2.f, 0.f, 0.f}, omath::Vector3<float>{1.f, 0.f, 0.f}};
|
||||
omath::Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
const bool res = s.handle(dir);
|
||||
EXPECT_FALSE(res);
|
||||
EXPECT_GT(dir.length_sqr(), 0.0f);
|
||||
}
|
||||
|
||||
TEST(SimplexMore, HandleTriangleFlipWinding)
|
||||
{
|
||||
constexpr omath::Vector3<float> a{1.f, 0.f, 0.f};
|
||||
constexpr omath::Vector3<float> b{0.f, 1.f, 0.f};
|
||||
constexpr omath::Vector3<float> c{0.f, 0.f, 1.f};
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s = {a, b, c};
|
||||
omath::Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
|
||||
constexpr auto ab = b - a;
|
||||
constexpr auto ac = c - a;
|
||||
const auto abc = ab.cross(ac);
|
||||
|
||||
const bool res = s.handle(dir);
|
||||
EXPECT_FALSE(res);
|
||||
const auto expected = -abc;
|
||||
EXPECT_NEAR(dir.x, expected.x, 1e-6f);
|
||||
EXPECT_NEAR(dir.y, expected.y, 1e-6f);
|
||||
EXPECT_NEAR(dir.z, expected.z, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(SimplexMore, HandleTetrahedronInsideTrue)
|
||||
{
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s = {omath::Vector3<float>{1.f, 0.f, 0.f}, omath::Vector3<float>{0.f, 1.f, 0.f},
|
||||
omath::Vector3<float>{0.f, 0.f, 1.f}, omath::Vector3<float>{-1.f, -1.f, -1.f}};
|
||||
omath::Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
const bool inside = s.handle(dir);
|
||||
EXPECT_TRUE(inside);
|
||||
}
|
||||
|
||||
TEST(SimplexMore, HandlePointSetsDirection)
|
||||
{
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s = {omath::Vector3<float>{1.f, 2.f, 3.f}};
|
||||
omath::Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
EXPECT_FALSE(s.handle(dir));
|
||||
EXPECT_NEAR(dir.x, -1.f, 1e-6f);
|
||||
EXPECT_NEAR(dir.y, -2.f, 1e-6f);
|
||||
EXPECT_NEAR(dir.z, -3.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(SimplexMore, HandleLineReducesToPointWhenAoOpposite)
|
||||
{
|
||||
omath::collision::Simplex<omath::Vector3<float>> s;
|
||||
s = {omath::Vector3<float>{1.f, 0.f, 0.f}, omath::Vector3<float>{2.f, 0.f, 0.f}};
|
||||
omath::Vector3<float> dir{0.f, 0.f, 0.f};
|
||||
EXPECT_FALSE(s.handle(dir));
|
||||
EXPECT_EQ(s.size(), 1u);
|
||||
EXPECT_NEAR(dir.x, -1.f, 1e-6f);
|
||||
}
|
||||
@@ -99,15 +99,15 @@ TEST_F(UnitTestTriangle, SideLengths)
|
||||
// Test side vectors
|
||||
TEST_F(UnitTestTriangle, SideVectors)
|
||||
{
|
||||
const Vector3 sideA_t1 = t1.side_a_vector(); // m_vertex1 - m_vertex2
|
||||
EXPECT_FLOAT_EQ(sideA_t1.x, 0.0f - 1.0f);
|
||||
EXPECT_FLOAT_EQ(sideA_t1.y, 0.0f - 0.0f);
|
||||
EXPECT_FLOAT_EQ(sideA_t1.z, 0.0f - 0.0f);
|
||||
const Vector3 side_a_t1 = t1.side_a_vector(); // m_vertex1 - m_vertex2
|
||||
EXPECT_FLOAT_EQ(side_a_t1.x, 0.0f - 1.0f);
|
||||
EXPECT_FLOAT_EQ(side_a_t1.y, 0.0f - 0.0f);
|
||||
EXPECT_FLOAT_EQ(side_a_t1.z, 0.0f - 0.0f);
|
||||
|
||||
const Vector3 sideB_t1 = t1.side_b_vector(); // m_vertex3 - m_vertex2
|
||||
EXPECT_FLOAT_EQ(sideB_t1.x, 0.0f - 1.0f);
|
||||
EXPECT_FLOAT_EQ(sideB_t1.y, 1.0f - 0.0f);
|
||||
EXPECT_FLOAT_EQ(sideB_t1.z, 0.0f - 0.0f);
|
||||
const Vector3 side_b_t1 = t1.side_b_vector(); // m_vertex3 - m_vertex2
|
||||
EXPECT_FLOAT_EQ(side_b_t1.x, 0.0f - 1.0f);
|
||||
EXPECT_FLOAT_EQ(side_b_t1.y, 1.0f - 0.0f);
|
||||
EXPECT_FLOAT_EQ(side_b_t1.z, 0.0f - 0.0f);
|
||||
}
|
||||
|
||||
TEST_F(UnitTestTriangle, IsRectangular)
|
||||
|
||||
@@ -306,7 +306,7 @@ TEST_F(UnitTestVector2, DivisionAssignmentOperator_VectorWithZero)
|
||||
// Test operations with infinity and NaN
|
||||
TEST_F(UnitTestVector2, Operator_WithInfinity)
|
||||
{
|
||||
constexpr Vector2 v_inf(INFINITY, INFINITY);
|
||||
const Vector2 v_inf(INFINITY, INFINITY);
|
||||
const Vector2 result = v1 + v_inf;
|
||||
EXPECT_TRUE(std::isinf(result.x));
|
||||
EXPECT_TRUE(std::isinf(result.y));
|
||||
|
||||
@@ -10,6 +10,61 @@
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(Vector3More, ConstructorsAndEquality)
|
||||
{
|
||||
constexpr Vector3<float> a;
|
||||
EXPECT_EQ(a.x, 0.f);
|
||||
EXPECT_EQ(a.y, 0.f);
|
||||
EXPECT_EQ(a.z, 0.f);
|
||||
|
||||
constexpr Vector3<float> b{1.f, 2.f, 3.f};
|
||||
EXPECT_EQ(b.x, 1.f);
|
||||
EXPECT_EQ(b.y, 2.f);
|
||||
EXPECT_EQ(b.z, 3.f);
|
||||
|
||||
const Vector3<float> c = b;
|
||||
EXPECT_EQ(c, b);
|
||||
}
|
||||
|
||||
TEST(Vector3More, ArithmeticAndDotCross)
|
||||
{
|
||||
constexpr Vector3<float> a{1.f, 0.f, 0.f};
|
||||
constexpr Vector3<float> b{0.f, 1.f, 0.f};
|
||||
const auto c = a + b;
|
||||
constexpr Vector3<float> expect_c{1.f,1.f,0.f};
|
||||
EXPECT_EQ(c, expect_c);
|
||||
|
||||
const auto d = a - b;
|
||||
constexpr Vector3<float> expect_d{1.f,-1.f,0.f};
|
||||
EXPECT_EQ(d, expect_d);
|
||||
|
||||
const auto e = a * 2.f;
|
||||
constexpr Vector3<float> expect_e{2.f,0.f,0.f};
|
||||
EXPECT_EQ(e, expect_e);
|
||||
|
||||
EXPECT_FLOAT_EQ(a.dot(b), 0.f);
|
||||
// manual cross product check
|
||||
const auto cr = Vector3<float>{ a.y * b.z - a.z * b.y,
|
||||
a.z * b.x - a.x * b.z,
|
||||
a.x * b.y - a.y * b.x };
|
||||
constexpr Vector3<float> expect_cr{0.f,0.f,1.f};
|
||||
EXPECT_EQ(cr, expect_cr);
|
||||
}
|
||||
|
||||
TEST(Vector3More, NormalizationEdgeCases)
|
||||
{
|
||||
constexpr Vector3<double> z{0.0,0.0,0.0};
|
||||
const auto zn = z.normalized();
|
||||
EXPECT_DOUBLE_EQ(zn.x, 0.0);
|
||||
EXPECT_DOUBLE_EQ(zn.y, 0.0);
|
||||
EXPECT_DOUBLE_EQ(zn.z, 0.0);
|
||||
|
||||
constexpr Vector3<double> v{3.0,4.0,0.0};
|
||||
const auto vn = v.normalized();
|
||||
EXPECT_NEAR(vn.x, 0.6, 1e-12);
|
||||
EXPECT_NEAR(vn.y, 0.8, 1e-12);
|
||||
}
|
||||
|
||||
class UnitTestVector3 : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
@@ -260,7 +315,7 @@ TEST_F(UnitTestVector3, Division_ByZeroScalar)
|
||||
// Test operations with infinity
|
||||
TEST_F(UnitTestVector3, Addition_WithInfinity)
|
||||
{
|
||||
constexpr Vector3 v_inf(INFINITY, INFINITY, INFINITY);
|
||||
const Vector3 v_inf(INFINITY, INFINITY, INFINITY);
|
||||
const Vector3 result = v1 + v_inf;
|
||||
EXPECT_TRUE(std::isinf(result.x));
|
||||
EXPECT_TRUE(std::isinf(result.y));
|
||||
@@ -390,8 +445,10 @@ TEST_F(UnitTestVector3, AsTuple)
|
||||
// Test AsTuple method
|
||||
TEST_F(UnitTestVector3, AngleBeatween)
|
||||
{
|
||||
EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).angle_between({1, 0 ,0}).value().as_degrees(), 90.0f);
|
||||
EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).angle_between({0.0f, 0.0f, 1.0f}).value().as_degrees(), 0.0f);
|
||||
EXPECT_NEAR(Vector3(0.0f, 0.0f, 1.0f).angle_between({1, 0, 0}).value().as_degrees(),
|
||||
90.0f, 0.001f);
|
||||
EXPECT_NEAR(Vector3(0.0f, 0.0f, 1.0f).angle_between({0.0f, 0.0f, 1.0f}).value().as_degrees(),
|
||||
0.0f, 0.001f);
|
||||
EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).angle_between({0.0f, 0.0f, 1.0f}).has_value());
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,32 @@
|
||||
|
||||
using namespace omath;
|
||||
|
||||
TEST(Vector4More, ConstructorsAndClamp)
|
||||
{
|
||||
constexpr Vector4<float> a;
|
||||
EXPECT_EQ(a.x, 0.f);
|
||||
EXPECT_EQ(a.y, 0.f);
|
||||
EXPECT_EQ(a.z, 0.f);
|
||||
EXPECT_EQ(a.w, 0.f);
|
||||
|
||||
Vector4<float> b{1.f, -2.f, 3.5f, 4.f};
|
||||
b.clamp(0.f, 3.f);
|
||||
EXPECT_GE(b.x, 0.f);
|
||||
EXPECT_GE(b.y, 0.f);
|
||||
EXPECT_LE(b.z, 3.f);
|
||||
}
|
||||
|
||||
TEST(Vector4More, ComparisonsAndHashFormatter)
|
||||
{
|
||||
constexpr Vector4<int> a{1,2,3,4};
|
||||
constexpr Vector4<int> b{1,2,3,5};
|
||||
EXPECT_NE(a, b);
|
||||
|
||||
// exercise to_string via formatting if available by converting via std::format
|
||||
// call length and comparison to exercise more branches
|
||||
EXPECT_LT(a.length(), b.length());
|
||||
}
|
||||
|
||||
class UnitTestVector4 : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "General purpose math library",
|
||||
"homepage": "https://github.com/orange-cpp/omath",
|
||||
"license": "Zlib",
|
||||
"supports": "windows | linux",
|
||||
"supports": "windows | linux | macos",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "vcpkg-cmake",
|
||||
@@ -26,6 +26,13 @@
|
||||
"benchmark"
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"description": "Build benchmarks",
|
||||
"dependencies": [
|
||||
"glfw3",
|
||||
"glew"
|
||||
]
|
||||
},
|
||||
"imgui": {
|
||||
"description": "Omath will define method to convert omath types to imgui types",
|
||||
"dependencies": [
|
||||
|
||||
Reference in New Issue
Block a user