Compare commits

...

147 Commits

Author SHA1 Message Date
20930c629a added method to get camera matrix 2026-04-18 15:40:38 +03:00
0845a2e863 clarified interfaces 2026-04-18 12:54:37 +03:00
f3f454b02e Merge pull request #182 from orange-cpp/feature/camera_upgrade
Feature/camera upgrade
2026-04-15 18:59:18 +03:00
0419043720 Update include/omath/engines/frostbite_engine/camera.hpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-15 03:48:03 +03:00
79f64d9679 fixed unreal bug, improved interface 2026-04-15 03:38:02 +03:00
dbe29926dc fixed unity bug 2026-04-15 03:25:53 +03:00
9d30446c55 added ability to get view angles from view matrix 2026-04-15 03:08:06 +03:00
ba80aebfae Merge pull request #181 from orange-cpp/feature/walk-bot
Feature/walk bot
2026-04-14 15:23:49 +03:00
9c1b6d0ba3 added tests improved API 2026-04-12 21:21:23 +03:00
ea07d17dbb improved walkbot 2026-04-12 12:05:40 +03:00
bb974da0e2 improvement 2026-04-12 11:16:39 +03:00
fde764c1fa added code 2026-04-12 11:12:17 +03:00
va_alpatov
28e86fc355 tests hotfix 2026-04-11 20:23:08 +03:00
va_alpatov
93e7a9457a fixed pathfinding bug 2026-04-11 20:06:39 +03:00
8f65183882 fixed tests 2026-04-08 15:34:10 +03:00
327db8d441 updated contributing 2026-04-03 20:59:34 +03:00
d8188de736 keeping 1 AABB type 2026-03-28 14:22:36 +03:00
33cd3f64e4 Merge pull request #180 from orange-cpp/feature/aabb-linetrace
added aabb line trace
2026-03-25 03:37:35 +03:00
67a07eed45 added aabb line trace 2026-03-25 03:14:22 +03:00
0b52b2847b Merge pull request #179 from orange-cpp/feature/aabb_check
added AABB check
2026-03-24 10:45:00 +03:00
d38895e4d7 added AABB check 2026-03-24 10:20:50 +03:00
04203d46ff patch 2026-03-24 06:44:10 +03:00
bcbb5c1a8d fixed index 2026-03-24 06:05:56 +03:00
ba46c86664 simplified method 2026-03-24 06:03:35 +03:00
3b0470cc11 Merge pull request #178 from orange-cpp/feature/imrovements
Feature/imrovements
2026-03-24 05:55:47 +03:00
8562c5d1f2 added more unreachable checks 2026-03-24 05:28:01 +03:00
8daba25c29 added ureachable 2026-03-24 05:21:00 +03:00
29b7ac6450 Merge pull request #177 from orange-cpp/feature/custom_ndc_z_range
Feature/custom ndc z range
2026-03-24 04:20:57 +03:00
89df10b778 specifeid ndc for game engines 2026-03-24 00:08:06 +03:00
8fb96b83db removed dead code 2026-03-23 23:52:41 +03:00
4b6db0c402 updated z range 2026-03-23 23:36:19 +03:00
a9ff7868cf simplified code 2026-03-23 05:52:35 +03:00
be80a5d243 added as_vector3 to view angles 2026-03-23 05:23:53 +03:00
881d3b9a2a added fields 2026-03-22 19:07:38 +03:00
f60e18b6ba replaced with table offset 2026-03-22 18:58:07 +03:00
0769d3d079 replaced with auto 2026-03-22 17:30:25 +03:00
b6755e21f9 fix 2026-03-22 16:32:00 +03:00
2287602fa2 Merge pull request #176 from orange-cpp/feature/vtable_index
added stuff
2026-03-22 16:21:39 +03:00
663890706e test fix 2026-03-22 16:06:57 +03:00
ab103f626b swaped to std::uintptr_t 2026-03-22 16:05:09 +03:00
cc4e01b100 added stuff 2026-03-22 16:00:35 +03:00
308f7ed481 forgot return 2026-03-21 16:43:18 +03:00
8802ad9af1 fix 2026-03-21 16:41:03 +03:00
2ac508d6e8 fixed tests 2026-03-21 16:28:48 +03:00
eb1ca6055b added additional error code 2026-03-21 16:15:48 +03:00
b528e41de3 fixed test names 2026-03-21 15:45:22 +03:00
8615ab2b7c changed name, fixed bug 2026-03-21 15:22:02 +03:00
5a4c042fec replaced enum 2026-03-21 14:53:04 +03:00
8063c1697a improved interface 2026-03-21 14:41:07 +03:00
7567501f00 Merge pull request #175 from orange-cpp/feature/w2s_no_clip
added clip option
2026-03-21 14:12:07 +03:00
46d999f846 added clip option 2026-03-21 13:58:06 +03:00
b54601132b added doc build to release 2026-03-21 06:32:05 +03:00
5c8ce2d163 Merge pull request #174 from orange-cpp/feature/docs-pipelines
added docs pipeline
2026-03-21 06:26:21 +03:00
04a86739b4 added docs pipeline 2026-03-21 06:11:20 +03:00
575b411863 updated install md 2026-03-21 06:05:29 +03:00
5a91151bc0 fix 2026-03-19 20:27:25 +03:00
66d4df0524 fix 2026-03-19 20:17:10 +03:00
54e14760ca fix 2026-03-19 20:09:07 +03:00
ee61c47d7d Merge pull request #173 from orange-cpp/feature/targeting_algorithms
Feature/targeting algorithms
2026-03-19 19:52:22 +03:00
d737aee1c5 added by distance targeting 2026-03-19 19:29:01 +03:00
ef422f0a86 added overload 2026-03-19 19:23:39 +03:00
e99ca0bc2b update 2026-03-19 19:19:42 +03:00
5f94e36965 fix for windows specific suff related to far near macroses 2026-03-19 15:32:05 +03:00
29510cf9e7 Removed from credit by own request 2026-03-19 15:24:35 +03:00
927508a76b Merge pull request #172 from orange-cpp/feaute/methods_calling_improvement
Feaute/methods calling improvement
2026-03-19 01:33:42 +03:00
f390b386d7 fix 2026-03-19 01:06:16 +03:00
012d837e8b fix windows x32 bit 2026-03-19 00:57:54 +03:00
6236c8fd68 added nodiscard 2026-03-18 21:24:35 +03:00
06dc36089f added overload 2026-03-18 21:19:09 +03:00
91136a61c4 improvement 2026-03-18 21:12:18 +03:00
9cdffcbdb1 added tests 2026-03-18 20:12:46 +03:00
a3e93ac259 added nttp 2026-03-18 20:05:32 +03:00
59f6d7a361 added call_method 2026-03-18 19:58:52 +03:00
dcf1ef1ea9 Merge pull request #171 from orange-cpp/feaute/projectile_pred_improvement
Feaute/projectile pred improvement
2026-03-17 21:58:59 +03:00
89bd879187 added tolerance depending on arch 2026-03-17 21:15:39 +03:00
aa08c7cb65 improved projectile prediction 2026-03-17 20:43:26 +03:00
a5c0ca0cbd added stuff 2026-03-17 20:31:46 +03:00
624683aed6 added unreachanble 2026-03-17 19:53:15 +03:00
f46672b2c6 Merge pull request #170 from orange-cpp/feature/projectile_aim_widget
add projectile
2026-03-17 19:51:12 +03:00
b8e61f49fa add projectile 2026-03-17 19:36:35 +03:00
37ea091282 Merge pull request #169 from orange-cpp/feaute/hud_features
Feaute/hud features
2026-03-16 14:39:58 +03:00
29a2743728 renamed args 2026-03-16 13:17:16 +03:00
1117eb37f1 added icon 2026-03-16 13:13:41 +03:00
b6b0d4db13 added aim dot 2026-03-16 03:24:53 +03:00
2e8a74aaaf imroved spacer 2026-03-16 03:06:14 +03:00
d8632dc74c added progress ring 2026-03-16 03:03:23 +03:00
fd531c930c added spacer 2026-03-16 02:21:24 +03:00
a91673216d added const 2026-03-16 02:10:05 +03:00
6487554844 corrected code style 2026-03-16 01:54:45 +03:00
1744172694 updated credits 2026-03-15 20:42:13 +03:00
114b2a6e58 Update README to enhance library description and features 2026-03-15 20:21:08 +03:00
d90a85d8b6 Merge pull request #168 from orange-cpp/feature/hud_declarative
Feature/hud declarative
2026-03-15 20:02:32 +03:00
e0a7179812 fix 2026-03-15 19:43:55 +03:00
a99dd24d6b improvement 2026-03-15 19:39:02 +03:00
d62dec9a8f changed api 2026-03-15 19:10:15 +03:00
1a176d8f09 fix 2026-03-15 18:48:22 +03:00
8e6ed19abf added dashed bar 2026-03-15 18:39:40 +03:00
311ab45722 Merge pull request #167 from orange-cpp/feaute/sig_scan_file_in_mem
added stuff
2026-03-15 17:37:42 +03:00
130277c1ae refactored test 2026-03-15 17:20:28 +03:00
4f1c42d6f6 tests fix 2026-03-15 17:04:21 +03:00
ccea4a0f0d added stuff 2026-03-15 16:54:47 +03:00
3fb98397e4 Merge pull request #166 from orange-cpp/feature/hud_improvement
Feature/hud improvement
2026-03-15 14:01:33 +03:00
56256c40fb cleaned code 2026-03-15 13:47:41 +03:00
46c94ae541 decomposed Run 2026-03-15 13:44:25 +03:00
a45f095b9c added skeleton 2026-03-15 04:59:47 +03:00
e849d23c47 improved dashed box 2026-03-15 04:56:10 +03:00
adad66599a adde dash box 2026-03-15 04:49:01 +03:00
69bdfc3307 improved example 2026-03-15 04:43:19 +03:00
55304c5df1 fixed bug 2026-03-15 04:28:56 +03:00
19d796cd4e improvement 2026-03-15 04:23:07 +03:00
d31ea6ed4d added more stuff 2026-03-15 04:17:30 +03:00
977d772687 fix 2026-03-13 22:20:57 +03:00
746f1b84a8 hot fix 2026-03-13 22:16:42 +03:00
af399a14ed Merge pull request #165 from orange-cpp/feature/hud
Feature/hud
2026-03-13 22:11:26 +03:00
6fb420642b updated props 2026-03-13 21:58:14 +03:00
6a2b4b90b4 fix 2026-03-13 21:49:56 +03:00
371d8154ee fix 2026-03-13 21:40:30 +03:00
d6a2165f83 fix 2026-03-13 21:37:03 +03:00
bb1b5ad14a removed shit 2026-03-13 21:32:44 +03:00
f188257e0f added stuff 2026-03-13 21:28:16 +03:00
87966c82b9 added realization 2026-03-13 21:09:12 +03:00
9da19582b5 added files 2026-03-13 20:51:59 +03:00
29f3e2565d Merge pull request #164 from orange-cpp/feaute/disk_optimization
avoid saving files on disk
2026-03-13 03:55:56 +03:00
e083b15e0b update 2026-03-13 03:42:12 +03:00
ed9da79d08 avoid saving files on disk 2026-03-13 03:33:57 +03:00
2002bcca83 Merge pull request #163 from orange-cpp/feature/serailization
Feature/serailization
2026-03-11 14:47:23 +03:00
ffacba71e2 changed to string view 2026-03-11 14:31:45 +03:00
6081a9c426 added throw test 2026-03-11 14:30:01 +03:00
8bbd504356 added check 2026-03-11 14:23:12 +03:00
1d54039f57 added events 2026-03-11 14:19:58 +03:00
93fc93d4f6 added more tests 2026-03-11 14:16:26 +03:00
b8a578774c improved serialization 2026-03-11 14:12:52 +03:00
bfa6c77776 Merge pull request #162 from orange-cpp/feature/scanner_example
Auto stash before checking out "origin/main"
2026-03-10 21:39:20 +03:00
1341ef9925 Auto stash before checking out "origin/main" 2026-03-10 20:06:00 +03:00
3ccbb0b25b Merge pull request #161 from orange-cpp/feature/lua-bindings
Feature/lua bindings
2026-03-10 19:41:34 +03:00
68993db48a updated read me 2026-03-10 19:22:39 +03:00
337204b3bf fixed missing source 2026-03-10 19:09:29 +03:00
1e601d2d8f android fix 2026-03-10 19:01:54 +03:00
2b4a75d011 added lua status to build logs 2026-03-10 19:00:36 +03:00
99a30e8fdf added pattern scan to lua 2026-03-10 18:55:55 +03:00
0cdd1d021f added triangle to lua 2026-03-10 18:39:20 +03:00
a79ac9743a added badge, added triangle 2026-03-10 18:39:10 +03:00
f26afb703b added bigobj for msvc and mingw 2026-03-10 18:29:13 +03:00
f237ee5f6a removed useless headers 2026-03-10 18:15:54 +03:00
9058ea9b39 refactored to class 2026-03-10 18:14:29 +03:00
f707ac1adb fixed forward decl 2026-03-10 18:04:31 +03:00
cbdabd3fc2 removed useless header 2026-03-10 17:57:11 +03:00
142 changed files with 8954 additions and 977 deletions

View File

@@ -12,6 +12,7 @@ AlignConsecutiveMacros: AcrossEmptyLinesAndComments
AlignTrailingComments: false
AllowShortBlocksOnASingleLine: Never
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
BreakTemplateDeclarations: Leave

View File

@@ -370,6 +370,8 @@ jobs:
shell: bash
run: |
cmake --preset ${{ matrix.preset }} \
-DCMAKE_C_COMPILER=$(xcrun --find clang) \
-DCMAKE_CXX_COMPILER=$(xcrun --find clang++) \
-DOMATH_BUILD_TESTS=ON \
-DOMATH_BUILD_BENCHMARK=OFF \
-DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || 'OFF' }} \
@@ -380,6 +382,7 @@ jobs:
run: cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
- name: Run unit_tests
if: ${{ matrix.coverage != true }}
shell: bash
run: ./out/Release/unit_tests

62
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Documentation
on:
push:
branches: [ main ]
paths:
- 'docs/**'
- 'mkdocs.yml'
- '.github/workflows/docs.yml'
pull_request:
branches: [ main ]
paths:
- 'docs/**'
- 'mkdocs.yml'
- '.github/workflows/docs.yml'
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
name: Build Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install mkdocs and dependencies
run: pip install mkdocs mkdocs-bootswatch
- name: Build documentation
run: mkdocs build --strict
- name: Upload artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: site/
deploy:
name: Deploy to GitHub Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -12,6 +12,35 @@ permissions:
contents: write
jobs:
##############################################################################
# 0) Documentation MkDocs
##############################################################################
docs-release:
name: Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install mkdocs and dependencies
run: pip install mkdocs mkdocs-bootswatch
- name: Build documentation
run: mkdocs build --strict
- name: Package
run: tar -czf omath-docs.tar.gz -C site .
- name: Upload release asset
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ github.event.release.tag_name }}" omath-docs.tar.gz --clobber
##############################################################################
# 1) Linux Clang / Ninja
##############################################################################

4
.idea/editor.xml generated
View File

@@ -17,7 +17,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
@@ -110,7 +110,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />

View File

@@ -77,6 +77,7 @@ if(${PROJECT_IS_TOP_LEVEL})
message(STATUS "[${PROJECT_NAME}]: Building using vcpkg ${OMATH_BUILD_VIA_VCPKG}")
message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}")
message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}")
message(STATUS "[${PROJECT_NAME}]: Lua feature status ${OMATH_ENABLE_LUA}")
endif()
file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
@@ -190,6 +191,12 @@ elseif(OMATH_THREAT_WARNING_AS_ERROR)
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(${PROJECT_NAME} PRIVATE /bigobj)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_HOST_SYSTEM_NAME EQUAL "Windows")
target_compile_options(${PROJECT_NAME} PRIVATE -mbig-obj)
endif()
# Windows SDK redefine min/max via preprocessor and break std::min and std::max
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_definitions(${PROJECT_NAME} INTERFACE NOMINMAX)

View File

