Compare commits

..

74 Commits

Author SHA1 Message Date
6ae3e37172 Merge pull request #148 from orange-cpp/feature/engine-units-to-metric
Feature/engine units to metric
2026-02-08 03:28:54 +03:00
afc613fcc0 added tests 2026-02-08 03:15:21 +03:00
d7a721f62e added frostbite tests 2026-02-08 03:03:23 +03:00
5aae9d6842 added for other engines 2026-02-08 02:58:59 +03:00
3e4598313d improved naming 2026-02-08 02:51:48 +03:00
d231139b83 added for source 2026-02-08 02:43:10 +03:00
9c4e2a3319 Merge pull request #146 from orange-cpp/feature/line_tracer_template
improvement
2026-02-06 00:14:15 +03:00
7597d95778 fixed warnings 2026-02-06 00:02:00 +03:00
5aa0e2e949 added noexcept 2026-02-05 23:45:41 +03:00
b7b1154f29 simplified shit 2026-02-05 23:43:17 +03:00
b10e26e6ba added constexpr 2026-02-05 23:38:51 +03:00
ba23fee243 removed uselss c++ file 2026-02-05 23:31:14 +03:00
32e0f9e636 improvement 2026-02-05 23:27:31 +03:00
63b4327c91 Merge pull request #145 from orange-cpp/feature/macho_improvement
Feature/macho improvement
2026-02-04 19:27:44 +03:00
dbad87de0f fixed bug 2026-02-04 19:10:06 +03:00
8dd044fa1e removed nesting 2026-02-04 18:35:04 +03:00
c25a3da196 removed nesting 2026-02-04 18:33:05 +03:00
d64d60cfcd fixed codestyle 2026-02-04 18:30:45 +03:00
2ef25b0ce8 added resharper ignore segment 2026-02-04 18:29:55 +03:00
Copilot
775949887a Add Mach-O pattern scanner (#144)
* Initial plan

* Add Mach-O pattern scanner implementation and unit tests

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

* Add Mach-O pattern scanner

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

* Remove CodeQL build artifacts from PR

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

---------

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

removed lock file
2026-01-30 02:36:34 +03:00
b0fd8d42f4 updated tag 2026-01-29 20:09:36 +03:00
b5229f72d5 removed unity build completely 2026-01-29 20:06:18 +03:00
d5233dd00b Merge pull request #139 from orange-cpp/feature/mesh_line_tracer
Feature/mesh line tracer
2026-01-27 01:42:41 +03:00
bd1d437d7d fixed naming 2026-01-27 01:25:49 +03:00
f67ad8ef42 mesh improvement 2026-01-27 01:25:07 +03:00
acc24f8438 WIP on feature/rotation_improved 2026-01-27 01:07:37 +03:00
8bccbdb620 Merge pull request #138 from orange-cpp/feature/primitives_improvement
Feature/primitives improvement
2026-01-27 01:07:15 +03:00
91a96fb97a dropped uselss cpp files 2026-01-27 00:51:17 +03:00
5fcac74a25 fixed naming 2026-01-27 00:39:34 +03:00
d2cf62bdbe added primitives to other engine triplets 2026-01-27 00:37:23 +03:00
2a2832c75f added opengl primitives 2026-01-27 00:33:35 +03:00
26fda5402e fix 2026-01-26 20:59:19 +03:00
906ddd75d4 fix 2026-01-26 19:01:39 +03:00
68505a77ae fix 2026-01-26 18:59:34 +03:00
d6746f6243 ppatch 2026-01-26 17:21:23 +03:00
ee2f084e0b added stuff 2026-01-26 17:07:38 +03:00
9bd42d9c8c added files 2026-01-26 15:44:46 +03:00
47d82fe083 Merge pull request #137 from luadebug/xm
xmake Add tests
2026-01-22 12:57:19 +03:00
Saikari
217fc108c2 fixup 2026-01-22 08:53:23 +03:00
Saikari
9c1c4fe6f3 Add tests 2026-01-22 08:52:18 +03:00
958156e6b7 Merge pull request #136 from luadebug/xmake
Add build configuration for omath and examples
2026-01-20 01:27:48 +03:00
Saikari
450f3a3ab0 Add rules for CMake and pkg-config imports 2026-01-19 04:45:41 +03:00
Saikari
8159686658 Add build configuration for omath and examples 2026-01-19 04:43:21 +03:00
9fc31d03e5 Update copyright year in LICENSE file 2026-01-17 21:35:34 +03:00
6017d40579 Merge pull request #135 from orange-cpp/feature/crypto_var
Feature/crypto var
2026-01-04 23:41:24 +03:00
618d4aa1c0 added final 2026-01-04 23:24:07 +03:00
8366c48965 improvement 2026-01-04 23:22:25 +03:00
d2e418c50b added cmake option 2026-01-04 23:02:58 +03:00
eae10d92ca forgot to remove 2026-01-04 22:53:57 +03:00
3f940c8e35 removed 128 bit int 2026-01-04 22:46:09 +03:00
0846022f8a improvement 2026-01-04 22:41:28 +03:00
8812abdf33 fixed test 2026-01-04 22:23:57 +03:00
d56d05f01e silenced warn 2026-01-04 22:16:40 +03:00
aabdebbbbe improved stuff 2026-01-04 22:16:40 +03:00
2b75b33d60 fix 2026-01-04 22:16:40 +03:00
9a3f5abb7c added check 2026-01-04 22:16:40 +03:00
be1049db93 changed algorithm 2026-01-04 22:16:40 +03:00
525b273a84 added stuff 2026-01-04 22:16:40 +03:00
77adc0ea8a unlikely added 2026-01-04 03:27:46 +03:00
83f17892d6 added unlikely 2026-01-03 08:57:56 +03:00
26fd8e158a i dont like icons now 2026-01-01 03:59:12 +03:00
2af77d30d4 Feature/elf pattern scan (#133)
* added some code

* improvement

* visit

* added scanner code

* update

* fixed naming

* added const

* added type casting

* added file

* patch

* added unlikely

* added in module scanner

* fixing test

* fix

* remove

* improvement

* fix

* Update source/utility/elf_pattern_scan.cpp

Co-authored-by: Saikari <lin@sz.cn.eu.org>

* rev

* fix

* patch

* decomposed method

* fix

* fix

* improvement

* fix

* fix

* commented stuff

---------

Co-authored-by: Saikari <lin@sz.cn.eu.org>
2026-01-01 03:37:55 +03:00
368272b1e8 fixed tests 2025-12-30 08:40:42 +03:00
Saikari
0ca471ed4f Use LLVM coverage and LCOV genhtml on Windows OS (#131) 2025-12-29 21:11:13 +03:00
Saikari
f0145ec68e Use LLVM coverage and LCOV genhtml on Windows OS (#129) 2025-12-28 02:44:00 +03:00
771e1e68fe improved code style 2025-12-28 02:32:35 +03:00
ffcf448a07 improved code style 2025-12-28 02:29:19 +03:00
00fcdc86bc Update license badge in README
Replaced the static badge with a dynamic GitHub license badge.
2025-12-27 20:07:45 +03:00
2c11c5ce1a returned back to zlib 2025-12-27 20:06:17 +03:00
73 changed files with 3816 additions and 706 deletions

25
.cmake-format Normal file
View File

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

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# SCM syntax highlighting & preventing 3-way merges
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff

View File

@@ -145,9 +145,9 @@ jobs:
cmake-build/build/${{ matrix.preset }}/**/*.log
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
##############################################################################
# 2) Windows MSVC / Ninja
##############################################################################
##############################################################################
# 2) Windows MSVC / Ninja
##############################################################################
windows-build-and-test:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
@@ -188,7 +188,12 @@ jobs:
- name: Configure (cmake --preset)
shell: bash
run: cmake --preset ${{ matrix.preset }} -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF -DOMATH_ENABLE_COVERAGE=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
@@ -198,35 +203,121 @@ jobs:
shell: bash
run: ./out/Release/unit_tests.exe
- name: Install OpenCppCoverage with Chocolatey
##########################################################################
# Coverage (x64-windows only)
##########################################################################
- name: Install LLVM
if: ${{ matrix.triplet == 'x64-windows' }}
run: choco install opencppcoverage -y
run: |
choco install llvm -y
- name: Clean Build Directory for Coverage
if: ${{ matrix.triplet == 'x64-windows' }}
shell: pwsh
run: |
$buildDir = "cmake-build/build/${{ matrix.preset }}"
if (Test-Path $buildDir) {
Write-Host "Cleaning build directory to prevent compiler conflict: $buildDir"
Remove-Item -Path $buildDir -Recurse -Force
}
- name: Build Debug for Coverage
if: ${{ matrix.triplet == 'x64-windows' }}
shell: bash
run: |
cmake --preset ${{ matrix.preset }} \
-DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" \
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" \
-DCMAKE_LINKER="C:/Program Files/LLVM/bin/lld-link.exe" \
-DOMATH_BUILD_TESTS=ON \
-DOMATH_BUILD_BENCHMARK=OFF \
-DOMATH_ENABLE_COVERAGE=ON \
-DOMATH_THREAT_WARNING_AS_ERROR=OFF \
-DCMAKE_BUILD_TYPE=Debug \
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
cmake --build cmake-build/build/${{ matrix.preset }} --config Debug --target unit_tests omath
- name: Run Coverage
- name: Run Tests (Generates .profraw)
if: ${{ matrix.triplet == 'x64-windows' }}
shell: pwsh
env:
LLVM_PROFILE_FILE: "cmake-build/build/${{ matrix.preset }}/unit_tests.profraw"
run: |
./out/Debug/unit_tests.exe
- name: Process Coverage (llvm-profdata & llvm-cov)
if: ${{ matrix.triplet == 'x64-windows' }}
shell: pwsh
run: |
$env:Path = "C:\Program Files\OpenCppCoverage;$env:Path"
cmake --build cmake-build/build/${{ matrix.preset }} --target coverage --config Debug
- name: Upload Coverage
$BUILD_DIR = "cmake-build/build/${{ matrix.preset }}"
$EXE_PATH = "./out/Debug/unit_tests.exe"
# 1. Merge raw profile data (essential step)
& "C:/Program Files/LLVM/bin/llvm-profdata.exe" merge `
-sparse "$BUILD_DIR/unit_tests.profraw" `
-o "$BUILD_DIR/unit_tests.profdata"
# 2. Export to LCOV format
# NOTE: We explicitly ignore vcpkg_installed and system headers
& "C:/Program Files/LLVM/bin/llvm-cov.exe" export "$EXE_PATH" `
-instr-profile="$BUILD_DIR/unit_tests.profdata" `
-format=lcov `
-ignore-filename-regex="vcpkg_installed|external|tests" `
> "$BUILD_DIR/lcov.info"
if (Test-Path "$BUILD_DIR/lcov.info") {
Write-Host "✅ LCOV info created at $BUILD_DIR/lcov.info"
} else {
Write-Error "Failed to create LCOV info"
exit 1
}
- name: Install LCOV (for genhtml)
if: ${{ matrix.triplet == 'x64-windows' }}
run: choco install lcov -y
- name: Generate HTML Report
if: ${{ matrix.triplet == 'x64-windows' }}
shell: bash
run: |
BUILD_DIR="cmake-build/build/${{ matrix.preset }}"
LCOV_INFO="${BUILD_DIR}/lcov.info"
HTML_DIR="${BUILD_DIR}/coverage-html"
# Fix paths for genhtml (Perl hates backslashes)
sed -i 's|\\|/|g' "${LCOV_INFO}"
# Locate genhtml provided by 'choco install lcov'
# It is typically in ProgramData/chocolatey/lib/lcov/tools/bin
GENHTML=$(find /c/ProgramData/chocolatey -name genhtml -print -quit)
if [ -z "$GENHTML" ]; then
echo "Error: genhtml executable not found"
exit 1
fi
echo "Using genhtml: $GENHTML"
mkdir -p "$HTML_DIR"
# Run genhtml
# Added --demangle-cpp if your version supports it, otherwise remove it
perl "$GENHTML" \
"${LCOV_INFO}" \
--output-directory "$HTML_DIR" \
--title "OMath Coverage Report" \
--legend \
--show-details \
--branch-coverage \
--ignore-errors source
echo "✅ LCOV HTML report generated at $HTML_DIR"
- name: Upload Coverage (HTML Report)
if: ${{ matrix.triplet == 'x64-windows' }}
uses: actions/upload-artifact@v4
with:
name: coverage-report-windows
path: cmake-build/build/${{ matrix.preset }}/coverage/
name: coverage-html-windows
path: cmake-build/build/${{ matrix.preset }}/coverage-html/
- name: Upload logs on failure
if: ${{ failure() }}
@@ -662,3 +753,61 @@ jobs:
path: |
cmake-build/build/${{ matrix.preset }}/**/*.log
${{ env.VCPKG_ROOT }}/buildtrees/**/*.log
##############################################################################
# 9) Valgrind Memory Check
##############################################################################
valgrind-memory-check:
name: Valgrind Analysis (All Targets)
runs-on: ubuntu-latest
needs: [linux-build-and-test]
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Install toolchain
shell: bash
run: |
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
sudo add-apt-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main"
sudo apt-get update
sudo apt-get install -y git build-essential cmake ninja-build \
zip unzip curl pkg-config ca-certificates \
clang-21 lld-21 libc++-21-dev libc++abi-21-dev \
llvm-21 valgrind libxmu-dev libxi-dev libgl-dev libxinerama-dev libxcursor-dev xorg-dev libglu1-mesa-dev
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-21 100
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset linux-release-vcpkg \
-DCMAKE_BUILD_TYPE=Debug \
-DOMATH_BUILD_EXAMPLES=OFF \
-DOMATH_BUILD_TESTS=ON \
-DOMATH_BUILD_BENCHMARK=ON \
-DOMATH_ENABLE_VALGRIND=ON \
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;benchmark"
- name: Build All Targets
shell: bash
run: cmake --build cmake-build/build/linux-release-vcpkg
- name: Run Valgrind (All Registered Targets)
shell: bash
working-directory: cmake-build/build/linux-release-vcpkg
run: |
cmake --build . --target valgrind_all

8
.gitignore vendored
View File

@@ -5,4 +5,10 @@
.idea/workspace.xml
/build/
/clang-coverage/
*.gcov
*.gcov
*.bin
# pixi lock
pixi.lock
# pixi environments
.pixi/*
!.pixi/config.toml

View File

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

44
LICENSE
View File

@@ -1,4 +1,4 @@
Copyright (C) 2024-2025 Orange++ <orange_github@proton.me>
Copyright (C) 2023-2026 Orange++ orange_github@proton.me
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -15,45 +15,3 @@ freely, subject to the following restrictions:
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
4. If you are an employee, contractor, volunteer, representative,
or have any other affiliation (past or present)
with any of the following entities:
* "Advertising Placement Services" LLC
* "NEW SOLUTIONS VERTICALS" LLC
* "Autoexpert" LLC
* "Creditit" LLC
* "Yandex.Taxi" LLC
* "Yandex.Eda" LLC
* "Yandex.Lavka" LLC
* "Yandex.Telecom" LLC
* "Yandex.Cloud" LLC
* "Micromobility" LLC
* "MM-Tech" LLC
* "Carsharing" LLC
* "Yandex.Drive" LLC
* "EDADIL PROMO" LLC
* "Kinopoisk" LLC
* "Yandex.Music" LLC
* "Refueling (Yandex.Zapravki)" LLC
* "Yandex.Pay" LLC
* "Financial and Payment Technologies" LLC
* "Yandex.Delivery" LLC
* "Delivery Club" LLC
* "Yandex.Check" LLC
* "SMB-Service" LLC
* "ADV-TECH" LLC
* "Yandex Fantech" LLC
* "Yandex Smena" LLC
* "Market.Operations" LLC
* "Yandex.Market" LLC
* "ID Tech" LLC
* "Yandex.Crowd" LLC
* "Yandex" LLC
* "Rutube" LLC
* "Kaspersky" LLC
Or if you represent or are associated with any legal, organizational, or
professional entity providing services to or on behalf of the aforementioned entities:
You are expressly forbidden from accessing, using, modifying, distributing, or
interacting with the Software and its source code in any form. You must immediately
delete or destroy any physical or digital copies of the Software and/or
its source code, including any derivative works, tools, or information obtained from the Software.

View File

@@ -2,7 +2,7 @@
![banner](docs/images/logos/omath_logo_macro.png)
![Static Badge](https://img.shields.io/badge/license-libomath-orange)
![GitHub License](https://img.shields.io/github/license/orange-cpp/omath)
![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath)
![GitHub top language](https://img.shields.io/github/languages/top/orange-cpp/omath)
![GitHub repo size](https://img.shields.io/github/repo-size/orange-cpp/omath)
@@ -44,7 +44,7 @@ It provides the latest features, is highly customizable, has all for cheat devel
</a>
</div>
## 🚀 Quick Example
## Quick Example
```cpp
#include <omath/omath.hpp>
@@ -69,20 +69,20 @@ if (auto screen = camera.world_to_screen(world_position)) {
}
```
**[➡️ See more examples and tutorials][TUTORIALS]**
**[See more examples and tutorials][TUTORIALS]**
# Features
- **🚀 Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
- **🎯 Versatility**: Includes a wide array of mathematical functions and algorithms.
- **Ease of Use**: Simplified interface for convenient integration into various projects.
- **🎮 Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
- **📐 3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
- **💥 Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
- **📦 No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
- **🔧 Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
- **🎯 Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL**.
- **🌍 Cross platform**: Supports Windows, MacOS and Linux.
- **🔍 Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps.
# Features
- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
- **Versatility**: Includes a wide array of mathematical functions and algorithms.
- **Ease of Use**: Simplified interface for convenient integration into various projects.
- **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
- **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
- **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL**.
- **Cross platform**: Supports Windows, MacOS and Linux.
- **Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps.
<div align = center>
# Gallery
@@ -116,7 +116,7 @@ if (auto screen = camera.world_to_screen(world_position)) {
</div>
## 📚 Documentation
## Documentation
- **[Getting Started Guide](http://libomath.org/getting_started/)** - Installation and first steps
- **[API Overview](http://libomath.org/api_overview/)** - Complete API reference
@@ -125,14 +125,14 @@ if (auto screen = camera.world_to_screen(world_position)) {
- **[Troubleshooting](http://libomath.org/troubleshooting/)** - Solutions to common issues
- **[Best Practices](http://libomath.org/best_practices/)** - Guidelines for effective usage
## 🤝 Community & Support
## Community & Support
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
- **Issues**: [Report bugs or request features](https://github.com/orange-cpp/omath/issues)
- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines
# 💘 Acknowledgments
# Acknowledgments
- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors)
<!----------------------------------{ Images }--------------------------------->

View File

@@ -1 +1 @@
4.6.0
4.7.1

View File

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

View File

@@ -6,117 +6,62 @@ function(omath_setup_coverage TARGET_NAME)
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
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC)
target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping
/Zi)
target_link_options(
${TARGET_NAME}
PRIVATE
-fprofile-instr-generate
-fcoverage-mapping
-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
)
/PROFILE)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping
-g -O0)
target_link_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${TARGET_NAME} PRIVATE --coverage -g -O0)
target_link_options(${TARGET_NAME} PRIVATE --coverage)
endif()
# 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
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC)
message(STATUS "MSVC detected: Use VS Code Coverage from CI workflow")
add_custom_target(
coverage
DEPENDS unit_tests
COMMAND "${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>"
COMMAND $<TARGET_FILE:unit_tests>
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Running OpenCppCoverage"
)
COMMENT "Running tests for coverage (use VS Code Coverage from CI)")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
# Linux/macOS: LLVM coverage via script
add_custom_target(coverage
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"
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"
)
COMMENT "Running LLVM coverage")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# GCC: lcov/gcov
add_custom_target(coverage
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"
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"
)
COMMENT "Running lcov/genhtml")
endif()
endfunction()

41
cmake/Valgrind.cmake Normal file
View File

@@ -0,0 +1,41 @@
# cmake/Valgrind.cmake
if(DEFINED __OMATH_VALGRIND_INCLUDED)
return()
endif()
set(__OMATH_VALGRIND_INCLUDED TRUE)
find_program(VALGRIND_EXECUTABLE valgrind)
option(OMATH_ENABLE_VALGRIND "Enable Valgrind target for memory checking" ON)
if(OMATH_ENABLE_VALGRIND AND NOT TARGET valgrind_all)
add_custom_target(valgrind_all)
endif()
function(omath_setup_valgrind TARGET_NAME)
if(NOT OMATH_ENABLE_VALGRIND)
return()
endif()
if(NOT VALGRIND_EXECUTABLE)
message(WARNING "OMATH_ENABLE_VALGRIND is ON, but 'valgrind' executable was not found.")
return()
endif()
set(VALGRIND_FLAGS --leak-check=full --show-leak-kinds=all --track-origins=yes
--error-exitcode=99)
set(VALGRIND_TARGET "valgrind_${TARGET_NAME}")
if(NOT TARGET ${VALGRIND_TARGET})
add_custom_target(
${VALGRIND_TARGET}
DEPENDS ${TARGET_NAME}
COMMAND ${VALGRIND_EXECUTABLE} ${VALGRIND_FLAGS} $<TARGET_FILE:${TARGET_NAME}>
WORKING_DIRECTORY $<TARGET_FILE_DIR:${TARGET_NAME}>
COMMENT "Running Valgrind memory check on ${TARGET_NAME}..."
USES_TERMINAL)
add_dependencies(valgrind_all ${VALGRIND_TARGET})
endif()
endfunction()

View File

@@ -1,32 +1,38 @@
project(examples)
add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp)
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}"
)
set_target_properties(
example_projection_matrix_builder
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
target_link_libraries(example_projection_matrix_builder PRIVATE omath::omath)
add_executable(example_signature_scan example_signature_scan.cpp)
set_target_properties(example_signature_scan PROPERTIES
CXX_STANDARD 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}"
)
set_target_properties(
example_signature_scan
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
target_link_libraries(example_signature_scan PRIVATE omath::omath)
add_executable(example_glfw3 example_glfw3.cpp)
set_target_properties(example_glfw3 PROPERTIES CXX_STANDARD 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}"
)
set_target_properties(
example_glfw3
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(OpenGL)
find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw)
target_link_libraries(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw OpenGL::OpenGL)
if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(example_projection_matrix_builder)
omath_setup_valgrind(example_signature_scan)
omath_setup_valgrind(example_glfw3)
endif()

View File

@@ -120,9 +120,14 @@ int main()
return -1;
}
std::cout << "GLFW Version: " << glfwGetVersionString() << "\n";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Force GLX context creation API to ensure compatibility with GLEW
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
@@ -141,15 +146,30 @@ int main()
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// Check if context is valid using standard GL
const GLubyte* renderer = glGetString(GL_RENDERER);
const GLubyte* version = glGetString(GL_VERSION);
if (renderer && version) {
std::cout << "Renderer: " << renderer << "\n";
std::cout << "OpenGL version supported: " << version << "\n";
} else {
std::cerr << "Failed to get GL_RENDERER or GL_VERSION. Context might be invalid.\n";
}
// ---------- GLEW init ----------
glewExperimental = GL_TRUE;
GLenum glewErr = glewInit();
if (glewErr != GLEW_OK)
{
std::cerr << "Failed to initialize GLEW: " << reinterpret_cast<const char*>(glewGetErrorString(glewErr))
<< "\n";
glfwTerminate();
return -1;
// Ignore NO_GLX_DISPLAY if we have a valid context
if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY && renderer) {
std::cerr << "GLEW warning: " << glewGetErrorString(glewErr) << " (Ignored because context seems valid)\n";
} else {
std::cerr << "Failed to initialize GLEW: " << reinterpret_cast<const char*>(glewGetErrorString(glewErr))
<< "\n";
glfwTerminate();
return -1;
}
}
// ---------- GL state ----------
@@ -239,8 +259,8 @@ int main()
// 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.reserve(cube.m_element_buffer_object.size() * 3);
for (const auto& tri : cube.m_element_buffer_object)
{
flatIndices.push_back(tri.x);
flatIndices.push_back(tri.y);

View File

@@ -3,14 +3,60 @@
//
#pragma once
#include "mesh.hpp"
#include "omath/engines/opengl_engine/camera.hpp"
#include "omath/engines/opengl_engine/traits/mesh_trait.hpp"
#include "omath/linear_algebra/triangle.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
namespace omath::primitives
{
template<class BoxMeshType>
[[nodiscard]]
std::array<Triangle<Vector3<float>>, 12> create_box(const Vector3<float>& top, const Vector3<float>& bottom,
const Vector3<float>& dir_forward, const Vector3<float>& dir_right,
float ratio = 4.f) noexcept;
}
BoxMeshType create_box(const Vector3<float>& top, const Vector3<float>& bottom, const Vector3<float>& dir_forward,
const Vector3<float>& dir_right, const float ratio = 4.f) noexcept
{
const auto height = top.distance_to(bottom);
const auto side_size = height / ratio;
// corner layout (03 bottom, 47 top)
std::array<Vector3<float>, 8> p;
p[0] = bottom + (dir_forward + dir_right) * side_size; // frontrightbottom
p[1] = bottom + (dir_forward - dir_right) * side_size; // frontleftbottom
p[2] = bottom + (-dir_forward + dir_right) * side_size; // backrightbottom
p[3] = bottom + (-dir_forward - dir_right) * side_size; // backleftbottom
p[4] = top + (dir_forward + dir_right) * side_size; // frontrighttop
p[5] = top + (dir_forward - dir_right) * side_size; // frontlefttop
p[6] = top + (-dir_forward + dir_right) * side_size; // backrighttop
p[7] = top + (-dir_forward - dir_right) * side_size; // backlefttop
std::array<Vector3<std::uint32_t>, 12> poly;
// bottom face (+Y up ⇒ wind CW when viewed from above)
poly[0] = {0, 2, 3};
poly[1] = {0, 3, 1};
// top face
poly[2] = {4, 7, 6};
poly[3] = {4, 5, 7};
// front face
poly[4] = {0, 5, 1};
poly[5] = {0, 4, 5};
// right face
poly[6] = {0, 6, 2};
poly[7] = {0, 4, 6};
// back face
poly[8] = {2, 7, 3};
poly[9] = {2, 6, 7};
// left face
poly[10] = {1, 7, 5};
poly[11] = {1, 3, 7};
return BoxMeshType{std::move(p), std::move(poly)};
}
} // namespace omath::primitives

View File

@@ -25,16 +25,17 @@ namespace omath::primitives
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<>>
template<class Mat4X4, class RotationAngles, class MeshTypeTrait, class VertType = Vertex<>,
class VboType = std::vector<VertType>, class EboType = std::vector<Vector3<std::uint32_t>>>
class Mesh final
{
public:
using VectorType = VertType::VectorType;
using VertexType = VertType;
using VertexType = VboType::value_type;
private:
using Vbo = std::vector<VertexType>;
using Ebo = std::vector<Vector3<std::uint32_t>>;
using Vbo = VboType;
using Ebo = EboType;
public:
Vbo m_vertex_buffer;
@@ -100,20 +101,26 @@ namespace omath::primitives
[[nodiscard]]
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<typename Mat4X4::ContainedType, Mat4X4::get_store_ordering()>(vertex_position);
auto abs_vec = get_to_world_matrix()
* mat_column_from_vector<typename Mat4X4::ContainedType, Mat4X4::get_store_ordering()>(
vertex_position);
return {abs_vec.at(0, 0), abs_vec.at(1, 0), abs_vec.at(2, 0)};
}
[[nodiscard]]
Triangle<VectorType> make_face_in_world_space(const Ebo::const_iterator vao_iterator) const
requires HasPosition<VertexType>
{
return {vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->x).position),
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->y).position),
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->z).position)};
if constexpr (HasPosition<VertexType>)
{
return {vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->x).position),
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->y).position),
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->z).position)};
}
return {vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->x)),
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->y)),
vertex_position_to_world_space(m_vertex_buffer.at(vao_iterator->z))};
}
private:

View File

@@ -3,14 +3,30 @@
//
#pragma once
#include "mesh.hpp"
#include "omath/engines/opengl_engine/camera.hpp"
#include "omath/engines/opengl_engine/mesh.hpp"
#include "omath/engines/opengl_engine/traits/mesh_trait.hpp"
#include "omath/linear_algebra/triangle.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
namespace omath::primitives
{
template<class PlaneMeshType>
[[nodiscard]]
std::array<Triangle<Vector3<float>>, 2> create_plane(const Vector3<float>& vertex_a,
const Vector3<float>& vertex_b,
const Vector3<float>& direction, float size) noexcept;
}
PlaneMeshType create_plane(const Vector3<float>& vertex_a, const Vector3<float>& vertex_b,
const Vector3<float>& direction, const float size) noexcept
{
const auto second_vertex_a = vertex_a + direction * size;
const auto second_vertex_b = vertex_b + direction * size;
std::array<Vector3<float>, 4> grid = {vertex_a, vertex_b, second_vertex_a, second_vertex_b};
std::array<Vector3<std::uint32_t>, 2> poly;
poly[0] = {1, 1, 2};
poly[1] = {0, 1, 3};
return PlaneMeshType(std::move(grid), std::move(poly));
}
} // namespace omath::primitives

View File

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

View File

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

View File

@@ -0,0 +1,201 @@
//
// Created by Vladislav on 04.01.2026.
//
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
#ifdef OMATH_ENABLE_FORCE_INLINE
#ifdef _MSC_VER
#define OMATH_FORCE_INLINE __forceinline
#else
#define OMATH_FORCE_INLINE __attribute__((always_inline)) inline
#endif
#else
#define OMATH_FORCE_INLINE
#endif
namespace omath::detail
{
[[nodiscard]]
consteval std::uint64_t fnv1a_64(const char* s)
{
std::uint64_t h = 14695981039346656037ull;
while (*s)
{
h ^= static_cast<unsigned char>(*s++);
h *= 1099511628211ull;
}
return h;
}
// SplitMix64 mixer (good quality for seeding / scrambling)
[[nodiscard]]
consteval std::uint64_t splitmix64(std::uint64_t x)
{
x += 0x9E3779B97F4A7C15ull;
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ull;
x = (x ^ (x >> 27)) * 0x94D049BB133111EBull;
return x ^ (x >> 31);
}
// Choose your policy:
// - If you want reproducible builds, REMOVE __DATE__/__TIME__.
// - If you want "different each build", keep them.
[[nodiscard]]
consteval std::uint64_t base_seed()
{
std::uint64_t h = 0;
h ^= fnv1a_64(__FILE__);
h ^= splitmix64(fnv1a_64(__DATE__));
h ^= splitmix64(fnv1a_64(__TIME__));
return splitmix64(h);
}
// Produce a "random" 64-bit value for a given stream index (compile-time)
template<std::uint64_t Stream>
[[nodiscard]]
consteval std::uint64_t rand_u64()
{
// Stream is usually __COUNTER__ so each call site differs
return splitmix64(base_seed() + 0xD1B54A32D192ED03ull * (Stream + 1));
}
[[nodiscard]]
consteval std::uint64_t bounded_u64(const std::uint64_t x, const std::uint64_t bound)
{
return (x * bound) >> 64;
}
template<std::int64_t Lo, std::int64_t Hi, std::uint64_t Stream>
[[nodiscard]]
consteval std::int64_t rand_uint8_t()
{
static_assert(Lo <= Hi);
const std::uint64_t span = static_cast<std::uint64_t>(Hi - Lo) + 1ull;
const std::uint64_t r = rand_u64<Stream>();
return static_cast<std::int64_t>(bounded_u64(r, span)) + Lo;
}
[[nodiscard]]
consteval std::uint64_t rand_u64(const std::uint64_t seed, const std::uint64_t i)
{
return splitmix64(seed + 0xD1B54A32D192ED03ull * (i + 1ull));
}
// Convert to int (uses low 32 bits; you can also use high bits if you prefer)
[[nodiscard]]
consteval std::uint8_t rand_uint8t(const std::uint64_t seed, const std::uint64_t i)
{
return static_cast<std::uint8_t>(rand_u64(seed, i)); // narrowing is fine/deterministic
}
template<std::size_t N, std::uint64_t Seed, std::size_t... I>
[[nodiscard]]
consteval std::array<std::uint8_t, N> make_array_impl(std::index_sequence<I...>)
{
return {rand_uint8t(Seed, static_cast<std::uint64_t>(I))...};
}
template<std::size_t N, std::uint64_t Seed>
[[nodiscard]]
consteval std::array<std::uint8_t, N> make_array()
{
return make_array_impl<N, Seed>(std::make_index_sequence<N>{});
}
} // namespace omath::detail
namespace omath
{
template<class T>
class VarAnchor;
template<class T, std::size_t key_size, std::array<std::uint8_t, key_size> key>
class EncryptedVariable final
{
using value_type = std::remove_cvref_t<T>;
bool m_is_encrypted{};
value_type m_data{};
OMATH_FORCE_INLINE constexpr void xor_contained_var_by_key()
{
// Safe, keeps const-correctness, and avoids reinterpret_cast issues
auto bytes = std::as_writable_bytes(std::span<value_type, 1>{&m_data, 1});
for (std::size_t i = 0; i < bytes.size(); ++i)
{
const std::uint8_t k = static_cast<std::uint8_t>(key[i % key_size] + (i * key_size));
bytes[i] ^= static_cast<std::byte>(k);
}
}
public:
OMATH_FORCE_INLINE constexpr explicit EncryptedVariable(const value_type& data)
: m_is_encrypted(false), m_data(data)
{
encrypt();
}
[[nodiscard]] constexpr bool is_encrypted() const
{
return m_is_encrypted;
}
OMATH_FORCE_INLINE constexpr void decrypt()
{
if (!m_is_encrypted)
return;
xor_contained_var_by_key();
m_is_encrypted = false;
}
OMATH_FORCE_INLINE constexpr void encrypt()
{
if (m_is_encrypted)
return;
xor_contained_var_by_key();
m_is_encrypted = true;
}
[[nodiscard]] OMATH_FORCE_INLINE constexpr value_type& value()
{
return m_data;
}
[[nodiscard]] OMATH_FORCE_INLINE constexpr const value_type& value() const
{
return m_data;
}
constexpr OMATH_FORCE_INLINE ~EncryptedVariable()
{
decrypt();
}
[[nodiscard]] constexpr OMATH_FORCE_INLINE auto drop_anchor()
{
return VarAnchor{*this};
}
};
template<class EncryptedVarType>
class VarAnchor final
{
public:
// ReSharper disable once CppNonExplicitConvertingConstructor
OMATH_FORCE_INLINE constexpr VarAnchor(EncryptedVarType& var): m_var(var)
{
m_var.decrypt();
}
OMATH_FORCE_INLINE constexpr ~VarAnchor()
{
m_var.encrypt();
}
private:
EncryptedVarType& m_var;
};
} // namespace omath
#define OMATH_CT_RAND_ARRAY_BYTE(N) \
(::omath::detail::make_array<(N), (::omath::detail::base_seed() ^ static_cast<std::uint64_t>(__COUNTER__))>())
#define OMATH_DEF_CRYPT_VAR(TYPE, KEY_SIZE) omath::EncryptedVariable<TYPE, KEY_SIZE, OMATH_CT_RAND_ARRAY_BYTE(KEY_SIZE)>

View File

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

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 27.01.2026.
//
#pragma once
#include "mesh.hpp"
#include "omath/engines/frostbite_engine/traits/mesh_trait.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
namespace omath::frostbite_engine
{
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
std::array<Vector3<std::uint32_t>, 12>>;
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
} // namespace omath::frostbite_engine

View File

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

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 27.01.2026.
//
#pragma once
#include "mesh.hpp"
#include "omath/engines/iw_engine/traits/mesh_trait.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
namespace omath::iw_engine
{
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
std::array<Vector3<std::uint32_t>, 12>>;
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
} // namespace omath::iw_engine

View File

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

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 27.01.2026.
//
#pragma once
#include "mesh.hpp"
#include "omath/engines/opengl_engine/traits/mesh_trait.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
namespace omath::opengl_engine
{
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
std::array<Vector3<std::uint32_t>, 12>>;
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
} // namespace omath::opengl_engine

View File

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

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 27.01.2026.
//
#pragma once
#include "mesh.hpp"
#include "omath/engines/source_engine/traits/mesh_trait.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
namespace omath::source_engine
{
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
std::array<Vector3<std::uint32_t>, 12>>;
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
} // namespace omath::source_engine

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 27.01.2026.
//
#pragma once
#include "mesh.hpp"
#include "omath/engines/unity_engine/traits/mesh_trait.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
namespace omath::unity_engine
{
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
std::array<Vector3<std::uint32_t>, 12>>;
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
} // namespace omath::unity_engine

View File

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

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 27.01.2026.
//
#pragma once
#include "mesh.hpp"
#include "omath/engines/unreal_engine/traits/mesh_trait.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <array>
namespace omath::unreal_engine
{
using BoxMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>, std::array<Vector3<float>, 8>,
std::array<Vector3<std::uint32_t>, 12>>;
using PlaneMesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, primitives::Vertex<>,
std::array<Vector3<float>, 4>, std::array<Vector3<std::uint32_t>, 2>>;
} // namespace omath::unreal_engine

View File

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

View File

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

View File

@@ -7,23 +7,20 @@
#include <filesystem>
#include <optional>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
struct PeSectionScanResult
{
std::uint64_t virtual_base_addr;
std::uint64_t raw_base_addr;
std::ptrdiff_t target_offset;
};
class PePatternScanner final
{
public:
[[nodiscard]]
static std::optional<std::uintptr_t> scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern);
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
[[nodiscard]]
static std::optional<PeSectionScanResult>
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
};

View File

@@ -0,0 +1,16 @@
//
// Created by Vladislav on 01.01.2026.
//
#pragma once
#include <cstddef>
#include <cstdint>
namespace omath
{
struct SectionScanResult final
{
std::uintptr_t virtual_base_addr;
std::uintptr_t raw_base_addr;
std::ptrdiff_t target_offset;
};
}

69
pixi.toml Normal file
View File

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

36
pixi/fmt.cmake Normal file
View File

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

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

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

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

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

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

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

77
scripts/valgrind.sh Executable file
View File

@@ -0,0 +1,77 @@
#!/bin/bash
# =============================================================================
# Local Reproduction of "Valgrind Analysis" GitHub Action
# =============================================================================
# Stop on error, undefined variables, or pipe failures
set -euo pipefail
# --- 1. Environment Setup ---
# Determine VCPKG_ROOT
# If VCPKG_ROOT is not set in your shell, we check if a local folder exists.
if [[ -z "${VCPKG_ROOT:-}" ]]; then
if [[ -d "./vcpkg" ]]; then
export VCPKG_ROOT="$(pwd)/vcpkg"
echo "Found local vcpkg at: $VCPKG_ROOT"
# Bootstrap vcpkg if the executable doesn't exist
if [[ ! -f "$VCPKG_ROOT/vcpkg" ]]; then
echo "Bootstrapping vcpkg..."
"$VCPKG_ROOT/bootstrap-vcpkg.sh"
fi
else
echo "Error: VCPKG_ROOT is not set and ./vcpkg directory not found."
echo "Please install vcpkg or set the VCPKG_ROOT environment variable."
exit 1
fi
else
echo "Using existing VCPKG_ROOT: $VCPKG_ROOT"
fi
# Set the build directory matching the YAML's preset expectation
# Assuming the preset writes to: cmake-build/build/linux-release-vcpkg
BUILD_DIR="cmake-build/build/linux-release-vcpkg"
# Check if Valgrind is installed
if ! command -v valgrind &> /dev/null; then
echo "Error: valgrind is not installed. Please install it (e.g., sudo apt install valgrind)."
exit 1
fi
echo "----------------------------------------------------"
echo "Starting Configuration (Debug Build with Valgrind)..."
echo "----------------------------------------------------"
# --- 2. Configure (CMake) ---
# We force CMAKE_BUILD_TYPE=Debug even though the preset says 'release'
# to ensure Valgrind has access to debug symbols (line numbers).
cmake --preset linux-release-vcpkg \
-DCMAKE_BUILD_TYPE=Debug \
-DOMATH_BUILD_EXAMPLES=OFF \
-DOMATH_BUILD_TESTS=ON \
-DOMATH_BUILD_BENCHMARK=ON \
-DOMATH_ENABLE_VALGRIND=ON \
-DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;benchmark"
echo "----------------------------------------------------"
echo "Building Targets..."
echo "----------------------------------------------------"
# --- 3. Build ---
# Using the specific build directory defined by the preset structure
cmake --build "$BUILD_DIR"
echo "----------------------------------------------------"
echo "Running Valgrind Analysis..."
echo "----------------------------------------------------"
# --- 4. Run Valgrind ---
# Runs the specific custom target defined in your CMakeLists.txt
cmake --build "$BUILD_DIR" --target valgrind_all
echo "----------------------------------------------------"
echo "Valgrind Analysis Complete."
echo "----------------------------------------------------"

View File

@@ -1,54 +0,0 @@
//
// Created by Vlad on 4/18/2025.
//
#include "omath/3d_primitives/box.hpp"
namespace omath::primitives
{
std::array<Triangle<Vector3<float>>, 12> create_box(const Vector3<float>& top, const Vector3<float>& bottom,
const Vector3<float>& dir_forward,
const Vector3<float>& dir_right, const float ratio) noexcept
{
const auto height = top.distance_to(bottom);
const auto side_size = height / ratio;
// corner layout (03 bottom, 47 top)
std::array<Vector3<float>, 8> p;
p[0] = bottom + (dir_forward + dir_right) * side_size; // frontrightbottom
p[1] = bottom + (dir_forward - dir_right) * side_size; // frontleftbottom
p[2] = bottom + (-dir_forward + dir_right) * side_size; // backrightbottom
p[3] = bottom + (-dir_forward - dir_right) * side_size; // backleftbottom
p[4] = top + (dir_forward + dir_right) * side_size; // frontrighttop
p[5] = top + (dir_forward - dir_right) * side_size; // frontlefttop
p[6] = top + (-dir_forward + dir_right) * side_size; // backrighttop
p[7] = top + (-dir_forward - dir_right) * side_size; // backlefttop
std::array<Triangle<Vector3<float>>, 12> poly;
// bottom face (+Y up ⇒ wind CW when viewed from above)
poly[0] = {p[0], p[2], p[3]};
poly[1] = {p[0], p[3], p[1]};
// top face
poly[2] = {p[4], p[7], p[6]};
poly[3] = {p[4], p[5], p[7]};
// front face
poly[4] = {p[0], p[5], p[1]};
poly[5] = {p[0], p[4], p[5]};
// right face
poly[6] = {p[0], p[6], p[2]};
poly[7] = {p[0], p[4], p[6]};
// back face
poly[8] = {p[2], p[7], p[3]};
poly[9] = {p[2], p[6], p[7]};
// left face
poly[10] = {p[1], p[7], p[5]};
poly[11] = {p[1], p[3], p[7]};
return poly;
}
} // namespace omath::primitives

View File

@@ -1,19 +0,0 @@
//
// Created by Vlad on 8/28/2025.
//
#include "omath/3d_primitives/plane.hpp"
namespace omath::primitives
{
std::array<Triangle<Vector3<float>>, 2> create_plane(const Vector3<float>& vertex_a,
const Vector3<float>& vertex_b,
const Vector3<float>& direction, const float size) noexcept
{
const auto second_vertex_a = vertex_a + direction * size;
return std::array
{
Triangle{second_vertex_a, vertex_a, vertex_b},
Triangle{second_vertex_a, vertex_b + direction * size, vertex_b}
};
}
} // namespace omath::primitives

View File

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

View File

@@ -0,0 +1,325 @@
//
// Created by Vladislav on 30.12.2025.
//
#include "omath/utility/pattern_scan.hpp"
#include <array>
#include <fstream>
#include <omath/utility/elf_pattern_scan.hpp>
#include <utility>
#include <variant>
#include <vector>
#pragma pack(push, 1)
namespace
{
// Common
constexpr uint8_t ei_nident = 16;
constexpr uint8_t ei_class = 4;
constexpr uint8_t elfclass32 = 1;
constexpr uint8_t elfclass64 = 2;
// ReSharper disable CppDeclaratorNeverUsed
struct Elf32Ehdr final
{
unsigned char e_ident[ei_nident];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint32_t e_entry;
uint32_t e_phoff;
uint32_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct Elf64Ehdr final
{
unsigned char e_ident[ei_nident];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint64_t e_entry;
uint64_t e_phoff;
uint64_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct Elf32Shdr final
{
uint32_t sh_name;
uint32_t sh_type;
uint32_t sh_flags;
uint32_t sh_addr;
uint32_t sh_offset;
uint32_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint32_t sh_addralign;
uint32_t sh_entsize;
};
struct Elf64Shdr final
{
uint32_t sh_name;
uint32_t sh_type;
uint64_t sh_flags;
uint64_t sh_addr;
uint64_t sh_offset;
uint64_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint64_t sh_addralign;
uint64_t sh_entsize;
};
// ReSharper restore CppDeclaratorNeverUsed
#pragma pack(pop)
} // namespace
namespace
{
enum class FileArch : std::int8_t
{
x32,
x64,
};
template<FileArch arch>
struct ElfHeaders
{
using FileHeader = std::conditional_t<arch == FileArch::x64, Elf64Ehdr, Elf32Ehdr>;
using SectionHeader = std::conditional_t<arch == FileArch::x64, Elf64Shdr, Elf32Shdr>;
FileHeader file_header;
SectionHeader section_header;
};
[[nodiscard]]
bool not_elf_file(std::fstream& file)
{
constexpr std::string_view valid_elf_signature = "\x7F"
"ELF";
std::array<char, valid_elf_signature.size() + 1> elf_signature{};
const std::streampos back_up_pose = file.tellg();
file.seekg(0, std::ios_base::beg);
file.read(elf_signature.data(), 4);
file.seekg(back_up_pose, std::ios_base::beg);
return std::string_view{elf_signature.data(), 4} != valid_elf_signature;
}
[[nodiscard]]
std::optional<FileArch> get_file_arch(std::fstream& file)
{
std::array<char, ei_nident> e_ident{};
const std::streampos back_up_pose = file.tellg();
file.seekg(0, std::ios_base::beg);
file.read(e_ident.data(), e_ident.size());
file.seekg(back_up_pose, std::ios_base::beg);
if (e_ident[ei_class] == elfclass64)
return FileArch::x64;
if (e_ident[ei_class] == elfclass32)
return FileArch::x32;
return std::nullopt;
}
struct ExtractedSection final
{
std::uintptr_t virtual_base_addr{};
std::uintptr_t raw_base_addr{};
std::vector<std::byte> data;
};
[[maybe_unused]]
std::optional<ExtractedSection> get_elf_section_by_name(const std::filesystem::path& path,
const std::string_view& section_name)
{
std::fstream file(path, std::ios::binary | std::ios::in);
if (!file.is_open()) [[unlikely]]
return std::nullopt;
if (not_elf_file(file)) [[unlikely]]
return std::nullopt;
const auto architecture = get_file_arch(file);
if (!architecture.has_value()) [[unlikely]]
return std::nullopt;
std::variant<ElfHeaders<FileArch::x64>, ElfHeaders<FileArch::x32>> elf_headers;
if (architecture.value() == FileArch::x64)
elf_headers = ElfHeaders<FileArch::x64>{};
else if (architecture.value() == FileArch::x32)
elf_headers = ElfHeaders<FileArch::x32>{};
return std::visit(
[&](auto& header) -> std::optional<ExtractedSection>
{
auto& [file_header, section_header] = header;
file.seekg(0, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&file_header), sizeof(file_header))) [[unlikely]]
return std::nullopt;
const std::streamoff shstr_off =
static_cast<std::streamoff>(file_header.e_shoff)
+ static_cast<std::streamoff>(file_header.e_shstrndx) * sizeof(section_header);
file.seekg(shstr_off, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&section_header), sizeof(section_header))) [[unlikely]]
return std::nullopt;
std::vector<char> shstrtab(static_cast<std::size_t>(section_header.sh_size));
file.seekg(section_header.sh_offset, std::ios_base::beg);
if (!file.read(shstrtab.data(), static_cast<std::streamsize>(shstrtab.size()))) [[unlikely]]
return std::nullopt;
for (std::uint16_t i = 0; i < file_header.e_shnum; ++i)
{
decltype(section_header) current_section{};
const std::streamoff off = static_cast<std::streamoff>(file_header.e_shoff)
+ static_cast<std::streamoff>(i) * sizeof(current_section);
file.seekg(off, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&current_section), sizeof(current_section))) [[unlikely]]
return std::nullopt;
if (current_section.sh_name >= shstrtab.size()) [[unlikely]]
continue;
// ReSharper disable once CppTooWideScopeInitStatement
const std::string_view name = &shstrtab[current_section.sh_name];
if (section_name != name)
continue;
ExtractedSection out;
out.virtual_base_addr = static_cast<std::uintptr_t>(current_section.sh_addr);
out.raw_base_addr = static_cast<std::uintptr_t>(current_section.sh_offset);
out.data.resize(static_cast<std::size_t>(current_section.sh_size));
file.seekg(static_cast<std::streamoff>(out.raw_base_addr), std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(out.data.data()),
static_cast<std::streamsize>(out.data.size()))) [[unlikely]]
return std::nullopt;
return out;
}
return std::nullopt;
},
elf_headers);
}
template<class FileHeader, class SectionHeader>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
const std::string_view target_section_name)
{
const auto* file_header = reinterpret_cast<const FileHeader*>(base);
const auto shoff = static_cast<std::size_t>(file_header->e_shoff);
const auto shnum = static_cast<std::size_t>(file_header->e_shnum);
const auto shstrnd = static_cast<std::size_t>(file_header->e_shstrndx);
const auto shstrtab_off = shoff + shstrnd * sizeof(SectionHeader);
const auto* shstrtab_hdr = reinterpret_cast<const SectionHeader*>(base + shstrtab_off);
const auto shstrtab = reinterpret_cast<const char*>(base + static_cast<std::size_t>(shstrtab_hdr->sh_offset));
const auto shstrtab_size = static_cast<std::size_t>(shstrtab_hdr->sh_size);
for (std::size_t i = 0; i < shnum; ++i)
{
const auto section_off = shoff + i * sizeof(SectionHeader);
const auto* section = reinterpret_cast<const SectionHeader*>(base + section_off);
if (section->sh_size == 0)
continue;
if (std::cmp_greater_equal(section->sh_name, shstrtab_size))
continue;
if (std::string_view{shstrtab + section->sh_name} != target_section_name)
continue;
const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr);
const auto* section_end = section_begin + static_cast<std::size_t>(section->sh_size);
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (scan_result == section_end)
return std::nullopt;
return reinterpret_cast<std::uintptr_t>(scan_result);
}
return std::nullopt;
}
} // namespace
namespace omath
{
std::optional<std::uintptr_t>
ElfPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
// Validate ELF signature.
constexpr std::string_view valid_elf_signature = "\x7F"
"ELF";
if (std::string_view{reinterpret_cast<const char*>(base), valid_elf_signature.size()} != valid_elf_signature)
return std::nullopt;
// Detect architecture.
const auto ei_class_value = static_cast<uint8_t>(base[ei_class]);
const auto arch = ei_class_value == elfclass64 ? FileArch::x64
: ei_class_value == elfclass32 ? FileArch::x32
: std::optional<FileArch>{};
if (!arch.has_value()) [[unlikely]]
return std::nullopt;
if (arch == FileArch::x64)
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address),
pattern, target_section_name);
if (arch == FileArch::x32)
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address),
pattern, target_section_name);
std::unreachable();
}
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
const auto pe_section = get_elf_section_by_name(path_to_file, target_section_name);
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto scan_result =
PatternScanner::scan_for_pattern(pe_section->data.cbegin(), pe_section->data.cend(), pattern);
if (scan_result == pe_section->data.cend())
return std::nullopt;
const auto offset = std::distance(pe_section->data.begin(), scan_result);
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = offset};
}
} // namespace omath

View File

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

View File

@@ -237,10 +237,10 @@ namespace
variant);
}
struct ExtractedSection
struct ExtractedSection final
{
std::uint64_t virtual_base_addr;
std::uint64_t raw_base_addr;
std::uintptr_t virtual_base_addr;
std::uintptr_t raw_base_addr;
std::vector<std::byte> data;
};
@@ -261,7 +261,7 @@ namespace
const auto nt_headers = get_nt_header_from_file(file, dos_header);
if (!nt_headers)
if (!nt_headers) [[unlikely]]
return std::nullopt;
if (invalid_nt_header_file(nt_headers.value())) [[unlikely]]
@@ -290,10 +290,11 @@ namespace
file.seekg(current_section.ptr_raw_data, std::ios::beg);
file.read(reinterpret_cast<char*>(section_data.data()),
static_cast<std::streamsize>(section_data.size()));
return ExtractedSection{.virtual_base_addr = current_section.virtual_address
+ concrete_headers.optional_header.image_base,
.raw_base_addr = current_section.ptr_raw_data,
.data = std::move(section_data)};
return ExtractedSection{
.virtual_base_addr = static_cast<std::uintptr_t>(
current_section.virtual_address + concrete_headers.optional_header.image_base),
.raw_base_addr = static_cast<std::uintptr_t>(current_section.ptr_raw_data),
.data = std::move(section_data)};
}
return std::nullopt;
},
@@ -304,46 +305,71 @@ namespace
namespace omath
{
std::optional<std::uintptr_t> PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern)
std::optional<std::uintptr_t>
PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
const auto base_address = reinterpret_cast<std::uintptr_t>(module_base_address);
const auto* base_bytes = static_cast<const std::byte*>(module_base_address);
if (!base_address)
return std::nullopt;
auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address);
const auto* dos_header = static_cast<const DosHeader*>(module_base_address);
if (!nt_header_variant)
if (invalid_dos_header_file(*dos_header)) [[unlikely]]
return std::nullopt;
const auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address);
if (!nt_header_variant) [[unlikely]]
return std::nullopt;
return std::visit(
[base_address, &pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
[base_bytes, base_address, lfanew = dos_header->e_lfanew, &target_section_name,
&pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
{
// Define .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;
constexpr std::size_t signature_size = sizeof(nt_header.signature);
const auto section_table_off = static_cast<std::size_t>(lfanew) + signature_size
+ sizeof(FileHeader) + nt_header.file_header.size_optional_header;
const auto scan_range = std::span{reinterpret_cast<std::byte*>(base_address) + start, scan_size};
const auto* section_table = reinterpret_cast<const SectionHeader*>(base_bytes + section_table_off);
// ReSharper disable once CppTooWideScopeInitStatement
const auto result = PatternScanner::scan_for_pattern(scan_range, pattern);
for (std::size_t i = 0; i < nt_header.file_header.num_sections; ++i)
{
const auto* section = section_table + i;
if (result != scan_range.end())
return reinterpret_cast<std::uintptr_t>(&*result);
if (std::string_view{section->name} != target_section_name || section->size_raw_data == 0)
continue;
const auto section_size = section->virtual_size != 0
? static_cast<std::size_t>(section->virtual_size)
: static_cast<std::size_t>(section->size_raw_data);
const auto* section_begin =
reinterpret_cast<std::byte*>(base_address + section->virtual_address);
const auto scan_range = std::span{section_begin, section_size};
const auto result =
PatternScanner::scan_for_pattern(scan_range.begin(), scan_range.end(), pattern);
if (result != scan_range.end())
return reinterpret_cast<std::uintptr_t>(&*result);
}
return std::nullopt;
},
nt_header_variant.value());
}
std::optional<PeSectionScanResult>
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
const auto pe_section = extract_section_from_pe_file(path_to_file, target_section_name);
if (!pe_section.has_value())
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto scan_result =
@@ -353,8 +379,8 @@ namespace omath
return std::nullopt;
const auto offset = std::distance(pe_section->data.begin(), scan_result);
return PeSectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = offset};
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = offset};
}
} // namespace omath

View File

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

View File

@@ -8,6 +8,125 @@
#include <print>
#include <random>
TEST(unit_test_frostbite_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_frostbite_engine, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_frostbite_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_frostbite_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_frostbite_engine, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_frostbite_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
const auto cm_f = omath::frostbite_engine::units_to_centimeters(units_f);
const auto units_f_back = omath::frostbite_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
const auto cm_d = omath::frostbite_engine::units_to_centimeters(units_d);
const auto units_d_back = omath::frostbite_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::frostbite_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::frostbite_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::frostbite_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::frostbite_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::frostbite_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::frostbite_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::frostbite_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::frostbite_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_frostbite_engine, ConversionChainConsistency)
{
const double units = 424242.42;
const auto cm_direct = omath::frostbite_engine::units_to_centimeters(units);
const auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
const auto km_direct = omath::frostbite_engine::units_to_kilometers(units);
const auto km_via_meters = omath::frostbite_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_frostbite_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::frostbite_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_frostbite_engine, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::frostbite_engine::units_to_centimeters(units);
constexpr double m = omath::frostbite_engine::units_to_meters(units);
constexpr double km = omath::frostbite_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_frostbite_engine, ForwardVector)
{
const auto forward = omath::frostbite_engine::forward_vector({});

View File

@@ -7,6 +7,125 @@
#include <omath/engines/opengl_engine/formulas.hpp>
#include <random>
TEST(unit_test_opengl, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_opengl, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_opengl, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_opengl, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_opengl, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_opengl, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_opengl, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
const auto cm_f = omath::opengl_engine::units_to_centimeters(units_f);
const auto units_f_back = omath::opengl_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
const auto cm_d = omath::opengl_engine::units_to_centimeters(units_d);
const auto units_d_back = omath::opengl_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_opengl, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
const auto m_f = omath::opengl_engine::units_to_meters(units_f);
const auto units_f_back = omath::opengl_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
const auto m_d = omath::opengl_engine::units_to_meters(units_d);
const auto units_d_back = omath::opengl_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_opengl, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
const auto km_f = omath::opengl_engine::units_to_kilometers(units_f);
const auto units_f_back = omath::opengl_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
const auto km_d = omath::opengl_engine::units_to_kilometers(units_d);
const auto units_d_back = omath::opengl_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_opengl, ConversionChainConsistency)
{
const double units = 424242.42;
const auto cm_direct = omath::opengl_engine::units_to_centimeters(units);
const auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
const auto km_direct = omath::opengl_engine::units_to_kilometers(units);
const auto km_via_meters = omath::opengl_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_opengl, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::opengl_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_opengl, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::opengl_engine::units_to_centimeters(units);
constexpr double m = omath::opengl_engine::units_to_meters(units);
constexpr double km = omath::opengl_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_opengl, ForwardVector)
{
const auto forward = omath::opengl_engine::forward_vector({});

View File

@@ -7,6 +7,129 @@
#include <omath/engines/source_engine/formulas.hpp>
#include <random>
TEST(unit_test_source_engine_units, HammerUnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(1.0f), 2.54f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(10.0f), 25.4f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(-2.0f), -5.08f);
}
TEST(unit_test_source_engine_units, HammerUnitsToMeters_BasicValues)
{
EXPECT_NEAR(omath::source_engine::units_to_meters(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(1.0), 0.0254, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(100.0), 2.54, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(-4.0), -0.1016, 1e-12);
}
TEST(unit_test_source_engine_units, HammerUnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::source_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(1.0), 0.0000254, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(1000.0), 0.0254, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(-10.0), -0.000254, 1e-15);
}
TEST(unit_test_source_engine_units, CentimetersToHammerUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::source_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(2.54f), 1.0f, 1e-6f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(25.4f), 10.0f, 1e-5f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(-5.08f), -2.0f, 1e-6f);
}
TEST(unit_test_source_engine_units, MetersToHammerUnits_BasicValues)
{
EXPECT_NEAR(omath::source_engine::meters_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::source_engine::meters_to_units(0.0254), 1.0, 1e-12);
EXPECT_NEAR(omath::source_engine::meters_to_units(2.54), 100.0, 1e-10);
EXPECT_NEAR(omath::source_engine::meters_to_units(-0.0508), -2.0, 1e-12);
}
TEST(unit_test_source_engine_units, KilometersToHammerUnits_BasicValues)
{
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.0), 0.0, 1e-9);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.0000254), 1.0, 1e-9);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.00254), 100.0, 1e-7);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(-0.0000508), -2.0, 1e-9);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToCentimetersToHammer)
{
constexpr float hu_f = 123.456f;
constexpr auto cm_f = omath::source_engine::units_to_centimeters(hu_f);
constexpr auto hu_f_back = omath::source_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-5f);
constexpr double hu_d = -98765.4321;
constexpr auto cm_d = omath::source_engine::units_to_centimeters(hu_d);
constexpr auto hu_d_back = omath::source_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-10);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToMetersToHammer)
{
constexpr float hu_f = 2500.25f;
constexpr auto m_f = omath::source_engine::units_to_meters(hu_f);
constexpr auto hu_f_back = omath::source_engine::meters_to_units(m_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-4f);
constexpr double hu_d = -42000.125;
constexpr auto m_d = omath::source_engine::units_to_meters(hu_d);
constexpr auto hu_d_back = omath::source_engine::meters_to_units(m_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-10);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToKilometersToHammer)
{
constexpr float hu_f = 100000.0f;
constexpr auto km_f = omath::source_engine::units_to_kilometers(hu_f);
constexpr auto hu_f_back = omath::source_engine::kilometers_to_units(km_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-2f); // looser due to float scaling
constexpr double hu_d = -1234567.89;
constexpr auto km_d = omath::source_engine::units_to_kilometers(hu_d);
constexpr auto hu_d_back = omath::source_engine::kilometers_to_units(km_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-7);
}
TEST(unit_test_source_engine_units, ConversionChainConsistency)
{
// hu -> cm -> m -> km should match direct helpers
constexpr auto hu = 54321.123;
constexpr auto cm = omath::source_engine::units_to_centimeters(hu);
constexpr auto m_via_cm = cm / 100.0;
constexpr auto km_via_cm = m_via_cm / 1000.0;
constexpr auto m_direct = omath::source_engine::units_to_meters(hu);
constexpr auto km_direct = omath::source_engine::units_to_kilometers(hu);
EXPECT_NEAR(m_direct, m_via_cm, 1e-12);
EXPECT_NEAR(km_direct, km_via_cm, 1e-15);
}
TEST(unit_test_source_engine_units, SupportsFloatAndDoubleTypes)
{
static_assert(std::is_same_v<decltype(omath::source_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::source_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::source_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::source_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_source_engine_units, ConstexprEvaluation)
{
constexpr double hu = 10.0;
constexpr double cm = omath::source_engine::units_to_centimeters(hu);
constexpr double m = omath::source_engine::units_to_meters(hu);
constexpr double km = omath::source_engine::units_to_kilometers(hu);
static_assert(cm == 25.4, "constexpr hu->cm failed");
static_assert(m == 0.254, "constexpr hu->m failed");
static_assert(km == 0.000254, "constexpr hu->km failed");
}
TEST(unit_test_source_engine, ForwardVector)
{
const auto forward = omath::source_engine::forward_vector({});

View File

@@ -38,14 +38,14 @@ static void expect_matrix_near(const MatT& a, const MatT& b, float eps = 1e-5f)
// Generic tests for PredEngineTrait behaviour across engines
TEST(TraitTests, Frostbite_Pred_And_Mesh_And_Camera)
{
namespace E = omath::frostbite_engine;
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);
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);
@@ -54,57 +54,57 @@ TEST(TraitTests, Frostbite_Pred_And_Mesh_And_Camera)
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);
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);
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);
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);
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 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);
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);
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});
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);
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;
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);
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);
@@ -112,50 +112,50 @@ TEST(TraitTests, IW_Pred_And_Mesh_And_Camera)
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);
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);
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);
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 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 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));
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);
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);
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;
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);
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);
@@ -163,49 +163,49 @@ TEST(TraitTests, OpenGL_Pred_And_Mesh_And_Camera)
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);
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);
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);
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 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);
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));
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);
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);
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;
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);
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);
@@ -213,49 +213,49 @@ TEST(TraitTests, Unity_Pred_And_Mesh_And_Camera)
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);
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);
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);
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 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);
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));
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);
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);
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;
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);
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);
@@ -263,35 +263,35 @@ TEST(TraitTests, Unreal_Pred_And_Mesh_And_Camera)
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);
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);
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);
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 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);
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));
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);
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);
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);
}

View File

@@ -8,6 +8,126 @@
#include <print>
#include <random>
TEST(unit_test_unity_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_unity_engine, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_unity_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_unity_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_unity_engine, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_unity_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
constexpr auto cm_f = omath::unity_engine::units_to_centimeters(units_f);
constexpr auto units_f_back = omath::unity_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
constexpr auto cm_d = omath::unity_engine::units_to_centimeters(units_d);
constexpr auto units_d_back = omath::unity_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::unity_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::unity_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::unity_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::unity_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::unity_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::unity_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::unity_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::unity_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_unity_engine, ConversionChainConsistency)
{
constexpr double units = 424242.42;
constexpr auto cm_direct = omath::unity_engine::units_to_centimeters(units);
constexpr auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
constexpr auto km_direct = omath::unity_engine::units_to_kilometers(units);
constexpr auto km_via_meters = omath::unity_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_unity_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::unity_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unity_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::unity_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unity_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_unity_engine, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::unity_engine::units_to_centimeters(units);
constexpr double m = omath::unity_engine::units_to_meters(units);
constexpr double km = omath::unity_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_unity_engine, ForwardVector)
{
const auto forward = omath::unity_engine::forward_vector({});

View File

@@ -238,4 +238,127 @@ TEST(unit_test_unreal_engine, loook_at_random_z_axis)
failed_points++;
}
EXPECT_LE(failed_points, 100);
}
TEST(unit_test_unreal_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(1.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(250.0f), 250.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(-42.5f), -42.5f);
}
TEST(unit_test_unreal_engine, UnitsToMeters_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::units_to_meters(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(1.0), 0.01, 1e-15);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(100.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(-250.0), -2.5, 1e-12);
}
TEST(unit_test_unreal_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(0.0), 0.0, 1e-18);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(1.0), 0.00001, 1e-18);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(100000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(-250000.0), -2.5, 1e-12);
}
TEST(unit_test_unreal_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(1.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(250.0f), 250.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(-42.5f), -42.5f);
}
TEST(unit_test_unreal_engine, MetersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::meters_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(0.01), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(1.0), 100.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(-2.5), -250.0, 1e-9);
}
TEST(unit_test_unreal_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(0.0), 0.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(0.00001), 1.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(1.0), 100000.0, 1e-6);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(-2.5), -250000.0, 1e-3);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
constexpr auto cm_f = omath::unreal_engine::units_to_centimeters(units_f);
constexpr auto units_f_back = omath::unreal_engine::centimeters_to_units(cm_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -987654.321;
constexpr auto cm_d = omath::unreal_engine::units_to_centimeters(units_d);
constexpr auto units_d_back = omath::unreal_engine::centimeters_to_units(cm_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::unreal_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::unreal_engine::meters_to_units(m_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::unreal_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::unreal_engine::meters_to_units(m_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::unreal_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::unreal_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::unreal_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::unreal_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_unreal_engine, ConversionChainConsistency)
{
constexpr double units = 424242.42;
constexpr auto cm_direct = omath::unreal_engine::units_to_centimeters(units);
constexpr auto cm_expected = units; // 1 uu == 1 cm
EXPECT_NEAR(cm_direct, cm_expected, 1e-12);
constexpr auto m_direct = omath::unreal_engine::units_to_meters(units);
constexpr auto m_via_cm = cm_direct / 100.0;
EXPECT_NEAR(m_direct, m_via_cm, 1e-12);
constexpr auto km_direct = omath::unreal_engine::units_to_kilometers(units);
constexpr auto km_via_m = m_direct / 1000.0;
EXPECT_NEAR(km_direct, km_via_m, 1e-15);
}
TEST(unit_test_unreal_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::unreal_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_unreal_engine, ConstexprConversions)
{
constexpr double units = 100000.0;
constexpr double cm = omath::unreal_engine::units_to_centimeters(units);
constexpr double m = omath::unreal_engine::units_to_meters(units);
constexpr double km = omath::unreal_engine::units_to_kilometers(units);
static_assert(cm == 100000.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}

View File

@@ -1,8 +1,8 @@
// 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/collision/mesh_collider.hpp>
#include <omath/collision/simplex.hpp>
#include <omath/engines/source_engine/collider.hpp>
using namespace omath;
@@ -12,7 +12,7 @@ TEST(SimplexTest, HandleEmptySimplex)
{
Simplex<Vector3<float>> simplex;
Vector3<float> direction{1, 0, 0};
EXPECT_EQ(simplex.size(), 0);
EXPECT_FALSE(simplex.handle(direction));
}
@@ -21,21 +21,21 @@ TEST(SimplexTest, HandleLineCollinearWithXAxis)
{
using Vec3 = Vector3<float>;
Simplex<Vec3> simplex;
simplex.push_front(Vec3{1, 0, 0});
simplex.push_front(Vec3{-1, 0, 0});
Vec3 direction{};
std::ignore = simplex.handle(direction);
EXPECT_NEAR(direction.x, 0.f, 1e-6f);
}
TEST(CollisionExtra, SimplexLineHandle)
{
Simplex<Vector3<float>> s;
s = { Vector3<float>{1.f,0.f,0.f}, Vector3<float>{2.f,0.f,0.f} };
Vector3<float> dir{0,0,0};
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);
@@ -44,8 +44,8 @@ TEST(CollisionExtra, SimplexLineHandle)
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};
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);
}
@@ -54,8 +54,9 @@ 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};
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);
@@ -63,44 +64,38 @@ TEST(CollisionExtra, SimplexTetrahedronInside)
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 }, {}, {} }
},
{}
};
source_engine::Mesh mesh = {
std::vector<primitives::Vertex<>>{{{1.f, 1.f, 1.f}, {}, {}}, {{-1.f, -1.f, -1.f}, {}, {}}}, {}};
mesh.set_origin({0, 2, 0});
omath::source_engine::MeshCollider collider(mesh);
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));
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});
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));
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;
source_engine::Mesh meshA = {
std::vector<primitives::Vertex<>>{{{0.f, 0.f, 0.f}, {}, {}}, {{1.f, 0.f, 0.f}, {}, {}}}, {}};
source_engine::Mesh mesh_b = meshA;
mesh_b.set_origin({0.5f, 0.f, 0.f}); // translate to overlap
omath::source_engine::MeshCollider a(meshA);
omath::source_engine::MeshCollider b(mesh_b);
source_engine::MeshCollider a(meshA);
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} };
Simplex<Vector3<float>> simplex;
simplex = {omath::Vector3<float>{0.5f, 0.f, 0.f}, omath::Vector3<float>{-0.5f, 0.f, 0.f},
omath::Vector3<float>{0.f, 0.5f, 0.f}, omath::Vector3<float>{0.f, -0.5f, 0.f}};
auto pool = std::pmr::monotonic_buffer_resource(1024);
auto res = Epa<omath::source_engine::MeshCollider>::solve(a, b, simplex, {}, pool);
auto res = Epa<source_engine::MeshCollider>::solve(a, b, simplex, {}, pool);
// EPA may or may not converge depending on numerics; ensure it returns optionally
// but if it does, fields should be finite
if (res.has_value())

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 30.12.2025.
//
// /Users/vladislav/Downloads/valencia
#include <gtest/gtest.h>
#include <omath/utility/elf_pattern_scan.hpp>
#include <print>
TEST(unit_test_elf_pattern_scan_file, ScanMissingPattern)
{
//FIXME: Implement normal tests :)
//constexpr std::string_view path = "/Users/vladislav/Downloads/crackme";
//const auto res = omath::ElfPatternScanner::scan_for_pattern_in_file(path, "F3 0F 1E FA 55 48 89 E5 B8 00 00 00 00", ".text");
//EXPECT_TRUE(res.has_value());
//std::println("In virtual mem: 0x{:x}", res->virtual_base_addr+res->target_offset);
}

View File

@@ -9,7 +9,7 @@
using Mesh = omath::source_engine::Mesh;
using Collider = omath::source_engine::MeshCollider;
using GJK = omath::collision::GjkAlgorithm<Collider>;
using Gjk = omath::collision::GjkAlgorithm<Collider>;
using EPA = omath::collision::Epa<Collider>;
TEST(UnitTestEpa, TestCollisionTrue)
@@ -37,15 +37,15 @@ TEST(UnitTestEpa, TestCollisionTrue)
Collider A(a), B(b);
// GJK
auto gjk = GJK::is_collide_with_simplex_info(A, B);
ASSERT_TRUE(gjk.hit) << "GJK should report collision";
auto [hit, simplex] = Gjk::is_collide_with_simplex_info(A, B);
ASSERT_TRUE(hit) << "GJK should report collision";
// 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, *pool);
auto epa = EPA::solve(A, B, simplex, params, *pool);
ASSERT_TRUE(epa.has_value()) << "EPA should converge";
// Normal is unit
@@ -70,8 +70,8 @@ TEST(UnitTestEpa, TestCollisionTrue)
Collider B_plus(b_plus), B_minus(b_minus);
const bool sep_plus = !GJK::is_collide_with_simplex_info(A, B_plus).hit;
const bool sep_minus = !GJK::is_collide_with_simplex_info(A, B_minus).hit;
const bool sep_plus = !Gjk::is_collide_with_simplex_info(A, B_plus).hit;
const bool sep_minus = !Gjk::is_collide_with_simplex_info(A, B_minus).hit;
// Exactly one direction should separate
EXPECT_NE(sep_plus, sep_minus) << "Exactly one of ±penetration must separate";
@@ -81,12 +81,12 @@ TEST(UnitTestEpa, TestCollisionTrue)
Mesh b_resolved = b;
b_resolved.set_origin(b_resolved.get_origin() + resolve);
EXPECT_FALSE(GJK::is_collide(A, Collider(b_resolved))) << "Resolved position should be non-colliding";
EXPECT_FALSE(Gjk::is_collide(A, Collider(b_resolved))) << "Resolved position should be non-colliding";
// Moving the other way should still collide
Mesh b_wrong = b;
b_wrong.set_origin(b_wrong.get_origin() - resolve);
EXPECT_TRUE(GJK::is_collide(A, Collider(b_wrong)));
EXPECT_TRUE(Gjk::is_collide(A, Collider(b_wrong)));
}
TEST(UnitTestEpa, TestCollisionTrue2)
{
@@ -112,7 +112,7 @@ TEST(UnitTestEpa, TestCollisionTrue2)
Collider A(a), B(b);
// --- GJK must detect collision and provide simplex ---
auto gjk = GJK::is_collide_with_simplex_info(A, B);
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;
@@ -139,11 +139,11 @@ TEST(UnitTestEpa, TestCollisionTrue2)
// Apply once: B + pen must separate; the opposite must still collide
Mesh b_resolved = b;
b_resolved.set_origin(b_resolved.get_origin() + pen * margin);
EXPECT_FALSE(GJK::is_collide(A, Collider(b_resolved))) << "Applying penetration should separate";
EXPECT_FALSE(Gjk::is_collide(A, Collider(b_resolved))) << "Applying penetration should separate";
Mesh b_wrong = b;
b_wrong.set_origin(b_wrong.get_origin() - pen * margin);
EXPECT_TRUE(GJK::is_collide(A, Collider(b_wrong))) << "Opposite direction should still intersect";
EXPECT_TRUE(Gjk::is_collide(A, Collider(b_wrong))) << "Opposite direction should still intersect";
// Some book-keeping sanity
EXPECT_GT(epa->iterations, 0);

View File

@@ -32,10 +32,8 @@ TEST(EpaInternal, SolveHandlesSmallPolytope)
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)
if (const auto result = EpaDummy::solve(a, b, s, params))
{
EXPECT_TRUE(std::isfinite(result->depth));
EXPECT_TRUE(std::isfinite(result->normal.x));

View File

@@ -10,7 +10,8 @@ struct DegenerateCollider
{
using VectorType = Vector3f;
// returns furthest point along dir
VectorType find_abs_furthest_vertex_position(const VectorType& dir) const noexcept
[[nodiscard]]
static VectorType find_abs_furthest_vertex_position(const VectorType& dir) 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};

View File

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

View File

@@ -15,9 +15,9 @@ TEST(LineTracerTests, ParallelRayReturnsEnd)
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);
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));
EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, tri));
}
TEST(LineTracerTests, MissesTriangleReturnsEnd)
@@ -27,7 +27,7 @@ TEST(LineTracerTests, MissesTriangleReturnsEnd)
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);
const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end);
}
@@ -38,7 +38,7 @@ TEST(LineTracerTests, HitTriangleReturnsPointInsideSegment)
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);
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);
@@ -60,6 +60,6 @@ TEST(LineTracerTests, InfiniteLengthEarlyOut)
// 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);
const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end);
}

View File

@@ -10,7 +10,7 @@ TEST(LineTracerExtra, MissParallel)
{
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false }; // parallel above triangle
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -18,7 +18,7 @@ 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);
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);
@@ -29,9 +29,8 @@ 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)
if (const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); hit != ray.end)
{
EXPECT_NEAR(hit.x, 0.0f, 1e-6f);
EXPECT_NEAR(hit.y, 0.0f, 1e-6f);
@@ -43,6 +42,6 @@ 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);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}

View File

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

View File

@@ -14,7 +14,7 @@ TEST(LineTracerMore2, UGreaterThanOneReturnsEnd)
// 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);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -24,7 +24,7 @@ TEST(LineTracerMore2, VGreaterThanOneReturnsEnd)
// 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);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -34,7 +34,7 @@ TEST(LineTracerMore2, UPlusVGreaterThanOneReturnsEnd)
// 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);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -52,6 +52,6 @@ TEST(LineTracerMore2, ZeroLengthRayHandled)
Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,0.f};
// Zero-length ray: direction length == 0; algorithm should handle without crash
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}

View File

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

View File

@@ -11,32 +11,60 @@ 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)
{
// Constants
constexpr std::uint32_t e_lfanew = 0x80;
const std::uint32_t total_size = e_lfanew + 0x200 + size_code + 0x100;
constexpr std::uint32_t nt_sig = 0x4550; // "PE\0\0"
constexpr std::uint16_t opt_magic = 0x020B; // PE32+
constexpr std::uint16_t num_sections = 1;
constexpr std::uint16_t opt_hdr_size = 0xF0; // Standard PE32+ optional header size
constexpr std::uint32_t section_table_off = e_lfanew + 4 + 20 + opt_hdr_size; // sig(4) + FileHdr(20)
constexpr std::uint32_t section_header_size = 40;
constexpr std::uint32_t text_characteristics = 0x60000020; // code | execute | read
const std::uint32_t headers_end = section_table_off + section_header_size;
const std::uint32_t code_end = base_of_code + size_code;
const std::uint32_t total_size = std::max(headers_end, code_end) + 0x100; // leave some padding
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));
auto w16 = [&](std::size_t off, std::uint16_t v) { std::memcpy(buf.data() + off, &v, sizeof(v)); };
auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, sizeof(v)); };
auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, sizeof(v)); };
// 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));
// DOS header
w16(0x00, 0x5A4D); // e_magic "MZ"
w32(0x3C, e_lfanew); // e_lfanew
// FileHeader is 20 bytes: we only need to ensure its size is present; leave zeros
// NT signature
w32(e_lfanew, nt_sig);
// 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));
// FileHeader (starts at e_lfanew + 4)
const std::size_t fh_off = e_lfanew + 4;
w16(fh_off + 2, num_sections); // NumberOfSections
w16(fh_off + 16, opt_hdr_size); // SizeOfOptionalHeader
// size_code is at offset 4 inside OptionalHeader -> absolute e_lfanew + 28
std::memcpy(buf.data() + e_lfanew + 28, &size_code, sizeof(size_code));
// OptionalHeader PE32+ (starts at e_lfanew + 4 + 20)
const std::size_t opt_off = fh_off + 20;
w16(opt_off + 0, opt_magic); // Magic
w32(opt_off + 4, size_code); // SizeOfCode
w32(opt_off + 16, 0); // AddressOfEntryPoint (unused in test)
w32(opt_off + 20, base_of_code); // BaseOfCode
w64(opt_off + 24, 0); // ImageBase
w32(opt_off + 32, 0x1000); // SectionAlignment
w32(opt_off + 36, 0x200); // FileAlignment
w32(opt_off + 56, code_end); // SizeOfImage (simple upper bound)
w32(opt_off + 60, headers_end); // SizeOfHeaders
w32(opt_off + 108, 0); // NumberOfRvaAndSizes (0 directories)
// 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));
// Section header (.text) at section_table_off
const std::size_t sh_off = section_table_off;
std::memcpy(buf.data() + sh_off + 0, ".text", 5); // Name[8]
w32(sh_off + 8, size_code); // VirtualSize
w32(sh_off + 12, base_of_code); // VirtualAddress
w32(sh_off + 16, size_code); // SizeOfRawData
w32(sh_off + 20, base_of_code); // PointerToRawData
w32(sh_off + 36, text_characteristics); // Characteristics
// place code bytes at offset base_of_code
// Place code bytes at BaseOfCode
if (base_of_code + code_bytes.size() <= buf.size())
std::memcpy(buf.data() + base_of_code, code_bytes.data(), code_bytes.size());
@@ -59,11 +87,12 @@ TEST(PePatternScanLoaded, FindsPatternAtBase)
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);
constexpr std::uint32_t base_of_code = 0x300;
auto buf = make_fake_module(base_of_code, static_cast<std::uint32_t>(code.size()), code);
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE ?? BE");
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE ?? BE", ".text");
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);
}
EXPECT_EQ(addr - base, base_of_code);
}

View File

@@ -163,6 +163,23 @@ TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds)
std::uint32_t size_headers; /* keep space */
std::uint8_t pad[200];
};
struct SectionHeader
{
char name[8];
union
{
std::uint32_t physical_address;
std::uint32_t virtual_size;
};
std::uint32_t virtual_address;
std::uint32_t size_raw_data;
std::uint32_t ptr_raw_data;
std::uint32_t ptr_relocs;
std::uint32_t ptr_line_numbers;
std::uint32_t num_relocs;
std::uint32_t num_line_numbers;
std::uint32_t characteristics;
};
struct ImageNtHeadersX64
{
std::uint32_t signature;
@@ -176,22 +193,41 @@ TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds)
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->file_header.size_optional_header = static_cast<std::uint16_t>(sizeof(OptionalHeaderX64));
nt->optional_header.magic = 0x020B; // x64
nt->optional_header.base_of_code = base_of_code;
nt->optional_header.size_code = size_code;
// Compute section table offset: e_lfanew + 4 (sig) + FileHeader + OptionalHeader
const std::size_t section_table_off =
static_cast<std::size_t>(dos->e_lfanew) + 4 + sizeof(FileHeader) + sizeof(OptionalHeaderX64);
nt->optional_header.size_headers = static_cast<std::uint32_t>(section_table_off + sizeof(SectionHeader));
// Section header (.text)
const auto sect = reinterpret_cast<SectionHeader*>(buf.data() + section_table_off);
std::memset(sect, 0, sizeof(SectionHeader));
std::memcpy(sect->name, ".text", 5);
sect->virtual_size = size_code;
sect->virtual_address = base_of_code;
sect->size_raw_data = size_code;
sect->ptr_raw_data = base_of_code;
sect->characteristics = 0x60000020; // code | execute | read
// 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");
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD BE EF", ".text");
EXPECT_TRUE(res.has_value());
}

View File

@@ -0,0 +1,16 @@
//
// Created by Vladislav on 11.01.2026.
//
#include "omath/3d_primitives/box.hpp"
#include "omath/collision/line_tracer.hpp"
#include "omath/engines/opengl_engine/primitives.hpp"
#include <gtest/gtest.h>
TEST(test, test)
{
auto result = omath::primitives::create_box<omath::opengl_engine::BoxMesh>(
{0.f, 30.f, 0.f}, {}, omath::opengl_engine::k_abs_forward, omath::opengl_engine::k_abs_right);
omath::collision::Ray ray{.start = {0, 0, 0}, .end = {-100, 0, 0}};
std::ignore = omath::collision::LineTracer<>::get_ray_hit_point(ray, result);
}

View File

@@ -8,39 +8,51 @@
class Player final
{
public:
virtual int foo() {return 1;}
virtual int bar() {return 2;}
[[nodiscard]] virtual int foo() const
{
return 1;
}
[[nodiscard]] virtual int bar() const
{
return 2;
}
omath::Vector3<float> m_origin{1.f, 2.f, 3.f};
int m_health{123};
};
class RevPlayer : omath::rev_eng::InternalReverseEngineeredObject
class RevPlayer final : omath::rev_eng::InternalReverseEngineeredObject
{
public:
omath::Vector3<float> get_origin()
[[nodiscard]]
omath::Vector3<float> get_origin() const
{
return get_by_offset<omath::Vector3<float>>(sizeof(std::uintptr_t));
}
int get_health()
[[nodiscard]]
int get_health() const
{
return get_by_offset<int>(sizeof(std::uintptr_t)+sizeof(omath::Vector3<float>));
return get_by_offset<int>(sizeof(std::uintptr_t) + sizeof(omath::Vector3<float>));
}
int rev_foo()
[[nodiscard]]
int rev_foo() const
{
return call_virtual_method<0, int>();
}
int rev_bar()
[[nodiscard]]
int rev_bar() const
{
return call_virtual_method<1, int>();
}
[[nodiscard]] int rev_bar_const() const
{
return call_virtual_method<1, int>();
}
};
TEST(unit_test_reverse_enineering, read_test)
{
Player player_original;

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 04.01.2026.
//
#include "omath/linear_algebra/vector3.hpp"
#include <gtest/gtest.h>
#include <omath/containers/encrypted_variable.hpp>
TEST(Enc, Test)
{
constexpr omath::Vector3<float> original = {1.f, 2.f, 3.f};
OMATH_DEF_CRYPT_VAR(omath::Vector3<float>, 128) var{original};
{
omath::VarAnchor _ = var.drop_anchor();
EXPECT_EQ(original, var.value());
}
EXPECT_NE(original, var.value());
}

8
tests/main.cpp Normal file
View File

@@ -0,0 +1,8 @@
#include <gtest/gtest.h>
int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

113
xmake.lua Normal file
View File

@@ -0,0 +1,113 @@
add_rules("mode.debug", "mode.release")
add_rules("utils.install.cmake_importfiles")
add_rules("utils.install.pkgconfig_importfiles")
set_version("4.6.1", {soname = "4.6"})
set_languages("cxx23")
set_warnings("all")
option("avx2")
set_default(true)
set_showmenu(true)
after_check(function (option)
import("core.base.cpu")
option:enable(cpu.has_feature("avx2"))
end)
option_end()
option("imgui")
set_default(true)
set_showmenu(true)
option_end()
option("examples")
set_default(true)
set_showmenu(true)
option_end()
option("tests")
set_default(true)
set_showmenu(true)
option_end()
if has_config("avx2") then
add_defines("OMATH_ENABLE_AVX2")
add_vectorexts("avx2")
end
if has_config("imgui") then
add_defines("OMATH_IMGUI_INTEGRATION")
add_requires("imgui")
add_packages("imgui")
end
if has_config("examples") then
add_requires("glew", "glfw")
end
if has_config("tests") then
add_requires("gtest")
end
target("omath")
set_kind("static")
add_files("source/**.cpp")
add_includedirs("include", {public = true})
add_headerfiles("include/(**.hpp)", {prefixdir = "omath"})
on_config(function (target)
if has_config("avx2") then
cprint("${green} ✔️ AVX2 supported")
else
cprint("${red} ❌ AVX2 not supported")
end
end)
target("example_projection_matrix_builder")
set_languages("cxx26")
set_kind("binary")
add_files("examples/example_proj_mat_builder.cpp")
add_deps("omath")
set_enabled(has_config("examples"))
target("example_signature_scan")
set_languages("cxx26")
set_kind("binary")
add_files("examples/example_signature_scan.cpp")
add_deps("omath")
set_enabled(has_config("examples"))
target("example_glfw3")
set_languages("cxx26")
set_kind("binary")
add_files("examples/example_glfw3.cpp")
add_deps("omath")
add_packages("glew", "glfw")
set_enabled(has_config("examples"))
for _, file in ipairs(os.files("tests/**.cpp")) do
local name = path.basename(file)
target(name)
set_languages("cxx23")
set_kind("binary")
add_files(file, "tests/main.cpp")
add_deps("omath")
add_packages("gtest")
add_defines("OMATH_BUILD_TESTS")
add_tests("default")
set_default(false)
set_enabled(has_config("tests"))
end
task("check")
on_run(function ()
import("core.project.task")
for _, file in ipairs(os.files("tests/**.cpp")) do
local name = path.basename(file)
task.run("run", {target = name})
end
end)
set_menu {
usage = "xmake check", description = "Run tests !", options = {}
}