Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9efabd8fb2 | |||
| 8f225c5f8e | |||
| 44682b6f2c | |||
| a6cf043d79 | |||
| 3968d13a19 | |||
| a3f45628aa | |||
| 6da44a5a51 | |||
| e5d8e1c953 | |||
| e2378bfa8b | |||
| ca3dab855b | |||
| 79482d56d1 | |||
| c4024663bb | |||
| b2a512eafe | |||
| 7b0e2127dc | |||
| 0b663b73d5 | |||
| afc0720f08 | |||
| 015fc9b1e7 | |||
| 62d1a615ae | |||
| 043b5c588d | |||
| cd18b088cb | |||
| ebfdd0b70e | |||
| e7b82f441c | |||
| 6e59957247 | |||
| 1e540862a0 | |||
| 31cc1116ae | |||
| 338246a618 | |||
| 10ebf6ed04 | |||
| ec9a15927a | |||
| 1a0e55b4cf | |||
| b48160e1b7 | |||
| d4a16a94e6 | |||
| 07c0eebf21 | |||
| 9ed18f27c3 | |||
|
|
86b1e8a00d | ||
| db9200d7b5 | |||
| d894b62773 | |||
| ade281cdd2 | |||
| 9bb94ee33b | |||
| 754b370d8b | |||
| d3e379e71b | |||
| 6ed40a7bc1 | |||
|
|
f3d5f84d2c | ||
|
|
0b89c1d36d | ||
|
|
b8ed0bd5a5 | ||
| b9ae356e57 | |||
|
|
e351b64355 | ||
|
|
f4df88bb7a | ||
| 8142d800c7 | |||
| 95c0873b8c | |||
| d12a2611b8 | |||
| 9212b1f51f | |||
| 56505cf3e1 | |||
| 0510dd8328 | |||
| ce9758c86b | |||
| 7d194a5c36 | |||
| eec34c1efb | |||
| 765d5e7216 | |||
| 7cb8a4de52 | |||
| d2fbc286b1 | |||
| 64385757af | |||
| ea7b812645 | |||
| b214cdf3a1 | |||
| dccc12ee30 | |||
| 60e744978c | |||
| 495a2a80e4 | |||
| 6fbc010fa1 | |||
| c8f77de4f2 | |||
| 5c2d09b175 | |||
| b3646ab708 | |||
| d751eaf132 | |||
| 62e7ccb054 | |||
| 4d9e055bb3 | |||
| 9be7ca886d | |||
| 30568f3633 | |||
| 5f22499b66 | |||
| f75afb8354 | |||
| e38df8bd23 | |||
| ae347c8509 | |||
| ee40979a2d | |||
| 69d0b7b829 | |||
| 490ccfe983 | |||
| 5630c2708e | |||
| ed7220dc9c | |||
| 5e4fae9e42 | |||
| 063f43e74c | |||
| 171b4ca3da | |||
| 878e3358c0 | |||
| 81aef68143 | |||
| e2a37f2c81 | |||
| 69575c1cb3 | |||
| 8adf5db409 | |||
| 2c7e443f52 | |||
| 6ad4ef29e9 | |||
| 8feb805067 | |||
| 816da38987 | |||
| 0ba795c767 | |||
| 88b709f531 | |||
| a7eacb529e | |||
| 5a4026a4fc | |||
| 404c7de594 | |||
| f26f48cc53 | |||
| ed8afb02a1 | |||
| d86695fad7 | |||
| 570c035f27 | |||
| 5657282577 | |||
| 551ac62075 | |||
| 1b1be48ee6 | |||
|
|
1f9ea136b0 | ||
| 8dadb22e75 | |||
| 8dd9860aa1 | |||
| 9a0470f781 | |||
| eb8688c90c | |||
| 9f2f619a21 | |||
| 568883ff1c | |||
| 97003b953a | |||
| 5646654317 | |||
| ee54e3de34 | |||
| 7b0af9cf66 | |||
| 563ae0a656 | |||
| b9e2307d7a | |||
| cd0a864e7c | |||
| 5c30f57c4c | |||
| 3dcd033616 | |||
| f10dc60772 | |||
| 37f624956b | |||
| 20dc4e6730 | |||
| f6c607d84c | |||
| 81ed5f80d7 | |||
|
|
e385686323 | ||
| 086eda4ee3 | |||
| 9419e390da | |||
| d919600ac2 | |||
| 11681ac601 | |||
| 6c5152297a | |||
| 547f64e4c4 | |||
| 3129e32f0c | |||
| 72b54e9673 | |||
| 2ff291b255 | |||
| 8eda1ce4bc | |||
| e1b4375621 | |||
| d84259fdcc | |||
| d64c7ad756 | |||
| 710f91999f | |||
| f3fe0e3cee | |||
| c5c5c2e972 | |||
| a7cb056ce7 | |||
| dae1555ec4 | |||
| 2c6ef1b8cc | |||
| d70e8853e8 | |||
| e35d64b486 | |||
| 9868c832a1 | |||
| 42e46f9921 | |||
| e7ccc2ead0 | |||
| 170f969697 | |||
| 131fd891b0 | |||
| a5396b72cb | |||
| ec876ea292 | |||
| 8021050fad | |||
| bd585b5412 | |||
| 06f9a8c9ee | |||
| 160b51da94 | |||
| 064edf9ef1 | |||
| f17d36dcd3 | |||
| ec4f9f6c90 | |||
| cbee8c2c95 | |||
| dc6edbb67f | |||
| 74381eda5c | |||
| 1ef7833bd9 | |||
| c10386f3f6 | |||
| 413cef4f23 | |||
| 867cee41c3 | |||
| 990fe78a33 | |||
| 1efdb50f73 | |||
|
|
86fea065e7 | ||
|
|
ac046aae6c | ||
|
|
9646054877 | ||
| 6334002639 | |||
| 72e0c0c90b | |||
| 2f2a5a00c4 | |||
| c394993418 | |||
| 476048583d | |||
| 76ae67c968 | |||
| d1d95d2f36 | |||
| 6409568334 | |||
| 83ba339eb5 | |||
| c49c93d542 | |||
| 92dab52d66 | |||
| dac3d8e43f | |||
| fb0b05d7ef | |||
| 993212e7ab | |||
| 380a9f0a16 | |||
| ba7bce5502 | |||
| ce40891e37 | |||
| c4d10f8872 | |||
| 2bb0c82c30 | |||
| a4dcf8dc3b | |||
| 17eaddbc8c | |||
| 9005c02499 | |||
| e7380b5eb6 | |||
| f8d6e4b544 | |||
| 893eca296b | |||
| b0bd58ccb2 | |||
| 44a42d39d0 | |||
| a8c7e8eb54 | |||
| cef22919a4 | |||
| f79350e609 | |||
| 1102cad390 | |||
| df4e999c82 | |||
| 3baeb182f0 | |||
| 5539bbad0d | |||
| 4d4dfae8f2 | |||
| 03c514104e | |||
| 792db7a673 | |||
| bfd399e631 | |||
| 7fef690f5e | |||
| 3df7d65ac1 | |||
| e1a1164136 | |||
| 23216279dc | |||
| 9e082f7dfa | |||
| 7750819e83 | |||
| 2ec0e2f93f | |||
| 9170ffb1a9 | |||
| e05f9ef5a9 | |||
| 89bb4aa625 | |||
|
|
9b0845593d | ||
| 617ded2dd4 | |||
| e882a224d2 | |||
| e04f6573c0 | |||
| 791e3b2313 | |||
| 26b56d757c | |||
| fbb77b9925 | |||
| 7b671dbd90 | |||
| 5875930f1a | |||
| d773985822 | |||
| a2de6f8fae | |||
| d71795006d | |||
| 561438d45c | |||
| 874b028e86 | |||
| 68ec42d9ed | |||
| 8aeb4667d7 | |||
| 565464f0cd | |||
| 04b50d4545 | |||
| e01d32fb22 | |||
| a3a023a664 | |||
| 1b5a7ed4fd | |||
|
|
362b818a71 |
@@ -56,7 +56,7 @@ SpaceBeforeParensOptions:
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
|
||||
BIN
.github/images/banner.png
vendored
|
Before Width: | Height: | Size: 137 KiB |
61
.github/workflows/cmake-multi-platform.yml
vendored
@@ -19,27 +19,32 @@ jobs:
|
||||
name: Arch Linux (Clang)
|
||||
runs-on: ubuntu-latest
|
||||
container: archlinux:latest
|
||||
|
||||
env:
|
||||
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
|
||||
steps:
|
||||
- name: Install basic tool-chain with pacman
|
||||
shell: bash
|
||||
run: |
|
||||
pacman -Sy --noconfirm archlinux-keyring
|
||||
pacman -Syu --noconfirm --needed \
|
||||
git base-devel clang cmake ninja
|
||||
git base-devel clang cmake ninja zip unzip fmt
|
||||
|
||||
- name: Checkout repository (with sub-modules)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up vcpkg
|
||||
shell: bash
|
||||
run: |
|
||||
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
|
||||
- name: Configure (cmake --preset)
|
||||
shell: bash
|
||||
run: cmake --preset linux-release -DOMATH_BUILD_TESTS=ON
|
||||
run: cmake --preset linux-release-vcpkg -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cmake --build cmake-build/build/linux-release --target all
|
||||
run: cmake --build cmake-build/build/linux-release-vcpkg --target unit_tests omath
|
||||
|
||||
- name: Run unit_tests
|
||||
shell: bash
|
||||
@@ -47,12 +52,14 @@ jobs:
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
# 2) Windows – MSVC / Ninja
|
||||
##############################################################################
|
||||
##############################################################################
|
||||
# 2) Windows – MSVC / Ninja
|
||||
##############################################################################
|
||||
windows-build-and-test:
|
||||
name: Windows (MSVC)
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
OMATH_BUILD_VIA_VCPKG: ON
|
||||
|
||||
steps:
|
||||
- name: Checkout repository (with sub-modules)
|
||||
@@ -68,12 +75,48 @@ jobs:
|
||||
|
||||
- name: Configure (cmake --preset)
|
||||
shell: bash
|
||||
run: cmake --preset windows-release -DOMATH_BUILD_TESTS=ON
|
||||
run: cmake --preset windows-release-vcpkg -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cmake --build cmake-build/build/windows-release --target all
|
||||
run: cmake --build cmake-build/build/windows-release-vcpkg --target unit_tests omath
|
||||
|
||||
- name: Run unit_tests.exe
|
||||
shell: bash
|
||||
run: ./out/Release/unit_tests.exe
|
||||
|
||||
##############################################################################
|
||||
# 3) macOS – AppleClang / Ninja
|
||||
##############################################################################
|
||||
macosx-build-and-test:
|
||||
name: macOS (AppleClang)
|
||||
runs-on: macOS-latest
|
||||
env:
|
||||
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
|
||||
steps:
|
||||
- name: Install basic tool-chain with Homebrew
|
||||
shell: bash
|
||||
run: |
|
||||
brew install cmake ninja
|
||||
|
||||
- name: Checkout repository (with sub-modules)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up vcpkg
|
||||
shell: bash
|
||||
run: |
|
||||
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
|
||||
|
||||
- name: Configure (cmake --preset)
|
||||
shell: bash
|
||||
run: cmake --preset darwin-release-vcpkg -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cmake --build cmake-build/build/darwin-release-vcpkg --target unit_tests omath
|
||||
|
||||
- name: Run unit_tests
|
||||
shell: bash
|
||||
run: ./out/Release/unit_tests
|
||||
|
||||
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "extlibs/googletest"]
|
||||
path = extlibs/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
1
.idea/vcs.xml
generated
@@ -2,6 +2,5 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/extlibs/googletest" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,42 +1,75 @@
|
||||
cmake_minimum_required(VERSION 3.26)
|
||||
|
||||
project(omath VERSION 3.5.0 LANGUAGES CXX)
|
||||
file(READ VERSION OMATH_VERSION)
|
||||
project(omath VERSION ${OMATH_VERSION} LANGUAGES CXX)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
if (MSVC)
|
||||
check_cxx_compiler_flag("/arch:AVX2" COMPILER_SUPPORTS_AVX2)
|
||||
else ()
|
||||
check_cxx_compiler_flag("-mavx2" COMPILER_SUPPORTS_AVX2)
|
||||
endif ()
|
||||
|
||||
option(OMATH_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL})
|
||||
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_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF)
|
||||
option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON)
|
||||
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" OFF)
|
||||
option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" ON)
|
||||
|
||||
message(STATUS "[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}")
|
||||
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}]: As dynamic library ${OMATH_BUILD_AS_SHARED_LIBRARY}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Static C++ runtime ${OMATH_STATIC_MSVC_RUNTIME_LIBRARY}")
|
||||
message(STATUS "[${PROJECT_NAME}]: CMake unity build ${OMATH_USE_UNITY_BUILD}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Example projects ${OMATH_BUILD_EXAMPLES}")
|
||||
message(STATUS "[${PROJECT_NAME}]: AVX2 feature status ${OMATH_USE_AVX2}")
|
||||
message(STATUS "[${PROJECT_NAME}]: ImGUI integration feature status ${OMATH_IMGUI_INTEGRATION}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Legacy features support ${OMATH_ENABLE_LEGACY}")
|
||||
|
||||
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")
|
||||
set(OMATH_USE_AVX2 ${COMPILER_SUPPORTS_AVX2})
|
||||
elseif (omath_feature STREQUAL "tests")
|
||||
set(OMATH_BUILD_TESTS ON)
|
||||
elseif (omath_feature STREQUAL "benchmark")
|
||||
set(OMATH_BUILD_BENCHMARK ON)
|
||||
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.")
|
||||
set(OMATH_USE_AVX2 OFF CACHE BOOL "Omath will use AVX2 to boost performance" FORCE)
|
||||
endif ()
|
||||
|
||||
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}")
|
||||
message(STATUS "[${PROJECT_NAME}]: As dynamic library ${OMATH_BUILD_AS_SHARED_LIBRARY}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Static C++ runtime ${OMATH_STATIC_MSVC_RUNTIME_LIBRARY}")
|
||||
message(STATUS "[${PROJECT_NAME}]: CMake unity build ${OMATH_USE_UNITY_BUILD}")
|
||||
message(STATUS "[${PROJECT_NAME}]: Example projects ${OMATH_BUILD_EXAMPLES}")
|
||||
message(STATUS "[${PROJECT_NAME}]: AVX2 feature status ${OMATH_USE_AVX2}")
|
||||
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 ()
|
||||
|
||||
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)
|
||||
add_library(${PROJECT_NAME} SHARED ${OMATH_SOURCES} ${OMATH_HEADERS})
|
||||
else ()
|
||||
add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS})
|
||||
endif ()
|
||||
|
||||
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}")
|
||||
@@ -69,7 +102,7 @@ if (OMATH_SUPRESS_SAFETY_CHECKS)
|
||||
endif ()
|
||||
|
||||
if (OMATH_ENABLE_LEGACY)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY)
|
||||
endif ()
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
@@ -90,19 +123,27 @@ if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY)
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -mavx2 -mfma)
|
||||
if (OMATH_USE_AVX2)
|
||||
if (MSVC)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC /arch:AVX2)
|
||||
elseif (EMSCRIPTEN)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -msimd128 -mavx2)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -mfma -mavx2)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
|
||||
|
||||
|
||||
if (OMATH_BUILD_TESTS)
|
||||
add_subdirectory(extlibs)
|
||||
add_subdirectory(tests)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS)
|
||||
endif ()
|
||||
|
||||
if (OMATH_BUILD_BENCHMARK)
|
||||
add_subdirectory(benchmark)
|
||||
endif ()
|
||||
|
||||
if (OMATH_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif ()
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
|
||||
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "cl.exe",
|
||||
"CMAKE_CXX_COMPILER": "cl.exe"
|
||||
"CMAKE_CXX_COMPILER": "cl.exe",
|
||||
"CMAKE_MAKE_PROGRAM": "Ninja"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
@@ -17,6 +17,17 @@
|
||||
"rhs": "Windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-base-vcpkg",
|
||||
"hidden": true,
|
||||
"inherits": "windows-base",
|
||||
"cacheVariables": {
|
||||
"OMATH_BUILD_VIA_VCPKG": "ON",
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"VCPKG_INSTALLED_DIR": "${sourceDir}/cmake-build/vcpkg_installed",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-debug",
|
||||
"displayName": "Debug",
|
||||
@@ -25,6 +36,23 @@
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-debug-vcpkg",
|
||||
"displayName": "Debug",
|
||||
"inherits": "windows-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-release-vcpkg",
|
||||
"displayName": "Release",
|
||||
"inherits": "windows-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"OMATH_BUILD_VIA_VCPKG": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-release",
|
||||
"displayName": "Release",
|
||||
@@ -40,8 +68,8 @@
|
||||
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
|
||||
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++"
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_MAKE_PROGRAM": "ninja"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
@@ -49,6 +77,17 @@
|
||||
"rhs": "Linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-base-vcpkg",
|
||||
"hidden": true,
|
||||
"inherits": "linux-base",
|
||||
"cacheVariables": {
|
||||
"OMATH_BUILD_VIA_VCPKG": "ON",
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"VCPKG_INSTALLED_DIR": "${sourceDir}/cmake-build/vcpkg_installed",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-debug",
|
||||
"displayName": "Linux Debug",
|
||||
@@ -57,6 +96,14 @@
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-debug-vcpkg",
|
||||
"displayName": "Linux Debug",
|
||||
"inherits": "linux-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-release",
|
||||
"displayName": "Linux Release",
|
||||
@@ -65,6 +112,14 @@
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-release-vcpkg",
|
||||
"displayName": "Linux Release",
|
||||
"inherits": "linux-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-base",
|
||||
"hidden": true,
|
||||
@@ -72,8 +127,8 @@
|
||||
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
|
||||
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++"
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_MAKE_PROGRAM": "ninja"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
@@ -81,6 +136,17 @@
|
||||
"rhs": "Darwin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-base-vcpkg",
|
||||
"hidden": true,
|
||||
"inherits": "darwin-base",
|
||||
"cacheVariables": {
|
||||
"OMATH_BUILD_VIA_VCPKG": "ON",
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"VCPKG_INSTALLED_DIR": "${sourceDir}/cmake-build/vcpkg_installed",
|
||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-debug",
|
||||
"displayName": "Darwin Debug",
|
||||
@@ -89,10 +155,26 @@
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-debug-vcpkg",
|
||||
"displayName": "Darwin Debug",
|
||||
"inherits": "darwin-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-release",
|
||||
"displayName": "Darwin Release",
|
||||
"inherits": "darwin-debug",
|
||||
"inherits": "darwin-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "darwin-release-vcpkg",
|
||||
"displayName": "Darwin Release",
|
||||
"inherits": "darwin-base-vcpkg",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ In order to send code back to the official OMath repository, you must first crea
|
||||
account ([fork](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)) and
|
||||
then [create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) back to OMath.
|
||||
|
||||
OMath developement is performed on multiple branches. Changes are then pull requested into master. By default, changes
|
||||
OMath development is performed on multiple branches. Changes are then pull requested into master. By default, changes
|
||||
merged into master will not roll out to stable build users unless the `stable` tag is updated.
|
||||
|
||||
### 📜 Code-Style
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Thanks to everyone who made this possible, including:
|
||||
|
||||
- Saikari aka luadebug for VCPKG port and awesome new initial logo design.
|
||||
- AmbushedRaccoon for telegram post about omath to boost repository activity.
|
||||
- Billy O'Neal aka BillyONeal for fixing compilation issues due to C math library compatibility.
|
||||
|
||||
And a big hand to everyone else who has contributed over the past!
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ target("...")
|
||||
cmake --preset windows-release -S .
|
||||
cmake --build cmake-build/build/windows-release --target omath -j 6
|
||||
```
|
||||
Use **\<platform\>-\<build configuration\>** preset to build siutable version for yourself. Like **windows-release** or **linux-release**.
|
||||
Use **\<platform\>-\<build configuration\>** preset to build suitable version for yourself. Like **windows-release** or **linux-release**.
|
||||
|
||||
| Platform Name | Build Config |
|
||||
|---------------|---------------|
|
||||
|
||||
39
LICENSE
@@ -1,7 +1,7 @@
|
||||
Copyright (C) 2024-2025 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
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
@@ -16,13 +16,44 @@ freely, subject to the following restrictions:
|
||||
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, present, or future)
|
||||
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
|
||||
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.
|
||||
|
||||
128
README.md
@@ -1,6 +1,6 @@
|
||||
<div align = center>
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
@@ -20,9 +20,11 @@ It provides the latest features, is highly customizable, has all for cheat devel
|
||||
|
||||
---
|
||||
|
||||
**[<kbd> <br> Install <br> </kbd>][INSTALL]**
|
||||
**[<kbd> <br> Examples <br> </kbd>][EXAMPLES]**
|
||||
**[<kbd> <br> Contribute <br> </kbd>][CONTRIBUTING]**
|
||||
**[<kbd> <br> Install <br> </kbd>][INSTALL]**
|
||||
**[<kbd> <br> Examples <br> </kbd>][EXAMPLES]**
|
||||
**[<kbd> <br> Documentation <br> </kbd>][DOCUMENTATION]**
|
||||
**[<kbd> <br> Contribute <br> </kbd>][CONTRIBUTING]**
|
||||
**[<kbd> <br> Donate <br> </kbd>][SPONSOR]**
|
||||
|
||||
---
|
||||
|
||||
@@ -41,21 +43,52 @@ It provides the latest features, is highly customizable, has all for cheat devel
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## 👁🗨 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!
|
||||
## 🚀 Quick Example
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
// 3D vector operations
|
||||
Vector3<float> a{1, 2, 3};
|
||||
Vector3<float> b{4, 5, 6};
|
||||
|
||||
auto dot = a.dot(b); // 32.0
|
||||
auto cross = a.cross(b); // (-3, 6, -3)
|
||||
auto distance = a.distance_to(b); // ~5.196
|
||||
auto normalized = a.normalized(); // Unit vector
|
||||
|
||||
// World-to-screen projection (Source Engine example)
|
||||
using namespace omath::source_engine;
|
||||
Camera camera(position, angles, viewport, fov, near_plane, far_plane);
|
||||
|
||||
if (auto screen = camera.world_to_screen(world_position)) {
|
||||
// Draw at screen->x, screen->y
|
||||
}
|
||||
```
|
||||
|
||||
**[➡️ 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.
|
||||
<div align = center>
|
||||
|
||||
# Gallery
|
||||
|
||||
<br>
|
||||
|
||||
[](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
|
||||
[](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
|
||||
|
||||
<br>
|
||||
|
||||
@@ -69,59 +102,44 @@ It provides the latest features, is highly customizable, has all for cheat devel
|
||||
|
||||
![CS2 Preview]
|
||||
|
||||
<br>
|
||||
|
||||
![TF2 Preview]
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
## Supported Render Pipelines
|
||||
| ENGINE | SUPPORT |
|
||||
|----------|---------|
|
||||
| Source | ✅YES |
|
||||
| Unity | ✅YES |
|
||||
| IWEngine | ✅YES |
|
||||
| OpenGL | ✅YES |
|
||||
| Unreal | ✅YES |
|
||||
- **[Getting Started Guide](http://libomath.org/getting_started/)** - Installation and first steps
|
||||
- **[API Overview](http://libomath.org/api_overview/)** - Complete API reference
|
||||
- **[Tutorials](http://libomath.org/tutorials/)** - Step-by-step guides
|
||||
- **[FAQ](http://libomath.org/faq/)** - Common questions and answers
|
||||
- **[Troubleshooting](http://libomath.org/troubleshooting/)** - Solutions to common issues
|
||||
- **[Best Practices](http://libomath.org/best_practices/)** - Guidelines for effective usage
|
||||
|
||||
## Supported Operating Systems
|
||||
## 🤝 Community & Support
|
||||
|
||||
| OS | SUPPORT |
|
||||
|----------------|---------|
|
||||
| Windows 10/11 | ✅YES |
|
||||
| Linux | ✅YES |
|
||||
| Darwin (MacOS) | ✅YES |
|
||||
- **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
|
||||
|
||||
## ❔ Usage
|
||||
ESP example
|
||||
```c++
|
||||
omath::source_engine::Camera cam{localPlayer.GetCameraOrigin(),
|
||||
localPlayer.GetAimPunch(),
|
||||
{1920.f, 1080.f},
|
||||
localPlayer.GetFieldOfView(),
|
||||
0.01.f, 30000.f};
|
||||
|
||||
for (auto ent: apex_sdk::EntityList::GetAllEntities())
|
||||
{
|
||||
const auto bottom = cam.world_to_screen(ent.GetOrigin());
|
||||
const auto top = cam.world_to_screen(ent.GetBonePosition(8) + omath::Vector3<float>{0, 0, 10});
|
||||
|
||||
const auto ent_health = ent.GetHealth();
|
||||
|
||||
if (!top || !bottom || ent_health <= 0)
|
||||
continue;
|
||||
// esp rendering...
|
||||
}
|
||||
```
|
||||
|
||||
## 💘 Acknowledgments
|
||||
# 💘 Acknowledgments
|
||||
- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors)
|
||||
|
||||
<!----------------------------------{ Images }--------------------------------->
|
||||
[APEX Preview]: .github/images/showcase/apex.png
|
||||
[BO2 Preview]: .github/images/showcase/cod_bo2.png
|
||||
[CS2 Preview]: .github/images/showcase/cs2.jpeg
|
||||
|
||||
[APEX Preview]: docs/images/showcase/apex.png
|
||||
[BO2 Preview]: docs/images/showcase/cod_bo2.png
|
||||
[CS2 Preview]: docs/images/showcase/cs2.jpeg
|
||||
[TF2 Preview]: docs/images/showcase/tf2.jpg
|
||||
<!----------------------------------{ Buttons }--------------------------------->
|
||||
[QUICKSTART]: docs/getting_started.md
|
||||
[INSTALL]: INSTALL.md
|
||||
[DOCUMENTATION]: http://libomath.org
|
||||
[TUTORIALS]: docs/tutorials.md
|
||||
[CONTRIBUTING]: CONTRIBUTING.md
|
||||
[EXAMPLES]: examples
|
||||
[SPONSOR]: https://boosty.to/orangecpp/purchase/3568644?ssource=DIRECT&share=subscription_link
|
||||
|
||||
19
benchmark/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
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)
|
||||
|
||||
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
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath)
|
||||
endif ()
|
||||
65
benchmark/benchmark_mat.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// Created by Vlad on 9/17/2025.
|
||||
//
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include <omath/omath.hpp>
|
||||
using namespace omath;
|
||||
|
||||
|
||||
void mat_float_multiplication_col_major(benchmark::State& state)
|
||||
{
|
||||
using MatType = Mat<128, 128, float, MatStoreType::COLUMN_MAJOR>;
|
||||
MatType a;
|
||||
MatType b;
|
||||
a.set(3.f);
|
||||
b.set(7.f);
|
||||
|
||||
|
||||
for ([[maybe_unused]] const auto _ : state)
|
||||
std::ignore = a * b;
|
||||
}
|
||||
void mat_float_multiplication_row_major(benchmark::State& state)
|
||||
{
|
||||
using MatType = Mat<128, 128, float, MatStoreType::ROW_MAJOR>;
|
||||
MatType a;
|
||||
MatType b;
|
||||
a.set(3.f);
|
||||
b.set(7.f);
|
||||
|
||||
|
||||
for ([[maybe_unused]] const auto _ : state)
|
||||
std::ignore = a * b;
|
||||
}
|
||||
|
||||
void mat_double_multiplication_row_major(benchmark::State& state)
|
||||
{
|
||||
using MatType = Mat<128, 128, double, MatStoreType::ROW_MAJOR>;
|
||||
MatType a;
|
||||
MatType b;
|
||||
a.set(3.f);
|
||||
b.set(7.f);
|
||||
|
||||
|
||||
for ([[maybe_unused]] const auto _ : state)
|
||||
std::ignore = a * b;
|
||||
}
|
||||
|
||||
void mat_double_multiplication_col_major(benchmark::State& state)
|
||||
{
|
||||
using MatType = Mat<128, 128, double, MatStoreType::COLUMN_MAJOR>;
|
||||
MatType a;
|
||||
MatType b;
|
||||
a.set(3.f);
|
||||
b.set(7.f);
|
||||
|
||||
|
||||
for ([[maybe_unused]] const auto _ : state)
|
||||
std::ignore = a * b;
|
||||
}
|
||||
|
||||
BENCHMARK(mat_float_multiplication_col_major)->Iterations(5000);
|
||||
BENCHMARK(mat_float_multiplication_row_major)->Iterations(5000);
|
||||
|
||||
BENCHMARK(mat_double_multiplication_col_major)->Iterations(5000);
|
||||
BENCHMARK(mat_double_multiplication_row_major)->Iterations(5000);
|
||||
23
benchmark/benchmark_projectile_pred.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by Vlad on 9/18/2025.
|
||||
//
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <omath/omath.hpp>
|
||||
using namespace omath;
|
||||
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
constexpr float simulation_time_step = 1.f / 1000.f;
|
||||
constexpr float hit_distance_tolerance = 5.f;
|
||||
|
||||
void source_engine_projectile_prediction(benchmark::State& state)
|
||||
{
|
||||
constexpr Target target{.m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||
constexpr Projectile projectile = {.m_origin = {3, 2, 1}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||
|
||||
for ([[maybe_unused]] const auto _: state)
|
||||
std::ignore = ProjPredEngineLegacy(400, simulation_time_step, 50, hit_distance_tolerance)
|
||||
.maybe_calculate_aim_point(projectile, target);
|
||||
}
|
||||
|
||||
BENCHMARK(source_engine_projectile_prediction)->Iterations(10'000);
|
||||
5
benchmark/main.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by Vlad on 9/17/2025.
|
||||
//
|
||||
#include <benchmark/benchmark.h>
|
||||
BENCHMARK_MAIN();
|
||||
118
docs/3d_primitives/box.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# `omath::primitives::create_box` — Build an oriented box as 12 triangles
|
||||
|
||||
> Header: your project’s `primitives/box.hpp` (declares `create_box`)
|
||||
> Namespace: `omath::primitives`
|
||||
> Depends on: `omath::Triangle<omath::Vector3<float>>`, `omath::Vector3<float>`
|
||||
|
||||
```cpp
|
||||
[[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;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What it does
|
||||
|
||||
Constructs a **rectangular cuboid (“box”)** oriented in 3D space and returns its surface as **12 triangles** (2 per face × 6 faces). The box’s central axis runs from `bottom` → `top`. The **up** direction is inferred from that segment; the **forward** and **right** directions define the box’s orientation around that axis.
|
||||
|
||||
The lateral half-extents are derived from the axis length and `ratio`:
|
||||
|
||||
> Let `H = |top - bottom|`. Lateral half-size ≈ `H / ratio` along both `dir_forward` and `dir_right`
|
||||
> (i.e., the cross-section is a square of side `2H/ratio`).
|
||||
|
||||
> **Note:** This describes the intended behavior from the interface. If you rely on a different sizing rule, document it next to your implementation.
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
* `top`
|
||||
Center of the **top face**.
|
||||
|
||||
* `bottom`
|
||||
Center of the **bottom face**.
|
||||
|
||||
* `dir_forward`
|
||||
A direction that orients the box around its up axis. Should be **non-zero** and **not collinear** with `top - bottom`.
|
||||
|
||||
* `dir_right`
|
||||
A direction roughly orthogonal to both `dir_forward` and `top - bottom`. Used to fully fix orientation.
|
||||
|
||||
* `ratio` (default `4.0f`)
|
||||
Controls thickness relative to height. Larger values → thinner box.
|
||||
With the default rule above, half-extent = `|top-bottom|/ratio`.
|
||||
|
||||
---
|
||||
|
||||
## Return value
|
||||
|
||||
`std::array<Triangle<Vector3<float>>, 12>` — the six faces of the box, triangulated.
|
||||
Winding is intended to be **outward-facing** (right-handed coordinates). Do not rely on a specific **face ordering**; treat the array as opaque unless your implementation guarantees an order.
|
||||
|
||||
---
|
||||
|
||||
## Expected math & robustness
|
||||
|
||||
* Define `u = normalize(top - bottom)`.
|
||||
* Re-orthonormalize the basis to avoid skew:
|
||||
|
||||
```cpp
|
||||
f = normalize(dir_forward - u * u.dot(dir_forward)); // drop any up component
|
||||
r = normalize(u.cross(f)); // right-handed basis
|
||||
// (Optionally recompute f = r.cross(u) for orthogonality)
|
||||
```
|
||||
* Half-extents: `h = length(top - bottom) / ratio; hf = h * f; hr = h * r`.
|
||||
* Corners (top): `t±r±f = top ± hr ± hf`; (bottom): `b±r±f = bottom ± hr ± hf`.
|
||||
* Triangulate each face with consistent CCW winding when viewed from outside.
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
```cpp
|
||||
using omath::Vector3;
|
||||
using omath::Triangle;
|
||||
using omath::primitives::create_box;
|
||||
|
||||
// Axis from bottom to top (height 2)
|
||||
Vector3<float> bottom{0, 0, 0};
|
||||
Vector3<float> top {0, 2, 0};
|
||||
|
||||
// Orientation around the axis
|
||||
Vector3<float> forward{0, 0, 1};
|
||||
Vector3<float> right {1, 0, 0};
|
||||
|
||||
// Ratio 4 → lateral half-size = height/4 = 0.5
|
||||
auto tris = create_box(top, bottom, forward, right, 4.0f);
|
||||
|
||||
// Use the triangles (normals, rendering, collision, etc.)
|
||||
for (const auto& tri : tris) {
|
||||
auto n = tri.calculate_normal();
|
||||
(void)n;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage notes & pitfalls
|
||||
|
||||
* **Degenerate axis**: If `top == bottom`, the box is undefined (zero height). Guard against this.
|
||||
* **Directions**: Provide **non-zero**, **reasonably orthogonal** `dir_forward`/`dir_right`. A robust implementation should project/normalize internally, but callers should still pass sensible inputs.
|
||||
* **Winding**: If your renderer or collision expects a specific winding, verify with a unit test and flip vertex order per face if necessary.
|
||||
* **Thickness policy**: This doc assumes both lateral half-extents equal `|top-bottom|/ratio`. If your implementation diverges (e.g., separate forward/right ratios), document it.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath::Triangle` (vertex utilities: normals, centroid, etc.)
|
||||
* `omath::Vector3` (geometry operations used by the construction)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
98
docs/3d_primitives/plane.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# `omath::primitives::create_plane` — Build an oriented quad (2 triangles)
|
||||
|
||||
> Header: your project’s `primitives/plane.hpp`
|
||||
> Namespace: `omath::primitives`
|
||||
> Depends on: `omath::Triangle<omath::Vector3<float>>`, `omath::Vector3<float>`
|
||||
|
||||
```cpp
|
||||
[[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;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What it does
|
||||
|
||||
Creates a **rectangle (quad)** in 3D oriented by the edge **A→B** and a second in-plane **direction**. The quad is returned as **two triangles** suitable for rendering or collision.
|
||||
|
||||
* Edge axis: `e = vertex_b - vertex_a`
|
||||
* Width axis: “direction”, **projected to be perpendicular to `e`** so the quad is planar and well-formed.
|
||||
* Normal (by right-hand rule): `n ∝ e × width`.
|
||||
|
||||
> **Sizing convention**
|
||||
> Typical construction uses **half-width = `size`** along the (normalized, orthogonalized) *direction*, i.e. the total width is `2*size`.
|
||||
> If your implementation interprets `size` as full width, adjust your expectations accordingly.
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
* `vertex_a`, `vertex_b` — two adjacent quad vertices defining the **long edge** of the plane.
|
||||
* `direction` — a vector indicating the **cross-edge direction** within the plane (does not need to be orthogonal or normalized).
|
||||
* `size` — **half-width** of the quad along the (processed) `direction`.
|
||||
|
||||
---
|
||||
|
||||
## Return
|
||||
|
||||
`std::array<Triangle<Vector3<float>>, 2>` — the quad triangulated (consistent CCW winding, outward normal per `e × width`).
|
||||
|
||||
---
|
||||
|
||||
## Robust construction (expected math)
|
||||
|
||||
1. `e = vertex_b - vertex_a`
|
||||
2. Make `d` perpendicular to `e`:
|
||||
|
||||
```
|
||||
d = direction - e * (e.dot(direction) / e.length_sqr());
|
||||
if (d.length_sqr() == 0) pick an arbitrary perpendicular to e
|
||||
d = d.normalized();
|
||||
```
|
||||
3. Offsets: `w = d * size`
|
||||
4. Four corners:
|
||||
|
||||
```
|
||||
A0 = vertex_a - w; A1 = vertex_a + w;
|
||||
B0 = vertex_b - w; B1 = vertex_b + w;
|
||||
```
|
||||
5. Triangles (CCW when viewed from +normal):
|
||||
|
||||
```
|
||||
T0 = Triangle{ A0, A1, B1 }
|
||||
T1 = Triangle{ A0, B1, B0 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
```cpp
|
||||
using omath::Vector3;
|
||||
using omath::Triangle;
|
||||
using omath::primitives::create_plane;
|
||||
|
||||
Vector3<float> a{ -1, 0, -1 }; // edge start
|
||||
Vector3<float> b{ 1, 0, -1 }; // edge end
|
||||
Vector3<float> dir{ 0, 0, 1 }; // cross-edge direction within the plane (roughly +Z)
|
||||
float half_width = 2.0f;
|
||||
|
||||
auto quad = create_plane(a, b, dir, half_width);
|
||||
|
||||
// e.g., compute normals
|
||||
for (const auto& tri : quad) {
|
||||
auto n = tri.calculate_normal(); (void)n;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & edge cases
|
||||
|
||||
* **Degenerate edge**: if `vertex_a == vertex_b`, the plane is undefined.
|
||||
* **Collinearity**: if `direction` is parallel to `vertex_b - vertex_a`, the function must choose an alternate perpendicular; expect a fallback.
|
||||
* **Winding**: If your renderer expects a specific face order, verify and swap the two vertices in each triangle as needed.
|
||||
527
docs/api_overview.md
Normal file
@@ -0,0 +1,527 @@
|
||||
# API Overview
|
||||
|
||||
This document provides a high-level overview of OMath's API, organized by functionality area.
|
||||
|
||||
---
|
||||
|
||||
## Module Organization
|
||||
|
||||
OMath is organized into several logical modules:
|
||||
|
||||
### Core Mathematics
|
||||
- **Linear Algebra** - Vectors, matrices, triangles
|
||||
- **Trigonometry** - Angles, view angles, trigonometric functions
|
||||
- **3D Primitives** - Boxes, planes, geometric shapes
|
||||
|
||||
### Game Development
|
||||
- **Collision Detection** - Ray tracing, intersection tests
|
||||
- **Projectile Prediction** - Ballistics and aim-assist calculations
|
||||
- **Projection** - Camera systems and world-to-screen transformations
|
||||
- **Pathfinding** - A* algorithm, navigation meshes
|
||||
|
||||
### Engine Support
|
||||
- **Source Engine** - Valve's Source Engine (CS:GO, TF2, etc.)
|
||||
- **Unity Engine** - Unity game engine
|
||||
- **Unreal Engine** - Epic's Unreal Engine
|
||||
- **Frostbite Engine** - EA's Frostbite Engine
|
||||
- **IW Engine** - Infinity Ward's engine (Call of Duty)
|
||||
- **OpenGL Engine** - Canonical OpenGL coordinate system
|
||||
|
||||
### Utilities
|
||||
- **Color** - RGBA color representation
|
||||
- **Pattern Scanning** - Memory pattern search (wildcards, PE files)
|
||||
- **Reverse Engineering** - Internal/external memory manipulation
|
||||
|
||||
---
|
||||
|
||||
## Core Types
|
||||
|
||||
### Vectors
|
||||
|
||||
All vector types are template-based and support arithmetic types.
|
||||
|
||||
| Type | Description | Key Methods |
|
||||
|------|-------------|-------------|
|
||||
| `Vector2<T>` | 2D vector | `length()`, `normalized()`, `dot()`, `distance_to()` |
|
||||
| `Vector3<T>` | 3D vector | `length()`, `normalized()`, `dot()`, `cross()`, `angle_between()` |
|
||||
| `Vector4<T>` | 4D vector | Extends Vector3 with `w` component |
|
||||
|
||||
**Common aliases:**
|
||||
```cpp
|
||||
using Vec2f = Vector2<float>;
|
||||
using Vec3f = Vector3<float>;
|
||||
using Vec4f = Vector4<float>;
|
||||
```
|
||||
|
||||
**Key features:**
|
||||
- Component-wise arithmetic (+, -, *, /)
|
||||
- Scalar multiplication/division
|
||||
- Dot and cross products
|
||||
- Safe normalization (returns original if length is zero)
|
||||
- Distance calculations
|
||||
- Angle calculations with error handling
|
||||
- Hash support for `float` variants
|
||||
- `std::formatter` support
|
||||
|
||||
### Matrices
|
||||
|
||||
| Type | Description | Key Methods |
|
||||
|------|-------------|-------------|
|
||||
| `Mat4X4` | 4×4 matrix | `identity()`, `transpose()`, `determinant()`, `inverse()` |
|
||||
|
||||
**Use cases:**
|
||||
- Transformation matrices
|
||||
- View matrices
|
||||
- Projection matrices
|
||||
- Model-view-projection pipelines
|
||||
|
||||
### Angles
|
||||
|
||||
Strong-typed angle system with compile-time range enforcement:
|
||||
|
||||
| Type | Range | Description |
|
||||
|------|-------|-------------|
|
||||
| `Angle<T, Min, Max, Flags>` | Custom | Generic angle type with bounds |
|
||||
| `PitchAngle` | [-89°, 89°] | Vertical camera rotation |
|
||||
| `YawAngle` | [-180°, 180°] | Horizontal camera rotation |
|
||||
| `RollAngle` | [-180°, 180°] | Camera roll |
|
||||
| `ViewAngles` | - | Composite pitch/yaw/roll |
|
||||
|
||||
**Features:**
|
||||
- Automatic normalization/clamping based on flags
|
||||
- Conversions between degrees and radians
|
||||
- Type-safe arithmetic
|
||||
- Prevents common angle bugs
|
||||
|
||||
---
|
||||
|
||||
## Projection System
|
||||
|
||||
### Camera
|
||||
|
||||
Generic camera template that works with any engine trait:
|
||||
|
||||
```cpp
|
||||
template<class MatrixType, class AnglesType, class EngineTrait>
|
||||
class Camera;
|
||||
```
|
||||
|
||||
**Engine-specific cameras:**
|
||||
```cpp
|
||||
omath::source_engine::Camera // Source Engine
|
||||
omath::unity_engine::Camera // Unity
|
||||
omath::unreal_engine::Camera // Unreal
|
||||
omath::frostbite_engine::Camera // Frostbite
|
||||
omath::iw_engine::Camera // IW Engine
|
||||
omath::opengl_engine::Camera // OpenGL
|
||||
```
|
||||
|
||||
**Core methods:**
|
||||
- `world_to_screen(Vector3<float>)` - Project 3D point to 2D screen
|
||||
- `get_view_matrix()` - Get current view matrix
|
||||
- `get_projection_matrix()` - Get current projection matrix
|
||||
- `update(position, angles)` - Update camera state
|
||||
|
||||
**Supporting types:**
|
||||
- `ViewPort` - Screen dimensions and aspect ratio
|
||||
- `FieldOfView` - FOV in degrees with validation
|
||||
- `ProjectionError` - Error codes for projection failures
|
||||
|
||||
---
|
||||
|
||||
## Collision Detection
|
||||
|
||||
### LineTracer
|
||||
|
||||
Ray-casting and line tracing utilities:
|
||||
|
||||
```cpp
|
||||
namespace omath::collision {
|
||||
class LineTracer;
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Ray-triangle intersection
|
||||
- Ray-plane intersection
|
||||
- Ray-box intersection
|
||||
- Distance calculations
|
||||
- Normal calculations at hit points
|
||||
|
||||
### 3D Primitives
|
||||
|
||||
| Type | Description | Key Methods |
|
||||
|------|-------------|-------------|
|
||||
| `Plane` | Infinite plane | `intersects_ray()`, `distance_to_point()` |
|
||||
| `Box` | Axis-aligned bounding box | `contains()`, `intersects()` |
|
||||
|
||||
---
|
||||
|
||||
## Projectile Prediction
|
||||
|
||||
### Interfaces
|
||||
|
||||
**`ProjPredEngineInterface`** - Base interface for all prediction engines
|
||||
|
||||
```cpp
|
||||
virtual std::optional<Vector3<float>>
|
||||
maybe_calculate_aim_point(const Projectile&, const Target&) const = 0;
|
||||
```
|
||||
|
||||
### Implementations
|
||||
|
||||
| Engine | Description | Optimizations |
|
||||
|--------|-------------|---------------|
|
||||
| `ProjPredEngineLegacy` | Standard implementation | Portable, works everywhere |
|
||||
| `ProjPredEngineAVX2` | AVX2 optimized | 2-4x faster on modern CPUs |
|
||||
|
||||
### Supporting Types
|
||||
|
||||
**`Projectile`** - Defines projectile properties:
|
||||
```cpp
|
||||
struct Projectile {
|
||||
Vector3<float> origin;
|
||||
float speed;
|
||||
Vector3<float> gravity;
|
||||
// ... additional properties
|
||||
};
|
||||
```
|
||||
|
||||
**`Target`** - Defines target state:
|
||||
```cpp
|
||||
struct Target {
|
||||
Vector3<float> position;
|
||||
Vector3<float> velocity;
|
||||
// ... additional properties
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pathfinding
|
||||
|
||||
### A* Algorithm
|
||||
|
||||
```cpp
|
||||
namespace omath::pathfinding {
|
||||
template<typename NodeType>
|
||||
class AStar;
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Generic node type support
|
||||
- Customizable heuristics
|
||||
- Efficient priority queue implementation
|
||||
- Path reconstruction
|
||||
|
||||
### Navigation Mesh
|
||||
|
||||
```cpp
|
||||
namespace omath::pathfinding {
|
||||
class NavigationMesh;
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Triangle-based navigation
|
||||
- Neighbor connectivity
|
||||
- Walkable area definitions
|
||||
|
||||
---
|
||||
|
||||
## Engine Traits
|
||||
|
||||
Each game engine has a trait system providing engine-specific math:
|
||||
|
||||
### CameraTrait
|
||||
|
||||
Implements camera math for an engine:
|
||||
- `calc_look_at_angle()` - Calculate angles to look at a point
|
||||
- `calc_view_matrix()` - Build view matrix from angles and position
|
||||
- `calc_projection_matrix()` - Build projection matrix from FOV and viewport
|
||||
|
||||
### PredEngineTrait
|
||||
|
||||
Provides physics/ballistics specific to an engine:
|
||||
- Gravity vectors
|
||||
- Coordinate system conventions
|
||||
- Unit conversions
|
||||
- Physics parameters
|
||||
|
||||
### Available Traits
|
||||
|
||||
| Engine | Camera Trait | Pred Engine Trait | Constants | Formulas |
|
||||
|--------|--------------|-------------------|-----------|----------|
|
||||
| Source Engine | ✓ | ✓ | ✓ | ✓ |
|
||||
| Unity Engine | ✓ | ✓ | ✓ | ✓ |
|
||||
| Unreal Engine | ✓ | ✓ | ✓ | ✓ |
|
||||
| Frostbite | ✓ | ✓ | ✓ | ✓ |
|
||||
| IW Engine | ✓ | ✓ | ✓ | ✓ |
|
||||
| OpenGL | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
**Documentation:**
|
||||
- See `docs/engines/<engine_name>/` for detailed per-engine docs
|
||||
- Each engine has separate docs for camera_trait, pred_engine_trait, constants, and formulas
|
||||
|
||||
---
|
||||
|
||||
## Utility Functions
|
||||
|
||||
### Color
|
||||
|
||||
```cpp
|
||||
struct Color {
|
||||
uint8_t r, g, b, a;
|
||||
|
||||
// Conversions
|
||||
static Color from_hsv(float h, float s, float v);
|
||||
static Color from_hex(uint32_t hex);
|
||||
uint32_t to_hex() const;
|
||||
|
||||
// Blending
|
||||
Color blend(const Color& other, float t) const;
|
||||
};
|
||||
```
|
||||
|
||||
### Pattern Scanning
|
||||
|
||||
**Binary pattern search with wildcards:**
|
||||
|
||||
```cpp
|
||||
// Pattern with wildcards (?? = any byte)
|
||||
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
|
||||
|
||||
// Scan memory
|
||||
auto result = pattern_scan(memory_buffer, pattern);
|
||||
if (result) {
|
||||
std::cout << "Found at offset: " << result->offset << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
**PE file scanning:**
|
||||
|
||||
```cpp
|
||||
PEPatternScanner scanner("target.exe");
|
||||
if (auto addr = scanner.scan_pattern(pattern)) {
|
||||
std::cout << "Found at RVA: " << *addr << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Reverse Engineering
|
||||
|
||||
**External memory access:**
|
||||
```cpp
|
||||
ExternalRevObject process("game.exe");
|
||||
Vector3<float> position = process.read<Vector3<float>>(address);
|
||||
process.write(address, new_position);
|
||||
```
|
||||
|
||||
**Internal memory access:**
|
||||
```cpp
|
||||
InternalRevObject memory;
|
||||
auto value = memory.read<float>(address);
|
||||
memory.write(address, new_value);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Concepts and Constraints
|
||||
|
||||
OMath uses C++20 concepts for type safety:
|
||||
|
||||
```cpp
|
||||
template<class T>
|
||||
concept Arithmetic = std::is_arithmetic_v<T>;
|
||||
|
||||
template<class EngineTrait>
|
||||
concept CameraEngineConcept = requires(EngineTrait t) {
|
||||
{ t.calc_look_at_angle(...) } -> /* returns angles */;
|
||||
{ t.calc_view_matrix(...) } -> /* returns matrix */;
|
||||
{ t.calc_projection_matrix(...) } -> /* returns matrix */;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
OMath uses modern C++ error handling:
|
||||
|
||||
### std::expected (C++23)
|
||||
|
||||
```cpp
|
||||
std::expected<Angle<...>, Vector3Error>
|
||||
angle_between(const Vector3& other) const;
|
||||
|
||||
if (auto angle = v1.angle_between(v2)) {
|
||||
// Success: use *angle
|
||||
} else {
|
||||
// Error: angle.error() gives Vector3Error
|
||||
}
|
||||
```
|
||||
|
||||
### std::optional
|
||||
|
||||
```cpp
|
||||
std::optional<Vector2<float>>
|
||||
world_to_screen(const Vector3<float>& world);
|
||||
|
||||
if (auto screen = camera.world_to_screen(pos)) {
|
||||
// Success: use screen->x, screen->y
|
||||
} else {
|
||||
// Point not visible
|
||||
}
|
||||
```
|
||||
|
||||
### Error Codes
|
||||
|
||||
```cpp
|
||||
enum class ProjectionError {
|
||||
SUCCESS = 0,
|
||||
POINT_BEHIND_CAMERA,
|
||||
INVALID_VIEWPORT,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### constexpr Support
|
||||
|
||||
Most operations are `constexpr` where possible:
|
||||
|
||||
```cpp
|
||||
constexpr Vector3<float> v{1, 2, 3};
|
||||
constexpr auto len_sq = v.length_sqr(); // Computed at compile time
|
||||
```
|
||||
|
||||
### AVX2 Optimizations
|
||||
|
||||
Use AVX2 variants when available:
|
||||
|
||||
```cpp
|
||||
// Standard: portable but slower
|
||||
ProjPredEngineLegacy legacy_engine;
|
||||
|
||||
// AVX2: 2-4x faster on modern CPUs
|
||||
ProjPredEngineAVX2 fast_engine;
|
||||
```
|
||||
|
||||
**When to use AVX2:**
|
||||
- Modern Intel/AMD processors (2013+)
|
||||
- Performance-critical paths
|
||||
- Batch operations
|
||||
|
||||
**When to use Legacy:**
|
||||
- Older processors
|
||||
- ARM platforms
|
||||
- Guaranteed compatibility
|
||||
|
||||
### Cache Efficiency
|
||||
|
||||
```cpp
|
||||
// Good: contiguous storage
|
||||
std::vector<Vector3<float>> positions;
|
||||
|
||||
// Good: structure of arrays for SIMD
|
||||
struct Particles {
|
||||
std::vector<float> x, y, z;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Platform | Support | Notes |
|
||||
|----------|---------|-------|
|
||||
| Windows | ✓ | MSVC, Clang, GCC |
|
||||
| Linux | ✓ | GCC, Clang |
|
||||
| macOS | ✓ | Clang |
|
||||
|
||||
**Minimum requirements:**
|
||||
- C++20 compiler
|
||||
- C++23 recommended for `std::expected`
|
||||
|
||||
---
|
||||
|
||||
## Thread Safety
|
||||
|
||||
- **Vector/Matrix types**: Thread-safe (immutable operations)
|
||||
- **Camera**: Not thread-safe (mutable state)
|
||||
- **Pattern scanning**: Thread-safe (read-only operations)
|
||||
- **Memory access**: Depends on OS/process synchronization
|
||||
|
||||
**Thread-safe example:**
|
||||
```cpp
|
||||
// Safe: each thread gets its own camera
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
threads.emplace_back([i]() {
|
||||
Camera camera = /* create camera */;
|
||||
// Use camera in this thread
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Type Aliases
|
||||
|
||||
```cpp
|
||||
using Vec3f = omath::Vector3<float>;
|
||||
using Mat4 = omath::Mat4X4;
|
||||
```
|
||||
|
||||
### 2. Prefer constexpr When Possible
|
||||
|
||||
```cpp
|
||||
constexpr auto compute_at_compile_time() {
|
||||
Vector3<float> v{1, 2, 3};
|
||||
return v.length_sqr();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Check Optional/Expected Results
|
||||
|
||||
```cpp
|
||||
// Good
|
||||
if (auto result = camera.world_to_screen(pos)) {
|
||||
use(*result);
|
||||
}
|
||||
|
||||
// Bad - may crash
|
||||
auto result = camera.world_to_screen(pos);
|
||||
use(result->x); // Undefined behavior if nullopt
|
||||
```
|
||||
|
||||
### 4. Use Engine-Specific Types
|
||||
|
||||
```cpp
|
||||
// Good: uses correct coordinate system
|
||||
using namespace omath::source_engine;
|
||||
Camera camera = /* ... */;
|
||||
|
||||
// Bad: mixing engine types
|
||||
using UnityCamera = omath::unity_engine::Camera;
|
||||
using SourceAngles = omath::source_engine::ViewAngles;
|
||||
UnityCamera camera{pos, SourceAngles{}}; // Wrong!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Getting Started Guide](getting_started.md)
|
||||
- [Installation Instructions](install.md)
|
||||
- [Examples Directory](../examples/)
|
||||
- Individual module documentation in respective folders
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
532
docs/best_practices.md
Normal file
@@ -0,0 +1,532 @@
|
||||
# Best Practices
|
||||
|
||||
Guidelines for using OMath effectively and avoiding common pitfalls.
|
||||
|
||||
---
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Use Type Aliases
|
||||
|
||||
Define clear type aliases for commonly used types:
|
||||
|
||||
```cpp
|
||||
// Good: Clear and concise
|
||||
using Vec3f = omath::Vector3<float>;
|
||||
using Vec2f = omath::Vector2<float>;
|
||||
using Mat4 = omath::Mat4X4;
|
||||
|
||||
Vec3f position{1.0f, 2.0f, 3.0f};
|
||||
```
|
||||
|
||||
```cpp
|
||||
// Avoid: Verbose and repetitive
|
||||
omath::Vector3<float> position{1.0f, 2.0f, 3.0f};
|
||||
omath::Vector3<float> velocity{0.0f, 0.0f, 0.0f};
|
||||
```
|
||||
|
||||
### Namespace Usage
|
||||
|
||||
Be selective with `using namespace`:
|
||||
|
||||
```cpp
|
||||
// Good: Specific namespace for your engine
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// Good: Import specific types
|
||||
using omath::Vector3;
|
||||
using omath::Vector2;
|
||||
|
||||
// Avoid: Too broad
|
||||
using namespace omath; // Imports everything
|
||||
```
|
||||
|
||||
### Include What You Use
|
||||
|
||||
```cpp
|
||||
// Good: Include specific headers
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
#include <omath/projection/camera.hpp>
|
||||
|
||||
// Okay for development
|
||||
#include <omath/omath.hpp>
|
||||
|
||||
// Production: Include only what you need
|
||||
// to reduce compile times
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Always Check Optional Results
|
||||
|
||||
```cpp
|
||||
// Good: Check before using
|
||||
if (auto screen = camera.world_to_screen(world_pos)) {
|
||||
draw_at(screen->x, screen->y);
|
||||
} else {
|
||||
// Handle point not visible
|
||||
}
|
||||
|
||||
// Bad: Unchecked access can crash
|
||||
auto screen = camera.world_to_screen(world_pos);
|
||||
draw_at(screen->x, screen->y); // Undefined behavior if nullopt!
|
||||
```
|
||||
|
||||
### Handle Expected Errors
|
||||
|
||||
```cpp
|
||||
// Good: Handle error case
|
||||
if (auto angle = v1.angle_between(v2)) {
|
||||
use_angle(*angle);
|
||||
} else {
|
||||
switch (angle.error()) {
|
||||
case Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE:
|
||||
// Handle zero-length vector
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Bad: Assume success
|
||||
auto angle = v1.angle_between(v2);
|
||||
use_angle(*angle); // Throws if error!
|
||||
```
|
||||
|
||||
### Validate Inputs
|
||||
|
||||
```cpp
|
||||
// Good: Validate before expensive operations
|
||||
bool is_valid_projectile(const Projectile& proj) {
|
||||
return proj.speed > 0.0f &&
|
||||
std::isfinite(proj.speed) &&
|
||||
std::isfinite(proj.origin.length());
|
||||
}
|
||||
|
||||
if (is_valid_projectile(proj) && is_valid_target(target)) {
|
||||
auto aim = engine.maybe_calculate_aim_point(proj, target);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
### Use constexpr When Possible
|
||||
|
||||
```cpp
|
||||
// Good: Computed at compile time
|
||||
constexpr Vector3<float> gravity{0.0f, 0.0f, -9.81f};
|
||||
constexpr float max_range = 1000.0f;
|
||||
constexpr float max_range_sq = max_range * max_range;
|
||||
|
||||
// Use in runtime calculations
|
||||
if (position.length_sqr() < max_range_sq) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Prefer Squared Distance
|
||||
|
||||
```cpp
|
||||
// Good: Avoids expensive sqrt
|
||||
constexpr float max_dist_sq = 100.0f * 100.0f;
|
||||
for (const auto& entity : entities) {
|
||||
if (entity.pos.distance_to_sqr(player_pos) < max_dist_sq) {
|
||||
// Process nearby entity
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid: Unnecessary sqrt calls
|
||||
constexpr float max_dist = 100.0f;
|
||||
for (const auto& entity : entities) {
|
||||
if (entity.pos.distance_to(player_pos) < max_dist) {
|
||||
// More expensive
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Expensive Calculations
|
||||
|
||||
```cpp
|
||||
// Good: Update camera once per frame
|
||||
void update_frame() {
|
||||
camera.update(current_position, current_angles);
|
||||
|
||||
// All projections use cached matrices
|
||||
for (const auto& entity : entities) {
|
||||
if (auto screen = camera.world_to_screen(entity.pos)) {
|
||||
draw_entity(screen->x, screen->y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bad: Camera recreated each call
|
||||
for (const auto& entity : entities) {
|
||||
Camera cam(pos, angles, viewport, fov, near, far); // Expensive!
|
||||
auto screen = cam.world_to_screen(entity.pos);
|
||||
}
|
||||
```
|
||||
|
||||
### Choose the Right Engine
|
||||
|
||||
```cpp
|
||||
// Good: Use AVX2 when available
|
||||
#ifdef __AVX2__
|
||||
using Engine = ProjPredEngineAVX2;
|
||||
#else
|
||||
using Engine = ProjPredEngineLegacy;
|
||||
#endif
|
||||
|
||||
Engine prediction_engine;
|
||||
|
||||
// Or runtime detection
|
||||
Engine* create_best_engine() {
|
||||
if (cpu_supports_avx2()) {
|
||||
return new ProjPredEngineAVX2();
|
||||
}
|
||||
return new ProjPredEngineLegacy();
|
||||
}
|
||||
```
|
||||
|
||||
### Minimize Allocations
|
||||
|
||||
```cpp
|
||||
// Good: Reuse vectors
|
||||
std::vector<Vector3<float>> positions;
|
||||
positions.reserve(expected_count);
|
||||
|
||||
// In loop
|
||||
positions.clear(); // Doesn't deallocate
|
||||
for (...) {
|
||||
positions.push_back(compute_position());
|
||||
}
|
||||
|
||||
// Bad: Allocate every time
|
||||
for (...) {
|
||||
std::vector<Vector3<float>> positions; // Allocates each iteration
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Type Safety
|
||||
|
||||
### Use Strong Angle Types
|
||||
|
||||
```cpp
|
||||
// Good: Type-safe angles
|
||||
PitchAngle pitch = PitchAngle::from_degrees(45.0f);
|
||||
YawAngle yaw = YawAngle::from_degrees(90.0f);
|
||||
ViewAngles angles{pitch, yaw, RollAngle::from_degrees(0.0f)};
|
||||
|
||||
// Bad: Raw floats lose safety
|
||||
float pitch = 45.0f; // No range checking
|
||||
float yaw = 90.0f; // Can go out of bounds
|
||||
```
|
||||
|
||||
### Match Engine Types
|
||||
|
||||
```cpp
|
||||
// Good: Use matching types from same engine
|
||||
using namespace omath::source_engine;
|
||||
Camera camera = /* ... */;
|
||||
ViewAngles angles = /* ... */;
|
||||
|
||||
// Bad: Mixing engine types
|
||||
using UnityCamera = omath::unity_engine::Camera;
|
||||
using SourceAngles = omath::source_engine::ViewAngles;
|
||||
UnityCamera camera{pos, SourceAngles{}, ...}; // May cause issues!
|
||||
```
|
||||
|
||||
### Template Type Parameters
|
||||
|
||||
```cpp
|
||||
// Good: Explicit and clear
|
||||
Vector3<float> position;
|
||||
Vector3<double> high_precision_pos;
|
||||
|
||||
// Okay: Use default float
|
||||
Vector3<> position; // Defaults to float
|
||||
|
||||
// Avoid: Mixing types unintentionally
|
||||
Vector3<float> a;
|
||||
Vector3<double> b;
|
||||
auto result = a + b; // Type mismatch!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing & Validation
|
||||
|
||||
### Test Edge Cases
|
||||
|
||||
```cpp
|
||||
void test_projection() {
|
||||
Camera camera = setup_camera();
|
||||
|
||||
// Test normal case
|
||||
assert(camera.world_to_screen({100, 100, 100}).has_value());
|
||||
|
||||
// Test edge cases
|
||||
assert(!camera.world_to_screen({0, 0, -100}).has_value()); // Behind
|
||||
assert(!camera.world_to_screen({1e10, 0, 0}).has_value()); // Too far
|
||||
|
||||
// Test boundaries
|
||||
Vector3<float> at_near{0, 0, camera.near_plane() + 0.1f};
|
||||
assert(camera.world_to_screen(at_near).has_value());
|
||||
}
|
||||
```
|
||||
|
||||
### Validate Assumptions
|
||||
|
||||
```cpp
|
||||
void validate_game_data() {
|
||||
// Validate FOV
|
||||
float fov = read_game_fov();
|
||||
assert(fov > 1.0f && fov < 179.0f);
|
||||
|
||||
// Validate positions
|
||||
Vector3<float> pos = read_player_position();
|
||||
assert(std::isfinite(pos.x));
|
||||
assert(std::isfinite(pos.y));
|
||||
assert(std::isfinite(pos.z));
|
||||
|
||||
// Validate viewport
|
||||
ViewPort vp = read_viewport();
|
||||
assert(vp.width > 0 && vp.height > 0);
|
||||
}
|
||||
```
|
||||
|
||||
### Use Assertions
|
||||
|
||||
```cpp
|
||||
// Good: Catch errors early in development
|
||||
void shoot_projectile(const Projectile& proj) {
|
||||
assert(proj.speed > 0.0f && "Projectile speed must be positive");
|
||||
assert(std::isfinite(proj.origin.length()) && "Invalid projectile origin");
|
||||
|
||||
// Continue with logic
|
||||
}
|
||||
|
||||
// Add debug-only checks
|
||||
#ifndef NDEBUG
|
||||
if (!is_valid_input(data)) {
|
||||
std::cerr << "Warning: Invalid input detected\n";
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Memory & Resources
|
||||
|
||||
### RAII for Resources
|
||||
|
||||
```cpp
|
||||
// Good: Automatic cleanup
|
||||
class GameOverlay {
|
||||
Camera camera_;
|
||||
std::vector<Entity> entities_;
|
||||
|
||||
public:
|
||||
GameOverlay(/* ... */) : camera_(/* ... */) {
|
||||
entities_.reserve(1000);
|
||||
}
|
||||
|
||||
// Resources cleaned up automatically
|
||||
~GameOverlay() = default;
|
||||
};
|
||||
```
|
||||
|
||||
### Avoid Unnecessary Copies
|
||||
|
||||
```cpp
|
||||
// Good: Pass by const reference
|
||||
void draw_entities(const std::vector<Vector3<float>>& positions) {
|
||||
for (const auto& pos : positions) {
|
||||
// Process position
|
||||
}
|
||||
}
|
||||
|
||||
// Bad: Copies entire vector
|
||||
void draw_entities(std::vector<Vector3<float>> positions) {
|
||||
// Expensive copy!
|
||||
}
|
||||
|
||||
// Good: Move when transferring ownership
|
||||
std::vector<Vector3<float>> compute_positions();
|
||||
auto positions = compute_positions(); // Move, not copy
|
||||
```
|
||||
|
||||
### Use Structured Bindings
|
||||
|
||||
```cpp
|
||||
// Good: Clear and concise
|
||||
if (auto [success, screen_pos] = try_project(world_pos); success) {
|
||||
draw_at(screen_pos.x, screen_pos.y);
|
||||
}
|
||||
|
||||
// Good: Decompose results
|
||||
auto [x, y, z] = position.as_tuple();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
### Document Assumptions
|
||||
|
||||
```cpp
|
||||
// Good: Clear documentation
|
||||
/**
|
||||
* Projects world position to screen space.
|
||||
*
|
||||
* @param world_pos Position in world coordinates (meters)
|
||||
* @return Screen position if visible, nullopt if behind camera or out of view
|
||||
*
|
||||
* @note Assumes camera.update() was called this frame
|
||||
* @note Screen coordinates are in viewport space [0, width] x [0, height]
|
||||
*/
|
||||
std::optional<Vector2<float>> project(const Vector3<float>& world_pos);
|
||||
```
|
||||
|
||||
### Explain Non-Obvious Code
|
||||
|
||||
```cpp
|
||||
// Good: Explain the math
|
||||
// Use squared distance to avoid expensive sqrt
|
||||
// max_range = 100.0 → max_range_sq = 10000.0
|
||||
constexpr float max_range_sq = 100.0f * 100.0f;
|
||||
if (dist_sq < max_range_sq) {
|
||||
// Entity is in range
|
||||
}
|
||||
|
||||
// Explain engine-specific quirks
|
||||
// Source Engine uses Z-up coordinates, but angles are in degrees
|
||||
// Pitch: [-89, 89], Yaw: [-180, 180], Roll: [-180, 180]
|
||||
ViewAngles angles{pitch, yaw, roll};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### Add Debug Visualization
|
||||
|
||||
```cpp
|
||||
#ifndef NDEBUG
|
||||
void debug_draw_projection() {
|
||||
// Draw camera frustum
|
||||
draw_frustum(camera);
|
||||
|
||||
// Draw world axes
|
||||
draw_line({0,0,0}, {100,0,0}, Color::Red); // X
|
||||
draw_line({0,0,0}, {0,100,0}, Color::Green); // Y
|
||||
draw_line({0,0,0}, {0,0,100}, Color::Blue); // Z
|
||||
|
||||
// Draw projected points
|
||||
for (const auto& entity : entities) {
|
||||
if (auto screen = camera.world_to_screen(entity.pos)) {
|
||||
draw_cross(screen->x, screen->y);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
### Log Important Values
|
||||
|
||||
```cpp
|
||||
void debug_projection_failure(const Vector3<float>& pos) {
|
||||
std::cerr << "Projection failed for position: "
|
||||
<< pos.x << ", " << pos.y << ", " << pos.z << "\n";
|
||||
|
||||
auto view_matrix = camera.get_view_matrix();
|
||||
std::cerr << "View matrix:\n";
|
||||
// Print matrix...
|
||||
|
||||
std::cerr << "Camera position: "
|
||||
<< camera.position().x << ", "
|
||||
<< camera.position().y << ", "
|
||||
<< camera.position().z << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Use Debug Builds
|
||||
|
||||
```cmake
|
||||
# CMakeLists.txt
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(your_target PRIVATE
|
||||
DEBUG_PROJECTION=1
|
||||
VALIDATE_INPUTS=1
|
||||
)
|
||||
endif()
|
||||
```
|
||||
|
||||
```cpp
|
||||
#ifdef DEBUG_PROJECTION
|
||||
std::cout << "Projecting: " << world_pos << "\n";
|
||||
#endif
|
||||
|
||||
#ifdef VALIDATE_INPUTS
|
||||
assert(std::isfinite(world_pos.length()));
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
### Cross-Platform Code
|
||||
|
||||
```cpp
|
||||
// Good: Platform-agnostic
|
||||
constexpr float PI = 3.14159265359f;
|
||||
|
||||
// Avoid: Platform-specific
|
||||
#ifdef _WIN32
|
||||
// Windows-only code
|
||||
#endif
|
||||
```
|
||||
|
||||
### Handle Different Compilers
|
||||
|
||||
```cpp
|
||||
// Good: Compiler-agnostic
|
||||
#if defined(_MSC_VER)
|
||||
// MSVC-specific
|
||||
#elif defined(__GNUC__)
|
||||
// GCC/Clang-specific
|
||||
#endif
|
||||
|
||||
// Use OMath's built-in compatibility
|
||||
// It handles compiler differences automatically
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Key principles:**
|
||||
1. **Safety first**: Always check optional/expected results
|
||||
2. **Performance matters**: Use constexpr, avoid allocations, cache results
|
||||
3. **Type safety**: Use strong types, match engine types
|
||||
4. **Clear code**: Use aliases, document assumptions, explain non-obvious logic
|
||||
5. **Test thoroughly**: Validate inputs, test edge cases, add assertions
|
||||
6. **Debug effectively**: Add visualization, log values, use debug builds
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Troubleshooting Guide](troubleshooting.md)
|
||||
- [FAQ](faq.md)
|
||||
- [API Overview](api_overview.md)
|
||||
- [Tutorials](tutorials.md)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
181
docs/collision/line_tracer.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# `omath::collision::Ray` & `LineTracer` — Ray–Triangle intersection (Möller–Trumbore)
|
||||
|
||||
> Headers: your project’s `ray.hpp` (includes `omath/linear_algebra/triangle.hpp`, `omath/linear_algebra/vector3.hpp`)
|
||||
> Namespace: `omath::collision`
|
||||
> Depends on: `omath::Vector3<float>`, `omath::Triangle<Vector3<float>>`
|
||||
> Algorithm: **Möller–Trumbore** ray–triangle intersection (no allocation)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
These types provide a minimal, fast path to test and compute intersections between a **ray or line segment** and a **single triangle**:
|
||||
|
||||
* `Ray` — start/end points plus a flag to treat the ray as **infinite** (half-line) or a **finite segment**.
|
||||
* `LineTracer` — static helpers:
|
||||
|
||||
* `can_trace_line(ray, triangle)` → `true` if they intersect.
|
||||
* `get_ray_hit_point(ray, triangle)` → the hit point (precondition: intersection exists).
|
||||
|
||||
---
|
||||
|
||||
## `Ray`
|
||||
|
||||
```cpp
|
||||
class Ray {
|
||||
public:
|
||||
omath::Vector3<float> start; // ray origin
|
||||
omath::Vector3<float> end; // end point (for finite segment) or a point along the direction
|
||||
bool infinite_length = false;
|
||||
|
||||
[[nodiscard]] omath::Vector3<float> direction_vector() const noexcept;
|
||||
[[nodiscard]] omath::Vector3<float> direction_vector_normalized() const noexcept;
|
||||
};
|
||||
```
|
||||
|
||||
### Semantics
|
||||
|
||||
* **Direction**: `direction_vector() == end - start`.
|
||||
The normalized variant returns a unit vector (or `{0,0,0}` if the direction length is zero).
|
||||
* **Extent**:
|
||||
|
||||
* `infinite_length == true` → treat as a **semi-infinite ray** from `start` along `direction`.
|
||||
* `infinite_length == false` → treat as a **closed segment** from `start` to `end`.
|
||||
|
||||
> Tip: For an infinite ray that points along some vector `d`, set `end = start + d`.
|
||||
|
||||
---
|
||||
|
||||
## `LineTracer`
|
||||
|
||||
```cpp
|
||||
class LineTracer {
|
||||
public:
|
||||
LineTracer() = delete;
|
||||
|
||||
[[nodiscard]]
|
||||
static bool can_trace_line(
|
||||
const Ray& ray,
|
||||
const omath::Triangle<omath::Vector3<float>>& triangle
|
||||
) noexcept;
|
||||
|
||||
// Möller–Trumbore intersection
|
||||
[[nodiscard]]
|
||||
static omath::Vector3<float> get_ray_hit_point(
|
||||
const Ray& ray,
|
||||
const omath::Triangle<omath::Vector3<float>>& triangle
|
||||
) noexcept;
|
||||
};
|
||||
```
|
||||
|
||||
### Behavior & contract
|
||||
|
||||
* **Intersection test**: `can_trace_line` returns `true` iff the ray/segment intersects the triangle (within the ray’s extent).
|
||||
* **Hit point**: `get_ray_hit_point` **assumes** there is an intersection.
|
||||
Call **only after** `can_trace_line(...) == true`. Otherwise the result is unspecified.
|
||||
* **Triangle winding**: Standard Möller–Trumbore works with either winding; no backface culling is implied here.
|
||||
* **Degenerate inputs**: A zero-length ray or degenerate triangle yields **no hit** under typical Möller–Trumbore tolerances.
|
||||
|
||||
---
|
||||
|
||||
## Quick examples
|
||||
|
||||
### 1) Segment vs triangle
|
||||
|
||||
```cpp
|
||||
using omath::Vector3;
|
||||
using omath::Triangle;
|
||||
using omath::collision::Ray;
|
||||
using omath::collision::LineTracer;
|
||||
|
||||
Triangle<Vector3<float>> tri(
|
||||
Vector3<float>{0, 0, 0},
|
||||
Vector3<float>{1, 0, 0},
|
||||
Vector3<float>{0, 1, 0}
|
||||
);
|
||||
|
||||
Ray seg;
|
||||
seg.start = {0.25f, 0.25f, 1.0f};
|
||||
seg.end = {0.25f, 0.25f,-1.0f};
|
||||
seg.infinite_length = false; // finite segment
|
||||
|
||||
if (LineTracer::can_trace_line(seg, tri)) {
|
||||
Vector3<float> hit = LineTracer::get_ray_hit_point(seg, tri);
|
||||
// use hit
|
||||
}
|
||||
```
|
||||
|
||||
### 2) Infinite ray
|
||||
|
||||
```cpp
|
||||
Ray ray;
|
||||
ray.start = {0.5f, 0.5f, 1.0f};
|
||||
ray.end = ray.start + Vector3<float>{0, 0, -1}; // direction only
|
||||
ray.infinite_length = true;
|
||||
|
||||
bool hit = LineTracer::can_trace_line(ray, tri);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & edge cases
|
||||
|
||||
* **Normalization**: `direction_vector_normalized()` returns `{0,0,0}` for a zero-length direction (safe, but unusable for tracing).
|
||||
* **Precision**: The underlying algorithm uses EPS thresholds to reject nearly parallel cases; results near edges can be sensitive to floating-point error. If you need robust edge inclusion/exclusion, document and enforce a policy (e.g., inclusive barycentric range with small epsilon).
|
||||
* **Hit location**: The point returned by `get_ray_hit_point` lies **on the triangle plane** and within its area by construction (when `can_trace_line` is `true`).
|
||||
|
||||
---
|
||||
|
||||
## API summary
|
||||
|
||||
```cpp
|
||||
namespace omath::collision {
|
||||
|
||||
class Ray {
|
||||
public:
|
||||
Vector3<float> start, end;
|
||||
bool infinite_length = false;
|
||||
|
||||
[[nodiscard]] Vector3<float> direction_vector() const noexcept;
|
||||
[[nodiscard]] Vector3<float> direction_vector_normalized() const noexcept;
|
||||
};
|
||||
|
||||
class LineTracer {
|
||||
public:
|
||||
LineTracer() = delete;
|
||||
|
||||
[[nodiscard]] static bool can_trace_line(
|
||||
const Ray&,
|
||||
const omath::Triangle<omath::Vector3<float>>&
|
||||
) noexcept;
|
||||
|
||||
[[nodiscard]] static Vector3<float> get_ray_hit_point(
|
||||
const Ray&,
|
||||
const omath::Triangle<omath::Vector3<float>>&
|
||||
) noexcept; // precondition: can_trace_line(...) == true
|
||||
};
|
||||
|
||||
} // namespace omath::collision
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation hints (if you extend it)
|
||||
|
||||
* Expose a variant that returns **barycentric coordinates** `(u, v, w)` alongside the hit point to support texture lookup or edge tests.
|
||||
* Provide an overload returning `std::optional<Vector3<float>>` (or `expected`) for safer one-shot queries without a separate test call.
|
||||
* If you need backface culling, add a flag or dedicated function (reject hits where the signed distance is negative with respect to triangle normal).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Plane Documentation](../3d_primitives/plane.md) - Ray-plane intersection
|
||||
- [Box Documentation](../3d_primitives/box.md) - AABB collision detection
|
||||
- [Triangle Documentation](../linear_algebra/triangle.md) - Triangle primitives
|
||||
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
|
||||
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
108
docs/engines/frostbite/camera_trait.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# `omath::frostbite_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||
|
||||
> Header: `omath/engines/frostbite_engine/traits/camera_trait.hpp` • Impl: `omath/engines/frostbite_engine/traits/camera_trait.cpp`
|
||||
> Namespace: `omath::frostbite_engine`
|
||||
> Purpose: provide Frostbite-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`CameraTrait` exposes three `static` functions:
|
||||
|
||||
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `-asin(dir.y)` and **yaw** as `atan2(dir.x, dir.z)`; **roll** is `0`. Pitch/yaw are returned using the project’s strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||
* `calc_view_matrix(angles, origin)` – delegates to Frostbite formulas `frostbite_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||
|
||||
The trait’s types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Frostbite engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::frostbite_engine {
|
||||
|
||||
class CameraTrait final {
|
||||
public:
|
||||
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||
static ViewAngles
|
||||
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||
const Vector3<float>& look_at) noexcept;
|
||||
|
||||
// Build view matrix for given angles and origin.
|
||||
static Mat4X4
|
||||
calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||
static Mat4X4
|
||||
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||
const projection::ViewPort& view_port,
|
||||
float near, float far) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::frostbite_engine
|
||||
```
|
||||
|
||||
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||
|
||||
---
|
||||
|
||||
## Behavior & conventions
|
||||
|
||||
* **Angles from look-at**:
|
||||
|
||||
```
|
||||
dir = normalize(look_at - origin)
|
||||
pitch = -asin(dir.y) // +Y is up
|
||||
yaw = atan2(dir.x, dir.z)
|
||||
roll = 0
|
||||
```
|
||||
|
||||
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||
|
||||
* **View matrix**: built by the Frostbite engine helper `frostbite_engine::calc_view_matrix(angles, origin)` to match the engine’s handedness and axis conventions.
|
||||
|
||||
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||
|
||||
---
|
||||
|
||||
## Using with `projection::Camera`
|
||||
|
||||
Create a camera whose math is driven by this trait:
|
||||
|
||||
```cpp
|
||||
using Mat4 = Mat4X4; // from Frostbite math headers
|
||||
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||
using FBcam = omath::projection::Camera<Mat4, Angs, omath::frostbite_engine::CameraTrait>;
|
||||
|
||||
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||
auto fov = omath::projection::FieldOfView::from_degrees(70.f);
|
||||
|
||||
FBcam cam(
|
||||
/*position*/ {0.f, 1.7f, -3.f},
|
||||
/*angles*/ omath::frostbite_engine::CameraTrait::calc_look_at_angle({0,1.7f,-3},{0,1.7f,0}),
|
||||
/*viewport*/ vp,
|
||||
/*fov*/ fov,
|
||||
/*near*/ 0.1f,
|
||||
/*far*/ 1000.f
|
||||
);
|
||||
```
|
||||
|
||||
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||
|
||||
---
|
||||
|
||||
## Notes & tips
|
||||
|
||||
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project’s angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* Frostbite math helpers in `omath/engines/frostbite_engine/formulas.hpp` (view/projection builders used above).
|
||||
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||
59
docs/engines/frostbite/constants.md
Normal file
@@ -0,0 +1,59 @@
|
||||
Nice! A clean little “types + constants” header. A few quick fixes and polish:
|
||||
|
||||
## Issues / suggestions
|
||||
|
||||
1. **Mat3X3 alias is wrong**
|
||||
|
||||
* You wrote `using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;`
|
||||
* That should be `Mat<3, 3, ...>`.
|
||||
|
||||
2. **`constexpr` globals in a header → make them `inline constexpr`**
|
||||
|
||||
* Since this is in a header included by multiple TUs, use `inline constexpr` to avoid ODR/link issues (C++17+).
|
||||
|
||||
3. **Consider column-major vs row-major**
|
||||
|
||||
* Most game/graphics stacks (GLSL/HLSL, many engines) lean column-major and column vectors. If the rest of your math lib or shaders assume column-major, align these typedefs now to avoid silent transposes later. If row-major is intentional, all good—just be consistent.
|
||||
|
||||
4. **Naming consistency**
|
||||
|
||||
* If you prefer `k_` prefix, keep it; otherwise consider `kAbsUp`/`ABS_UP` to match your codebase’s conventions.
|
||||
|
||||
5. **`Mat1X3` as a “row vector”**
|
||||
|
||||
* If you actually use it as a 3-component vector, consider just `Vector3<float>` (clearer) and reserve `Mat1X3` for real row-vector math.
|
||||
|
||||
---
|
||||
|
||||
## Tidied version
|
||||
|
||||
```cpp
|
||||
// Created by Vlad on 10/21/2025.
|
||||
#pragma once
|
||||
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <omath/trigonometry/angle.hpp>
|
||||
#include <omath/trigonometry/view_angles.hpp>
|
||||
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
// Inline to avoid ODR across translation units
|
||||
inline constexpr Vector3<float> k_abs_up = {0.0f, 1.0f, 0.0f};
|
||||
inline constexpr Vector3<float> k_abs_right = {1.0f, 0.0f, 0.0f};
|
||||
inline constexpr Vector3<float> k_abs_forward = {0.0f, 0.0f, 1.0f};
|
||||
|
||||
// NOTE: verify row/column major matches the rest of your engine
|
||||
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat3X3 = Mat<3, 3, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||
|
||||
using PitchAngle = Angle<float, -90.0f, 90.0f, AngleFlags::Clamped >;
|
||||
using YawAngle = Angle<float,-180.0f, 180.0f, AngleFlags::Normalized>;
|
||||
using RollAngle = Angle<float,-180.0f, 180.0f, AngleFlags::Normalized>;
|
||||
|
||||
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||
} // namespace omath::frostbite_engine
|
||||
```
|
||||
|
||||
If you share how your matrices multiply vectors (row vs column) and your world handedness, I can double-check the axis constants and angle normalization to make sure yaw/pitch signs line up with your camera and `atan2` usage.
|
||||
0
docs/engines/frostbite/formulas.md
Normal file
105
docs/engines/frostbite/pred_engine_trait.md
Normal file
@@ -0,0 +1,105 @@
|
||||
// Created by Vlad on 8/6/2025.
|
||||
#pragma once
|
||||
|
||||
#include <cmath> // sqrt, hypot, tan, asin, atan2
|
||||
#include <optional>
|
||||
|
||||
#include "omath/engines/frostbite_engine/formulas.hpp"
|
||||
#include "omath/projectile_prediction/projectile.hpp"
|
||||
#include "omath/projectile_prediction/target.hpp"
|
||||
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
class PredEngineTrait final
|
||||
{
|
||||
public:
|
||||
// Predict projectile position given launch angles (degrees), time (s), and world gravity (m/s^2).
|
||||
// Note: kept runtime function; remove constexpr to avoid CTAD surprises across toolchains.
|
||||
static Vector3<float> predict_projectile_position(
|
||||
const projectile_prediction::Projectile& projectile,
|
||||
float pitch_deg, float yaw_deg,
|
||||
float time, float gravity) noexcept
|
||||
{
|
||||
// Engine convention: negative pitch looks up (your original used -pitch).
|
||||
const auto fwd = forward_vector({
|
||||
PitchAngle::from_degrees(-pitch_deg),
|
||||
YawAngle::from_degrees(yaw_deg),
|
||||
RollAngle::from_degrees(0.0f)
|
||||
});
|
||||
|
||||
Vector3<float> pos =
|
||||
projectile.m_origin +
|
||||
fwd * (projectile.m_launch_speed * time);
|
||||
|
||||
// s = 1/2 a t^2 downward
|
||||
pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
|
||||
return pos;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static Vector3<float> predict_target_position(
|
||||
const projectile_prediction::Target& target,
|
||||
float time, float gravity) noexcept
|
||||
{
|
||||
Vector3<float> predicted = target.m_origin + target.m_velocity * time;
|
||||
|
||||
if (target.m_is_airborne) {
|
||||
// If targets also have a gravity scale in your model, multiply here.
|
||||
predicted.y -= gravity * (time * time) * 0.5f;
|
||||
}
|
||||
return predicted;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static float calc_vector_2d_distance(const Vector3<float>& delta) noexcept
|
||||
{
|
||||
// More stable than sqrt(x*x + z*z)
|
||||
return std::hypot(delta.x, delta.z);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
|
||||
{
|
||||
return vec.y;
|
||||
}
|
||||
|
||||
// Computes a viewpoint above the predicted target, using an optional projectile pitch.
|
||||
// If pitch is absent, we leave Y unchanged (or you can choose a sensible default).
|
||||
[[nodiscard]]
|
||||
static Vector3<float> calc_viewpoint_from_angles(
|
||||
const projectile_prediction::Projectile& projectile,
|
||||
const Vector3<float>& predicted_target_position,
|
||||
const std::optional<float> projectile_pitch_deg) noexcept
|
||||
{
|
||||
// Lateral separation from projectile to target (X/Z plane).
|
||||
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
|
||||
|
||||
float y = predicted_target_position.y;
|
||||
if (projectile_pitch_deg.has_value()) {
|
||||
const float pitch_rad = angles::degrees_to_radians(*projectile_pitch_deg);
|
||||
const float height = delta2d * std::tan(pitch_rad);
|
||||
y += height;
|
||||
}
|
||||
|
||||
// Use the target's Z, not the projectile's Z (likely bugfix).
|
||||
return { predicted_target_position.x, y, predicted_target_position.z };
|
||||
}
|
||||
|
||||
// Due to maybe_calculate_projectile_launch_pitch_angle spec: +89° up, -89° down.
|
||||
[[nodiscard]]
|
||||
static float calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept
|
||||
{
|
||||
const auto direction = (view_to - origin).normalized();
|
||||
return angles::radians_to_degrees(std::asin(direction.y));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static float calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept
|
||||
{
|
||||
const auto direction = (view_to - origin).normalized();
|
||||
return angles::radians_to_degrees(std::atan2(direction.x, direction.z));
|
||||
}
|
||||
};
|
||||
} // namespace omath::frostbite_engine
|
||||
109
docs/engines/iw_engine/camera_trait.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# `omath::iw_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||
|
||||
> Header: `omath/engines/iw_engine/traits/camera_trait.hpp` • Impl: `omath/engines/iw_engine/traits/camera_trait.cpp`
|
||||
> Namespace: `omath::iw_engine`
|
||||
> Purpose: provide IW Engine (Call of Duty)-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`CameraTrait` exposes three `static` functions:
|
||||
|
||||
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||
* `calc_view_matrix(angles, origin)` – delegates to IW Engine formulas `iw_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||
|
||||
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the IW Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::iw_engine {
|
||||
|
||||
class CameraTrait final {
|
||||
public:
|
||||
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||
static ViewAngles
|
||||
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||
const Vector3<float>& look_at) noexcept;
|
||||
|
||||
// Build view matrix for given angles and origin.
|
||||
static Mat4X4
|
||||
calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||
static Mat4X4
|
||||
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||
const projection::ViewPort& view_port,
|
||||
float near, float far) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::iw_engine
|
||||
```
|
||||
|
||||
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||
|
||||
---
|
||||
|
||||
## Behavior & conventions
|
||||
|
||||
* **Angles from look-at** (Z-up coordinate system):
|
||||
|
||||
```
|
||||
dir = normalize(look_at - origin)
|
||||
pitch = asin(dir.z) // +Z is up
|
||||
yaw = atan2(dir.y, dir.x) // horizontal rotation
|
||||
roll = 0
|
||||
```
|
||||
|
||||
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||
|
||||
* **View matrix**: built by the IW Engine helper `iw_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
|
||||
|
||||
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||
|
||||
---
|
||||
|
||||
## Using with `projection::Camera`
|
||||
|
||||
Create a camera whose math is driven by this trait:
|
||||
|
||||
```cpp
|
||||
using Mat4 = Mat4X4; // from IW Engine math headers
|
||||
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||
using IWcam = omath::projection::Camera<Mat4, Angs, omath::iw_engine::CameraTrait>;
|
||||
|
||||
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||
auto fov = omath::projection::FieldOfView::from_degrees(65.f);
|
||||
|
||||
IWcam cam(
|
||||
/*position*/ {500.f, 200.f, 100.f},
|
||||
/*angles*/ omath::iw_engine::CameraTrait::calc_look_at_angle({500,200,100},{0,0,100}),
|
||||
/*viewport*/ vp,
|
||||
/*fov*/ fov,
|
||||
/*near*/ 0.1f,
|
||||
/*far*/ 5000.f
|
||||
);
|
||||
```
|
||||
|
||||
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||
|
||||
---
|
||||
|
||||
## Notes & tips
|
||||
|
||||
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||
* IW Engine uses **Z-up**: pitch angles control vertical look, positive = up.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* IW Engine math helpers in `omath/engines/iw_engine/formulas.hpp` (view/projection builders used above).
|
||||
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||
77
docs/engines/iw_engine/constants.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# `omath::iw_engine` — types & constants
|
||||
|
||||
> Header: `omath/engines/iw_engine/constants.hpp`
|
||||
> Namespace: `omath::iw_engine`
|
||||
> Purpose: define IW Engine (Call of Duty) coordinate system, matrix types, and angle ranges
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The **IW Engine** (Infinity Ward Engine, used in Call of Duty series) uses a **Z-up, right-handed** coordinate system:
|
||||
|
||||
* **Up** = `{0, 0, 1}` (Z-axis)
|
||||
* **Right** = `{0, -1, 0}` (negative Y-axis)
|
||||
* **Forward** = `{1, 0, 0}` (X-axis)
|
||||
|
||||
Matrices are **row-major**. Angles are **clamped pitch** (±89°) and **normalized yaw/roll** (±180°).
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```cpp
|
||||
namespace omath::iw_engine {
|
||||
constexpr Vector3<float> k_abs_up = {0, 0, 1};
|
||||
constexpr Vector3<float> k_abs_right = {0, -1, 0};
|
||||
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
|
||||
}
|
||||
```
|
||||
|
||||
These basis vectors define the engine's **world coordinate frame**.
|
||||
|
||||
---
|
||||
|
||||
## Matrix types
|
||||
|
||||
```cpp
|
||||
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||
```
|
||||
|
||||
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
|
||||
|
||||
---
|
||||
|
||||
## Angle types
|
||||
|
||||
```cpp
|
||||
using PitchAngle = Angle<float, -89.f, 89.f, AngleFlags::Clamped>;
|
||||
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
|
||||
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||
```
|
||||
|
||||
* **PitchAngle**: clamped to **[-89°, +89°]** (looking down vs. up)
|
||||
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||
|
||||
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate system notes
|
||||
|
||||
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
|
||||
* **Right-handed**: cross product `forward × right = up` holds
|
||||
* This matches **IW Engine** (Call of Duty series: Modern Warfare, Black Ops, etc.) conventions
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/iw_engine/formulas.hpp` — view/projection matrix builders
|
||||
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||
135
docs/engines/iw_engine/formulas.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# `omath::iw_engine` — formulas & matrix helpers
|
||||
|
||||
> Header: `omath/engines/iw_engine/formulas.hpp`
|
||||
> Namespace: `omath::iw_engine`
|
||||
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for IW Engine (Call of Duty)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This header provides **IW Engine**-specific math for:
|
||||
|
||||
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||
* **Rotation matrices** from Euler angles
|
||||
* **View matrices** (camera transforms)
|
||||
* **Perspective projection** matrices
|
||||
|
||||
All functions respect IW Engine's **Z-up, right-handed** coordinate system.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::iw_engine {
|
||||
|
||||
// Compute forward direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute right direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute up direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build 3x3 rotation matrix from angles
|
||||
[[nodiscard]]
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build view matrix (camera space transform)
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection matrix
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||
float aspect_ratio,
|
||||
float near, float far) noexcept;
|
||||
|
||||
} // namespace omath::iw_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Direction vectors
|
||||
|
||||
Given camera angles (pitch/yaw/roll):
|
||||
|
||||
* `forward_vector(angles)` → unit vector pointing where the camera looks
|
||||
* `right_vector(angles)` → unit vector pointing to the camera's right
|
||||
* `up_vector(angles)` → unit vector pointing upward relative to the camera
|
||||
|
||||
These are used for movement, aim direction, and building coordinate frames.
|
||||
|
||||
---
|
||||
|
||||
## Rotation & view matrices
|
||||
|
||||
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
|
||||
* `calc_view_matrix(angles, origin)` → camera view matrix
|
||||
|
||||
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||
|
||||
---
|
||||
|
||||
## Perspective projection
|
||||
|
||||
```cpp
|
||||
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||
fov_degrees, // vertical field of view (e.g., 65)
|
||||
aspect_ratio, // width / height (e.g., 16/9)
|
||||
near_plane, // e.g., 0.1
|
||||
far_plane // e.g., 5000.0
|
||||
);
|
||||
```
|
||||
|
||||
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::iw_engine;
|
||||
|
||||
// Camera setup
|
||||
ViewAngles angles = {
|
||||
PitchAngle::from_degrees(-10.0f),
|
||||
YawAngle::from_degrees(90.0f),
|
||||
RollAngle::from_degrees(0.0f)
|
||||
};
|
||||
Vector3<float> cam_pos{500.0f, 200.0f, 100.0f};
|
||||
|
||||
// Compute direction
|
||||
auto forward = forward_vector(angles);
|
||||
auto right = right_vector(angles);
|
||||
auto up = up_vector(angles);
|
||||
|
||||
// Build matrices
|
||||
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||
auto proj_mat = calc_perspective_projection_matrix(65.0f, 16.0f/9.0f, 0.1f, 5000.0f);
|
||||
|
||||
// Use view_mat and proj_mat for rendering...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||
* **Pitch**: positive = looking up, negative = looking down
|
||||
* **Yaw**: increases counter-clockwise from the +X axis
|
||||
* **Coordinate system**: Z-up, X-forward, Y-right (negative in code convention)
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/iw_engine/constants.hpp` — coordinate frame & angle types
|
||||
* `omath/engines/iw_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||
198
docs/engines/iw_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# `omath::iw_engine::PredEngineTrait` — projectile prediction trait
|
||||
|
||||
> Header: `omath/engines/iw_engine/traits/pred_engine_trait.hpp`
|
||||
> Namespace: `omath::iw_engine`
|
||||
> Purpose: provide IW Engine (Call of Duty)-specific projectile and target prediction for ballistic calculations
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||
|
||||
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||
* `calc_vector_2d_distance` – horizontal distance (X/Y plane, ignoring Z)
|
||||
* `get_vector_height_coordinate` – extracts vertical coordinate (Z in IW Engine)
|
||||
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||
|
||||
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::iw_engine {
|
||||
|
||||
class PredEngineTrait final {
|
||||
public:
|
||||
// Predict projectile position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||
float pitch, float yaw, float time,
|
||||
float gravity) noexcept;
|
||||
|
||||
// Predict target position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_target_position(const projectile_prediction::Target& target,
|
||||
float time, float gravity) noexcept;
|
||||
|
||||
// Compute horizontal (2D) distance
|
||||
static float
|
||||
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||
|
||||
// Get vertical coordinate (Z in IW Engine)
|
||||
static constexpr float
|
||||
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||
|
||||
// Compute aim point from angles
|
||||
static Vector3<float>
|
||||
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||
Vector3<float> predicted_target_position,
|
||||
std::optional<float> projectile_pitch) noexcept;
|
||||
|
||||
// Compute pitch angle to look at target
|
||||
static float
|
||||
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
|
||||
// Compute yaw angle to look at target
|
||||
static float
|
||||
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::iw_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projectile prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_projectile_position(
|
||||
projectile, // initial position, speed, gravity scale
|
||||
pitch_deg, // launch pitch (positive = up)
|
||||
yaw_deg, // launch yaw
|
||||
time, // time in seconds
|
||||
gravity // gravity constant (e.g., 800 units/s²)
|
||||
);
|
||||
```
|
||||
|
||||
Computes:
|
||||
|
||||
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||
2. Initial velocity: `forward * launch_speed`
|
||||
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Z component only)
|
||||
|
||||
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||
|
||||
---
|
||||
|
||||
## Target prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_target_position(
|
||||
target, // position, velocity, airborne flag
|
||||
time, // time in seconds
|
||||
gravity // gravity constant
|
||||
);
|
||||
```
|
||||
|
||||
Simple linear extrapolation plus gravity if target is airborne:
|
||||
|
||||
```
|
||||
predicted = origin + velocity * time
|
||||
if (airborne)
|
||||
predicted.z -= 0.5 * gravity * time²
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Distance & height helpers
|
||||
|
||||
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.y²)` (horizontal distance)
|
||||
* `get_vector_height_coordinate(vec)` → `vec.z` (vertical coordinate in IW Engine)
|
||||
|
||||
Used to compute ballistic arc parameters.
|
||||
|
||||
---
|
||||
|
||||
## Aim angle calculation
|
||||
|
||||
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||
- Formula: `asin(Δz / distance)` converted to degrees
|
||||
- Positive = looking up, negative = looking down
|
||||
|
||||
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||
- Formula: `atan2(Δy, Δx)` converted to degrees
|
||||
- Horizontal rotation around Z-axis
|
||||
|
||||
---
|
||||
|
||||
## Viewpoint from angles
|
||||
|
||||
```cpp
|
||||
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||
projectile,
|
||||
predicted_target_pos,
|
||||
optional_pitch_deg
|
||||
);
|
||||
```
|
||||
|
||||
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Coordinate system**: Z-up (height increases with Z)
|
||||
* **Angles**: pitch in [-89°, +89°], yaw in [-180°, +180°]
|
||||
* **Gravity**: applied downward along -Z axis
|
||||
* **Pitch convention**: +89° = straight up, -89° = straight down
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::iw_engine;
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
Projectile proj{
|
||||
.m_origin = {0, 0, 100},
|
||||
.m_launch_speed = 1200.0f,
|
||||
.m_gravity_scale = 1.0f
|
||||
};
|
||||
|
||||
Target tgt{
|
||||
.m_origin = {800, 300, 100},
|
||||
.m_velocity = {15, 8, 0},
|
||||
.m_is_airborne = false
|
||||
};
|
||||
|
||||
float gravity = 800.0f;
|
||||
float time = 0.5f;
|
||||
|
||||
// Predict where target will be
|
||||
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||
|
||||
// Compute aim angles
|
||||
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||
|
||||
// Predict projectile position with those angles
|
||||
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/iw_engine/formulas.hpp` — direction vectors and matrix builders
|
||||
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||
110
docs/engines/opengl_engine/camera_trait.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# `omath::opengl_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||
|
||||
> Header: `omath/engines/opengl_engine/traits/camera_trait.hpp` • Impl: `omath/engines/opengl_engine/traits/camera_trait.cpp`
|
||||
> Namespace: `omath::opengl_engine`
|
||||
> Purpose: provide OpenGL-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`CameraTrait` exposes three `static` functions:
|
||||
|
||||
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.y)` and **yaw** as `-atan2(dir.x, -dir.z)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||
* `calc_view_matrix(angles, origin)` – delegates to OpenGL formulas `opengl_engine::calc_view_matrix`, producing a `Mat4X4` view matrix (column-major) for the given angles and origin.
|
||||
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||
|
||||
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the OpenGL math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::opengl_engine {
|
||||
|
||||
class CameraTrait final {
|
||||
public:
|
||||
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||
static ViewAngles
|
||||
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||
const Vector3<float>& look_at) noexcept;
|
||||
|
||||
// Build view matrix for given angles and origin (column-major).
|
||||
static Mat4X4
|
||||
calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection from FOV (deg), viewport, near/far (column-major).
|
||||
static Mat4X4
|
||||
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||
const projection::ViewPort& view_port,
|
||||
float near, float far) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::opengl_engine
|
||||
```
|
||||
|
||||
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||
|
||||
---
|
||||
|
||||
## Behavior & conventions
|
||||
|
||||
* **Angles from look-at** (Y-up, -Z forward coordinate system):
|
||||
|
||||
```
|
||||
dir = normalize(look_at - origin)
|
||||
pitch = asin(dir.y) // +Y is up
|
||||
yaw = -atan2(dir.x, -dir.z) // horizontal rotation
|
||||
roll = 0
|
||||
```
|
||||
|
||||
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||
|
||||
* **View matrix**: built by the OpenGL helper `opengl_engine::calc_view_matrix(angles, origin)` to match OpenGL's right-handed, Y-up, -Z forward conventions. Matrix is **column-major**.
|
||||
|
||||
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix (column-major).
|
||||
|
||||
---
|
||||
|
||||
## Using with `projection::Camera`
|
||||
|
||||
Create a camera whose math is driven by this trait:
|
||||
|
||||
```cpp
|
||||
using Mat4 = Mat4X4; // from OpenGL math headers (column-major)
|
||||
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||
using GLcam = omath::projection::Camera<Mat4, Angs, omath::opengl_engine::CameraTrait>;
|
||||
|
||||
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||
auto fov = omath::projection::FieldOfView::from_degrees(45.f);
|
||||
|
||||
GLcam cam(
|
||||
/*position*/ {5.f, 3.f, 5.f},
|
||||
/*angles*/ omath::opengl_engine::CameraTrait::calc_look_at_angle({5,3,5},{0,0,0}),
|
||||
/*viewport*/ vp,
|
||||
/*fov*/ fov,
|
||||
/*near*/ 0.1f,
|
||||
/*far*/ 100.f
|
||||
);
|
||||
```
|
||||
|
||||
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||
|
||||
---
|
||||
|
||||
## Notes & tips
|
||||
|
||||
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||
* OpenGL uses **Y-up, -Z forward**: pitch angles control vertical look (positive = up), yaw controls horizontal rotation.
|
||||
* Matrices are **column-major** (no transpose needed for OpenGL shaders).
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* OpenGL math helpers in `omath/engines/opengl_engine/formulas.hpp` (view/projection builders used above).
|
||||
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||
78
docs/engines/opengl_engine/constants.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# `omath::opengl_engine` — types & constants
|
||||
|
||||
> Header: `omath/engines/opengl_engine/constants.hpp`
|
||||
> Namespace: `omath::opengl_engine`
|
||||
> Purpose: define OpenGL coordinate system, matrix types, and angle ranges
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The **OpenGL Engine** uses a **Y-up, right-handed** coordinate system:
|
||||
|
||||
* **Up** = `{0, 1, 0}` (Y-axis)
|
||||
* **Right** = `{1, 0, 0}` (X-axis)
|
||||
* **Forward** = `{0, 0, -1}` (negative Z-axis)
|
||||
|
||||
Matrices are **column-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```cpp
|
||||
namespace omath::opengl_engine {
|
||||
constexpr Vector3<float> k_abs_up = {0, 1, 0};
|
||||
constexpr Vector3<float> k_abs_right = {1, 0, 0};
|
||||
constexpr Vector3<float> k_abs_forward = {0, 0, -1};
|
||||
}
|
||||
```
|
||||
|
||||
These basis vectors define the engine's **world coordinate frame**.
|
||||
|
||||
---
|
||||
|
||||
## Matrix types
|
||||
|
||||
```cpp
|
||||
using Mat4X4 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>;
|
||||
using Mat3X3 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>;
|
||||
using Mat1X3 = Mat<1, 3, float, MatStoreType::COLUMN_MAJOR>;
|
||||
```
|
||||
|
||||
**Column-major** storage means columns are contiguous in memory. This matches OpenGL's native matrix layout and shader expectations (GLSL).
|
||||
|
||||
---
|
||||
|
||||
## Angle types
|
||||
|
||||
```cpp
|
||||
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
|
||||
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
|
||||
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||
```
|
||||
|
||||
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
|
||||
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||
|
||||
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate system notes
|
||||
|
||||
* **Y-up**: gravity points along `-Y`, height increases with `+Y`
|
||||
* **Right-handed**: cross product `right × up = forward` (forward is `-Z`)
|
||||
* **Forward = -Z**: the camera looks down the negative Z-axis (OpenGL convention)
|
||||
* This matches **OpenGL** conventions for 3D graphics pipelines
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/opengl_engine/formulas.hpp` — view/projection matrix builders
|
||||
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||
140
docs/engines/opengl_engine/formulas.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# `omath::opengl_engine` — formulas & matrix helpers
|
||||
|
||||
> Header: `omath/engines/opengl_engine/formulas.hpp`
|
||||
> Namespace: `omath::opengl_engine`
|
||||
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for OpenGL
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This header provides **OpenGL**-specific math for:
|
||||
|
||||
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||
* **Rotation matrices** from Euler angles
|
||||
* **View matrices** (camera transforms)
|
||||
* **Perspective projection** matrices
|
||||
|
||||
All functions respect OpenGL's **Y-up, right-handed** coordinate system with **forward = -Z**.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::opengl_engine {
|
||||
|
||||
// Compute forward direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute right direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute up direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build 3x3 rotation matrix from angles
|
||||
[[nodiscard]]
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build view matrix (camera space transform)
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection matrix
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||
float aspect_ratio,
|
||||
float near, float far) noexcept;
|
||||
|
||||
} // namespace omath::opengl_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Direction vectors
|
||||
|
||||
Given camera angles (pitch/yaw/roll):
|
||||
|
||||
* `forward_vector(angles)` → unit vector pointing where the camera looks (typically `-Z` direction)
|
||||
* `right_vector(angles)` → unit vector pointing to the camera's right (`+X` direction)
|
||||
* `up_vector(angles)` → unit vector pointing upward relative to the camera (`+Y` direction)
|
||||
|
||||
These are used for movement, aim direction, and building coordinate frames.
|
||||
|
||||
---
|
||||
|
||||
## Rotation & view matrices
|
||||
|
||||
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles (column-major)
|
||||
* `calc_view_matrix(angles, origin)` → camera view matrix (column-major)
|
||||
|
||||
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||
|
||||
**Note**: Matrices are **column-major** to match OpenGL/GLSL conventions. No transpose needed when uploading to shaders.
|
||||
|
||||
---
|
||||
|
||||
## Perspective projection
|
||||
|
||||
```cpp
|
||||
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||
fov_degrees, // vertical field of view (e.g., 45)
|
||||
aspect_ratio, // width / height (e.g., 16/9)
|
||||
near_plane, // e.g., 0.1
|
||||
far_plane // e.g., 100.0
|
||||
);
|
||||
```
|
||||
|
||||
Produces a **perspective projection matrix** suitable for OpenGL rendering. Combined with the view matrix, this implements the standard camera transform chain.
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::opengl_engine;
|
||||
|
||||
// Camera setup
|
||||
ViewAngles angles = {
|
||||
PitchAngle::from_degrees(-20.0f),
|
||||
YawAngle::from_degrees(135.0f),
|
||||
RollAngle::from_degrees(0.0f)
|
||||
};
|
||||
Vector3<float> cam_pos{5.0f, 3.0f, 5.0f};
|
||||
|
||||
// Compute direction
|
||||
auto forward = forward_vector(angles);
|
||||
auto right = right_vector(angles);
|
||||
auto up = up_vector(angles);
|
||||
|
||||
// Build matrices (column-major for OpenGL)
|
||||
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||
auto proj_mat = calc_perspective_projection_matrix(45.0f, 16.0f/9.0f, 0.1f, 100.0f);
|
||||
|
||||
// Upload to OpenGL shaders (no transpose needed)
|
||||
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view_mat.data());
|
||||
glUniformMatrix4fv(proj_loc, 1, GL_FALSE, proj_mat.data());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||
* **Pitch**: positive = looking up, negative = looking down
|
||||
* **Yaw**: increases counter-clockwise from the -Z axis
|
||||
* **Coordinate system**: Y-up, -Z-forward, X-right (right-handed)
|
||||
* **Matrix storage**: column-major (matches OpenGL/GLSL)
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/opengl_engine/constants.hpp` — coordinate frame & angle types
|
||||
* `omath/engines/opengl_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||
199
docs/engines/opengl_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# `omath::opengl_engine::PredEngineTrait` — projectile prediction trait
|
||||
|
||||
> Header: `omath/engines/opengl_engine/traits/pred_engine_trait.hpp`
|
||||
> Namespace: `omath::opengl_engine`
|
||||
> Purpose: provide OpenGL-specific projectile and target prediction for ballistic calculations
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||
|
||||
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||
* `calc_vector_2d_distance` – horizontal distance (X/Z plane, ignoring Y)
|
||||
* `get_vector_height_coordinate` – extracts vertical coordinate (Y in OpenGL)
|
||||
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||
|
||||
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::opengl_engine {
|
||||
|
||||
class PredEngineTrait final {
|
||||
public:
|
||||
// Predict projectile position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||
float pitch, float yaw, float time,
|
||||
float gravity) noexcept;
|
||||
|
||||
// Predict target position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_target_position(const projectile_prediction::Target& target,
|
||||
float time, float gravity) noexcept;
|
||||
|
||||
// Compute horizontal (2D) distance
|
||||
static float
|
||||
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||
|
||||
// Get vertical coordinate (Y in OpenGL)
|
||||
static constexpr float
|
||||
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||
|
||||
// Compute aim point from angles
|
||||
static Vector3<float>
|
||||
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||
Vector3<float> predicted_target_position,
|
||||
std::optional<float> projectile_pitch) noexcept;
|
||||
|
||||
// Compute pitch angle to look at target
|
||||
static float
|
||||
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
|
||||
// Compute yaw angle to look at target
|
||||
static float
|
||||
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::opengl_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projectile prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_projectile_position(
|
||||
projectile, // initial position, speed, gravity scale
|
||||
pitch_deg, // launch pitch (positive = up)
|
||||
yaw_deg, // launch yaw
|
||||
time, // time in seconds
|
||||
gravity // gravity constant (e.g., 9.81 m/s²)
|
||||
);
|
||||
```
|
||||
|
||||
Computes:
|
||||
|
||||
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||
2. Initial velocity: `forward * launch_speed`
|
||||
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component only)
|
||||
|
||||
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||
|
||||
---
|
||||
|
||||
## Target prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_target_position(
|
||||
target, // position, velocity, airborne flag
|
||||
time, // time in seconds
|
||||
gravity // gravity constant
|
||||
);
|
||||
```
|
||||
|
||||
Simple linear extrapolation plus gravity if target is airborne:
|
||||
|
||||
```
|
||||
predicted = origin + velocity * time
|
||||
if (airborne)
|
||||
predicted.y -= 0.5 * gravity * time²
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Distance & height helpers
|
||||
|
||||
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.z²)` (horizontal distance)
|
||||
* `get_vector_height_coordinate(vec)` → `vec.y` (vertical coordinate in OpenGL)
|
||||
|
||||
Used to compute ballistic arc parameters.
|
||||
|
||||
---
|
||||
|
||||
## Aim angle calculation
|
||||
|
||||
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||
- Formula: `asin(Δy / distance)` converted to degrees (direction normalized first)
|
||||
- Positive = looking up, negative = looking down
|
||||
|
||||
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||
- Formula: `-atan2(Δx, -Δz)` converted to degrees (direction normalized first)
|
||||
- Horizontal rotation around Y-axis (accounts for -Z forward convention)
|
||||
|
||||
---
|
||||
|
||||
## Viewpoint from angles
|
||||
|
||||
```cpp
|
||||
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||
projectile,
|
||||
predicted_target_pos,
|
||||
optional_pitch_deg
|
||||
);
|
||||
```
|
||||
|
||||
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset. Result has adjusted Y coordinate.
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Coordinate system**: Y-up, -Z forward (height increases with Y)
|
||||
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
|
||||
* **Gravity**: applied downward along -Y axis
|
||||
* **Pitch convention**: +90° = straight up, -90° = straight down
|
||||
* **Forward direction**: negative Z-axis
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::opengl_engine;
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
Projectile proj{
|
||||
.m_origin = {0, 2, 0},
|
||||
.m_launch_speed = 30.0f,
|
||||
.m_gravity_scale = 1.0f
|
||||
};
|
||||
|
||||
Target tgt{
|
||||
.m_origin = {10, 2, -15},
|
||||
.m_velocity = {0.5f, 0, -1.0f},
|
||||
.m_is_airborne = false
|
||||
};
|
||||
|
||||
float gravity = 9.81f;
|
||||
float time = 0.5f;
|
||||
|
||||
// Predict where target will be
|
||||
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||
|
||||
// Compute aim angles
|
||||
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||
|
||||
// Predict projectile position with those angles
|
||||
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/opengl_engine/formulas.hpp` — direction vectors and matrix builders
|
||||
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||
113
docs/engines/source_engine/camera_trait.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# `omath::source_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||
|
||||
> Header: `omath/engines/source_engine/traits/camera_trait.hpp` • Impl: `omath/engines/source_engine/traits/camera_trait.cpp`
|
||||
> Namespace: `omath::source_engine`
|
||||
> Purpose: provide Source Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`CameraTrait` exposes three `static` functions:
|
||||
|
||||
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||
* `calc_view_matrix(angles, origin)` – delegates to Source Engine formulas `source_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||
|
||||
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Source Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::source_engine {
|
||||
|
||||
class CameraTrait final {
|
||||
public:
|
||||
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||
static ViewAngles
|
||||
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||
const Vector3<float>& look_at) noexcept;
|
||||
|
||||
// Build view matrix for given angles and origin.
|
||||
static Mat4X4
|
||||
calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||
static Mat4X4
|
||||
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||
const projection::ViewPort& view_port,
|
||||
float near, float far) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::source_engine
|
||||
```
|
||||
|
||||
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||
|
||||
---
|
||||
|
||||
## Behavior & conventions
|
||||
|
||||
* **Angles from look-at** (Z-up coordinate system):
|
||||
|
||||
```
|
||||
dir = normalize(look_at - origin)
|
||||
pitch = asin(dir.z) // +Z is up
|
||||
yaw = atan2(dir.y, dir.x) // horizontal rotation
|
||||
roll = 0
|
||||
```
|
||||
|
||||
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||
|
||||
* **View matrix**: built by the Source Engine helper `source_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
|
||||
|
||||
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||
|
||||
---
|
||||
|
||||
## Using with `projection::Camera`
|
||||
|
||||
Create a camera whose math is driven by this trait:
|
||||
|
||||
```cpp
|
||||
using Mat4 = Mat4X4; // from Source Engine math headers
|
||||
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||
using SEcam = omath::projection::Camera<Mat4, Angs, omath::source_engine::CameraTrait>;
|
||||
|
||||
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||
auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
|
||||
SEcam cam(
|
||||
/*position*/ {100.f, 50.f, 80.f},
|
||||
/*angles*/ omath::source_engine::CameraTrait::calc_look_at_angle({100,50,80},{0,0,80}),
|
||||
/*viewport*/ vp,
|
||||
/*fov*/ fov,
|
||||
/*near*/ 0.1f,
|
||||
/*far*/ 1000.f
|
||||
);
|
||||
```
|
||||
|
||||
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||
|
||||
---
|
||||
|
||||
## Notes & tips
|
||||
|
||||
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||
* Source Engine uses **Z-up**: pitch angles control vertical look, positive = up.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* [Source Engine Formulas](formulas.md) - View/projection matrix builders
|
||||
* [Source Engine Constants](constants.md) - Engine-specific constants
|
||||
* [Source Engine Pred Engine Trait](pred_engine_trait.md) - Projectile prediction for Source Engine
|
||||
* [Generic Camera Documentation](../../projection/camera.md) - Camera base class
|
||||
* [Getting Started Guide](../../getting_started.md) - Quick start with OMath
|
||||
* [Tutorials - World-to-Screen](../../tutorials.md#tutorial-2-world-to-screen-projection) - Projection tutorial
|
||||
77
docs/engines/source_engine/constants.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# `omath::source_engine` — types & constants
|
||||
|
||||
> Header: `omath/engines/source_engine/constants.hpp`
|
||||
> Namespace: `omath::source_engine`
|
||||
> Purpose: define Source Engine coordinate system, matrix types, and angle ranges
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The **Source Engine** uses a **Z-up, right-handed** coordinate system:
|
||||
|
||||
* **Up** = `{0, 0, 1}` (Z-axis)
|
||||
* **Right** = `{0, -1, 0}` (negative Y-axis)
|
||||
* **Forward** = `{1, 0, 0}` (X-axis)
|
||||
|
||||
Matrices are **row-major**. Angles are **clamped pitch** (±89°) and **normalized yaw/roll** (±180°).
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```cpp
|
||||
namespace omath::source_engine {
|
||||
constexpr Vector3<float> k_abs_up = {0, 0, 1};
|
||||
constexpr Vector3<float> k_abs_right = {0, -1, 0};
|
||||
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
|
||||
}
|
||||
```
|
||||
|
||||
These basis vectors define the engine's **world coordinate frame**.
|
||||
|
||||
---
|
||||
|
||||
## Matrix types
|
||||
|
||||
```cpp
|
||||
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||
```
|
||||
|
||||
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
|
||||
|
||||
---
|
||||
|
||||
## Angle types
|
||||
|
||||
```cpp
|
||||
using PitchAngle = Angle<float, -89.f, 89.f, AngleFlags::Clamped>;
|
||||
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
|
||||
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||
```
|
||||
|
||||
* **PitchAngle**: clamped to **[-89°, +89°]** (looking down vs. up)
|
||||
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||
|
||||
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate system notes
|
||||
|
||||
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
|
||||
* **Right-handed**: cross product `forward × right = up` holds
|
||||
* This matches **Source Engine** (Half-Life 2, TF2, CS:GO, etc.) conventions
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/source_engine/formulas.hpp` — view/projection matrix builders
|
||||
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||
135
docs/engines/source_engine/formulas.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# `omath::source_engine` — formulas & matrix helpers
|
||||
|
||||
> Header: `omath/engines/source_engine/formulas.hpp`
|
||||
> Namespace: `omath::source_engine`
|
||||
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Source Engine
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This header provides **Source Engine**-specific math for:
|
||||
|
||||
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||
* **Rotation matrices** from Euler angles
|
||||
* **View matrices** (camera transforms)
|
||||
* **Perspective projection** matrices
|
||||
|
||||
All functions respect Source Engine's **Z-up, right-handed** coordinate system.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::source_engine {
|
||||
|
||||
// Compute forward direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute right direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute up direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build 3x3 rotation matrix from angles
|
||||
[[nodiscard]]
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build view matrix (camera space transform)
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection matrix
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||
float aspect_ratio,
|
||||
float near, float far) noexcept;
|
||||
|
||||
} // namespace omath::source_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Direction vectors
|
||||
|
||||
Given camera angles (pitch/yaw/roll):
|
||||
|
||||
* `forward_vector(angles)` → unit vector pointing where the camera looks
|
||||
* `right_vector(angles)` → unit vector pointing to the camera's right
|
||||
* `up_vector(angles)` → unit vector pointing upward relative to the camera
|
||||
|
||||
These are used for movement, aim direction, and building coordinate frames.
|
||||
|
||||
---
|
||||
|
||||
## Rotation & view matrices
|
||||
|
||||
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
|
||||
* `calc_view_matrix(angles, origin)` → camera view matrix
|
||||
|
||||
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||
|
||||
---
|
||||
|
||||
## Perspective projection
|
||||
|
||||
```cpp
|
||||
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||
fov_degrees, // vertical field of view (e.g., 90)
|
||||
aspect_ratio, // width / height (e.g., 16/9)
|
||||
near_plane, // e.g., 0.1
|
||||
far_plane // e.g., 1000.0
|
||||
);
|
||||
```
|
||||
|
||||
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// Camera setup
|
||||
ViewAngles angles = {
|
||||
PitchAngle::from_degrees(-15.0f),
|
||||
YawAngle::from_degrees(45.0f),
|
||||
RollAngle::from_degrees(0.0f)
|
||||
};
|
||||
Vector3<float> cam_pos{100.0f, 50.0f, 80.0f};
|
||||
|
||||
// Compute direction
|
||||
auto forward = forward_vector(angles);
|
||||
auto right = right_vector(angles);
|
||||
auto up = up_vector(angles);
|
||||
|
||||
// Build matrices
|
||||
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||
auto proj_mat = calc_perspective_projection_matrix(90.0f, 16.0f/9.0f, 0.1f, 1000.0f);
|
||||
|
||||
// Use view_mat and proj_mat for rendering...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||
* **Pitch**: positive = looking up, negative = looking down
|
||||
* **Yaw**: increases counter-clockwise from the +X axis
|
||||
* **Coordinate system**: Z-up, X-forward, Y-right (negative in code convention)
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/source_engine/constants.hpp` — coordinate frame & angle types
|
||||
* `omath/engines/source_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||
198
docs/engines/source_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# `omath::source_engine::PredEngineTrait` — projectile prediction trait
|
||||
|
||||
> Header: `omath/engines/source_engine/traits/pred_engine_trait.hpp`
|
||||
> Namespace: `omath::source_engine`
|
||||
> Purpose: provide Source Engine-specific projectile and target prediction for ballistic calculations
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||
|
||||
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||
* `calc_vector_2d_distance` – horizontal distance (X/Y plane, ignoring Z)
|
||||
* `get_vector_height_coordinate` – extracts vertical coordinate (Z in Source Engine)
|
||||
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||
|
||||
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::source_engine {
|
||||
|
||||
class PredEngineTrait final {
|
||||
public:
|
||||
// Predict projectile position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||
float pitch, float yaw, float time,
|
||||
float gravity) noexcept;
|
||||
|
||||
// Predict target position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_target_position(const projectile_prediction::Target& target,
|
||||
float time, float gravity) noexcept;
|
||||
|
||||
// Compute horizontal (2D) distance
|
||||
static float
|
||||
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||
|
||||
// Get vertical coordinate (Z in Source Engine)
|
||||
static constexpr float
|
||||
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||
|
||||
// Compute aim point from angles
|
||||
static Vector3<float>
|
||||
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||
Vector3<float> predicted_target_position,
|
||||
std::optional<float> projectile_pitch) noexcept;
|
||||
|
||||
// Compute pitch angle to look at target
|
||||
static float
|
||||
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
|
||||
// Compute yaw angle to look at target
|
||||
static float
|
||||
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::source_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projectile prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_projectile_position(
|
||||
projectile, // initial position, speed, gravity scale
|
||||
pitch_deg, // launch pitch (positive = up)
|
||||
yaw_deg, // launch yaw
|
||||
time, // time in seconds
|
||||
gravity // gravity constant (e.g., 800 units/s²)
|
||||
);
|
||||
```
|
||||
|
||||
Computes:
|
||||
|
||||
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||
2. Initial velocity: `forward * launch_speed`
|
||||
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Z component only)
|
||||
|
||||
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||
|
||||
---
|
||||
|
||||
## Target prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_target_position(
|
||||
target, // position, velocity, airborne flag
|
||||
time, // time in seconds
|
||||
gravity // gravity constant
|
||||
);
|
||||
```
|
||||
|
||||
Simple linear extrapolation plus gravity if target is airborne:
|
||||
|
||||
```
|
||||
predicted = origin + velocity * time
|
||||
if (airborne)
|
||||
predicted.z -= 0.5 * gravity * time²
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Distance & height helpers
|
||||
|
||||
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.y²)` (horizontal distance)
|
||||
* `get_vector_height_coordinate(vec)` → `vec.z` (vertical coordinate in Source Engine)
|
||||
|
||||
Used to compute ballistic arc parameters.
|
||||
|
||||
---
|
||||
|
||||
## Aim angle calculation
|
||||
|
||||
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||
- Formula: `asin(Δz / distance)` converted to degrees
|
||||
- Positive = looking up, negative = looking down
|
||||
|
||||
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||
- Formula: `atan2(Δy, Δx)` converted to degrees
|
||||
- Horizontal rotation around Z-axis
|
||||
|
||||
---
|
||||
|
||||
## Viewpoint from angles
|
||||
|
||||
```cpp
|
||||
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||
projectile,
|
||||
predicted_target_pos,
|
||||
optional_pitch_deg
|
||||
);
|
||||
```
|
||||
|
||||
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Coordinate system**: Z-up (height increases with Z)
|
||||
* **Angles**: pitch in [-89°, +89°], yaw in [-180°, +180°]
|
||||
* **Gravity**: applied downward along -Z axis
|
||||
* **Pitch convention**: +89° = straight up, -89° = straight down
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::source_engine;
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
Projectile proj{
|
||||
.m_origin = {0, 0, 100},
|
||||
.m_launch_speed = 1000.0f,
|
||||
.m_gravity_scale = 1.0f
|
||||
};
|
||||
|
||||
Target tgt{
|
||||
.m_origin = {500, 200, 100},
|
||||
.m_velocity = {10, 5, 0},
|
||||
.m_is_airborne = false
|
||||
};
|
||||
|
||||
float gravity = 800.0f;
|
||||
float time = 0.5f;
|
||||
|
||||
// Predict where target will be
|
||||
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||
|
||||
// Compute aim angles
|
||||
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||
|
||||
// Predict projectile position with those angles
|
||||
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/source_engine/formulas.hpp` — direction vectors and matrix builders
|
||||
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||
109
docs/engines/unity_engine/camera_trait.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# `omath::unity_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||
|
||||
> Header: `omath/engines/unity_engine/traits/camera_trait.hpp` • Impl: `omath/engines/unity_engine/traits/camera_trait.cpp`
|
||||
> Namespace: `omath::unity_engine`
|
||||
> Purpose: provide Unity Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`CameraTrait` exposes three `static` functions:
|
||||
|
||||
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.y)` and **yaw** as `atan2(dir.x, dir.z)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||
* `calc_view_matrix(angles, origin)` – delegates to Unity Engine formulas `unity_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||
|
||||
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Unity Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::unity_engine {
|
||||
|
||||
class CameraTrait final {
|
||||
public:
|
||||
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||
static ViewAngles
|
||||
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||
const Vector3<float>& look_at) noexcept;
|
||||
|
||||
// Build view matrix for given angles and origin.
|
||||
static Mat4X4
|
||||
calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||
static Mat4X4
|
||||
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||
const projection::ViewPort& view_port,
|
||||
float near, float far) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::unity_engine
|
||||
```
|
||||
|
||||
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||
|
||||
---
|
||||
|
||||
## Behavior & conventions
|
||||
|
||||
* **Angles from look-at** (Y-up coordinate system):
|
||||
|
||||
```
|
||||
dir = normalize(look_at - origin)
|
||||
pitch = asin(dir.y) // +Y is up
|
||||
yaw = atan2(dir.x, dir.z) // horizontal rotation
|
||||
roll = 0
|
||||
```
|
||||
|
||||
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||
|
||||
* **View matrix**: built by the Unity Engine helper `unity_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
|
||||
|
||||
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||
|
||||
---
|
||||
|
||||
## Using with `projection::Camera`
|
||||
|
||||
Create a camera whose math is driven by this trait:
|
||||
|
||||
```cpp
|
||||
using Mat4 = Mat4X4; // from Unity Engine math headers
|
||||
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||
using UEcam = omath::projection::Camera<Mat4, Angs, omath::unity_engine::CameraTrait>;
|
||||
|
||||
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||
auto fov = omath::projection::FieldOfView::from_degrees(60.f);
|
||||
|
||||
UEcam cam(
|
||||
/*position*/ {10.f, 5.f, -10.f},
|
||||
/*angles*/ omath::unity_engine::CameraTrait::calc_look_at_angle({10,5,-10},{0,5,0}),
|
||||
/*viewport*/ vp,
|
||||
/*fov*/ fov,
|
||||
/*near*/ 0.3f,
|
||||
/*far*/ 1000.f
|
||||
);
|
||||
```
|
||||
|
||||
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||
|
||||
---
|
||||
|
||||
## Notes & tips
|
||||
|
||||
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||
* Unity Engine uses **Y-up**: pitch angles control vertical look, positive = up.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* Unity Engine math helpers in `omath/engines/unity_engine/formulas.hpp` (view/projection builders used above).
|
||||
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||
77
docs/engines/unity_engine/constants.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# `omath::unity_engine` — types & constants
|
||||
|
||||
> Header: `omath/engines/unity_engine/constants.hpp`
|
||||
> Namespace: `omath::unity_engine`
|
||||
> Purpose: define Unity Engine coordinate system, matrix types, and angle ranges
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The **Unity Engine** uses a **Y-up, left-handed** coordinate system:
|
||||
|
||||
* **Up** = `{0, 1, 0}` (Y-axis)
|
||||
* **Right** = `{1, 0, 0}` (X-axis)
|
||||
* **Forward** = `{0, 0, 1}` (Z-axis)
|
||||
|
||||
Matrices are **row-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```cpp
|
||||
namespace omath::unity_engine {
|
||||
constexpr Vector3<float> k_abs_up = {0, 1, 0};
|
||||
constexpr Vector3<float> k_abs_right = {1, 0, 0};
|
||||
constexpr Vector3<float> k_abs_forward = {0, 0, 1};
|
||||
}
|
||||
```
|
||||
|
||||
These basis vectors define the engine's **world coordinate frame**.
|
||||
|
||||
---
|
||||
|
||||
## Matrix types
|
||||
|
||||
```cpp
|
||||
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||
```
|
||||
|
||||
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
|
||||
|
||||
---
|
||||
|
||||
## Angle types
|
||||
|
||||
```cpp
|
||||
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
|
||||
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
|
||||
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||
```
|
||||
|
||||
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
|
||||
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||
|
||||
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate system notes
|
||||
|
||||
* **Y-up**: gravity points along `-Y`, height increases with `+Y`
|
||||
* **Left-handed**: cross product `forward × right = up` with left-hand rule
|
||||
* This matches **Unity Engine** conventions for 3D games and simulations
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/unity_engine/formulas.hpp` — view/projection matrix builders
|
||||
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||
135
docs/engines/unity_engine/formulas.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# `omath::unity_engine` — formulas & matrix helpers
|
||||
|
||||
> Header: `omath/engines/unity_engine/formulas.hpp`
|
||||
> Namespace: `omath::unity_engine`
|
||||
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Unity Engine
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This header provides **Unity Engine**-specific math for:
|
||||
|
||||
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||
* **Rotation matrices** from Euler angles
|
||||
* **View matrices** (camera transforms)
|
||||
* **Perspective projection** matrices
|
||||
|
||||
All functions respect Unity Engine's **Y-up, left-handed** coordinate system.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::unity_engine {
|
||||
|
||||
// Compute forward direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute right direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute up direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build 3x3 rotation matrix from angles
|
||||
[[nodiscard]]
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build view matrix (camera space transform)
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection matrix
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||
float aspect_ratio,
|
||||
float near, float far) noexcept;
|
||||
|
||||
} // namespace omath::unity_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Direction vectors
|
||||
|
||||
Given camera angles (pitch/yaw/roll):
|
||||
|
||||
* `forward_vector(angles)` → unit vector pointing where the camera looks
|
||||
* `right_vector(angles)` → unit vector pointing to the camera's right
|
||||
* `up_vector(angles)` → unit vector pointing upward relative to the camera
|
||||
|
||||
These are used for movement, aim direction, and building coordinate frames.
|
||||
|
||||
---
|
||||
|
||||
## Rotation & view matrices
|
||||
|
||||
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
|
||||
* `calc_view_matrix(angles, origin)` → camera view matrix
|
||||
|
||||
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||
|
||||
---
|
||||
|
||||
## Perspective projection
|
||||
|
||||
```cpp
|
||||
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||
fov_degrees, // vertical field of view (e.g., 60)
|
||||
aspect_ratio, // width / height (e.g., 16/9)
|
||||
near_plane, // e.g., 0.3
|
||||
far_plane // e.g., 1000.0
|
||||
);
|
||||
```
|
||||
|
||||
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::unity_engine;
|
||||
|
||||
// Camera setup
|
||||
ViewAngles angles = {
|
||||
PitchAngle::from_degrees(-15.0f),
|
||||
YawAngle::from_degrees(45.0f),
|
||||
RollAngle::from_degrees(0.0f)
|
||||
};
|
||||
Vector3<float> cam_pos{10.0f, 5.0f, -10.0f};
|
||||
|
||||
// Compute direction
|
||||
auto forward = forward_vector(angles);
|
||||
auto right = right_vector(angles);
|
||||
auto up = up_vector(angles);
|
||||
|
||||
// Build matrices
|
||||
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||
auto proj_mat = calc_perspective_projection_matrix(60.0f, 16.0f/9.0f, 0.3f, 1000.0f);
|
||||
|
||||
// Use view_mat and proj_mat for rendering...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||
* **Pitch**: positive = looking up, negative = looking down
|
||||
* **Yaw**: increases counter-clockwise from the +Z axis
|
||||
* **Coordinate system**: Y-up, Z-forward, X-right (left-handed)
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/unity_engine/constants.hpp` — coordinate frame & angle types
|
||||
* `omath/engines/unity_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||
198
docs/engines/unity_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# `omath::unity_engine::PredEngineTrait` — projectile prediction trait
|
||||
|
||||
> Header: `omath/engines/unity_engine/traits/pred_engine_trait.hpp`
|
||||
> Namespace: `omath::unity_engine`
|
||||
> Purpose: provide Unity Engine-specific projectile and target prediction for ballistic calculations
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||
|
||||
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||
* `calc_vector_2d_distance` – horizontal distance (X/Z plane, ignoring Y)
|
||||
* `get_vector_height_coordinate` – extracts vertical coordinate (Y in Unity Engine)
|
||||
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||
|
||||
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::unity_engine {
|
||||
|
||||
class PredEngineTrait final {
|
||||
public:
|
||||
// Predict projectile position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||
float pitch, float yaw, float time,
|
||||
float gravity) noexcept;
|
||||
|
||||
// Predict target position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_target_position(const projectile_prediction::Target& target,
|
||||
float time, float gravity) noexcept;
|
||||
|
||||
// Compute horizontal (2D) distance
|
||||
static float
|
||||
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||
|
||||
// Get vertical coordinate (Y in Unity Engine)
|
||||
static constexpr float
|
||||
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||
|
||||
// Compute aim point from angles
|
||||
static Vector3<float>
|
||||
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||
Vector3<float> predicted_target_position,
|
||||
std::optional<float> projectile_pitch) noexcept;
|
||||
|
||||
// Compute pitch angle to look at target
|
||||
static float
|
||||
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
|
||||
// Compute yaw angle to look at target
|
||||
static float
|
||||
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::unity_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projectile prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_projectile_position(
|
||||
projectile, // initial position, speed, gravity scale
|
||||
pitch_deg, // launch pitch (positive = up)
|
||||
yaw_deg, // launch yaw
|
||||
time, // time in seconds
|
||||
gravity // gravity constant (e.g., 9.81 m/s²)
|
||||
);
|
||||
```
|
||||
|
||||
Computes:
|
||||
|
||||
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||
2. Initial velocity: `forward * launch_speed`
|
||||
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component only)
|
||||
|
||||
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||
|
||||
---
|
||||
|
||||
## Target prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_target_position(
|
||||
target, // position, velocity, airborne flag
|
||||
time, // time in seconds
|
||||
gravity // gravity constant
|
||||
);
|
||||
```
|
||||
|
||||
Simple linear extrapolation plus gravity if target is airborne:
|
||||
|
||||
```
|
||||
predicted = origin + velocity * time
|
||||
if (airborne)
|
||||
predicted.y -= 0.5 * gravity * time²
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Distance & height helpers
|
||||
|
||||
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.z²)` (horizontal distance)
|
||||
* `get_vector_height_coordinate(vec)` → `vec.y` (vertical coordinate in Unity Engine)
|
||||
|
||||
Used to compute ballistic arc parameters.
|
||||
|
||||
---
|
||||
|
||||
## Aim angle calculation
|
||||
|
||||
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||
- Formula: `asin(Δy / distance)` converted to degrees (direction normalized first)
|
||||
- Positive = looking up, negative = looking down
|
||||
|
||||
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||
- Formula: `atan2(Δx, Δz)` converted to degrees (direction normalized first)
|
||||
- Horizontal rotation around Y-axis
|
||||
|
||||
---
|
||||
|
||||
## Viewpoint from angles
|
||||
|
||||
```cpp
|
||||
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||
projectile,
|
||||
predicted_target_pos,
|
||||
optional_pitch_deg
|
||||
);
|
||||
```
|
||||
|
||||
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset. Result has adjusted Y coordinate.
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Coordinate system**: Y-up (height increases with Y)
|
||||
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
|
||||
* **Gravity**: applied downward along -Y axis
|
||||
* **Pitch convention**: +90° = straight up, -90° = straight down
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::unity_engine;
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
Projectile proj{
|
||||
.m_origin = {0, 2, 0},
|
||||
.m_launch_speed = 50.0f,
|
||||
.m_gravity_scale = 1.0f
|
||||
};
|
||||
|
||||
Target tgt{
|
||||
.m_origin = {20, 2, 15},
|
||||
.m_velocity = {1, 0, 0.5f},
|
||||
.m_is_airborne = false
|
||||
};
|
||||
|
||||
float gravity = 9.81f;
|
||||
float time = 0.5f;
|
||||
|
||||
// Predict where target will be
|
||||
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||
|
||||
// Compute aim angles
|
||||
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||
|
||||
// Predict projectile position with those angles
|
||||
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/unity_engine/formulas.hpp` — direction vectors and matrix builders
|
||||
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||
109
docs/engines/unreal_engine/camera_trait.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# `omath::unreal_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||
|
||||
> Header: `omath/engines/unreal_engine/traits/camera_trait.hpp` • Impl: `omath/engines/unreal_engine/traits/camera_trait.cpp`
|
||||
> Namespace: `omath::unreal_engine`
|
||||
> Purpose: provide Unreal Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`CameraTrait` exposes three `static` functions:
|
||||
|
||||
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||
* `calc_view_matrix(angles, origin)` – delegates to Unreal Engine formulas `unreal_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||
|
||||
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Unreal Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::unreal_engine {
|
||||
|
||||
class CameraTrait final {
|
||||
public:
|
||||
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||
static ViewAngles
|
||||
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||
const Vector3<float>& look_at) noexcept;
|
||||
|
||||
// Build view matrix for given angles and origin.
|
||||
static Mat4X4
|
||||
calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||
static Mat4X4
|
||||
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||
const projection::ViewPort& view_port,
|
||||
float near, float far) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::unreal_engine
|
||||
```
|
||||
|
||||
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||
|
||||
---
|
||||
|
||||
## Behavior & conventions
|
||||
|
||||
* **Angles from look-at** (Z-up coordinate system):
|
||||
|
||||
```
|
||||
dir = normalize(look_at - origin)
|
||||
pitch = asin(dir.z) // +Z is up
|
||||
yaw = atan2(dir.y, dir.x) // horizontal rotation
|
||||
roll = 0
|
||||
```
|
||||
|
||||
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||
|
||||
* **View matrix**: built by the Unreal Engine helper `unreal_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
|
||||
|
||||
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||
|
||||
---
|
||||
|
||||
## Using with `projection::Camera`
|
||||
|
||||
Create a camera whose math is driven by this trait:
|
||||
|
||||
```cpp
|
||||
using Mat4 = Mat4X4; // from Unreal Engine math headers
|
||||
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||
using UEcam = omath::projection::Camera<Mat4, Angs, omath::unreal_engine::CameraTrait>;
|
||||
|
||||
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||
auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
|
||||
UEcam cam(
|
||||
/*position*/ {1000.f, 500.f, 200.f},
|
||||
/*angles*/ omath::unreal_engine::CameraTrait::calc_look_at_angle({1000,500,200},{0,0,200}),
|
||||
/*viewport*/ vp,
|
||||
/*fov*/ fov,
|
||||
/*near*/ 10.f,
|
||||
/*far*/ 100000.f
|
||||
);
|
||||
```
|
||||
|
||||
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||
|
||||
---
|
||||
|
||||
## Notes & tips
|
||||
|
||||
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||
* Unreal Engine uses **Z-up**: pitch angles control vertical look, positive = up.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* Unreal Engine math helpers in `omath/engines/unreal_engine/formulas.hpp` (view/projection builders used above).
|
||||
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||
77
docs/engines/unreal_engine/constants.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# `omath::unreal_engine` — types & constants
|
||||
|
||||
> Header: `omath/engines/unreal_engine/constants.hpp`
|
||||
> Namespace: `omath::unreal_engine`
|
||||
> Purpose: define Unreal Engine coordinate system, matrix types, and angle ranges
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The **Unreal Engine** uses a **Z-up, left-handed** coordinate system:
|
||||
|
||||
* **Up** = `{0, 0, 1}` (Z-axis)
|
||||
* **Right** = `{0, 1, 0}` (Y-axis)
|
||||
* **Forward** = `{1, 0, 0}` (X-axis)
|
||||
|
||||
Matrices are **row-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```cpp
|
||||
namespace omath::unreal_engine {
|
||||
constexpr Vector3<float> k_abs_up = {0, 0, 1};
|
||||
constexpr Vector3<float> k_abs_right = {0, 1, 0};
|
||||
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
|
||||
}
|
||||
```
|
||||
|
||||
These basis vectors define the engine's **world coordinate frame**.
|
||||
|
||||
---
|
||||
|
||||
## Matrix types
|
||||
|
||||
```cpp
|
||||
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||
```
|
||||
|
||||
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
|
||||
|
||||
---
|
||||
|
||||
## Angle types
|
||||
|
||||
```cpp
|
||||
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
|
||||
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
|
||||
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||
```
|
||||
|
||||
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
|
||||
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||
|
||||
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate system notes
|
||||
|
||||
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
|
||||
* **Left-handed**: cross product `forward × right = up` with left-hand rule
|
||||
* This matches **Unreal Engine** conventions for 3D games and simulations
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/unreal_engine/formulas.hpp` — view/projection matrix builders
|
||||
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||
135
docs/engines/unreal_engine/formulas.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# `omath::unreal_engine` — formulas & matrix helpers
|
||||
|
||||
> Header: `omath/engines/unreal_engine/formulas.hpp`
|
||||
> Namespace: `omath::unreal_engine`
|
||||
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Unreal Engine
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This header provides **Unreal Engine**-specific math for:
|
||||
|
||||
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||
* **Rotation matrices** from Euler angles
|
||||
* **View matrices** (camera transforms)
|
||||
* **Perspective projection** matrices
|
||||
|
||||
All functions respect Unreal Engine's **Z-up, left-handed** coordinate system.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::unreal_engine {
|
||||
|
||||
// Compute forward direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute right direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Compute up direction from pitch/yaw/roll
|
||||
[[nodiscard]]
|
||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build 3x3 rotation matrix from angles
|
||||
[[nodiscard]]
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||
|
||||
// Build view matrix (camera space transform)
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||
const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
// Build perspective projection matrix
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||
float aspect_ratio,
|
||||
float near, float far) noexcept;
|
||||
|
||||
} // namespace omath::unreal_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Direction vectors
|
||||
|
||||
Given camera angles (pitch/yaw/roll):
|
||||
|
||||
* `forward_vector(angles)` → unit vector pointing where the camera looks
|
||||
* `right_vector(angles)` → unit vector pointing to the camera's right
|
||||
* `up_vector(angles)` → unit vector pointing upward relative to the camera
|
||||
|
||||
These are used for movement, aim direction, and building coordinate frames.
|
||||
|
||||
---
|
||||
|
||||
## Rotation & view matrices
|
||||
|
||||
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
|
||||
* `calc_view_matrix(angles, origin)` → camera view matrix
|
||||
|
||||
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||
|
||||
---
|
||||
|
||||
## Perspective projection
|
||||
|
||||
```cpp
|
||||
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||
fov_degrees, // vertical field of view (e.g., 90)
|
||||
aspect_ratio, // width / height (e.g., 16/9)
|
||||
near_plane, // e.g., 10.0
|
||||
far_plane // e.g., 100000.0
|
||||
);
|
||||
```
|
||||
|
||||
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::unreal_engine;
|
||||
|
||||
// Camera setup
|
||||
ViewAngles angles = {
|
||||
PitchAngle::from_degrees(-20.0f),
|
||||
YawAngle::from_degrees(45.0f),
|
||||
RollAngle::from_degrees(0.0f)
|
||||
};
|
||||
Vector3<float> cam_pos{1000.0f, 500.0f, 200.0f};
|
||||
|
||||
// Compute direction
|
||||
auto forward = forward_vector(angles);
|
||||
auto right = right_vector(angles);
|
||||
auto up = up_vector(angles);
|
||||
|
||||
// Build matrices
|
||||
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||
auto proj_mat = calc_perspective_projection_matrix(90.0f, 16.0f/9.0f, 10.0f, 100000.0f);
|
||||
|
||||
// Use view_mat and proj_mat for rendering...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||
* **Pitch**: positive = looking up, negative = looking down
|
||||
* **Yaw**: increases counter-clockwise from the +X axis
|
||||
* **Coordinate system**: Z-up, X-forward, Y-right (left-handed)
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/unreal_engine/constants.hpp` — coordinate frame & angle types
|
||||
* `omath/engines/unreal_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||
200
docs/engines/unreal_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# `omath::unreal_engine::PredEngineTrait` — projectile prediction trait
|
||||
|
||||
> Header: `omath/engines/unreal_engine/traits/pred_engine_trait.hpp`
|
||||
> Namespace: `omath::unreal_engine`
|
||||
> Purpose: provide Unreal Engine-specific projectile and target prediction for ballistic calculations
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||
|
||||
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||
* `calc_vector_2d_distance` – horizontal distance (X/Y plane, ignoring Z)
|
||||
* `get_vector_height_coordinate` – extracts vertical coordinate (Y in Unreal Engine, note: code uses Z)
|
||||
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||
|
||||
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::unreal_engine {
|
||||
|
||||
class PredEngineTrait final {
|
||||
public:
|
||||
// Predict projectile position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||
float pitch, float yaw, float time,
|
||||
float gravity) noexcept;
|
||||
|
||||
// Predict target position after `time` seconds
|
||||
static constexpr Vector3<float>
|
||||
predict_target_position(const projectile_prediction::Target& target,
|
||||
float time, float gravity) noexcept;
|
||||
|
||||
// Compute horizontal (2D) distance
|
||||
static float
|
||||
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||
|
||||
// Get vertical coordinate (implementation returns Y, but UE is Z-up)
|
||||
static constexpr float
|
||||
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||
|
||||
// Compute aim point from angles
|
||||
static Vector3<float>
|
||||
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||
Vector3<float> predicted_target_position,
|
||||
std::optional<float> projectile_pitch) noexcept;
|
||||
|
||||
// Compute pitch angle to look at target
|
||||
static float
|
||||
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
|
||||
// Compute yaw angle to look at target
|
||||
static float
|
||||
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||
const Vector3<float>& view_to) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::unreal_engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projectile prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_projectile_position(
|
||||
projectile, // initial position, speed, gravity scale
|
||||
pitch_deg, // launch pitch (positive = up)
|
||||
yaw_deg, // launch yaw
|
||||
time, // time in seconds
|
||||
gravity // gravity constant (e.g., 980 cm/s²)
|
||||
);
|
||||
```
|
||||
|
||||
Computes:
|
||||
|
||||
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||
2. Initial velocity: `forward * launch_speed`
|
||||
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component per implementation, though UE is Z-up)
|
||||
|
||||
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||
|
||||
---
|
||||
|
||||
## Target prediction
|
||||
|
||||
```cpp
|
||||
auto pos = PredEngineTrait::predict_target_position(
|
||||
target, // position, velocity, airborne flag
|
||||
time, // time in seconds
|
||||
gravity // gravity constant
|
||||
);
|
||||
```
|
||||
|
||||
Simple linear extrapolation plus gravity if target is airborne:
|
||||
|
||||
```
|
||||
predicted = origin + velocity * time
|
||||
if (airborne)
|
||||
predicted.y -= 0.5 * gravity * time² // Note: implementation uses Y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Distance & height helpers
|
||||
|
||||
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.z²)` (horizontal distance in X/Z plane)
|
||||
* `get_vector_height_coordinate(vec)` → `vec.y` (implementation returns Y; UE convention is Z-up)
|
||||
|
||||
Used to compute ballistic arc parameters.
|
||||
|
||||
---
|
||||
|
||||
## Aim angle calculation
|
||||
|
||||
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||
- Formula: `asin(Δz / distance)` converted to degrees (direction normalized first)
|
||||
- Positive = looking up, negative = looking down
|
||||
|
||||
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||
- Formula: `atan2(Δy, Δx)` converted to degrees (direction normalized first)
|
||||
- Horizontal rotation around Z-axis
|
||||
|
||||
---
|
||||
|
||||
## Viewpoint from angles
|
||||
|
||||
```cpp
|
||||
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||
projectile,
|
||||
predicted_target_pos,
|
||||
optional_pitch_deg
|
||||
);
|
||||
```
|
||||
|
||||
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
* **Coordinate system**: Z-up (height increases with Z)
|
||||
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
|
||||
* **Gravity**: applied downward (implementation uses Y component, but UE is Z-up)
|
||||
* **Pitch convention**: +90° = straight up, -90° = straight down
|
||||
|
||||
**Note**: Some implementation details (gravity application to Y coordinate) may need adjustment for full Unreal Engine Z-up consistency.
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::unreal_engine;
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
Projectile proj{
|
||||
.m_origin = {0, 0, 200},
|
||||
.m_launch_speed = 5000.0f,
|
||||
.m_gravity_scale = 1.0f
|
||||
};
|
||||
|
||||
Target tgt{
|
||||
.m_origin = {2000, 1000, 200},
|
||||
.m_velocity = {50, 20, 0},
|
||||
.m_is_airborne = false
|
||||
};
|
||||
|
||||
float gravity = 980.0f; // cm/s² in Unreal units
|
||||
float time = 0.5f;
|
||||
|
||||
// Predict where target will be
|
||||
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||
|
||||
// Compute aim angles
|
||||
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||
|
||||
// Predict projectile position with those angles
|
||||
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath/engines/unreal_engine/formulas.hpp` — direction vectors and matrix builders
|
||||
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||
406
docs/faq.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# FAQ
|
||||
|
||||
Common questions and answers about OMath.
|
||||
|
||||
---
|
||||
|
||||
## General Questions
|
||||
|
||||
### What is OMath?
|
||||
|
||||
OMath is a modern C++ math library designed for game development, graphics programming, and high-performance computing. It provides:
|
||||
- Vector and matrix operations
|
||||
- 3D projection and camera systems
|
||||
- Projectile prediction
|
||||
- Collision detection
|
||||
- Support for multiple game engines (Source, Unity, Unreal, etc.)
|
||||
- Pattern scanning utilities
|
||||
|
||||
### Why choose OMath over other math libraries?
|
||||
|
||||
- **Modern C++**: Uses C++20/23 features (concepts, `constexpr`, `std::expected`)
|
||||
- **No legacy code**: Built from scratch without legacy baggage
|
||||
- **Game engine support**: Pre-configured for Source, Unity, Unreal, Frostbite, etc.
|
||||
- **Zero dependencies**: No external dependencies needed (except for testing)
|
||||
- **Performance**: AVX2 optimizations available
|
||||
- **Type safety**: Strong typing prevents common errors
|
||||
- **Cross-platform**: Works on Windows, Linux, and macOS
|
||||
|
||||
### Is OMath suitable for production use?
|
||||
|
||||
Yes! OMath is production-ready and used in various projects. It has:
|
||||
- Comprehensive test coverage
|
||||
- Clear error handling
|
||||
- Well-documented API
|
||||
- Active maintenance and community support
|
||||
|
||||
---
|
||||
|
||||
## Installation & Setup
|
||||
|
||||
### How do I install OMath?
|
||||
|
||||
Three main methods:
|
||||
|
||||
**vcpkg (recommended):**
|
||||
```bash
|
||||
vcpkg install orange-math
|
||||
```
|
||||
|
||||
**xrepo:**
|
||||
```bash
|
||||
xrepo install omath
|
||||
```
|
||||
|
||||
**From source:**
|
||||
See [Installation Guide](install.md)
|
||||
|
||||
### What are the minimum requirements?
|
||||
|
||||
- **Compiler**: C++20 support required
|
||||
- GCC 10+
|
||||
- Clang 11+
|
||||
- MSVC 2019 16.10+
|
||||
- **CMake**: 3.15+ (if building from source)
|
||||
- **Platform**: Windows, Linux, or macOS
|
||||
|
||||
### Do I need C++23?
|
||||
|
||||
C++23 is **recommended** but not required. Some features like `std::expected` work better with C++23, but fallbacks are available for C++20.
|
||||
|
||||
### Can I use OMath in a C++17 project?
|
||||
|
||||
No, OMath requires C++20 minimum due to use of concepts, `constexpr` enhancements, and other C++20 features.
|
||||
|
||||
---
|
||||
|
||||
## Usage Questions
|
||||
|
||||
### How do I include OMath in my project?
|
||||
|
||||
**Full library:**
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
```
|
||||
|
||||
**Specific components:**
|
||||
```cpp
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
#include <omath/engines/source_engine/camera.hpp>
|
||||
```
|
||||
|
||||
### Which game engine should I use?
|
||||
|
||||
Choose based on your target game or application:
|
||||
|
||||
| Engine | Use For |
|
||||
|--------|---------|
|
||||
| **Source Engine** | CS:GO, TF2, CS2, Half-Life, Portal, L4D |
|
||||
| **Unity Engine** | Unity games (many indie and mobile games) |
|
||||
| **Unreal Engine** | Fortnite, Unreal games |
|
||||
| **Frostbite** | Battlefield, Star Wars games (EA titles) |
|
||||
| **IW Engine** | Call of Duty series |
|
||||
| **OpenGL** | Custom OpenGL applications, generic 3D |
|
||||
|
||||
### How do I switch between engines?
|
||||
|
||||
Just change the namespace:
|
||||
|
||||
```cpp
|
||||
// Source Engine
|
||||
using namespace omath::source_engine;
|
||||
Camera cam = /* ... */;
|
||||
|
||||
// Unity Engine
|
||||
using namespace omath::unity_engine;
|
||||
Camera cam = /* ... */;
|
||||
```
|
||||
|
||||
Each engine has the same API but different coordinate system handling.
|
||||
|
||||
### What if my game isn't listed?
|
||||
|
||||
Use the **OpenGL engine** as a starting point - it uses canonical OpenGL conventions. You may need to adjust coordinate transformations based on your specific game.
|
||||
|
||||
---
|
||||
|
||||
## Performance Questions
|
||||
|
||||
### Should I use the AVX2 or Legacy engine?
|
||||
|
||||
**Use AVX2 if:**
|
||||
- Target modern CPUs (2013+)
|
||||
- Need maximum performance
|
||||
- Can accept reduced compatibility
|
||||
|
||||
**Use Legacy if:**
|
||||
- Need broad compatibility
|
||||
- Target older CPUs or ARM
|
||||
- Unsure about target hardware
|
||||
|
||||
The API is identical - just change the class:
|
||||
```cpp
|
||||
// Legacy (compatible)
|
||||
ProjPredEngineLegacy engine;
|
||||
|
||||
// AVX2 (faster)
|
||||
ProjPredEngineAVX2 engine;
|
||||
```
|
||||
|
||||
### How much faster is AVX2?
|
||||
|
||||
Typically 2-4x faster for projectile prediction calculations, depending on the CPU and specific use case.
|
||||
|
||||
### Are vector operations constexpr?
|
||||
|
||||
Yes! Most operations are `constexpr` and can be evaluated at compile-time:
|
||||
|
||||
```cpp
|
||||
constexpr Vector3<float> v{1, 2, 3};
|
||||
constexpr auto len_sq = v.length_sqr(); // Computed at compile time
|
||||
```
|
||||
|
||||
### Is OMath thread-safe?
|
||||
|
||||
- **Immutable operations** (vector math, etc.) are thread-safe
|
||||
- **Mutable state** (Camera updates) is NOT thread-safe
|
||||
- Use separate instances per thread or synchronize access
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `world_to_screen()` always returns `nullopt`
|
||||
|
||||
Check:
|
||||
1. **Is the point behind the camera?** Points behind the camera cannot be projected.
|
||||
2. **Are near/far planes correct?** Ensure `near < far` and both are positive.
|
||||
3. **Is FOV valid?** FOV should be between 1° and 179°.
|
||||
4. **Are camera angles normalized?** Use engine-provided angle types.
|
||||
|
||||
### Angles are wrapping incorrectly
|
||||
|
||||
Use the correct angle type:
|
||||
```cpp
|
||||
// Good: uses proper angle type
|
||||
PitchAngle pitch = PitchAngle::from_degrees(45.0f);
|
||||
|
||||
// Bad: raw float loses normalization
|
||||
float pitch = 45.0f;
|
||||
```
|
||||
|
||||
### Projection seems mirrored or inverted
|
||||
|
||||
You may be using the wrong engine trait. Each engine has different coordinate conventions:
|
||||
- **Source/Unity**: Z-up
|
||||
- **Unreal**: Z-up, different handedness
|
||||
- **OpenGL**: Y-up
|
||||
|
||||
Ensure you're using the trait matching your game.
|
||||
|
||||
### Pattern scanning finds multiple matches
|
||||
|
||||
This is normal! Patterns may appear multiple times. Solutions:
|
||||
1. Make the pattern more specific (more bytes, fewer wildcards)
|
||||
2. Use additional context (nearby code patterns)
|
||||
3. Verify each match programmatically
|
||||
|
||||
### Projectile prediction returns `nullopt`
|
||||
|
||||
Common reasons:
|
||||
1. **Target too fast**: Target velocity exceeds projectile speed
|
||||
2. **Out of range**: Distance exceeds max flight time
|
||||
3. **Invalid input**: Check projectile speed > 0
|
||||
4. **Gravity too strong**: Projectile can't reach target height
|
||||
|
||||
### Compilation errors about `std::expected`
|
||||
|
||||
If using C++20 (not C++23), you may need a backport library like `tl::expected`:
|
||||
|
||||
```cmake
|
||||
# CMakeLists.txt
|
||||
find_package(tl-expected CONFIG REQUIRED)
|
||||
target_link_libraries(your_target PRIVATE tl::expected)
|
||||
```
|
||||
|
||||
Or upgrade to C++23 if possible.
|
||||
|
||||
---
|
||||
|
||||
## Feature Questions
|
||||
|
||||
### Can I use OMath with DirectX/OpenGL/Vulkan?
|
||||
|
||||
Yes! OMath matrices and vectors work with all graphics APIs. Use:
|
||||
- **OpenGL**: `opengl_engine` traits
|
||||
- **DirectX**: Use appropriate engine trait or OpenGL as base
|
||||
- **Vulkan**: Use OpenGL traits as starting point
|
||||
|
||||
### Does OMath support quaternions?
|
||||
|
||||
Not currently. Quaternion support may be added in future versions. For now, use euler angles (ViewAngles) or convert manually.
|
||||
|
||||
### Can I extend OMath with custom engine traits?
|
||||
|
||||
Yes! Implement the `CameraEngineConcept`:
|
||||
|
||||
```cpp
|
||||
class MyEngineTrait {
|
||||
public:
|
||||
static ViewAngles calc_look_at_angle(
|
||||
const Vector3<float>& origin,
|
||||
const Vector3<float>& target
|
||||
);
|
||||
|
||||
static Mat4X4 calc_view_matrix(
|
||||
const ViewAngles& angles,
|
||||
const Vector3<float>& origin
|
||||
);
|
||||
|
||||
static Mat4X4 calc_projection_matrix(
|
||||
const FieldOfView& fov,
|
||||
const ViewPort& viewport,
|
||||
float near, float far
|
||||
);
|
||||
};
|
||||
|
||||
// Use with Camera
|
||||
using MyCamera = Camera<Mat4X4, ViewAngles, MyEngineTrait>;
|
||||
```
|
||||
|
||||
### Does OMath support SIMD for vector operations?
|
||||
|
||||
AVX2 support is available for projectile prediction. General vector SIMD may be added in future versions. The library already compiles to efficient code with compiler optimizations enabled.
|
||||
|
||||
### Can I use OMath for machine learning?
|
||||
|
||||
OMath is optimized for game development and graphics, not ML. For machine learning, consider libraries like Eigen or xtensor which are designed for that domain.
|
||||
|
||||
---
|
||||
|
||||
## Debugging Questions
|
||||
|
||||
### How do I print vectors?
|
||||
|
||||
OMath provides `std::formatter` support:
|
||||
|
||||
```cpp
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
|
||||
Vector3<float> v{1, 2, 3};
|
||||
std::cout << std::format("{}", v) << "\n"; // Prints: [1, 2, 3]
|
||||
```
|
||||
|
||||
### How do I visualize projection problems?
|
||||
|
||||
1. Check if `world_to_screen()` succeeds
|
||||
2. Print camera matrices:
|
||||
```cpp
|
||||
auto view = camera.get_view_matrix();
|
||||
auto proj = camera.get_projection_matrix();
|
||||
// Print matrix values
|
||||
```
|
||||
3. Test with known good points (e.g., origin, simple positions)
|
||||
4. Verify viewport and FOV values
|
||||
|
||||
### How can I debug pattern scanning?
|
||||
|
||||
```cpp
|
||||
PatternView pattern{"48 8B 05 ?? ?? ?? ??"};
|
||||
|
||||
// Print pattern details
|
||||
std::cout << "Pattern length: " << pattern.size() << "\n";
|
||||
std::cout << "Pattern bytes: ";
|
||||
for (auto byte : pattern) {
|
||||
if (byte.has_value()) {
|
||||
std::cout << std::hex << (int)*byte << " ";
|
||||
} else {
|
||||
std::cout << "?? ";
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
### How can I contribute to OMath?
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/orange-cpp/omath/blob/master/CONTRIBUTING.md) for guidelines. Contributions welcome:
|
||||
- Bug fixes
|
||||
- New features
|
||||
- Documentation improvements
|
||||
- Test coverage
|
||||
- Examples
|
||||
|
||||
### Where do I report bugs?
|
||||
|
||||
[GitHub Issues](https://github.com/orange-cpp/omath/issues)
|
||||
|
||||
Please include:
|
||||
- OMath version
|
||||
- Compiler and version
|
||||
- Minimal reproducible example
|
||||
- Expected vs actual behavior
|
||||
|
||||
### How do I request a feature?
|
||||
|
||||
Open a GitHub issue with:
|
||||
- Use case description
|
||||
- Proposed API (if applicable)
|
||||
- Why existing features don't meet your needs
|
||||
|
||||
---
|
||||
|
||||
## License & Legal
|
||||
|
||||
### What license does OMath use?
|
||||
|
||||
OMath uses a custom "libomath" license. See [LICENSE](https://github.com/orange-cpp/omath/blob/master/LICENSE) for full details.
|
||||
|
||||
### Can I use OMath in commercial projects?
|
||||
|
||||
Check the LICENSE file for commercial use terms.
|
||||
|
||||
### Can I use OMath for game cheating/hacking?
|
||||
|
||||
OMath is a math library and can be used for various purposes. However:
|
||||
- Using it to cheat in online games may violate game ToS
|
||||
- Creating cheats may be illegal in your jurisdiction
|
||||
- The developers do not condone cheating in online games
|
||||
|
||||
Use responsibly and ethically.
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
### Where can I get help?
|
||||
|
||||
- **Documentation**: [http://libomath.org](http://libomath.org)
|
||||
- **Discord**: [Join community](https://discord.gg/eDgdaWbqwZ)
|
||||
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
|
||||
- **GitHub Issues**: [Report bugs/ask questions](https://github.com/orange-cpp/omath/issues)
|
||||
|
||||
### Is there a Discord/community?
|
||||
|
||||
Yes! Join our Discord: [https://discord.gg/eDgdaWbqwZ](https://discord.gg/eDgdaWbqwZ)
|
||||
|
||||
### Are there video tutorials?
|
||||
|
||||
Check our [YouTube channel](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ) for demonstrations and tutorials.
|
||||
|
||||
---
|
||||
|
||||
## Didn't find your answer?
|
||||
|
||||
- Search the [documentation](index.md)
|
||||
- Check [tutorials](tutorials.md)
|
||||
- Ask on [Discord](https://discord.gg/eDgdaWbqwZ)
|
||||
- Open a [GitHub issue](https://github.com/orange-cpp/omath/issues)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
305
docs/getting_started.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# Getting Started
|
||||
Welcome to OMath! This guide will help you get up and running with the library quickly.
|
||||
|
||||
## What is OMath?
|
||||
|
||||
OMath is a modern, blazingly fast C++ math library designed for:
|
||||
- **Game development** and cheat development
|
||||
- **Graphics programming** (DirectX/OpenGL/Vulkan)
|
||||
- **3D applications** with support for multiple game engines
|
||||
- **High-performance computing** with AVX2 optimizations
|
||||
|
||||
Key features:
|
||||
- 100% independent, no legacy C++ code
|
||||
- Fully `constexpr` template-based design
|
||||
- Zero additional dependencies (except for unit tests)
|
||||
- Cross-platform (Windows, macOS, Linux)
|
||||
- Built-in support for Source, Unity, Unreal, Frostbite, IWEngine, and OpenGL coordinate systems
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Choose one of the following methods to install OMath:
|
||||
|
||||
### Using vcpkg (Recommended)
|
||||
|
||||
```bash
|
||||
vcpkg install orange-math
|
||||
```
|
||||
|
||||
Then in your CMakeLists.txt:
|
||||
```cmake
|
||||
find_package(omath CONFIG REQUIRED)
|
||||
target_link_libraries(your_target PRIVATE omath::omath)
|
||||
```
|
||||
|
||||
### Using xrepo
|
||||
|
||||
```bash
|
||||
xrepo install omath
|
||||
```
|
||||
|
||||
Then in your xmake.lua:
|
||||
```lua
|
||||
add_requires("omath")
|
||||
target("your_target")
|
||||
add_packages("omath")
|
||||
```
|
||||
|
||||
### Building from Source
|
||||
|
||||
See the detailed [Installation Guide](install.md) for complete instructions.
|
||||
|
||||
---
|
||||
|
||||
## Quick Example
|
||||
|
||||
Here's a simple example to get you started:
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
using namespace omath;
|
||||
|
||||
// Create 3D vectors
|
||||
Vector3<float> a{1.0f, 2.0f, 3.0f};
|
||||
Vector3<float> b{4.0f, 5.0f, 6.0f};
|
||||
|
||||
// Vector operations
|
||||
auto sum = a + b; // Vector addition
|
||||
auto dot_product = a.dot(b); // Dot product: 32.0
|
||||
auto cross_product = a.cross(b); // Cross product: (-3, 6, -3)
|
||||
auto length = a.length(); // Length: ~3.74
|
||||
auto normalized = a.normalized(); // Unit vector
|
||||
|
||||
std::cout << "Sum: [" << sum.x << ", " << sum.y << ", " << sum.z << "]\n";
|
||||
std::cout << "Dot product: " << dot_product << "\n";
|
||||
std::cout << "Length: " << length << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Vectors
|
||||
|
||||
OMath provides 2D, 3D, and 4D vector types:
|
||||
|
||||
```cpp
|
||||
using namespace omath;
|
||||
|
||||
Vector2<float> vec2{1.0f, 2.0f};
|
||||
Vector3<float> vec3{1.0f, 2.0f, 3.0f};
|
||||
Vector4<float> vec4{1.0f, 2.0f, 3.0f, 4.0f};
|
||||
```
|
||||
|
||||
All vector types support:
|
||||
- Arithmetic operations (+, -, *, /)
|
||||
- Dot and cross products (where applicable)
|
||||
- Length and distance calculations
|
||||
- Normalization
|
||||
- Component-wise operations
|
||||
|
||||
See: [Vector2](linear_algebra/vector2.md), [Vector3](linear_algebra/vector3.md), [Vector4](linear_algebra/vector4.md)
|
||||
|
||||
### 2. Matrices
|
||||
|
||||
4x4 matrices for transformations:
|
||||
|
||||
```cpp
|
||||
using namespace omath;
|
||||
|
||||
Mat4X4 matrix = Mat4X4::identity();
|
||||
// Use for transformations, projections, etc.
|
||||
```
|
||||
|
||||
See: [Matrix Documentation](linear_algebra/mat.md)
|
||||
|
||||
### 3. Angles
|
||||
|
||||
Strong-typed angle system with automatic range management:
|
||||
|
||||
```cpp
|
||||
using namespace omath;
|
||||
|
||||
auto angle = Angle<float, 0.0f, 360.0f>::from_degrees(45.0f);
|
||||
auto radians = angle.as_radians();
|
||||
|
||||
// View angles for camera systems
|
||||
ViewAngles view{
|
||||
PitchAngle::from_degrees(-10.0f),
|
||||
YawAngle::from_degrees(90.0f),
|
||||
RollAngle::from_degrees(0.0f)
|
||||
};
|
||||
```
|
||||
|
||||
See: [Angle](trigonometry/angle.md), [View Angles](trigonometry/view_angles.md)
|
||||
|
||||
### 4. 3D Projection
|
||||
|
||||
Built-in camera and projection systems:
|
||||
|
||||
```cpp
|
||||
using namespace omath;
|
||||
using namespace omath::projection;
|
||||
|
||||
ViewPort viewport{1920.0f, 1080.0f};
|
||||
auto fov = FieldOfView::from_degrees(90.0f);
|
||||
|
||||
// Example using Source Engine
|
||||
using namespace omath::source_engine;
|
||||
Camera cam(
|
||||
Vector3<float>{0, 0, 100}, // Position
|
||||
ViewAngles{}, // Angles
|
||||
viewport,
|
||||
fov,
|
||||
0.1f, // near plane
|
||||
1000.0f // far plane
|
||||
);
|
||||
|
||||
// Project 3D point to 2D screen
|
||||
Vector3<float> world_pos{100, 50, 75};
|
||||
if (auto screen_pos = cam.world_to_screen(world_pos)) {
|
||||
std::cout << "Screen: " << screen_pos->x << ", " << screen_pos->y << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
See: [Camera](projection/camera.md)
|
||||
|
||||
### 5. Game Engine Support
|
||||
|
||||
OMath provides pre-configured traits for major game engines:
|
||||
|
||||
```cpp
|
||||
// Source Engine
|
||||
#include <omath/engines/source_engine/camera.hpp>
|
||||
using SourceCamera = omath::source_engine::Camera;
|
||||
|
||||
// Unity Engine
|
||||
#include <omath/engines/unity_engine/camera.hpp>
|
||||
using UnityCamera = omath::unity_engine::Camera;
|
||||
|
||||
// Unreal Engine
|
||||
#include <omath/engines/unreal_engine/camera.hpp>
|
||||
using UnrealCamera = omath::unreal_engine::Camera;
|
||||
|
||||
// And more: OpenGL, Frostbite, IWEngine
|
||||
```
|
||||
|
||||
Each engine has its own coordinate system conventions automatically handled.
|
||||
|
||||
See: Engine-specific docs in [engines/](engines/) folder
|
||||
|
||||
---
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### World-to-Screen Projection
|
||||
|
||||
```cpp
|
||||
using namespace omath;
|
||||
using namespace omath::source_engine;
|
||||
|
||||
Camera cam = /* initialize camera */;
|
||||
Vector3<float> enemy_position{100, 200, 50};
|
||||
|
||||
if (auto screen = cam.world_to_screen(enemy_position)) {
|
||||
// Draw ESP box at screen->x, screen->y
|
||||
std::cout << "Enemy on screen at: " << screen->x << ", " << screen->y << "\n";
|
||||
} else {
|
||||
// Enemy not visible (behind camera or outside frustum)
|
||||
}
|
||||
```
|
||||
|
||||
### Projectile Prediction
|
||||
|
||||
```cpp
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
Projectile bullet{
|
||||
Vector3<float>{0, 0, 0}, // shooter position
|
||||
1000.0f, // muzzle velocity (m/s)
|
||||
Vector3<float>{0, 0, -9.81f} // gravity
|
||||
};
|
||||
|
||||
Target enemy{
|
||||
Vector3<float>{100, 200, 50}, // position
|
||||
Vector3<float>{10, 0, 0} // velocity
|
||||
};
|
||||
|
||||
// Calculate where to aim
|
||||
ProjPredEngineLegacy engine;
|
||||
if (auto aim_point = engine.maybe_calculate_aim_point(bullet, enemy)) {
|
||||
// Aim at *aim_point to hit moving target
|
||||
}
|
||||
```
|
||||
|
||||
See: [Projectile Prediction](projectile_prediction/projectile_engine.md)
|
||||
|
||||
### Collision Detection
|
||||
|
||||
```cpp
|
||||
using namespace omath;
|
||||
|
||||
// Ray-plane intersection
|
||||
Plane ground{
|
||||
Vector3<float>{0, 0, 0}, // point on plane
|
||||
Vector3<float>{0, 0, 1} // normal (pointing up)
|
||||
};
|
||||
|
||||
Vector3<float> ray_origin{0, 0, 100};
|
||||
Vector3<float> ray_direction{0, 0, -1};
|
||||
|
||||
if (auto hit = ground.intersects_ray(ray_origin, ray_direction)) {
|
||||
std::cout << "Hit ground at: " << hit->x << ", " << hit->y << ", " << hit->z << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
See: [Collision Detection](collision/line_tracer.md)
|
||||
|
||||
### Pattern Scanning
|
||||
|
||||
```cpp
|
||||
#include <omath/utility/pattern_scan.hpp>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
std::vector<uint8_t> memory = /* ... */;
|
||||
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
|
||||
|
||||
if (auto result = pattern_scan(memory, pattern)) {
|
||||
std::cout << "Pattern found at offset: " << result->offset << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
See: [Pattern Scanning](utility/pattern_scan.md)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have the basics, explore these topics:
|
||||
|
||||
1. **[API Reference](index.md)** - Complete API documentation
|
||||
2. **[Examples](../examples/)** - Working code examples
|
||||
3. **[Engine-Specific Features](engines/)** - Deep dive into game engine support
|
||||
4. **[Advanced Topics](#)** - Performance optimization, custom traits, etc.
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Documentation**: [http://libomath.org](http://libomath.org)
|
||||
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
|
||||
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
|
||||
- **Issues**: [GitHub Issues](https://github.com/orange-cpp/omath/issues)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 454 KiB After Width: | Height: | Size: 454 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 644 KiB After Width: | Height: | Size: 644 KiB |
BIN
docs/images/showcase/tf2.jpg
Normal file
|
After Width: | Height: | Size: 324 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
239
docs/index.md
Normal file
@@ -0,0 +1,239 @@
|
||||
<div class="center-text">
|
||||
<!-- Banner -->
|
||||
<p>
|
||||
<img src="images/logos/omath_logo_macro.png" alt="omath banner">
|
||||
</p>
|
||||
|
||||
<!-- Badges -->
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/license-libomath-orange" alt="license: libomath">
|
||||
<img src="https://img.shields.io/github/contributors/orange-cpp/omath" alt="GitHub contributors">
|
||||
<img src="https://img.shields.io/github/languages/top/orange-cpp/omath" alt="Top language">
|
||||
<a href="https://www.codefactor.io/repository/github/orange-cpp/omath">
|
||||
<img src="https://www.codefactor.io/repository/github/orange-cpp/omath/badge" alt="CodeFactor">
|
||||
</a>
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml" alt="GitHub Actions Workflow Status">
|
||||
<a href="https://repology.org/project/orange-math/versions">
|
||||
<img src="https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg" alt="Vcpkg package">
|
||||
</a>
|
||||
<img src="https://img.shields.io/github/forks/orange-cpp/omath" alt="GitHub forks">
|
||||
<a href="https://discord.gg/eDgdaWbqwZ">
|
||||
<img src="https://dcbadge.limes.pink/api/server/https://discord.gg/eDgdaWbqwZ?style=flat" alt="Join us on Discord">
|
||||
</a>
|
||||
<a href="https://t.me/orangennotes">
|
||||
<img src="https://img.shields.io/badge/Telegram-2CA5E0?style=flat-squeare&logo=telegram&logoColor=white" alt="Telegram">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
OMath is a 100% independent, constexpr template blazingly fast math library that doesn't have legacy C++ code.
|
||||
|
||||
It provides the latest features, is highly customizable, has all for cheat development, DirectX/OpenGL/Vulkan support, premade support for different game engines, much more constexpr stuff than in other libraries and more...
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
**New to OMath?** Start here:
|
||||
|
||||
- **[Getting Started Guide](getting_started.md)** - Installation and first steps
|
||||
- **[API Overview](api_overview.md)** - High-level API reference
|
||||
- **[Installation Instructions](install.md)** - Detailed setup guide
|
||||
|
||||
**Quick example:**
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
Vector3<float> a{1, 2, 3};
|
||||
Vector3<float> b{4, 5, 6};
|
||||
|
||||
auto dot = a.dot(b); // 32.0
|
||||
auto cross = a.cross(b); // (-3, 6, -3)
|
||||
auto distance = a.distance_to(b); // ~5.196
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Structure
|
||||
|
||||
### Core Mathematics
|
||||
|
||||
**Linear Algebra**
|
||||
- [Vector2](linear_algebra/vector2.md) - 2D vectors with full operator support
|
||||
- [Vector3](linear_algebra/vector3.md) - 3D vectors, dot/cross products, angles
|
||||
- [Vector4](linear_algebra/vector4.md) - 4D vectors (homogeneous coordinates)
|
||||
- [Mat4X4](linear_algebra/mat.md) - 4×4 matrices for transformations
|
||||
- [Triangle](linear_algebra/triangle.md) - Triangle primitive and utilities
|
||||
|
||||
**Trigonometry**
|
||||
- [Angle](trigonometry/angle.md) - Strong-typed angle system with range enforcement
|
||||
- [Angles](trigonometry/angles.md) - Angle utilities and conversions
|
||||
- [View Angles](trigonometry/view_angles.md) - Pitch/Yaw/Roll for camera systems
|
||||
|
||||
**3D Primitives**
|
||||
- [Box](3d_primitives/box.md) - Axis-aligned bounding boxes
|
||||
- [Plane](3d_primitives/plane.md) - Infinite planes and intersections
|
||||
|
||||
### Game Development Features
|
||||
|
||||
**Projection & Camera**
|
||||
- [Camera](projection/camera.md) - Generic camera system with engine traits
|
||||
- [Error Codes](projection/error_codes.md) - Projection error handling
|
||||
|
||||
**Collision Detection**
|
||||
- [Line Tracer](collision/line_tracer.md) - Ray-triangle, ray-plane intersections
|
||||
|
||||
**Projectile Prediction**
|
||||
- [Projectile Engine Interface](projectile_prediction/projectile_engine.md) - Base interface
|
||||
- [Projectile](projectile_prediction/projectile.md) - Projectile properties
|
||||
- [Target](projectile_prediction/target.md) - Target state representation
|
||||
- [Legacy Engine](projectile_prediction/proj_pred_engine_legacy.md) - Standard implementation
|
||||
- [AVX2 Engine](projectile_prediction/proj_pred_engine_avx2.md) - Optimized implementation
|
||||
|
||||
**Pathfinding**
|
||||
- [A* Algorithm](pathfinding/a_star.md) - A* pathfinding implementation
|
||||
- [Navigation Mesh](pathfinding/navigation_mesh.md) - Triangle-based navigation
|
||||
|
||||
### Game Engine Support
|
||||
|
||||
OMath provides built-in support for multiple game engines with proper coordinate system handling:
|
||||
|
||||
**Source Engine** (Valve - CS:GO, TF2, etc.)
|
||||
- [Camera Trait](engines/source_engine/camera_trait.md)
|
||||
- [Pred Engine Trait](engines/source_engine/pred_engine_trait.md)
|
||||
- [Constants](engines/source_engine/constants.md)
|
||||
- [Formulas](engines/source_engine/formulas.md)
|
||||
|
||||
**Unity Engine**
|
||||
- [Camera Trait](engines/unity_engine/camera_trait.md)
|
||||
- [Pred Engine Trait](engines/unity_engine/pred_engine_trait.md)
|
||||
- [Constants](engines/unity_engine/constants.md)
|
||||
- [Formulas](engines/unity_engine/formulas.md)
|
||||
|
||||
**Unreal Engine** (Epic Games)
|
||||
- [Camera Trait](engines/unreal_engine/camera_trait.md)
|
||||
- [Pred Engine Trait](engines/unreal_engine/pred_engine_trait.md)
|
||||
- [Constants](engines/unreal_engine/constants.md)
|
||||
- [Formulas](engines/unreal_engine/formulas.md)
|
||||
|
||||
**Frostbite Engine** (EA - Battlefield, etc.)
|
||||
- [Camera Trait](engines/frostbite/camera_trait.md)
|
||||
- [Pred Engine Trait](engines/frostbite/pred_engine_trait.md)
|
||||
- [Constants](engines/frostbite/constants.md)
|
||||
- [Formulas](engines/frostbite/formulas.md)
|
||||
|
||||
**IW Engine** (Infinity Ward - Call of Duty)
|
||||
- [Camera Trait](engines/iw_engine/camera_trait.md)
|
||||
- [Pred Engine Trait](engines/iw_engine/pred_engine_trait.md)
|
||||
- [Constants](engines/iw_engine/constants.md)
|
||||
- [Formulas](engines/iw_engine/formulas.md)
|
||||
|
||||
**OpenGL Engine** (Canonical OpenGL)
|
||||
- [Camera Trait](engines/opengl_engine/camera_trait.md)
|
||||
- [Pred Engine Trait](engines/opengl_engine/pred_engine_trait.md)
|
||||
- [Constants](engines/opengl_engine/constants.md)
|
||||
- [Formulas](engines/opengl_engine/formulas.md)
|
||||
|
||||
### Utilities
|
||||
|
||||
**Color**
|
||||
- [Color](utility/color.md) - RGBA color with conversions
|
||||
|
||||
**Pattern Scanning & Memory**
|
||||
- [Pattern Scan](utility/pattern_scan.md) - Binary pattern search with wildcards
|
||||
- [PE Pattern Scan](utility/pe_pattern_scan.md) - PE file pattern scanning
|
||||
|
||||
**Reverse Engineering**
|
||||
- [External Rev Object](rev_eng/external_rev_object.md) - External process memory access
|
||||
- [Internal Rev Object](rev_eng/internal_rev_object.md) - Internal memory access
|
||||
|
||||
---
|
||||
|
||||
## ✨ Key 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.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Common Use Cases
|
||||
|
||||
### World-to-Screen Projection
|
||||
Project 3D world coordinates to 2D screen space for ESP overlays, UI elements, or visualization.
|
||||
|
||||
### Projectile Prediction
|
||||
Calculate aim points for moving targets considering projectile speed, gravity, and target velocity.
|
||||
|
||||
### Collision Detection
|
||||
Perform ray-casting, line tracing, and intersection tests for hit detection and physics.
|
||||
|
||||
### Pattern Scanning
|
||||
Search for byte patterns in memory for reverse engineering, modding, or tool development.
|
||||
|
||||
### Pathfinding
|
||||
Find optimal paths through 3D spaces using A* algorithm and navigation meshes.
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Gallery
|
||||
|
||||
<br>
|
||||
|
||||
[](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
|
||||
|
||||
<br>
|
||||
|
||||
![APEX Preview]
|
||||
|
||||
<br>
|
||||
|
||||
![BO2 Preview]
|
||||
|
||||
<br>
|
||||
|
||||
![CS2 Preview]
|
||||
|
||||
<br>
|
||||
|
||||
![TF2 Preview]
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Community & Support
|
||||
|
||||
- **Documentation**: [http://libomath.org](http://libomath.org)
|
||||
- **GitHub**: [orange-cpp/omath](https://github.com/orange-cpp/omath)
|
||||
- **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
|
||||
|
||||
OMath is open source and welcomes contributions! See [CONTRIBUTING.md](https://github.com/orange-cpp/omath/blob/master/CONTRIBUTING.md) for guidelines.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
|
||||
<!----------------------------------{ Images }--------------------------------->
|
||||
[APEX Preview]: images/showcase/apex.png
|
||||
[BO2 Preview]: images/showcase/cod_bo2.png
|
||||
[CS2 Preview]: images/showcase/cs2.jpeg
|
||||
[TF2 Preview]: images/showcase/tf2.jpg
|
||||
@@ -1,6 +1,6 @@
|
||||
# 📥Installation Guide
|
||||
# Installation
|
||||
|
||||
## Using vcpkg
|
||||
## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg
|
||||
**Note**: Support vcpkg for package management
|
||||
1. Install [vcpkg](https://github.com/microsoft/vcpkg)
|
||||
2. Run the following command to install the orange-math package:
|
||||
@@ -14,7 +14,21 @@ target_link_libraries(main PRIVATE omath::omath)
|
||||
```
|
||||
For detailed commands on installing different versions and more information, please refer to Microsoft's [official instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/overview).
|
||||
|
||||
## Build from source using CMake
|
||||
## <img width="28px" src="https://xmake.io/assets/img/logo.svg" /> Using xrepo
|
||||
**Note**: Support xrepo for package management
|
||||
1. Install [xmake](https://xmake.io/)
|
||||
2. Run the following command to install the omath package:
|
||||
```
|
||||
xrepo install omath
|
||||
```
|
||||
xmake.lua
|
||||
```xmake
|
||||
add_requires("omath")
|
||||
target("...")
|
||||
add_packages("omath")
|
||||
```
|
||||
|
||||
## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake
|
||||
1. **Preparation**
|
||||
|
||||
Install needed tools: cmake, clang, git, msvc (windows only).
|
||||
@@ -45,10 +59,10 @@ For detailed commands on installing different versions and more information, ple
|
||||
cmake --preset windows-release -S .
|
||||
cmake --build cmake-build/build/windows-release --target omath -j 6
|
||||
```
|
||||
Use **\<platform\>-\<build configuration\>** preset to build siutable version for yourself. Like **windows-release** or **linux-release**.
|
||||
Use **\<platform\>-\<build configuration\>** preset to build suitable version for yourself. Like **windows-release** or **linux-release**.
|
||||
|
||||
| Platform Name | Build Config |
|
||||
|---------------|---------------|
|
||||
|---------------|---------------|
|
||||
| windows | release/debug |
|
||||
| linux | release/debug |
|
||||
| darwin | release/debug |
|
||||
| darwin | release/debug |
|
||||
428
docs/linear_algebra/mat.md
Normal file
@@ -0,0 +1,428 @@
|
||||
# `omath::Mat` — Matrix class (C++20/23)
|
||||
|
||||
> Header: your project’s `mat.hpp` (requires `vector3.hpp`)
|
||||
> Namespace: `omath`
|
||||
> Requires: **C++23** (uses multi-parameter `operator[]`)
|
||||
> SIMD (optional): define **`OMATH_USE_AVX2`** to enable AVX2-accelerated multiplication for `float`/`double`.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`omath::Mat<Rows, Columns, Type, StoreType>` is a compile-time, fixed-size matrix with:
|
||||
|
||||
* **Row/column counts** as template parameters (no heap allocations).
|
||||
* **Row-major** or **column-major** storage (compile-time via `MatStoreType`).
|
||||
* **Arithmetic** and **linear algebra**: matrix × matrix, scalar ops, transpose, determinant, inverse (optional), etc.
|
||||
* **Transform helpers**: translation, axis rotations, look-at, perspective & orthographic projections.
|
||||
* **I/O helpers**: `to_string`/`to_wstring`/`to_u8string` and `std::formatter` specializations.
|
||||
|
||||
---
|
||||
|
||||
## Template parameters
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ----------- | ------------------------------------------------------------------------ | ----------- |
|
||||
| `Rows` | Number of rows (size_t, compile-time) | — |
|
||||
| `Columns` | Number of columns (size_t, compile-time) | — |
|
||||
| `Type` | Element type (arithmetic) | `float` |
|
||||
| `StoreType` | Storage order: `MatStoreType::ROW_MAJOR` or `MatStoreType::COLUMN_MAJOR` | `ROW_MAJOR` |
|
||||
|
||||
```cpp
|
||||
enum class MatStoreType : uint8_t { ROW_MAJOR = 0, COLUMN_MAJOR };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "mat.hpp"
|
||||
using omath::Mat;
|
||||
|
||||
// 4x4 float, row-major
|
||||
Mat<4,4> I = {
|
||||
{1,0,0,0},
|
||||
{0,1,0,0},
|
||||
{0,0,1,0},
|
||||
{0,0,0,1},
|
||||
};
|
||||
|
||||
// Multiply 4x4 transforms
|
||||
Mat<4,4> A = { {1,2,3,0},{0,1,4,0},{5,6,0,0},{0,0,0,1} };
|
||||
Mat<4,4> B = { {2,0,0,0},{0,2,0,0},{0,0,2,0},{0,0,0,1} };
|
||||
Mat<4,4> C = A * B; // matrix × matrix
|
||||
|
||||
// Scalar ops
|
||||
auto D = C * 0.5f; // scale all entries
|
||||
|
||||
// Indexing (C++23 multi-parameter operator[])
|
||||
float a03 = A[0,3]; // same as A.at(0,3)
|
||||
A[1,2] = 42.0f;
|
||||
|
||||
// Transpose, determinant, inverse
|
||||
auto AT = A.transposed();
|
||||
float det = A.determinant(); // only for square matrices
|
||||
auto inv = A.inverted(); // std::optional<Mat>; std::nullopt if non-invertible
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Multiplication requires the **same** `StoreType` and `Type` on both operands, and dimensions must match at compile time.
|
||||
|
||||
---
|
||||
|
||||
## Construction
|
||||
|
||||
```cpp
|
||||
Mat(); // zero-initialized
|
||||
Mat(std::initializer_list<std::initializer_list<Type>> rows);
|
||||
explicit Mat(const Type* raw_data); // copies Rows*Columns elements
|
||||
Mat(const Mat&); Mat(Mat&&);
|
||||
```
|
||||
|
||||
* **Zeroing/setting**
|
||||
|
||||
```cpp
|
||||
m.clear(); // set all entries to 0
|
||||
m.set(3.14f); // set all entries to a value
|
||||
```
|
||||
|
||||
* **Shape & metadata**
|
||||
|
||||
```cpp
|
||||
Mat<>::row_count(); // constexpr size_t
|
||||
Mat<>::columns_count(); // constexpr size_t
|
||||
Mat<>::size(); // constexpr MatSize {rows, columns}
|
||||
Mat<>::get_store_ordering(); // constexpr MatStoreType
|
||||
using ContainedType = Type; // alias
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Element access
|
||||
|
||||
```cpp
|
||||
T& at(size_t r, size_t c);
|
||||
T const& at(size_t r, size_t c) const;
|
||||
|
||||
T& operator[](size_t r, size_t c); // C++23
|
||||
T const& operator[](size_t r, size_t c) const; // C++23
|
||||
```
|
||||
|
||||
> **Bounds checking**
|
||||
> In debug builds you may enable/disable range checks via your compile-time macros (see the source guard around `at()`).
|
||||
|
||||
---
|
||||
|
||||
## Arithmetic
|
||||
|
||||
* **Matrix × matrix**
|
||||
|
||||
```cpp
|
||||
// (Rows x Columns) * (Columns x OtherColumns) -> (Rows x OtherColumns)
|
||||
template<size_t OtherColumns>
|
||||
Mat<Rows, OtherColumns, Type, StoreType>
|
||||
operator*(const Mat<Columns, OtherColumns, Type, StoreType>&) const;
|
||||
```
|
||||
|
||||
* Complexity: `O(Rows * Columns * OtherColumns)`.
|
||||
* AVX2-accelerated when `OMATH_USE_AVX2` is defined and `Type` is `float` or `double`.
|
||||
|
||||
* **Scalars**
|
||||
|
||||
```cpp
|
||||
Mat operator*(const Type& s) const; Mat& operator*=(const Type& s);
|
||||
Mat operator/(const Type& s) const; Mat& operator/=(const Type& s);
|
||||
```
|
||||
|
||||
* **Transpose**
|
||||
|
||||
```cpp
|
||||
Mat<Columns, Rows, Type, StoreType> transposed() const noexcept;
|
||||
```
|
||||
|
||||
* **Determinant (square only)**
|
||||
|
||||
```cpp
|
||||
Type determinant() const; // 1x1, 2x2 fast path; larger uses Laplace expansion
|
||||
```
|
||||
|
||||
* **Inverse (square only)**
|
||||
|
||||
```cpp
|
||||
std::optional<Mat> inverted() const; // nullopt if det == 0
|
||||
```
|
||||
|
||||
* **Minors & cofactors (square only)**
|
||||
|
||||
```cpp
|
||||
Mat<Rows-1, Columns-1, Type, StoreType> strip(size_t r, size_t c) const;
|
||||
Type minor(size_t r, size_t c) const;
|
||||
Type alg_complement(size_t r, size_t c) const; // cofactor
|
||||
```
|
||||
|
||||
* **Utilities**
|
||||
|
||||
```cpp
|
||||
Type sum() const noexcept;
|
||||
auto& raw_array(); // std::array<Type, Rows*Columns>&
|
||||
auto const& raw_array() const;
|
||||
```
|
||||
|
||||
* **Comparison / formatting**
|
||||
|
||||
```cpp
|
||||
bool operator==(const Mat&) const;
|
||||
bool operator!=(const Mat&) const;
|
||||
|
||||
std::string to_string() const noexcept;
|
||||
std::wstring to_wstring() const noexcept;
|
||||
std::u8string to_u8string() const noexcept;
|
||||
```
|
||||
|
||||
// std::formatter specialization provided for char, wchar_t, char8_t
|
||||
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
## Storage order notes
|
||||
|
||||
- **Row-major**: `index = row * Columns + column`
|
||||
- **Column-major**: `index = row + column * Rows`
|
||||
|
||||
Choose one **consistently** across your math types and shader conventions. Mixed orders are supported by the type system but not for cross-multiplying (store types must match).
|
||||
|
||||
---
|
||||
|
||||
## Transform helpers
|
||||
|
||||
### From vectors
|
||||
|
||||
```cpp
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||
Mat<1,4,T,St> mat_row_from_vector(const Vector3<T>& v);
|
||||
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||
Mat<4,1,T,St> mat_column_from_vector(const Vector3<T>& v);
|
||||
````
|
||||
|
||||
### Translation
|
||||
|
||||
```cpp
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||
Mat<4,4,T,St> mat_translation(const Vector3<T>& d) noexcept;
|
||||
```
|
||||
|
||||
### Axis rotations
|
||||
|
||||
```cpp
|
||||
// Angle type must provide angle.cos() and angle.sin()
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
|
||||
Mat<4,4,T,St> mat_rotation_axis_x(const Angle& a) noexcept;
|
||||
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
|
||||
Mat<4,4,T,St> mat_rotation_axis_y(const Angle& a) noexcept;
|
||||
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
|
||||
Mat<4,4,T,St> mat_rotation_axis_z(const Angle& a) noexcept;
|
||||
```
|
||||
|
||||
### Camera/view
|
||||
|
||||
```cpp
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||
Mat<4,4,T,St> mat_camera_view(const Vector3<T>& forward,
|
||||
const Vector3<T>& right,
|
||||
const Vector3<T>& up,
|
||||
const Vector3<T>& camera_origin) noexcept;
|
||||
```
|
||||
|
||||
### Perspective projections
|
||||
|
||||
```cpp
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||
Mat<4,4,T,St> mat_perspective_left_handed (float fov_deg, float aspect, float near, float far) noexcept;
|
||||
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||
Mat<4,4,T,St> mat_perspective_right_handed(float fov_deg, float aspect, float near, float far) noexcept;
|
||||
```
|
||||
|
||||
### Orthographic projections
|
||||
|
||||
```cpp
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||
Mat<4,4,T,St> mat_ortho_left_handed (T left, T right, T bottom, T top, T near, T far) noexcept;
|
||||
|
||||
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||
Mat<4,4,T,St> mat_ortho_right_handed(T left, T right, T bottom, T top, T near, T far) noexcept;
|
||||
```
|
||||
|
||||
### Look-at matrices
|
||||
|
||||
```cpp
|
||||
template<class T=float, MatStoreType St=COLUMN_MAJOR>
|
||||
Mat<4,4,T,St> mat_look_at_left_handed (const Vector3<T>& eye,
|
||||
const Vector3<T>& center,
|
||||
const Vector3<T>& up);
|
||||
|
||||
template<class T=float, MatStoreType St=COLUMN_MAJOR>
|
||||
Mat<4,4,T,St> mat_look_at_right_handed(const Vector3<T>& eye,
|
||||
const Vector3<T>& center,
|
||||
const Vector3<T>& up);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Screen-space helper
|
||||
|
||||
```cpp
|
||||
template<class Type=float>
|
||||
static constexpr Mat<4,4> to_screen_mat(const Type& screen_w, const Type& screen_h) noexcept;
|
||||
// Maps NDC to screen space (origin top-left, y down)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### 1) Building a left-handed camera and perspective
|
||||
|
||||
```cpp
|
||||
using V3 = omath::Vector3<float>;
|
||||
using M4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
|
||||
|
||||
V3 eye{0, 1, -5}, center{0, 0, 0}, up{0, 1, 0};
|
||||
M4 view = omath::mat_look_at_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(eye, center, up);
|
||||
|
||||
float fov = 60.f, aspect = 16.f/9.f, n = 0.1f, f = 100.f;
|
||||
M4 proj = omath::mat_perspective_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(fov, aspect, n, f);
|
||||
|
||||
// final VP
|
||||
M4 vp = proj * view;
|
||||
```
|
||||
|
||||
### 2) Inverting a transform safely
|
||||
|
||||
```cpp
|
||||
omath::Mat<4,4> T = omath::mat_translation(omath::Vector3<float>{2,3,4});
|
||||
if (auto inv = T.inverted()) {
|
||||
// use *inv
|
||||
} else {
|
||||
// handle non-invertible
|
||||
}
|
||||
```
|
||||
|
||||
### 3) Formatting for logs
|
||||
|
||||
```cpp
|
||||
omath::Mat<2,2> A = { {1,2},{3,4} };
|
||||
std::string s = A.to_string(); // "[[ 1.000, 2.000]\n [ 3.000, 4.000]]"
|
||||
std::string f = std::format("A = {}", A); // uses std::formatter
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
* **Cache-friendly kernels** per storage order when AVX2 is not enabled.
|
||||
* **AVX2 path** (`OMATH_USE_AVX2`) for `float`/`double` implements FMAs with 256-bit vectors for both row-major and column-major multiplication.
|
||||
* Complexity for `A(R×K) * B(K×C)`: **O(RKC)** regardless of storage order.
|
||||
|
||||
---
|
||||
|
||||
## Constraints & concepts
|
||||
|
||||
```cpp
|
||||
template<typename M1, typename M2>
|
||||
concept MatTemplateEqual =
|
||||
(M1::rows == M2::rows) &&
|
||||
(M1::columns == M2::columns) &&
|
||||
std::is_same_v<typename M1::value_type, typename M2::value_type> &&
|
||||
(M1::store_type == M2::store_type);
|
||||
```
|
||||
|
||||
> Use this concept to constrain generic functions that operate on like-shaped matrices.
|
||||
|
||||
---
|
||||
|
||||
## Exceptions
|
||||
|
||||
* `std::invalid_argument` — initializer list dimensions mismatch.
|
||||
* `std::out_of_range` — out-of-bounds in `at()` when bounds checking is active (see source guard).
|
||||
* `inverted()` does **not** throw; returns `std::nullopt` if `determinant() == 0`.
|
||||
|
||||
---
|
||||
|
||||
## Build switches
|
||||
|
||||
* **`OMATH_USE_AVX2`** — enable AVX2 vectorized multiplication paths (`<immintrin.h>` required).
|
||||
* **Debug checks** — the `at()` method contains a conditional range check; refer to the preprocessor guard in the code to enable/disable in your configuration.
|
||||
|
||||
---
|
||||
|
||||
## Known requirements & interoperability
|
||||
|
||||
* **C++23** is required for multi-parameter `operator[]`. If you target pre-C++23, use `at(r,c)` instead.
|
||||
* All binary operations require matching `Type` and `StoreType`. Convert explicitly if needed.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath::Vector3<T>`
|
||||
* Projection helpers: `mat_perspective_*`, `mat_ortho_*`
|
||||
* View helpers: `mat_look_at_*`, `mat_camera_view`
|
||||
* Construction helpers: `mat_row_from_vector`, `mat_column_from_vector`, `mat_translation`, `mat_rotation_axis_*`
|
||||
|
||||
---
|
||||
|
||||
## Appendix: API summary (signatures)
|
||||
|
||||
```cpp
|
||||
// Core
|
||||
Mat(); Mat(const Mat&); Mat(Mat&&);
|
||||
Mat(std::initializer_list<std::initializer_list<Type>>);
|
||||
explicit Mat(const Type* raw);
|
||||
Mat& operator=(const Mat&); Mat& operator=(Mat&&);
|
||||
|
||||
static constexpr size_t row_count();
|
||||
static constexpr size_t columns_count();
|
||||
static consteval MatSize size();
|
||||
static constexpr MatStoreType get_store_ordering();
|
||||
|
||||
T& at(size_t r, size_t c);
|
||||
T const& at(size_t r, size_t c) const;
|
||||
T& operator[](size_t r, size_t c);
|
||||
T const& operator[](size_t r, size_t c) const;
|
||||
|
||||
void clear();
|
||||
void set(const Type& v);
|
||||
Type sum() const noexcept;
|
||||
|
||||
template<size_t OC> Mat<Rows,OC,Type,StoreType> operator*(const Mat<Columns,OC,Type,StoreType>&) const;
|
||||
Mat& operator*=(const Type&); Mat operator*(const Type&) const;
|
||||
Mat& operator/=(const Type&); Mat operator/(const Type&) const;
|
||||
|
||||
Mat<Columns,Rows,Type,StoreType> transposed() const noexcept;
|
||||
Type determinant() const; // square only
|
||||
std::optional<Mat> inverted() const; // square only
|
||||
|
||||
Mat<Rows-1,Columns-1,Type,StoreType> strip(size_t r, size_t c) const;
|
||||
Type minor(size_t r, size_t c) const;
|
||||
Type alg_complement(size_t r, size_t c) const;
|
||||
|
||||
auto& raw_array(); auto const& raw_array() const;
|
||||
std::string to_string() const noexcept;
|
||||
std::wstring to_wstring() const noexcept;
|
||||
std::u8string to_u8string() const noexcept;
|
||||
|
||||
bool operator==(const Mat&) const;
|
||||
bool operator!=(const Mat&) const;
|
||||
|
||||
// Helpers (see sections above)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
173
docs/linear_algebra/triangle.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# `omath::Triangle` — Simple 3D triangle utility
|
||||
|
||||
> Header: your project’s `triangle.hpp`
|
||||
> Namespace: `omath`
|
||||
> Depends on: `omath::Vector3<float>` (from `vector3.hpp`)
|
||||
|
||||
A tiny helper around three `Vector3<float>` vertices with convenience methods for normals, edge vectors/lengths, a right-angle test (at **`v2`**), and the triangle centroid.
|
||||
|
||||
> **Note on the template parameter**
|
||||
>
|
||||
> The class is declared as `template<class Vector> class Triangle`, but the stored vertices are concretely `Vector3<float>`. In practice this type is currently **fixed to `Vector3<float>`**. You can ignore the template parameter or refactor to store `Vector` if you intend true genericity.
|
||||
|
||||
---
|
||||
|
||||
## Vertex layout & naming
|
||||
|
||||
```
|
||||
v1
|
||||
|\
|
||||
| \
|
||||
a | \ hypot = |v1 - v3|
|
||||
| \
|
||||
v2 -- v3
|
||||
b
|
||||
|
||||
a = |v1 - v2| (side_a_length)
|
||||
b = |v3 - v2| (side_b_length)
|
||||
```
|
||||
|
||||
* **`side_a_vector()`** = `v1 - v2` (points from v2 → v1)
|
||||
* **`side_b_vector()`** = `v3 - v2` (points from v2 → v3)
|
||||
* **Right-angle check** uses `a² + b² ≈ hypot²` with an epsilon of `1e-4`.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "triangle.hpp"
|
||||
using omath::Vector3;
|
||||
using omath::Triangle;
|
||||
|
||||
Triangle<void> tri( // template arg unused; any placeholder ok
|
||||
Vector3<float>{0,0,0}, // v1
|
||||
Vector3<float>{0,0,1}, // v2 (right angle is tested at v2)
|
||||
Vector3<float>{1,0,1} // v3
|
||||
);
|
||||
|
||||
auto n = tri.calculate_normal(); // unit normal (right-handed: (v3-v2) × (v1-v2))
|
||||
float a = tri.side_a_length(); // |v1 - v2|
|
||||
float b = tri.side_b_length(); // |v3 - v2|
|
||||
float hyp = tri.hypot(); // |v1 - v3|
|
||||
bool rect = tri.is_rectangular(); // true if ~right triangle at v2
|
||||
auto C = tri.mid_point(); // centroid (average of v1,v2,v3)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data members
|
||||
|
||||
```cpp
|
||||
Vector3<float> m_vertex1; // v1
|
||||
Vector3<float> m_vertex2; // v2 (the corner tested by is_rectangular)
|
||||
Vector3<float> m_vertex3; // v3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Constructors
|
||||
|
||||
```cpp
|
||||
constexpr Triangle() = default;
|
||||
constexpr Triangle(const Vector3<float>& v1,
|
||||
const Vector3<float>& v2,
|
||||
const Vector3<float>& v3);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Methods
|
||||
|
||||
```cpp
|
||||
// Normal (unit) using right-handed cross product:
|
||||
// n = (v3 - v2) × (v1 - v2), then normalized()
|
||||
[[nodiscard]] constexpr Vector3<float> calculate_normal() const;
|
||||
|
||||
// Edge lengths with the naming from the diagram
|
||||
[[nodiscard]] float side_a_length() const; // |v1 - v2|
|
||||
[[nodiscard]] float side_b_length() const; // |v3 - v2|
|
||||
|
||||
// Edge vectors (from v2 to the other vertex)
|
||||
[[nodiscard]] constexpr Vector3<float> side_a_vector() const; // v1 - v2
|
||||
[[nodiscard]] constexpr Vector3<float> side_b_vector() const; // v3 - v2
|
||||
|
||||
// Hypotenuse length between v1 and v3
|
||||
[[nodiscard]] constexpr float hypot() const; // |v1 - v3|
|
||||
|
||||
// Right-triangle check at vertex v2 (Pythagoras with epsilon 1e-4)
|
||||
[[nodiscard]] constexpr bool is_rectangular() const;
|
||||
|
||||
// Centroid of the triangle (average of the 3 vertices)
|
||||
[[nodiscard]] constexpr Vector3<float> mid_point() const; // actually the centroid
|
||||
```
|
||||
|
||||
### Notes & edge cases
|
||||
|
||||
* **Normal direction** follows the right-hand rule for the ordered vertices `{v2 → v3} × {v2 → v1}`.
|
||||
Swap vertex order to flip the normal.
|
||||
* **Degenerate triangles** (collinear or overlapping vertices) yield a **zero vector** normal (since `normalized()` of the zero vector returns the zero vector in your math types).
|
||||
* **`mid_point()` is the centroid**, not the midpoint of any single edge. If you need the midpoint of edge `v1–v2`, use `(m_vertex1 + m_vertex2) * 0.5f`.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Area and plane from existing API
|
||||
|
||||
```cpp
|
||||
const auto a = tri.side_a_vector();
|
||||
const auto b = tri.side_b_vector();
|
||||
const auto n = b.cross(a); // unnormalized normal
|
||||
float area = 0.5f * n.length(); // triangle area
|
||||
|
||||
// Plane equation n̂·(x - v2) = 0
|
||||
auto nhat = n.length() > 0 ? n / n.length() : n;
|
||||
float d = -nhat.dot(tri.m_vertex2);
|
||||
```
|
||||
|
||||
### Project a point onto the triangle’s plane
|
||||
|
||||
```cpp
|
||||
Vector3<float> p{0.3f, 1.0f, 0.7f};
|
||||
auto n = tri.calculate_normal();
|
||||
float t = n.dot(tri.m_vertex2 - p); // signed distance along normal
|
||||
auto projected = p + n * t; // on-plane projection
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API summary (signatures)
|
||||
|
||||
```cpp
|
||||
class Triangle final {
|
||||
public:
|
||||
constexpr Triangle();
|
||||
constexpr Triangle(const Vector3<float>& v1,
|
||||
const Vector3<float>& v2,
|
||||
const Vector3<float>& v3);
|
||||
|
||||
Vector3<float> m_vertex1, m_vertex2, m_vertex3;
|
||||
|
||||
[[nodiscard]] constexpr Vector3<float> calculate_normal() const;
|
||||
[[nodiscard]] float side_a_length() const;
|
||||
[[nodiscard]] float side_b_length() const;
|
||||
[[nodiscard]] constexpr Vector3<float> side_a_vector() const;
|
||||
[[nodiscard]] constexpr Vector3<float> side_b_vector() const;
|
||||
[[nodiscard]] constexpr float hypot() const;
|
||||
[[nodiscard]] constexpr bool is_rectangular() const;
|
||||
[[nodiscard]] constexpr Vector3<float> mid_point() const;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Suggestions (optional improvements)
|
||||
|
||||
* If generic vectors are intended, store `Vector m_vertex*;` and constrain `Vector` to the required ops (`-`, `cross`, `normalized`, `distance_to`, `+`, `/`).
|
||||
* Consider renaming `mid_point()` → `centroid()` to avoid ambiguity.
|
||||
* Expose an `area()` helper and (optionally) a barycentric coordinate routine if you plan to use this in rasterization or intersection tests.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
300
docs/linear_algebra/vector2.md
Normal file
@@ -0,0 +1,300 @@
|
||||
# `omath::Vector2` — 2D vector (C++20/23)
|
||||
|
||||
> Header: your project’s `vector2.hpp`
|
||||
> Namespace: `omath`
|
||||
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
|
||||
|
||||
`Vector2<Type>` is a lightweight, POD-like 2D math type with arithmetic, geometry helpers, comparisons, hashing (for `float`), optional ImGui interop, and `std::formatter` support.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "vector2.hpp"
|
||||
using omath::Vector2;
|
||||
|
||||
using Vec2f = Vector2<float>;
|
||||
|
||||
Vec2f a{3.f, 4.f};
|
||||
Vec2f b{1.f, 2.f};
|
||||
|
||||
auto d = a.distance_to(b); // ≈ 3.1623
|
||||
auto dot = a.dot(b); // 11
|
||||
auto len = a.length(); // 5
|
||||
auto unit_a = a.normalized(); // (0.6, 0.8)
|
||||
|
||||
// Component-wise mutate
|
||||
Vec2f c{2, 3};
|
||||
c *= b; // c -> (2*1, 3*2) = (2, 6)
|
||||
|
||||
// Scalar ops (non-mutating + mutating)
|
||||
auto scaled = a * 0.5f; // (1.5, 2)
|
||||
a *= 2.f; // (6, 8)
|
||||
|
||||
// Ordering by length()
|
||||
bool shorter = (b < a);
|
||||
|
||||
// Formatted printing
|
||||
std::string s = std::format("a = {}", a); // "a = [6, 8]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Members
|
||||
|
||||
```cpp
|
||||
Type x{0};
|
||||
Type y{0};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Constructors
|
||||
|
||||
```cpp
|
||||
constexpr Vector2(); // (0,0)
|
||||
constexpr Vector2(const Type& x, const Type& y) noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Equality & ordering
|
||||
|
||||
```cpp
|
||||
constexpr bool operator==(const Vector2&) const noexcept; // component-wise equality
|
||||
constexpr bool operator!=(const Vector2&) const noexcept;
|
||||
|
||||
bool operator< (const Vector2&) const noexcept; // compares by length()
|
||||
bool operator> (const Vector2&) const noexcept;
|
||||
bool operator<=(const Vector2&) const noexcept;
|
||||
bool operator>=(const Vector2&) const noexcept;
|
||||
```
|
||||
|
||||
> **Note:** `<`, `>`, `<=`, `>=` order vectors by **magnitude** (not lexicographically).
|
||||
|
||||
---
|
||||
|
||||
## Arithmetic
|
||||
|
||||
### With another vector (component-wise, **mutating**)
|
||||
|
||||
```cpp
|
||||
Vector2& operator+=(const Vector2&) noexcept;
|
||||
Vector2& operator-=(const Vector2&) noexcept;
|
||||
Vector2& operator*=(const Vector2&) noexcept; // Hadamard product (x*=x, y*=y)
|
||||
Vector2& operator/=(const Vector2&) noexcept;
|
||||
```
|
||||
|
||||
> Non-mutating `v * u` / `v / u` (vector × vector) are **not** provided.
|
||||
> Use `v *= u` (mutating) or build a new vector explicitly.
|
||||
|
||||
### With a scalar
|
||||
|
||||
```cpp
|
||||
Vector2& operator*=(const Type& v) noexcept;
|
||||
Vector2& operator/=(const Type& v) noexcept;
|
||||
Vector2& operator+=(const Type& v) noexcept;
|
||||
Vector2& operator-=(const Type& v) noexcept;
|
||||
|
||||
constexpr Vector2 operator*(const Type& v) const noexcept;
|
||||
constexpr Vector2 operator/(const Type& v) const noexcept;
|
||||
```
|
||||
|
||||
### Binary (+/−) with another vector (non-mutating)
|
||||
|
||||
```cpp
|
||||
constexpr Vector2 operator+(const Vector2&) const noexcept;
|
||||
constexpr Vector2 operator-(const Vector2&) const noexcept;
|
||||
```
|
||||
|
||||
### Unary
|
||||
|
||||
```cpp
|
||||
constexpr Vector2 operator-() const noexcept; // negation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Geometry & helpers
|
||||
|
||||
```cpp
|
||||
Type distance_to (const Vector2&) const noexcept; // sqrt of squared distance
|
||||
constexpr Type distance_to_sqr(const Vector2&) const noexcept;
|
||||
|
||||
constexpr Type dot(const Vector2&) const noexcept;
|
||||
|
||||
#ifndef _MSC_VER
|
||||
constexpr Type length() const noexcept; // uses std::hypot; constexpr on non-MSVC
|
||||
constexpr Vector2 normalized() const noexcept; // returns *this if length==0
|
||||
#else
|
||||
Type length() const noexcept;
|
||||
Vector2 normalized() const noexcept;
|
||||
#endif
|
||||
|
||||
constexpr Type length_sqr() const noexcept; // x*x + y*y
|
||||
Vector2& abs() noexcept; // component-wise absolute (constexpr-friendly impl)
|
||||
|
||||
constexpr Type sum() const noexcept; // x + y
|
||||
constexpr std::tuple<Type, Type> as_tuple() const noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ImGui integration (optional)
|
||||
|
||||
Define `OMATH_IMGUI_INTEGRATION` **before** including the header.
|
||||
|
||||
```cpp
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
constexpr ImVec2 to_im_vec2() const noexcept; // {float(x), float(y)}
|
||||
static Vector2 from_im_vec2(const ImVec2&) noexcept;
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hashing & formatting
|
||||
|
||||
* **Hash (for `Vector2<float>`)**
|
||||
|
||||
```cpp
|
||||
template<> struct std::hash<omath::Vector2<float>> {
|
||||
std::size_t operator()(const omath::Vector2<float>&) const noexcept;
|
||||
};
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
std::unordered_set<omath::Vector2<float>> set;
|
||||
set.insert({1.f, 2.f});
|
||||
```
|
||||
|
||||
* **`std::formatter`** (for any `Type`)
|
||||
|
||||
```cpp
|
||||
// prints "[x, y]" for char / wchar_t / char8_t
|
||||
template<class Type>
|
||||
struct std::formatter<omath::Vector2<Type>>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & invariants
|
||||
|
||||
* `Type` must be arithmetic (e.g., `float`, `double`, `int`, …).
|
||||
* `normalized()` returns the input unchanged if `length() == 0`.
|
||||
* `abs()` uses a constexpr-friendly implementation (not `std::abs`) to allow compile-time evaluation.
|
||||
* On MSVC, `length()`/`normalized()` are not `constexpr` due to library constraints; they’re still `noexcept`.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Component-wise operations and scalar scaling
|
||||
|
||||
```cpp
|
||||
omath::Vector2<float> u{2, 3}, v{4, 5};
|
||||
|
||||
u += v; // (6, 8)
|
||||
u -= v; // (2, 3)
|
||||
u *= v; // (8, 15) Hadamard product (mutates u)
|
||||
auto w = v * 2.0f; // (8, 10) non-mutating scalar multiply
|
||||
```
|
||||
|
||||
### Geometry helpers
|
||||
|
||||
```cpp
|
||||
omath::Vector2<double> p{0.0, 0.0}, q{3.0, 4.0};
|
||||
|
||||
auto dsq = p.distance_to_sqr(q); // 25
|
||||
auto d = p.distance_to(q); // 5
|
||||
auto dot = p.dot(q); // 0
|
||||
auto uq = q.normalized(); // (0.6, 0.8)
|
||||
```
|
||||
|
||||
### Using as a key in unordered containers (`float`)
|
||||
|
||||
```cpp
|
||||
std::unordered_map<omath::Vector2<float>, int> counts;
|
||||
counts[{1.f, 2.f}] = 42;
|
||||
```
|
||||
|
||||
### ImGui interop
|
||||
|
||||
```cpp
|
||||
#define OMATH_IMGUI_INTEGRATION
|
||||
#include "vector2.hpp"
|
||||
|
||||
omath::Vector2<float> v{10, 20};
|
||||
ImVec2 iv = v.to_im_vec2();
|
||||
v = omath::Vector2<float>::from_im_vec2(iv);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API summary (signatures)
|
||||
|
||||
```cpp
|
||||
// Constructors
|
||||
constexpr Vector2();
|
||||
constexpr Vector2(const Type& x, const Type& y) noexcept;
|
||||
|
||||
// Equality & ordering
|
||||
constexpr bool operator==(const Vector2&) const noexcept;
|
||||
constexpr bool operator!=(const Vector2&) const noexcept;
|
||||
bool operator< (const Vector2&) const noexcept;
|
||||
bool operator> (const Vector2&) const noexcept;
|
||||
bool operator<=(const Vector2&) const noexcept;
|
||||
bool operator>=(const Vector2&) const noexcept;
|
||||
|
||||
// Compound (vector/vector and scalar)
|
||||
Vector2& operator+=(const Vector2&) noexcept;
|
||||
Vector2& operator-=(const Vector2&) noexcept;
|
||||
Vector2& operator*=(const Vector2&) noexcept;
|
||||
Vector2& operator/=(const Vector2&) noexcept;
|
||||
Vector2& operator*=(const Type&) noexcept;
|
||||
Vector2& operator/=(const Type&) noexcept;
|
||||
Vector2& operator+=(const Type&) noexcept;
|
||||
Vector2& operator-=(const Type&) noexcept;
|
||||
|
||||
// Non-mutating arithmetic
|
||||
constexpr Vector2 operator+(const Vector2&) const noexcept;
|
||||
constexpr Vector2 operator-(const Vector2&) const noexcept;
|
||||
constexpr Vector2 operator*(const Type&) const noexcept;
|
||||
constexpr Vector2 operator/(const Type&) const noexcept;
|
||||
constexpr Vector2 operator-() const noexcept;
|
||||
|
||||
// Geometry
|
||||
Type distance_to(const Vector2&) const noexcept;
|
||||
constexpr Type distance_to_sqr(const Vector2&) const noexcept;
|
||||
constexpr Type dot(const Vector2&) const noexcept;
|
||||
Type length() const noexcept; // constexpr on non-MSVC
|
||||
Vector2 normalized() const noexcept; // constexpr on non-MSVC
|
||||
constexpr Type length_sqr() const noexcept;
|
||||
Vector2& abs() noexcept;
|
||||
constexpr Type sum() const noexcept;
|
||||
constexpr std::tuple<Type,Type> as_tuple() const noexcept;
|
||||
|
||||
// ImGui (optional)
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
constexpr ImVec2 to_im_vec2() const noexcept;
|
||||
static Vector2 from_im_vec2(const ImVec2&) noexcept;
|
||||
#endif
|
||||
|
||||
// Hash (float) and formatter are specialized in the header
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vector3 Documentation](vector3.md) - 3D vector operations
|
||||
- [Vector4 Documentation](vector4.md) - 4D vector operations
|
||||
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
|
||||
- [Tutorials](../tutorials.md) - Step-by-step examples
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
307
docs/linear_algebra/vector3.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# `omath::Vector3` — 3D vector (C++20/23)
|
||||
|
||||
> Header: your project’s `vector3.hpp`
|
||||
> Namespace: `omath`
|
||||
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
|
||||
> Depends on: `omath::Vector2<Type>` (base class), `omath::Angle` (for `angle_between`)
|
||||
> C++: uses `std::expected` ⇒ **C++23** recommended (or a backport)
|
||||
|
||||
`Vector3<Type>` extends `Vector2<Type>` with a `z` component and 3D operations: arithmetic, geometry (dot, cross, distance), normalization, angle-between with robust error signaling, hashing (for `float`) and `std::formatter` support.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "vector3.hpp"
|
||||
using omath::Vector3;
|
||||
|
||||
using Vec3f = Vector3<float>;
|
||||
|
||||
Vec3f a{3, 4, 0};
|
||||
Vec3f b{1, 2, 2};
|
||||
|
||||
auto d = a.distance_to(b); // Euclidean distance
|
||||
auto dot = a.dot(b); // 3*1 + 4*2 + 0*2 = 11
|
||||
auto cr = a.cross(b); // (8, -6, 2)
|
||||
auto len = a.length(); // hypot(x,y,z)
|
||||
auto unit = a.normalized(); // safe normalize (returns a if length==0)
|
||||
|
||||
if (auto ang = a.angle_between(b)) {
|
||||
float deg = ang->as_degrees(); // [0, 180], clamped
|
||||
} else {
|
||||
// vectors have zero length -> no defined angle
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data members
|
||||
|
||||
```cpp
|
||||
Type x{0}; // inherited from Vector2<Type>
|
||||
Type y{0}; // inherited from Vector2<Type>
|
||||
Type z{0};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Constructors
|
||||
|
||||
```cpp
|
||||
constexpr Vector3() noexcept;
|
||||
constexpr Vector3(const Type& x, const Type& y, const Type& z) noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Equality & ordering
|
||||
|
||||
```cpp
|
||||
constexpr bool operator==(const Vector3&) const noexcept; // component-wise
|
||||
constexpr bool operator!=(const Vector3&) const noexcept;
|
||||
|
||||
bool operator< (const Vector3&) const noexcept; // compare by length()
|
||||
bool operator> (const Vector3&) const noexcept;
|
||||
bool operator<=(const Vector3&) const noexcept;
|
||||
bool operator>=(const Vector3&) const noexcept;
|
||||
```
|
||||
|
||||
> **Note:** Ordering uses **magnitude**, not lexicographic order.
|
||||
|
||||
---
|
||||
|
||||
## Arithmetic (mutating)
|
||||
|
||||
Component-wise with another vector:
|
||||
|
||||
```cpp
|
||||
Vector3& operator+=(const Vector3&) noexcept;
|
||||
Vector3& operator-=(const Vector3&) noexcept;
|
||||
Vector3& operator*=(const Vector3&) noexcept; // Hadamard product
|
||||
Vector3& operator/=(const Vector3&) noexcept;
|
||||
```
|
||||
|
||||
With a scalar:
|
||||
|
||||
```cpp
|
||||
Vector3& operator*=(const Type& v) noexcept;
|
||||
Vector3& operator/=(const Type& v) noexcept;
|
||||
Vector3& operator+=(const Type& v) noexcept;
|
||||
Vector3& operator-=(const Type& v) noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Arithmetic (non-mutating)
|
||||
|
||||
```cpp
|
||||
constexpr Vector3 operator-() const noexcept;
|
||||
constexpr Vector3 operator+(const Vector3&) const noexcept;
|
||||
constexpr Vector3 operator-(const Vector3&) const noexcept;
|
||||
constexpr Vector3 operator*(const Vector3&) const noexcept; // Hadamard
|
||||
constexpr Vector3 operator/(const Vector3&) const noexcept; // Hadamard
|
||||
constexpr Vector3 operator*(const Type& scalar) const noexcept;
|
||||
constexpr Vector3 operator/(const Type& scalar) const noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Geometry & helpers
|
||||
|
||||
```cpp
|
||||
// Distances & lengths
|
||||
Type distance_to(const Vector3&) const; // sqrt of squared distance
|
||||
constexpr Type distance_to_sqr(const Vector3&) const noexcept;
|
||||
#ifndef _MSC_VER
|
||||
constexpr Type length() const; // hypot(x,y,z)
|
||||
constexpr Type length_2d() const; // 2D length from base
|
||||
constexpr Vector3 normalized() const; // returns *this if length==0
|
||||
#else
|
||||
Type length() const noexcept;
|
||||
Type length_2d() const noexcept;
|
||||
Vector3 normalized() const noexcept;
|
||||
#endif
|
||||
constexpr Type length_sqr() const noexcept;
|
||||
|
||||
// Products
|
||||
constexpr Type dot(const Vector3&) const noexcept;
|
||||
constexpr Vector3 cross(const Vector3&) const noexcept; // right-handed
|
||||
|
||||
// Sums & tuples
|
||||
constexpr Type sum() const noexcept; // x + y + z
|
||||
constexpr Type sum_2d() const noexcept; // x + y
|
||||
constexpr auto as_tuple() const noexcept -> std::tuple<Type,Type,Type>;
|
||||
|
||||
// Utilities
|
||||
Vector3& abs() noexcept; // component-wise absolute
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Angles & orthogonality
|
||||
|
||||
```cpp
|
||||
enum class Vector3Error { IMPOSSIBLE_BETWEEN_ANGLE };
|
||||
|
||||
// Angle in degrees, clamped to [0,180]. Error if any vector has zero length.
|
||||
std::expected<
|
||||
omath::Angle<float, 0.f, 180.f, AngleFlags::Clamped>,
|
||||
Vector3Error
|
||||
> angle_between(const Vector3& other) const noexcept;
|
||||
|
||||
bool is_perpendicular(const Vector3& other) const noexcept; // true if angle == 90°
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hashing & formatting
|
||||
|
||||
* **Hash (for `Vector3<float>`)**
|
||||
|
||||
```cpp
|
||||
template<> struct std::hash<omath::Vector3<float>> {
|
||||
std::size_t operator()(const omath::Vector3<float>&) const noexcept;
|
||||
};
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
std::unordered_map<omath::Vector3<float>, int> counts;
|
||||
counts[{1.f, 2.f, 3.f}] = 7;
|
||||
```
|
||||
|
||||
* **`std::formatter`** (all character types)
|
||||
|
||||
```cpp
|
||||
template<class Type>
|
||||
struct std::formatter<omath::Vector3<Type>>; // prints "[x, y, z]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error handling
|
||||
|
||||
* `angle_between()` returns `std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE)` if either vector length is zero.
|
||||
* Other operations are total for arithmetic `Type` (no throwing behavior in this class).
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Cross product & perpendicular check
|
||||
|
||||
```cpp
|
||||
omath::Vector3<float> x{1,0,0}, y{0,1,0};
|
||||
auto z = x.cross(y); // (0,0,1)
|
||||
bool perp = x.is_perpendicular(y); // true
|
||||
```
|
||||
|
||||
### Safe normalization and angle
|
||||
|
||||
```cpp
|
||||
omath::Vector3<float> u{0,0,0}, v{1,1,0};
|
||||
auto nu = u.normalized(); // returns {0,0,0}
|
||||
if (auto ang = u.angle_between(v)) {
|
||||
// won't happen: u has zero length → error
|
||||
} else {
|
||||
// handle degenerate case
|
||||
}
|
||||
```
|
||||
|
||||
### Hadamard vs scalar multiply
|
||||
|
||||
```cpp
|
||||
omath::Vector3<float> a{2,3,4}, b{5,6,7};
|
||||
auto h = a * b; // (10, 18, 28) component-wise
|
||||
auto s = a * 2.f; // (4, 6, 8) scalar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API summary (signatures)
|
||||
|
||||
```cpp
|
||||
// Ctors
|
||||
constexpr Vector3() noexcept;
|
||||
constexpr Vector3(const Type& x, const Type& y, const Type& z) noexcept;
|
||||
|
||||
// Equality & ordering
|
||||
constexpr bool operator==(const Vector3&) const noexcept;
|
||||
constexpr bool operator!=(const Vector3&) const noexcept;
|
||||
bool operator< (const Vector3&) const noexcept;
|
||||
bool operator> (const Vector3&) const noexcept;
|
||||
bool operator<=(const Vector3&) const noexcept;
|
||||
bool operator>=(const Vector3&) const noexcept;
|
||||
|
||||
// Mutating arithmetic
|
||||
Vector3& operator+=(const Vector3&) noexcept;
|
||||
Vector3& operator-=(const Vector3&) noexcept;
|
||||
Vector3& operator*=(const Vector3&) noexcept;
|
||||
Vector3& operator/=(const Vector3&) noexcept;
|
||||
Vector3& operator*=(const Type&) noexcept;
|
||||
Vector3& operator/=(const Type&) noexcept;
|
||||
Vector3& operator+=(const Type&) noexcept;
|
||||
Vector3& operator-=(const Type&) noexcept;
|
||||
|
||||
// Non-mutating arithmetic
|
||||
constexpr Vector3 operator-() const noexcept;
|
||||
constexpr Vector3 operator+(const Vector3&) const noexcept;
|
||||
constexpr Vector3 operator-(const Vector3&) const noexcept;
|
||||
constexpr Vector3 operator*(const Vector3&) const noexcept;
|
||||
constexpr Vector3 operator/(const Vector3&) const noexcept;
|
||||
constexpr Vector3 operator*(const Type&) const noexcept;
|
||||
constexpr Vector3 operator/(const Type&) const noexcept;
|
||||
|
||||
// Geometry
|
||||
Type distance_to(const Vector3&) const;
|
||||
constexpr Type distance_to_sqr(const Vector3&) const noexcept;
|
||||
#ifndef _MSC_VER
|
||||
constexpr Type length() const;
|
||||
constexpr Type length_2d() const;
|
||||
constexpr Vector3 normalized() const;
|
||||
#else
|
||||
Type length() const noexcept;
|
||||
Type length_2d() const noexcept;
|
||||
Vector3 normalized() const noexcept;
|
||||
#endif
|
||||
constexpr Type length_sqr() const noexcept;
|
||||
constexpr Type dot(const Vector3&) const noexcept;
|
||||
constexpr Vector3 cross(const Vector3&) const noexcept;
|
||||
|
||||
Vector3& abs() noexcept;
|
||||
constexpr Type sum() const noexcept;
|
||||
constexpr Type sum_2d() const noexcept;
|
||||
constexpr auto as_tuple() const noexcept -> std::tuple<Type,Type,Type>;
|
||||
|
||||
// Angles
|
||||
std::expected<omath::Angle<float,0.f,180.f,AngleFlags::Clamped>, omath::Vector3Error>
|
||||
angle_between(const Vector3&) const noexcept;
|
||||
bool is_perpendicular(const Vector3&) const noexcept;
|
||||
|
||||
// Hash (float) and formatter specializations provided below the class
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
* Inherits all public API of `Vector2<Type>` (including `x`, `y`, many operators, and helpers used internally).
|
||||
* `normalized()` returns the original vector if its length is zero (no NaNs).
|
||||
* `cross()` uses the standard right-handed definition.
|
||||
* `length()`/`normalized()` are `constexpr` on non-MSVC; MSVC builds provide `noexcept` runtime versions.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vector2 Documentation](vector2.md) - 2D vector operations
|
||||
- [Vector4 Documentation](vector4.md) - 4D vector operations
|
||||
- [Angle Documentation](../trigonometry/angle.md) - Working with angles
|
||||
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
|
||||
- [Tutorials](../tutorials.md) - Practical examples including vector math
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
253
docs/linear_algebra/vector4.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# `omath::Vector4` — 4D vector (C++20/23)
|
||||
|
||||
> Header: your project’s `vector4.hpp`
|
||||
> Namespace: `omath`
|
||||
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
|
||||
> Inherits: `omath::Vector3<Type>` (brings `x`, `y`, `z` and most scalar ops)
|
||||
|
||||
`Vector4<Type>` extends `Vector3<Type>` with a `w` component and 4D operations: component-wise arithmetic, scalar ops, dot/length helpers, clamping, hashing (for `float`) and `std::formatter` support. Optional ImGui interop is available behind a macro.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "vector4.hpp"
|
||||
using omath::Vector4;
|
||||
|
||||
using Vec4f = Vector4<float>;
|
||||
|
||||
Vec4f a{1, 2, 3, 1};
|
||||
Vec4f b{4, 5, 6, 2};
|
||||
|
||||
// Component-wise & scalar ops
|
||||
auto c = a + b; // (5, 7, 9, 3)
|
||||
c *= 0.5f; // (2.5, 3.5, 4.5, 1.5)
|
||||
auto h = a * b; // Hadamard: (4, 10, 18, 2)
|
||||
|
||||
// Dot / length
|
||||
float d = a.dot(b); // 1*4 + 2*5 + 3*6 + 1*2 = 32
|
||||
float L = a.length(); // sqrt(x²+y²+z²+w²)
|
||||
|
||||
// Clamp (x,y,z only; see notes)
|
||||
Vec4f col{1.4f, -0.2f, 0.7f, 42.f};
|
||||
col.clamp(0.f, 1.f); // -> (1, 0, 0.7, w unchanged)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data members
|
||||
|
||||
```cpp
|
||||
// Inherited from Vector3<Type>:
|
||||
Type x{0};
|
||||
Type y{0};
|
||||
Type z{0};
|
||||
|
||||
// Added in Vector4:
|
||||
Type w{0};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Constructors
|
||||
|
||||
```cpp
|
||||
constexpr Vector4() noexcept; // (0,0,0,0)
|
||||
constexpr Vector4(const Type& x, const Type& y,
|
||||
const Type& z, const Type& w); // value-init
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Equality & ordering
|
||||
|
||||
```cpp
|
||||
constexpr bool operator==(const Vector4&) const noexcept; // component-wise
|
||||
constexpr bool operator!=(const Vector4&) const noexcept;
|
||||
|
||||
bool operator< (const Vector4&) const noexcept; // compare by length()
|
||||
bool operator> (const Vector4&) const noexcept;
|
||||
bool operator<=(const Vector4&) const noexcept;
|
||||
bool operator>=(const Vector4&) const noexcept;
|
||||
```
|
||||
|
||||
> **Note:** Ordering uses **magnitude** (Euclidean norm), not lexicographic order.
|
||||
|
||||
---
|
||||
|
||||
## Arithmetic (mutating)
|
||||
|
||||
With another vector (component-wise):
|
||||
|
||||
```cpp
|
||||
Vector4& operator+=(const Vector4&) noexcept;
|
||||
Vector4& operator-=(const Vector4&) noexcept;
|
||||
Vector4& operator*=(const Vector4&) noexcept; // Hadamard
|
||||
Vector4& operator/=(const Vector4&) noexcept;
|
||||
```
|
||||
|
||||
With a scalar:
|
||||
|
||||
```cpp
|
||||
Vector4& operator*=(const Type& v) noexcept;
|
||||
Vector4& operator/=(const Type& v) noexcept;
|
||||
|
||||
// From base class (inherited):
|
||||
Vector4& operator+=(const Type& v) noexcept; // adds v to x,y,z (and w via base? see notes)
|
||||
Vector4& operator-=(const Type& v) noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Arithmetic (non-mutating)
|
||||
|
||||
```cpp
|
||||
constexpr Vector4 operator-() const noexcept;
|
||||
constexpr Vector4 operator+(const Vector4&) const noexcept;
|
||||
constexpr Vector4 operator-(const Vector4&) const noexcept;
|
||||
constexpr Vector4 operator*(const Vector4&) const noexcept; // Hadamard
|
||||
constexpr Vector4 operator/(const Vector4&) const noexcept; // Hadamard
|
||||
constexpr Vector4 operator*(const Type& scalar) const noexcept;
|
||||
constexpr Vector4 operator/(const Type& scalar) const noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Geometry & helpers
|
||||
|
||||
```cpp
|
||||
constexpr Type length_sqr() const noexcept; // x² + y² + z² + w²
|
||||
Type length() const noexcept; // std::sqrt(length_sqr())
|
||||
constexpr Type dot(const Vector4& rhs) const noexcept;
|
||||
|
||||
Vector4& abs() noexcept; // component-wise absolute
|
||||
Vector4& clamp(const Type& min, const Type& max) noexcept;
|
||||
// clamps x,y,z; leaves w unchanged (see notes)
|
||||
constexpr Type sum() const noexcept; // x + y + z + w
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ImGui integration (optional)
|
||||
|
||||
Guarded by `OMATH_IMGUI_INTEGRATION`:
|
||||
|
||||
```cpp
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
constexpr ImVec4 to_im_vec4() const noexcept;
|
||||
// NOTE: Provided signature returns Vector4<float> and (in current code) sets only x,y,z.
|
||||
// See "Notes & caveats" for a corrected version you may prefer.
|
||||
static Vector4<float> from_im_vec4(const ImVec4& other) noexcept;
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hashing & formatting
|
||||
|
||||
* **Hash specialization** (only for `Vector4<float>`):
|
||||
|
||||
```cpp
|
||||
template<> struct std::hash<omath::Vector4<float>> {
|
||||
std::size_t operator()(const omath::Vector4<float>&) const noexcept;
|
||||
};
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
std::unordered_map<omath::Vector4<float>, int> counts;
|
||||
counts[{1.f, 2.f, 3.f, 1.f}] = 7;
|
||||
```
|
||||
|
||||
* **`std::formatter`** (for any `Type`, all character kinds):
|
||||
|
||||
```cpp
|
||||
template<class Type>
|
||||
struct std::formatter<omath::Vector4<Type>>; // -> "[x, y, z, w]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & caveats (as implemented)
|
||||
|
||||
* `clamp(min,max)` **clamps only `x`, `y`, `z`** and **does not clamp `w`**. This may be intentional (e.g., when `w` is a homogeneous coordinate) — document your intent in your codebase.
|
||||
If you want to clamp `w` too:
|
||||
|
||||
```cpp
|
||||
w = std::clamp(w, min, max);
|
||||
```
|
||||
|
||||
* **ImGui interop**:
|
||||
|
||||
* The header references `ImVec4` but does not include `<imgui.h>` itself. Ensure it’s included **before** this header whenever `OMATH_IMGUI_INTEGRATION` is defined.
|
||||
* `from_im_vec4` currently returns `Vector4<float>` and (in the snippet shown) initializes **only x,y,z**. A more consistent version would be:
|
||||
|
||||
```cpp
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
static Vector4<Type> from_im_vec4(const ImVec4& v) noexcept {
|
||||
return {static_cast<Type>(v.x), static_cast<Type>(v.y),
|
||||
static_cast<Type>(v.z), static_cast<Type>(v.w)};
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
* Many scalar compound operators (`+= Type`, `-= Type`) are inherited from `Vector3<Type>`.
|
||||
|
||||
---
|
||||
|
||||
## API summary (signatures)
|
||||
|
||||
```cpp
|
||||
// Ctors
|
||||
constexpr Vector4() noexcept;
|
||||
constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w);
|
||||
|
||||
// Equality & ordering
|
||||
constexpr bool operator==(const Vector4&) const noexcept;
|
||||
constexpr bool operator!=(const Vector4&) const noexcept;
|
||||
bool operator< (const Vector4&) const noexcept;
|
||||
bool operator> (const Vector4&) const noexcept;
|
||||
bool operator<=(const Vector4&) const noexcept;
|
||||
bool operator>=(const Vector4&) const noexcept;
|
||||
|
||||
// Mutating arithmetic
|
||||
Vector4& operator+=(const Vector4&) noexcept;
|
||||
Vector4& operator-=(const Vector4&) noexcept;
|
||||
Vector4& operator*=(const Vector4&) noexcept;
|
||||
Vector4& operator/=(const Vector4&) noexcept;
|
||||
Vector4& operator*=(const Type&) noexcept;
|
||||
Vector4& operator/=(const Type&) noexcept;
|
||||
// (inherited) Vector4& operator+=(const Type&) noexcept;
|
||||
// (inherited) Vector4& operator-=(const Type&) noexcept;
|
||||
|
||||
// Non-mutating arithmetic
|
||||
constexpr Vector4 operator-() const noexcept;
|
||||
constexpr Vector4 operator+(const Vector4&) const noexcept;
|
||||
constexpr Vector4 operator-(const Vector4&) const noexcept;
|
||||
constexpr Vector4 operator*(const Vector4&) const noexcept;
|
||||
constexpr Vector4 operator/(const Vector4&) const noexcept;
|
||||
constexpr Vector4 operator*(const Type&) const noexcept;
|
||||
constexpr Vector4 operator/(const Type&) const noexcept;
|
||||
|
||||
// Geometry & helpers
|
||||
constexpr Type length_sqr() const noexcept;
|
||||
Type length() const noexcept;
|
||||
constexpr Type dot(const Vector4&) const noexcept;
|
||||
Vector4& abs() noexcept;
|
||||
Vector4& clamp(const Type& min, const Type& max) noexcept;
|
||||
constexpr Type sum() const noexcept;
|
||||
|
||||
// ImGui (optional)
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
constexpr ImVec4 to_im_vec4() const noexcept;
|
||||
static Vector4<float> from_im_vec4(const ImVec4&) noexcept; // see note for preferred template version
|
||||
#endif
|
||||
|
||||
// Hash (float) and formatter specializations provided below the class
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
188
docs/pathfinding/a_star.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# `omath::pathfinding::Astar` — Pathfinding over a navigation mesh
|
||||
|
||||
> Header: your project’s `pathfinding/astar.hpp`
|
||||
> Namespace: `omath::pathfinding`
|
||||
> Inputs: start/end as `Vector3<float>`, a `NavigationMesh`
|
||||
> Output: ordered list of waypoints `std::vector<Vector3<float>>`
|
||||
|
||||
`Astar` exposes a single public function, `find_path`, that computes a path of 3D waypoints on a navigation mesh. Internally it reconstructs the result with `reconstruct_final_path` from a closed set keyed by `Vector3<float>`.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::pathfinding {
|
||||
|
||||
struct PathNode; // holds per-node search data (see "Expected PathNode fields")
|
||||
|
||||
class Astar final {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static std::vector<Vector3<float>>
|
||||
find_path(const Vector3<float>& start,
|
||||
const Vector3<float>& end,
|
||||
const NavigationMesh& nav_mesh) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]]
|
||||
static std::vector<Vector3<float>>
|
||||
reconstruct_final_path(
|
||||
const std::unordered_map<Vector3<float>, PathNode>& closed_list,
|
||||
const Vector3<float>& current) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::pathfinding
|
||||
```
|
||||
|
||||
### Semantics
|
||||
|
||||
* Returns a **polyline** of 3D points from `start` to `end`.
|
||||
* If no path exists, the function typically returns an **empty vector** (behavior depends on implementation details; keep this contract in unit tests).
|
||||
|
||||
---
|
||||
|
||||
## What `NavigationMesh` is expected to provide
|
||||
|
||||
The header doesn’t constrain `NavigationMesh`, but for A* it commonly needs:
|
||||
|
||||
* **Neighborhood queries**: given a position or node key → iterable neighbors.
|
||||
* **Traversal cost**: `g(u,v)` (often Euclidean distance or edge weight).
|
||||
* **Heuristic**: `h(x,end)` (commonly straight-line distance on the mesh).
|
||||
* **Projection / snap**: the ability to map `start`/`end` to valid nodes/points on the mesh (if they are off-mesh).
|
||||
|
||||
> If your `NavigationMesh` doesn’t directly expose these, `Astar::find_path` likely does the adapter work (snapping to the nearest convex polygon/portal nodes and expanding across adjacency).
|
||||
|
||||
---
|
||||
|
||||
## Expected `PathNode` fields
|
||||
|
||||
Although not visible here, `PathNode` typically carries:
|
||||
|
||||
* `Vector3<float> parent;` — predecessor position or key for backtracking
|
||||
* `float g;` — cost from `start`
|
||||
* `float h;` — heuristic to `end`
|
||||
* `float f;` — `g + h`
|
||||
|
||||
`reconstruct_final_path(closed_list, current)` walks `parent` links from `current` back to the start, **reverses** the chain, and returns the path.
|
||||
|
||||
---
|
||||
|
||||
## Heuristic & optimality
|
||||
|
||||
* Use an **admissible** heuristic (never overestimates true cost) to keep A* optimal.
|
||||
The usual choice is **Euclidean distance** in 3D:
|
||||
|
||||
```cpp
|
||||
h(x, goal) = (goal - x).length();
|
||||
```
|
||||
* For best performance, make it **consistent** (triangle inequality holds). Euclidean distance is consistent on standard navmeshes.
|
||||
|
||||
---
|
||||
|
||||
## Complexity
|
||||
|
||||
Let `V` be explored vertices (or portal nodes) and `E` the traversed edges.
|
||||
|
||||
* With a binary heap open list: **O(E log V)** time, **O(V)** memory.
|
||||
* With a d-ary heap or pairing heap you may reduce practical constants.
|
||||
|
||||
---
|
||||
|
||||
## Typical usage
|
||||
|
||||
```cpp
|
||||
#include "omath/pathfinding/astar.hpp"
|
||||
#include "omath/pathfinding/navigation_mesh.hpp"
|
||||
|
||||
using omath::Vector3;
|
||||
using omath::pathfinding::Astar;
|
||||
|
||||
NavigationMesh nav = /* ... load/build mesh ... */;
|
||||
|
||||
Vector3<float> start{2.5f, 0.0f, -1.0f};
|
||||
Vector3<float> goal {40.0f, 0.0f, 12.0f};
|
||||
|
||||
auto path = Astar::find_path(start, goal, nav);
|
||||
|
||||
if (!path.empty()) {
|
||||
// feed to your agent/renderer
|
||||
for (const auto& p : path) {
|
||||
// draw waypoint p or push to steering
|
||||
}
|
||||
} else {
|
||||
// handle "no path" (e.g., unreachable or disconnected mesh)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & recommendations
|
||||
|
||||
* **Start/end snapping**: If `start` or `end` are outside the mesh, decide whether to snap to the nearest polygon/portal or fail early. Keep this behavior consistent and document it where `NavigationMesh` is defined.
|
||||
* **Numerical stability**: Prefer squared distances when only comparing (`dist2`) to avoid unnecessary `sqrt`.
|
||||
* **Tie-breaking**: When `f` ties are common (grid-like graphs), bias toward larger `g` or smaller `h` to reduce zig-zagging.
|
||||
* **Smoothing**: A* returns a polyline that may hug polygon edges. Consider:
|
||||
|
||||
* **String pulling / Funnel algorithm** over the corridor of polygons to get a straightened path.
|
||||
* **Raycast smoothing** (visibility checks) to remove redundant interior points.
|
||||
* **Hashing `Vector3<float>`**: Your repo defines `std::hash<omath::Vector3<float>>`. Ensure equality/precision rules for using float keys are acceptable (or use discrete node IDs instead).
|
||||
|
||||
---
|
||||
|
||||
## Testing checklist
|
||||
|
||||
* Start/end on the **same polygon** → direct path of 2 points.
|
||||
* **Disconnected components** → empty result.
|
||||
* **Narrow corridors** → path stays inside.
|
||||
* **Obstacles blocking** → no path.
|
||||
* **Floating-point noise** → still reconstructs a valid chain from parents.
|
||||
|
||||
---
|
||||
|
||||
## Minimal pseudo-implementation outline (for reference)
|
||||
|
||||
```cpp
|
||||
// Pseudocode only — matches the header’s intent
|
||||
std::vector<Vec3> find_path(start, goal, mesh) {
|
||||
auto [snode, gnode] = mesh.snap_to_nodes(start, goal);
|
||||
OpenSet open; // min-heap by f
|
||||
std::unordered_map<Vec3, PathNode> closed;
|
||||
|
||||
open.push({snode, g=0, h=heuristic(snode, gnode)});
|
||||
parents.clear();
|
||||
|
||||
while (!open.empty()) {
|
||||
auto current = open.pop_min(); // node with lowest f
|
||||
|
||||
if (current.pos == gnode.pos)
|
||||
return reconstruct_final_path(closed, current.pos);
|
||||
|
||||
for (auto [nbr, cost] : mesh.neighbors(current.pos)) {
|
||||
float tentative_g = current.g + cost;
|
||||
if (auto it = closed.find(nbr); it == closed.end() || tentative_g < it->second.g) {
|
||||
closed[nbr] = { .parent = current.pos,
|
||||
.g = tentative_g,
|
||||
.h = heuristic(nbr, gnode.pos),
|
||||
.f = tentative_g + heuristic(nbr, gnode.pos) };
|
||||
open.push(closed[nbr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {}; // no path
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
* **Why return `std::vector<Vector3<float>>` and not polygon IDs?**
|
||||
Waypoints are directly usable by agents/steering and for rendering. If you also need the corridor (polygon chain), extend the API or `PathNode` to store it.
|
||||
|
||||
* **Does `find_path` modify the mesh?**
|
||||
No; it should be a read-only search over `NavigationMesh`.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
113
docs/pathfinding/navigation_mesh.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# `omath::pathfinding::NavigationMesh` — Lightweight vertex graph for A*
|
||||
|
||||
> Header: your project’s `pathfinding/navigation_mesh.hpp`
|
||||
> Namespace: `omath::pathfinding`
|
||||
> Nodes: `Vector3<float>` (3D points)
|
||||
> Storage: adjacency map `unordered_map<Vector3<float>, std::vector<Vector3<float>>>`
|
||||
|
||||
A minimal navigation mesh represented as a **vertex/edge graph**. Each vertex is a `Vector3<float>` and neighbors are stored in an adjacency list. Designed to pair with `Astar::find_path`.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
class NavigationMesh final {
|
||||
public:
|
||||
// Nearest graph vertex to an arbitrary 3D point.
|
||||
// On success -> closest vertex; on failure -> std::string error (e.g., empty mesh).
|
||||
[[nodiscard]]
|
||||
std::expected<Vector3<float>, std::string>
|
||||
get_closest_vertex(const Vector3<float>& point) const noexcept;
|
||||
|
||||
// Read-only neighbor list for a vertex key.
|
||||
// If vertex is absent, implementation should return an empty list (see notes).
|
||||
[[nodiscard]]
|
||||
const std::vector<Vector3<float>>&
|
||||
get_neighbors(const Vector3<float>& vertex) const noexcept;
|
||||
|
||||
// True if the graph has no vertices/edges.
|
||||
[[nodiscard]]
|
||||
bool empty() const;
|
||||
|
||||
// Serialize/deserialize the graph (opaque binary).
|
||||
[[nodiscard]] std::vector<uint8_t> serialize() const noexcept;
|
||||
void deserialize(const std::vector<uint8_t>& raw) noexcept;
|
||||
|
||||
// Public adjacency (vertex -> neighbors)
|
||||
std::unordered_map<Vector3<float>, std::vector<Vector3<float>>> m_vertex_map;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
using omath::Vector3;
|
||||
using omath::pathfinding::NavigationMesh;
|
||||
|
||||
// Build a tiny mesh (triangle)
|
||||
NavigationMesh nav;
|
||||
nav.m_vertex_map[ {0,0,0} ] = { {1,0,0}, {0,0,1} };
|
||||
nav.m_vertex_map[ {1,0,0} ] = { {0,0,0}, {0,0,1} };
|
||||
nav.m_vertex_map[ {0,0,1} ] = { {0,0,0}, {1,0,0} };
|
||||
|
||||
// Query the closest node to an arbitrary point
|
||||
auto q = nav.get_closest_vertex({0.3f, 0.0f, 0.2f});
|
||||
if (q) {
|
||||
const auto& v = *q;
|
||||
const auto& nbrs = nav.get_neighbors(v);
|
||||
(void)nbrs;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Semantics & expectations
|
||||
|
||||
* **Nearest vertex**
|
||||
`get_closest_vertex(p)` should scan known vertices and return the one with minimal Euclidean distance to `p`. If the mesh is empty, expect an error (`unexpected` with a message).
|
||||
|
||||
* **Neighbors**
|
||||
`get_neighbors(v)` returns the adjacency list for `v`. If `v` is not present, a conventional behavior is to return a **reference to a static empty vector** (since the API is `noexcept` and returns by reference). Verify in your implementation.
|
||||
|
||||
* **Graph invariants** (recommended)
|
||||
|
||||
* Neighbor links are **symmetric** for undirected navigation (if `u` has `v`, then `v` has `u`).
|
||||
* No self-loops unless explicitly desired.
|
||||
* Vertices are unique keys; hashing uses `std::hash<Vector3<float>>` (be mindful of floating-point equality).
|
||||
|
||||
---
|
||||
|
||||
## Serialization
|
||||
|
||||
* `serialize()` → opaque, implementation-defined binary of the current `m_vertex_map`.
|
||||
* `deserialize(raw)` → restores the internal map from `raw`.
|
||||
Keep versioning in mind if you evolve the format (e.g., add a header/magic/version).
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
Let `V = m_vertex_map.size()` and `E = Σ|neighbors(v)|`.
|
||||
|
||||
* `get_closest_vertex`: **O(V)** (linear scan) unless you back it with a spatial index (KD-tree, grid, etc.).
|
||||
* `get_neighbors`: **O(1)** average (hash lookup).
|
||||
* Memory: **O(V + E)**.
|
||||
|
||||
---
|
||||
|
||||
## Usage notes
|
||||
|
||||
* **Floating-point keys**: Using `Vector3<float>` as an unordered_map key relies on your `std::hash<omath::Vector3<float>>` and exact `operator==`. Avoid building meshes with numerically “close but not equal” duplicates; consider canonicalizing or using integer IDs if needed.
|
||||
* **Pathfinding**: Pair with `Astar::find_path(start, end, nav)`; the A* heuristic can use straight-line distance between vertex positions.
|
||||
|
||||
---
|
||||
|
||||
## Minimal test ideas
|
||||
|
||||
* Empty mesh → `get_closest_vertex` returns error; `empty() == true`.
|
||||
* Single vertex → nearest always that vertex; neighbors empty.
|
||||
* Symmetric edges → `get_neighbors(a)` contains `b` and vice versa.
|
||||
* Serialization round-trip preserves vertex/edge counts and neighbor lists.
|
||||
161
docs/projectile_prediction/proj_pred_engine_avx2.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# `omath::projectile_prediction::ProjPredEngineAvx2` — AVX2-accelerated ballistic aim solver
|
||||
|
||||
> Header: your project’s `projectile_prediction/proj_pred_engine_avx2.hpp`
|
||||
> Namespace: `omath::projectile_prediction`
|
||||
> Inherits: `ProjPredEngineInterface`
|
||||
> Depends on: `Vector3<float>`, `Projectile`, `Target`
|
||||
> CPU: Uses AVX2 when available; falls back to scalar elsewhere (fields are marked `[[maybe_unused]]` for non-x86/AVX2 builds).
|
||||
|
||||
This engine computes a **world-space aim point** (and implicitly the firing **yaw/pitch**) to intersect a moving target under a **constant gravity** model and **constant muzzle speed**. It typically scans candidate times of flight and solves for the elevation (`pitch`) that makes the vertical and horizontal kinematics meet at the same time.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
class ProjPredEngineAvx2 final : public ProjPredEngineInterface {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
std::optional<Vector3<float>>
|
||||
maybe_calculate_aim_point(const Projectile& projectile,
|
||||
const Target& target) const override;
|
||||
|
||||
ProjPredEngineAvx2(float gravity_constant,
|
||||
float simulation_time_step,
|
||||
float maximum_simulation_time);
|
||||
~ProjPredEngineAvx2() override = default;
|
||||
|
||||
private:
|
||||
// Solve for pitch at a fixed time-of-flight t.
|
||||
[[nodiscard]]
|
||||
static std::optional<float>
|
||||
calculate_pitch(const Vector3<float>& proj_origin,
|
||||
const Vector3<float>& target_pos,
|
||||
float bullet_gravity, float v0, float time);
|
||||
|
||||
// Tunables (may be unused on non-AVX2 builds)
|
||||
[[maybe_unused]] const float m_gravity_constant; // |g| (e.g., 9.81)
|
||||
[[maybe_unused]] const float m_simulation_time_step; // Δt (e.g., 1/240 s)
|
||||
[[maybe_unused]] const float m_maximum_simulation_time; // Tmax (e.g., 3 s)
|
||||
};
|
||||
```
|
||||
|
||||
### Parameters (constructor)
|
||||
|
||||
* `gravity_constant` — magnitude of gravity (units consistent with your world, e.g., **m/s²**).
|
||||
* `simulation_time_step` — Δt used to scan candidate intercept times.
|
||||
* `maximum_simulation_time` — cap on time of flight; larger allows longer-range solutions but increases cost.
|
||||
|
||||
### Return (solver)
|
||||
|
||||
* `maybe_calculate_aim_point(...)`
|
||||
|
||||
* **`Vector3<float>`**: a world-space **aim point** yielding an intercept under the model.
|
||||
* **`std::nullopt`**: no feasible solution (e.g., target receding too fast, out of range, or kinematics inconsistent).
|
||||
|
||||
---
|
||||
|
||||
## How it solves (expected flow)
|
||||
|
||||
1. **Predict target at time `t`** (constant-velocity model unless your `Target` carries more):
|
||||
|
||||
```
|
||||
T(t) = target.position + target.velocity * t
|
||||
```
|
||||
2. **Horizontal/vertical kinematics at fixed `t`** with muzzle speed `v0` and gravity `g`:
|
||||
|
||||
* Let `Δ = T(t) - proj_origin`, `d = length(Δ.xz)`, `h = Δ.y`.
|
||||
* Required initial components:
|
||||
|
||||
```
|
||||
cosθ = d / (v0 * t)
|
||||
sinθ = (h + 0.5 * g * t^2) / (v0 * t)
|
||||
```
|
||||
* If `cosθ` ∈ [−1,1] and `sinθ` ∈ [−1,1] and `sin²θ + cos²θ ≈ 1`, then
|
||||
|
||||
```
|
||||
θ = atan2(sinθ, cosθ)
|
||||
```
|
||||
|
||||
That is what `calculate_pitch(...)` returns on success.
|
||||
3. **Yaw** is the azimuth toward `Δ.xz`.
|
||||
4. **Pick the earliest feasible `t`** in `[Δt, Tmax]` (scanned in steps of `Δt`; AVX2 batches several `t` at once).
|
||||
5. **Return the aim point.** Common choices:
|
||||
|
||||
* The **impact point** `T(t*)` (useful as a HUD marker), or
|
||||
* A point along the **initial firing ray** at some convenient range using `(yaw, pitch)`; both are consistent—pick the convention your caller expects.
|
||||
|
||||
> The private `calculate_pitch(...)` matches step **2** and returns `nullopt` if the trigonometric constraints are violated for that `t`.
|
||||
|
||||
---
|
||||
|
||||
## AVX2 notes
|
||||
|
||||
* On x86/x64 with AVX2, candidate times `t` can be evaluated **8 at a time** using FMA (great for dense scans).
|
||||
* On ARM/ARM64 (no AVX2), code falls back to scalar math; the `[[maybe_unused]]` members acknowledge compilation without SIMD.
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
ProjPredEngineAvx2 solver(
|
||||
/*gravity*/ 9.81f,
|
||||
/*dt*/ 1.0f/240.0f,
|
||||
/*Tmax*/ 3.0f
|
||||
);
|
||||
|
||||
Projectile proj; // fill: origin, muzzle_speed, etc.
|
||||
Target tgt; // fill: position, velocity
|
||||
|
||||
if (auto aim = solver.maybe_calculate_aim_point(proj, tgt)) {
|
||||
// Aim your weapon at *aim and fire with muzzle speed proj.v0
|
||||
// If you need yaw/pitch explicitly, replicate the pitch solve and azimuth.
|
||||
} else {
|
||||
// No solution (out of envelope) — pick a fallback
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Edge cases & failure modes
|
||||
|
||||
* **Zero or tiny `v0`** → no solution.
|
||||
* **Target collinear & receding faster than `v0`** → no solution.
|
||||
* **`t` constraints**: if viable solutions exist only beyond `Tmax`, you’ll get `nullopt`.
|
||||
* **Geometric infeasibility** at a given `t` (e.g., `d > v0*t`) causes `calculate_pitch` to fail that sample.
|
||||
* **Numerical tolerance**: check `sin²θ + cos²θ` against 1 with a small epsilon (e.g., `1e-3`).
|
||||
|
||||
---
|
||||
|
||||
## Performance & tuning
|
||||
|
||||
* Work is roughly `O(Nt)` where `Nt ≈ Tmax / Δt`.
|
||||
* Smaller `Δt` → better accuracy, higher cost. With AVX2 you can afford smaller steps.
|
||||
* If you frequently miss solutions **between** steps, consider:
|
||||
|
||||
* **Coarse-to-fine**: coarse scan, then local refine around the best `t`.
|
||||
* **Newton on time**: root-find `‖horizontal‖ − v0 t cosθ(t) = 0` shaped from the kinematics.
|
||||
|
||||
---
|
||||
|
||||
## Testing checklist
|
||||
|
||||
* **Stationary target** at same height → θ ≈ 0, aim point ≈ target.
|
||||
* **Higher target** → positive pitch; **lower target** → negative pitch.
|
||||
* **Perpendicular moving target** → feasible at moderate speeds.
|
||||
* **Very fast receding target** → `nullopt`.
|
||||
* **Boundary**: `d ≈ v0*Tmax` and `h` large → verify pass/fail around thresholds.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `ProjPredEngineInterface` — base interface and general contract
|
||||
* `Projectile`, `Target` — data carriers for solver inputs (speed, origin, position, velocity, etc.)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
184
docs/projectile_prediction/proj_pred_engine_legacy.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# `omath::projectile_prediction::ProjPredEngineLegacy` — Legacy trait-based aim solver
|
||||
|
||||
> Header: `omath/projectile_prediction/proj_pred_engine_legacy.hpp`
|
||||
> Namespace: `omath::projectile_prediction`
|
||||
> Inherits: `ProjPredEngineInterface`
|
||||
> Template param (default): `EngineTrait = source_engine::PredEngineTrait`
|
||||
> Purpose: compute a world-space **aim point** to hit a (possibly moving) target using a **discrete time scan** and a **closed-form ballistic pitch** under constant gravity.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`ProjPredEngineLegacy` is a portable, trait-driven projectile lead solver. At each simulation time step `t` it:
|
||||
|
||||
1. **Predicts target position** with `EngineTrait::predict_target_position(target, t, g)`.
|
||||
2. **Computes launch pitch** via a gravity-aware closed form (or a direct angle if gravity is zero).
|
||||
3. **Validates** that a projectile fired with that pitch (and direct yaw) actually reaches the predicted target within a **distance tolerance** at time `t`.
|
||||
4. On success, **returns an aim point** computed by `EngineTrait::calc_viewpoint_from_angles(...)`.
|
||||
|
||||
If no time step yields a feasible solution up to `maximum_simulation_time`, returns `std::nullopt`.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
template<class EngineTrait = source_engine::PredEngineTrait>
|
||||
requires PredEngineConcept<EngineTrait>
|
||||
class ProjPredEngineLegacy final : public ProjPredEngineInterface {
|
||||
public:
|
||||
ProjPredEngineLegacy(float gravity_constant,
|
||||
float simulation_time_step,
|
||||
float maximum_simulation_time,
|
||||
float distance_tolerance);
|
||||
|
||||
[[nodiscard]]
|
||||
std::optional<Vector3<float>>
|
||||
maybe_calculate_aim_point(const Projectile& projectile,
|
||||
const Target& target) const override;
|
||||
|
||||
private:
|
||||
// Closed-form ballistic pitch solver (internal)
|
||||
std::optional<float>
|
||||
maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile,
|
||||
const Vector3<float>& target_position) const noexcept;
|
||||
|
||||
bool is_projectile_reached_target(const Vector3<float>& target_position,
|
||||
const Projectile& projectile,
|
||||
float pitch, float time) const noexcept;
|
||||
|
||||
const float m_gravity_constant;
|
||||
const float m_simulation_time_step;
|
||||
const float m_maximum_simulation_time;
|
||||
const float m_distance_tolerance;
|
||||
};
|
||||
```
|
||||
|
||||
### Constructor parameters
|
||||
|
||||
* `gravity_constant` — magnitude of gravity (e.g., `9.81f`), world units/s².
|
||||
* `simulation_time_step` — Δt for the scan (e.g., `1/240.f`).
|
||||
* `maximum_simulation_time` — search horizon in seconds.
|
||||
* `distance_tolerance` — max allowed miss distance at time `t` to accept a solution.
|
||||
|
||||
---
|
||||
|
||||
## Trait requirements (`PredEngineConcept`)
|
||||
|
||||
Your `EngineTrait` must expose **noexcept** static methods with these signatures:
|
||||
|
||||
```cpp
|
||||
Vector3<float> predict_projectile_position(const Projectile&, float pitch_deg, float yaw_deg,
|
||||
float time, float gravity) noexcept;
|
||||
|
||||
Vector3<float> predict_target_position(const Target&, float time, float gravity) noexcept;
|
||||
|
||||
float calc_vector_2d_distance(const Vector3<float>& v) noexcept; // typically length in XZ plane
|
||||
float get_vector_height_coordinate(const Vector3<float>& v) noexcept; // typically Y
|
||||
|
||||
Vector3<float> calc_viewpoint_from_angles(const Projectile&, Vector3<float> target,
|
||||
std::optional<float> maybe_pitch_deg) noexcept;
|
||||
|
||||
float calc_direct_pitch_angle(const Vector3<float>& from, const Vector3<float>& to) noexcept;
|
||||
float calc_direct_yaw_angle (const Vector3<float>& from, const Vector3<float>& to) noexcept;
|
||||
```
|
||||
|
||||
> This design lets you adapt different game/physics conventions (axes, units, handedness) without changing the solver.
|
||||
|
||||
---
|
||||
|
||||
## Algorithm details
|
||||
|
||||
### Time scan
|
||||
|
||||
For `t = 0 .. maximum_simulation_time` in steps of `simulation_time_step`:
|
||||
|
||||
1. `T = EngineTrait::predict_target_position(target, t, g)`
|
||||
2. `pitch = maybe_calculate_projectile_launch_pitch_angle(projectile, T)`
|
||||
|
||||
* If `std::nullopt`: continue
|
||||
3. `yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin, T)`
|
||||
4. `P = EngineTrait::predict_projectile_position(projectile, pitch, yaw, t, g)`
|
||||
5. Accept if `|P - T| <= distance_tolerance`
|
||||
6. Return `EngineTrait::calc_viewpoint_from_angles(projectile, T, pitch)`
|
||||
|
||||
### Closed-form pitch (gravity on)
|
||||
|
||||
Implements the classic ballistic formula (low-arc branch), where:
|
||||
|
||||
* `v` = muzzle speed,
|
||||
* `g` = `gravity_constant * projectile.m_gravity_scale`,
|
||||
* `x` = horizontal (2D) distance to target,
|
||||
* `y` = vertical offset to target.
|
||||
|
||||
[
|
||||
\theta ;=; \arctan!\left(\frac{v^{2} ;-; \sqrt{v^{4}-g!\left(gx^{2}+2yv^{2}\right)}}{gx}\right)
|
||||
]
|
||||
|
||||
* If the **discriminant** ( v^{4}-g(gx^{2}+2yv^{2}) < 0 ) ⇒ **no real solution**.
|
||||
* If `g == 0`, falls back to `EngineTrait::calc_direct_pitch_angle(...)`.
|
||||
* Returns **degrees** (internally converts from radians).
|
||||
|
||||
---
|
||||
|
||||
## Usage example
|
||||
|
||||
```cpp
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
ProjPredEngineLegacy solver(
|
||||
/*gravity*/ 9.81f,
|
||||
/*dt*/ 1.f / 240.f,
|
||||
/*Tmax*/ 3.0f,
|
||||
/*tol*/ 0.05f
|
||||
);
|
||||
|
||||
Projectile proj; // fill: m_origin, m_launch_speed, m_gravity_scale, etc.
|
||||
Target tgt; // fill: position/velocity as required by your trait
|
||||
|
||||
if (auto aim = solver.maybe_calculate_aim_point(proj, tgt)) {
|
||||
// Drive your turret/reticle toward *aim
|
||||
} else {
|
||||
// No feasible intercept in the given horizon
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Behavior & edge cases
|
||||
|
||||
* **Zero gravity or zero distance**: uses direct pitch toward the target.
|
||||
* **Negative discriminant** in the pitch formula: returns `std::nullopt` for that time step.
|
||||
* **Very small `x`** (horizontal distance): the formula’s denominator `gx` approaches zero; your trait’s direct pitch helper provides a stable fallback.
|
||||
* **Tolerance**: `distance_tolerance` controls acceptance; tighten for accuracy, loosen for robustness.
|
||||
|
||||
---
|
||||
|
||||
## Complexity & tuning
|
||||
|
||||
* Time: **O(T)** where ( T \approx \frac{\text{maximum_simulation_time}}{\text{simulation_time_step}} )
|
||||
plus trait costs for prediction and angle math per step.
|
||||
* Smaller `simulation_time_step` improves precision but increases runtime.
|
||||
* If needed, do a **coarse-to-fine** search: coarse Δt scan, then refine around the best hit time.
|
||||
|
||||
---
|
||||
|
||||
## Testing checklist
|
||||
|
||||
* Stationary, level target → pitch ≈ 0 for short ranges; accepted within tolerance.
|
||||
* Elevated/depressed targets → pitch positive/negative as expected.
|
||||
* Receding fast target → unsolved within horizon ⇒ `nullopt`.
|
||||
* Gravity scale = 0 → identical to straight-line solution.
|
||||
* Near-horizon shots (large range, small arc) → discriminant near zero; verify stability.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
* All angles produced/consumed by the trait in this implementation are **degrees**.
|
||||
* `calc_viewpoint_from_angles` defines what “aim point” means in your engine (e.g., a point along the initial ray or the predicted impact point). Keep this consistent with your HUD/reticle.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
96
docs/projectile_prediction/projectile.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# `omath::projectile_prediction::Projectile` — Projectile parameters for aim solvers
|
||||
|
||||
> Header: `omath/projectile_prediction/projectile.hpp`
|
||||
> Namespace: `omath::projectile_prediction`
|
||||
> Used by: `ProjPredEngineInterface` implementations (e.g., `ProjPredEngineLegacy`, `ProjPredEngineAvx2`)
|
||||
|
||||
`Projectile` is a tiny data holder that describes how a projectile is launched: **origin** (world position), **launch speed**, and a **gravity scale** (multiplier applied to the engine’s gravity constant).
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::projectile_prediction {
|
||||
|
||||
class Projectile final {
|
||||
public:
|
||||
Vector3<float> m_origin; // Launch position (world space)
|
||||
float m_launch_speed{}; // Initial speed magnitude (units/sec)
|
||||
float m_gravity_scale{}; // Multiplier for global gravity (dimensionless)
|
||||
};
|
||||
|
||||
} // namespace omath::projectile_prediction
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Field semantics
|
||||
|
||||
* **`m_origin`**
|
||||
World-space position where the projectile is spawned (e.g., muzzle or emitter point).
|
||||
|
||||
* **`m_launch_speed`**
|
||||
Initial speed **magnitude** in your world units per second. Direction is determined by the solver (from yaw/pitch).
|
||||
|
||||
* Must be **non-negative**. Zero disables meaningful ballistic solutions.
|
||||
|
||||
* **`m_gravity_scale`**
|
||||
Multiplies the engine’s gravity constant provided to the solver (e.g., `g = gravity_constant * m_gravity_scale`).
|
||||
|
||||
* Use `1.0f` for normal gravity, `0.0f` for no-drop projectiles, other values to simulate heavier/lighter rounds.
|
||||
|
||||
> Units must be consistent across your project (e.g., meters & seconds). If `gravity_constant = 9.81f`, then `m_launch_speed` is in m/s and positions are in meters.
|
||||
|
||||
---
|
||||
|
||||
## Typical usage
|
||||
|
||||
```cpp
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
Projectile proj;
|
||||
proj.m_origin = { 0.0f, 1.6f, 0.0f }; // player eye / muzzle height
|
||||
proj.m_launch_speed = 850.0f; // e.g., 850 m/s
|
||||
proj.m_gravity_scale = 1.0f; // normal gravity
|
||||
|
||||
// With an aim solver:
|
||||
auto aim = engine->maybe_calculate_aim_point(proj, target);
|
||||
if (aim) {
|
||||
// rotate/aim toward *aim and fire
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## With gravity-aware solver (outline)
|
||||
|
||||
Engines typically compute the firing angles to reach a predicted target position:
|
||||
|
||||
* Horizontal distance `x` and vertical offset `y` are derived from `target - m_origin`.
|
||||
* Gravity used is `g = gravity_constant * m_gravity_scale`.
|
||||
* Launch direction has speed `m_launch_speed` and angles solved by the engine.
|
||||
|
||||
If `m_gravity_scale == 0`, engines usually fall back to straight-line (no-drop) solutions.
|
||||
|
||||
---
|
||||
|
||||
## Validation & tips
|
||||
|
||||
* Keep `m_launch_speed ≥ 0`. Negative values are nonsensical.
|
||||
* If your weapon can vary muzzle speed (charge-up, attachments), update `m_launch_speed` per shot.
|
||||
* For different ammo types (tracers, grenades), prefer tweaking **`m_gravity_scale`** (and possibly the engine’s gravity constant) to match observed arc.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `ProjPredEngineInterface` — common interface for aim solvers
|
||||
* `ProjPredEngineLegacy` — trait-based, time-stepped ballistic solver
|
||||
* `ProjPredEngineAvx2` — AVX2-accelerated solver with fixed-time pitch solve
|
||||
* `Target` — target state consumed by the solvers
|
||||
* `Vector3<float>` — math type used for positions and directions
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
162
docs/projectile_prediction/projectile_engine.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# `omath::projectile_prediction::ProjPredEngineInterface` — Aim-point solver interface
|
||||
|
||||
> Header: your project’s `projectile_prediction/proj_pred_engine_interface.hpp`
|
||||
> Namespace: `omath::projectile_prediction`
|
||||
> Depends on: `Vector3<float>`, `Projectile`, `Target`
|
||||
> Purpose: **contract** for engines that compute a lead/aim point to hit a moving target.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`ProjPredEngineInterface` defines a single pure-virtual method that attempts to compute the **world-space aim point** where a projectile should be launched to intersect a target under the engine’s physical model (e.g., constant projectile speed, gravity, drag, max flight time, etc.).
|
||||
|
||||
If a valid solution exists, the engine returns the 3D aim point. Otherwise, it returns `std::nullopt` (no feasible intercept).
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::projectile_prediction {
|
||||
|
||||
class ProjPredEngineInterface {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
virtual std::optional<Vector3<float>>
|
||||
maybe_calculate_aim_point(const Projectile& projectile,
|
||||
const Target& target) const = 0;
|
||||
|
||||
virtual ~ProjPredEngineInterface() = default;
|
||||
};
|
||||
|
||||
} // namespace omath::projectile_prediction
|
||||
```
|
||||
|
||||
### Semantics
|
||||
|
||||
* **Input**
|
||||
|
||||
* `Projectile` — engine-specific projectile properties (typical: muzzle speed, gravity vector, drag flag/coeff, max range / flight time).
|
||||
* `Target` — target state (typical: position, velocity, possibly acceleration).
|
||||
|
||||
* **Output**
|
||||
|
||||
* `std::optional<Vector3<float>>`
|
||||
|
||||
* `value()` — world-space point to aim at **now** so that the projectile intersects the target under the model.
|
||||
* `std::nullopt` — no solution (e.g., target outruns projectile, blocked by constraints, numerical failure).
|
||||
|
||||
* **No side effects**: method is `const` and should not modify inputs.
|
||||
|
||||
---
|
||||
|
||||
## Typical usage
|
||||
|
||||
```cpp
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
std::unique_ptr<ProjPredEngineInterface> engine = /* your implementation */;
|
||||
Projectile proj = /* fill from weapon config */;
|
||||
Target tgt = /* read from tracking system */;
|
||||
|
||||
if (auto aim = engine->maybe_calculate_aim_point(proj, tgt)) {
|
||||
// Rotate/steer to (*aim)
|
||||
} else {
|
||||
// Fall back: no-lead, predictive UI, or do not fire
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation guidance (for engine authors)
|
||||
|
||||
**Common models:**
|
||||
|
||||
1. **No gravity, constant speed**
|
||||
Closed form intersect time `t` solves `‖p_t + v_t t − p_0‖ = v_p t`.
|
||||
Choose the smallest non-negative real root; aim point = `p_t + v_t t`.
|
||||
|
||||
2. **Gravity (constant g), constant speed**
|
||||
Solve ballistics with vertical drop: either numerical (Newton–Raphson on time) or 2D elevation + azimuth decomposition. Ensure convergence caps and time bounds.
|
||||
|
||||
3. **Drag**
|
||||
Typically requires numeric integration (e.g., RK4) wrapped in a root find on time-of-flight.
|
||||
|
||||
**Robustness tips:**
|
||||
|
||||
* **Feasibility checks:** return `nullopt` when:
|
||||
|
||||
* projectile speed ≤ 0; target too fast in receding direction; solution time outside `[0, t_max]`.
|
||||
* **Bounds:** clamp search time to reasonable `[t_min, t_max]` (e.g., `[0, max_flight_time]` or by range).
|
||||
* **Tolerances:** use epsilons for convergence (e.g., `|f(t)| < 1e-4`, `|Δt| < 1e-4 s`).
|
||||
* **Determinism:** fix iteration counts or seeds if needed for replayability.
|
||||
|
||||
---
|
||||
|
||||
## Example: constant-speed, no-gravity intercept (closed form)
|
||||
|
||||
```cpp
|
||||
// Solve ||p + v t|| = s t where p = target_pos - shooter_pos, v = target_vel, s = projectile_speed
|
||||
// Quadratic: (v·v - s^2) t^2 + 2 (p·v) t + (p·p) = 0
|
||||
inline std::optional<float> intercept_time_no_gravity(const Vector3<float>& p,
|
||||
const Vector3<float>& v,
|
||||
float s) {
|
||||
const float a = v.dot(v) - s*s;
|
||||
const float b = 2.f * p.dot(v);
|
||||
const float c = p.dot(p);
|
||||
if (std::abs(a) < 1e-6f) { // near linear
|
||||
if (std::abs(b) < 1e-6f) return std::nullopt;
|
||||
float t = -c / b;
|
||||
return t >= 0.f ? std::optional{t} : std::nullopt;
|
||||
}
|
||||
const float disc = b*b - 4.f*a*c;
|
||||
if (disc < 0.f) return std::nullopt;
|
||||
const float sqrtD = std::sqrt(disc);
|
||||
float t1 = (-b - sqrtD) / (2.f*a);
|
||||
float t2 = (-b + sqrtD) / (2.f*a);
|
||||
float t = (t1 >= 0.f ? t1 : t2);
|
||||
return t >= 0.f ? std::optional{t} : std::nullopt;
|
||||
}
|
||||
```
|
||||
|
||||
Aim point (given shooter origin `S`, target pos `T`, vel `V`):
|
||||
|
||||
```
|
||||
p = T - S
|
||||
t* = intercept_time_no_gravity(p, V, speed)
|
||||
aim = T + V * t*
|
||||
```
|
||||
|
||||
Return `nullopt` if `t*` is absent.
|
||||
|
||||
---
|
||||
|
||||
## Testing checklist
|
||||
|
||||
* **Stationary target**: aim point equals target position when `s > 0`.
|
||||
* **Target perpendicular motion**: lead equals lateral displacement `V⊥ * t`.
|
||||
* **Receding too fast**: expect `nullopt`.
|
||||
* **Gravity model**: verify arc solutions exist for short & long trajectories (if implemented).
|
||||
* **Numerics**: convergence within max iterations; monotonic improvement of residuals.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
* This is an **interface** only; concrete engines (e.g., `SimpleNoGravityEngine`, `BallisticGravityEngine`) should document their assumptions (gravity, drag, wind, bounds) and units (meters, seconds).
|
||||
* The coordinate system and handedness should be consistent with `Vector3<float>` and the rest of your math stack.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Projectile Documentation](projectile.md) - Projectile properties
|
||||
- [Target Documentation](target.md) - Target state representation
|
||||
- [Legacy Implementation](proj_pred_engine_legacy.md) - Standard projectile prediction engine
|
||||
- [AVX2 Implementation](proj_pred_engine_avx2.md) - Optimized AVX2 engine
|
||||
- [Tutorials - Projectile Prediction](../tutorials.md#tutorial-3-projectile-prediction-aim-bot) - Complete aim-bot tutorial
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
70
docs/projectile_prediction/target.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# `omath::projectile_prediction::Target` — Target state for aim solvers
|
||||
|
||||
> Header: `omath/projectile_prediction/target.hpp`
|
||||
> Namespace: `omath::projectile_prediction`
|
||||
> Used by: `ProjPredEngineInterface` implementations (e.g., Legacy/AVX2 engines)
|
||||
|
||||
A small POD-style container describing a target’s **current pose** and **motion** for projectile lead/aim computations.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath::projectile_prediction {
|
||||
|
||||
class Target final {
|
||||
public:
|
||||
Vector3<float> m_origin; // Current world-space position of the target
|
||||
Vector3<float> m_velocity; // World-space linear velocity (units/sec)
|
||||
bool m_is_airborne{}; // Domain hint (e.g., ignore ground snapping)
|
||||
};
|
||||
|
||||
} // namespace omath::projectile_prediction
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Field semantics
|
||||
|
||||
* **`m_origin`** — target position in world coordinates (same units as your `Vector3<float>` grid).
|
||||
* **`m_velocity`** — instantaneous linear velocity. Solvers commonly assume **constant velocity** between “now” and impact unless your trait injects gravity/accel.
|
||||
* **`m_is_airborne`** — optional hint for engine/trait logic (e.g., apply gravity to the target, skip ground friction/snap). Exact meaning is engine-dependent.
|
||||
|
||||
> Keep units consistent with your projectile model (e.g., meters & seconds). If projectiles use `g = 9.81 m/s²`, velocity should be in m/s and positions in meters.
|
||||
|
||||
---
|
||||
|
||||
## Typical usage
|
||||
|
||||
```cpp
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
Target tgt;
|
||||
tgt.m_origin = { 42.0f, 1.8f, -7.5f };
|
||||
tgt.m_velocity = { 3.0f, 0.0f, 0.0f }; // moving +X at 3 units/s
|
||||
tgt.m_is_airborne = false;
|
||||
|
||||
// Feed into an aim solver with a Projectile
|
||||
auto aim = engine->maybe_calculate_aim_point(projectile, tgt);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & tips
|
||||
|
||||
* If you track acceleration (e.g., gravity on ragdolls), your **EngineTrait** may derive it from `m_is_airborne` and world gravity; otherwise most solvers treat the target’s motion as linear.
|
||||
* For highly agile targets, refresh `m_origin`/`m_velocity` every tick and re-solve; don’t reuse stale aim points.
|
||||
* Precision: `Vector3<float>` is typically enough; if you need sub-millimeter accuracy over long ranges, consider double-precision internally in your trait.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `Projectile` — shooter origin, muzzle speed, gravity scale
|
||||
* `ProjPredEngineInterface` — common interface for aim solvers
|
||||
* `ProjPredEngineLegacy`, `ProjPredEngineAvx2` — concrete solvers using this data
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
270
docs/projection/camera.md
Normal file
@@ -0,0 +1,270 @@
|
||||
# `omath::projection::Camera` — Generic, trait-driven camera with screen/world conversion
|
||||
|
||||
> Header: `omath/projection/camera.hpp` (this header)
|
||||
> Namespace: `omath::projection`
|
||||
> Template: `Camera<Mat4X4Type, ViewAnglesType, TraitClass>`
|
||||
> Requires: `CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType>`
|
||||
> Key features: **lazy view-projection caching**, world↔screen helpers, pluggable math via a **Trait**
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`Camera` is a small, zero-allocation camera wrapper. It delegates the math for **view**, **projection**, and **look-at** to a **Trait** (`TraitClass`), which lets you plug in different coordinate systems or conventions without changing the camera code. The class caches the **View×Projection** matrix and invalidates it when any parameter changes.
|
||||
|
||||
Alongside the camera, the header defines:
|
||||
|
||||
* `struct ViewPort { float m_width, m_height; float aspect_ratio() const; }`
|
||||
* `using FieldOfView = Angle<float, 0.f, 180.f, AngleFlags::Clamped>;`
|
||||
|
||||
---
|
||||
|
||||
## Template & trait requirements
|
||||
|
||||
```cpp
|
||||
template<class T, class MatType, class ViewAnglesType>
|
||||
concept CameraEngineConcept = requires(
|
||||
const omath::Vector3<float>& cam_origin,
|
||||
const omath::Vector3<float>& look_at,
|
||||
const ViewAnglesType& angles,
|
||||
const omath::projection::FieldOfView& fov,
|
||||
const omath::projection::ViewPort& viewport,
|
||||
float znear, float zfar
|
||||
) {
|
||||
{ T::calc_look_at_angle(cam_origin, look_at) } noexcept -> std::same_as<ViewAnglesType>;
|
||||
{ T::calc_view_matrix(angles, cam_origin) } noexcept -> std::same_as<MatType>;
|
||||
{ T::calc_projection_matrix(fov, viewport, znear, zfar)}noexcept -> std::same_as<MatType>;
|
||||
};
|
||||
```
|
||||
|
||||
Your `Mat4X4Type` must behave like the library’s `Mat<4,4,...>` (supports `*`, `/`, `inverted()`, `.at(r,c)`, `.raw_array()`, and `static constexpr get_store_ordering()`).
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
using Mat4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
|
||||
|
||||
// Example trait (sketch): assumes Y-up, column-major, left-handed
|
||||
struct MyCamTrait {
|
||||
static ViewAnglesType calc_look_at_angle(const Vector3<float>& eye,
|
||||
const Vector3<float>& at) noexcept;
|
||||
static Mat4 calc_view_matrix(const ViewAnglesType& ang,
|
||||
const Vector3<float>& eye) noexcept;
|
||||
static Mat4 calc_projection_matrix(const FieldOfView& fov,
|
||||
const ViewPort& vp,
|
||||
float znear, float zfar) noexcept;
|
||||
};
|
||||
|
||||
using Camera = omath::projection::Camera<Mat4, MyViewAngles, MyCamTrait>;
|
||||
|
||||
omath::projection::ViewPort vp{1920, 1080};
|
||||
omath::projection::FieldOfView fov = omath::angles::degrees(70.f);
|
||||
|
||||
Camera cam(/*position*/ {0,1.7f, -3},
|
||||
/*angles*/ MyViewAngles{/*...*/},
|
||||
/*viewport*/ vp, fov,
|
||||
/*near*/ 0.1f,
|
||||
/*far*/ 1000.f);
|
||||
|
||||
// Project world → screen (origin top-left)
|
||||
auto s = cam.world_to_screen<Camera::ScreenStart::TOP_LEFT_CORNER>({1, 1, 0});
|
||||
if (s) {
|
||||
// s->x, s->y in pixels; s->z in NDC depth
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
enum class ScreenStart { TOP_LEFT_CORNER, BOTTOM_LEFT_CORNER };
|
||||
|
||||
class Camera final {
|
||||
public:
|
||||
~Camera() = default;
|
||||
|
||||
Camera(const Vector3<float>& position,
|
||||
const ViewAnglesType& view_angles,
|
||||
const ViewPort& view_port,
|
||||
const FieldOfView& fov,
|
||||
float near, float far) noexcept;
|
||||
|
||||
void look_at(const Vector3<float>& target); // recomputes view angles; invalidates cache
|
||||
|
||||
// Lazily computed and cached:
|
||||
const Mat4X4Type& get_view_projection_matrix() const noexcept;
|
||||
|
||||
// Setters (all invalidate cached VP):
|
||||
void set_field_of_view(const FieldOfView&) noexcept;
|
||||
void set_near_plane(float) noexcept;
|
||||
void set_far_plane(float) noexcept;
|
||||
void set_view_angles(const ViewAnglesType&) noexcept;
|
||||
void set_origin(const Vector3<float>&) noexcept;
|
||||
void set_view_port(const ViewPort&) noexcept;
|
||||
|
||||
// Getters:
|
||||
const FieldOfView& get_field_of_view() const noexcept;
|
||||
const float& get_near_plane() const noexcept;
|
||||
const float& get_far_plane() const noexcept;
|
||||
const ViewAnglesType& get_view_angles() const noexcept;
|
||||
const Vector3<float>& get_origin() const noexcept;
|
||||
|
||||
// World → Screen (pixels) via NDC; choose screen origin:
|
||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||
std::expected<Vector3<float>, Error>
|
||||
world_to_screen(const Vector3<float>& world) const noexcept;
|
||||
|
||||
// World → NDC (aka “viewport” in this code) ∈ [-1,1]^3
|
||||
std::expected<Vector3<float>, Error>
|
||||
world_to_view_port(const Vector3<float>& world) const noexcept;
|
||||
|
||||
// NDC → World (uses inverse VP)
|
||||
std::expected<Vector3<float>, Error>
|
||||
view_port_to_screen(const Vector3<float>& ndc) const noexcept;
|
||||
|
||||
// Screen (pixels) → World
|
||||
std::expected<Vector3<float>, Error>
|
||||
screen_to_world(const Vector3<float>& screen) const noexcept;
|
||||
|
||||
// 2D overload (z defaults to 1, i.e., far plane ray-end in NDC)
|
||||
std::expected<Vector3<float>, Error>
|
||||
screen_to_world(const Vector2<float>& screen) const noexcept;
|
||||
|
||||
protected:
|
||||
ViewPort m_view_port{};
|
||||
FieldOfView m_field_of_view;
|
||||
mutable std::optional<Mat4X4Type> m_view_projection_matrix;
|
||||
float m_far_plane_distance{};
|
||||
float m_near_plane_distance{};
|
||||
ViewAnglesType m_view_angles;
|
||||
Vector3<float> m_origin;
|
||||
|
||||
private:
|
||||
static constexpr bool is_ndc_out_of_bounds(const Mat4X4Type& ndc) noexcept;
|
||||
Vector3<float> ndc_to_screen_position_from_top_left_corner(const Vector3<float>& ndc) const noexcept;
|
||||
Vector3<float> ndc_to_screen_position_from_bottom_left_corner(const Vector3<float>& ndc) const noexcept;
|
||||
Vector3<float> screen_to_ndc(const Vector3<float>& screen) const noexcept;
|
||||
};
|
||||
```
|
||||
|
||||
### Error handling
|
||||
|
||||
All conversions return `std::expected<..., Error>` with errors from `error_codes.hpp`, notably:
|
||||
|
||||
* `Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS` — clip space W=0 or NDC outside `[-1,1]`.
|
||||
* `Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO` — non-invertible View×Projection matrix.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate spaces & conversions
|
||||
|
||||
### World → NDC (`world_to_view_port`)
|
||||
|
||||
1. Build (or reuse cached) `VP = P * V` (projection * view).
|
||||
2. Multiply by homogeneous column from the world point.
|
||||
3. Reject if `w == 0`.
|
||||
4. Perspective divide → NDC in `[-1,1]^3`.
|
||||
5. Reject if any component is out of range.
|
||||
|
||||
Returns `{x_ndc, y_ndc, z_ndc}`.
|
||||
|
||||
### NDC → Screen (pixels)
|
||||
|
||||
The class offers two origins:
|
||||
|
||||
* **Top-left (default)**
|
||||
|
||||
```
|
||||
x_px = (x_ndc + 1)/2 * width
|
||||
y_px = ( -y_ndc/2 + 0.5) * height // flips Y
|
||||
```
|
||||
* **Bottom-left**
|
||||
|
||||
```
|
||||
x_px = (x_ndc + 1)/2 * width
|
||||
y_px = ( y_ndc/2 + 0.5) * height
|
||||
```
|
||||
|
||||
### Screen (pixels) → NDC
|
||||
|
||||
```
|
||||
x_ndc = screen_x / width * 2 - 1
|
||||
y_ndc = 1 - screen_y / height * 2 // Top-left screen origin assumed here
|
||||
z_ndc = screen_z // Caller-provided (e.g., 0..1 depth)
|
||||
```
|
||||
|
||||
### NDC → World (`view_port_to_screen`)
|
||||
|
||||
Despite the method name, this function **unprojects** an NDC point back to world space:
|
||||
|
||||
1. Compute `VP^{-1}`; if not invertible → error.
|
||||
2. Multiply by NDC (homogeneous 4D) and divide by `w`.
|
||||
3. Return world point.
|
||||
|
||||
> Tip: to build a **world-space ray** from a screen pixel, unproject at `z=0` (near) and `z=1` (far).
|
||||
|
||||
---
|
||||
|
||||
## Caching & invalidation
|
||||
|
||||
* `get_view_projection_matrix()` computes `P*V` once and caches it.
|
||||
* Any setter (`set_*`) or `look_at()` clears the cache (`m_view_projection_matrix = std::nullopt`).
|
||||
|
||||
---
|
||||
|
||||
## Notes & gotchas
|
||||
|
||||
* **Matrix order**: The camera multiplies `P * V`. Make sure your **Trait** matches this convention.
|
||||
* **Store ordering**: The `Mat4X4Type::get_store_ordering()` is used when building homogeneous columns; ensure it’s consistent with your matrix implementation.
|
||||
* **Naming quirk**: `view_port_to_screen()` returns a **world** point from **NDC** (it’s an unproject). Consider renaming to `ndc_to_world()` in your codebase for clarity.
|
||||
* **FOV units**: `FieldOfView` uses the project’s `Angle` type; pass degrees via `angles::degrees(...)`.
|
||||
|
||||
---
|
||||
|
||||
## Minimal trait sketch (column-major, left-handed)
|
||||
|
||||
```cpp
|
||||
struct LHCTrait {
|
||||
static MyAngles calc_look_at_angle(const Vector3<float>& eye,
|
||||
const Vector3<float>& at) noexcept { /* ... */ }
|
||||
|
||||
static Mat4 calc_view_matrix(const MyAngles& ang,
|
||||
const Vector3<float>& eye) noexcept {
|
||||
// Build from forward/right/up and translation
|
||||
}
|
||||
|
||||
static Mat4 calc_projection_matrix(const FieldOfView& fov,
|
||||
const ViewPort& vp,
|
||||
float zn, float zf) noexcept {
|
||||
return omath::mat_perspective_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(
|
||||
fov.as_degrees(), vp.aspect_ratio(), zn, zf
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing checklist
|
||||
|
||||
* World point centered in view projects to **screen center**.
|
||||
* Points outside frustum → `WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS`.
|
||||
* Inverting `VP` fails gracefully for singular matrices.
|
||||
* `ScreenStart` switch flips Y as expected.
|
||||
* Screen→World ray: unproject `(x,y,0)` and `(x,y,1)` and verify direction passes through the camera frustum.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Engine-Specific Camera Traits](../engines/) - Camera implementations for different game engines
|
||||
- [View Angles Documentation](../trigonometry/view_angles.md) - Understanding pitch/yaw/roll
|
||||
- [Getting Started Guide](../getting_started.md) - Quick start with projection
|
||||
- [Tutorials - World-to-Screen](../tutorials.md#tutorial-2-world-to-screen-projection) - Complete projection tutorial
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
79
docs/projection/error_codes.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# `omath::projection::Error` — Error codes for world/screen projection
|
||||
|
||||
> Header: `omath/projection/error_codes.hpp`
|
||||
> Namespace: `omath::projection`
|
||||
> Type: `enum class Error : uint16_t`
|
||||
|
||||
These error codes are returned by camera/projection helpers (e.g., `Camera::world_to_screen`, `Camera::screen_to_world`) wrapped in `std::expected<..., Error>`. Use them to distinguish **clipping/visibility** problems from **matrix/math** failures.
|
||||
|
||||
---
|
||||
|
||||
## Enum values
|
||||
|
||||
```cpp
|
||||
enum class Error : uint16_t {
|
||||
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
|
||||
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
|
||||
};
|
||||
```
|
||||
|
||||
* **`WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS`**
|
||||
The input point cannot produce a valid on-screen coordinate:
|
||||
|
||||
* Clip-space `w == 0` (point at/infinite or behind camera plane), or
|
||||
* After projection, any NDC component is outside `[-1, 1]`.
|
||||
|
||||
* **`INV_VIEW_PROJ_MAT_DET_EQ_ZERO`**
|
||||
The **View × Projection** matrix is not invertible (determinant ≈ 0).
|
||||
Unprojection (`screen_to_world` / `view_port_to_screen`) requires an invertible matrix.
|
||||
|
||||
---
|
||||
|
||||
## Typical usage
|
||||
|
||||
```cpp
|
||||
using omath::projection::Error;
|
||||
|
||||
auto pix = cam.world_to_screen(point);
|
||||
if (!pix) {
|
||||
switch (pix.error()) {
|
||||
case Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS:
|
||||
// Cull label/marker; point is off-screen or behind camera.
|
||||
break;
|
||||
case Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
|
||||
// Investigate camera/projection setup; near/far/FOV or trait bug.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Unproject a screen pixel (top-left origin) at depth 1.0
|
||||
if (auto world = cam.screen_to_world({sx, sy, 1.0f})) {
|
||||
// use *world
|
||||
} else if (world.error() == Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO) {
|
||||
// handle singular VP matrix
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When you might see these errors
|
||||
|
||||
* **Out-of-bounds**
|
||||
|
||||
* The world point lies outside the camera frustum.
|
||||
* The point is behind the camera (clip `w <= 0`).
|
||||
* Extremely large coordinates cause overflow and fail NDC bounds.
|
||||
|
||||
* **Non-invertible VP**
|
||||
|
||||
* Degenerate projection settings (e.g., `near == far`, zero FOV).
|
||||
* Trait builds `P` or `V` incorrectly (wrong handedness/order).
|
||||
* Numerical issues from nearly singular configurations.
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
* Validate camera setup: `near > 0`, `far > near`, sensible FOV (e.g., 30°–120°).
|
||||
* For UI markers: treat `WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS` as a simple **cull** signal.
|
||||
* Log `INV_VIEW_PROJ_MAT_DET_EQ_ZERO` — it usually indicates a configuration or math bug worth fixing rather than hiding.
|
||||
164
docs/rev_eng/external_rev_object.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# `omath::rev_eng::ExternalReverseEngineeredObject` — typed offsets over external memory
|
||||
|
||||
> Header: `omath/rev_eng/external_reverse_engineered_object.hpp`
|
||||
> Namespace: `omath::rev_eng`
|
||||
> Pattern: **CRTP-style wrapper** around a user-provided *ExternalMemoryManagementTrait* that actually reads/writes another process or device’s memory.
|
||||
|
||||
A tiny base class for reverse-engineered objects that live **outside** your address space. You pass an absolute base address and a trait with `read_memory` / `write_memory`. Your derived types then expose strongly-typed getters/setters that delegate into the trait using **byte offsets**.
|
||||
|
||||
---
|
||||
|
||||
## Quick look
|
||||
|
||||
```cpp
|
||||
template<class ExternalMemoryManagementTrait>
|
||||
class ExternalReverseEngineeredObject {
|
||||
public:
|
||||
explicit ExternalReverseEngineeredObject(std::uintptr_t addr)
|
||||
: m_object_address(addr) {}
|
||||
|
||||
protected:
|
||||
template<class Type>
|
||||
[[nodiscard]] Type get_by_offset(std::ptrdiff_t offset) const {
|
||||
return ExternalMemoryManagementTrait::read_memory(m_object_address + offset);
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
void set_by_offset(std::ptrdiff_t offset, const Type& value) const {
|
||||
ExternalMemoryManagementTrait::write_memory(m_object_address + offset, value);
|
||||
}
|
||||
|
||||
private:
|
||||
std::uintptr_t m_object_address{};
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Trait requirements
|
||||
|
||||
Your `ExternalMemoryManagementTrait` must provide:
|
||||
|
||||
```cpp
|
||||
// Reads sizeof(T) bytes starting at absolute address and returns T.
|
||||
template<class T>
|
||||
static T read_memory(std::uintptr_t absolute_address);
|
||||
|
||||
// Writes sizeof(T) bytes to absolute address.
|
||||
template<class T>
|
||||
static void write_memory(std::uintptr_t absolute_address, const T& value);
|
||||
```
|
||||
|
||||
> Tip: If your implementation prefers returning `bool`/`expected<>` for writes, either:
|
||||
>
|
||||
> * make `write_memory` `void` and throw/log internally, or
|
||||
> * adjust `set_by_offset` in your fork to surface the status.
|
||||
|
||||
### Common implementations
|
||||
|
||||
* **Windows**: wrap `ReadProcessMemory` / `WriteProcessMemory` with a stored `HANDLE` (often captured via a singleton or embedded in the trait).
|
||||
* **Linux**: `/proc/<pid>/mem`, `process_vm_readv/writev`, `ptrace`.
|
||||
* **Device/FPGA**: custom MMIO/driver APIs.
|
||||
|
||||
---
|
||||
|
||||
## How to use (derive and map fields)
|
||||
|
||||
Create a concrete type for your target structure and map known offsets:
|
||||
|
||||
```cpp
|
||||
struct WinRPMTrait {
|
||||
template<class T>
|
||||
static T read_memory(std::uintptr_t addr) {
|
||||
T out{};
|
||||
SIZE_T n{};
|
||||
if (!ReadProcessMemory(g_handle, reinterpret_cast<LPCVOID>(addr), &out, sizeof(T), &n) || n != sizeof(T))
|
||||
throw std::runtime_error("RPM failed");
|
||||
return out;
|
||||
}
|
||||
template<class T>
|
||||
static void write_memory(std::uintptr_t addr, const T& val) {
|
||||
SIZE_T n{};
|
||||
if (!WriteProcessMemory(g_handle, reinterpret_cast<LPVOID>(addr), &val, sizeof(T), &n) || n != sizeof(T))
|
||||
throw std::runtime_error("WPM failed");
|
||||
}
|
||||
};
|
||||
|
||||
class Player final : public omath::rev_eng::ExternalReverseEngineeredObject<WinRPMTrait> {
|
||||
using Base = omath::rev_eng::ExternalReverseEngineeredObject<WinRPMTrait>;
|
||||
public:
|
||||
using Base::Base; // inherit ctor (takes base address)
|
||||
|
||||
// Offsets taken from your RE notes (in bytes)
|
||||
Vector3<float> position() const { return get_by_offset<Vector3<float>>(0x30); }
|
||||
void set_position(const Vector3<float>& p) const { set_by_offset(0x30, p); }
|
||||
|
||||
float health() const { return get_by_offset<float>(0x100); }
|
||||
void set_health(float h) const { set_by_offset(0x100, h); }
|
||||
};
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```cpp
|
||||
Player p{ /* base address you discovered */ 0x7FF6'1234'0000ull };
|
||||
auto pos = p.position();
|
||||
p.set_health(100.f);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design notes & constraints
|
||||
|
||||
* **Offsets are byte offsets** from the object’s **base address** passed to the constructor.
|
||||
* **Type safety is on you**: `Type` must match the external layout at that offset (endian, packing, alignment).
|
||||
* **No lifetime tracking**: if the target object relocates/frees, you must update/recreate the wrapper with the new base address.
|
||||
* **Thread safety**: the class itself is stateless; thread safety depends on your trait implementation.
|
||||
* **Endianness**: assumes the host and target endianness agree, or your trait handles conversion.
|
||||
* **Error handling**: this header doesn’t prescribe it; adopt exceptions/expected/logging inside the trait.
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
* Centralize offsets in one place (constexprs or a small struct) and **comment source/version** (e.g., *game v1.2.3*).
|
||||
* Wrap fragile multi-field writes in a trait-level **transaction** if your platform supports it.
|
||||
* Validate pointers/guards (e.g., vtable signature, canary) before trusting offsets.
|
||||
* Prefer **plain old data** (`struct` without virtuals) for `Type` to ensure trivial byte copies.
|
||||
|
||||
---
|
||||
|
||||
## Minimal trait sketch (POSIX, `process_vm_readv`)
|
||||
|
||||
```cpp
|
||||
struct LinuxPvmTrait {
|
||||
static pid_t pid;
|
||||
|
||||
template<class T> static T read_memory(std::uintptr_t addr) {
|
||||
T out{};
|
||||
iovec local{ &out, sizeof(out) }, remote{ reinterpret_cast<void*>(addr), sizeof(out) };
|
||||
if (process_vm_readv(pid, &local, 1, &remote, 1, 0) != ssize_t(sizeof(out)))
|
||||
throw std::runtime_error("pvm_readv failed");
|
||||
return out;
|
||||
}
|
||||
|
||||
template<class T> static void write_memory(std::uintptr_t addr, const T& val) {
|
||||
iovec local{ const_cast<T*>(&val), sizeof(val) }, remote{ reinterpret_cast<void*>(addr), sizeof(val) };
|
||||
if (process_vm_writev(pid, &local, 1, &remote, 1, 0) != ssize_t(sizeof(val)))
|
||||
throw std::runtime_error("pvm_writev failed");
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* **Garbled values** → wrong offset/Type, or target’s structure changed between versions.
|
||||
* **Access denied** → missing privileges (admin/root), wrong process handle, or page protections.
|
||||
* **Crashes in trait** → add bounds/sanity checks; many APIs fail on unmapped pages.
|
||||
* **Writes “stick” only briefly** → the target may constantly overwrite (server authority / anti-cheat / replication).
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
142
docs/rev_eng/internal_rev_object.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# `omath::rev_eng::InternalReverseEngineeredObject` — raw in-process offset/VTABLE access
|
||||
|
||||
> Header: `omath/rev_eng/internal_reverse_engineered_object.hpp`
|
||||
> Namespace: `omath::rev_eng`
|
||||
> Purpose: Convenience base for **internal** (same-process) RE wrappers that:
|
||||
>
|
||||
> * read/write fields by **byte offset** from `this`
|
||||
> * call **virtual methods** by **vtable index**
|
||||
|
||||
---
|
||||
|
||||
## At a glance
|
||||
|
||||
```cpp
|
||||
class InternalReverseEngineeredObject {
|
||||
protected:
|
||||
template<class Type>
|
||||
[[nodiscard]] Type& get_by_offset(std::ptrdiff_t offset);
|
||||
|
||||
template<class Type>
|
||||
[[nodiscard]] const Type& get_by_offset(std::ptrdiff_t offset) const;
|
||||
|
||||
template<std::size_t id, class ReturnType>
|
||||
ReturnType call_virtual_method(auto... arg_list);
|
||||
};
|
||||
```
|
||||
|
||||
* `get_by_offset<T>(off)` — returns a **reference** to `T` located at `reinterpret_cast<uintptr_t>(this) + off`.
|
||||
* `call_virtual_method<id, Ret>(args...)` — fetches the function pointer from `(*reinterpret_cast<void***>(this))[id]` and invokes it as a free function with implicit `this` passed explicitly.
|
||||
|
||||
On MSVC builds the function pointer type uses `__thiscall`; on non-MSVC it uses a plain function pointer taking `void*` as the first parameter (the typical Itanium ABI shape).
|
||||
|
||||
---
|
||||
|
||||
## Example: wrapping a reverse-engineered class
|
||||
|
||||
```cpp
|
||||
struct Player : omath::rev_eng::InternalReverseEngineeredObject {
|
||||
// Field offsets (document game/app version!)
|
||||
static constexpr std::ptrdiff_t kHealth = 0x100;
|
||||
static constexpr std::ptrdiff_t kPosition = 0x30;
|
||||
|
||||
// Accessors
|
||||
float& health() { return get_by_offset<float>(kHealth); }
|
||||
const float& health() const { return get_by_offset<float>(kHealth); }
|
||||
|
||||
Vector3<float>& position() { return get_by_offset<Vector3<float>>(kPosition); }
|
||||
const Vector3<float>& position() const { return get_by_offset<Vector3<float>>(kPosition); }
|
||||
|
||||
// Virtuals (vtable indices discovered via RE)
|
||||
int getTeam() { return call_virtual_method<27, int>(); }
|
||||
void setArmor(float val) { call_virtual_method<42, void>(val); } // signature must match!
|
||||
};
|
||||
```
|
||||
|
||||
Usage:
|
||||
|
||||
```cpp
|
||||
auto* p = /* pointer to live Player instance within the same process */;
|
||||
p->health() = 100.f;
|
||||
int team = p->getTeam();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How `call_virtual_method` resolves the signature
|
||||
|
||||
```cpp
|
||||
template<std::size_t id, class ReturnType>
|
||||
ReturnType call_virtual_method(auto... arg_list) {
|
||||
#ifdef _MSC_VER
|
||||
using Fn = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
||||
#else
|
||||
using Fn = ReturnType(*)(void*, decltype(arg_list)...);
|
||||
#endif
|
||||
return (*reinterpret_cast<Fn**>(this))[id](this, arg_list...);
|
||||
}
|
||||
```
|
||||
|
||||
* The **first parameter** is always `this` (`void*`).
|
||||
* Remaining parameter types are deduced from the **actual arguments** (`decltype(arg_list)...`).
|
||||
Ensure you pass arguments with the correct types (e.g., `int32_t` vs `int`, pointer/ref qualifiers), or define thin wrappers that cast to the exact signature you recovered.
|
||||
|
||||
> ⚠ On 32-bit MSVC the `__thiscall` distinction matters; on 64-bit MSVC it’s ignored (all member funcs use the common x64 calling convention).
|
||||
|
||||
---
|
||||
|
||||
## Safety notes (read before using!)
|
||||
|
||||
Working at this level is inherently unsafe; be deliberate:
|
||||
|
||||
1. **Correct offsets & alignment**
|
||||
|
||||
* `get_by_offset<T>` assumes `this + offset` is **properly aligned** for `T` and points to an object of type `T`.
|
||||
* Wrong offsets or misalignment ⇒ **undefined behavior** (UB), crashes, silent corruption.
|
||||
|
||||
2. **Object layout assumptions**
|
||||
|
||||
* The vtable pointer is assumed to be at the **start of the most-derived subobject at `this`**.
|
||||
* With **multiple/virtual inheritance**, the desired subobject’s vptr may be at a non-zero offset. If so, adjust `this` to that subobject before calling, e.g.:
|
||||
|
||||
```cpp
|
||||
auto* sub = reinterpret_cast<void*>(reinterpret_cast<std::uintptr_t>(this) + kSubobjectOffset);
|
||||
// … then reinterpret sub instead of this inside a custom helper
|
||||
```
|
||||
|
||||
3. **ABI & calling convention**
|
||||
|
||||
* Indices and signatures are **compiler/ABI-specific**. Recheck after updates or different builds (MSVC vs Clang/LLVM-MSVC vs MinGW).
|
||||
|
||||
4. **Strict aliasing**
|
||||
|
||||
* Reinterpreting memory as unrelated `T` can violate aliasing rules. Prefer **trivially copyable** PODs and exact original types where possible.
|
||||
|
||||
5. **Const-correctness**
|
||||
|
||||
* The `const` overload returns `const T&` but still reinterprets memory; do not write through it. Use the non-const overload to mutate.
|
||||
|
||||
6. **Thread safety**
|
||||
|
||||
* No synchronization is provided. Ensure the underlying object isn’t concurrently mutated in incompatible ways.
|
||||
|
||||
---
|
||||
|
||||
## Tips & patterns
|
||||
|
||||
* **Centralize offsets** in `constexpr` with comments (`// game v1.2.3, sig XYZ`).
|
||||
* **Guard reads**: if you have a canary or RTTI/vtable hash, check it before relying on offsets.
|
||||
* **Prefer accessors** returning references**:** lets you both read and write with natural syntax.
|
||||
* **Wrap tricky virtuals**: if a method takes complex/reference params, wrap `call_virtual_method` in a strongly typed member that casts exactly as needed.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* **Crash on virtual call** → wrong index or wrong `this` (subobject), or mismatched signature (args/ret or calling conv).
|
||||
* **Weird field values** → wrong offset, wrong type size/packing, stale layout after an update.
|
||||
* **Only in 32-bit** → double-check `__thiscall` and parameter passing (register vs stack).
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
4
docs/styles/center.css
Normal file
@@ -0,0 +1,4 @@
|
||||
/* docs/css/custom.css */
|
||||
.center-text {
|
||||
text-align: center;
|
||||
}
|
||||
11
docs/styles/custom-header.css
Normal file
@@ -0,0 +1,11 @@
|
||||
/* Widen the navbar container */
|
||||
.navbar .container {
|
||||
max-width: 100%; /* adjust to your target width */
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
/* Tighter spacing between navbar items */
|
||||
.navbar-nav > li > a {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
165
docs/trigonometry/angle.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# `omath::Angle` — templated angle with normalize/clamper + trig
|
||||
|
||||
> Header: `omath/trigonometry/angle.hpp`
|
||||
> Namespace: `omath`
|
||||
> Template: `Angle<Type = float, min = 0, max = 360, flags = AngleFlags::Normalized>`
|
||||
> Requires: `std::is_arithmetic_v<Type>`
|
||||
> Formatters: `std::formatter` for `char`, `wchar_t`, `char8_t` → `"{}deg"`
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`Angle` is a tiny value-type that stores an angle in **degrees** and automatically **normalizes** or **clamps** it into a compile-time range. It exposes conversions to/from radians, common trig (`sin/cos/tan/cot`), arithmetic with wrap/clamp semantics, and lightweight formatting.
|
||||
|
||||
Two behaviors via `AngleFlags`:
|
||||
|
||||
* `AngleFlags::Normalized` (default): values are wrapped into `[min, max]` using `angles::wrap_angle`.
|
||||
* `AngleFlags::Clamped`: values are clamped to `[min, max]` using `std::clamp`.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath {
|
||||
|
||||
enum class AngleFlags { Normalized = 0, Clamped = 1 };
|
||||
|
||||
template<class Type = float, Type min = Type(0), Type max = Type(360),
|
||||
AngleFlags flags = AngleFlags::Normalized>
|
||||
requires std::is_arithmetic_v<Type>
|
||||
class Angle {
|
||||
public:
|
||||
// Construction
|
||||
static constexpr Angle from_degrees(const Type& deg) noexcept;
|
||||
static constexpr Angle from_radians(const Type& rad) noexcept;
|
||||
constexpr Angle() noexcept; // 0 deg, adjusted by flags/range
|
||||
|
||||
// Accessors / conversions (degrees stored internally)
|
||||
constexpr const Type& operator*() const noexcept; // raw degrees reference
|
||||
constexpr Type as_degrees() const noexcept;
|
||||
constexpr Type as_radians() const noexcept;
|
||||
|
||||
// Trig (computed from radians)
|
||||
Type sin() const noexcept;
|
||||
Type cos() const noexcept;
|
||||
Type tan() const noexcept;
|
||||
Type atan() const noexcept; // atan(as_radians()) (rarely used)
|
||||
Type cot() const noexcept; // cos()/sin() (watch sin≈0)
|
||||
|
||||
// Arithmetic (wraps or clamps per flags and [min,max])
|
||||
constexpr Angle& operator+=(const Angle&) noexcept;
|
||||
constexpr Angle& operator-=(const Angle&) noexcept;
|
||||
constexpr Angle operator+(const Angle&) noexcept;
|
||||
constexpr Angle operator-(const Angle&) noexcept;
|
||||
constexpr Angle operator-() const noexcept;
|
||||
|
||||
// Comparison (partial ordering)
|
||||
constexpr std::partial_ordering operator<=>(const Angle&) const noexcept = default;
|
||||
};
|
||||
|
||||
} // namespace omath
|
||||
```
|
||||
|
||||
### Formatting
|
||||
|
||||
```cpp
|
||||
std::format("{}", Angle<float>::from_degrees(45)); // "45deg"
|
||||
```
|
||||
|
||||
Formatters exist for `char`, `wchar_t`, and `char8_t`.
|
||||
|
||||
---
|
||||
|
||||
## Usage examples
|
||||
|
||||
### Defaults (0–360, normalized)
|
||||
|
||||
```cpp
|
||||
using Deg = omath::Angle<>; // float, [0,360], Normalized
|
||||
|
||||
auto a = Deg::from_degrees(370); // -> 10deg
|
||||
auto b = Deg::from_radians(omath::angles::pi); // -> 180deg
|
||||
|
||||
a += Deg::from_degrees(355); // 10 + 355 -> 365 -> wraps -> 5deg
|
||||
|
||||
float s = a.sin(); // sin(5°)
|
||||
```
|
||||
|
||||
### Clamped range
|
||||
|
||||
```cpp
|
||||
using Fov = omath::Angle<float, 1.f, 179.f, omath::AngleFlags::Clamped>;
|
||||
auto fov = Fov::from_degrees(200.f); // -> 179deg (clamped)
|
||||
```
|
||||
|
||||
### Signed, normalized range
|
||||
|
||||
```cpp
|
||||
using SignedDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
|
||||
|
||||
auto x = SignedDeg::from_degrees(190.f); // -> -170deg
|
||||
auto y = SignedDeg::from_degrees(-200.f); // -> 160deg
|
||||
auto z = x + y; // -170 + 160 = -10deg (wrapped if needed)
|
||||
```
|
||||
|
||||
### Get/set raw degrees
|
||||
|
||||
```cpp
|
||||
auto yaw = SignedDeg::from_degrees(-45.f);
|
||||
float deg = *yaw; // same as yaw.as_degrees()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Semantics & notes
|
||||
|
||||
* **Storage & units:** Internally stores **degrees** (`Type m_angle`). `as_radians()`/`from_radians()` use the project helpers in `omath::angles`.
|
||||
* **Arithmetic honors policy:** `operator+=`/`-=` and the binary `+`/`-` apply **wrap** or **clamp** in `[min,max]`, mirroring construction behavior.
|
||||
* **`atan()`**: returns `std::atan(as_radians())` (the arctangent of the *radian value*). This is mathematically unusual for an angle type and is rarely useful; prefer `tan()`/`atan2` in client code when solving geometry problems.
|
||||
* **`cot()` / `tan()` singularities:** Near multiples where `sin() ≈ 0` or `cos() ≈ 0`, results blow up. Guard in your usage if inputs can approach these points.
|
||||
* **Comparison:** `operator<=>` is defaulted. With normalization, distinct representatives can compare as expected (e.g., `-180` vs `180` in signed ranges are distinct endpoints).
|
||||
* **No implicit numeric conversion:** There’s **no `operator Type()`**. Use `as_degrees()`/`as_radians()` (or `*angle`) explicitly—this intentional friction avoids unit mistakes.
|
||||
|
||||
---
|
||||
|
||||
## Customization patterns
|
||||
|
||||
* **Radians workflow:** Keep angles in degrees internally but wrap helper creators:
|
||||
|
||||
```cpp
|
||||
inline Deg degf(float d) { return Deg::from_degrees(d); }
|
||||
inline Deg radf(float r) { return Deg::from_radians(r); }
|
||||
```
|
||||
* **Compile-time policy:** Pick ranges/flags at the type level to enforce invariants (e.g., `YawDeg = Angle<float,-180,180,Normalized>`; `FovDeg = Angle<float,1,179,Clamped>`).
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & gotchas
|
||||
|
||||
* Ensure `min < max` at compile time for meaningful wrap/clamp behavior.
|
||||
* For normalized signed ranges, decide whether your `wrap_angle(min,max)` treats endpoints half-open (e.g., `[-180,180)`) to avoid duplicate representations; the formatter will print the stored value verbatim.
|
||||
* If you need **sum of many angles**, accumulating in radians then converting back can improve numeric stability at extreme values.
|
||||
|
||||
---
|
||||
|
||||
## Minimal tests
|
||||
|
||||
```cpp
|
||||
using A = omath::Angle<>;
|
||||
REQUIRE(A::from_degrees(360).as_degrees() == 0.f);
|
||||
REQUIRE(A::from_degrees(-1).as_degrees() == 359.f);
|
||||
|
||||
using S = omath::Angle<float,-180.f,180.f, omath::AngleFlags::Normalized>;
|
||||
REQUIRE(S::from_degrees( 181).as_degrees() == -179.f);
|
||||
REQUIRE(S::from_degrees(-181).as_degrees() == 179.f);
|
||||
|
||||
using C = omath::Angle<float, 10.f, 20.f, omath::AngleFlags::Clamped>;
|
||||
REQUIRE(C::from_degrees(5).as_degrees() == 10.f);
|
||||
REQUIRE(C::from_degrees(25).as_degrees() == 20.f);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
107
docs/trigonometry/angles.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# `omath::angles` — angle conversions, FOV helpers, and wrapping
|
||||
|
||||
> Header: `omath/trigonometry/angles.hpp`
|
||||
> Namespace: `omath::angles`
|
||||
> All functions are `[[nodiscard]]` and `noexcept` where applicable.
|
||||
|
||||
A small set of constexpr-friendly utilities for converting between degrees/radians, converting horizontal/vertical field of view, and wrapping angles into a closed interval.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
// Degrees ↔ Radians (Type must be floating-point)
|
||||
template<class Type>
|
||||
requires std::is_floating_point_v<Type>
|
||||
constexpr Type radians_to_degrees(const Type& radians) noexcept;
|
||||
|
||||
template<class Type>
|
||||
requires std::is_floating_point_v<Type>
|
||||
constexpr Type degrees_to_radians(const Type& degrees) noexcept;
|
||||
|
||||
// FOV conversion (inputs/outputs in degrees, aspect = width/height)
|
||||
template<class Type>
|
||||
requires std::is_floating_point_v<Type>
|
||||
Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) noexcept;
|
||||
|
||||
template<class Type>
|
||||
requires std::is_floating_point_v<Type>
|
||||
Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) noexcept;
|
||||
|
||||
// Wrap angle into [min, max] (any arithmetic type)
|
||||
template<class Type>
|
||||
requires std::is_arithmetic_v<Type>
|
||||
Type wrap_angle(const Type& angle, const Type& min, const Type& max) noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Degrees ↔ Radians
|
||||
|
||||
```cpp
|
||||
float rad = omath::angles::degrees_to_radians(180.0f); // π
|
||||
double deg = omath::angles::radians_to_degrees(std::numbers::pi); // 180
|
||||
```
|
||||
|
||||
### Horizontal ↔ Vertical FOV
|
||||
|
||||
* `aspect` = **width / height**.
|
||||
* Inputs/outputs are **degrees**.
|
||||
|
||||
```cpp
|
||||
float hdeg = 90.0f;
|
||||
float aspect = 16.0f / 9.0f;
|
||||
|
||||
float vdeg = omath::angles::horizontal_fov_to_vertical(hdeg, aspect); // ~58.0°
|
||||
float hdeg2 = omath::angles::vertical_fov_to_horizontal(vdeg, aspect); // ≈ 90.0°
|
||||
```
|
||||
|
||||
Formulas (in radians):
|
||||
|
||||
* `v = 2 * atan( tan(h/2) / aspect )`
|
||||
* `h = 2 * atan( tan(v/2) * aspect )`
|
||||
|
||||
### Wrapping angles (or any periodic value)
|
||||
|
||||
Wrap any numeric `angle` into `[min, max]`:
|
||||
|
||||
```cpp
|
||||
// Wrap degrees into [0, 360]
|
||||
float a = omath::angles::wrap_angle( 370.0f, 0.0f, 360.0f); // 10
|
||||
float b = omath::angles::wrap_angle( -15.0f, 0.0f, 360.0f); // 345
|
||||
// Signed range [-180,180]
|
||||
float c = omath::angles::wrap_angle( 200.0f, -180.0f, 180.0f); // -160
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & edge cases
|
||||
|
||||
* **Type requirements**
|
||||
|
||||
* Converters & FOV helpers require **floating-point** `Type`.
|
||||
* `wrap_angle` accepts any arithmetic `Type` (floats or integers).
|
||||
* **Aspect ratio** must be **positive** and finite. For `aspect == 0` the FOV helpers are undefined.
|
||||
* **Units**: FOV functions accept/return **degrees** but compute internally in radians.
|
||||
* **Wrapping interval**: Behavior assumes `max > min`. The result lies in the **closed interval** `[min, max]` with modulo arithmetic; if you need half-open behavior (e.g., `[min,max)`), adjust your range or post-process endpoint cases.
|
||||
* **constexpr**: Converters are `constexpr`; FOV helpers are runtime constexpr-compatible except for `std::atan/std::tan` constraints on some standard libraries.
|
||||
|
||||
---
|
||||
|
||||
## Quick tests
|
||||
|
||||
```cpp
|
||||
using namespace omath::angles;
|
||||
|
||||
static_assert(degrees_to_radians(180.0) == std::numbers::pi);
|
||||
static_assert(radians_to_degrees(std::numbers::pi_v<float>) == 180.0f);
|
||||
|
||||
float v = horizontal_fov_to_vertical(90.0f, 16.0f/9.0f);
|
||||
float h = vertical_fov_to_horizontal(v, 16.0f/9.0f);
|
||||
assert(std::abs(h - 90.0f) < 1e-5f);
|
||||
|
||||
assert(wrap_angle(360.0f, 0.0f, 360.0f) == 0.0f || wrap_angle(360.0f, 0.0f, 360.0f) == 360.0f);
|
||||
```
|
||||
87
docs/trigonometry/view_angles.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# `omath::ViewAngles` — tiny POD for pitch/yaw/roll
|
||||
|
||||
> Header: your project’s `view_angles.hpp`
|
||||
> Namespace: `omath`
|
||||
> Kind: **aggregate struct** (POD), no methods, no allocation
|
||||
|
||||
A minimal container for Euler angles. You choose the types for each component (e.g., raw `float` or the strong `omath::Angle<>` type), and plug it into systems like `projection::Camera`.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath {
|
||||
template<class PitchType, class YawType, class RollType>
|
||||
struct ViewAngles {
|
||||
PitchType pitch;
|
||||
YawType yaw;
|
||||
RollType roll;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
* Aggregate: supports brace-init, aggregate copying, and `constexpr` usage when the component types do.
|
||||
* Semantics (units/handedness/ranges) are **entirely defined by your chosen types**.
|
||||
|
||||
---
|
||||
|
||||
## Common aliases
|
||||
|
||||
```cpp
|
||||
// Simple, raw degrees as floats (be careful with wrapping!)
|
||||
using ViewAnglesF = omath::ViewAngles<float, float, float>;
|
||||
|
||||
// Safer, policy-based angles (recommended)
|
||||
using PitchDeg = omath::Angle<float, -89.f, 89.f, omath::AngleFlags::Clamped>;
|
||||
using YawDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
|
||||
using RollDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
|
||||
using ViewAnglesDeg = omath::ViewAngles<PitchDeg, YawDeg, RollDeg>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic construction
|
||||
|
||||
```cpp
|
||||
omath::ViewAngles<float,float,float> a{ 10.f, 45.f, 0.f }; // pitch, yaw, roll in degrees
|
||||
```
|
||||
|
||||
### With `omath::Angle<>` (automatic wrap/clamper)
|
||||
|
||||
```cpp
|
||||
ViewAnglesDeg v{
|
||||
PitchDeg::from_degrees( 95.f), // -> 89deg (clamped)
|
||||
YawDeg::from_degrees (-190.f), // -> 170deg (wrapped)
|
||||
RollDeg::from_degrees ( 30.f)
|
||||
};
|
||||
```
|
||||
|
||||
### Using with `projection::Camera`
|
||||
|
||||
```cpp
|
||||
using Mat4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
|
||||
using Cam = omath::projection::Camera<Mat4, ViewAnglesDeg, MyCameraTrait>;
|
||||
|
||||
omath::projection::ViewPort vp{1920,1080};
|
||||
auto fov = omath::angles::degrees_to_radians(70.f); // or your Angle type
|
||||
|
||||
Cam cam(/*position*/ {0,1.7f,-3},
|
||||
/*angles*/ ViewAnglesDeg{ PitchDeg::from_degrees(0),
|
||||
YawDeg::from_degrees(0),
|
||||
RollDeg::from_degrees(0) },
|
||||
/*viewport*/ vp,
|
||||
/*fov*/ omath::Angle<float,0.f,180.f,omath::AngleFlags::Clamped>::from_degrees(70.f),
|
||||
/*near*/ 0.1f,
|
||||
/*far*/ 1000.f);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & tips
|
||||
|
||||
* **Ranges/units**: pick types that encode your policy (e.g., signed yaw in `[-180,180]`, pitch clamped to avoid gimbal flips).
|
||||
* **Handedness & order**: this struct doesn’t impose rotation order. Your math/trait layer (e.g., `MyCameraTrait`) must define how `(pitch, yaw, roll)` map to a view matrix (common orders: ZYX or XYZ).
|
||||
* **Zero-cost**: with plain `float`s this is as cheap as three scalars; with `Angle<>` you gain safety at the cost of tiny wrap/clamp logic on construction/arithmetic.
|
||||
525
docs/troubleshooting.md
Normal file
@@ -0,0 +1,525 @@
|
||||
# Troubleshooting
|
||||
|
||||
Solutions to common problems when using OMath.
|
||||
|
||||
---
|
||||
|
||||
## Build & Compilation Issues
|
||||
|
||||
### Error: C++20 features not available
|
||||
|
||||
**Problem:** Compiler doesn't support C++20.
|
||||
|
||||
**Solution:**
|
||||
Upgrade your compiler:
|
||||
- **GCC**: Version 10 or newer
|
||||
- **Clang**: Version 11 or newer
|
||||
- **MSVC**: Visual Studio 2019 16.10 or newer
|
||||
|
||||
Set C++20 in CMakeLists.txt:
|
||||
```cmake
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
```
|
||||
|
||||
### Error: `std::expected` not found
|
||||
|
||||
**Problem:** Using C++20 without C++23's `std::expected`.
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Upgrade to C++23** (recommended):
|
||||
```cmake
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
```
|
||||
|
||||
2. **Use a backport library**:
|
||||
```cmake
|
||||
find_package(tl-expected CONFIG REQUIRED)
|
||||
target_link_libraries(your_target PRIVATE tl::expected)
|
||||
```
|
||||
|
||||
### Error: `omath/omath.hpp` not found
|
||||
|
||||
**Problem:** OMath not installed or not in include path.
|
||||
|
||||
**Solution:**
|
||||
|
||||
Check installation:
|
||||
```bash
|
||||
# vcpkg
|
||||
vcpkg list | grep omath
|
||||
|
||||
# Check if files exist
|
||||
ls /path/to/vcpkg/installed/x64-linux/include/omath
|
||||
```
|
||||
|
||||
In CMakeLists.txt:
|
||||
```cmake
|
||||
find_package(omath CONFIG REQUIRED)
|
||||
target_link_libraries(your_target PRIVATE omath::omath)
|
||||
```
|
||||
|
||||
### Linker errors with AVX2 engine
|
||||
|
||||
**Problem:** Undefined references to AVX2 functions.
|
||||
|
||||
**Solution:**
|
||||
|
||||
Enable AVX2 in your build:
|
||||
```cmake
|
||||
if(MSVC)
|
||||
target_compile_options(your_target PRIVATE /arch:AVX2)
|
||||
else()
|
||||
target_compile_options(your_target PRIVATE -mavx2)
|
||||
endif()
|
||||
```
|
||||
|
||||
Or use the legacy engine instead:
|
||||
```cpp
|
||||
// Use this instead of ProjPredEngineAVX2
|
||||
ProjPredEngineLegacy engine;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Runtime Issues
|
||||
|
||||
### `world_to_screen()` always returns `nullopt`
|
||||
|
||||
**Common causes:**
|
||||
|
||||
1. **Point behind camera**
|
||||
```cpp
|
||||
// Point is behind the camera
|
||||
Vector3<float> behind = camera_pos - Vector3<float>{0, 0, 100};
|
||||
auto result = camera.world_to_screen(behind); // Returns nullopt
|
||||
```
|
||||
|
||||
**Fix:** Only project points in front of camera. Check Z-coordinate in view space.
|
||||
|
||||
2. **Invalid near/far planes**
|
||||
```cpp
|
||||
// Bad: near >= far
|
||||
Camera camera(pos, angles, viewport, fov, 100.0f, 1.0f);
|
||||
|
||||
// Good: near < far
|
||||
Camera camera(pos, angles, viewport, fov, 0.1f, 1000.0f);
|
||||
```
|
||||
|
||||
3. **Invalid FOV**
|
||||
```cpp
|
||||
// Bad: FOV out of range
|
||||
auto fov = FieldOfView::from_degrees(0.0f); // Too small
|
||||
auto fov = FieldOfView::from_degrees(180.0f); // Too large
|
||||
|
||||
// Good: FOV in valid range
|
||||
auto fov = FieldOfView::from_degrees(90.0f);
|
||||
```
|
||||
|
||||
4. **Uninitialized camera**
|
||||
```cpp
|
||||
// Make sure camera is properly initialized
|
||||
camera.update(current_position, current_angles);
|
||||
```
|
||||
|
||||
**Debugging:**
|
||||
```cpp
|
||||
Vector3<float> world_pos{100, 100, 100};
|
||||
|
||||
// Check projection step by step
|
||||
std::cout << "World pos: " << world_pos.x << ", "
|
||||
<< world_pos.y << ", " << world_pos.z << "\n";
|
||||
|
||||
auto view_matrix = camera.get_view_matrix();
|
||||
// Transform to view space manually and check if Z > 0
|
||||
|
||||
if (auto screen = camera.world_to_screen(world_pos)) {
|
||||
std::cout << "Success: " << screen->x << ", " << screen->y << "\n";
|
||||
} else {
|
||||
std::cout << "Failed - check if point is behind camera\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Angles wrapping incorrectly
|
||||
|
||||
**Problem:** Angles not normalizing to expected ranges.
|
||||
|
||||
**Solution:**
|
||||
|
||||
Use proper angle types:
|
||||
```cpp
|
||||
// Wrong: using raw floats
|
||||
float pitch = 95.0f; // Out of valid range!
|
||||
|
||||
// Right: using typed angles
|
||||
auto pitch = PitchAngle::from_degrees(89.0f); // Clamped to valid range
|
||||
```
|
||||
|
||||
For custom ranges:
|
||||
```cpp
|
||||
// Define custom angle with wrapping
|
||||
auto angle = Angle<float, -180.0f, 180.0f, AngleFlags::Normalized>::from_degrees(270.0f);
|
||||
// Result: -90° (wrapped)
|
||||
```
|
||||
|
||||
### Projection appears mirrored or inverted
|
||||
|
||||
**Problem:** Using wrong engine trait for your game.
|
||||
|
||||
**Solution:**
|
||||
|
||||
Different engines have different coordinate systems:
|
||||
|
||||
| Symptom | Likely Issue | Fix |
|
||||
|---------|-------------|-----|
|
||||
| Upside down | Y-axis inverted | Try different engine or negate Y |
|
||||
| Left-right flipped | Wrong handedness | Check engine documentation |
|
||||
| Rotated 90° | Axis swap | Verify engine coordinate system |
|
||||
|
||||
```cpp
|
||||
// Try different engine traits
|
||||
using namespace omath::source_engine; // Z-up, left-handed
|
||||
using namespace omath::unity_engine; // Y-up, left-handed
|
||||
using namespace omath::unreal_engine; // Z-up, left-handed (different conventions)
|
||||
using namespace omath::opengl_engine; // Y-up, right-handed
|
||||
```
|
||||
|
||||
If still wrong, manually transform coordinates:
|
||||
```cpp
|
||||
// Example: swap Y and Z for Y-up to Z-up conversion
|
||||
Vector3<float> convert_y_up_to_z_up(const Vector3<float>& pos) {
|
||||
return Vector3<float>{pos.x, pos.z, pos.y};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projectile Prediction Issues
|
||||
|
||||
### `maybe_calculate_aim_point()` returns `nullopt`
|
||||
|
||||
**Common causes:**
|
||||
|
||||
1. **Target moving too fast**
|
||||
```cpp
|
||||
Target target;
|
||||
target.velocity = Vector3<float>{1000, 0, 0}; // Very fast!
|
||||
|
||||
Projectile proj;
|
||||
proj.speed = 500.0f; // Too slow to catch target
|
||||
|
||||
// Returns nullopt - projectile can't catch target
|
||||
```
|
||||
|
||||
**Fix:** Check if projectile speed > target speed in the direction of motion.
|
||||
|
||||
2. **Zero projectile speed**
|
||||
```cpp
|
||||
Projectile proj;
|
||||
proj.speed = 0.0f; // Invalid!
|
||||
|
||||
// Returns nullopt
|
||||
```
|
||||
|
||||
**Fix:** Ensure `proj.speed > 0`.
|
||||
|
||||
3. **Invalid positions**
|
||||
```cpp
|
||||
// NaN or infinite values
|
||||
target.position = Vector3<float>{NAN, 0, 0};
|
||||
|
||||
// Returns nullopt
|
||||
```
|
||||
|
||||
**Fix:** Validate all input values are finite.
|
||||
|
||||
4. **Target out of range**
|
||||
```cpp
|
||||
// Target very far away
|
||||
float distance = shooter_pos.distance_to(target.position);
|
||||
float max_range = proj.speed * max_flight_time;
|
||||
|
||||
if (distance > max_range) {
|
||||
// Will return nullopt
|
||||
}
|
||||
```
|
||||
|
||||
**Debugging:**
|
||||
```cpp
|
||||
Projectile proj{/* ... */};
|
||||
Target target{/* ... */};
|
||||
|
||||
// Check inputs
|
||||
assert(proj.speed > 0);
|
||||
assert(std::isfinite(target.position.length()));
|
||||
assert(std::isfinite(target.velocity.length()));
|
||||
|
||||
// Check if target is reachable
|
||||
float distance = proj.origin.distance_to(target.position);
|
||||
float target_speed = target.velocity.length();
|
||||
|
||||
std::cout << "Distance: " << distance << "\n";
|
||||
std::cout << "Projectile speed: " << proj.speed << "\n";
|
||||
std::cout << "Target speed: " << target_speed << "\n";
|
||||
|
||||
if (target_speed >= proj.speed) {
|
||||
std::cout << "Target may be too fast!\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Aim point is inaccurate
|
||||
|
||||
**Problem:** Calculated aim point doesn't hit target.
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
1. **Unit mismatch**
|
||||
```cpp
|
||||
// All units must match!
|
||||
proj.speed = 800.0f; // meters per second
|
||||
target.velocity = Vector3<float>{2, 1, 0}; // Must also be m/s!
|
||||
|
||||
// If using different units (e.g., game units vs meters), convert:
|
||||
float game_units_to_meters = 0.01905f; // Example for Source
|
||||
target.velocity = game_velocity * game_units_to_meters;
|
||||
```
|
||||
|
||||
2. **Wrong gravity vector**
|
||||
```cpp
|
||||
// Source Engine: Z-up
|
||||
proj.gravity = Vector3<float>{0, 0, -9.81f};
|
||||
|
||||
// Unity: Y-up
|
||||
proj.gravity = Vector3<float>{0, -9.81f, 0};
|
||||
```
|
||||
|
||||
3. **Target velocity not updated**
|
||||
```cpp
|
||||
// Update target velocity each frame
|
||||
target.velocity = current_velocity; // Not last frame's velocity!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pattern Scanning Issues
|
||||
|
||||
### Pattern not found when it should be
|
||||
|
||||
**Problem:** `pattern_scan()` returns `nullopt` but pattern exists.
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Pattern syntax error**
|
||||
```cpp
|
||||
// Wrong: missing spaces
|
||||
PatternView pattern{"488B05????????"};
|
||||
|
||||
// Right: spaces between bytes
|
||||
PatternView pattern{"48 8B 05 ?? ?? ?? ??"};
|
||||
```
|
||||
|
||||
2. **Pattern too specific**
|
||||
```cpp
|
||||
// May fail if any byte is different
|
||||
PatternView pattern{"48 8B 05 01 02 03 04 48 85 C0"};
|
||||
|
||||
// Better: use wildcards for variable bytes
|
||||
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
|
||||
```
|
||||
|
||||
3. **Searching wrong memory region**
|
||||
```cpp
|
||||
// Make sure you're scanning the right memory
|
||||
std::vector<uint8_t> code_section = get_code_section();
|
||||
auto result = pattern_scan(code_section, pattern);
|
||||
```
|
||||
|
||||
4. **Pattern might have multiple matches**
|
||||
```cpp
|
||||
// Find all matches instead of just first
|
||||
size_t offset = 0;
|
||||
while (offset < memory.size()) {
|
||||
auto result = pattern_scan(
|
||||
std::span(memory.begin() + offset, memory.end()),
|
||||
pattern
|
||||
);
|
||||
if (result) {
|
||||
std::cout << "Match at: " << offset + result->offset << "\n";
|
||||
offset += result->offset + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern found at wrong location
|
||||
|
||||
**Problem:** Pattern matches unintended code.
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Make pattern more specific**
|
||||
```cpp
|
||||
// Too generic
|
||||
PatternView pattern{"48 8B"};
|
||||
|
||||
// More specific - include more context
|
||||
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0 74 ??"};
|
||||
```
|
||||
|
||||
2. **Verify found address**
|
||||
```cpp
|
||||
if (auto result = pattern_scan(memory, pattern)) {
|
||||
// Verify by checking nearby bytes
|
||||
size_t offset = result->offset;
|
||||
|
||||
// Check if instruction makes sense
|
||||
if (memory[offset] == 0x48 && memory[offset + 1] == 0x8B) {
|
||||
// Looks good
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Use multiple patterns**
|
||||
```cpp
|
||||
// Find reference function first
|
||||
auto ref_pattern = PatternView{"E8 ?? ?? ?? ?? 85 C0"};
|
||||
auto ref_result = pattern_scan(memory, ref_pattern);
|
||||
|
||||
// Then search near that location
|
||||
// This provides context validation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vector & Math Issues
|
||||
|
||||
### `normalized()` returns zero vector
|
||||
|
||||
**Problem:** Normalizing a zero-length vector.
|
||||
|
||||
**Behavior:**
|
||||
```cpp
|
||||
Vector3<float> zero{0, 0, 0};
|
||||
auto result = zero.normalized(); // Returns {0, 0, 0}
|
||||
```
|
||||
|
||||
This is **intentional** to avoid NaN. Check vector length first:
|
||||
```cpp
|
||||
if (v.length() > 0.001f) {
|
||||
auto normalized = v.normalized();
|
||||
// Use normalized vector
|
||||
} else {
|
||||
// Handle zero-length case
|
||||
}
|
||||
```
|
||||
|
||||
### `angle_between()` returns error
|
||||
|
||||
**Problem:** One or both vectors have zero length.
|
||||
|
||||
**Solution:**
|
||||
```cpp
|
||||
auto angle_result = v1.angle_between(v2);
|
||||
|
||||
if (angle_result) {
|
||||
float degrees = angle_result->as_degrees();
|
||||
} else {
|
||||
// Handle error - one or both vectors have zero length
|
||||
std::cerr << "Cannot compute angle between zero-length vectors\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Cross product seems wrong
|
||||
|
||||
**Problem:** Unexpected cross product result.
|
||||
|
||||
**Check:**
|
||||
1. **Right-handed system**
|
||||
```cpp
|
||||
Vector3<float> x{1, 0, 0};
|
||||
Vector3<float> y{0, 1, 0};
|
||||
auto z = x.cross(y); // Should be {0, 0, 1} in right-handed system
|
||||
```
|
||||
|
||||
2. **Order matters**
|
||||
```cpp
|
||||
auto cross1 = a.cross(b); // {x1, y1, z1}
|
||||
auto cross2 = b.cross(a); // {-x1, -y1, -z1} (opposite direction!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Issues
|
||||
|
||||
### Code is slower than expected
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Enable optimizations**
|
||||
```cmake
|
||||
# CMakeLists.txt
|
||||
target_compile_options(your_target PRIVATE
|
||||
$<$<CONFIG:Release>:-O3>
|
||||
$<$<CONFIG:Release>:-march=native>
|
||||
)
|
||||
```
|
||||
|
||||
2. **Use AVX2 engine**
|
||||
```cpp
|
||||
// Instead of
|
||||
ProjPredEngineLegacy engine;
|
||||
|
||||
// Use
|
||||
ProjPredEngineAVX2 engine;
|
||||
```
|
||||
|
||||
3. **Avoid unnecessary operations**
|
||||
```cpp
|
||||
// Bad: recompute every frame
|
||||
for (auto& entity : entities) {
|
||||
float dist = entity.pos.distance_to(player_pos); // Expensive sqrt!
|
||||
if (dist < 100.0f) { /* ... */ }
|
||||
}
|
||||
|
||||
// Good: use squared distance
|
||||
constexpr float max_dist_sq = 100.0f * 100.0f;
|
||||
for (auto& entity : entities) {
|
||||
float dist_sq = entity.pos.distance_to_sqr(player_pos); // No sqrt!
|
||||
if (dist_sq < max_dist_sq) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
4. **Cache matrices**
|
||||
```cpp
|
||||
// Bad: recompute matrix every call
|
||||
for (auto& pos : positions) {
|
||||
auto screen = camera.world_to_screen(pos); // Recomputes matrices!
|
||||
}
|
||||
|
||||
// Good: matrices are cached in camera automatically
|
||||
camera.update(pos, angles); // Updates matrices once
|
||||
for (auto& pos : positions) {
|
||||
auto screen = camera.world_to_screen(pos); // Uses cached matrices
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting More Help
|
||||
|
||||
If your issue isn't covered here:
|
||||
|
||||
1. **Check the docs**: [API Overview](api_overview.md), [Tutorials](tutorials.md)
|
||||
2. **Search GitHub issues**: [Issues page](https://github.com/orange-cpp/omath/issues)
|
||||
3. **Ask on Discord**: [Join community](https://discord.gg/eDgdaWbqwZ)
|
||||
4. **Open a new issue**: Include:
|
||||
- OMath version
|
||||
- Compiler and version
|
||||
- Minimal reproducible example
|
||||
- What you expected vs what happened
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
616
docs/tutorials.md
Normal file
@@ -0,0 +1,616 @@
|
||||
# Tutorials
|
||||
|
||||
This page provides step-by-step tutorials for common OMath use cases.
|
||||
|
||||
---
|
||||
|
||||
## Tutorial 1: Basic Vector Math
|
||||
|
||||
Learn the fundamentals of vector operations in OMath.
|
||||
|
||||
### Step 1: Include OMath
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace omath;
|
||||
```
|
||||
|
||||
### Step 2: Create Vectors
|
||||
|
||||
```cpp
|
||||
// 2D vectors
|
||||
Vector2<float> v2a{3.0f, 4.0f};
|
||||
Vector2<float> v2b{1.0f, 2.0f};
|
||||
|
||||
// 3D vectors
|
||||
Vector3<float> v3a{1.0f, 2.0f, 3.0f};
|
||||
Vector3<float> v3b{4.0f, 5.0f, 6.0f};
|
||||
|
||||
// 4D vectors (often used for homogeneous coordinates)
|
||||
Vector4<float> v4{1.0f, 2.0f, 3.0f, 1.0f};
|
||||
```
|
||||
|
||||
### Step 3: Perform Operations
|
||||
|
||||
```cpp
|
||||
// Addition
|
||||
auto sum = v3a + v3b; // {5, 7, 9}
|
||||
|
||||
// Subtraction
|
||||
auto diff = v3a - v3b; // {-3, -3, -3}
|
||||
|
||||
// Scalar multiplication
|
||||
auto scaled = v3a * 2.0f; // {2, 4, 6}
|
||||
|
||||
// Dot product
|
||||
float dot = v3a.dot(v3b); // 32.0
|
||||
|
||||
// Cross product (3D only)
|
||||
auto cross = v3a.cross(v3b); // {-3, 6, -3}
|
||||
|
||||
// Length
|
||||
float len = v3a.length(); // ~3.74
|
||||
|
||||
// Normalization (safe - returns original if length is zero)
|
||||
auto normalized = v3a.normalized();
|
||||
|
||||
// Distance between vectors
|
||||
float dist = v3a.distance_to(v3b); // ~5.196
|
||||
```
|
||||
|
||||
### Step 4: Angle Calculations
|
||||
|
||||
```cpp
|
||||
if (auto angle = v3a.angle_between(v3b)) {
|
||||
std::cout << "Angle in degrees: " << angle->as_degrees() << "\n";
|
||||
std::cout << "Angle in radians: " << angle->as_radians() << "\n";
|
||||
} else {
|
||||
std::cout << "Cannot compute angle (zero-length vector)\n";
|
||||
}
|
||||
|
||||
// Check if perpendicular
|
||||
if (v3a.is_perpendicular(v3b)) {
|
||||
std::cout << "Vectors are perpendicular\n";
|
||||
}
|
||||
```
|
||||
|
||||
**Key takeaways:**
|
||||
- All vector operations are type-safe and constexpr-friendly
|
||||
- Safe normalization never produces NaN
|
||||
- Angle calculations use `std::expected` for error handling
|
||||
|
||||
---
|
||||
|
||||
## Tutorial 2: World-to-Screen Projection
|
||||
|
||||
Project 3D coordinates to 2D screen space for overlays and ESP.
|
||||
|
||||
### Step 1: Choose Your Game Engine
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
|
||||
// For Source Engine games (CS:GO, TF2, etc.)
|
||||
using namespace omath::source_engine;
|
||||
|
||||
// Or for other engines:
|
||||
// using namespace omath::unity_engine;
|
||||
// using namespace omath::unreal_engine;
|
||||
// using namespace omath::frostbite_engine;
|
||||
```
|
||||
|
||||
### Step 2: Set Up the Camera
|
||||
|
||||
```cpp
|
||||
using namespace omath;
|
||||
using namespace omath::projection;
|
||||
|
||||
// Define viewport (screen dimensions)
|
||||
ViewPort viewport{1920.0f, 1080.0f};
|
||||
|
||||
// Define field of view
|
||||
auto fov = FieldOfView::from_degrees(90.0f);
|
||||
|
||||
// Camera position and angles
|
||||
Vector3<float> camera_pos{0.0f, 0.0f, 100.0f};
|
||||
ViewAngles camera_angles{
|
||||
PitchAngle::from_degrees(0.0f),
|
||||
YawAngle::from_degrees(0.0f),
|
||||
RollAngle::from_degrees(0.0f)
|
||||
};
|
||||
|
||||
// Create camera (using Source Engine in this example)
|
||||
Camera camera(
|
||||
camera_pos,
|
||||
camera_angles,
|
||||
viewport,
|
||||
fov,
|
||||
0.1f, // near plane
|
||||
1000.0f // far plane
|
||||
);
|
||||
```
|
||||
|
||||
### Step 3: Project 3D Points
|
||||
|
||||
```cpp
|
||||
// 3D world position (e.g., enemy player position)
|
||||
Vector3<float> enemy_pos{150.0f, 200.0f, 75.0f};
|
||||
|
||||
// Project to screen
|
||||
if (auto screen = camera.world_to_screen(enemy_pos)) {
|
||||
std::cout << "Enemy on screen at: "
|
||||
<< screen->x << ", " << screen->y << "\n";
|
||||
|
||||
// Draw ESP box or marker at screen->x, screen->y
|
||||
// Note: screen coordinates are in viewport space (0-width, 0-height)
|
||||
} else {
|
||||
// Enemy is not visible (behind camera or outside frustum)
|
||||
std::cout << "Enemy not visible\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Update Camera for Each Frame
|
||||
|
||||
```cpp
|
||||
void render_frame() {
|
||||
// Read current camera data from game
|
||||
Vector3<float> new_pos = read_camera_position();
|
||||
ViewAngles new_angles = read_camera_angles();
|
||||
|
||||
// Update camera
|
||||
camera.update(new_pos, new_angles);
|
||||
|
||||
// Project all entities
|
||||
for (const auto& entity : entities) {
|
||||
if (auto screen = camera.world_to_screen(entity.position)) {
|
||||
draw_esp_box(screen->x, screen->y);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key takeaways:**
|
||||
- Choose the engine trait that matches your target game
|
||||
- `world_to_screen()` returns `std::optional` - always check the result
|
||||
- Update camera each frame for accurate projections
|
||||
- Screen coordinates are in the viewport space you defined
|
||||
|
||||
---
|
||||
|
||||
## Tutorial 3: Projectile Prediction (Aim-Bot)
|
||||
|
||||
Calculate where to aim to hit a moving target.
|
||||
|
||||
### Step 1: Define Projectile Properties
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
#include <omath/projectile_prediction/proj_pred_engine_legacy.hpp>
|
||||
|
||||
using namespace omath;
|
||||
using namespace omath::projectile_prediction;
|
||||
|
||||
// Define your weapon's projectile
|
||||
Projectile bullet;
|
||||
bullet.origin = Vector3<float>{0, 0, 0}; // Shooter position
|
||||
bullet.speed = 800.0f; // Muzzle velocity (m/s or game units/s)
|
||||
bullet.gravity = Vector3<float>{0, 0, -9.81f}; // Gravity vector
|
||||
```
|
||||
|
||||
### Step 2: Define Target State
|
||||
|
||||
```cpp
|
||||
// Target information (enemy player)
|
||||
Target enemy;
|
||||
enemy.position = Vector3<float>{100, 200, 50}; // Current position
|
||||
enemy.velocity = Vector3<float>{10, 5, 0}; // Current velocity
|
||||
```
|
||||
|
||||
### Step 3: Calculate Aim Point
|
||||
|
||||
```cpp
|
||||
// Create prediction engine
|
||||
// Use AVX2 version if available for better performance:
|
||||
// ProjPredEngineAVX2 engine;
|
||||
ProjPredEngineLegacy engine;
|
||||
|
||||
// Calculate where to aim
|
||||
if (auto aim_point = engine.maybe_calculate_aim_point(bullet, enemy)) {
|
||||
std::cout << "Aim at: "
|
||||
<< aim_point->x << ", "
|
||||
<< aim_point->y << ", "
|
||||
<< aim_point->z << "\n";
|
||||
|
||||
// Calculate angles to aim_point
|
||||
Vector3<float> aim_direction = (*aim_point - bullet.origin).normalized();
|
||||
|
||||
// Convert to view angles (engine-specific)
|
||||
// ViewAngles angles = calculate_angles_to_direction(aim_direction);
|
||||
// set_aim_angles(angles);
|
||||
} else {
|
||||
// Cannot hit target (too fast, out of range, etc.)
|
||||
std::cout << "Target cannot be hit\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Handle Different Scenarios
|
||||
|
||||
```cpp
|
||||
// Stationary target
|
||||
Target stationary;
|
||||
stationary.position = Vector3<float>{100, 100, 100};
|
||||
stationary.velocity = Vector3<float>{0, 0, 0};
|
||||
// aim_point will equal position for stationary targets
|
||||
|
||||
// Fast-moving target
|
||||
Target fast;
|
||||
fast.position = Vector3<float>{100, 100, 100};
|
||||
fast.velocity = Vector3<float>{50, 0, 0}; // Moving very fast
|
||||
// May return nullopt if target is too fast
|
||||
|
||||
// Target at different heights
|
||||
Target aerial;
|
||||
aerial.position = Vector3<float>{100, 100, 200}; // High up
|
||||
aerial.velocity = Vector3<float>{5, 5, -10}; // Falling
|
||||
// Gravity will be factored into the calculation
|
||||
```
|
||||
|
||||
### Step 5: Performance Optimization
|
||||
|
||||
```cpp
|
||||
// For better performance on modern CPUs, use AVX2:
|
||||
#include <omath/projectile_prediction/proj_pred_engine_avx2.hpp>
|
||||
|
||||
ProjPredEngineAVX2 fast_engine; // 2-4x faster than legacy
|
||||
|
||||
// Use the same way as legacy engine
|
||||
if (auto aim = fast_engine.maybe_calculate_aim_point(bullet, enemy)) {
|
||||
// Process aim point
|
||||
}
|
||||
```
|
||||
|
||||
**Key takeaways:**
|
||||
- Always check if aim point exists before using
|
||||
- Velocity must be in same units as position/speed
|
||||
- Gravity vector points down (typically negative Z or Y depending on engine)
|
||||
- Use AVX2 engine when possible for better performance
|
||||
- Returns `nullopt` when target is unreachable
|
||||
|
||||
---
|
||||
|
||||
## Tutorial 4: Collision Detection
|
||||
|
||||
Perform ray-casting and intersection tests.
|
||||
|
||||
### Step 1: Ray-Plane Intersection
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
// Define a ground plane (Z=0, normal pointing up)
|
||||
Plane ground{
|
||||
Vector3<float>{0, 0, 0}, // Point on plane
|
||||
Vector3<float>{0, 0, 1} // Normal vector (Z-up)
|
||||
};
|
||||
|
||||
// Define a ray (e.g., looking downward from above)
|
||||
Vector3<float> ray_origin{10, 20, 100};
|
||||
Vector3<float> ray_direction{0, 0, -1}; // Pointing down
|
||||
|
||||
// Test intersection
|
||||
if (auto hit = ground.intersects_ray(ray_origin, ray_direction)) {
|
||||
std::cout << "Hit ground at: "
|
||||
<< hit->x << ", " << hit->y << ", " << hit->z << "\n";
|
||||
// Expected: (10, 20, 0)
|
||||
} else {
|
||||
std::cout << "Ray does not intersect plane\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Distance to Plane
|
||||
|
||||
```cpp
|
||||
// Calculate signed distance from point to plane
|
||||
Vector3<float> point{10, 20, 50};
|
||||
float distance = ground.distance_to_point(point);
|
||||
|
||||
std::cout << "Distance to ground: " << distance << "\n";
|
||||
// Expected: 50.0 (50 units above ground)
|
||||
|
||||
// Negative distance means point is below the plane
|
||||
Vector3<float> below{10, 20, -5};
|
||||
float dist_below = ground.distance_to_point(below);
|
||||
// Expected: -5.0
|
||||
```
|
||||
|
||||
### Step 3: Axis-Aligned Bounding Box
|
||||
|
||||
```cpp
|
||||
#include <omath/3d_primitives/box.hpp>
|
||||
|
||||
// Create a bounding box
|
||||
Box bbox{
|
||||
Vector3<float>{0, 0, 0}, // Min corner
|
||||
Vector3<float>{100, 100, 100} // Max corner
|
||||
};
|
||||
|
||||
// Test if point is inside
|
||||
Vector3<float> inside{50, 50, 50};
|
||||
if (bbox.contains(inside)) {
|
||||
std::cout << "Point is inside box\n";
|
||||
}
|
||||
|
||||
Vector3<float> outside{150, 50, 50};
|
||||
if (!bbox.contains(outside)) {
|
||||
std::cout << "Point is outside box\n";
|
||||
}
|
||||
|
||||
// Box-box intersection
|
||||
Box other{
|
||||
Vector3<float>{50, 50, 50},
|
||||
Vector3<float>{150, 150, 150}
|
||||
};
|
||||
|
||||
if (bbox.intersects(other)) {
|
||||
std::cout << "Boxes overlap\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Line Tracing
|
||||
|
||||
```cpp
|
||||
#include <omath/collision/line_tracer.hpp>
|
||||
|
||||
using namespace omath::collision;
|
||||
|
||||
// Ray-triangle intersection
|
||||
Vector3<float> v0{0, 0, 0};
|
||||
Vector3<float> v1{100, 0, 0};
|
||||
Vector3<float> v2{0, 100, 0};
|
||||
|
||||
Vector3<float> ray_start{25, 25, 100};
|
||||
Vector3<float> ray_dir{0, 0, -1};
|
||||
|
||||
LineTracer tracer;
|
||||
if (auto hit = tracer.ray_triangle_intersect(ray_start, ray_dir, v0, v1, v2)) {
|
||||
std::cout << "Hit triangle at: "
|
||||
<< hit->point.x << ", "
|
||||
<< hit->point.y << ", "
|
||||
<< hit->point.z << "\n";
|
||||
std::cout << "Hit distance: " << hit->distance << "\n";
|
||||
std::cout << "Surface normal: "
|
||||
<< hit->normal.x << ", "
|
||||
<< hit->normal.y << ", "
|
||||
<< hit->normal.z << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
**Key takeaways:**
|
||||
- Plane normals should be unit vectors
|
||||
- Ray direction should typically be normalized
|
||||
- Signed distance indicates which side of plane a point is on
|
||||
- AABB tests are very fast for broad-phase collision detection
|
||||
- Line tracer provides hit point, distance, and surface normal
|
||||
|
||||
---
|
||||
|
||||
## Tutorial 5: Pattern Scanning
|
||||
|
||||
Search for byte patterns in memory.
|
||||
|
||||
### Step 1: Basic Pattern Scanning
|
||||
|
||||
```cpp
|
||||
#include <omath/utility/pattern_scan.hpp>
|
||||
#include <vector>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
// Memory to search (e.g., from a loaded module)
|
||||
std::vector<uint8_t> memory = {
|
||||
0x48, 0x8B, 0x05, 0xAA, 0xBB, 0xCC, 0xDD,
|
||||
0x48, 0x85, 0xC0, 0x74, 0x10,
|
||||
// ... more bytes
|
||||
};
|
||||
|
||||
// Pattern with wildcards (?? = match any byte)
|
||||
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
|
||||
|
||||
// Scan for pattern
|
||||
if (auto result = pattern_scan(memory, pattern)) {
|
||||
std::cout << "Pattern found at offset: " << result->offset << "\n";
|
||||
|
||||
// Extract wildcard values if needed
|
||||
// result->wildcards contains the matched bytes at ?? positions
|
||||
} else {
|
||||
std::cout << "Pattern not found\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: PE File Scanning
|
||||
|
||||
```cpp
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
|
||||
// Scan a PE file (EXE or DLL)
|
||||
PEPatternScanner scanner("game.exe");
|
||||
|
||||
PatternView pattern{"E8 ?? ?? ?? ?? 85 C0 75 ??"};
|
||||
|
||||
if (auto rva = scanner.scan_pattern(pattern)) {
|
||||
std::cout << "Pattern found at RVA: 0x"
|
||||
<< std::hex << *rva << std::dec << "\n";
|
||||
|
||||
// Convert RVA to absolute address if needed
|
||||
uintptr_t base_address = get_module_base("game.exe");
|
||||
uintptr_t absolute = base_address + *rva;
|
||||
} else {
|
||||
std::cout << "Pattern not found in PE file\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Multiple Patterns
|
||||
|
||||
```cpp
|
||||
// Search for multiple patterns
|
||||
std::vector<PatternView> patterns{
|
||||
PatternView{"48 8B 05 ?? ?? ?? ??"},
|
||||
PatternView{"E8 ?? ?? ?? ?? 85 C0"},
|
||||
PatternView{"FF 15 ?? ?? ?? ?? 48 8B"}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < patterns.size(); ++i) {
|
||||
if (auto result = pattern_scan(memory, patterns[i])) {
|
||||
std::cout << "Pattern " << i << " found at: "
|
||||
<< result->offset << "\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Pattern with Masks
|
||||
|
||||
```cpp
|
||||
// Alternative: use mask-based patterns
|
||||
// Pattern: bytes to match
|
||||
std::vector<uint8_t> pattern_bytes{0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// Mask: 'x' = must match, '?' = wildcard
|
||||
std::string mask{"xxx????"};
|
||||
|
||||
// Custom scan function
|
||||
auto scan_with_mask = [&](const std::vector<uint8_t>& data) {
|
||||
for (size_t i = 0; i < data.size() - pattern_bytes.size(); ++i) {
|
||||
bool match = true;
|
||||
for (size_t j = 0; j < pattern_bytes.size(); ++j) {
|
||||
if (mask[j] == 'x' && data[i + j] != pattern_bytes[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return i;
|
||||
}
|
||||
return size_t(-1);
|
||||
};
|
||||
```
|
||||
|
||||
**Key takeaways:**
|
||||
- Use `??` in pattern strings for wildcards
|
||||
- PE scanner works with files and modules
|
||||
- Pattern scanning is useful for finding functions, vtables, or data
|
||||
- Always validate found addresses before use
|
||||
- Patterns may have multiple matches - consider context
|
||||
|
||||
---
|
||||
|
||||
## Tutorial 6: Angles and View Angles
|
||||
|
||||
Work with game camera angles properly.
|
||||
|
||||
### Step 1: Understanding Angle Types
|
||||
|
||||
```cpp
|
||||
#include <omath/omath.hpp>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
// Generic angle with custom range
|
||||
auto angle1 = Angle<float, 0.0f, 360.0f>::from_degrees(45.0f);
|
||||
auto angle2 = Angle<float, -180.0f, 180.0f>::from_degrees(270.0f);
|
||||
|
||||
// Specialized camera angles
|
||||
auto pitch = PitchAngle::from_degrees(-10.0f); // Looking down
|
||||
auto yaw = YawAngle::from_degrees(90.0f); // Looking right
|
||||
auto roll = RollAngle::from_degrees(0.0f); // No tilt
|
||||
```
|
||||
|
||||
### Step 2: Angle Conversions
|
||||
|
||||
```cpp
|
||||
// Create from degrees
|
||||
auto deg_angle = PitchAngle::from_degrees(45.0f);
|
||||
|
||||
// Get as radians
|
||||
float radians = deg_angle.as_radians();
|
||||
std::cout << "45° = " << radians << " radians\n";
|
||||
|
||||
// Get as degrees
|
||||
float degrees = deg_angle.as_degrees();
|
||||
std::cout << "Value: " << degrees << "°\n";
|
||||
```
|
||||
|
||||
### Step 3: View Angles (Camera)
|
||||
|
||||
```cpp
|
||||
// Pitch: vertical rotation (-89° to 89°)
|
||||
// Yaw: horizontal rotation (-180° to 180°)
|
||||
// Roll: camera tilt (-180° to 180°)
|
||||
|
||||
ViewAngles camera_angles{
|
||||
PitchAngle::from_degrees(-15.0f), // Looking slightly down
|
||||
YawAngle::from_degrees(45.0f), // Facing northeast
|
||||
RollAngle::from_degrees(0.0f) // No tilt
|
||||
};
|
||||
|
||||
// Access individual components
|
||||
float pitch_val = camera_angles.pitch.as_degrees();
|
||||
float yaw_val = camera_angles.yaw.as_degrees();
|
||||
float roll_val = camera_angles.roll.as_degrees();
|
||||
```
|
||||
|
||||
### Step 4: Calculating Look-At Angles
|
||||
|
||||
```cpp
|
||||
using namespace omath::source_engine; // Or your game's engine
|
||||
|
||||
Vector3<float> camera_pos{0, 0, 100};
|
||||
Vector3<float> target_pos{100, 100, 100};
|
||||
|
||||
// Calculate angles to look at target
|
||||
ViewAngles look_at = CameraTrait::calc_look_at_angle(camera_pos, target_pos);
|
||||
|
||||
std::cout << "Pitch: " << look_at.pitch.as_degrees() << "°\n";
|
||||
std::cout << "Yaw: " << look_at.yaw.as_degrees() << "°\n";
|
||||
std::cout << "Roll: " << look_at.roll.as_degrees() << "°\n";
|
||||
```
|
||||
|
||||
### Step 5: Angle Arithmetic
|
||||
|
||||
```cpp
|
||||
// Angles support arithmetic with automatic normalization
|
||||
auto angle1 = YawAngle::from_degrees(170.0f);
|
||||
auto angle2 = YawAngle::from_degrees(20.0f);
|
||||
|
||||
// Addition (wraps around)
|
||||
auto sum = angle1 + angle2; // 190° → normalized to -170°
|
||||
|
||||
// Subtraction
|
||||
auto diff = angle2 - angle1; // -150°
|
||||
|
||||
// Scaling
|
||||
auto scaled = angle1 * 2.0f;
|
||||
```
|
||||
|
||||
**Key takeaways:**
|
||||
- Use specialized angle types for camera angles (PitchAngle, YawAngle, RollAngle)
|
||||
- Angles automatically normalize to their valid ranges
|
||||
- Each game engine may have different angle conventions
|
||||
- Use engine traits to calculate look-at angles correctly
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you've completed these tutorials, explore:
|
||||
|
||||
- **[API Overview](api_overview.md)** - Complete API reference
|
||||
- **[Engine Documentation](engines/)** - Engine-specific features
|
||||
- **[Examples](../examples/)** - More code examples
|
||||
- **[Getting Started](getting_started.md)** - Quick start guide
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 1 Nov 2025*
|
||||
190
docs/utility/color.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# `omath::Color` — RGBA color with HSV helpers (C++20/23)
|
||||
|
||||
> Header: your project’s `color.hpp`
|
||||
> Namespace: `omath`
|
||||
> Inherits: `Vector4<float>` (`x=r`, `y=g`, `z=b`, `w=a`)
|
||||
> Depends on: `<cstdint>`, `Vector4`, optionally ImGui (`OMATH_IMGUI_INTEGRATION`)
|
||||
> Formatting: provides `std::formatter<omath::Color>`
|
||||
|
||||
`Color` is a tiny RGBA utility on top of `Vector4<float>`. It offers sRGB-style channel construction, HSV↔RGB conversion, in-place HSV setters, linear blending, and string/formatter helpers.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "color.hpp"
|
||||
using omath::Color;
|
||||
|
||||
// RGBA in [0,1] (r,g,b clamped to [0,1] on construction)
|
||||
Color c{0.2f, 0.4f, 0.8f, 0.5f};
|
||||
|
||||
// From 8-bit channels
|
||||
auto red = Color::from_rgba(255, 0, 0, 255);
|
||||
auto green = Color::from_rgba(0, 255, 0, 160);
|
||||
|
||||
// From HSV (h ∈ [0,1], s ∈ [0,1], v ∈ [0,1])
|
||||
auto cyan = Color::from_hsv(0.5f, 1.0f, 1.0f); // a = 1
|
||||
|
||||
// Read/modify via HSV
|
||||
auto hsv = cyan.to_hsv(); // hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1]
|
||||
cyan.set_value(0.6f); // converts back to RGB (alpha becomes 1)
|
||||
|
||||
// Blend linearly (lerp)
|
||||
auto mid = red.blend(green, 0.5f);
|
||||
|
||||
// Printable (0–255 per channel)
|
||||
std::string s = std::format("{}", mid); // "[r:128, g:128, b:0, a:207]" for example
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data model
|
||||
|
||||
* Inherits `Vector4<float>`:
|
||||
|
||||
* `x` = **red**, `y` = **green**, `z` = **blue**, `w` = **alpha**.
|
||||
* Construction clamps **RGB** to `[0,1]` (via `Vector4::clamp(0,1)`), **alpha is not clamped** by that call (see notes).
|
||||
|
||||
---
|
||||
|
||||
## Construction & factories
|
||||
|
||||
```cpp
|
||||
// RGBA in [0,1] (RGB clamped to [0,1]; alpha untouched by clamp)
|
||||
constexpr Color(float r, float g, float b, float a) noexcept;
|
||||
|
||||
// Default
|
||||
constexpr Color() noexcept;
|
||||
|
||||
// From 8-bit RGBA (0–255) → normalized to [0,1]
|
||||
constexpr static Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
|
||||
|
||||
// From HSV where hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1]
|
||||
struct Hsv { float hue{}, saturation{}, value{}; };
|
||||
|
||||
constexpr static Color from_hsv(float hue, float saturation, float value) noexcept;
|
||||
constexpr static Color from_hsv(const Hsv& hsv) noexcept; // delegates to the above
|
||||
|
||||
// Construct from a Vector4 (RGB clamped, alpha not clamped)
|
||||
constexpr explicit Color(const Vector4& vec) noexcept;
|
||||
```
|
||||
|
||||
**HSV details**
|
||||
|
||||
* `from_hsv(h, s, v)`: `h` is **normalized** (`[0,1]`); it is clamped, then mapped to the 6 hue sectors; **alpha = 1.0**.
|
||||
* `to_hsv()`: returns `Hsv{h,s,v}` with **`h ∈ [0,1]`** (internally computes degrees and divides by 360), `s,v ∈ [0,1]`.
|
||||
|
||||
---
|
||||
|
||||
## Mutators
|
||||
|
||||
```cpp
|
||||
constexpr void set_hue(float h) noexcept; // h ∈ [0,1] recommended
|
||||
constexpr void set_saturation(float s) noexcept; // s ∈ [0,1]
|
||||
constexpr void set_value(float v) noexcept; // v ∈ [0,1]
|
||||
|
||||
// Linear blend: (1-ratio)*this + ratio*other, ratio clamped to [0,1]
|
||||
constexpr Color blend(const Color& other, float ratio) const noexcept;
|
||||
```
|
||||
|
||||
> ⚠️ **Alpha reset on HSV setters:** each `set_*` converts HSV→RGB using `from_hsv(...)`, which **sets alpha to 1.0** (overwriting previous `w`). If you need to preserve alpha:
|
||||
>
|
||||
> ```cpp
|
||||
> float a = col.w;
|
||||
> col.set_value(0.5f);
|
||||
> col.w = a;
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```cpp
|
||||
static constexpr Color red(); // (1,0,0,1)
|
||||
static constexpr Color green(); // (0,1,0,1)
|
||||
static constexpr Color blue(); // (0,0,1,1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## String & formatting
|
||||
|
||||
```cpp
|
||||
// "[r:R, g:G, b:B, a:A]" with each channel shown as 0–255 integer
|
||||
std::string to_string() const noexcept;
|
||||
std::wstring to_wstring() const noexcept;
|
||||
std::u8string to_u8string() const noexcept;
|
||||
|
||||
// Formatter forwards to the above (char/wchar_t/char8_t)
|
||||
template<> struct std::formatter<omath::Color>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ImGui (optional)
|
||||
|
||||
```cpp
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
ImColor to_im_color() const noexcept; // constructs from Vector4's to_im_vec4()
|
||||
#endif
|
||||
```
|
||||
|
||||
Ensure `<imgui.h>` is included somewhere before this header when the macro is enabled.
|
||||
|
||||
---
|
||||
|
||||
## Notes & caveats
|
||||
|
||||
* **Alpha clamping:** `Vector4::clamp(min,max)` (called by `Color` ctors) clamps **x,y,z** only in the provided `Vector4` implementation; `w` is **left unchanged**. If you require strict `[0,1]` alpha, clamp it yourself:
|
||||
|
||||
```cpp
|
||||
col.w = std::clamp(col.w, 0.0f, 1.0f);
|
||||
```
|
||||
* **HSV range:** The API consistently uses **normalized hue** (`[0,1]`). Convert degrees ↔ normalized as `h_norm = h_deg / 360.f`.
|
||||
* **Blend space:** `blend` is a **linear** interpolation in RGBA; it is not perceptually uniform.
|
||||
|
||||
---
|
||||
|
||||
## API summary
|
||||
|
||||
```cpp
|
||||
struct Hsv { float hue{}, saturation{}, value{}; };
|
||||
|
||||
class Color final : public Vector4<float> {
|
||||
public:
|
||||
constexpr Color(float r, float g, float b, float a) noexcept;
|
||||
constexpr Color() noexcept;
|
||||
constexpr explicit Color(const Vector4& vec) noexcept;
|
||||
|
||||
static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
|
||||
static constexpr Color from_hsv(float hue, float saturation, float value) noexcept;
|
||||
static constexpr Color from_hsv(const Hsv& hsv) noexcept;
|
||||
|
||||
constexpr Hsv to_hsv() const noexcept;
|
||||
|
||||
constexpr void set_hue(float h) noexcept;
|
||||
constexpr void set_saturation(float s) noexcept;
|
||||
constexpr void set_value(float v) noexcept;
|
||||
|
||||
constexpr Color blend(const Color& other, float ratio) const noexcept;
|
||||
|
||||
static constexpr Color red();
|
||||
static constexpr Color green();
|
||||
static constexpr Color blue();
|
||||
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
ImColor to_im_color() const noexcept;
|
||||
#endif
|
||||
|
||||
std::string to_string() const noexcept;
|
||||
std::wstring to_wstring() const noexcept;
|
||||
std::u8string to_u8string() const noexcept;
|
||||
};
|
||||
|
||||
// formatter<omath::Color> provided
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
194
docs/utility/pattern_scan.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# `omath::PatternScanner` — Fast byte-pattern search with wildcards
|
||||
|
||||
> Header: your project’s `pattern_scanner.hpp`
|
||||
> Namespace: `omath`
|
||||
> Core API: `scan_for_pattern(...)` (span or iterators)
|
||||
> Errors: `PatternScanError::INVALID_PATTERN_STRING` (from `parse_pattern`)
|
||||
> C++: uses `std::span`, `std::expected`, `std::byte`
|
||||
|
||||
`PatternScanner` scans a contiguous byte range for a **hex pattern** that may include **wildcards**. It returns an iterator to the **first match** or the end iterator if not found (or if the pattern string is invalid).
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "pattern_scanner.hpp"
|
||||
using omath::PatternScanner;
|
||||
|
||||
std::vector<std::byte> buf = /* ... bytes ... */;
|
||||
std::span<std::byte> s{buf.data(), buf.size()};
|
||||
|
||||
// Example pattern: "48 8B ?? ?? 89" (hex bytes with '?' as any byte)
|
||||
auto it = PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89");
|
||||
if (it != s.end()) {
|
||||
// Found at offset:
|
||||
auto offset = static_cast<std::size_t>(it - s.begin());
|
||||
}
|
||||
```
|
||||
|
||||
Or with iterators:
|
||||
|
||||
```cpp
|
||||
auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "DE AD BE EF");
|
||||
if (it != buf.end()) { /* ... */ }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pattern string grammar
|
||||
|
||||
The pattern string is parsed into a sequence of byte **tokens**:
|
||||
|
||||
* **Hex byte**: two hexadecimal digits form one byte.
|
||||
Examples: `90`, `4F`, `00`, `ff`
|
||||
* **Wildcard byte**: `?` or `??` matches **any single byte**.
|
||||
* **Separators**: any ASCII whitespace (space, tab, newline) is **ignored** and may be used to group tokens.
|
||||
|
||||
> ✔️ Valid: `"48 8B ?? 05 00"`, `"90 90 90"`, `"??"`
|
||||
> ❌ Invalid: odd number of hex digits in a token, non-hex characters (besides `?` and whitespace)
|
||||
|
||||
If the string cannot be parsed into a clean sequence of tokens, `parse_pattern()` returns `std::unexpected(PatternScanError::INVALID_PATTERN_STRING)`, and the public scan function returns **end**.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath {
|
||||
|
||||
enum class PatternScanError { INVALID_PATTERN_STRING };
|
||||
|
||||
class PatternScanner final {
|
||||
public:
|
||||
// Contiguous range (span) overload
|
||||
[[nodiscard]]
|
||||
static std::span<std::byte>::iterator
|
||||
scan_for_pattern(const std::span<std::byte>& range,
|
||||
const std::string_view& pattern);
|
||||
|
||||
// Deleted rvalue-span overload (prevents dangling)
|
||||
static std::span<std::byte>::iterator
|
||||
scan_for_pattern(std::span<std::byte>&&,
|
||||
const std::string_view&) = delete;
|
||||
|
||||
// Iterator overload
|
||||
template<class IteratorType>
|
||||
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
|
||||
static IteratorType
|
||||
scan_for_pattern(const IteratorType& begin,
|
||||
const IteratorType& end,
|
||||
const std::string_view& pattern);
|
||||
private:
|
||||
[[nodiscard]]
|
||||
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
|
||||
parse_pattern(const std::string_view& pattern_string);
|
||||
};
|
||||
|
||||
} // namespace omath
|
||||
```
|
||||
|
||||
### Return value
|
||||
|
||||
* On success: iterator to the **first** matching position.
|
||||
* On failure / not found / invalid pattern: **`end`**.
|
||||
|
||||
---
|
||||
|
||||
## Complexity
|
||||
|
||||
Let `N` be the number of bytes in the range and `M` the number of **pattern tokens**.
|
||||
|
||||
* Time: **O(N × M)** (simple sliding window with early break).
|
||||
* Space: **O(M)** for the parsed pattern vector.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Find a function prologue
|
||||
|
||||
```cpp
|
||||
// x86-64: push rbp; mov rbp, rsp
|
||||
auto it = PatternScanner::scan_for_pattern(s, "55 48 89 E5");
|
||||
if (it != s.end()) {
|
||||
// ... process
|
||||
}
|
||||
```
|
||||
|
||||
### Skip variable bytes with wildcards
|
||||
|
||||
```cpp
|
||||
// mov rax, <imm32>; call <rel32>
|
||||
auto it = PatternScanner::scan_for_pattern(s, "48 B8 ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ??");
|
||||
```
|
||||
|
||||
### Iterator-based scan (subrange)
|
||||
|
||||
```cpp
|
||||
auto sub_begin = buf.begin() + 1024;
|
||||
auto sub_end = buf.begin() + 4096;
|
||||
auto it = PatternScanner::scan_for_pattern(sub_begin, sub_end, "DE AD ?? BE EF");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Behavior & edge cases
|
||||
|
||||
* **Empty or too-short**: If the effective pattern token count `M` is `0` or `M > N`, the function returns `end`.
|
||||
* **Wildcards**: Each `?`/`??` token matches **exactly one** byte.
|
||||
* **No exceptions**: Invalid pattern → parsed as `unexpected`, public API returns `end`.
|
||||
* **No ownership**: The span overload takes `const std::span<std::byte>&` and returns a **mutable iterator** into that span. You must ensure the underlying memory stays alive. The rvalue-span overload is **deleted** to prevent dangling.
|
||||
|
||||
---
|
||||
|
||||
## Notes & caveats (implementation-sensitive)
|
||||
|
||||
* Although the iterator overload is constrained with `std::input_or_output_iterator`, the implementation uses `*(begin + i + j)` and arithmetic on iterators. In practice this means you should pass **random-access / contiguous iterators** (e.g., from `std::vector<std::byte>` or `std::span<std::byte>`). Using non-random-access iterators would be ill-formed.
|
||||
* The inner matching loop compares a parsed token (`std::optional<std::byte>`) to the candidate byte; `std::nullopt` is treated as a **wildcard** (always matches).
|
||||
* **Implementation note:** the outer scan currently derives its scan bound from the **pattern string length**; conceptually it should use the **number of parsed tokens** (hex/wildcard bytes). If you plan to accept spaces (or other non-byte characters) in the pattern string, ensure the scan window uses the parsed token count to avoid false negatives near the end of the buffer.
|
||||
|
||||
---
|
||||
|
||||
## Testing hooks
|
||||
|
||||
The class befriends several unit tests:
|
||||
|
||||
```
|
||||
unit_test_pattern_scan_read_test_Test
|
||||
unit_test_pattern_scan_corner_case_1_Test
|
||||
unit_test_pattern_scan_corner_case_2_Test
|
||||
unit_test_pattern_scan_corner_case_3_Test
|
||||
unit_test_pattern_scan_corner_case_4_Test
|
||||
```
|
||||
|
||||
Use these to validate parsing correctness, wildcard handling, and boundary conditions.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* **Always returns end**: verify the pattern string is valid hex/wildcards and that you’re scanning the intended subrange. Try a simpler pattern (e.g., a single known byte) to sanity-check.
|
||||
* **Crashes or compile errors with iterator overload**: use iterators that support random access (e.g., from `std::vector`), or prefer the `std::span` overload.
|
||||
* **Ambiguous wildcards**: this scanner treats `?` and `??` as **byte-wide** wildcards (not per-nibble). If you need nibble-level masks, extend `parse_pattern` to support patterns like `A?`/`?F` with bitmask matching.
|
||||
|
||||
---
|
||||
|
||||
## Minimal unit test sketch
|
||||
|
||||
```cpp
|
||||
TEST(pattern, basic) {
|
||||
std::array<std::byte, 8> data{
|
||||
std::byte{0x48}, std::byte{0x8B}, std::byte{0x01}, std::byte{0x02},
|
||||
std::byte{0x89}, std::byte{0x50}, std::byte{0x90}, std::byte{0xCC}
|
||||
};
|
||||
std::span<std::byte> s{data.data(), data.size()};
|
||||
auto it = omath::PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89");
|
||||
ASSERT_NE(it, s.end());
|
||||
EXPECT_EQ(static_cast<size_t>(it - s.begin()), 0U);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
155
docs/utility/pe_pattern_scan.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# `omath::PePatternScanner` — Scan PE images for byte patterns
|
||||
|
||||
> Header: your project’s `pe_pattern_scanner.hpp`
|
||||
> Namespace: `omath`
|
||||
> Platform: **Windows / PE (Portable Executable) images**
|
||||
> Depends on: `<filesystem>`, `<optional>`, `<cstdint>`
|
||||
> Companion: works well with `omath::PatternScanner` (same pattern grammar)
|
||||
|
||||
`PePatternScanner` searches **Portable Executable (PE)** binaries for a hex pattern (with wildcards). You can scan:
|
||||
|
||||
* a **loaded module** in the current process, or
|
||||
* a **PE file on disk** (by section name; defaults to **`.text`**).
|
||||
|
||||
---
|
||||
|
||||
## Pattern string grammar (same as `PatternScanner`)
|
||||
|
||||
* **Hex byte**: two hex digits → one byte (`90`, `4F`, `00`, `ff`).
|
||||
* **Wildcard byte**: `?` or `??` matches **any byte**.
|
||||
* **Whitespace**: ignored (use to group tokens).
|
||||
|
||||
✔️ `"48 8B ?? ?? 89"`, `"55 8B EC"`, `"??"`
|
||||
❌ odd digit counts, non-hex characters (besides `?` and whitespace)
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath {
|
||||
|
||||
struct PeSectionScanResult {
|
||||
std::uint64_t virtual_base_addr; // RVA base of the scanned section (ImageBase + SectionRVA)
|
||||
std::uint64_t raw_base_addr; // file offset (start of section in the file)
|
||||
std::ptrdiff_t target_offset; // offset from section base to the first matched byte
|
||||
};
|
||||
|
||||
class PePatternScanner final {
|
||||
public:
|
||||
// Scan a module already loaded in *this* process.
|
||||
// module_base_address: HMODULE / ImageBase (e.g., from GetModuleHandle)
|
||||
// Returns absolute address (process VA) of the first match, or nullopt.
|
||||
static std::optional<std::uintptr_t>
|
||||
scan_for_pattern_in_loaded_module(const void* module_base_address,
|
||||
const std::string_view& pattern);
|
||||
|
||||
// Scan a PE file on disk, by section name (default ".text").
|
||||
// Returns section bases (virtual + raw) and match offset within the section, or nullopt.
|
||||
static std::optional<PeSectionScanResult>
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Return values
|
||||
|
||||
* **Loaded module**: `std::optional<std::uintptr_t>`
|
||||
|
||||
* `value()` = **process virtual address** (ImageBase + SectionRVA + match offset).
|
||||
* `nullopt` = no match or parse/PE error.
|
||||
|
||||
* **File scan**: `std::optional<PeSectionScanResult>`
|
||||
|
||||
* `virtual_base_addr` = **ImageBase + SectionRVA** of the scanned section (as if mapped).
|
||||
* `raw_base_addr` = **file offset** of section start.
|
||||
* `target_offset` = offset from the section base to the **first matched byte**.
|
||||
* To get addresses:
|
||||
|
||||
* **Virtual (RVA)** of hit = `virtual_base_addr + target_offset`
|
||||
* **Raw file offset** of hit = `raw_base_addr + target_offset`
|
||||
|
||||
---
|
||||
|
||||
## Usage examples
|
||||
|
||||
### Scan a loaded module (current process)
|
||||
|
||||
```cpp
|
||||
#include <windows.h>
|
||||
#include "pe_pattern_scanner.hpp"
|
||||
|
||||
using omath::PePatternScanner;
|
||||
|
||||
auto hMod = ::GetModuleHandleW(L"kernel32.dll");
|
||||
if (hMod) {
|
||||
auto addr = PePatternScanner::scan_for_pattern_in_loaded_module(
|
||||
hMod, "48 8B ?? ?? 89" // hex + wildcards
|
||||
);
|
||||
if (addr) {
|
||||
// Use the absolute process VA:
|
||||
std::uintptr_t hit_va = *addr;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scan a PE file on disk (default section “.text”)
|
||||
|
||||
```cpp
|
||||
#include "pe_pattern_scanner.hpp"
|
||||
using omath::PePatternScanner;
|
||||
|
||||
auto res = PePatternScanner::scan_for_pattern_in_file(
|
||||
R"(C:\Windows\System32\kernel32.dll)", "55 8B EC"
|
||||
);
|
||||
if (res) {
|
||||
auto rva_hit = res->virtual_base_addr + res->target_offset;
|
||||
auto raw_hit = res->raw_base_addr + res->target_offset;
|
||||
// rva_hit: where it would be in memory; raw_hit: file offset
|
||||
}
|
||||
```
|
||||
|
||||
### Scan another section (e.g., “.rdata”)
|
||||
|
||||
```cpp
|
||||
auto res = PePatternScanner::scan_for_pattern_in_file(
|
||||
"foo.dll", "48 8D 0D ?? ?? ?? ??", ".rdata"
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & edge cases
|
||||
|
||||
* **PE only**: these functions assume a valid **PE/COFF** layout. Non-PE files or corrupted headers yield `nullopt`.
|
||||
* **Section name**: `scan_for_pattern_in_file` defaults to **`.text`**; pass a different name to target other sections.
|
||||
* **Alignment & RVAs**: `virtual_base_addr` is computed from section headers (RVA aligned per section alignment). The returned “virtual” base is suitable for RVA math; the **process VA** returned by the “loaded module” API already includes the image base.
|
||||
* **Architecture**: works for 32-bit and 64-bit PEs; `std::uintptr_t` size matches the build architecture.
|
||||
* **Performance**: Pattern matching is **O(N × M)** (sliding window with wildcards). For large images, prefer scanning only necessary sections.
|
||||
* **Wildcards**: Each `?` matches **one byte** (no nibble masks). If you need `A?`-style nibble wildcards, extend the parser (see `PatternScanner`).
|
||||
* **Safety**: For loaded modules, you must have access to the memory; scanning read-only sections is fine, but never write. For file scans, ensure the file path is accessible.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* **`nullopt`**: Verify the pattern (valid tokens), correct **section**, and that you’re scanning the intended module/file (check bitness and version).
|
||||
* **“Loaded module” address math**: If you need an **offset from the module base**, compute `offset = hit_va - reinterpret_cast<std::uintptr_t>(module_base_address)`.
|
||||
* **Multiple matches**: Only the **first** match is returned. If you need all matches, extend the implementation to continue scanning from `target_offset + 1`.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath::PatternScanner` — raw buffer/iterator scanning with the same pattern grammar.
|
||||
* `omath::Triangle`, `omath::Vector3` — math types used elsewhere in the library.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
@@ -1,4 +1,9 @@
|
||||
project(examples)
|
||||
|
||||
add_executable(ExampleProjectionMatrixBuilder example_proj_mat_builder.cpp)
|
||||
target_link_libraries(ExampleProjectionMatrixBuilder PRIVATE omath::omath)
|
||||
add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp)
|
||||
set_target_properties(example_projection_matrix_builder PROPERTIES CXX_STANDARD 26)
|
||||
target_link_libraries(example_projection_matrix_builder PRIVATE omath::omath)
|
||||
|
||||
add_executable(example_signature_scan example_signature_scan.cpp)
|
||||
set_target_properties(example_signature_scan PROPERTIES CXX_STANDARD 26)
|
||||
target_link_libraries(example_signature_scan PRIVATE omath::omath)
|
||||
39
examples/example_signature_scan.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Created by Vlad on 11/8/2025.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
#include <print>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::println("OMATH Signature Scanner");
|
||||
|
||||
std::print("Enter path to PE file: ");
|
||||
std::string file_path;
|
||||
std::getline(std::cin, file_path); // allows spaces
|
||||
|
||||
std::print("Enter target section: ");
|
||||
std::string section;
|
||||
std::getline(std::cin, section);
|
||||
|
||||
std::print("Enter signature: ");
|
||||
std::string signature;
|
||||
std::getline(std::cin, signature);
|
||||
|
||||
std::println("[LOG] Performing scan....");
|
||||
|
||||
const auto result = omath::PePatternScanner::scan_for_pattern_in_file(file_path, signature, section);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
std::println("[ERROR] Scan failed or signature was not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::println("Found at virtual 0x{:x} , raw 0x{:x}", result->virtual_base_addr + result->target_offset,
|
||||
result->raw_base_addr + result->target_offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
add_subdirectory(googletest)
|
||||
@@ -3,8 +3,8 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/triangle.hpp"
|
||||
#include <array>
|
||||
|
||||
namespace omath::primitives
|
||||
|
||||
102
include/omath/3d_primitives/mesh.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include <omath/linear_algebra/mat.hpp>
|
||||
#include <omath/linear_algebra/vector3.hpp>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace omath::primitives
|
||||
{
|
||||
template<class Mat4X4, class RotationAngles, class MeshTypeTrait, class Type = float>
|
||||
class Mesh final
|
||||
{
|
||||
public:
|
||||
using NumericType = Type;
|
||||
|
||||
private:
|
||||
using Vbo = std::vector<Vector3<NumericType>>;
|
||||
using Vao = std::vector<Vector3<std::size_t>>;
|
||||
|
||||
public:
|
||||
Vbo m_vertex_buffer;
|
||||
Vao m_vertex_array_object;
|
||||
|
||||
Mesh(Vbo vbo, Vao vao, const Vector3<NumericType> scale = {1, 1, 1,})
|
||||
: m_vertex_buffer(std::move(vbo)), m_vertex_array_object(std::move(vao)), m_scale(scale)
|
||||
{
|
||||
}
|
||||
void set_origin(const Vector3<NumericType>& new_origin)
|
||||
{
|
||||
m_origin = new_origin;
|
||||
m_to_world_matrix = std::nullopt;
|
||||
}
|
||||
|
||||
void set_scale(const Vector3<NumericType>& new_scale)
|
||||
{
|
||||
m_scale = new_scale;
|
||||
m_to_world_matrix = std::nullopt;
|
||||
}
|
||||
|
||||
void set_rotation(const RotationAngles& new_rotation_angles)
|
||||
{
|
||||
m_rotation_angles = new_rotation_angles;
|
||||
m_to_world_matrix = std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const Vector3<NumericType>& get_origin() const
|
||||
{
|
||||
return m_origin;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const Vector3<NumericType>& get_scale() const
|
||||
{
|
||||
return m_scale;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const RotationAngles& get_rotation_angles() const
|
||||
{
|
||||
return m_rotation_angles;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const Mat4X4& get_to_world_matrix() const
|
||||
{
|
||||
if (m_to_world_matrix)
|
||||
return m_to_world_matrix.value();
|
||||
m_to_world_matrix =
|
||||
mat_translation(m_origin) * mat_scale(m_scale) * MeshTypeTrait::rotation_matrix(m_rotation_angles);
|
||||
|
||||
return m_to_world_matrix.value();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<float> vertex_to_world_space(const Vector3<float>& vertex) const
|
||||
{
|
||||
auto abs_vec = get_to_world_matrix() * mat_column_from_vector(vertex);
|
||||
|
||||
return {abs_vec.at(0, 0), abs_vec.at(1, 0), abs_vec.at(2, 0)};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Triangle<Vector3<float>> make_face_in_world_space(const Vao::const_iterator vao_iterator) const
|
||||
{
|
||||
return {vertex_to_world_space(m_vertex_buffer.at(vao_iterator->x)),
|
||||
vertex_to_world_space(m_vertex_buffer.at(vao_iterator->y)),
|
||||
vertex_to_world_space(m_vertex_buffer.at(vao_iterator->z))};
|
||||
}
|
||||
|
||||
private:
|
||||
Vector3<NumericType> m_origin;
|
||||
Vector3<NumericType> m_scale;
|
||||
|
||||
RotationAngles m_rotation_angles;
|
||||
|
||||
mutable std::optional<Mat4X4> m_to_world_matrix;
|
||||
};
|
||||
} // namespace omath::primitives
|
||||
@@ -3,8 +3,8 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/triangle.hpp"
|
||||
#include <array>
|
||||
|
||||
namespace omath::primitives
|
||||
|
||||
49
include/omath/collision/gjk_algorithm.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Created by Vlad on 11/9/2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "mesh_collider.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "simplex.hpp"
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
template<class ColliderType>
|
||||
class GjkAlgorithm final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static ColliderType::VertexType find_support_vertex(const ColliderType& collider_a,
|
||||
const ColliderType& collider_b,
|
||||
const ColliderType::VertexType& direction)
|
||||
{
|
||||
return collider_a.find_abs_furthest_vertex(direction) - collider_b.find_abs_furthest_vertex(-direction);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static bool is_collide(const ColliderType& collider_a, const ColliderType& collider_b)
|
||||
{
|
||||
// Get initial support point in any direction
|
||||
auto support = find_support_vertex(collider_a, collider_b, {1, 0, 0});
|
||||
|
||||
Simplex<typename ColliderType::VertexType> simplex;
|
||||
simplex.push_front(support);
|
||||
|
||||
auto direction = -support;
|
||||
|
||||
while (true)
|
||||
{
|
||||
support = find_support_vertex(collider_a, collider_b, direction);
|
||||
|
||||
if (support.dot(direction) <= 0.f)
|
||||
return false;
|
||||
|
||||
simplex.push_front(support);
|
||||
|
||||
if (simplex.handle(direction))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace omath::collision
|
||||
@@ -3,8 +3,8 @@
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include "omath/triangle.hpp"
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
|
||||
37
include/omath/collision/mesh_collider.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by Vlad on 11/9/2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
template<class MeshType>
|
||||
class MeshCollider
|
||||
{
|
||||
public:
|
||||
using NumericType = typename MeshType::NumericType;
|
||||
|
||||
using VertexType = Vector3<NumericType>;
|
||||
explicit MeshCollider(MeshType mesh): m_mesh(std::move(mesh))
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const Vector3<float>& find_furthest_vertex(const Vector3<float>& direction) const
|
||||
{
|
||||
return *std::ranges::max_element(m_mesh.m_vertex_buffer, [&direction](const auto& first, const auto& second)
|
||||
{ return first.dot(direction) < second.dot(direction); });
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<float> find_abs_furthest_vertex(const Vector3<float>& direction) const
|
||||
{
|
||||
return m_mesh.vertex_to_world_space(find_furthest_vertex(direction));
|
||||
}
|
||||
|
||||
private:
|
||||
MeshType m_mesh;
|
||||
};
|
||||
} // namespace omath::collision
|
||||
257
include/omath/collision/simplex.hpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
namespace omath::collision
|
||||
{
|
||||
|
||||
// Minimal structural contract for the vector type used by GJK.
|
||||
template<class V>
|
||||
concept GjkVector = requires(const V& a, const V& b) {
|
||||
{ -a } -> std::same_as<V>;
|
||||
{ a - b } -> std::same_as<V>;
|
||||
{ a.cross(b) } -> std::same_as<V>;
|
||||
{ a.point_to_same_direction(b) } -> std::same_as<bool>;
|
||||
};
|
||||
|
||||
template<GjkVector VectorType = Vector3<float>>
|
||||
class Simplex final
|
||||
{
|
||||
std::array<VectorType, 4> m_points{};
|
||||
std::size_t m_size{0};
|
||||
|
||||
public:
|
||||
static constexpr std::size_t capacity = 4;
|
||||
|
||||
constexpr Simplex() = default;
|
||||
|
||||
constexpr Simplex& operator=(std::initializer_list<VectorType> list) noexcept
|
||||
{
|
||||
assert(list.size() <= capacity && "Simplex can have at most 4 points");
|
||||
m_size = 0;
|
||||
for (const auto& p : list)
|
||||
m_points[m_size++] = p;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr void push_front(const VectorType& p) noexcept
|
||||
{
|
||||
const std::size_t limit = (m_size < capacity) ? m_size : capacity - 1;
|
||||
for (std::size_t i = limit; i > 0; --i)
|
||||
m_points[i] = m_points[i - 1];
|
||||
m_points[0] = p;
|
||||
if (m_size < capacity)
|
||||
++m_size;
|
||||
}
|
||||
|
||||
constexpr const VectorType& operator[](std::size_t i) const noexcept
|
||||
{
|
||||
return m_points[i];
|
||||
}
|
||||
constexpr VectorType& operator[](std::size_t i) noexcept
|
||||
{
|
||||
return m_points[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::size_t size() const noexcept
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool empty() const noexcept
|
||||
{
|
||||
return m_size == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const VectorType& front() const noexcept
|
||||
{
|
||||
return m_points[0];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const VectorType& back() const noexcept
|
||||
{
|
||||
return m_points[m_size - 1];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const VectorType* data() const noexcept
|
||||
{
|
||||
return m_points.data();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto begin() const noexcept
|
||||
{
|
||||
return m_points.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto end() const noexcept
|
||||
{
|
||||
return m_points.begin() + m_size;
|
||||
}
|
||||
|
||||
constexpr void clear() noexcept
|
||||
{
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
// GJK step: updates simplex + next search direction.
|
||||
// Returns true iff the origin lies inside the tetrahedron.
|
||||
[[nodiscard]] constexpr bool handle(VectorType& direction) noexcept
|
||||
{
|
||||
switch (m_size)
|
||||
{
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
return handle_point(direction);
|
||||
case 2:
|
||||
return handle_line(direction);
|
||||
case 3:
|
||||
return handle_triangle(direction);
|
||||
case 4:
|
||||
return handle_tetrahedron(direction);
|
||||
default:
|
||||
std::unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] constexpr bool handle_point(VectorType& direction) noexcept
|
||||
{
|
||||
const auto& a = m_points[0];
|
||||
direction = -a;
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
static constexpr bool near_zero(const V& v, const float eps = 1e-7f)
|
||||
{
|
||||
return v.dot(v) <= eps * eps;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
static constexpr V any_perp(const V& v)
|
||||
{
|
||||
// try cross with axes until non-zero
|
||||
V d = v.cross(V{1, 0, 0});
|
||||
if (near_zero(d))
|
||||
d = v.cross(V{0, 1, 0});
|
||||
if (near_zero(d))
|
||||
d = v.cross(V{0, 0, 1});
|
||||
return d;
|
||||
}
|
||||
|
||||
constexpr bool handle_line(VectorType& direction)
|
||||
{
|
||||
const auto& a = m_points[0];
|
||||
const auto& b = m_points[1];
|
||||
|
||||
const auto ab = b - a;
|
||||
const auto ao = -a;
|
||||
|
||||
if (ab.point_to_same_direction(ao))
|
||||
{
|
||||
// ReSharper disable once CppTooWideScopeInitStatement
|
||||
auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos
|
||||
if (near_zero(n))
|
||||
{
|
||||
// collinear: origin lies on ray AB (often on segment), pick any perp to escape
|
||||
direction = any_perp(ab);
|
||||
}
|
||||
else
|
||||
{
|
||||
direction = n.cross(ab);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*this = {a};
|
||||
direction = ao;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool handle_triangle(VectorType& direction) noexcept
|
||||
{
|
||||
const auto& a = m_points[0];
|
||||
const auto& b = m_points[1];
|
||||
const auto& c = m_points[2];
|
||||
|
||||
const auto ab = b - a;
|
||||
const auto ac = c - a;
|
||||
const auto ao = -a;
|
||||
|
||||
const auto abc = ab.cross(ac);
|
||||
|
||||
// Region AC
|
||||
if (abc.cross(ac).point_to_same_direction(ao))
|
||||
{
|
||||
if (ac.point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, c};
|
||||
direction = ac.cross(ao).cross(ac);
|
||||
return false;
|
||||
}
|
||||
*this = {a, b};
|
||||
return handle_line(direction);
|
||||
}
|
||||
|
||||
// Region AB
|
||||
if (ab.cross(abc).point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, b};
|
||||
return handle_line(direction);
|
||||
}
|
||||
|
||||
// Above or below triangle
|
||||
if (abc.point_to_same_direction(ao))
|
||||
{
|
||||
direction = abc;
|
||||
}
|
||||
else
|
||||
{
|
||||
*this = {a, c, b}; // flip winding
|
||||
direction = -abc;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool handle_tetrahedron(VectorType& direction) noexcept
|
||||
{
|
||||
const auto& a = m_points[0];
|
||||
const auto& b = m_points[1];
|
||||
const auto& c = m_points[2];
|
||||
const auto& d = m_points[3];
|
||||
|
||||
const auto ab = b - a;
|
||||
const auto ac = c - a;
|
||||
const auto ad = d - a;
|
||||
const auto ao = -a;
|
||||
|
||||
const auto abc = ab.cross(ac);
|
||||
const auto acd = ac.cross(ad);
|
||||
const auto adb = ad.cross(ab);
|
||||
|
||||
if (abc.point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, b, c};
|
||||
return handle_triangle(direction);
|
||||
}
|
||||
if (acd.point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, c, d};
|
||||
return handle_triangle(direction);
|
||||
}
|
||||
if (adb.point_to_same_direction(ao))
|
||||
{
|
||||
*this = {a, d, b};
|
||||
return handle_triangle(direction);
|
||||
}
|
||||
// Origin inside tetrahedron
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace omath::collision
|
||||
13
include/omath/engines/frostbite_engine/camera.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by Vlad on 3/22/2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/engines/frostbite_engine/constants.hpp"
|
||||
#include "omath/projection/camera.hpp"
|
||||
#include "traits/camera_trait.hpp"
|
||||
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>;
|
||||
} // namespace omath::unity_engine
|
||||
25
include/omath/engines/frostbite_engine/constants.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by Vlad on 10/21/2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
#include <omath/trigonometry/angle.hpp>
|
||||
#include <omath/trigonometry/view_angles.hpp>
|
||||
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
constexpr Vector3<float> k_abs_up = {0, 1, 0};
|
||||
constexpr Vector3<float> k_abs_right = {1, 0, 0};
|
||||
constexpr Vector3<float> k_abs_forward = {0, 0, 1};
|
||||
|
||||
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
|
||||
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||
|
||||
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||
} // namespace omath::frostbite_engine
|
||||
26
include/omath/engines/frostbite_engine/formulas.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by Vlad on 3/22/2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/engines/frostbite_engine/constants.hpp"
|
||||
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
[[nodiscard]]
|
||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
|
||||
} // namespace omath::unity_engine
|
||||
12
include/omath/engines/frostbite_engine/mesh.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Created by Vladislav on 09.11.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include "constants.hpp"
|
||||
#include "omath/3d_primitives/mesh.hpp"
|
||||
#include "traits/mesh_trait.hpp"
|
||||
|
||||
namespace omath::frostbite_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
|
||||
}
|
||||