@@ -1,32 +1,36 @@
## 🤝 Contributing to OMath or other Orange's Projects
# Contributing
### ❕ Prerequisites
## Prerequisites
- A working up-to-date OMath installation
- C++ knowledge
- Git knowledge
- Ability to ask for help (Feel free to create empty pull-request or PM a maintainer
in [Telegram](https://t.me/orange_cpp))
- C++ compiler with C++23 support (Clang 18+, GCC 14+, MSVC 19.38+)
- CMake 3.25+
- Git
- Familiarity with the codebase (see `INSTALL.md` for setup)
### ⏬ Setting up OMath
For questions, create a draft PR or reach out via [Telegram](https://t.me/orange_cpp).
Please read INSTALL.md file in repository
## Workflow
### 🔀 Pull requests and Branches
1. [Fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) the repository.
2. Create a feature branch from `main`.
3. Make your changes, ensuring tests pass.
4. Open a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) against `main`.
In order to send code back to the official OMath repository, you must first create a copy of OMath on your github
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.
## Code Style
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.
Follow the project `.clang-format`. Run `clang-format` before committing.
### 📜 Code-Style
## Building
The orange code-style can be found in `.clang-format`.
Use one of the CMake presets defined in `CMakePresets.json`:
### 📦 Building
```bash
cmake --preset <preset-name> -DOMATH_BUILD_TESTS=ON
cmake --build --preset <preset-name>
```
OMath has already created the `cmake-build` and `out` directories where cmake/bin files are located. By default, you
can build OMath by running `cmake --build cmake-build/build/windows-release --target omath -j 6` in the source
directory.
Run `cmake --list-presets` to see available configurations.
## Tests
All new functionality must include unit tests. Run the test binary after building to verify nothing is broken.

View File

@@ -3,8 +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.
- Alex2772 for reference of AUI declarative interface design for omath::hud
And a big hand to everyone else who has contributed over the past!

View File

@@ -28,6 +28,29 @@ target("...")
add_packages("omath")
```
## <img width="28px" src="https://conan.io/favicon.png" /> Using Conan
**Note**: Support Conan for package management
1. Install [Conan](https://conan.io/downloads)
2. Run the following command to install the omath package:
```
conan install --requires="omath/[*]" --build=missing
```
conanfile.txt
```ini
[requires]
omath/[*]
[generators]
CMakeDeps
CMakeToolchain
```
CMakeLists.txt
```cmake
find_package(omath CONFIG REQUIRED)
target_link_libraries(main PRIVATE omath::omath)
```
For more details, see the [Conan documentation](https://docs.conan.io/2/).
## <img width="28px" src="https://github.githubassets.com/favicons/favicon.svg" /> Using prebuilt binaries (GitHub Releases)
**Note**: This is the fastest option if you dont want to build from source.

View File

@@ -9,11 +9,12 @@
[![CodeFactor](https://www.codefactor.io/repository/github/orange-cpp/omath/badge)](https://www.codefactor.io/repository/github/orange-cpp/omath)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml)
[![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg)](https://repology.org/project/orange-math/versions)
![Conan Center](https://img.shields.io/conan/v/omath)
![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath)
[![discord badge](https://dcbadge.limes.pink/api/server/https://discord.gg/eDgdaWbqwZ?style=flat)](https://discord.gg/eDgdaWbqwZ)
[![telegram badge](https://img.shields.io/badge/Telegram-2CA5E0?style=flat-squeare&logo=telegram&logoColor=white)](https://t.me/orangennotes)
OMath is a 100% independent, constexpr template blazingly fast math library that doesn't have legacy C++ code.
OMath is a 100% independent, constexpr template blazingly fast math/physics/games/mods/cheats development framework 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...
<br>
@@ -83,6 +84,8 @@ if (auto screen = camera.world_to_screen(world_position)) {
- **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**.
- **Cross platform**: Supports Windows, MacOS and Linux.
- **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps.
- **Scripting**: Supports to make scripts in Lua out of box.
- **Handy**: Allow to design wall hacks in modern jetpack compose like way.
- **Battle tested**: It's already used by some big players on the market like wraith.su and bluedream.ltd
<div align = center>

View File

@@ -1,6 +1,6 @@
# Installation
# Installation Guide
## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg
## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg (recomended)
**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:
@@ -28,6 +28,69 @@ target("...")
add_packages("omath")
```
## <img width="28px" src="https://conan.io/favicon.png" /> Using Conan
**Note**: Support Conan for package management
1. Install [Conan](https://conan.io/downloads)
2. Run the following command to install the omath package:
```
conan install --requires="omath/[*]" --build=missing
```
conanfile.txt
```ini
[requires]
omath/[*]
[generators]
CMakeDeps
CMakeToolchain
```
CMakeLists.txt
```cmake
find_package(omath CONFIG REQUIRED)
target_link_libraries(main PRIVATE omath::omath)
```
For more details, see the [Conan documentation](https://docs.conan.io/2/).
## <img width="28px" src="https://github.githubassets.com/favicons/favicon.svg" /> Using prebuilt binaries (GitHub Releases)
**Note**: This is the fastest option if you dont want to build from source.
1. **Go to the Releases page**
- Open the projects GitHub **Releases** page and choose the latest version.
2. **Download the correct asset for your platform**
- Pick the archive that matches your OS and architecture (for example: Windows x64 / Linux x64 / macOS arm64).
3. **Extract the archive**
- You should end up with something like:
- `include/` (headers)
- `lib/` or `bin/` (library files / DLLs)
- sometimes `cmake/` (CMake package config)
4. **Use it in your project**
### Option A: CMake package (recommended if the release includes CMake config files)
If the extracted folder contains something like `lib/cmake/omath` or `cmake/omath`, you can point CMake to it:
```cmake
# Example: set this to the extracted prebuilt folder
list(APPEND CMAKE_PREFIX_PATH "path/to/omath-prebuilt")
find_package(omath CONFIG REQUIRED)
target_link_libraries(main PRIVATE omath::omath)
```
### Option B: Manual include + link (works with any layout)
If theres no CMake package config, link it manually:
```cmake
target_include_directories(main PRIVATE "path/to/omath-prebuilt/include")
# Choose ONE depending on what you downloaded:
# - Static library: .lib / .a
# - Shared library: .dll + .lib import (Windows), .so (Linux), .dylib (macOS)
target_link_directories(main PRIVATE "path/to/omath-prebuilt/lib")
target_link_libraries(main PRIVATE omath) # or the actual library filename
```
## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake
1. **Preparation**
@@ -62,7 +125,7 @@ target("...")
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 |

View File

@@ -2,6 +2,7 @@ add_subdirectory(example_barycentric)
add_subdirectory(example_glfw3)
add_subdirectory(example_proj_mat_builder)
add_subdirectory(example_signature_scan)
add_subdirectory(example_hud)
if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(example_projection_matrix_builder)

View File

@@ -71,18 +71,18 @@ void drawChar(char c, float x, float y, float scale, const Color& color, std::ve
lines.push_back(x + x1 * w);
lines.push_back(y + y1 * h);
lines.push_back(0.0f);
lines.push_back(color.x);
lines.push_back(color.y);
lines.push_back(color.z);
lines.push_back(color.value().x);
lines.push_back(color.value().y);
lines.push_back(color.value().z);
lines.push_back(1.0f); // size
lines.push_back(1.0f); // isLine
lines.push_back(x + x2 * w);
lines.push_back(y + y2 * h);
lines.push_back(0.0f);
lines.push_back(color.x);
lines.push_back(color.y);
lines.push_back(color.z);
lines.push_back(color.value().x);
lines.push_back(color.value().y);
lines.push_back(color.value().z);
lines.push_back(1.0f); // size
lines.push_back(1.0f); // isLine
};

View File

@@ -318,22 +318,22 @@ int main()
glfwPollEvents();
omath::Vector3<float> move_dir;
if (glfwGetKey(window, GLFW_KEY_W))
move_dir += camera.get_forward();
move_dir += camera.get_abs_forward();
if (glfwGetKey(window, GLFW_KEY_A))
move_dir -= camera.get_right();
move_dir -= camera.get_abs_right();
if (glfwGetKey(window, GLFW_KEY_S))
move_dir -= camera.get_forward();
move_dir -= camera.get_abs_forward();
if (glfwGetKey(window, GLFW_KEY_D))
move_dir += camera.get_right();
move_dir += camera.get_abs_right();
if (glfwGetKey(window, GLFW_KEY_SPACE))
move_dir += camera.get_up();
move_dir += camera.get_abs_up();
if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL))
move_dir -= camera.get_up();
move_dir -= camera.get_abs_up();
auto delta = glfwGetTime() - old_mouse_time;

View File

@@ -0,0 +1,16 @@
project(example_hud)
add_executable(${PROJECT_NAME} main.cpp gui/main_window.cpp gui/main_window.hpp)
set_target_properties(
${PROJECT_NAME}
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(OpenGL)
find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE glfw imgui::imgui omath::omath OpenGL::GL)

View File

@@ -0,0 +1,263 @@
//
// Created by Orange on 11/11/2024.
//
#include "main_window.hpp"
#include "omath/hud/renderer_realizations/imgui_renderer.hpp"
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <omath/hud/entity_overlay.hpp>
namespace imgui_desktop::gui
{
bool MainWindow::m_canMoveWindow = false;
MainWindow::MainWindow(const std::string_view& caption, int width, int height)
{
if (!glfwInit())
std::exit(EXIT_FAILURE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, true);
m_window = glfwCreateWindow(width, height, caption.data(), nullptr, nullptr);
glfwMakeContextCurrent(m_window);
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = {0.05f, 0.05f, 0.05f, 0.92f};
ImGui::GetStyle().AntiAliasedLines = false;
ImGui::GetStyle().AntiAliasedFill = false;
ImGui_ImplGlfw_InitForOpenGL(m_window, true);
ImGui_ImplOpenGL3_Init("#version 150");
}
void MainWindow::Run()
{
while (!glfwWindowShouldClose(m_window) && m_opened)
{
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
const auto* vp = ImGui::GetMainViewport();
ImGui::GetBackgroundDrawList()->AddRectFilled({}, vp->Size, ImColor(30, 30, 30, 220));
draw_controls();
draw_overlay();
ImGui::Render();
present();
}
glfwDestroyWindow(m_window);
}
void MainWindow::draw_controls()
{
const auto* vp = ImGui::GetMainViewport();
ImGui::SetNextWindowPos({0.f, 0.f});
ImGui::SetNextWindowSize({280.f, vp->Size.y});
ImGui::Begin("Controls", &m_opened,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
ImGui::PushItemWidth(160.f);
if (ImGui::CollapsingHeader("Entity", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::SliderFloat("X##ent", &m_entity_x, 100.f, vp->Size.x - 100.f);
ImGui::SliderFloat("Top Y", &m_entity_top_y, 20.f, m_entity_bottom_y - 20.f);
ImGui::SliderFloat("Bottom Y", &m_entity_bottom_y, m_entity_top_y + 20.f, vp->Size.y - 20.f);
}
if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Checkbox("Box##chk", &m_show_box);
ImGui::SameLine();
ImGui::Checkbox("Cornered", &m_show_cornered_box);
ImGui::SameLine();
ImGui::Checkbox("Dashed", &m_show_dashed_box);
ImGui::ColorEdit4("Color##box", reinterpret_cast<float*>(&m_box_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Fill##box", reinterpret_cast<float*>(&m_box_fill), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Thickness", &m_box_thickness, 0.5f, 5.f);
ImGui::SliderFloat("Corner ratio", &m_corner_ratio, 0.05f, 0.5f);
ImGui::Separator();
ImGui::ColorEdit4("Dash color", reinterpret_cast<float*>(&m_dash_color), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Dash length", &m_dash_len, 2.f, 30.f);
ImGui::SliderFloat("Dash gap", &m_dash_gap, 1.f, 20.f);
ImGui::SliderFloat("Dash thick", &m_dash_thickness, 0.5f, 5.f);
}
if (ImGui::CollapsingHeader("Bars", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::ColorEdit4("Color##bar", reinterpret_cast<float*>(&m_bar_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("BG##bar", reinterpret_cast<float*>(&m_bar_bg_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Outline##bar", reinterpret_cast<float*>(&m_bar_outline_color),
ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Width##bar", &m_bar_width, 1.f, 20.f);
ImGui::SliderFloat("Value##bar", &m_bar_value, 0.f, 1.f);
ImGui::SliderFloat("Offset##bar", &m_bar_offset, 1.f, 20.f);
ImGui::Checkbox("Right##bar", &m_show_right_bar);
ImGui::SameLine();
ImGui::Checkbox("Left##bar", &m_show_left_bar);
ImGui::Checkbox("Top##bar", &m_show_top_bar);
ImGui::SameLine();
ImGui::Checkbox("Bottom##bar", &m_show_bottom_bar);
ImGui::Checkbox("Right dashed##bar", &m_show_right_dashed_bar);
ImGui::SameLine();
ImGui::Checkbox("Left dashed##bar", &m_show_left_dashed_bar);
ImGui::Checkbox("Top dashed##bar", &m_show_top_dashed_bar);
ImGui::SameLine();
ImGui::Checkbox("Bot dashed##bar", &m_show_bottom_dashed_bar);
ImGui::SliderFloat("Dash len##bar", &m_bar_dash_len, 2.f, 20.f);
ImGui::SliderFloat("Dash gap##bar", &m_bar_dash_gap, 1.f, 15.f);
}
if (ImGui::CollapsingHeader("Labels", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Checkbox("Outlined", &m_outlined);
ImGui::SliderFloat("Offset##lbl", &m_label_offset, 0.f, 15.f);
ImGui::Checkbox("Right##lbl", &m_show_right_labels);
ImGui::SameLine();
ImGui::Checkbox("Left##lbl", &m_show_left_labels);
ImGui::Checkbox("Top##lbl", &m_show_top_labels);
ImGui::SameLine();
ImGui::Checkbox("Bottom##lbl", &m_show_bottom_labels);
ImGui::Checkbox("Ctr top##lbl", &m_show_centered_top);
ImGui::SameLine();
ImGui::Checkbox("Ctr bot##lbl", &m_show_centered_bottom);
}
if (ImGui::CollapsingHeader("Skeleton"))
{
ImGui::Checkbox("Show##skel", &m_show_skeleton);
ImGui::ColorEdit4("Color##skel", reinterpret_cast<float*>(&m_skel_color), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Thick##skel", &m_skel_thickness, 0.5f, 5.f);
}
if (ImGui::CollapsingHeader("Progress Ring"))
{
ImGui::Checkbox("Show##ring", &m_show_ring);
ImGui::ColorEdit4("Color##ring", reinterpret_cast<float*>(&m_ring_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("BG##ring", reinterpret_cast<float*>(&m_ring_bg), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Radius##ring", &m_ring_radius, 4.f, 30.f);
ImGui::SliderFloat("Value##ring", &m_ring_ratio, 0.f, 1.f);
ImGui::SliderFloat("Thick##ring", &m_ring_thickness, 0.5f, 6.f);
ImGui::SliderFloat("Offset##ring", &m_ring_offset, 0.f, 15.f);
}
if (ImGui::CollapsingHeader("Scan Marker"))
{
ImGui::Checkbox("Show##scan", &m_show_scan);
ImGui::ColorEdit4("Fill##scan", reinterpret_cast<float*>(&m_scan_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Outline##scan", reinterpret_cast<float*>(&m_scan_outline), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Thick##scan", &m_scan_outline_thickness, 0.5f, 5.f);
}
if (ImGui::CollapsingHeader("Aim Dot"))
{
ImGui::Checkbox("Show##aim", &m_show_aim);
ImGui::ColorEdit4("Color##aim", reinterpret_cast<float*>(&m_aim_color), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Radius##aim", &m_aim_radius, 1.f, 10.f);
}
if (ImGui::CollapsingHeader("Projectile Aim"))
{
ImGui::Checkbox("Show##proj", &m_show_proj);
ImGui::ColorEdit4("Color##proj", reinterpret_cast<float*>(&m_proj_color), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Size##proj", &m_proj_size, 1.f, 30.f);
ImGui::SliderFloat("Line width##proj", &m_proj_line_width, 0.5f, 5.f);
ImGui::SliderFloat("Pos X##proj", &m_proj_pos_x, 0.f, vp->Size.x);
ImGui::SliderFloat("Pos Y##proj", &m_proj_pos_y, 0.f, vp->Size.y);
ImGui::Combo("Figure##proj", &m_proj_figure, "Circle\0Square\0");
}
if (ImGui::CollapsingHeader("Snap Line"))
{
ImGui::Checkbox("Show##snap", &m_show_snap);
ImGui::ColorEdit4("Color##snap", reinterpret_cast<float*>(&m_snap_color), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Width##snap", &m_snap_width, 0.5f, 5.f);
}
ImGui::PopItemWidth();
ImGui::End();
}
void MainWindow::draw_overlay()
{
using namespace omath::hud::widget;
using omath::hud::when;
const auto* vp = ImGui::GetMainViewport();
const Bar bar{m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset};
const DashedBar dbar{m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width,
m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset};
omath::hud::EntityOverlay({m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y},
std::make_shared<omath::hud::ImguiHudRenderer>())
.contents(
// ── Boxes ────────────────────────────────────────────────────
when(m_show_box, Box{m_box_color, m_box_fill, m_box_thickness}),
when(m_show_cornered_box, CorneredBox{omath::Color::from_rgba(255, 0, 255, 255), m_box_fill,
m_corner_ratio, m_box_thickness}),
when(m_show_dashed_box, DashedBox{m_dash_color, m_dash_len, m_dash_gap, m_dash_thickness}),
RightSide{
when(m_show_right_bar, bar),
when(m_show_right_dashed_bar, dbar),
when(m_show_right_labels,
Label{{0.f, 1.f, 0.f, 1.f}, m_label_offset, m_outlined, "Health: 100/100"}),
when(m_show_right_labels,
Label{{1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: 125/125"}),
when(m_show_right_labels,
Label{{1.f, 0.f, 1.f, 1.f}, m_label_offset, m_outlined, "*LOCKED*"}),
SpaceVertical{10},
when(m_show_ring, ProgressRing{m_ring_color, m_ring_bg, m_ring_radius, m_ring_ratio,
m_ring_thickness, m_ring_offset}),
},
LeftSide{
when(m_show_left_bar, bar),
when(m_show_left_dashed_bar, dbar),
when(m_show_left_labels, Label{omath::Color::from_rgba(255, 128, 0, 255),
m_label_offset, m_outlined, "Armor: 75"}),
when(m_show_left_labels, Label{omath::Color::from_rgba(0, 200, 255, 255),
m_label_offset, m_outlined, "Level: 42"}),
},
TopSide{
when(m_show_top_bar, bar),
when(m_show_top_dashed_bar, dbar),
when(m_show_centered_top, Centered{Label{omath::Color::from_rgba(0, 255, 255, 255),
m_label_offset, m_outlined, "*VISIBLE*"}}),
when(m_show_top_labels, Label{omath::Color::from_rgba(255, 255, 0, 255), m_label_offset,
m_outlined, "*SCOPED*"}),
when(m_show_top_labels, Label{omath::Color::from_rgba(255, 0, 0, 255), m_label_offset,
m_outlined, "*BLEEDING*"}),
},
BottomSide{
when(m_show_bottom_bar, bar),
when(m_show_bottom_dashed_bar, dbar),
when(m_show_centered_bottom, Centered{Label{omath::Color::from_rgba(255, 255, 255, 255),
m_label_offset, m_outlined, "PlayerName"}}),
when(m_show_bottom_labels, Label{omath::Color::from_rgba(200, 200, 0, 255),
m_label_offset, m_outlined, "42m"}),
},
when(m_show_aim, AimDot{{m_entity_x, m_entity_top_y+40.f}, m_aim_color, m_aim_radius}),
when(m_show_scan, ScanMarker{m_scan_color, m_scan_outline, m_scan_outline_thickness}),
when(m_show_skeleton, Skeleton{m_skel_color, m_skel_thickness}),
when(m_show_proj, ProjectileAim{{m_proj_pos_x, m_proj_pos_y}, m_proj_color, m_proj_size, m_proj_line_width, static_cast<ProjectileAim::Figure>(m_proj_figure)}),
when(m_show_snap, SnapLine{{vp->Size.x / 2.f, vp->Size.y}, m_snap_color, m_snap_width}));
}
void MainWindow::present()
{
int w, h;
glfwGetFramebufferSize(m_window, &w, &h);
glViewport(0, 0, w, h);
glClearColor(0.f, 0.f, 0.f, 0.f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(m_window);
}
} // namespace imgui_desktop::gui

View File

@@ -0,0 +1,94 @@
//
// Created by Orange on 11/11/2024.
//
#pragma once
#include <omath/hud/entity_overlay.hpp>
#include <omath/utility/color.hpp>
#include <string_view>
struct GLFWwindow;
namespace imgui_desktop::gui
{
class MainWindow
{
public:
MainWindow(const std::string_view& caption, int width, int height);
void Run();
private:
void draw_controls();
void draw_overlay();
void present();
GLFWwindow* m_window = nullptr;
static bool m_canMoveWindow;
bool m_opened = true;
// Entity
float m_entity_x = 550.f, m_entity_top_y = 150.f, m_entity_bottom_y = 450.f;
// Box
omath::Color m_box_color{1.f, 1.f, 1.f, 1.f};
omath::Color m_box_fill{0.f, 0.f, 0.f, 0.f};
float m_box_thickness = 1.f, m_corner_ratio = 0.2f;
bool m_show_box = true, m_show_cornered_box = true, m_show_dashed_box = false;
// Dashed box
omath::Color m_dash_color = omath::Color::from_rgba(255, 200, 0, 255);
float m_dash_len = 8.f, m_dash_gap = 5.f, m_dash_thickness = 1.f;
// Bars
omath::Color m_bar_color{0.f, 1.f, 0.f, 1.f};
omath::Color m_bar_bg_color{0.f, 0.f, 0.f, 0.5f};
omath::Color m_bar_outline_color{0.f, 0.f, 0.f, 1.f};
float m_bar_width = 4.f, m_bar_value = 0.75f, m_bar_offset = 5.f;
bool m_show_right_bar = true, m_show_left_bar = true;
bool m_show_top_bar = true, m_show_bottom_bar = true;
bool m_show_right_dashed_bar = false, m_show_left_dashed_bar = false;
bool m_show_top_dashed_bar = false, m_show_bottom_dashed_bar = false;
float m_bar_dash_len = 6.f, m_bar_dash_gap = 4.f;
// Labels
float m_label_offset = 3.f;
bool m_outlined = true;
bool m_show_right_labels = true, m_show_left_labels = true;
bool m_show_top_labels = true, m_show_bottom_labels = true;
bool m_show_centered_top = true, m_show_centered_bottom = true;
// Skeleton
omath::Color m_skel_color = omath::Color::from_rgba(255, 255, 255, 200);
float m_skel_thickness = 1.f;
bool m_show_skeleton = false;
// Progress ring
omath::Color m_ring_color = omath::Color::from_rgba(0, 200, 255, 255);
omath::Color m_ring_bg{0.3f, 0.3f, 0.3f, 0.5f};
float m_ring_radius = 10.f, m_ring_ratio = 0.65f, m_ring_thickness = 2.5f, m_ring_offset = 5.f;
bool m_show_ring = false;
// Scan marker
omath::Color m_scan_color = omath::Color::from_rgba(255, 200, 0, 150);
omath::Color m_scan_outline = omath::Color::from_rgba(255, 200, 0, 255);
float m_scan_outline_thickness = 2.f;
bool m_show_scan = false;
// Aim dot
omath::Color m_aim_color = omath::Color::from_rgba(255, 0, 0, 255);
float m_aim_radius = 3.f;
bool m_show_aim = false;
// Snap line
omath::Color m_snap_color = omath::Color::from_rgba(255, 50, 50, 255);
float m_snap_width = 1.5f;
bool m_show_snap = true;
// Projectile aim
omath::Color m_proj_color = omath::Color::from_rgba(255, 50, 50, 255);
float m_proj_size = 10.f;
float m_proj_line_width = 1.5f;
float m_proj_pos_x = 300.f, m_proj_pos_y = 30.f;
int m_proj_figure = 1; // 0=circle, 1=square
bool m_show_proj = true;
};
} // namespace imgui_desktop::gui

View File

@@ -0,0 +1,8 @@
//
// Created by orange on 13.03.2026.
//
#include "gui/main_window.hpp"
int main()
{
imgui_desktop::gui::MainWindow("omath::hud", 800, 600).Run();
}

View File

@@ -0,0 +1,28 @@
//
// Created by Vladislav on 24.03.2026.
//
#pragma once
#include "omath/linear_algebra/vector3.hpp"
namespace omath::primitives
{
template<class Type>
struct Aabb final
{
Vector3<Type> min;
Vector3<Type> max;
[[nodiscard]]
constexpr Vector3<Type> center() const noexcept
{
return (min + max) / static_cast<Type>(2);
}
[[nodiscard]]
constexpr Vector3<Type> extents() const noexcept
{
return (max - min) / static_cast<Type>(2);
}
};
} // namespace omath::primitives

View File

@@ -0,0 +1,98 @@
//
// Created by Vladislav on 19.03.2026.
//
#pragma once
#include "omath/linear_algebra/vector3.hpp"
#include <functional>
#include <iterator>
#include <optional>
#include <ranges>
namespace omath::algorithm
{
template<class CameraType, std::input_or_output_iterator IteratorType, class FilterT>
requires std::is_invocable_r_v<bool, std::function<FilterT>, std::iter_reference_t<IteratorType>>
[[nodiscard]]
IteratorType get_closest_target_by_fov(const IteratorType& begin, const IteratorType& end, const CameraType& camera,
auto get_position,
const std::optional<std::function<FilterT>>& filter_func = std::nullopt)
{
auto best_target = end;
const auto& camera_angles = camera.get_view_angles();
const Vector2<float> camera_angles_vec = {camera_angles.pitch.as_degrees(), camera_angles.yaw.as_degrees()};
for (auto current = begin; current != end; current = std::next(current))
{
if (filter_func && !filter_func.value()(*current))
continue;
if (best_target == end)
{
best_target = current;
continue;
}
const auto current_target_angles = camera.calc_look_at_angles(get_position(*current));
const auto best_target_angles = camera.calc_look_at_angles(get_position(*best_target));
const auto current_target_distance = camera_angles_vec.distance_to(current_target_angles.as_vector3());
const auto best_target_distance = camera_angles.as_vector3().distance_to(best_target_angles.as_vector3());
if (current_target_distance < best_target_distance)
best_target = current;
}
return best_target;
}
template<class CameraType, std::ranges::range RangeType, class FilterT>
requires std::is_invocable_r_v<bool, std::function<FilterT>,
std::ranges::range_reference_t<const RangeType>>
[[nodiscard]]
auto get_closest_target_by_fov(const RangeType& range, const CameraType& camera,
auto get_position,
const std::optional<std::function<FilterT>>& filter_func = std::nullopt)
{
return get_closest_target_by_fov<CameraType, decltype(std::ranges::begin(range)), FilterT>(
std::ranges::begin(range), std::ranges::end(range), camera, get_position, filter_func);
}
// ── By world-space distance ───────────────────────────────────────────────
template<std::input_or_output_iterator IteratorType, class FilterT>
requires std::is_invocable_r_v<bool, std::function<FilterT>, std::iter_reference_t<IteratorType>>
[[nodiscard]]
IteratorType get_closest_target_by_distance(const IteratorType& begin, const IteratorType& end,
const Vector3<float>& origin, auto get_position,
const std::optional<std::function<FilterT>>& filter_func = std::nullopt)
{
auto best_target = end;
for (auto current = begin; current != end; current = std::next(current))
{
if (filter_func && !filter_func.value()(*current))
continue;
if (best_target == end)
{
best_target = current;
continue;
}
if (origin.distance_to(get_position(*current)) < origin.distance_to(get_position(*best_target)))
best_target = current;
}
return best_target;
}
template<std::ranges::range RangeType, class FilterT>
requires std::is_invocable_r_v<bool, std::function<FilterT>,
std::ranges::range_reference_t<const RangeType>>
[[nodiscard]]
auto get_closest_target_by_distance(const RangeType& range, const Vector3<float>& origin,
auto get_position,
const std::optional<std::function<FilterT>>& filter_func = std::nullopt)
{
return get_closest_target_by_distance<decltype(std::ranges::begin(range)), FilterT>(
std::ranges::begin(range), std::ranges::end(range), origin, get_position, filter_func);
}
} // namespace omath::algorithm

View File

@@ -3,6 +3,7 @@
//
#pragma once
#include "omath/3d_primitives/aabb.hpp"
#include "omath/linear_algebra/triangle.hpp"
#include "omath/linear_algebra/vector3.hpp"
@@ -34,6 +35,7 @@ namespace omath::collision
class LineTracer final
{
using TriangleType = Triangle<typename RayType::VectorType>;
using AABBType = primitives::Aabb<typename RayType::VectorType::ContainedType>;
public:
LineTracer() = delete;
@@ -87,6 +89,54 @@ namespace omath::collision
return ray.start + ray_dir * t_hit;
}
// Slab method ray-AABB intersection
// Returns the hit point on the AABB surface, or ray.end if no intersection
[[nodiscard]]
constexpr static auto get_ray_hit_point(const RayType& ray, const AABBType& aabb) noexcept
{
using T = typename RayType::VectorType::ContainedType;
const auto dir = ray.direction_vector();
auto t_min = -std::numeric_limits<T>::infinity();
auto t_max = std::numeric_limits<T>::infinity();
const auto process_axis = [&](const T& d, const T& origin, const T& box_min,
const T& box_max) -> bool
{
constexpr T k_epsilon = std::numeric_limits<T>::epsilon();
if (std::abs(d) < k_epsilon)
return origin >= box_min && origin <= box_max;
const T inv = T(1) / d;
T t0 = (box_min - origin) * inv;
T t1 = (box_max - origin) * inv;
if (t0 > t1)
std::swap(t0, t1);
t_min = std::max(t_min, t0);
t_max = std::min(t_max, t1);
return t_min <= t_max;
};
if (!process_axis(dir.x, ray.start.x, aabb.min.x, aabb.max.x))
return ray.end;
if (!process_axis(dir.y, ray.start.y, aabb.min.y, aabb.max.y))
return ray.end;
if (!process_axis(dir.z, ray.start.z, aabb.min.z, aabb.max.z))
return ray.end;
// t_hit: use entry point if in front of origin, otherwise 0 (started inside)
const T t_hit = std::max(T(0), t_min);
if (t_max < T(0))
return ray.end; // box entirely behind origin
if (!ray.infinite_length && t_hit > T(1))
return ray.end; // box beyond ray endpoint
return ray.start + dir * t_hit;
}
template<class MeshType>
[[nodiscard]]
constexpr static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept

View File

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

View File

@@ -22,7 +22,8 @@ namespace omath::cry_engine
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;
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>

View File

@@ -18,7 +18,7 @@ namespace omath::cry_engine
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
float near, float far) noexcept;
float near, float far, NDCDepthRange ndc_depth_range) noexcept;
};
} // namespace omath::cry_engine

View File

@@ -16,7 +16,8 @@ namespace omath::cry_engine
const float pitch, const float yaw,
const float time, const float gravity) noexcept
{
auto current_pos = projectile.m_origin
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
auto current_pos = launch_pos
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;

View File

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

View File

@@ -22,7 +22,8 @@ namespace omath::frostbite_engine
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;
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>

View File

@@ -18,7 +18,7 @@ namespace omath::frostbite_engine
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
float near, float far) noexcept;
float near, float far, NDCDepthRange ndc_depth_range) noexcept;
};
} // namespace omath::unreal_engine

View File

@@ -16,7 +16,8 @@ namespace omath::frostbite_engine
const float pitch, const float yaw,
const float time, const float gravity) noexcept
{
auto current_pos = projectile.m_origin
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
auto current_pos = launch_pos
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;

View File

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

View File

@@ -22,7 +22,8 @@ namespace omath::iw_engine
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>

View File

@@ -18,7 +18,7 @@ namespace omath::iw_engine
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
float near, float far) noexcept;
float near, float far, NDCDepthRange ndc_depth_range) noexcept;
};
} // namespace omath::iw_engine

View File

@@ -17,7 +17,8 @@ namespace omath::iw_engine
const float pitch, const float yaw,
const float time, const float gravity) noexcept
{
auto current_pos = projectile.m_origin
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
auto current_pos = launch_pos
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;

View File

@@ -8,5 +8,5 @@
namespace omath::opengl_engine
{
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, true>;
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::NEGATIVE_ONE_TO_ONE, {.inverted_forward = true}>;
} // namespace omath::opengl_engine

View File

@@ -21,7 +21,8 @@ namespace omath::opengl_engine
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;
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>

View File

@@ -18,7 +18,7 @@ namespace omath::opengl_engine
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
float near, float far) noexcept;
float near, float far, NDCDepthRange ndc_depth_range) noexcept;
};
} // namespace omath::opengl_engine

View File

@@ -16,7 +16,8 @@ namespace omath::opengl_engine
const float pitch, const float yaw,
const float time, const float gravity) noexcept
{
auto current_pos = projectile.m_origin
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
auto current_pos = launch_pos
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;

View File

@@ -7,5 +7,5 @@
#include "traits/camera_trait.hpp"
namespace omath::source_engine
{
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>;
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::ZERO_TO_ONE>;
} // namespace omath::source_engine

View File

@@ -21,7 +21,8 @@ namespace omath::source_engine
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>

View File

@@ -18,7 +18,7 @@ namespace omath::source_engine
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
float near, float far) noexcept;
float near, float far, NDCDepthRange ndc_depth_range) noexcept;
};
} // namespace omath::source_engine

View File

@@ -17,7 +17,8 @@ namespace omath::source_engine
const float pitch, const float yaw,
const float time, const float gravity) noexcept
{
auto current_pos = projectile.m_origin
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
auto current_pos = launch_pos
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;

View File

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

View File

@@ -22,7 +22,8 @@ namespace omath::unity_engine
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;
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>

View File

@@ -18,7 +18,7 @@ namespace omath::unity_engine
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
float near, float far) noexcept;
float near, float far, NDCDepthRange ndc_depth_range) noexcept;
};
} // namespace omath::unity_engine

View File

@@ -16,7 +16,8 @@ namespace omath::unity_engine
const float pitch, const float yaw,
const float time, const float gravity) noexcept
{
auto current_pos = projectile.m_origin
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
auto current_pos = launch_pos
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;

View File

@@ -9,5 +9,5 @@
namespace omath::unreal_engine
{
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>;
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::ZERO_TO_ONE, {.inverted_right = true}>;
} // namespace omath::unreal_engine

View File

@@ -22,7 +22,8 @@ namespace omath::unreal_engine
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;
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>

View File

@@ -18,7 +18,7 @@ namespace omath::unreal_engine
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
[[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
float near, float far) noexcept;
float near, float far, NDCDepthRange ndc_depth_range) noexcept;
};
} // namespace omath::unreal_engine

