Compare commits

..

56 Commits

Author SHA1 Message Date
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
fdb2ad099a umped version 2025-12-27 19:43:56 +03:00
88ce5e6b8c warning fix 2025-12-25 02:35:50 +03:00
54 changed files with 2096 additions and 595 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.4.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

@@ -33,5 +33,25 @@ namespace omath::collision
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
[[nodiscard]]
static Vector3<float> get_ray_hit_point(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept;
template<class MeshType>
[[nodiscard]]
static Vector3<float> get_ray_hit_point(const Ray& ray, const MeshType& mesh) noexcept
{
Vector3<float> mesh_hit = ray.end;
auto begin = mesh.m_element_buffer_object.cbegin();
auto end = mesh.m_element_buffer_object.cend();
for (auto current = begin; current < end; current = std::next(current))
{
auto face = mesh.make_face_in_world_space(current);
auto ray_stop_point = get_ray_hit_point(ray, face);
if (ray_stop_point.distance_to(ray.start) < mesh_hit.distance_to(ray.start))
mesh_hit = ray_stop_point;
}
return mesh_hit;
}
};
} // namespace omath::collision

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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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{};
simplex.handle(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

@@ -49,6 +49,13 @@ namespace
{
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,

View File

@@ -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);

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 = {}
}