View File

@@ -16,7 +16,8 @@ namespace omath::unreal_engine
const float pitch, const float yaw,
const float time, const float gravity) noexcept
{
auto current_pos = projectile.m_origin
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
auto current_pos = launch_pos
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;

View File

@@ -0,0 +1,23 @@
//
// Created by orange on 13.03.2026.
//
#pragma once
#include "omath/linear_algebra/vector2.hpp"
#include <array>
namespace omath::hud
{
class CanvasBox final
{
public:
CanvasBox(Vector2<float> top, Vector2<float> bottom, float ratio = 4.f);
[[nodiscard]]
std::array<Vector2<float>, 4> as_array() const;
Vector2<float> top_left_corner;
Vector2<float> top_right_corner;
Vector2<float> bottom_left_corner;
Vector2<float> bottom_right_corner;
};
} // namespace omath::hud

View File

@@ -0,0 +1,202 @@
//
// Created by orange on 13.03.2026.
//
#pragma once
#include "canvas_box.hpp"
#include "entity_overlay_widgets.hpp"
#include "hud_renderer_interface.hpp"
#include "omath/linear_algebra/vector2.hpp"
#include "omath/utility/color.hpp"
#include <memory>
#include <string_view>
namespace omath::hud
{
class EntityOverlay final
{
public:
EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom,
const std::shared_ptr<HudRendererInterface>& renderer);
// ── Boxes ────────────────────────────────────────────────────────
EntityOverlay& add_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f},
float thickness = 1.f);
EntityOverlay& add_cornered_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f},
float corner_ratio_len = 0.2f, float thickness = 1.f);
EntityOverlay& add_dashed_box(const Color& color, float dash_len = 8.f, float gap_len = 5.f,
float thickness = 1.f);
// ── Bars ─────────────────────────────────────────────────────────
EntityOverlay& add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width,
float ratio, float offset = 5.f);
EntityOverlay& add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width,
float ratio, float offset = 5.f);
EntityOverlay& add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height,
float ratio, float offset = 5.f);
EntityOverlay& add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color,
float height, float ratio, float offset = 5.f);
EntityOverlay& add_right_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color,
float width, float ratio, float dash_len, float gap_len,
float offset = 5.f);
EntityOverlay& add_left_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color,
float width, float ratio, float dash_len, float gap_len, float offset = 5.f);
EntityOverlay& add_top_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color,
float height, float ratio, float dash_len, float gap_len, float offset = 5.f);
EntityOverlay& add_bottom_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color,
float height, float ratio, float dash_len, float gap_len,
float offset = 5.f);
// ── Labels ───────────────────────────────────────────────────────
EntityOverlay& add_right_label(const Color& color, float offset, bool outlined, const std::string_view& text);
EntityOverlay& add_left_label(const Color& color, float offset, bool outlined, const std::string_view& text);
EntityOverlay& add_top_label(const Color& color, float offset, bool outlined, std::string_view text);
EntityOverlay& add_bottom_label(const Color& color, float offset, bool outlined, std::string_view text);
EntityOverlay& add_centered_top_label(const Color& color, float offset, bool outlined,
const std::string_view& text);
EntityOverlay& add_centered_bottom_label(const Color& color, float offset, bool outlined,
const std::string_view& text);
template<typename... Args>
EntityOverlay& add_right_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
Args&&... args)
{
return add_right_label(color, offset, outlined,
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
}
template<typename... Args>
EntityOverlay& add_left_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
Args&&... args)
{
return add_left_label(color, offset, outlined,
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
}
template<typename... Args>
EntityOverlay& add_top_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
Args&&... args)
{
return add_top_label(color, offset, outlined,
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
}
template<typename... Args>
EntityOverlay& add_bottom_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> fmt, Args&&... args)
{
return add_bottom_label(color, offset, outlined,
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
}
template<typename... Args>
EntityOverlay& add_centered_top_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> fmt, Args&&... args)
{
return add_centered_top_label(color, offset, outlined,
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
}
template<typename... Args>
EntityOverlay& add_centered_bottom_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> fmt, Args&&... args)
{
return add_centered_bottom_label(color, offset, outlined,
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
}
// ── Spacers ─────────────────────────────────────────────────────
EntityOverlay& add_right_space_vertical(float size);
EntityOverlay& add_right_space_horizontal(float size);
EntityOverlay& add_left_space_vertical(float size);
EntityOverlay& add_left_space_horizontal(float size);
EntityOverlay& add_top_space_vertical(float size);
EntityOverlay& add_top_space_horizontal(float size);
EntityOverlay& add_bottom_space_vertical(float size);
EntityOverlay& add_bottom_space_horizontal(float size);
// ── Progress rings ──────────────────────────────────────────────
EntityOverlay& add_right_progress_ring(const Color& color, const Color& bg, float radius, float ratio,
float thickness = 2.f, float offset = 5.f, int segments = 0);
EntityOverlay& add_left_progress_ring(const Color& color, const Color& bg, float radius, float ratio,
float thickness = 2.f, float offset = 5.f, int segments = 0);
EntityOverlay& add_top_progress_ring(const Color& color, const Color& bg, float radius, float ratio,
float thickness = 2.f, float offset = 5.f, int segments = 0);
EntityOverlay& add_bottom_progress_ring(const Color& color, const Color& bg, float radius, float ratio,
float thickness = 2.f, float offset = 5.f, int segments = 0);
// ── Icons ────────────────────────────────────────────────────────
EntityOverlay& add_right_icon(const std::any& texture_id, float width, float height,
const Color& tint = Color{1.f, 1.f, 1.f, 1.f}, float offset = 5.f);
EntityOverlay& add_left_icon(const std::any& texture_id, float width, float height,
const Color& tint = Color{1.f, 1.f, 1.f, 1.f}, float offset = 5.f);
EntityOverlay& add_top_icon(const std::any& texture_id, float width, float height,
const Color& tint = Color{1.f, 1.f, 1.f, 1.f}, float offset = 5.f);
EntityOverlay& add_bottom_icon(const std::any& texture_id, float width, float height,
const Color& tint = Color{1.f, 1.f, 1.f, 1.f}, float offset = 5.f);
// ── Misc ─────────────────────────────────────────────────────────
EntityOverlay& add_snap_line(const Vector2<float>& start_pos, const Color& color, float width);
EntityOverlay& add_skeleton(const Color& color, float thickness = 1.f);
// ── Declarative interface ─────────────────────────────────────────
/// Pass any combination of widget:: descriptor structs (and std::optional<W>
/// from when()) to render them all in declaration order.
template<typename... Widgets>
EntityOverlay& contents(Widgets&&... widgets)
{
(dispatch(std::forward<Widgets>(widgets)), ...);
return *this;
}
private:
// optional<W> dispatch — enables when() conditional widgets
template<typename W>
void dispatch(const std::optional<W>& w)
{
if (w)
dispatch(*w);
}
void dispatch(const widget::Box& box);
void dispatch(const widget::CorneredBox& cornered_box);
void dispatch(const widget::DashedBox& dashed_box);
void dispatch(const widget::RightSide& right_side);
void dispatch(const widget::LeftSide& left_side);
void dispatch(const widget::TopSide& top_side);
void dispatch(const widget::BottomSide& bottom_side);
void dispatch(const widget::Skeleton& skeleton);
void dispatch(const widget::SnapLine& snap_line);
void dispatch(const widget::ScanMarker& scan_marker);
void dispatch(const widget::AimDot& aim_dot);
void dispatch(const widget::ProjectileAim& proj_widget);
void draw_progress_ring(const Vector2<float>& center, const widget::ProgressRing& ring);
void draw_outlined_text(const Vector2<float>& position, const Color& color, const std::string_view& text);
void draw_dashed_line(const Vector2<float>& from, const Vector2<float>& to, const Color& color, float dash_len,
float gap_len, float thickness) const;
void draw_dashed_fill(const Vector2<float>& origin, const Vector2<float>& step_dir,
const Vector2<float>& perp_dir, float full_len, float filled_len, const Color& fill_color,
const Color& split_color, float dash_len, float gap_len) const;
CanvasBox m_canvas;
Vector2<float> m_text_cursor_right;
Vector2<float> m_text_cursor_top;
Vector2<float> m_text_cursor_bottom;
Vector2<float> m_text_cursor_left;
std::shared_ptr<HudRendererInterface> m_renderer;
};
} // namespace omath::hud

View File

@@ -0,0 +1,233 @@
//
// Created by orange on 15.03.2026.
//
#pragma once
#include "omath/linear_algebra/vector2.hpp"
#include "omath/utility/color.hpp"
#include <any>
#include <initializer_list>
#include <optional>
#include <string_view>
#include <variant>
namespace omath::hud::widget
{
// ── Overloaded helper for std::visit ──────────────────────────────────────
template<typename... Ts>
struct Overloaded : Ts...
{
using Ts::operator()...;
};
template<typename... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;
// ── Standalone widgets ────────────────────────────────────────────────────
struct Box
{
Color color;
Color fill{0.f, 0.f, 0.f, 0.f};
float thickness = 1.f;
};
struct CorneredBox
{
Color color;
Color fill{0.f, 0.f, 0.f, 0.f};
float corner_ratio = 0.2f;
float thickness = 1.f;
};
struct DashedBox
{
Color color;
float dash_len = 8.f;
float gap_len = 5.f;
float thickness = 1.f;
};
struct Skeleton
{
Color color;
float thickness = 1.f;
};
struct SnapLine
{
Vector2<float> start;
Color color;
float width;
};
struct ScanMarker
{
Color color;
Color outline{0.f, 0.f, 0.f, 0.f};
float outline_thickness = 1.f;
};
/// Dot at an absolute screen position.
struct AimDot
{
Vector2<float> position;
Color color;
float radius = 3.f;
};
struct ProjectileAim
{
enum class Figure
{
CIRCLE,
SQUARE,
};
Vector2<float> position;
Color color;
float size = 3.f;
float line_size = 1.f;
Figure figure = Figure::SQUARE;
};
// ── Side-agnostic widgets (used inside XxxSide containers) ────────────────
/// A filled bar. `size` is width for left/right sides, height for top/bottom.
struct Bar
{
Color color;
Color outline;
Color bg;
float size;
float ratio;
float offset = 5.f;
};
/// A dashed bar. Same field semantics as Bar plus dash parameters.
struct DashedBar
{
Color color;
Color outline;
Color bg;
float size;
float ratio;
float dash_len;
float gap_len;
float offset = 5.f;
};
struct Label
{
Color color;
float offset;
bool outlined;
std::string_view text;
};
/// Wraps a Label to request horizontal centering (only applied in TopSide / BottomSide).
template<typename W>
struct Centered
{
W child;
};
template<typename W>
Centered(W) -> Centered<W>;
/// Empty vertical gap that advances the Y cursor without drawing.
struct SpaceVertical
{
float size;
};
/// Empty horizontal gap that advances the X cursor without drawing.
struct SpaceHorizontal
{
float size;
};
struct ProgressRing
{
Color color;
Color bg{0.3f, 0.3f, 0.3f, 0.5f};
float radius = 12.f;
float ratio;
float thickness = 2.f;
float offset = 5.f;
int segments = 32;
};
struct Icon
{
std::any texture_id;
float width;
float height;
Color tint{1.f, 1.f, 1.f, 1.f};
float offset = 5.f;
};
// ── Side widget variant ───────────────────────────────────────────────────
struct None
{
}; ///< No-op placeholder — used by widget::when for disabled elements.
using SideWidget =
std::variant<None, Bar, DashedBar, Label, Centered<Label>, SpaceVertical, SpaceHorizontal, ProgressRing, Icon>;
// ── Side containers ───────────────────────────────────────────────────────
// Storing std::initializer_list<SideWidget> is safe here: the backing array
// is a const SideWidget[] on the caller's stack whose lifetime matches the
// temporary side-container object, which is consumed within the same
// full-expression by EntityOverlay::dispatch. No heap allocation occurs.
struct RightSide
{
std::initializer_list<SideWidget> children;
RightSide(const std::initializer_list<SideWidget> c): children(c)
{
}
};
struct LeftSide
{
std::initializer_list<SideWidget> children;
LeftSide(const std::initializer_list<SideWidget> c): children(c)
{
}
};
struct TopSide
{
std::initializer_list<SideWidget> children;
TopSide(const std::initializer_list<SideWidget> c): children(c)
{
}
};
struct BottomSide
{
std::initializer_list<SideWidget> children;
BottomSide(const std::initializer_list<SideWidget> c): children(c)
{
}
};
} // namespace omath::hud::widget
namespace omath::hud::widget
{
/// Inside XxxSide containers: returns the widget as a SideWidget when condition is true,
/// or None{} otherwise. Preferred over hud::when for types inside the SideWidget variant.
template<typename W>
requires std::constructible_from<SideWidget, W>
SideWidget when(const bool condition, W widget)
{
if (condition)
return SideWidget{std::move(widget)};
return None{};
}
} // namespace omath::hud::widget
namespace omath::hud
{
/// Top-level: returns an engaged optional<W> when condition is true, std::nullopt otherwise.
/// Designed for use with EntityOverlay::contents() for top-level widget types.
template<typename W>
std::optional<W> when(const bool condition, W widget)
{
if (condition)
return std::move(widget);
return std::nullopt;
}
} // namespace omath::hud

View File

@@ -0,0 +1,47 @@
//
// Created by orange on 13.03.2026.
//
#pragma once
#include "omath/linear_algebra/vector2.hpp"
#include "omath/utility/color.hpp"
#include <any>
#include <span>
namespace omath::hud
{
class HudRendererInterface
{
public:
virtual ~HudRendererInterface() = default;
virtual void add_line(const Vector2<float>& line_start, const Vector2<float>& line_end, const Color& color,
float thickness) = 0;
virtual void add_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color,
float thickness) = 0;
virtual void add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color) = 0;
virtual void add_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) = 0;
virtual void add_filled_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) = 0;
virtual void add_circle(const Vector2<float>& center, float radius, const Color& color, float thickness,
int segments = 0) = 0;
virtual void add_filled_circle(const Vector2<float>& center, float radius, const Color& color,
int segments = 0) = 0;
/// Draw an arc (partial circle outline). Angles in radians, 0 = right (+X), counter-clockwise.
virtual void add_arc(const Vector2<float>& center, float radius, float a_min, float a_max, const Color& color,
float thickness, int segments = 0) = 0;
/// Draw a textured quad. texture_id is renderer-specific (e.g. ImTextureID for ImGui).
virtual void add_image(const std::any& texture_id, const Vector2<float>& min, const Vector2<float>& max,
const Color& tint = Color{1.f, 1.f, 1.f, 1.f}) = 0;
virtual void add_text(const Vector2<float>& position, const Color& color, const std::string_view& text) = 0;
[[nodiscard]]
virtual Vector2<float> calc_text_size(const std::string_view& text) = 0;
};
} // namespace omath::hud

View File

@@ -0,0 +1,33 @@
//
// Created by orange on 13.03.2026.
//
#pragma once
#include <omath/hud/hud_renderer_interface.hpp>
#ifdef OMATH_IMGUI_INTEGRATION
namespace omath::hud
{
class ImguiHudRenderer final : public HudRendererInterface
{
public:
~ImguiHudRenderer() override;
void add_line(const Vector2<float>& line_start, const Vector2<float>& line_end, const Color& color,
float thickness) override;
void add_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color, float thickness) override;
void add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color) override;
void add_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) override;
void add_filled_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) override;
void add_circle(const Vector2<float>& center, float radius, const Color& color, float thickness,
int segments = 0) override;
void add_filled_circle(const Vector2<float>& center, float radius, const Color& color,
int segments = 0) override;
void add_arc(const Vector2<float>& center, float radius, float a_min, float a_max, const Color& color,
float thickness, int segments = 0) override;
void add_image(const std::any& texture_id, const Vector2<float>& min, const Vector2<float>& max,
const Color& tint = Color{1.f, 1.f, 1.f, 1.f}) override;
void add_text(const Vector2<float>& position, const Color& color, const std::string_view& text) override;
[[nodiscard]]
virtual Vector2<float> calc_text_size(const std::string_view& text) override;
};
} // namespace omath::hud
#endif // OMATH_IMGUI_INTEGRATION

View File

@@ -37,6 +37,12 @@ namespace omath
COLUMN_MAJOR
};
enum class NDCDepthRange : uint8_t
{
NEGATIVE_ONE_TO_ONE = 0, // OpenGL: [-1.0, 1.0]
ZERO_TO_ONE // DirectX / Vulkan: [0.0, 1.0]
};
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);
@@ -658,56 +664,98 @@ namespace omath
} * mat_translation<Type, St>(-camera_origin);
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]]
Mat<4, 4, Type, St> mat_perspective_left_handed(const float field_of_view, const float aspect_ratio,
const float near, const float far) noexcept
{
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f);
return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f},
{0.f, 1.f / fov_half_tan, 0.f, 0.f},
{0.f, 0.f, (far + near) / (far - near), -(2.f * near * far) / (far - near)},
{0.f, 0.f, 1.f, 0.f}};
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f},
{0.f, 1.f / fov_half_tan, 0.f, 0.f},
{0.f, 0.f, far / (far - near), -(near * far) / (far - near)},
{0.f, 0.f, 1.f, 0.f}};
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f},
{0.f, 1.f / fov_half_tan, 0.f, 0.f},
{0.f, 0.f, (far + near) / (far - near), -(2.f * near * far) / (far - near)},
{0.f, 0.f, 1.f, 0.f}};
else
std::unreachable();
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]]
Mat<4, 4, Type, St> mat_perspective_right_handed(const float field_of_view, const float aspect_ratio,
const float near, const float far) noexcept
{
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f);
return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f},
{0.f, 1.f / fov_half_tan, 0.f, 0.f},
{0.f, 0.f, -(far + near) / (far - near), -(2.f * near * far) / (far - near)},
{0.f, 0.f, -1.f, 0.f}};
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f},
{0.f, 1.f / fov_half_tan, 0.f, 0.f},
{0.f, 0.f, -far / (far - near), -(near * far) / (far - near)},
{0.f, 0.f, -1.f, 0.f}};
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f},
{0.f, 1.f / fov_half_tan, 0.f, 0.f},
{0.f, 0.f, -(far + near) / (far - near), -(2.f * near * far) / (far - near)},
{0.f, 0.f, -1.f, 0.f}};
else
std::unreachable();
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]]
Mat<4, 4, Type, St> mat_ortho_left_handed(const Type left, const Type right, const Type bottom, const Type top,
const Type near, const Type far) noexcept
{
return
{
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)},
{ 0.f, 0.f, static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
{ 0.f, 0.f, 0.f, 1.f }
};
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
return
{
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)},
{ 0.f, 0.f, static_cast<Type>(1) / (far - near), -near / (far - near) },
{ 0.f, 0.f, 0.f, 1.f }
};
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return
{
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)},
{ 0.f, 0.f, static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
{ 0.f, 0.f, 0.f, 1.f }
};
else
std::unreachable();
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]]
Mat<4, 4, Type, St> mat_ortho_right_handed(const Type left, const Type right, const Type bottom, const Type top,
const Type near, const Type far) noexcept
{
return
{
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)},
{ 0.f, 0.f, -static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
{ 0.f, 0.f, 0.f, 1.f }
};
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
return
{
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)},
{ 0.f, 0.f, -static_cast<Type>(1) / (far - near), -near / (far - near) },
{ 0.f, 0.f, 0.f, 1.f }
};
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return
{
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)},
{ 0.f, 0.f, -static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
{ 0.f, 0.f, 0.f, 1.f }
};
else
std::unreachable();
}
template<class T = float, MatStoreType St = MatStoreType::COLUMN_MAJOR>
Mat<4, 4, T, St> mat_look_at_left_handed(const Vector3<T>& eye, const Vector3<T>& center, const Vector3<T>& up)

View File

@@ -3,11 +3,23 @@
//
#pragma once
#ifdef OMATH_ENABLE_LUA
// ReSharper disable once CppInconsistentNaming
struct lua_State;
#include <sol/forward.hpp>
namespace omath::lua
{
void register_lib(lua_State* lua_state);
class LuaInterpreter final
{
public:
static void register_lib(lua_State* lua_state);
private:
static void register_vec2(sol::table& omath_table);
static void register_vec3(sol::table& omath_table);
static void register_vec4(sol::table& omath_table);
static void register_color(sol::table& omath_table);
static void register_triangle(sol::table& omath_table);
static void register_shared_types(sol::table& omath_table);
static void register_engines(sol::table& omath_table);
static void register_pattern_scan(sol::table& omath_table);
};
}
#endif

View File

@@ -6,7 +6,9 @@
#include "omath/linear_algebra/vector3.hpp"
#include <expected>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace omath::pathfinding
@@ -28,10 +30,20 @@ namespace omath::pathfinding
[[nodiscard]]
bool empty() const;
[[nodiscard]] std::vector<uint8_t> serialize() const noexcept;
// Events -- per-vertex optional tag (e.g. "jump", "teleport")
void set_event(const Vector3<float>& vertex, const std::string_view& event_id);
void clear_event(const Vector3<float>& vertex);
void deserialize(const std::vector<uint8_t>& raw) noexcept;
[[nodiscard]]
std::optional<std::string> get_event(const Vector3<float>& vertex) const noexcept;
[[nodiscard]] std::string serialize() const noexcept;
void deserialize(const std::string& raw);
std::unordered_map<Vector3<float>, std::vector<Vector3<float>>> m_vertex_map;
private:
std::unordered_map<Vector3<float>, std::string> m_vertex_events;
};
} // namespace omath::pathfinding

View File

@@ -0,0 +1,46 @@
//
// Created by orange on 4/12/2026.
//
#pragma once
#include "navigation_mesh.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <functional>
#include <memory>
namespace omath::pathfinding
{
enum class WalkBotStatus
{
IDLE,
PATHING,
FINISHED
};
class WalkBot
{
public:
WalkBot() = default;
explicit WalkBot(const std::shared_ptr<NavigationMesh>& mesh, float min_node_distance = 1.f);
void set_nav_mesh(const std::shared_ptr<NavigationMesh>& mesh);
void set_min_node_distance(float distance);
void set_target(const Vector3<float>& target);
// Clear navigation state so the bot can be re-routed without stale
// visited-node memory.
void reset();
// Call every game tick with the current bot world position.
void update(const Vector3<float>& bot_position);
void on_path(const std::function<void(const Vector3<float>&)>& callback);
void on_status(const std::function<void(WalkBotStatus)>& callback);
private:
std::weak_ptr<NavigationMesh> m_nav_mesh;
std::optional<std::function<void(const Vector3<float>&)>> m_on_next_path_node;
std::optional<std::function<void(WalkBotStatus)>> m_on_status_update;
std::optional<Vector3<float>> m_last_visited;
std::optional<Vector3<float>> m_target;
float m_min_node_distance{1.f};
};
} // namespace omath::pathfinding

View File

@@ -8,12 +8,23 @@
namespace omath::projectile_prediction
{
struct AimAngles
{
float pitch{};
float yaw{};
};
class ProjPredEngineInterface
{
public:
[[nodiscard]]
virtual std::optional<Vector3<float>> maybe_calculate_aim_point(const Projectile& projectile,
const Target& target) const = 0;
[[nodiscard]]
virtual std::optional<AimAngles> maybe_calculate_aim_angles(const Projectile& projectile,
const Target& target) const = 0;
virtual ~ProjPredEngineInterface() = default;
};
} // namespace omath::projectile_prediction

View File

@@ -12,6 +12,9 @@ namespace omath::projectile_prediction
[[nodiscard]] std::optional<Vector3<float>>
maybe_calculate_aim_point(const Projectile& projectile, const Target& target) const override;
[[nodiscard]] std::optional<AimAngles>
maybe_calculate_aim_angles(const Projectile& projectile, const Target& target) const override;
ProjPredEngineAvx2(float gravity_constant, float simulation_time_step, float maximum_simulation_time);
~ProjPredEngineAvx2() override = default;

View File

@@ -54,6 +54,36 @@ namespace omath::projectile_prediction
[[nodiscard]]
std::optional<Vector3<float>> maybe_calculate_aim_point(const Projectile& projectile,
const Target& target) const override
{
const auto solution = find_solution(projectile, target);
if (!solution)
return std::nullopt;
return EngineTrait::calc_viewpoint_from_angles(projectile, solution->predicted_target_position,
solution->pitch);
}
[[nodiscard]]
std::optional<AimAngles> maybe_calculate_aim_angles(const Projectile& projectile,
const Target& target) const override
{
const auto solution = find_solution(projectile, target);
if (!solution)
return std::nullopt;
const auto yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin + projectile.m_launch_offset, solution->predicted_target_position);
return AimAngles{solution->pitch, yaw};
}
private:
struct Solution
{
Vector3<float> predicted_target_position;
float pitch;
};
[[nodiscard]]
std::optional<Solution> find_solution(const Projectile& projectile, const Target& target) const
{
for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step)
{
@@ -70,12 +100,11 @@ namespace omath::projectile_prediction
time))
continue;
return EngineTrait::calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch);
return Solution{predicted_target_position, projectile_pitch.value()};
}
return std::nullopt;
}
private:
const float m_gravity_constant;
const float m_simulation_time_step;
const float m_maximum_simulation_time;
@@ -100,10 +129,12 @@ namespace omath::projectile_prediction
{
const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
if (bullet_gravity == 0.f)
return EngineTrait::calc_direct_pitch_angle(projectile.m_origin, target_position);
const auto launch_origin = projectile.m_origin + projectile.m_launch_offset;
const auto delta = target_position - projectile.m_origin;
if (bullet_gravity == 0.f)
return EngineTrait::calc_direct_pitch_angle(launch_origin, target_position);
const auto delta = target_position - launch_origin;
const auto distance2d = EngineTrait::calc_vector_2d_distance(delta);
const auto distance2d_sqr = distance2d * distance2d;
@@ -126,7 +157,7 @@ namespace omath::projectile_prediction
bool is_projectile_reached_target(const Vector3<float>& target_position, const Projectile& projectile,
const float pitch, const float time) const noexcept
{
const auto yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin, target_position);
const auto yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin + projectile.m_launch_offset, target_position);
const auto projectile_position =
EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);

View File

@@ -11,6 +11,7 @@ namespace omath::projectile_prediction
{
public:
Vector3<float> m_origin;
Vector3<float> m_launch_offset{0.f, 0.f, 0.f};
float m_launch_speed{};
float m_gravity_scale{};
};

View File

@@ -4,6 +4,7 @@
#pragma once
#include "omath/3d_primitives/aabb.hpp"
#include "omath/linear_algebra/mat.hpp"
#include "omath/linear_algebra/triangle.hpp"
#include "omath/linear_algebra/vector3.hpp"
@@ -36,23 +37,36 @@ namespace omath::projection
}
};
using FieldOfView = Angle<float, 0.f, 180.f, AngleFlags::Clamped>;
enum class ViewPortClipping
{
AUTO,
MANUAL,
};
struct CameraAxes
{
bool inverted_forward = false;
bool inverted_right = false;
};
template<class T, class MatType, class ViewAnglesType>
concept CameraEngineConcept =
requires(const Vector3<float>& cam_origin, const Vector3<float>& look_at, const ViewAnglesType& angles,
const FieldOfView& fov, const ViewPort& viewport, float znear, float zfar) {
const FieldOfView& fov, const ViewPort& viewport, float znear, float zfar,
NDCDepthRange ndc_depth_range) {
// Presence + return types
{ T::calc_look_at_angle(cam_origin, look_at) } -> std::same_as<ViewAnglesType>;
{ T::calc_view_matrix(angles, cam_origin) } -> std::same_as<MatType>;
{ T::calc_projection_matrix(fov, viewport, znear, zfar) } -> std::same_as<MatType>;
{ T::calc_projection_matrix(fov, viewport, znear, zfar, ndc_depth_range) } -> std::same_as<MatType>;
// Enforce noexcept as in the trait declaration
requires noexcept(T::calc_look_at_angle(cam_origin, look_at));
requires noexcept(T::calc_view_matrix(angles, cam_origin));
requires noexcept(T::calc_projection_matrix(fov, viewport, znear, zfar));
requires noexcept(T::calc_projection_matrix(fov, viewport, znear, zfar, ndc_depth_range));
};
template<class Mat4X4Type, class ViewAnglesType, class TraitClass, bool inverted_z = false>
template<class Mat4X4Type, class ViewAnglesType, class TraitClass,
NDCDepthRange depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE,
CameraAxes axes = {}>
requires CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType>
class Camera final
{
@@ -76,20 +90,62 @@ namespace omath::projection
{
}
struct ProjectionParams
{
FieldOfView fov;
float aspect_ratio;
};
// Recovers vertical FOV and aspect ratio from a perspective projection matrix
// built by any of the engine traits. Both variants (ZERO_TO_ONE and
// NEGATIVE_ONE_TO_ONE) share the same m[0,0]/m[1,1] layout, so this works
// regardless of the NDC depth range.
[[nodiscard]]
static ProjectionParams extract_projection_params(const Mat4X4Type& proj_matrix) noexcept
{
// m[1,1] == 1 / tan(fov/2) => fov = 2 * atan(1 / m[1,1])
const float f = proj_matrix.at(1, 1);
// m[0,0] == m[1,1] / aspect_ratio => aspect = m[1,1] / m[0,0]
return {FieldOfView::from_radians(2.f * std::atan(1.f / f)), f / proj_matrix.at(0, 0)};
}
[[nodiscard]]
static ViewAnglesType calc_view_angles_from_view_matrix(const Mat4X4Type& view_matrix) noexcept
{
Vector3<float> forward_vector = {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]};
if constexpr (axes.inverted_forward)
forward_vector = -forward_vector;
return TraitClass::calc_look_at_angle({}, forward_vector);
}
[[nodiscard]]
static Vector3<float> calc_origin_from_view_matrix(const Mat4X4Type& view_matrix) noexcept
{
// The view matrix is R * T(-origin), so the last column stores t = -R * origin.
// Recovering origin: origin = -R^T * t
return {
-(view_matrix[0, 0] * view_matrix[0, 3] + view_matrix[1, 0] * view_matrix[1, 3] + view_matrix[2, 0] * view_matrix[2, 3]),
-(view_matrix[0, 1] * view_matrix[0, 3] + view_matrix[1, 1] * view_matrix[1, 3] + view_matrix[2, 1] * view_matrix[2, 3]),
-(view_matrix[0, 2] * view_matrix[0, 3] + view_matrix[1, 2] * view_matrix[1, 3] + view_matrix[2, 2] * view_matrix[2, 3]),
};
}
void look_at(const Vector3<float>& target)
{
m_view_angles = TraitClass::calc_look_at_angle(m_origin, target);
m_view_projection_matrix = std::nullopt;
m_view_matrix = std::nullopt;
}
[[nodiscard]]
ViewAnglesType calc_look_at_angles(const Vector3<float>& look_to) const
{
return TraitClass::calc_look_at_angle(m_origin, look_to);
}
[[nodiscard]]
Vector3<float> get_forward() const noexcept
{
const auto& view_matrix = get_view_matrix();
if constexpr (inverted_z)
return -Vector3<float>{view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]};
return {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]};
}
@@ -106,6 +162,27 @@ namespace omath::projection
const auto& view_matrix = get_view_matrix();
return {view_matrix[1, 0], view_matrix[1, 1], view_matrix[1, 2]};
}
[[nodiscard]]
Vector3<float> get_abs_forward() const noexcept
{
if constexpr (axes.inverted_forward)
return -get_forward();
return get_forward();
}
[[nodiscard]]
Vector3<float> get_abs_right() const noexcept
{
if constexpr (axes.inverted_right)
return -get_right();
return get_right();
}
[[nodiscard]]
Vector3<float> get_abs_up() const noexcept
{
return get_up();
}
[[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const noexcept
{
@@ -126,7 +203,8 @@ namespace omath::projection
{
if (!m_projection_matrix.has_value())
m_projection_matrix = TraitClass::calc_projection_matrix(m_field_of_view, m_view_port,
m_near_plane_distance, m_far_plane_distance);
m_near_plane_distance, m_far_plane_distance,
depth_range);
return m_projection_matrix.value();
}
@@ -138,16 +216,16 @@ namespace omath::projection
m_projection_matrix = std::nullopt;
}
void set_near_plane(const float near) noexcept
void set_near_plane(const float near_plane) noexcept
{
m_near_plane_distance = near;
m_near_plane_distance = near_plane;
m_view_projection_matrix = std::nullopt;
m_projection_matrix = std::nullopt;
}
void set_far_plane(const float far) noexcept
void set_far_plane(const float far_plane) noexcept
{
m_far_plane_distance = far;
m_far_plane_distance = far_plane;
m_view_projection_matrix = std::nullopt;
m_projection_matrix = std::nullopt;
}
@@ -213,6 +291,22 @@ namespace omath::projection
else
std::unreachable();
}
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
[[nodiscard]] std::expected<Vector3<float>, Error>
world_to_screen_unclipped(const Vector3<float>& world_position) const noexcept
{
const auto normalized_cords = world_to_view_port(world_position, ViewPortClipping::MANUAL);
if (!normalized_cords.has_value())
return std::unexpected{normalized_cords.error()};
if constexpr (screen_start == ScreenStart::TOP_LEFT_CORNER)
return ndc_to_screen_position_from_top_left_corner(*normalized_cords);
else if constexpr (screen_start == ScreenStart::BOTTOM_LEFT_CORNER)
return ndc_to_screen_position_from_bottom_left_corner(*normalized_cords);
else
std::unreachable();
}
[[nodiscard]] bool is_culled_by_frustum(const Triangle<Vector3<float>>& triangle) const noexcept
{
@@ -246,40 +340,127 @@ namespace omath::projection
return a[axis] < -a[3] && b[axis] < -b[3] && c[axis] < -c[3];
};
// Clip volume in clip space (OpenGL-style):
// Clip volume in clip space:
// -w <= x <= w
// -w <= y <= w
// -w <= z <= w
// z_min <= z <= w (z_min = -w for [-1,1], 0 for [0,1])
for (int i = 0; i < 3; i++)
// x and y planes
for (int i = 0; i < 2; i++)
{
if (all_outside_plane(i, c0, c1, c2, false))
return true; // x < -w (left)
return true;
if (all_outside_plane(i, c0, c1, c2, true))
return true; // x > w (right)
return true;
}
// z far plane: z > w
if (all_outside_plane(2, c0, c1, c2, true))
return true;
// z near plane
if constexpr (depth_range == NDCDepthRange::ZERO_TO_ONE)
{
// 0 <= z, so reject if z < 0 for all vertices
if (c0[2] < 0.f && c1[2] < 0.f && c2[2] < 0.f)
return true;
}
else
{
// -w <= z
if (all_outside_plane(2, c0, c1, c2, false))
return true;
}
return false;
}
[[nodiscard]] bool is_aabb_culled_by_frustum(const primitives::Aabb<float>& aabb) const noexcept
{
const auto& m = get_view_projection_matrix();
// Gribb-Hartmann: extract 6 frustum planes from the view-projection matrix.
// Each plane is (a, b, c, d) such that ax + by + cz + d >= 0 means inside.
// For a 4x4 matrix with rows r0..r3:
// Left = r3 + r0
// Right = r3 - r0
// Bottom = r3 + r1
// Top = r3 - r1
// Near = r3 + r2 ([-1,1]) or r2 ([0,1])
// Far = r3 - r2
struct Plane final
{
float a, b, c, d;
};
const auto extract_plane = [&m](const int sign, const int row) -> Plane
{
return {
m.at(3, 0) + static_cast<float>(sign) * m.at(row, 0),
m.at(3, 1) + static_cast<float>(sign) * m.at(row, 1),
m.at(3, 2) + static_cast<float>(sign) * m.at(row, 2),
m.at(3, 3) + static_cast<float>(sign) * m.at(row, 3),
};
};
std::array<Plane, 6> planes = {
extract_plane(1, 0), // left
extract_plane(-1, 0), // right
extract_plane(1, 1), // bottom
extract_plane(-1, 1), // top
extract_plane(-1, 2), // far
};
// Near plane depends on NDC depth range
if constexpr (depth_range == NDCDepthRange::ZERO_TO_ONE)
planes[5] = {m.at(2, 0), m.at(2, 1), m.at(2, 2), m.at(2, 3)};
else
planes[5] = extract_plane(1, 2);
// For each plane, find the AABB corner most in the direction of the plane normal
// (the "positive vertex"). If it's outside, the entire AABB is outside.
for (const auto& [a, b, c, d] : planes)
{
const float px = a >= 0.f ? aabb.max.x : aabb.min.x;
const float py = b >= 0.f ? aabb.max.y : aabb.min.y;
const float pz = c >= 0.f ? aabb.max.z : aabb.min.z;
if (a * px + b * py + c * pz + d < 0.f)
return true;
}
return false;
}
[[nodiscard]] std::expected<Vector3<float>, Error>
world_to_view_port(const Vector3<float>& world_position) const noexcept
world_to_view_port(const Vector3<float>& world_position,
const ViewPortClipping& clipping = ViewPortClipping::AUTO) const noexcept
{
auto projected = get_view_projection_matrix()
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(world_position);
const auto& w = projected.at(3, 0);
if (w <= std::numeric_limits<float>::epsilon())
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
constexpr auto eps = std::numeric_limits<float>::epsilon();
if (w <= eps)
return std::unexpected(Error::PERSPECTIVE_DIVIDER_LESS_EQ_ZERO);
projected /= w;
if (is_ndc_out_of_bounds(projected))
// ReSharper disable once CppTooWideScope
const auto clipped_automatically = clipping == ViewPortClipping::AUTO && is_ndc_out_of_bounds(projected);
if (clipped_automatically)
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
// ReSharper disable once CppTooWideScope
constexpr auto z_min = depth_range == NDCDepthRange::ZERO_TO_ONE ? 0.0f : -1.0f;
const auto clipped_manually = clipping == ViewPortClipping::MANUAL && (projected.at(2, 0) < z_min - eps
|| projected.at(2, 0) > 1.0f + eps);
if (clipped_manually)
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
return Vector3<float>{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)};
}
[[nodiscard]]
std::expected<Vector3<float>, Error> view_port_to_screen(const Vector3<float>& ndc) const noexcept
std::expected<Vector3<float>, Error> view_port_to_world(const Vector3<float>& ndc) const noexcept
{
const auto inv_view_proj = get_view_projection_matrix().inverted();
@@ -304,7 +485,7 @@ namespace omath::projection
[[nodiscard]]
std::expected<Vector3<float>, Error> screen_to_world(const Vector3<float>& screen_pos) const noexcept
{
return view_port_to_screen(screen_to_ndc<screen_start>(screen_pos));
return view_port_to_world(screen_to_ndc<screen_start>(screen_pos));
}
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
@@ -333,8 +514,26 @@ namespace omath::projection
[[nodiscard]] constexpr static bool is_ndc_out_of_bounds(const Type& ndc) noexcept
{
constexpr auto eps = std::numeric_limits<float>::epsilon();
return std::ranges::any_of(ndc.raw_array(),
[](const auto& val) { return val < -1.0f - eps || val > 1.0f + eps; });
const auto& data = ndc.raw_array();
// x and y are always in [-1, 1]
if (data[0] < -1.0f - eps || data[0] > 1.0f + eps)
return true;
if (data[1] < -1.0f - eps || data[1] > 1.0f + eps)
return true;
return is_ndc_z_value_out_of_bounds(data[2]);
}
template<class ZType>
[[nodiscard]]
constexpr static bool is_ndc_z_value_out_of_bounds(const ZType& z_ndc) noexcept
{
constexpr auto eps = std::numeric_limits<float>::epsilon();
if constexpr (depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return z_ndc < -1.0f - eps || z_ndc > 1.0f + eps;
if constexpr (depth_range == NDCDepthRange::ZERO_TO_ONE)
return z_ndc < 0.0f - eps || z_ndc > 1.0f + eps;
std::unreachable();
}
// NDC REPRESENTATION:

View File

@@ -11,5 +11,6 @@ namespace omath::projection
{
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
PERSPECTIVE_DIVIDER_LESS_EQ_ZERO,
};
}

View File

@@ -3,11 +3,43 @@
//
#pragma once
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <string_view>
#ifdef _WIN32
#include "omath/utility/pe_pattern_scan.hpp"
#include <windows.h>
#elif defined(__APPLE__)
#include "omath/utility/macho_pattern_scan.hpp"
#include <mach-o/dyld.h>
#else
#include "omath/utility/elf_pattern_scan.hpp"
#include <link.h>
#endif
namespace omath::rev_eng
{
template<std::size_t N>
struct FixedString final
{
char data[N]{};
// ReSharper disable once CppNonExplicitConvertingConstructor
constexpr FixedString(const char (&str)[N]) noexcept // NOLINT(*-explicit-constructor)
{
for (std::size_t i = 0; i < N; ++i)
data[i] = str[i];
}
// ReSharper disable once CppNonExplicitConversionOperator
constexpr operator std::string_view() const noexcept // NOLINT(*-explicit-constructor)
{
return {data, N - 1};
}
};
template<std::size_t N>
FixedString(const char (&)[N]) -> FixedString<N>;
class InternalReverseEngineeredObject
{
protected:
@@ -23,26 +55,150 @@ namespace omath::rev_eng
return *reinterpret_cast<Type*>(reinterpret_cast<std::uintptr_t>(this) + offset);
}
template<std::size_t id, class ReturnType>
template<class ReturnType>
ReturnType call_method(const void* ptr, auto... arg_list)
{
#ifdef _MSC_VER
using MethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
#else
using MethodType = ReturnType (*)(void*, decltype(arg_list)...);
#endif
return reinterpret_cast<MethodType>(const_cast<void*>(ptr))(this, arg_list...);
}
template<class ReturnType>
ReturnType call_method(const void* ptr, auto... arg_list) const
{
#ifdef _MSC_VER
using MethodType = ReturnType(__thiscall*)(const void*, decltype(arg_list)...);
#else
using MethodType = ReturnType (*)(const void*, decltype(arg_list)...);
#endif
return reinterpret_cast<MethodType>(const_cast<void*>(ptr))(this, arg_list...);
}
template<FixedString ModuleName, FixedString Pattern, class ReturnType>
ReturnType call_method(auto... arg_list)
{
static const auto* address = resolve_pattern(ModuleName, Pattern);
return call_method<ReturnType>(address, arg_list...);
}
template<FixedString ModuleName, FixedString Pattern, class ReturnType>
ReturnType call_method(auto... arg_list) const
{
static const auto* address = resolve_pattern(ModuleName, Pattern);
return call_method<ReturnType>(address, arg_list...);
}
template<class ReturnType>
ReturnType call_method(const std::string_view& module_name,const std::string_view& pattern, auto... arg_list)
{
static const auto* address = resolve_pattern(module_name, pattern);
return call_method<ReturnType>(address, arg_list...);
}
template<class ReturnType>
ReturnType call_method(const std::string_view& module_name,const std::string_view& pattern, auto... arg_list) const
{
static const auto* address = resolve_pattern(module_name, pattern);
return call_method<ReturnType>(address, arg_list...);
}
template<std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list)
{
#ifdef _MSC_VER
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
#else
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
#endif
return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, arg_list...);
const auto vtable = *reinterpret_cast<void***>(this);
return call_method<ReturnType>(vtable[Id], arg_list...);
}
template<std::size_t id, class ReturnType>
template<std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list) const
{
const auto vtable = *reinterpret_cast<void* const* const*>(this);
return call_method<ReturnType>(vtable[Id], arg_list...);
}
template<std::ptrdiff_t TableOffset, std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list)
{
auto sub_this = reinterpret_cast<void*>(
reinterpret_cast<std::uintptr_t>(this) + TableOffset);
const auto vtable = *reinterpret_cast<void***>(sub_this);
#ifdef _MSC_VER
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
using Fn = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
#else
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
using Fn = ReturnType(*)(void*, decltype(arg_list)...);
#endif
return reinterpret_cast<Fn>(vtable[Id])(sub_this, arg_list...);
}
template<std::ptrdiff_t TableOffset, std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list) const
{
auto sub_this = reinterpret_cast<const void*>(
reinterpret_cast<std::uintptr_t>(this) + TableOffset);
const auto vtable = *reinterpret_cast<void* const* const*>(sub_this);
#ifdef _MSC_VER
using Fn = ReturnType(__thiscall*)(const void*, decltype(arg_list)...);
#else
using Fn = ReturnType(*)(const void*, decltype(arg_list)...);
#endif
return reinterpret_cast<Fn>(vtable[Id])(sub_this, arg_list...);
}
private:
[[nodiscard]]
static const void* resolve_pattern(const std::string_view module_name, const std::string_view pattern)
{
const auto* base = get_module_base(module_name);
assert(base && "Failed to find module");
#ifdef _WIN32
const auto result = PePatternScanner::scan_for_pattern_in_loaded_module(base, pattern);
#elif defined(__APPLE__)
const auto result = MachOPatternScanner::scan_for_pattern_in_loaded_module(base, pattern);
#else
const auto result = ElfPatternScanner::scan_for_pattern_in_loaded_module(base, pattern);
#endif
assert(result.has_value() && "Pattern scan failed");
return reinterpret_cast<const void*>(*result);
}
[[nodiscard]]
static const void* get_module_base(const std::string_view module_name)
{
#ifdef _WIN32
return GetModuleHandleA(module_name.data());
#elif defined(__APPLE__)
// On macOS, iterate loaded images to find the module by name
const auto count = _dyld_image_count();
for (std::uint32_t i = 0; i < count; ++i)
{
const auto* name = _dyld_get_image_name(i);
if (name && std::string_view{name}.find(module_name) != std::string_view::npos)
return static_cast<const void*>(_dyld_get_image_header(i));
}
return nullptr;
#else
// On Linux, use dl_iterate_phdr to find loaded module by name
struct CallbackData
{
std::string_view name;
const void* base;
} cb_data{module_name, nullptr};
dl_iterate_phdr(
[](dl_phdr_info* info, std::size_t, void* data) -> int
{
auto* cb = static_cast<CallbackData*>(data);
if (info->dlpi_name
&& std::string_view{info->dlpi_name}.find(cb->name) != std::string_view::npos)
{
cb->base = reinterpret_cast<const void*>(info->dlpi_addr);
return 1;
}
return 0;
},
&cb_data);
return cb_data.base;
#endif
return (*static_cast<VirtualMethodType**>((void*)(this)))[id](
const_cast<void*>(static_cast<const void*>(this)), arg_list...);
}
};
} // namespace omath::rev_eng

View File

@@ -36,6 +36,7 @@ namespace omath
}
public:
using ArithmeticType = Type;
[[nodiscard]]
constexpr static Angle from_degrees(const Type& degrees) noexcept
{

View File

@@ -2,14 +2,25 @@
// Created by Orange on 11/30/2024.
//
#pragma once
#include "omath/linear_algebra/vector3.hpp"
#include <type_traits>
namespace omath
{
template<class PitchType, class YawType, class RollType>
requires std::is_same_v<typename PitchType::ArithmeticType, typename YawType::ArithmeticType>
&& std::is_same_v<typename YawType::ArithmeticType, typename RollType::ArithmeticType>
struct ViewAngles
{
using ArithmeticType = PitchType::ArithmeticType;
PitchType pitch;
YawType yaw;
RollType roll;
[[nodiscard]]
Vector3<ArithmeticType> as_vector3() const
{
return {pitch.as_degrees(), yaw.as_degrees(), roll.as_degrees()};
}
};
} // namespace omath

View File

@@ -5,6 +5,7 @@
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
@@ -21,5 +22,10 @@ namespace omath
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
};
} // namespace omath

View File

@@ -5,6 +5,7 @@
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
@@ -21,5 +22,10 @@ namespace omath
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
};
} // namespace omath

View File

@@ -6,6 +6,7 @@
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
@@ -23,5 +24,10 @@ namespace omath
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
};
} // namespace omath

View File

@@ -16,15 +16,42 @@ echo "[*] Output dir: ${OUTPUT_DIR}"
# Find llvm tools - handle versioned names (Linux) and xcrun (macOS)
find_llvm_tool() {
local tool_name="$1"
# macOS: use xcrun
# First priority: derive from the actual compiler used by cmake (CMakeCache.txt).
# This guarantees the profraw format version matches the instrumented binary.
local cache_file="${BINARY_DIR}/CMakeCache.txt"
if [[ -f "$cache_file" ]]; then
local cmake_cxx
cmake_cxx=$(grep '^CMAKE_CXX_COMPILER:' "$cache_file" | cut -d= -f2)
if [[ -n "$cmake_cxx" && -x "$cmake_cxx" ]]; then
local tool_path
tool_path="$(dirname "$cmake_cxx")/${tool_name}"
if [[ -x "$tool_path" ]]; then
echo "$tool_path"
return 0
fi
fi
fi
# macOS: derive from xcrun clang as fallback
if [[ "$(uname)" == "Darwin" ]]; then
local clang_path
clang_path=$(xcrun --find clang 2>/dev/null)
if [[ -n "$clang_path" ]]; then
local tool_path
tool_path="$(dirname "$clang_path")/${tool_name}"
if [[ -x "$tool_path" ]]; then
echo "$tool_path"
return 0
fi
fi
# Fallback: xcrun
if xcrun --find "${tool_name}" &>/dev/null; then
echo "xcrun ${tool_name}"
return 0
fi
fi
# Try versioned names (Linux with LLVM 21, 20, 19, etc.)
for version in 21 20 19 18 17 ""; do
local versioned_name="${tool_name}${version:+-$version}"
@@ -33,7 +60,7 @@ find_llvm_tool() {
return 0
fi
done
echo ""
return 1
}
@@ -51,6 +78,18 @@ fi
echo "[*] Using: ${LLVM_PROFDATA}"
echo "[*] Using: ${LLVM_COV}"
# Print version info for debugging version mismatches
if [[ "$(uname)" == "Darwin" ]]; then
echo "[*] Default clang: $(xcrun clang --version 2>&1 | head -1)"
# Show actual compiler used by the build (from CMakeCache.txt if available)
CACHE_FILE="${BINARY_DIR}/CMakeCache.txt"
if [[ -f "$CACHE_FILE" ]]; then
ACTUAL_CXX=$(grep '^CMAKE_CXX_COMPILER:' "$CACHE_FILE" | cut -d= -f2)
echo "[*] Build compiler: ${ACTUAL_CXX} ($(${ACTUAL_CXX} --version 2>&1 | head -1))"
fi
echo "[*] profdata: $(${LLVM_PROFDATA} show --version 2>&1 | head -1 || true)"
fi
# Find test binary
if [[ -z "${TEST_BINARY}" ]]; then
for path in \

View File

@@ -35,8 +35,15 @@ namespace omath::cry_engine
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return mat_perspective_left_handed(field_of_view, aspect_ratio, near, far);
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near, far);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
field_of_view, aspect_ratio, near, far);
std::unreachable();
}
} // namespace omath::unity_engine

View File

@@ -19,8 +19,9 @@ namespace omath::cry_engine
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far);
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::unity_engine

View File

@@ -35,8 +35,16 @@ namespace omath::frostbite_engine
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return mat_perspective_left_handed(field_of_view, aspect_ratio, near, far);
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near, far);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
field_of_view, aspect_ratio, near, far);
std::unreachable();
}
} // namespace omath::unity_engine

View File

@@ -19,8 +19,9 @@ namespace omath::frostbite_engine
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far);
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::unity_engine

View File

@@ -36,18 +36,27 @@ namespace omath::iw_engine
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
// NOTE: Need magic number to fix fov calculation, since IW engine inherit Quake proj matrix calculation
constexpr auto k_multiply_factor = 0.75f;
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f) * k_multiply_factor;
return {
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
{0, 1.f / (fov_half_tan), 0, 0},
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
{0, 0, 1, 0},
};
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return {
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
{0, 1.f / (fov_half_tan), 0, 0},
{0, 0, far / (far - near), -(near * far) / (far - near)},
{0, 0, 1, 0},
};
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return {
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
{0, 1.f / (fov_half_tan), 0, 0},
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
{0, 0, 1, 0},
};
std::unreachable();
};
} // namespace omath::iw_engine

View File

@@ -19,8 +19,9 @@ namespace omath::iw_engine
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far);
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::iw_engine

View File

@@ -8,15 +8,15 @@ namespace omath::opengl_engine
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
const auto vec
= rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_forward);
const auto vec =
rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_forward);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
const auto vec
= rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_right);
const auto vec =
rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_right);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
@@ -28,7 +28,7 @@ namespace omath::opengl_engine
}
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_look_at_right_handed(cam_origin, cam_origin+forward_vector(angles), up_vector(angles));
return mat_look_at_right_handed(cam_origin, cam_origin + forward_vector(angles), up_vector(angles));
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
@@ -37,15 +37,16 @@ namespace omath::opengl_engine
* mat_rotation_axis_x<float, MatStoreType::COLUMN_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_right_handed<float, MatStoreType::COLUMN_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
field_of_view, aspect_ratio, near, far);
return {
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
{0, 1.f / (fov_half_tan), 0, 0},
{0, 0, -(far + near) / (far - near), -(2.f * far * near) / (far - near)},
{0, 0, -1, 0},
};
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_right_handed<float, MatStoreType::COLUMN_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near, far);
std::unreachable();
}
} // namespace omath::opengl_engine

View File

@@ -20,8 +20,9 @@ namespace omath::opengl_engine
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far);
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::opengl_engine

View File

@@ -36,18 +36,27 @@ namespace omath::source_engine
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
// NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation
constexpr auto k_multiply_factor = 0.75f;
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f) * k_multiply_factor;
return {
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
{0, 1.f / (fov_half_tan), 0, 0},
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
{0, 0, 1, 0},
};
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return {
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
{0, 1.f / (fov_half_tan), 0, 0},
{0, 0, far / (far - near), -(near * far) / (far - near)},
{0, 0, 1, 0},
};
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return {
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
{0, 1.f / (fov_half_tan), 0, 0},
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
{0, 0, 1, 0},
};
std::unreachable();
}
} // namespace omath::source_engine

View File

@@ -20,8 +20,9 @@ namespace omath::source_engine
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far);
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::source_engine

View File

@@ -35,8 +35,15 @@ namespace omath::unity_engine
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return omath::mat_perspective_right_handed(field_of_view, aspect_ratio, near, far);
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return omath::mat_perspective_right_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near, far);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return omath::mat_perspective_right_handed<float, MatStoreType::ROW_MAJOR,
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(field_of_view, aspect_ratio,
near, far);
std::unreachable();
}
} // namespace omath::unity_engine

View File

@@ -19,8 +19,9 @@ namespace omath::unity_engine
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far);
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::unity_engine

View File

@@ -35,8 +35,12 @@ namespace omath::unreal_engine
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near, far);
return mat_perspective_left_handed(field_of_view, aspect_ratio, near, far);
}
} // namespace omath::unreal_engine

View File

@@ -19,8 +19,9 @@ namespace omath::unreal_engine
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far) noexcept
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far);
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::unreal_engine

27
source/hud/canvas_box.cpp Normal file
View File

@@ -0,0 +1,27 @@
//
// Created by orange on 13.03.2026.
//
//
// Created by Vlad on 6/17/2025.
//
#include "omath/hud/canvas_box.hpp"
namespace omath::hud
{
CanvasBox::CanvasBox(const Vector2<float> top, Vector2<float> bottom, const float ratio)
{
bottom.x = top.x;
const auto height = std::abs(top.y - bottom.y);
top_left_corner = top - Vector2<float>{height / ratio, 0};
top_right_corner = top + Vector2<float>{height / ratio, 0};
bottom_left_corner = bottom - Vector2<float>{height / ratio, 0};
bottom_right_corner = bottom + Vector2<float>{height / ratio, 0};
}
std::array<Vector2<float>, 4> CanvasBox::as_array() const
{
return {top_left_corner, top_right_corner, bottom_right_corner, bottom_left_corner};
}
} // namespace ohud

View File

@@ -0,0 +1,870 @@
//
// Created by orange on 13.03.2026.
//
#include "omath/hud/entity_overlay.hpp"
namespace omath::hud
{
EntityOverlay& EntityOverlay::add_2d_box(const Color& box_color, const Color& fill_color, const float thickness)
{
const auto points = m_canvas.as_array();
m_renderer->add_polyline({points.data(), points.size()}, box_color, thickness);
if (fill_color.value().w > 0.f)
m_renderer->add_filled_polyline({points.data(), points.size()}, fill_color);
return *this;
}
EntityOverlay& EntityOverlay::add_cornered_2d_box(const Color& box_color, const Color& fill_color,
const float corner_ratio_len, const float thickness)
{
const auto corner_line_length =
std::abs((m_canvas.top_left_corner - m_canvas.top_right_corner).x * corner_ratio_len);
if (fill_color.value().w > 0.f)
add_2d_box(fill_color, fill_color);
// Left Side
m_renderer->add_line(m_canvas.top_left_corner,
m_canvas.top_left_corner + Vector2<float>{corner_line_length, 0.f}, box_color, thickness);
m_renderer->add_line(m_canvas.top_left_corner,
m_canvas.top_left_corner + Vector2<float>{0.f, corner_line_length}, box_color, thickness);
m_renderer->add_line(m_canvas.bottom_left_corner,
m_canvas.bottom_left_corner - Vector2<float>{0.f, corner_line_length}, box_color,
thickness);
m_renderer->add_line(m_canvas.bottom_left_corner,
m_canvas.bottom_left_corner + Vector2<float>{corner_line_length, 0.f}, box_color,
thickness);
// Right Side
m_renderer->add_line(m_canvas.top_right_corner,
m_canvas.top_right_corner - Vector2<float>{corner_line_length, 0.f}, box_color, thickness);
m_renderer->add_line(m_canvas.top_right_corner,
m_canvas.top_right_corner + Vector2<float>{0.f, corner_line_length}, box_color, thickness);
m_renderer->add_line(m_canvas.bottom_right_corner,
m_canvas.bottom_right_corner - Vector2<float>{0.f, corner_line_length}, box_color,
thickness);
m_renderer->add_line(m_canvas.bottom_right_corner,
m_canvas.bottom_right_corner - Vector2<float>{corner_line_length, 0.f}, box_color,
thickness);
return *this;
}
EntityOverlay& EntityOverlay::add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color,
const float width, float ratio, const float offset)
{
ratio = std::clamp(ratio, 0.f, 1.f);
const auto max_bar_height = std::abs(m_canvas.top_right_corner.y - m_canvas.bottom_right_corner.y);
const auto bar_start = Vector2<float>{m_text_cursor_right.x + offset, m_canvas.bottom_right_corner.y};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(width, -max_bar_height), bg_color);
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(width, -max_bar_height * ratio), color);
m_renderer->add_rectangle(bar_start - Vector2<float>(1.f, 0.f),
bar_start + Vector2<float>(width, -max_bar_height), outline_color);
m_text_cursor_right.x += offset + width;
return *this;
}
EntityOverlay& EntityOverlay::add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color,
const float width, float ratio, const float offset)
{
ratio = std::clamp(ratio, 0.f, 1.f);
const auto max_bar_height = std::abs(m_canvas.top_left_corner.y - m_canvas.bottom_right_corner.y);
const auto bar_start = Vector2<float>{m_text_cursor_left.x - (offset + width), m_canvas.bottom_left_corner.y};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(width, -max_bar_height), bg_color);
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(width, -max_bar_height * ratio), color);
m_renderer->add_rectangle(bar_start - Vector2<float>(1.f, 0.f),
bar_start + Vector2<float>(width, -max_bar_height), outline_color);
m_text_cursor_left.x -= offset + width;
return *this;
}
EntityOverlay& EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text)
{
if (outlined)
draw_outlined_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text);
else
m_renderer->add_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text.data());
m_text_cursor_right.y += m_renderer->calc_text_size(text.data()).y;
return *this;
}
EntityOverlay& EntityOverlay::add_top_label(const Color& color, const float offset, const bool outlined,
const std::string_view text)
{
m_text_cursor_top.y -= m_renderer->calc_text_size(text.data()).y;
if (outlined)
draw_outlined_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text);
else
m_renderer->add_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text.data());
return *this;
}
EntityOverlay& EntityOverlay::add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color,
const float height, float ratio, const float offset)
{
ratio = std::clamp(ratio, 0.f, 1.f);
const auto max_bar_width = std::abs(m_canvas.top_left_corner.x - m_canvas.bottom_right_corner.x);
const auto bar_start = Vector2<float>{m_canvas.top_left_corner.x, m_text_cursor_top.y - offset};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, -height), bg_color);
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width * ratio, -height), color);
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, -height), outline_color);
m_text_cursor_top.y -= offset + height;
return *this;
}
EntityOverlay& EntityOverlay::add_snap_line(const Vector2<float>& start_pos, const Color& color, const float width)
{
const Vector2<float> line_end =
m_canvas.bottom_left_corner
+ Vector2<float>{m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x, 0.f} / 2;
m_renderer->add_line(start_pos, line_end, color, width);
return *this;
}
void EntityOverlay::draw_dashed_fill(const Vector2<float>& origin, const Vector2<float>& step_dir,
const Vector2<float>& perp_dir, const float full_len, const float filled_len,
const Color& fill_color, const Color& split_color, const float dash_len,
const float gap_len) const
{
if (full_len <= 0.f)
return;
const float step = dash_len + gap_len;
const float n = std::floor((full_len + gap_len) / step);
if (n < 1.f)
return;
const float used = n * dash_len + (n - 1.f) * gap_len;
const float offset = (full_len - used) / 2.f;
const auto fill_rect = [&](const Vector2<float>& a, const Vector2<float>& b, const Color& c)
{
m_renderer->add_filled_rectangle({std::min(a.x, b.x), std::min(a.y, b.y)},
{std::max(a.x, b.x), std::max(a.y, b.y)}, c);
};
// Draw split lines (gaps) across the full bar first
// Leading gap
if (offset > 0.f)
fill_rect(origin, origin + step_dir * offset + perp_dir, split_color);
for (float i = 0.f; i < n; ++i)
{
const float dash_start = offset + i * step;
const float dash_end = dash_start + dash_len;
const float gap_start = dash_end;
const float gap_end = dash_start + step;
// Fill dash only up to filled_len
if (dash_start < filled_len)
{
const auto a = origin + step_dir * dash_start;
const auto b = a + step_dir * std::min(dash_len, filled_len - dash_start) + perp_dir;
fill_rect(a, b, fill_color);
}
// Split line (gap) — always drawn across full bar
if (i < n - 1.f && gap_start < full_len)
{
const auto a = origin + step_dir * gap_start;
const auto b = origin + step_dir * std::min(gap_end, full_len) + perp_dir;
fill_rect(a, b, split_color);
}
}
// Trailing gap
const float trail_start = offset + n * dash_len + (n - 1.f) * gap_len;
if (trail_start < full_len)
fill_rect(origin + step_dir * trail_start, origin + step_dir * full_len + perp_dir, split_color);
}
EntityOverlay& EntityOverlay::add_right_dashed_bar(const Color& color, const Color& outline_color,
const Color& bg_color, const float width, float ratio,
const float dash_len, const float gap_len, const float offset)
{
ratio = std::clamp(ratio, 0.f, 1.f);
const float height = std::abs(m_canvas.top_right_corner.y - m_canvas.bottom_right_corner.y);
const auto bar_start = Vector2<float>{m_text_cursor_right.x + offset, m_canvas.bottom_right_corner.y};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{width, -height}, bg_color);
draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, color, outline_color, dash_len,
gap_len);
m_renderer->add_rectangle(bar_start - Vector2<float>{1.f, 0.f}, bar_start + Vector2<float>{width, -height},
outline_color);
m_text_cursor_right.x += offset + width;
return *this;
}
EntityOverlay& EntityOverlay::add_left_dashed_bar(const Color& color, const Color& outline_color,
const Color& bg_color, const float width, float ratio,
const float dash_len, const float gap_len, const float offset)
{
ratio = std::clamp(ratio, 0.f, 1.f);
const float height = std::abs(m_canvas.top_left_corner.y - m_canvas.bottom_left_corner.y);
const auto bar_start = Vector2<float>{m_text_cursor_left.x - (offset + width), m_canvas.bottom_left_corner.y};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{width, -height}, bg_color);
draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, color, outline_color, dash_len,
gap_len);
m_renderer->add_rectangle(bar_start - Vector2<float>{1.f, 0.f}, bar_start + Vector2<float>{width, -height},
outline_color);
m_text_cursor_left.x -= offset + width;
return *this;
}
EntityOverlay& EntityOverlay::add_top_dashed_bar(const Color& color, const Color& outline_color,
const Color& bg_color, const float height, float ratio,
const float dash_len, const float gap_len, const float offset)
{
ratio = std::clamp(ratio, 0.f, 1.f);
const float bar_w = std::abs(m_canvas.top_left_corner.x - m_canvas.top_right_corner.x);
const auto bar_start = Vector2<float>{m_canvas.top_left_corner.x, m_text_cursor_top.y - offset};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{bar_w, -height}, bg_color);
draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, -height}, bar_w, bar_w * ratio, color, outline_color, dash_len,
gap_len);
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>{bar_w, -height}, outline_color);
m_text_cursor_top.y -= offset + height;
return *this;
}
EntityOverlay& EntityOverlay::add_bottom_dashed_bar(const Color& color, const Color& outline_color,
const Color& bg_color, const float height, float ratio,
const float dash_len, const float gap_len, const float offset)
{
ratio = std::clamp(ratio, 0.f, 1.f);
const float bar_w = std::abs(m_canvas.bottom_left_corner.x - m_canvas.bottom_right_corner.x);
const auto bar_start = Vector2<float>{m_canvas.bottom_left_corner.x, m_text_cursor_bottom.y + offset};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{bar_w, height}, bg_color);
draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, height}, bar_w, bar_w * ratio, color, outline_color, dash_len,
gap_len);
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>{bar_w, height}, outline_color);
m_text_cursor_bottom.y += offset + height;
return *this;
}
EntityOverlay& EntityOverlay::add_skeleton(const Color& color, const float thickness)
{
// Maps normalized (rx in [0,1], ry in [0,1]) to canvas screen position
const auto joint = [&](const float rx, const float ry) -> Vector2<float>
{
const auto top = m_canvas.top_left_corner + (m_canvas.top_right_corner - m_canvas.top_left_corner) * rx;
const auto bot =
m_canvas.bottom_left_corner + (m_canvas.bottom_right_corner - m_canvas.bottom_left_corner) * rx;
return top + (bot - top) * ry;
};
using B = std::pair<std::pair<float, float>, std::pair<float, float>>;
static constexpr std::array<B, 15> k_bones{{
// Spine
{{0.50f, 0.13f}, {0.50f, 0.22f}}, // head → neck
{{0.50f, 0.22f}, {0.50f, 0.38f}}, // neck → chest
{{0.50f, 0.38f}, {0.50f, 0.55f}}, // chest → pelvis
// Left arm
{{0.50f, 0.22f}, {0.25f, 0.25f}}, // neck → L shoulder
{{0.25f, 0.25f}, {0.13f, 0.42f}}, // L shoulder → L elbow
{{0.13f, 0.42f}, {0.08f, 0.56f}}, // L elbow → L hand
// Right arm
{{0.50f, 0.22f}, {0.75f, 0.25f}}, // neck → R shoulder
{{0.75f, 0.25f}, {0.87f, 0.42f}}, // R shoulder → R elbow
{{0.87f, 0.42f}, {0.92f, 0.56f}}, // R elbow → R hand
// Left leg
{{0.50f, 0.55f}, {0.36f, 0.58f}}, // pelvis → L hip
{{0.36f, 0.58f}, {0.32f, 0.77f}}, // L hip → L knee
{{0.32f, 0.77f}, {0.27f, 0.97f}}, // L knee → L foot
// Right leg
{{0.50f, 0.55f}, {0.64f, 0.58f}}, // pelvis → R hip
{{0.64f, 0.58f}, {0.68f, 0.77f}}, // R hip → R knee
{{0.68f, 0.77f}, {0.73f, 0.97f}}, // R knee → R foot
}};
for (const auto& [a, b] : k_bones)
m_renderer->add_line(joint(a.first, a.second), joint(b.first, b.second), color, thickness);
return *this;
}
void EntityOverlay::draw_dashed_line(const Vector2<float>& from, const Vector2<float>& to, const Color& color,
const float dash_len, const float gap_len, const float thickness) const
{
const auto total = (to - from).length();
if (total <= 0.f)
return;
const auto dir = (to - from).normalized();
const float step = dash_len + gap_len;
const float n_dashes = std::floor((total + gap_len) / step);
if (n_dashes < 1.f)
return;
const float used = n_dashes * dash_len + (n_dashes - 1.f) * gap_len;
const float offset = (total - used) / 2.f;
for (float i = 0.f; i < n_dashes; ++i)
{
const float pos = offset + i * step;
const auto dash_start = from + dir * pos;
const auto dash_end = from + dir * std::min(pos + dash_len, total);
m_renderer->add_line(dash_start, dash_end, color, thickness);
}
}
EntityOverlay& EntityOverlay::add_dashed_box(const Color& color, const float dash_len, const float gap_len,
const float thickness)
{
const float min_edge = std::min((m_canvas.top_right_corner - m_canvas.top_left_corner).length(),
(m_canvas.bottom_right_corner - m_canvas.top_right_corner).length());
const float corner_len = std::min(dash_len, min_edge / 2.f);
const auto draw_edge = [&](const Vector2<float>& from, const Vector2<float>& to)
{
const auto dir = (to - from).normalized();
m_renderer->add_line(from, from + dir * corner_len, color, thickness);
draw_dashed_line(from + dir * corner_len, to - dir * corner_len, color, dash_len, gap_len, thickness);
m_renderer->add_line(to - dir * corner_len, to, color, thickness);
};
draw_edge(m_canvas.top_left_corner, m_canvas.top_right_corner);
draw_edge(m_canvas.top_right_corner, m_canvas.bottom_right_corner);
draw_edge(m_canvas.bottom_right_corner, m_canvas.bottom_left_corner);
draw_edge(m_canvas.bottom_left_corner, m_canvas.top_left_corner);
return *this;
}
void EntityOverlay::draw_outlined_text(const Vector2<float>& position, const Color& color,
const std::string_view& text)
{
static constexpr std::array outline_offsets = {
Vector2<float>{-1, -1}, Vector2<float>{-1, 0}, Vector2<float>{-1, 1}, Vector2<float>{0, -1},
Vector2<float>{0, 1}, Vector2<float>{1, -1}, Vector2<float>{1, 0}, Vector2<float>{1, 1}};
for (const auto& outline_offset : outline_offsets)
m_renderer->add_text(position + outline_offset, Color{0.f, 0.f, 0.f, 1.f}, text.data());
m_renderer->add_text(position, color, text.data());
}
EntityOverlay& EntityOverlay::add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color,
const float height, float ratio, const float offset)
{
ratio = std::clamp(ratio, 0.f, 1.f);
const auto max_bar_width = std::abs(m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x);
const auto bar_start = Vector2<float>{m_canvas.bottom_left_corner.x, m_text_cursor_bottom.y + offset};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, height), bg_color);
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width * ratio, height), color);
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, height), outline_color);
m_text_cursor_bottom.y += offset + height;
return *this;
}
EntityOverlay& EntityOverlay::add_bottom_label(const Color& color, const float offset, const bool outlined,
const std::string_view text)
{
const auto text_size = m_renderer->calc_text_size(text);
if (outlined)
draw_outlined_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text);
else
m_renderer->add_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text);
m_text_cursor_bottom.y += text_size.y;
return *this;
}
EntityOverlay& EntityOverlay::add_left_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text)
{
const auto text_size = m_renderer->calc_text_size(text);
const auto pos = m_text_cursor_left + Vector2<float>{-(offset + text_size.x), 0.f};
if (outlined)
draw_outlined_text(pos, color, text);
else
m_renderer->add_text(pos, color, text);
m_text_cursor_left.y += text_size.y;
return *this;
}
EntityOverlay& EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text)
{
const auto text_size = m_renderer->calc_text_size(text);
const auto box_center_x =
m_canvas.bottom_left_corner.x + (m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x) / 2.f;
const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_bottom.y + offset};
if (outlined)
draw_outlined_text(pos, color, text);
else
m_renderer->add_text(pos, color, text);
m_text_cursor_bottom.y += text_size.y;
return *this;
}
EntityOverlay& EntityOverlay::add_centered_top_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text)
{
const auto text_size = m_renderer->calc_text_size(text);
const auto box_center_x =
m_canvas.top_left_corner.x + (m_canvas.top_right_corner.x - m_canvas.top_left_corner.x) / 2.f;
m_text_cursor_top.y -= text_size.y;
const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_top.y - offset};
if (outlined)
draw_outlined_text(pos, color, text);
else
m_renderer->add_text(pos, color, text);
return *this;
}
EntityOverlay::EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom,
const std::shared_ptr<HudRendererInterface>& renderer)
: m_canvas(top, bottom), m_text_cursor_right(m_canvas.top_right_corner),
m_text_cursor_top(m_canvas.top_left_corner), m_text_cursor_bottom(m_canvas.bottom_left_corner),
m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer)
{
}
// ── Spacers ─────────────────────────────────────────────────────────────────
EntityOverlay& EntityOverlay::add_right_space_vertical(const float size)
{
m_text_cursor_right.y += size;
return *this;
}
EntityOverlay& EntityOverlay::add_right_space_horizontal(const float size)
{
m_text_cursor_right.x += size;
return *this;
}
EntityOverlay& EntityOverlay::add_left_space_vertical(const float size)
{
m_text_cursor_left.y += size;
return *this;
}
EntityOverlay& EntityOverlay::add_left_space_horizontal(const float size)
{
m_text_cursor_left.x -= size;
return *this;
}
EntityOverlay& EntityOverlay::add_top_space_vertical(const float size)
{
m_text_cursor_top.y -= size;
return *this;
}
EntityOverlay& EntityOverlay::add_top_space_horizontal(const float size)
{
m_text_cursor_top.x += size;
return *this;
}
EntityOverlay& EntityOverlay::add_bottom_space_vertical(const float size)
{
m_text_cursor_bottom.y += size;
return *this;
}
EntityOverlay& EntityOverlay::add_bottom_space_horizontal(const float size)
{
m_text_cursor_bottom.x += size;
return *this;
}
// ── Progress rings ──────────────────────────────────────────────────────────
EntityOverlay& EntityOverlay::add_right_progress_ring(const Color& color, const Color& bg, const float radius,
const float ratio, const float thickness, const float offset,
const int segments)
{
const auto cx = m_text_cursor_right.x + offset + radius;
const auto cy = m_text_cursor_right.y + radius;
draw_progress_ring({cx, cy}, widget::ProgressRing{color, bg, radius, ratio, thickness, offset, segments});
m_text_cursor_right.y += radius * 2.f;
return *this;
}
EntityOverlay& EntityOverlay::add_left_progress_ring(const Color& color, const Color& bg, const float radius,
const float ratio, const float thickness, const float offset,
const int segments)
{
const auto cx = m_text_cursor_left.x - offset - radius;
const auto cy = m_text_cursor_left.y + radius;
draw_progress_ring({cx, cy}, widget::ProgressRing{color, bg, radius, ratio, thickness, offset, segments});
m_text_cursor_left.y += radius * 2.f;
return *this;
}
EntityOverlay& EntityOverlay::add_top_progress_ring(const Color& color, const Color& bg, const float radius,
const float ratio, const float thickness, const float offset,
const int segments)
{
m_text_cursor_top.y -= radius * 2.f;
const auto cx = m_text_cursor_top.x + radius;
const auto cy = m_text_cursor_top.y - offset + radius;
draw_progress_ring({cx, cy}, widget::ProgressRing{color, bg, radius, ratio, thickness, offset, segments});
return *this;
}
EntityOverlay& EntityOverlay::add_bottom_progress_ring(const Color& color, const Color& bg, const float radius,
const float ratio, const float thickness, const float offset,
const int segments)
{
const auto cx = m_text_cursor_bottom.x + radius;
const auto cy = m_text_cursor_bottom.y + offset + radius;
draw_progress_ring({cx, cy}, widget::ProgressRing{color, bg, radius, ratio, thickness, offset, segments});
m_text_cursor_bottom.y += radius * 2.f;
return *this;
}
// ── Icons ────────────────────────────────────────────────────────────────────
EntityOverlay& EntityOverlay::add_right_icon(const std::any& texture_id, const float width, const float height,
const Color& tint, const float offset)
{
const auto pos = m_text_cursor_right + Vector2<float>{offset, 0.f};
m_renderer->add_image(texture_id, pos, pos + Vector2<float>{width, height}, tint);
m_text_cursor_right.y += height;
return *this;
}
EntityOverlay& EntityOverlay::add_left_icon(const std::any& texture_id, const float width, const float height,
const Color& tint, const float offset)
{
const auto pos = m_text_cursor_left + Vector2<float>{-(offset + width), 0.f};
m_renderer->add_image(texture_id, pos, pos + Vector2<float>{width, height}, tint);
m_text_cursor_left.y += height;
return *this;
}
EntityOverlay& EntityOverlay::add_top_icon(const std::any& texture_id, const float width, const float height,
const Color& tint, const float offset)
{
m_text_cursor_top.y -= height;
const auto pos = m_text_cursor_top + Vector2<float>{0.f, -offset};
m_renderer->add_image(texture_id, pos, pos + Vector2<float>{width, height}, tint);
return *this;
}
EntityOverlay& EntityOverlay::add_bottom_icon(const std::any& texture_id, const float width, const float height,
const Color& tint, const float offset)
{
const auto pos = m_text_cursor_bottom + Vector2<float>{0.f, offset};
m_renderer->add_image(texture_id, pos, pos + Vector2<float>{width, height}, tint);
m_text_cursor_bottom.y += height;
return *this;
}
// ── widget dispatch ───────────────────────────────────────────────────────
void EntityOverlay::dispatch(const widget::Box& box)
{
add_2d_box(box.color, box.fill, box.thickness);
}
void EntityOverlay::dispatch(const widget::CorneredBox& cornered_box)
{
add_cornered_2d_box(cornered_box.color, cornered_box.fill, cornered_box.corner_ratio, cornered_box.thickness);
}
void EntityOverlay::dispatch(const widget::DashedBox& dashed_box)
{
add_dashed_box(dashed_box.color, dashed_box.dash_len, dashed_box.gap_len, dashed_box.thickness);
}
void EntityOverlay::dispatch(const widget::Skeleton& skeleton)
{
add_skeleton(skeleton.color, skeleton.thickness);
}
void EntityOverlay::dispatch(const widget::SnapLine& snap_line)
{
add_snap_line(snap_line.start, snap_line.color, snap_line.width);
}
void EntityOverlay::dispatch(const widget::ScanMarker& scan_marker)
{
const auto box_width = std::abs(m_canvas.top_right_corner.x - m_canvas.top_left_corner.x);
const auto box_height = std::abs(m_canvas.bottom_left_corner.y - m_canvas.top_left_corner.y);
const auto center_x = (m_canvas.top_left_corner.x + m_canvas.top_right_corner.x) / 2.f;
const auto center_y = m_canvas.top_left_corner.y + box_height * 0.44f;
const auto side = std::min(box_width, box_height) * 0.5f;
const auto h = side * std::sqrt(3.f) / 2.f;
const std::array<Vector2<float>, 3> tri = {
Vector2<float>{center_x, center_y - h * 2.f / 3.f},
Vector2<float>{center_x - side / 2.f, center_y + h / 3.f},
Vector2<float>{center_x + side / 2.f, center_y + h / 3.f},
};
m_renderer->add_filled_polyline({tri.data(), tri.size()}, scan_marker.color);
if (scan_marker.outline.value().w > 0.f)
m_renderer->add_polyline({tri.data(), tri.size()}, scan_marker.outline, scan_marker.outline_thickness);
}
void EntityOverlay::dispatch(const widget::AimDot& aim_dot)
{
m_renderer->add_filled_circle(aim_dot.position, aim_dot.radius, aim_dot.color);
}
void EntityOverlay::dispatch(const widget::ProjectileAim& proj_widget)
{
const auto box_width = std::abs(m_canvas.top_right_corner.x - m_canvas.top_left_corner.x);
const auto box_height = std::abs(m_canvas.bottom_left_corner.y - m_canvas.top_left_corner.y);
const auto box_center = m_canvas.top_left_corner + Vector2{box_width, box_height} / 2.f;
m_renderer->add_line(box_center, proj_widget.position, proj_widget.color, proj_widget.line_size);
if (proj_widget.figure == widget::ProjectileAim::Figure::CIRCLE)
{
m_renderer->add_filled_circle(proj_widget.position, proj_widget.size, proj_widget.color);
return;
}
if (proj_widget.figure == widget::ProjectileAim::Figure::SQUARE)
{
const auto box_min = proj_widget.position - Vector2{proj_widget.size, proj_widget.size} / 2.f;
const auto box_max = proj_widget.position + Vector2{proj_widget.size, proj_widget.size} / 2.f;
m_renderer->add_filled_rectangle(box_min, box_max, proj_widget.color);
return;
}
std::unreachable();
}
void EntityOverlay::draw_progress_ring(const Vector2<float>& center, const widget::ProgressRing& ring)
{
constexpr auto pi = std::numbers::pi_v<float>;
const float ratio = std::clamp(ring.ratio, 0.f, 1.f);
m_renderer->add_circle(center, ring.radius, ring.bg, ring.thickness, ring.segments);
if (ratio > 0.f)
{
const float a_min = -pi / 2.f;
const float a_max = a_min + ratio * 2.f * pi;
m_renderer->add_arc(center, ring.radius, a_min, a_max, ring.color, ring.thickness, ring.segments);
}
}
// ── Side container dispatch ───────────────────────────────────────────────
void EntityOverlay::dispatch(const widget::RightSide& right_side)
{
for (const auto& child : right_side.children)
std::visit(
widget::Overloaded{
[](const widget::None&)
{
},
[this](const widget::Bar& w)
{
add_right_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset);
},
[this](const widget::DashedBar& w)
{
add_right_dashed_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.dash_len, w.gap_len,
w.offset);
},
[this](const widget::Label& w)
{
add_right_label(w.color, w.offset, w.outlined, w.text);
},
[this](const widget::Centered<widget::Label>& w)
{
add_right_label(w.child.color, w.child.offset, w.child.outlined, w.child.text);
},
[this](const widget::SpaceVertical& w)
{
add_right_space_vertical(w.size);
},
[this](const widget::SpaceHorizontal& w)
{
add_right_space_horizontal(w.size);
},
[this](const widget::ProgressRing& w)
{
add_right_progress_ring(w.color, w.bg, w.radius, w.ratio, w.thickness, w.offset,
w.segments);
},
[this](const widget::Icon& w)
{
add_right_icon(w.texture_id, w.width, w.height, w.tint, w.offset);
},
},
child);
}
void EntityOverlay::dispatch(const widget::LeftSide& left_side)
{
for (const auto& child : left_side.children)
std::visit(
widget::Overloaded{
[](const widget::None&)
{
},
[this](const widget::Bar& w)
{
add_left_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset);
},
[this](const widget::DashedBar& w)
{
add_left_dashed_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.dash_len, w.gap_len,
w.offset);
},
[this](const widget::Label& w)
{
add_left_label(w.color, w.offset, w.outlined, w.text);
},
[this](const widget::Centered<widget::Label>& w)
{
add_left_label(w.child.color, w.child.offset, w.child.outlined, w.child.text);
},
[this](const widget::SpaceVertical& w)
{
add_left_space_vertical(w.size);
},
[this](const widget::SpaceHorizontal& w)
{
add_left_space_horizontal(w.size);
},
[this](const widget::ProgressRing& w)
{
add_left_progress_ring(w.color, w.bg, w.radius, w.ratio, w.thickness, w.offset,
w.segments);
},
[this](const widget::Icon& w)
{
add_left_icon(w.texture_id, w.width, w.height, w.tint, w.offset);
},
},
child);
}
void EntityOverlay::dispatch(const widget::TopSide& top_side)
{
for (const auto& child : top_side.children)
std::visit(
widget::Overloaded{
[](const widget::None&)
{
},
[this](const widget::Bar& w)
{
add_top_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset);
},
[this](const widget::DashedBar& w)
{
add_top_dashed_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.dash_len, w.gap_len,
w.offset);
},
[this](const widget::Label& w)
{
add_top_label(w.color, w.offset, w.outlined, w.text);
},
[this](const widget::Centered<widget::Label>& w)
{
add_centered_top_label(w.child.color, w.child.offset, w.child.outlined, w.child.text);
},
[this](const widget::SpaceVertical& w)
{
add_top_space_vertical(w.size);
},
[this](const widget::SpaceHorizontal& w)
{
add_top_space_horizontal(w.size);
},
[this](const widget::ProgressRing& w)
{
add_top_progress_ring(w.color, w.bg, w.radius, w.ratio, w.thickness, w.offset,
w.segments);
},
[this](const widget::Icon& w)
{
add_top_icon(w.texture_id, w.width, w.height, w.tint, w.offset);
},
},
child);
}
void EntityOverlay::dispatch(const widget::BottomSide& bottom_side)
{
for (const auto& child : bottom_side.children)
std::visit(
widget::Overloaded{
[](const widget::None&)
{
},
[this](const widget::Bar& w)
{
add_bottom_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset);
},
[this](const widget::DashedBar& w)
{
add_bottom_dashed_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.dash_len, w.gap_len,
w.offset);
},
[this](const widget::Label& w)
{
add_bottom_label(w.color, w.offset, w.outlined, w.text);
},
[this](const widget::Centered<widget::Label>& w)
{
add_centered_bottom_label(w.child.color, w.child.offset, w.child.outlined,
w.child.text);
},
[this](const widget::SpaceVertical& w)
{
add_bottom_space_vertical(w.size);
},
[this](const widget::SpaceHorizontal& w)
{
add_bottom_space_horizontal(w.size);
},
[this](const widget::ProgressRing& w)
{
add_bottom_progress_ring(w.color, w.bg, w.radius, w.ratio, w.thickness, w.offset,
w.segments);
},
[this](const widget::Icon& w)
{
add_bottom_icon(w.texture_id, w.width, w.height, w.tint, w.offset);
},
},
child);
}
} // namespace omath::hud

View File

@@ -0,0 +1,82 @@
//
// Created by orange on 13.03.2026.
//
#include "omath/hud/renderer_realizations/imgui_renderer.hpp"
#ifdef OMATH_IMGUI_INTEGRATION
#include <imgui.h>
namespace omath::hud
{
ImguiHudRenderer::~ImguiHudRenderer() = default;
void ImguiHudRenderer::add_line(const Vector2<float>& line_start, const Vector2<float>& line_end,
const Color& color, const float thickness)
{
ImGui::GetBackgroundDrawList()->AddLine(line_start.to_im_vec2(), line_end.to_im_vec2(), color.to_im_color(),
thickness);
}
void ImguiHudRenderer::add_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color,
const float thickness)
{
ImGui::GetBackgroundDrawList()->AddPolyline(reinterpret_cast<const ImVec2*>(vertexes.data()),
static_cast<int>(vertexes.size()), color.to_im_color(),
ImDrawFlags_Closed, thickness);
}
void ImguiHudRenderer::add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color)
{
ImGui::GetBackgroundDrawList()->AddConvexPolyFilled(reinterpret_cast<const ImVec2*>(vertexes.data()),
static_cast<int>(vertexes.size()), color.to_im_color());
}
void ImguiHudRenderer::add_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color)
{
ImGui::GetBackgroundDrawList()->AddRect(min.to_im_vec2(), max.to_im_vec2(), color.to_im_color());
}
void ImguiHudRenderer::add_filled_rectangle(const Vector2<float>& min, const Vector2<float>& max,
const Color& color)
{
ImGui::GetBackgroundDrawList()->AddRectFilled(min.to_im_vec2(), max.to_im_vec2(), color.to_im_color());
}
void ImguiHudRenderer::add_circle(const Vector2<float>& center, const float radius, const Color& color,
const float thickness, const int segments)
{
ImGui::GetBackgroundDrawList()->AddCircle(center.to_im_vec2(), radius, color.to_im_color(), segments, thickness);
}
void ImguiHudRenderer::add_filled_circle(const Vector2<float>& center, const float radius, const Color& color,
const int segments)
{
ImGui::GetBackgroundDrawList()->AddCircleFilled(center.to_im_vec2(), radius, color.to_im_color(), segments);
}
void ImguiHudRenderer::add_arc(const Vector2<float>& center, const float radius, const float a_min, const float a_max,
const Color& color, const float thickness, const int segments)
{
ImGui::GetBackgroundDrawList()->PathArcTo(center.to_im_vec2(), radius, a_min, a_max, segments);
ImGui::GetBackgroundDrawList()->PathStroke(color.to_im_color(), ImDrawFlags_None, thickness);
}
void ImguiHudRenderer::add_image(const std::any& texture_id, const Vector2<float>& min, const Vector2<float>& max,
const Color& tint)
{
ImGui::GetBackgroundDrawList()->AddImage(std::any_cast<ImTextureID>(texture_id), min.to_im_vec2(),
max.to_im_vec2(), {0, 0}, {1, 1}, tint.to_im_color());
}
void ImguiHudRenderer::add_text(const Vector2<float>& position, const Color& color, const std::string_view& text)
{
ImGui::GetBackgroundDrawList()->AddText(position.to_im_vec2(), color.to_im_color(), text.data(),
text.data() + text.size());
}
[[nodiscard]]
Vector2<float> ImguiHudRenderer::calc_text_size(const std::string_view& text)
{
return Vector2<float>::from_im_vec2(ImGui::CalcTextSize(text.data()));
}
} // namespace omath::hud
#endif // OMATH_IMGUI_INTEGRATION

View File

@@ -1,17 +0,0 @@
//
// Created by orange on 07.03.2026.
//
#pragma once
#ifdef OMATH_ENABLE_LUA
#include <sol/sol.hpp>
namespace omath::lua::detail
{
void register_vec2(sol::table& omath_table);
void register_vec3(sol::table& omath_table);
void register_vec4(sol::table& omath_table);
void register_color(sol::table& omath_table);
void register_shared_types(sol::table& omath_table);
void register_engines(sol::table& omath_table);
}
#endif

View File

@@ -3,23 +3,25 @@
//
#ifdef OMATH_ENABLE_LUA
#include "lua.hpp"
#include "internal.hpp"
#include <sol/sol.hpp>
#include "omath/lua/lua.hpp"
namespace omath::lua
{
void register_lib(lua_State* lua_state)
void LuaInterpreter::register_lib(lua_State* lua_state)
{
sol::state_view lua(lua_state);
auto omath_table = lua["omath"].get_or_create<sol::table>();
detail::register_vec2(omath_table);
detail::register_vec3(omath_table);
detail::register_vec4(omath_table);
detail::register_color(omath_table);
detail::register_shared_types(omath_table);
detail::register_engines(omath_table);
register_vec2(omath_table);
register_vec3(omath_table);
register_vec4(omath_table);
register_color(omath_table);
register_triangle(omath_table);
register_shared_types(omath_table);
register_engines(omath_table);
register_pattern_scan(omath_table);
}
} // namespace omath::lua
#endif

View File

@@ -2,47 +2,45 @@
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "internal.hpp"
#include "omath/lua/lua.hpp"
#include <sol/sol.hpp>
#include <omath/utility/color.hpp>
namespace omath::lua::detail
namespace omath::lua
{
void register_color(sol::table& omath_table)
void LuaInterpreter::register_color(sol::table& omath_table)
{
omath_table.new_usertype<omath::Color>(
"Color",
sol::factories(
[](float r, float g, float b, float a) { return omath::Color(r, g, b, a); },
[]() { return omath::Color(); }),
sol::factories([](float r, float g, float b, float a) { return omath::Color(r, g, b, a); },
[]() { return omath::Color(); }),
"from_rgba", [](uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
return omath::Color::from_rgba(r, g, b, a);
},
"from_hsv", sol::overload(
[](float h, float s, float v) { return omath::Color::from_hsv(h, s, v); },
[](const omath::Hsv& hsv) { return omath::Color::from_hsv(hsv); }),
"red", []() { return omath::Color::red(); },
"green", []() { return omath::Color::green(); },
"blue", []() { return omath::Color::blue(); },
"from_rgba", [](uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{ return omath::Color::from_rgba(r, g, b, a); }, "from_hsv",
sol::overload([](float h, float s, float v) { return omath::Color::from_hsv(h, s, v); },
[](const omath::Hsv& hsv) { return omath::Color::from_hsv(hsv); }),
"red", []() { return omath::Color::red(); }, "green", []() { return omath::Color::green(); }, "blue",
[]() { return omath::Color::blue(); },
"r", sol::property([](const omath::Color& c) { return c.value().x; }),
"g", sol::property([](const omath::Color& c) { return c.value().y; }),
"b", sol::property([](const omath::Color& c) { return c.value().z; }),
"a", sol::property([](const omath::Color& c) { return c.value().w; }),
"r", sol::property([](const omath::Color& c) { return c.value().x; }), "g",
sol::property([](const omath::Color& c) { return c.value().y; }), "b",
sol::property([](const omath::Color& c) { return c.value().z; }), "a",
sol::property([](const omath::Color& c) { return c.value().w; }),
"to_hsv", &omath::Color::to_hsv,
"set_hue", &omath::Color::set_hue,
"set_saturation", &omath::Color::set_saturation,
"set_value", &omath::Color::set_value,
"blend", &omath::Color::blend,
"to_hsv", &omath::Color::to_hsv, "set_hue", &omath::Color::set_hue, "set_saturation",
&omath::Color::set_saturation, "set_value", &omath::Color::set_value, "blend", &omath::Color::blend,
sol::meta_function::to_string, &omath::Color::to_string);
omath_table.new_usertype<omath::Hsv>(
"Hsv", sol::constructors<omath::Hsv()>(),
"hue", sol::property([](const omath::Hsv& h) { return h.hue; }, [](omath::Hsv& h, float val) { h.hue = val; }),
"saturation", sol::property([](const omath::Hsv& h) { return h.saturation; }, [](omath::Hsv& h, float val) { h.saturation = val; }),
"value", sol::property([](const omath::Hsv& h) { return h.value; }, [](omath::Hsv& h, float val) { h.value = val; }));
"Hsv", sol::constructors<omath::Hsv()>(), "hue",
sol::property([](const omath::Hsv& h) { return h.hue; }, [](omath::Hsv& h, float val) { h.hue = val; }),
"saturation",
sol::property([](const omath::Hsv& h) { return h.saturation; },
[](omath::Hsv& h, float val) { h.saturation = val; }),
"value",
sol::property([](const omath::Hsv& h) { return h.value; },
[](omath::Hsv& h, float val) { h.value = val; }));
}
} // namespace omath::lua::detail
#endif

View File

@@ -2,25 +2,17 @@
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "internal.hpp"
#include "omath/lua/lua.hpp"
#include "omath/omath.hpp"
#include "omath/projection/error_codes.hpp"
#include <omath/engines/cry_engine/camera.hpp>
#include <omath/engines/cry_engine/constants.hpp>
#include <omath/engines/frostbite_engine/camera.hpp>
#include <omath/engines/frostbite_engine/constants.hpp>
#include <omath/engines/iw_engine/camera.hpp>
#include <omath/engines/iw_engine/constants.hpp>
#include <omath/engines/opengl_engine/camera.hpp>
#include <omath/engines/opengl_engine/constants.hpp>
#include <omath/engines/opengl_engine/formulas.hpp>
#include <omath/engines/source_engine/camera.hpp>
#include <omath/engines/source_engine/constants.hpp>
#include <omath/engines/unity_engine/camera.hpp>
#include <omath/engines/unity_engine/constants.hpp>
#include <omath/engines/unreal_engine/camera.hpp>
#include <omath/engines/unreal_engine/constants.hpp>
#include <omath/linear_algebra/vector3.hpp>
#include <omath/projection/camera.hpp>
#include <omath/projection/error_codes.hpp>
#include <sol/sol.hpp>
#include <string_view>
namespace
@@ -28,14 +20,14 @@ namespace
// ---- Canonical shared C++ type aliases ----------------------------------
// Each unique template instantiation must be registered exactly once.
using PitchAngle90 = omath::Angle<float, -90.f, 90.f, omath::AngleFlags::Clamped>;
using PitchAngle89 = omath::Angle<float, -89.f, 89.f, omath::AngleFlags::Clamped>;
using PitchAngle90 = omath::Angle<float, -90.f, 90.f, omath::AngleFlags::Clamped>;
using PitchAngle89 = omath::Angle<float, -89.f, 89.f, omath::AngleFlags::Clamped>;
using SharedYawRoll = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
using SharedFoV = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>;
using ViewAngles90 = omath::ViewAngles<PitchAngle90, SharedYawRoll, SharedYawRoll>;
using ViewAngles89 = omath::ViewAngles<PitchAngle89, SharedYawRoll, SharedYawRoll>;
using SharedFoV = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>;
using ViewAngles90 = omath::ViewAngles<PitchAngle90, SharedYawRoll, SharedYawRoll>;
using ViewAngles89 = omath::ViewAngles<PitchAngle89, SharedYawRoll, SharedYawRoll>;
static std::string projection_error_to_string(omath::projection::Error e)
std::string projection_error_to_string(omath::projection::Error e)
{
switch (e)
{
@@ -43,6 +35,8 @@ namespace
return "world position is out of screen bounds";
case omath::projection::Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
return "inverse view-projection matrix determinant is zero";
case omath::projection::Error::PERSPECTIVE_DIVIDER_LESS_EQ_ZERO:
return "perspective divider is less or equal to zero";
}
return "unknown error";
}
@@ -51,27 +45,16 @@ namespace
void register_angle(sol::table& table, const char* name)
{
table.new_usertype<AngleType>(
name, sol::no_constructor,
"from_degrees", &AngleType::from_degrees,
"from_radians", &AngleType::from_radians,
"as_degrees", &AngleType::as_degrees,
"as_radians", &AngleType::as_radians,
"sin", &AngleType::sin,
"cos", &AngleType::cos,
"tan", &AngleType::tan,
"cot", &AngleType::cot,
sol::meta_function::addition,
name, sol::no_constructor, "from_degrees", &AngleType::from_degrees, "from_radians",
&AngleType::from_radians, "as_degrees", &AngleType::as_degrees, "as_radians", &AngleType::as_radians,
"sin", &AngleType::sin, "cos", &AngleType::cos, "tan", &AngleType::tan, "cot", &AngleType::cot,
sol::meta_function::addition, [](const AngleType& a, const AngleType& b)
{ return AngleType::from_degrees(a.as_degrees() + b.as_degrees()); }, sol::meta_function::subtraction,
[](const AngleType& a, const AngleType& b)
{ return AngleType::from_degrees(a.as_degrees() + b.as_degrees()); },
sol::meta_function::subtraction,
[](const AngleType& a, const AngleType& b)
{ return AngleType::from_degrees(a.as_degrees() - b.as_degrees()); },
sol::meta_function::unary_minus,
{ return AngleType::from_degrees(a.as_degrees() - b.as_degrees()); }, sol::meta_function::unary_minus,
[](const AngleType& a) { return AngleType::from_degrees(-a.as_degrees()); },
sol::meta_function::equal_to,
[](const AngleType& a, const AngleType& b) { return a == b; },
sol::meta_function::to_string,
[](const AngleType& a) { return std::format("{}deg", a.as_degrees()); });
sol::meta_function::equal_to, [](const AngleType& a, const AngleType& b) { return a == b; },
sol::meta_function::to_string, [](const AngleType& a) { return std::format("{}deg", a.as_degrees()); });
}
// Set aliases in an engine subtable pointing to the already-registered shared types
@@ -83,10 +66,10 @@ namespace
else
engine_table["PitchAngle"] = types["PitchAngle89"];
engine_table["YawAngle"] = types["YawRoll"];
engine_table["RollAngle"] = types["YawRoll"];
engine_table["YawAngle"] = types["YawRoll"];
engine_table["RollAngle"] = types["YawRoll"];
engine_table["FieldOfView"] = types["FieldOfView"];
engine_table["ViewPort"] = types["ViewPort"];
engine_table["ViewPort"] = types["ViewPort"];
if constexpr (std::is_same_v<ViewAnglesType, ViewAngles90>)
engine_table["ViewAngles"] = types["ViewAngles90"];
@@ -100,40 +83,33 @@ namespace
{
using PitchAngle = typename EngineTraits::PitchAngle;
using ViewAngles = typename EngineTraits::ViewAngles;
using Camera = typename EngineTraits::Camera;
using Camera = typename EngineTraits::Camera;
auto engine_table = omath_table[subtable_name].get_or_create<sol::table>();
auto types = omath_table["_types"].get<sol::table>();
auto types = omath_table["_types"].get<sol::table>();
set_engine_aliases<PitchAngle, ViewAngles>(engine_table, types);
engine_table.new_usertype<Camera>(
"Camera",
sol::constructors<Camera(const omath::Vector3<float>&, const ViewAngles&,
const omath::projection::ViewPort&,
const omath::projection::FieldOfView&, float, float)>(),
"look_at", &Camera::look_at,
"get_forward", &Camera::get_forward,
"get_right", &Camera::get_right,
"get_up", &Camera::get_up,
"get_origin", &Camera::get_origin,
"get_view_angles", &Camera::get_view_angles,
"get_near_plane", &Camera::get_near_plane,
"get_far_plane", &Camera::get_far_plane,
"get_field_of_view", &Camera::get_field_of_view,
"set_origin", &Camera::set_origin,
"set_view_angles", &Camera::set_view_angles,
"set_view_port", &Camera::set_view_port,
"set_field_of_view", &Camera::set_field_of_view,
"set_near_plane", &Camera::set_near_plane,
"set_far_plane", &Camera::set_far_plane,
const omath::projection::ViewPort&, const omath::projection::FieldOfView&,
float, float)>(),
"look_at", &Camera::look_at, "get_forward", &Camera::get_forward, "get_right", &Camera::get_right,
"get_up", &Camera::get_up, "get_origin", &Camera::get_origin, "get_view_angles",
&Camera::get_view_angles, "get_near_plane", &Camera::get_near_plane, "get_far_plane",
&Camera::get_far_plane, "get_field_of_view", &Camera::get_field_of_view, "set_origin",
&Camera::set_origin, "set_view_angles", &Camera::set_view_angles, "set_view_port",
&Camera::set_view_port, "set_field_of_view", &Camera::set_field_of_view, "set_near_plane",
&Camera::set_near_plane, "set_far_plane", &Camera::set_far_plane,
"world_to_screen",
[](const Camera& cam, const omath::Vector3<float>& pos)
-> std::tuple<sol::optional<omath::Vector3<float>>, sol::optional<std::string>>
{
auto result = cam.world_to_screen(pos);
if (result) return {*result, sol::nullopt};
if (result)
return {*result, sol::nullopt};
return {sol::nullopt, projection_error_to_string(result.error())};
},
@@ -142,7 +118,8 @@ namespace
-> std::tuple<sol::optional<omath::Vector3<float>>, sol::optional<std::string>>
{
auto result = cam.screen_to_world(pos);
if (result) return {*result, sol::nullopt};
if (result)
return {*result, sol::nullopt};
return {sol::nullopt, projection_error_to_string(result.error())};
});
}
@@ -153,90 +130,102 @@ namespace
{
using PitchAngle = omath::opengl_engine::PitchAngle;
using ViewAngles = omath::opengl_engine::ViewAngles;
using Camera = omath::opengl_engine::Camera;
using Camera = omath::opengl_engine::Camera;
};
struct FrostbiteEngineTraits
{
using PitchAngle = omath::frostbite_engine::PitchAngle;
using ViewAngles = omath::frostbite_engine::ViewAngles;
using Camera = omath::frostbite_engine::Camera;
using Camera = omath::frostbite_engine::Camera;
};
struct IWEngineTraits
{
using PitchAngle = omath::iw_engine::PitchAngle;
using ViewAngles = omath::iw_engine::ViewAngles;
using Camera = omath::iw_engine::Camera;
using Camera = omath::iw_engine::Camera;
};
struct SourceEngineTraits
{
using PitchAngle = omath::source_engine::PitchAngle;
using ViewAngles = omath::source_engine::ViewAngles;
using Camera = omath::source_engine::Camera;
using Camera = omath::source_engine::Camera;
};
struct UnityEngineTraits
{
using PitchAngle = omath::unity_engine::PitchAngle;
using ViewAngles = omath::unity_engine::ViewAngles;
using Camera = omath::unity_engine::Camera;
using Camera = omath::unity_engine::Camera;
};
struct UnrealEngineTraits
{
using PitchAngle = omath::unreal_engine::PitchAngle;
using ViewAngles = omath::unreal_engine::ViewAngles;
using Camera = omath::unreal_engine::Camera;
using Camera = omath::unreal_engine::Camera;
};
struct CryEngineTraits
{
using PitchAngle = omath::cry_engine::PitchAngle;
using ViewAngles = omath::cry_engine::ViewAngles;
using Camera = omath::cry_engine::Camera;
using Camera = omath::cry_engine::Camera;
};
} // namespace
namespace omath::lua::detail
namespace omath::lua
{
void register_shared_types(sol::table& omath_table)
void LuaInterpreter::register_shared_types(sol::table& omath_table)
{
auto t = omath_table["_types"].get_or_create<sol::table>();
register_angle<PitchAngle90>(t, "PitchAngle90");
register_angle<PitchAngle89>(t, "PitchAngle89");
register_angle<PitchAngle90>(t, "PitchAngle90");
register_angle<PitchAngle89>(t, "PitchAngle89");
register_angle<SharedYawRoll>(t, "YawRoll");
register_angle<SharedFoV>(t, "FieldOfView");
register_angle<SharedFoV>(t, "FieldOfView");
t.new_usertype<omath::projection::ViewPort>(
"ViewPort",
sol::factories([](float w, float h) { return omath::projection::ViewPort{w, h}; }),
"width", sol::property([](const omath::projection::ViewPort& vp) { return vp.m_width; }, [](omath::projection::ViewPort& vp, float val) { vp.m_width = val; }),
"height", sol::property([](const omath::projection::ViewPort& vp) { return vp.m_height; }, [](omath::projection::ViewPort& vp, float val) { vp.m_height = val; }),
"ViewPort", sol::factories([](float w, float h) { return omath::projection::ViewPort{w, h}; }), "width",
sol::property([](const omath::projection::ViewPort& vp) { return vp.m_width; },
[](omath::projection::ViewPort& vp, float val) { vp.m_width = val; }),
"height",
sol::property([](const omath::projection::ViewPort& vp) { return vp.m_height; },
[](omath::projection::ViewPort& vp, float val) { vp.m_height = val; }),
"aspect_ratio", &omath::projection::ViewPort::aspect_ratio);
t.new_usertype<ViewAngles90>(
"ViewAngles90",
sol::factories([](PitchAngle90 p, SharedYawRoll y, SharedYawRoll r)
{ return ViewAngles90{p, y, r}; }),
"pitch", sol::property([](const ViewAngles90& va) { return va.pitch; }, [](ViewAngles90& va, const PitchAngle90& val) { va.pitch = val; }),
"yaw", sol::property([](const ViewAngles90& va) { return va.yaw; }, [](ViewAngles90& va, const SharedYawRoll& val) { va.yaw = val; }),
"roll", sol::property([](const ViewAngles90& va) { return va.roll; }, [](ViewAngles90& va, const SharedYawRoll& val) { va.roll = val; }));
sol::factories([](PitchAngle90 p, SharedYawRoll y, SharedYawRoll r) { return ViewAngles90{p, y, r}; }),
"pitch",
sol::property([](const ViewAngles90& va) { return va.pitch; },
[](ViewAngles90& va, const PitchAngle90& val) { va.pitch = val; }),
"yaw",
sol::property([](const ViewAngles90& va) { return va.yaw; },
[](ViewAngles90& va, const SharedYawRoll& val) { va.yaw = val; }),
"roll",
sol::property([](const ViewAngles90& va) { return va.roll; },
[](ViewAngles90& va, const SharedYawRoll& val) { va.roll = val; }));
t.new_usertype<ViewAngles89>(
"ViewAngles89",
sol::factories([](PitchAngle89 p, SharedYawRoll y, SharedYawRoll r)
{ return ViewAngles89{p, y, r}; }),
"pitch", sol::property([](const ViewAngles89& va) { return va.pitch; }, [](ViewAngles89& va, const PitchAngle89& val) { va.pitch = val; }),
"yaw", sol::property([](const ViewAngles89& va) { return va.yaw; }, [](ViewAngles89& va, const SharedYawRoll& val) { va.yaw = val; }),
"roll", sol::property([](const ViewAngles89& va) { return va.roll; }, [](ViewAngles89& va, const SharedYawRoll& val) { va.roll = val; }));
sol::factories([](PitchAngle89 p, SharedYawRoll y, SharedYawRoll r) { return ViewAngles89{p, y, r}; }),
"pitch",
sol::property([](const ViewAngles89& va) { return va.pitch; },
[](ViewAngles89& va, const PitchAngle89& val) { va.pitch = val; }),
"yaw",
sol::property([](const ViewAngles89& va) { return va.yaw; },
[](ViewAngles89& va, const SharedYawRoll& val) { va.yaw = val; }),
"roll",
sol::property([](const ViewAngles89& va) { return va.roll; },
[](ViewAngles89& va, const SharedYawRoll& val) { va.roll = val; }));
}
void register_engines(sol::table& omath_table)
void LuaInterpreter::register_engines(sol::table& omath_table)
{
register_engine<OpenGLEngineTraits>(omath_table, "opengl");
register_engine<OpenGLEngineTraits>(omath_table, "opengl");
register_engine<FrostbiteEngineTraits>(omath_table, "frostbite");
register_engine<IWEngineTraits>(omath_table, "iw");
register_engine<SourceEngineTraits>(omath_table, "source");
register_engine<UnityEngineTraits>(omath_table, "unity");
register_engine<UnrealEngineTraits>(omath_table, "unreal");
register_engine<CryEngineTraits>(omath_table, "cry");
register_engine<IWEngineTraits>(omath_table, "iw");
register_engine<SourceEngineTraits>(omath_table, "source");
register_engine<UnityEngineTraits>(omath_table, "unity");
register_engine<UnrealEngineTraits>(omath_table, "unreal");
register_engine<CryEngineTraits>(omath_table, "cry");
}
} // namespace omath::lua::detail
#endif

View File

@@ -0,0 +1,104 @@
//
// Created by orange on 10.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <format>
#include <omath/utility/elf_pattern_scan.hpp>
#include <omath/utility/macho_pattern_scan.hpp>
#include <omath/utility/pattern_scan.hpp>
#include <omath/utility/pe_pattern_scan.hpp>
#include <omath/utility/section_scan_result.hpp>
#include <sol/sol.hpp>
namespace omath::lua
{
void LuaInterpreter::register_pattern_scan(sol::table& omath_table)
{
omath_table.new_usertype<SectionScanResult>(
"SectionScanResult", sol::no_constructor,
"virtual_base_addr",
sol::property([](const SectionScanResult& r) { return r.virtual_base_addr; }),
"raw_base_addr",
sol::property([](const SectionScanResult& r) { return r.raw_base_addr; }),
"target_offset",
sol::property([](const SectionScanResult& r) { return r.target_offset; }),
sol::meta_function::to_string,
[](const SectionScanResult& r)
{
return std::format("SectionScanResult(vbase=0x{:X}, raw_base=0x{:X}, offset={})",
r.virtual_base_addr, r.raw_base_addr, r.target_offset);
});
// Generic scanner: accepts a Lua string as a byte buffer
auto ps_table = omath_table["PatternScanner"].get_or_create<sol::table>();
ps_table["scan"] = [](const std::string& data, const std::string& pattern) -> sol::optional<std::ptrdiff_t>
{
const auto* begin = reinterpret_cast<const std::byte*>(data.data());
const auto* end = begin + data.size();
const auto* result = PatternScanner::scan_for_pattern(begin, end, pattern);
if (result == end)
return sol::nullopt;
return std::distance(begin, result);
};
auto pe_table = omath_table["PePatternScanner"].get_or_create<sol::table>();
pe_table["scan_in_module"] = [](std::uintptr_t base_addr, const std::string& pattern,
sol::optional<std::string> section) -> sol::optional<std::uintptr_t>
{
auto result = PePatternScanner::scan_for_pattern_in_loaded_module(reinterpret_cast<const void*>(base_addr),
pattern, section.value_or(".text"));
if (!result)
return sol::nullopt;
return *result;
};
pe_table["scan_in_file"] = [](const std::string& path, const std::string& pattern,
sol::optional<std::string> section) -> sol::optional<SectionScanResult>
{
auto result = PePatternScanner::scan_for_pattern_in_file(std::filesystem::path(path), pattern,
section.value_or(".text"));
if (!result)
return sol::nullopt;
return *result;
};
auto elf_table = omath_table["ElfPatternScanner"].get_or_create<sol::table>();
elf_table["scan_in_module"] = [](std::uintptr_t base_addr, const std::string& pattern,
sol::optional<std::string> section) -> sol::optional<std::uintptr_t>
{
auto result = ElfPatternScanner::scan_for_pattern_in_loaded_module(reinterpret_cast<const void*>(base_addr),
pattern, section.value_or(".text"));
if (!result)
return sol::nullopt;
return *result;
};
elf_table["scan_in_file"] = [](const std::string& path, const std::string& pattern,
sol::optional<std::string> section) -> sol::optional<SectionScanResult>
{
auto result = ElfPatternScanner::scan_for_pattern_in_file(std::filesystem::path(path), pattern,
section.value_or(".text"));
if (!result)
return sol::nullopt;
return *result;
};
auto macho_table = omath_table["MachOPatternScanner"].get_or_create<sol::table>();
macho_table["scan_in_module"] = [](std::uintptr_t base_addr, const std::string& pattern,
sol::optional<std::string> section) -> sol::optional<std::uintptr_t>
{
auto result = MachOPatternScanner::scan_for_pattern_in_loaded_module(
reinterpret_cast<const void*>(base_addr), pattern, section.value_or("__text"));
if (!result)
return sol::nullopt;
return *result;
};
macho_table["scan_in_file"] = [](const std::string& path, const std::string& pattern,
sol::optional<std::string> section) -> sol::optional<SectionScanResult>
{
auto result = MachOPatternScanner::scan_for_pattern_in_file(std::filesystem::path(path), pattern,
section.value_or("__text"));
if (!result)
return sol::nullopt;
return *result;
};
}
} // namespace omath::lua
#endif

View File

@@ -0,0 +1,48 @@
//
// Created by orange on 10.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <sol/sol.hpp>
#include <omath/linear_algebra/triangle.hpp>
namespace omath::lua
{
void LuaInterpreter::register_triangle(sol::table& omath_table)
{
using Vec3f = omath::Vector3<float>;
using Tri3f = omath::Triangle<Vec3f>;
omath_table.new_usertype<Tri3f>(
"Triangle", sol::constructors<Tri3f(), Tri3f(const Vec3f&, const Vec3f&, const Vec3f&)>(),
"vertex1",
sol::property([](const Tri3f& t) { return t.m_vertex1; },
[](Tri3f& t, const Vec3f& v) { t.m_vertex1 = v; }),
"vertex2",
sol::property([](const Tri3f& t) { return t.m_vertex2; },
[](Tri3f& t, const Vec3f& v) { t.m_vertex2 = v; }),
"vertex3",
sol::property([](const Tri3f& t) { return t.m_vertex3; },
[](Tri3f& t, const Vec3f& v) { t.m_vertex3 = v; }),
"calculate_normal", &Tri3f::calculate_normal,
"side_a_length", &Tri3f::side_a_length,
"side_b_length", &Tri3f::side_b_length,
"side_a_vector", &Tri3f::side_a_vector,
"side_b_vector", &Tri3f::side_b_vector,
"hypot", &Tri3f::hypot,
"is_rectangular", &Tri3f::is_rectangular,
"mid_point", &Tri3f::mid_point,
sol::meta_function::to_string,
[](const Tri3f& t)
{
return std::format("Triangle(({}, {}, {}), ({}, {}, {}), ({}, {}, {}))",
t.m_vertex1.x, t.m_vertex1.y, t.m_vertex1.z,
t.m_vertex2.x, t.m_vertex2.y, t.m_vertex2.z,
t.m_vertex3.x, t.m_vertex3.y, t.m_vertex3.z);
});
}
} // namespace omath::lua
#endif

View File

@@ -2,12 +2,13 @@
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "internal.hpp"
#include "omath/lua/lua.hpp"
#include <omath/linear_algebra/vector2.hpp>
#include <sol/sol.hpp>
namespace omath::lua::detail
namespace omath::lua
{
void register_vec2(sol::table& omath_table)
void LuaInterpreter::register_vec2(sol::table& omath_table)
{
using Vec2f = omath::Vector2<float>;

View File

@@ -2,12 +2,13 @@
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "internal.hpp"
#include "omath/lua/lua.hpp"
#include <sol/sol.hpp>
#include <omath/linear_algebra/vector3.hpp>
namespace omath::lua::detail
namespace omath::lua
{
void register_vec3(sol::table& omath_table)
void LuaInterpreter::register_vec3(sol::table& omath_table)
{
using Vec3f = omath::Vector3<float>;

View File

@@ -2,12 +2,13 @@
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "internal.hpp"
#include "omath/lua/lua.hpp"
#include <sol/sol.hpp>
#include <omath/linear_algebra/vector4.hpp>
namespace omath::lua::detail
namespace omath::lua
{
void register_vec4(sol::table& omath_table)
void LuaInterpreter::register_vec4(sol::table& omath_table)
{
using Vec4f = omath::Vector4<float>;

View File

@@ -87,11 +87,11 @@ namespace omath::pathfinding
const auto current_node = current_node_it->second;
closed_list.emplace(current, current_node);
if (current == end_vertex)
return reconstruct_final_path(closed_list, current);
closed_list.emplace(current, current_node);
for (const auto& neighbor: nav_mesh.get_neighbors(current))
{
if (closed_list.contains(neighbor))

View File

@@ -3,9 +3,9 @@
//
#include "omath/pathfinding/navigation_mesh.hpp"
#include <algorithm>
#include <cstring>
#include <limits>
#include <sstream>
#include <stdexcept>
namespace omath::pathfinding
{
std::expected<Vector3<float>, std::string>
@@ -30,77 +30,72 @@ namespace omath::pathfinding
return m_vertex_map.empty();
}
std::vector<uint8_t> NavigationMesh::serialize() const noexcept
void NavigationMesh::set_event(const Vector3<float>& vertex, const std::string_view& event_id)
{
std::vector<std::uint8_t> raw;
if (!m_vertex_map.contains(vertex))
throw std::invalid_argument(std::format("Vertex '{}' not found", vertex));
// Pre-calculate total size for better performance
std::size_t total_size = 0;
for (const auto& [vertex, neighbors] : m_vertex_map)
{
total_size += sizeof(vertex) + sizeof(std::uint16_t) + sizeof(Vector3<float>) * neighbors.size();
}
raw.reserve(total_size);
auto dump_to_vector = [&raw]<typename T>(const T& t)
{
const auto* byte_ptr = reinterpret_cast<const std::uint8_t*>(&t);
raw.insert(raw.end(), byte_ptr, byte_ptr + sizeof(T));
};
for (const auto& [vertex, neighbors] : m_vertex_map)
{
// Clamp neighbors count to fit in uint16_t (prevents silent data corruption)
// NOTE: If neighbors.size() > 65535, only the first 65535 neighbors will be serialized.
// This is a limitation of the current serialization format using uint16_t for count.
const auto clamped_count =
std::min<std::size_t>(neighbors.size(), std::numeric_limits<std::uint16_t>::max());
const auto neighbors_count = static_cast<std::uint16_t>(clamped_count);
dump_to_vector(vertex);
dump_to_vector(neighbors_count);
// Only serialize up to the clamped count
for (std::size_t i = 0; i < clamped_count; ++i)
dump_to_vector(neighbors[i]);
}
return raw;
m_vertex_events[vertex] = event_id;
}
void NavigationMesh::deserialize(const std::vector<uint8_t>& raw) noexcept
void NavigationMesh::clear_event(const Vector3<float>& vertex)
{
auto load_from_vector = [](const std::vector<uint8_t>& vec, std::size_t& offset, auto& value)
m_vertex_events.erase(vertex);
}
std::optional<std::string> NavigationMesh::get_event(const Vector3<float>& vertex) const noexcept
{
const auto it = m_vertex_events.find(vertex);
if (it == m_vertex_events.end())
return std::nullopt;
return it->second;
}
// Serialization format per vertex line:
// x y z neighbor_count event_id
// where event_id is "-" when no event is set.
// Neighbor lines follow: nx ny nz
std::string NavigationMesh::serialize() const noexcept
{
std::ostringstream oss;
for (const auto& [vertex, neighbors] : m_vertex_map)
{
if (offset + sizeof(value) > vec.size())
throw std::runtime_error("Deserialize: Invalid input data size.");
const auto event_it = m_vertex_events.find(vertex);
const std::string& event = (event_it != m_vertex_events.end()) ? event_it->second : "-";
std::copy_n(vec.data() + offset, sizeof(value), reinterpret_cast<uint8_t*>(&value));
offset += sizeof(value);
};
oss << vertex.x << ' ' << vertex.y << ' ' << vertex.z << ' ' << neighbors.size() << ' ' << event << '\n';
for (const auto& n : neighbors)
oss << n.x << ' ' << n.y << ' ' << n.z << '\n';
}
return oss.str();
}
void NavigationMesh::deserialize(const std::string& raw)
{
m_vertex_map.clear();
m_vertex_events.clear();
std::istringstream iss(raw);
std::size_t offset = 0;
while (offset < raw.size())
Vector3<float> vertex;
std::size_t neighbors_count;
std::string event;
while (iss >> vertex.x >> vertex.y >> vertex.z >> neighbors_count >> event)
{
Vector3<float> vertex;
load_from_vector(raw, offset, vertex);
std::uint16_t neighbors_count;
load_from_vector(raw, offset, neighbors_count);
std::vector<Vector3<float>> neighbors;
neighbors.reserve(neighbors_count);
for (std::size_t i = 0; i < neighbors_count; ++i)
{
Vector3<float> neighbor;
load_from_vector(raw, offset, neighbor);
neighbors.push_back(neighbor);
Vector3<float> n;
if (!(iss >> n.x >> n.y >> n.z))
throw std::runtime_error("Deserialize: Unexpected end of data.");
neighbors.push_back(n);
}
m_vertex_map.emplace(vertex, std::move(neighbors));
if (event != "-")
m_vertex_events.emplace(vertex, std::move(event));
}
}
} // namespace omath::pathfinding

View File

@@ -0,0 +1,92 @@
//
// Created by orange on 4/12/2026.
//
#include "omath/pathfinding/walk_bot.hpp"
#include "omath/pathfinding/a_star.hpp"
namespace omath::pathfinding
{
WalkBot::WalkBot(const std::shared_ptr<NavigationMesh>& mesh, const float min_node_distance)
: m_nav_mesh(mesh), m_min_node_distance(min_node_distance) {}
void WalkBot::set_nav_mesh(const std::shared_ptr<NavigationMesh>& mesh)
{
m_nav_mesh = mesh;
}
void WalkBot::set_min_node_distance(const float distance)
{
m_min_node_distance = distance;
}
void WalkBot::set_target(const Vector3<float>& target)
{
m_target = target;
}
void WalkBot::reset()
{
m_last_visited.reset();
}
void WalkBot::update(const Vector3<float>& bot_position)
{
if (!m_target.has_value())
return;
if (m_target->distance_to(bot_position) <= m_min_node_distance)
{
if (m_on_status_update.has_value())
m_on_status_update->operator()(WalkBotStatus::FINISHED);
return;
}
if (!m_on_next_path_node.has_value())
return;
const auto nav_mesh = m_nav_mesh.lock();
if (!nav_mesh)
{
if (m_on_status_update.has_value())
m_on_status_update->operator()(WalkBotStatus::IDLE);
return;
}
const auto path = Astar::find_path(bot_position, *m_target, *nav_mesh);
if (path.empty())
{
if (m_on_status_update.has_value())
m_on_status_update->operator()(WalkBotStatus::IDLE);
return;
}
const auto& nearest = path.front();
// Record the nearest node as visited once we are close enough to it.
if (nearest.distance_to(bot_position) <= m_min_node_distance)
m_last_visited = nearest;
// If the nearest node was already visited, advance to the next one so
// we never oscillate back to a node we just left.
// If the bot was displaced (blown back), nearest will be an unvisited
// node, so we route to it first before continuing forward.
if (m_last_visited.has_value() && *m_last_visited == nearest && path.size() > 1)
m_on_next_path_node->operator()(path[1]);
else
m_on_next_path_node->operator()(nearest);
if (m_on_status_update.has_value())
m_on_status_update->operator()(WalkBotStatus::PATHING);
}
void WalkBot::on_path(const std::function<void(const Vector3<float>&)>& callback)
{
m_on_next_path_node = callback;
}
void WalkBot::on_status(const std::function<void(WalkBotStatus)>& callback)
{
m_on_status_update = callback;
}
} // namespace omath::pathfinding

Some files were not shown because too many files have changed in this diff Show More