mirror of
https://github.com/orange-cpp/omath.git
synced 2026-04-25 13:23:27 +00:00
Compare commits
111 Commits
1744172694
...
feauture/c
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c65781c6f | |||
| 29b49685be | |||
| 92582079c5 | |||
| 13c7f7eb5a | |||
| 65cb803cfb | |||
| 607c034be7 | |||
| 0487e285ef | |||
| 180f2f2afa | |||
| 35bb1bc3c0 | |||
| e62e8672b3 | |||
| 11c053e28c | |||
| 56ebc47553 | |||
| b3ba9eaadf | |||
| 42a8a5a763 | |||
| 2eccb4023f | |||
| 3eb9daf10b | |||
| 27cb511510 | |||
| 27b24b5fe7 | |||
| 4186ae8d76 | |||
| 8e6e3211c2 | |||
| 1c0619ff7b | |||
| dfd18e96fb | |||
| 20930c629a | |||
| 0845a2e863 | |||
| f3f454b02e | |||
| 0419043720 | |||
| 79f64d9679 | |||
| dbe29926dc | |||
| 9d30446c55 | |||
| ba80aebfae | |||
| 9c1b6d0ba3 | |||
| ea07d17dbb | |||
| bb974da0e2 | |||
| fde764c1fa | |||
|
|
28e86fc355 | ||
|
|
93e7a9457a | ||
| 8f65183882 | |||
| 327db8d441 | |||
| d8188de736 | |||
| 33cd3f64e4 | |||
| 67a07eed45 | |||
| 0b52b2847b | |||
| d38895e4d7 | |||
| 04203d46ff | |||
| bcbb5c1a8d | |||
| ba46c86664 | |||
| 3b0470cc11 | |||
| 8562c5d1f2 | |||
| 8daba25c29 | |||
| 29b7ac6450 | |||
| 89df10b778 | |||
| 8fb96b83db | |||
| 4b6db0c402 | |||
| a9ff7868cf | |||
| be80a5d243 | |||
| 881d3b9a2a | |||
| f60e18b6ba | |||
| 0769d3d079 | |||
| b6755e21f9 | |||
| 2287602fa2 | |||
| 663890706e | |||
| ab103f626b | |||
| cc4e01b100 | |||
| 308f7ed481 | |||
| 8802ad9af1 | |||
| 2ac508d6e8 | |||
| eb1ca6055b | |||
| b528e41de3 | |||
| 8615ab2b7c | |||
| 5a4c042fec | |||
| 8063c1697a | |||
| 7567501f00 | |||
| 46d999f846 | |||
| b54601132b | |||
| 5c8ce2d163 | |||
| 04a86739b4 | |||
| 575b411863 | |||
| 5a91151bc0 | |||
| 66d4df0524 | |||
| 54e14760ca | |||
| ee61c47d7d | |||
| d737aee1c5 | |||
| ef422f0a86 | |||
| e99ca0bc2b | |||
| 5f94e36965 | |||
| 29510cf9e7 | |||
| 927508a76b | |||
| f390b386d7 | |||
| 012d837e8b | |||
| 6236c8fd68 | |||
| 06dc36089f | |||
| 91136a61c4 | |||
| 9cdffcbdb1 | |||
| a3e93ac259 | |||
| 59f6d7a361 | |||
| dcf1ef1ea9 | |||
| 89bd879187 | |||
| aa08c7cb65 | |||
| a5c0ca0cbd | |||
| 624683aed6 | |||
| f46672b2c6 | |||
| b8e61f49fa | |||
| 37ea091282 | |||
| 29a2743728 | |||
| 1117eb37f1 | |||
| b6b0d4db13 | |||
| 2e8a74aaaf | |||
| d8632dc74c | |||
| fd531c930c | |||
| a91673216d | |||
| 6487554844 |
@@ -12,6 +12,7 @@ AlignConsecutiveMacros: AcrossEmptyLinesAndComments
|
|||||||
AlignTrailingComments: false
|
AlignTrailingComments: false
|
||||||
AllowShortBlocksOnASingleLine: Never
|
AllowShortBlocksOnASingleLine: Never
|
||||||
AllowShortFunctionsOnASingleLine: None
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortLambdasOnASingleLine: None
|
||||||
AllowShortIfStatementsOnASingleLine: false
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
AllowShortLoopsOnASingleLine: false
|
AllowShortLoopsOnASingleLine: false
|
||||||
BreakTemplateDeclarations: Leave
|
BreakTemplateDeclarations: Leave
|
||||||
|
|||||||
3
.github/workflows/cmake-multi-platform.yml
vendored
3
.github/workflows/cmake-multi-platform.yml
vendored
@@ -370,6 +370,8 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cmake --preset ${{ matrix.preset }} \
|
cmake --preset ${{ matrix.preset }} \
|
||||||
|
-DCMAKE_C_COMPILER=$(xcrun --find clang) \
|
||||||
|
-DCMAKE_CXX_COMPILER=$(xcrun --find clang++) \
|
||||||
-DOMATH_BUILD_TESTS=ON \
|
-DOMATH_BUILD_TESTS=ON \
|
||||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||||
-DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || '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
|
run: cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||||
|
|
||||||
- name: Run unit_tests
|
- name: Run unit_tests
|
||||||
|
if: ${{ matrix.coverage != true }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: ./out/Release/unit_tests
|
run: ./out/Release/unit_tests
|
||||||
|
|
||||||
|
|||||||
62
.github/workflows/docs.yml
vendored
Normal file
62
.github/workflows/docs.yml
vendored
Normal 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
|
||||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -12,6 +12,35 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
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
|
# 1) Linux – Clang / Ninja
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
10
.idea/editor.xml
generated
10
.idea/editor.xml
generated
@@ -17,7 +17,7 @@
|
|||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
|
<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/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" 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/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@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" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
@@ -103,14 +103,14 @@
|
|||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="HINT" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
<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/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" 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/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" 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" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
@@ -216,7 +216,7 @@
|
|||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
## 🤝 Contributing to OMath or other Orange's Projects
|
# Contributing
|
||||||
|
|
||||||
### ❕ Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- A working up-to-date OMath installation
|
- C++ compiler with C++23 support (Clang 18+, GCC 14+, MSVC 19.38+)
|
||||||
- C++ knowledge
|
- CMake 3.25+
|
||||||
- Git knowledge
|
- Git
|
||||||
- Ability to ask for help (Feel free to create empty pull-request or PM a maintainer
|
- Familiarity with the codebase (see `INSTALL.md` for setup)
|
||||||
in [Telegram](https://t.me/orange_cpp))
|
|
||||||
|
|
||||||
### ⏬ 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
|
## Code Style
|
||||||
account ([fork](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)) and
|
|
||||||
then [create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) back to OMath.
|
|
||||||
|
|
||||||
OMath development is performed on multiple branches. Changes are then pull requested into master. By default, changes
|
Follow the project `.clang-format`. Run `clang-format` before committing.
|
||||||
merged into master will not roll out to stable build users unless the `stable` tag is updated.
|
|
||||||
|
|
||||||
### 📜 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
|
Run `cmake --list-presets` to see available configurations.
|
||||||
can build OMath by running `cmake --build cmake-build/build/windows-release --target omath -j 6` in the source
|
|
||||||
directory.
|
## Tests
|
||||||
|
|
||||||
|
All new functionality must include unit tests. Run the test binary after building to verify nothing is broken.
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
Thanks to everyone who made this possible, including:
|
Thanks to everyone who made this possible, including:
|
||||||
|
|
||||||
- Saikari aka luadebug for VCPKG port and awesome new initial logo design.
|
- 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.
|
- 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
|
- Alex2772 for reference of AUI declarative interface design for omath::hud
|
||||||
|
|
||||||
|
|||||||
23
INSTALL.md
23
INSTALL.md
@@ -28,6 +28,29 @@ target("...")
|
|||||||
add_packages("omath")
|
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)
|
## <img width="28px" src="https://github.githubassets.com/favicons/favicon.svg" /> Using prebuilt binaries (GitHub Releases)
|
||||||
|
|
||||||
**Note**: This is the fastest option if you don’t want to build from source.
|
**Note**: This is the fastest option if you don’t want to build from source.
|
||||||
|
|||||||
@@ -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
|
**Note**: Support vcpkg for package management
|
||||||
1. Install [vcpkg](https://github.com/microsoft/vcpkg)
|
1. Install [vcpkg](https://github.com/microsoft/vcpkg)
|
||||||
2. Run the following command to install the orange-math package:
|
2. Run the following command to install the orange-math package:
|
||||||
@@ -28,6 +28,69 @@ target("...")
|
|||||||
add_packages("omath")
|
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 don’t want to build from source.
|
||||||
|
|
||||||
|
1. **Go to the Releases page**
|
||||||
|
- Open the project’s 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 there’s 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
|
## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake
|
||||||
1. **Preparation**
|
1. **Preparation**
|
||||||
|
|
||||||
|
|||||||
@@ -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(x + x1 * w);
|
||||||
lines.push_back(y + y1 * h);
|
lines.push_back(y + y1 * h);
|
||||||
lines.push_back(0.0f);
|
lines.push_back(0.0f);
|
||||||
lines.push_back(color.x);
|
lines.push_back(color.value().x);
|
||||||
lines.push_back(color.y);
|
lines.push_back(color.value().y);
|
||||||
lines.push_back(color.z);
|
lines.push_back(color.value().z);
|
||||||
lines.push_back(1.0f); // size
|
lines.push_back(1.0f); // size
|
||||||
lines.push_back(1.0f); // isLine
|
lines.push_back(1.0f); // isLine
|
||||||
|
|
||||||
lines.push_back(x + x2 * w);
|
lines.push_back(x + x2 * w);
|
||||||
lines.push_back(y + y2 * h);
|
lines.push_back(y + y2 * h);
|
||||||
lines.push_back(0.0f);
|
lines.push_back(0.0f);
|
||||||
lines.push_back(color.x);
|
lines.push_back(color.value().x);
|
||||||
lines.push_back(color.y);
|
lines.push_back(color.value().y);
|
||||||
lines.push_back(color.z);
|
lines.push_back(color.value().z);
|
||||||
lines.push_back(1.0f); // size
|
lines.push_back(1.0f); // size
|
||||||
lines.push_back(1.0f); // isLine
|
lines.push_back(1.0f); // isLine
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -318,22 +318,22 @@ int main()
|
|||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
omath::Vector3<float> move_dir;
|
omath::Vector3<float> move_dir;
|
||||||
if (glfwGetKey(window, GLFW_KEY_W))
|
if (glfwGetKey(window, GLFW_KEY_W))
|
||||||
move_dir += camera.get_forward();
|
move_dir += camera.get_abs_forward();
|
||||||
|
|
||||||
if (glfwGetKey(window, GLFW_KEY_A))
|
if (glfwGetKey(window, GLFW_KEY_A))
|
||||||
move_dir -= camera.get_right();
|
move_dir -= camera.get_abs_right();
|
||||||
|
|
||||||
if (glfwGetKey(window, GLFW_KEY_S))
|
if (glfwGetKey(window, GLFW_KEY_S))
|
||||||
move_dir -= camera.get_forward();
|
move_dir -= camera.get_abs_forward();
|
||||||
|
|
||||||
if (glfwGetKey(window, GLFW_KEY_D))
|
if (glfwGetKey(window, GLFW_KEY_D))
|
||||||
move_dir += camera.get_right();
|
move_dir += camera.get_abs_right();
|
||||||
|
|
||||||
if (glfwGetKey(window, GLFW_KEY_SPACE))
|
if (glfwGetKey(window, GLFW_KEY_SPACE))
|
||||||
move_dir += camera.get_up();
|
move_dir += camera.get_abs_up();
|
||||||
|
|
||||||
if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL))
|
if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL))
|
||||||
move_dir -= camera.get_up();
|
move_dir -= camera.get_abs_up();
|
||||||
|
|
||||||
|
|
||||||
auto delta = glfwGetTime() - old_mouse_time;
|
auto delta = glfwGetTime() - old_mouse_time;
|
||||||
|
|||||||
@@ -138,6 +138,43 @@ namespace imgui_desktop::gui
|
|||||||
ImGui::SliderFloat("Thick##skel", &m_skel_thickness, 0.5f, 5.f);
|
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"))
|
if (ImGui::CollapsingHeader("Snap Line"))
|
||||||
{
|
{
|
||||||
ImGui::Checkbox("Show##snap", &m_show_snap);
|
ImGui::Checkbox("Show##snap", &m_show_snap);
|
||||||
@@ -166,9 +203,7 @@ namespace imgui_desktop::gui
|
|||||||
when(m_show_cornered_box, CorneredBox{omath::Color::from_rgba(255, 0, 255, 255), m_box_fill,
|
when(m_show_cornered_box, CorneredBox{omath::Color::from_rgba(255, 0, 255, 255), m_box_fill,
|
||||||
m_corner_ratio, m_box_thickness}),
|
m_corner_ratio, m_box_thickness}),
|
||||||
when(m_show_dashed_box, DashedBox{m_dash_color, m_dash_len, m_dash_gap, m_dash_thickness}),
|
when(m_show_dashed_box, DashedBox{m_dash_color, m_dash_len, m_dash_gap, m_dash_thickness}),
|
||||||
|
RightSide{
|
||||||
RightSide
|
|
||||||
{
|
|
||||||
when(m_show_right_bar, bar),
|
when(m_show_right_bar, bar),
|
||||||
when(m_show_right_dashed_bar, dbar),
|
when(m_show_right_dashed_bar, dbar),
|
||||||
when(m_show_right_labels,
|
when(m_show_right_labels,
|
||||||
@@ -177,9 +212,12 @@ namespace imgui_desktop::gui
|
|||||||
Label{{1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: 125/125"}),
|
Label{{1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: 125/125"}),
|
||||||
when(m_show_right_labels,
|
when(m_show_right_labels,
|
||||||
Label{{1.f, 0.f, 1.f, 1.f}, m_label_offset, m_outlined, "*LOCKED*"}),
|
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
|
LeftSide{
|
||||||
{
|
|
||||||
when(m_show_left_bar, bar),
|
when(m_show_left_bar, bar),
|
||||||
when(m_show_left_dashed_bar, dbar),
|
when(m_show_left_dashed_bar, dbar),
|
||||||
when(m_show_left_labels, Label{omath::Color::from_rgba(255, 128, 0, 255),
|
when(m_show_left_labels, Label{omath::Color::from_rgba(255, 128, 0, 255),
|
||||||
@@ -187,8 +225,7 @@ namespace imgui_desktop::gui
|
|||||||
when(m_show_left_labels, Label{omath::Color::from_rgba(0, 200, 255, 255),
|
when(m_show_left_labels, Label{omath::Color::from_rgba(0, 200, 255, 255),
|
||||||
m_label_offset, m_outlined, "Level: 42"}),
|
m_label_offset, m_outlined, "Level: 42"}),
|
||||||
},
|
},
|
||||||
TopSide
|
TopSide{
|
||||||
{
|
|
||||||
when(m_show_top_bar, bar),
|
when(m_show_top_bar, bar),
|
||||||
when(m_show_top_dashed_bar, dbar),
|
when(m_show_top_dashed_bar, dbar),
|
||||||
when(m_show_centered_top, Centered{Label{omath::Color::from_rgba(0, 255, 255, 255),
|
when(m_show_centered_top, Centered{Label{omath::Color::from_rgba(0, 255, 255, 255),
|
||||||
@@ -198,8 +235,7 @@ namespace imgui_desktop::gui
|
|||||||
when(m_show_top_labels, Label{omath::Color::from_rgba(255, 0, 0, 255), m_label_offset,
|
when(m_show_top_labels, Label{omath::Color::from_rgba(255, 0, 0, 255), m_label_offset,
|
||||||
m_outlined, "*BLEEDING*"}),
|
m_outlined, "*BLEEDING*"}),
|
||||||
},
|
},
|
||||||
BottomSide
|
BottomSide{
|
||||||
{
|
|
||||||
when(m_show_bottom_bar, bar),
|
when(m_show_bottom_bar, bar),
|
||||||
when(m_show_bottom_dashed_bar, dbar),
|
when(m_show_bottom_dashed_bar, dbar),
|
||||||
when(m_show_centered_bottom, Centered{Label{omath::Color::from_rgba(255, 255, 255, 255),
|
when(m_show_centered_bottom, Centered{Label{omath::Color::from_rgba(255, 255, 255, 255),
|
||||||
@@ -207,7 +243,10 @@ namespace imgui_desktop::gui
|
|||||||
when(m_show_bottom_labels, Label{omath::Color::from_rgba(200, 200, 0, 255),
|
when(m_show_bottom_labels, Label{omath::Color::from_rgba(200, 200, 0, 255),
|
||||||
m_label_offset, m_outlined, "42m"}),
|
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_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}));
|
when(m_show_snap, SnapLine{{vp->Size.x / 2.f, vp->Size.y}, m_snap_color, m_snap_width}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,9 +61,34 @@ namespace imgui_desktop::gui
|
|||||||
float m_skel_thickness = 1.f;
|
float m_skel_thickness = 1.f;
|
||||||
bool m_show_skeleton = false;
|
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
|
// Snap line
|
||||||
omath::Color m_snap_color = omath::Color::from_rgba(255, 50, 50, 255);
|
omath::Color m_snap_color = omath::Color::from_rgba(255, 50, 50, 255);
|
||||||
float m_snap_width = 1.5f;
|
float m_snap_width = 1.5f;
|
||||||
bool m_show_snap = true;
|
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
|
} // namespace imgui_desktop::gui
|
||||||
|
|||||||
67
include/omath/3d_primitives/aabb.hpp
Normal file
67
include/omath/3d_primitives/aabb.hpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// Created by Vladislav on 24.03.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "omath/linear_algebra/vector3.hpp"
|
||||||
|
|
||||||
|
namespace omath::primitives
|
||||||
|
{
|
||||||
|
enum class UpAxis { X, Y, Z };
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<UpAxis Up = UpAxis::Y>
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr Vector3<Type> top() const noexcept
|
||||||
|
{
|
||||||
|
const auto aabb_center = center();
|
||||||
|
if constexpr (Up == UpAxis::Z)
|
||||||
|
return {aabb_center.x, aabb_center.y, max.z};
|
||||||
|
else if constexpr (Up == UpAxis::X)
|
||||||
|
return {max.x, aabb_center.y, aabb_center.z};
|
||||||
|
else if constexpr (Up == UpAxis::Y)
|
||||||
|
return {aabb_center.x, max.y, aabb_center.z};
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<UpAxis Up = UpAxis::Y>
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr Vector3<Type> bottom() const noexcept
|
||||||
|
{
|
||||||
|
const auto aabb_center = center();
|
||||||
|
if constexpr (Up == UpAxis::Z)
|
||||||
|
return {aabb_center.x, aabb_center.y, min.z};
|
||||||
|
else if constexpr (Up == UpAxis::X)
|
||||||
|
return {min.x, aabb_center.y, aabb_center.z};
|
||||||
|
else if constexpr (Up == UpAxis::Y)
|
||||||
|
return {aabb_center.x, min.y, aabb_center.z};
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr bool is_collide(const Aabb& other) const noexcept
|
||||||
|
{
|
||||||
|
return min.x <= other.max.x && max.x >= other.min.x &&
|
||||||
|
min.y <= other.max.y && max.y >= other.min.y &&min.z <= other.max.z && max.z >= other.min.z;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace omath::primitives
|
||||||
98
include/omath/algorithm/targeting.hpp
Normal file
98
include/omath/algorithm/targeting.hpp
Normal 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
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "omath/3d_primitives/aabb.hpp"
|
||||||
#include "omath/linear_algebra/triangle.hpp"
|
#include "omath/linear_algebra/triangle.hpp"
|
||||||
#include "omath/linear_algebra/vector3.hpp"
|
#include "omath/linear_algebra/vector3.hpp"
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ namespace omath::collision
|
|||||||
class LineTracer final
|
class LineTracer final
|
||||||
{
|
{
|
||||||
using TriangleType = Triangle<typename RayType::VectorType>;
|
using TriangleType = Triangle<typename RayType::VectorType>;
|
||||||
|
using AABBType = primitives::Aabb<typename RayType::VectorType::ContainedType>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LineTracer() = delete;
|
LineTracer() = delete;
|
||||||
@@ -87,6 +89,54 @@ namespace omath::collision
|
|||||||
return ray.start + ray_dir * t_hit;
|
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>
|
template<class MeshType>
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
constexpr static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept
|
constexpr static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
|
|
||||||
namespace omath::cry_engine
|
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
|
} // namespace omath::cry_engine
|
||||||
@@ -22,7 +22,8 @@ namespace omath::cry_engine
|
|||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[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>
|
template<class FloatingType>
|
||||||
requires std::is_floating_point_v<FloatingType>
|
requires std::is_floating_point_v<FloatingType>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace omath::cry_engine
|
|||||||
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
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
|
} // namespace omath::cry_engine
|
||||||
@@ -16,7 +16,8 @@ namespace omath::cry_engine
|
|||||||
const float pitch, const float yaw,
|
const float pitch, const float yaw,
|
||||||
const float time, const float gravity) noexcept
|
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),
|
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
||||||
RollAngle::from_degrees(0)})
|
RollAngle::from_degrees(0)})
|
||||||
* projectile.m_launch_speed * time;
|
* projectile.m_launch_speed * time;
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
|
|
||||||
namespace omath::frostbite_engine
|
namespace omath::frostbite_engine
|
||||||
{
|
{
|
||||||
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>;
|
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::ZERO_TO_ONE>;
|
||||||
} // namespace omath::unity_engine
|
} // namespace omath::frostbite_engine
|
||||||
@@ -22,7 +22,8 @@ namespace omath::frostbite_engine
|
|||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[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>
|
template<class FloatingType>
|
||||||
requires std::is_floating_point_v<FloatingType>
|
requires std::is_floating_point_v<FloatingType>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace omath::frostbite_engine
|
|||||||
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
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
|
} // namespace omath::unreal_engine
|
||||||
@@ -16,7 +16,8 @@ namespace omath::frostbite_engine
|
|||||||
const float pitch, const float yaw,
|
const float pitch, const float yaw,
|
||||||
const float time, const float gravity) noexcept
|
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),
|
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
||||||
RollAngle::from_degrees(0)})
|
RollAngle::from_degrees(0)})
|
||||||
* projectile.m_launch_speed * time;
|
* projectile.m_launch_speed * time;
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
|
|
||||||
namespace omath::iw_engine
|
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
|
} // namespace omath::iw_engine
|
||||||
@@ -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_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[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>
|
template<class FloatingType>
|
||||||
requires std::is_floating_point_v<FloatingType>
|
requires std::is_floating_point_v<FloatingType>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace omath::iw_engine
|
|||||||
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
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
|
} // namespace omath::iw_engine
|
||||||
@@ -17,7 +17,8 @@ namespace omath::iw_engine
|
|||||||
const float pitch, const float yaw,
|
const float pitch, const float yaw,
|
||||||
const float time, const float gravity) noexcept
|
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),
|
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
||||||
RollAngle::from_degrees(0)})
|
RollAngle::from_degrees(0)})
|
||||||
* projectile.m_launch_speed * time;
|
* projectile.m_launch_speed * time;
|
||||||
|
|||||||
@@ -8,5 +8,5 @@
|
|||||||
|
|
||||||
namespace omath::opengl_engine
|
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
|
} // namespace omath::opengl_engine
|
||||||
@@ -21,7 +21,8 @@ namespace omath::opengl_engine
|
|||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[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>
|
template<class FloatingType>
|
||||||
requires std::is_floating_point_v<FloatingType>
|
requires std::is_floating_point_v<FloatingType>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace omath::opengl_engine
|
|||||||
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
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
|
} // namespace omath::opengl_engine
|
||||||
@@ -16,7 +16,8 @@ namespace omath::opengl_engine
|
|||||||
const float pitch, const float yaw,
|
const float pitch, const float yaw,
|
||||||
const float time, const float gravity) noexcept
|
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),
|
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
||||||
RollAngle::from_degrees(0)})
|
RollAngle::from_degrees(0)})
|
||||||
* projectile.m_launch_speed * time;
|
* projectile.m_launch_speed * time;
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
#include "traits/camera_trait.hpp"
|
#include "traits/camera_trait.hpp"
|
||||||
namespace omath::source_engine
|
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
|
} // namespace omath::source_engine
|
||||||
@@ -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_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[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>
|
template<class FloatingType>
|
||||||
requires std::is_floating_point_v<FloatingType>
|
requires std::is_floating_point_v<FloatingType>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace omath::source_engine
|
|||||||
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
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
|
} // namespace omath::source_engine
|
||||||
@@ -17,7 +17,8 @@ namespace omath::source_engine
|
|||||||
const float pitch, const float yaw,
|
const float pitch, const float yaw,
|
||||||
const float time, const float gravity) noexcept
|
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),
|
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
||||||
RollAngle::from_degrees(0)})
|
RollAngle::from_degrees(0)})
|
||||||
* projectile.m_launch_speed * time;
|
* projectile.m_launch_speed * time;
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
|
|
||||||
namespace omath::unity_engine
|
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
|
} // namespace omath::unity_engine
|
||||||
@@ -22,7 +22,8 @@ namespace omath::unity_engine
|
|||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[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>
|
template<class FloatingType>
|
||||||
requires std::is_floating_point_v<FloatingType>
|
requires std::is_floating_point_v<FloatingType>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace omath::unity_engine
|
|||||||
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
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
|
} // namespace omath::unity_engine
|
||||||
@@ -16,7 +16,8 @@ namespace omath::unity_engine
|
|||||||
const float pitch, const float yaw,
|
const float pitch, const float yaw,
|
||||||
const float time, const float gravity) noexcept
|
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),
|
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
||||||
RollAngle::from_degrees(0)})
|
RollAngle::from_degrees(0)})
|
||||||
* projectile.m_launch_speed * time;
|
* projectile.m_launch_speed * time;
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
|
|
||||||
namespace omath::unreal_engine
|
namespace omath::unreal_engine
|
||||||
{
|
{
|
||||||
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait>;
|
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::ZERO_TO_ONE, {}, double>;
|
||||||
} // namespace omath::unreal_engine
|
} // namespace omath::unreal_engine
|
||||||
@@ -11,16 +11,16 @@
|
|||||||
|
|
||||||
namespace omath::unreal_engine
|
namespace omath::unreal_engine
|
||||||
{
|
{
|
||||||
constexpr Vector3<float> k_abs_up = {0, 0, 1};
|
constexpr Vector3<double> k_abs_up = {0, 0, 1};
|
||||||
constexpr Vector3<float> k_abs_right = {0, 1, 0};
|
constexpr Vector3<double> k_abs_right = {0, 1, 0};
|
||||||
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
|
constexpr Vector3<double> k_abs_forward = {1, 0, 0};
|
||||||
|
|
||||||
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
using Mat4X4 = Mat<4, 4, double, MatStoreType::ROW_MAJOR>;
|
||||||
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
using Mat3X3 = Mat<4, 4, double, MatStoreType::ROW_MAJOR>;
|
||||||
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
using Mat1X3 = Mat<1, 3, double, MatStoreType::ROW_MAJOR>;
|
||||||
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
|
using PitchAngle = Angle<double, -90., 90., AngleFlags::Clamped>;
|
||||||
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
using YawAngle = Angle<double, -180., 180., AngleFlags::Normalized>;
|
||||||
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
using RollAngle = Angle<double, -180., 180., AngleFlags::Normalized>;
|
||||||
|
|
||||||
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||||
} // namespace omath::unreal_engine
|
} // namespace omath::unreal_engine
|
||||||
|
|||||||
@@ -8,21 +8,22 @@
|
|||||||
namespace omath::unreal_engine
|
namespace omath::unreal_engine
|
||||||
{
|
{
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
Vector3<double> forward_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
Vector3<double> right_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
Vector3<double> up_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
|
Mat4X4 calc_perspective_projection_matrix(double field_of_view, double aspect_ratio, double near, double far,
|
||||||
|
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
||||||
|
|
||||||
template<class FloatingType>
|
template<class FloatingType>
|
||||||
requires std::is_floating_point_v<FloatingType>
|
requires std::is_floating_point_v<FloatingType>
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ namespace omath::unreal_engine
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept;
|
static ViewAngles calc_look_at_angle(const Vector3<double>& cam_origin, const Vector3<double>& look_at) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
||||||
float near, float far) noexcept;
|
double near, double far, NDCDepthRange ndc_depth_range) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace omath::unreal_engine
|
} // namespace omath::unreal_engine
|
||||||
@@ -16,9 +16,12 @@ namespace omath::unreal_engine
|
|||||||
const float pitch, const float yaw,
|
const float pitch, const float yaw,
|
||||||
const float time, const float gravity) noexcept
|
const float time, const float gravity) noexcept
|
||||||
{
|
{
|
||||||
auto current_pos = projectile.m_origin
|
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
|
||||||
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
const auto fwd_d = forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
||||||
RollAngle::from_degrees(0)})
|
RollAngle::from_degrees(0)});
|
||||||
|
auto current_pos = launch_pos
|
||||||
|
+ Vector3<float>{static_cast<float>(fwd_d.x), static_cast<float>(fwd_d.y),
|
||||||
|
static_cast<float>(fwd_d.z)}
|
||||||
* projectile.m_launch_speed * time;
|
* projectile.m_launch_speed * time;
|
||||||
current_pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
|
current_pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace omath::hud
|
|||||||
const std::string_view& text);
|
const std::string_view& text);
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
EntityOverlay& add_right_label(const Color& color, float offset, bool outlined, std::format_string<Args...> fmt,
|
EntityOverlay& add_right_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
|
||||||
Args&&... args)
|
Args&&... args)
|
||||||
{
|
{
|
||||||
return add_right_label(color, offset, outlined,
|
return add_right_label(color, offset, outlined,
|
||||||
@@ -79,7 +79,7 @@ namespace omath::hud
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
EntityOverlay& add_left_label(const Color& color, float offset, bool outlined, std::format_string<Args...> fmt,
|
EntityOverlay& add_left_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
|
||||||
Args&&... args)
|
Args&&... args)
|
||||||
{
|
{
|
||||||
return add_left_label(color, offset, outlined,
|
return add_left_label(color, offset, outlined,
|
||||||
@@ -87,7 +87,7 @@ namespace omath::hud
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
EntityOverlay& add_top_label(const Color& color, float offset, bool outlined, std::format_string<Args...> fmt,
|
EntityOverlay& add_top_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
|
||||||
Args&&... args)
|
Args&&... args)
|
||||||
{
|
{
|
||||||
return add_top_label(color, offset, outlined,
|
return add_top_label(color, offset, outlined,
|
||||||
@@ -95,7 +95,7 @@ namespace omath::hud
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
EntityOverlay& add_bottom_label(const Color& color, float offset, bool outlined,
|
EntityOverlay& add_bottom_label(const Color& color, const float offset, const bool outlined,
|
||||||
std::format_string<Args...> fmt, Args&&... args)
|
std::format_string<Args...> fmt, Args&&... args)
|
||||||
{
|
{
|
||||||
return add_bottom_label(color, offset, outlined,
|
return add_bottom_label(color, offset, outlined,
|
||||||
@@ -103,7 +103,7 @@ namespace omath::hud
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
EntityOverlay& add_centered_top_label(const Color& color, float offset, bool outlined,
|
EntityOverlay& add_centered_top_label(const Color& color, const float offset, const bool outlined,
|
||||||
std::format_string<Args...> fmt, Args&&... args)
|
std::format_string<Args...> fmt, Args&&... args)
|
||||||
{
|
{
|
||||||
return add_centered_top_label(color, offset, outlined,
|
return add_centered_top_label(color, offset, outlined,
|
||||||
@@ -111,13 +111,43 @@ namespace omath::hud
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
EntityOverlay& add_centered_bottom_label(const Color& color, float offset, bool outlined,
|
EntityOverlay& add_centered_bottom_label(const Color& color, const float offset, const bool outlined,
|
||||||
std::format_string<Args...> fmt, Args&&... args)
|
std::format_string<Args...> fmt, Args&&... args)
|
||||||
{
|
{
|
||||||
return add_centered_bottom_label(color, offset, outlined,
|
return add_centered_bottom_label(color, offset, outlined,
|
||||||
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
|
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 ─────────────────────────────────────────────────────────
|
// ── Misc ─────────────────────────────────────────────────────────
|
||||||
EntityOverlay& add_snap_line(const Vector2<float>& start_pos, const Color& color, float width);
|
EntityOverlay& add_snap_line(const Vector2<float>& start_pos, const Color& color, float width);
|
||||||
|
|
||||||
@@ -142,15 +172,19 @@ namespace omath::hud
|
|||||||
dispatch(*w);
|
dispatch(*w);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispatch(const widget::Box& w);
|
void dispatch(const widget::Box& box);
|
||||||
void dispatch(const widget::CorneredBox& w);
|
void dispatch(const widget::CorneredBox& cornered_box);
|
||||||
void dispatch(const widget::DashedBox& w);
|
void dispatch(const widget::DashedBox& dashed_box);
|
||||||
void dispatch(const widget::RightSide& w);
|
void dispatch(const widget::RightSide& right_side);
|
||||||
void dispatch(const widget::LeftSide& w);
|
void dispatch(const widget::LeftSide& left_side);
|
||||||
void dispatch(const widget::TopSide& w);
|
void dispatch(const widget::TopSide& top_side);
|
||||||
void dispatch(const widget::BottomSide& w);
|
void dispatch(const widget::BottomSide& bottom_side);
|
||||||
void dispatch(const widget::Skeleton& w);
|
void dispatch(const widget::Skeleton& skeleton);
|
||||||
void dispatch(const widget::SnapLine& w);
|
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_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,
|
void draw_dashed_line(const Vector2<float>& from, const Vector2<float>& to, const Color& color, float dash_len,
|
||||||
float gap_len, float thickness) const;
|
float gap_len, float thickness) const;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "omath/linear_algebra/vector2.hpp"
|
#include "omath/linear_algebra/vector2.hpp"
|
||||||
#include "omath/utility/color.hpp"
|
#include "omath/utility/color.hpp"
|
||||||
|
#include <any>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -56,6 +57,35 @@ namespace omath::hud::widget
|
|||||||
float width;
|
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) ────────────────
|
// ── Side-agnostic widgets (used inside XxxSide containers) ────────────────
|
||||||
|
|
||||||
/// A filled bar. `size` is width for left/right sides, height for top/bottom.
|
/// A filled bar. `size` is width for left/right sides, height for top/bottom.
|
||||||
@@ -99,9 +129,44 @@ namespace omath::hud::widget
|
|||||||
template<typename W>
|
template<typename W>
|
||||||
Centered(W) -> Centered<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 ───────────────────────────────────────────────────
|
// ── Side widget variant ───────────────────────────────────────────────────
|
||||||
struct None {}; ///< No-op placeholder — used by widget::when for disabled elements.
|
struct None
|
||||||
using SideWidget = std::variant<None, Bar, DashedBar, Label, Centered<Label>>;
|
{
|
||||||
|
}; ///< 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 ───────────────────────────────────────────────────────
|
// ── Side containers ───────────────────────────────────────────────────────
|
||||||
// Storing std::initializer_list<SideWidget> is safe here: the backing array
|
// Storing std::initializer_list<SideWidget> is safe here: the backing array
|
||||||
@@ -109,10 +174,34 @@ namespace omath::hud::widget
|
|||||||
// temporary side-container object, which is consumed within the same
|
// temporary side-container object, which is consumed within the same
|
||||||
// full-expression by EntityOverlay::dispatch. No heap allocation occurs.
|
// full-expression by EntityOverlay::dispatch. No heap allocation occurs.
|
||||||
|
|
||||||
struct RightSide { std::initializer_list<SideWidget> children; RightSide(std::initializer_list<SideWidget> c) : children(c) {} };
|
struct RightSide
|
||||||
struct LeftSide { std::initializer_list<SideWidget> children; LeftSide(std::initializer_list<SideWidget> c) : children(c) {} };
|
{
|
||||||
struct TopSide { std::initializer_list<SideWidget> children; TopSide(std::initializer_list<SideWidget> c) : children(c) {} };
|
std::initializer_list<SideWidget> children;
|
||||||
struct BottomSide { std::initializer_list<SideWidget> children; BottomSide(std::initializer_list<SideWidget> c) : children(c) {} };
|
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
|
||||||
|
|
||||||
@@ -124,7 +213,8 @@ namespace omath::hud::widget
|
|||||||
requires std::constructible_from<SideWidget, W>
|
requires std::constructible_from<SideWidget, W>
|
||||||
SideWidget when(const bool condition, W widget)
|
SideWidget when(const bool condition, W widget)
|
||||||
{
|
{
|
||||||
if (condition) return SideWidget{std::move(widget)};
|
if (condition)
|
||||||
|
return SideWidget{std::move(widget)};
|
||||||
return None{};
|
return None{};
|
||||||
}
|
}
|
||||||
} // namespace omath::hud::widget
|
} // namespace omath::hud::widget
|
||||||
@@ -136,7 +226,8 @@ namespace omath::hud
|
|||||||
template<typename W>
|
template<typename W>
|
||||||
std::optional<W> when(const bool condition, W widget)
|
std::optional<W> when(const bool condition, W widget)
|
||||||
{
|
{
|
||||||
if (condition) return std::move(widget);
|
if (condition)
|
||||||
|
return std::move(widget);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
} // namespace omath::hud
|
} // namespace omath::hud
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "omath/linear_algebra/vector2.hpp"
|
#include "omath/linear_algebra/vector2.hpp"
|
||||||
#include "omath/utility/color.hpp"
|
#include "omath/utility/color.hpp"
|
||||||
|
#include <any>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
namespace omath::hud
|
namespace omath::hud
|
||||||
@@ -24,6 +25,20 @@ namespace omath::hud
|
|||||||
|
|
||||||
virtual void add_filled_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;
|
virtual void add_text(const Vector2<float>& position, const Color& color, const std::string_view& text) = 0;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ namespace omath::hud
|
|||||||
void add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color) 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_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_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;
|
void add_text(const Vector2<float>& position, const Color& color, const std::string_view& text) override;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
virtual Vector2<float> calc_text_size(const std::string_view& text) override;
|
virtual Vector2<float> calc_text_size(const std::string_view& text) override;
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ namespace omath
|
|||||||
COLUMN_MAJOR
|
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
|
template<typename M1, typename M2> concept MatTemplateEqual
|
||||||
= (M1::rows == M2::rows) && (M1::columns == M2::columns)
|
= (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);
|
&& std::is_same_v<typename M1::value_type, typename M2::value_type> && (M1::store_type == M2::store_type);
|
||||||
@@ -658,36 +664,117 @@ namespace omath
|
|||||||
} * mat_translation<Type, St>(-camera_origin);
|
} * 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]]
|
[[nodiscard]]
|
||||||
Mat<4, 4, Type, St> mat_perspective_left_handed(const float field_of_view, const float aspect_ratio,
|
Mat<4, 4, Type, St> mat_perspective_left_handed(const Type field_of_view, const Type aspect_ratio,
|
||||||
const float near, const float far) noexcept
|
const Type near, const Type far) noexcept
|
||||||
{
|
{
|
||||||
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f);
|
const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2});
|
||||||
|
|
||||||
return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f},
|
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
|
||||||
{0.f, 1.f / fov_half_tan, 0.f, 0.f},
|
return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}},
|
||||||
{0.f, 0.f, (far + near) / (far - near), -(2.f * near * far) / (far - near)},
|
{Type{0}, Type{1} / fov_half_tan, Type{0}, Type{0}},
|
||||||
{0.f, 0.f, 1.f, 0.f}};
|
{Type{0}, Type{0}, far / (far - near), -(near * far) / (far - near)},
|
||||||
|
{Type{0}, Type{0}, Type{1}, Type{0}}};
|
||||||
|
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
|
return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}},
|
||||||
|
{Type{0}, Type{1} / fov_half_tan, Type{0}, Type{0}},
|
||||||
|
{Type{0}, Type{0}, (far + near) / (far - near), -(Type{2} * near * far) / (far - near)},
|
||||||
|
{Type{0}, Type{0}, Type{1}, Type{0}}};
|
||||||
|
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]]
|
[[nodiscard]]
|
||||||
Mat<4, 4, Type, St> mat_perspective_right_handed(const float field_of_view, const float aspect_ratio,
|
Mat<4, 4, Type, St> mat_perspective_right_handed(const Type field_of_view, const Type aspect_ratio,
|
||||||
const float near, const float far) noexcept
|
const Type near, const Type far) noexcept
|
||||||
{
|
{
|
||||||
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f);
|
const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2});
|
||||||
|
|
||||||
return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f},
|
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
|
||||||
{0.f, 1.f / fov_half_tan, 0.f, 0.f},
|
return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}},
|
||||||
{0.f, 0.f, -(far + near) / (far - near), -(2.f * near * far) / (far - near)},
|
{Type{0}, Type{1} / fov_half_tan, Type{0}, Type{0}},
|
||||||
{0.f, 0.f, -1.f, 0.f}};
|
{Type{0}, Type{0}, -far / (far - near), -(near * far) / (far - near)},
|
||||||
|
{Type{0}, Type{0}, -Type{1}, Type{0}}};
|
||||||
|
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
|
return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}},
|
||||||
|
{Type{0}, Type{1} / fov_half_tan, Type{0}, Type{0}},
|
||||||
|
{Type{0}, Type{0}, -(far + near) / (far - near), -(Type{2} * near * far) / (far - near)},
|
||||||
|
{Type{0}, Type{0}, -Type{1}, Type{0}}};
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
}
|
}
|
||||||
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
|
|
||||||
|
// Horizontal-FOV variants — use these when the engine reports FOV as
|
||||||
|
// horizontal (UE's FMinimalViewInfo::FOV, Quake-family fov_x, etc.).
|
||||||
|
// X and Y scales derived as: X = 1 / tan(hfov/2), Y = aspect / tan(hfov/2).
|
||||||
|
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_horizontal_fov(const Type horizontal_fov,
|
||||||
|
const Type aspect_ratio, const Type near,
|
||||||
|
const Type far) noexcept
|
||||||
|
{
|
||||||
|
const auto inv_tan_half_hfov = Type{1} / std::tan(angles::degrees_to_radians(horizontal_fov) / Type{2});
|
||||||
|
const auto x_axis = inv_tan_half_hfov;
|
||||||
|
const auto y_axis = inv_tan_half_hfov * aspect_ratio;
|
||||||
|
|
||||||
|
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
|
||||||
|
return {{x_axis, Type{0}, Type{0}, Type{0}},
|
||||||
|
{Type{0}, y_axis, Type{0}, Type{0}},
|
||||||
|
{Type{0}, Type{0}, far / (far - near), -(near * far) / (far - near)},
|
||||||
|
{Type{0}, Type{0}, Type{1}, Type{0}}};
|
||||||
|
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
|
return {{x_axis, Type{0}, Type{0}, Type{0}},
|
||||||
|
{Type{0}, y_axis, Type{0}, Type{0}},
|
||||||
|
{Type{0}, Type{0}, (far + near) / (far - near), -(2.f * near * far) / (far - near)},
|
||||||
|
{Type{0}, Type{0}, Type{1}, Type{0}}};
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_horizontal_fov(const Type horizontal_fov,
|
||||||
|
const Type aspect_ratio, const Type near,
|
||||||
|
const Type far) noexcept
|
||||||
|
{
|
||||||
|
const auto inv_tan_half_hfov = Type{1} / std::tan(angles::degrees_to_radians(horizontal_fov) / Type{2});
|
||||||
|
const auto x_axis = inv_tan_half_hfov;
|
||||||
|
const auto y_axis = inv_tan_half_hfov * aspect_ratio;
|
||||||
|
|
||||||
|
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
|
||||||
|
return {{x_axis, Type{0}, Type{0}, Type{0}},
|
||||||
|
{Type{0}, y_axis, Type{0}, Type{0}},
|
||||||
|
{Type{0}, Type{0}, -far / (far - near), -(near * far) / (far - near)},
|
||||||
|
{Type{0}, Type{0}, -Type{1}, Type{0}}};
|
||||||
|
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
|
return {{x_axis, Type{0}, Type{0}, Type{0}},
|
||||||
|
{Type{0}, y_axis, Type{0}, Type{0}},
|
||||||
|
{Type{0}, Type{0}, -(far + near) / (far - near), -(2.f * near * far) / (far - near)},
|
||||||
|
{Type{0}, Type{0}, -Type{1}, Type{0}}};
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat<4, 4, Type, St> mat_ortho_left_handed(const Type left, const Type right, const Type bottom, const Type top,
|
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
|
const Type near, const Type far) noexcept
|
||||||
{
|
{
|
||||||
|
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
|
return
|
||||||
{
|
{
|
||||||
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
|
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
|
||||||
@@ -695,12 +782,24 @@ namespace omath
|
|||||||
{ 0.f, 0.f, static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
|
{ 0.f, 0.f, static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
|
||||||
{ 0.f, 0.f, 0.f, 1.f }
|
{ 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]]
|
[[nodiscard]]
|
||||||
Mat<4, 4, Type, St> mat_ortho_right_handed(const Type left, const Type right, const Type bottom, const Type top,
|
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
|
const Type near, const Type far) noexcept
|
||||||
{
|
{
|
||||||
|
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
|
return
|
||||||
{
|
{
|
||||||
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
|
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)},
|
||||||
@@ -708,6 +807,8 @@ namespace omath
|
|||||||
{ 0.f, 0.f, -static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
|
{ 0.f, 0.f, -static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
|
||||||
{ 0.f, 0.f, 0.f, 1.f }
|
{ 0.f, 0.f, 0.f, 1.f }
|
||||||
};
|
};
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
}
|
}
|
||||||
template<class T = float, MatStoreType St = MatStoreType::COLUMN_MAJOR>
|
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)
|
Mat<4, 4, T, St> mat_look_at_left_handed(const Vector3<T>& eye, const Vector3<T>& center, const Vector3<T>& up)
|
||||||
|
|||||||
46
include/omath/pathfinding/walk_bot.hpp
Normal file
46
include/omath/pathfinding/walk_bot.hpp
Normal 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
|
||||||
@@ -8,12 +8,23 @@
|
|||||||
|
|
||||||
namespace omath::projectile_prediction
|
namespace omath::projectile_prediction
|
||||||
{
|
{
|
||||||
|
struct AimAngles
|
||||||
|
{
|
||||||
|
float pitch{};
|
||||||
|
float yaw{};
|
||||||
|
};
|
||||||
|
|
||||||
class ProjPredEngineInterface
|
class ProjPredEngineInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
virtual std::optional<Vector3<float>> maybe_calculate_aim_point(const Projectile& projectile,
|
virtual std::optional<Vector3<float>> maybe_calculate_aim_point(const Projectile& projectile,
|
||||||
const Target& target) const = 0;
|
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;
|
virtual ~ProjPredEngineInterface() = default;
|
||||||
};
|
};
|
||||||
} // namespace omath::projectile_prediction
|
} // namespace omath::projectile_prediction
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ namespace omath::projectile_prediction
|
|||||||
[[nodiscard]] std::optional<Vector3<float>>
|
[[nodiscard]] std::optional<Vector3<float>>
|
||||||
maybe_calculate_aim_point(const Projectile& projectile, const Target& target) const override;
|
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(float gravity_constant, float simulation_time_step, float maximum_simulation_time);
|
||||||
~ProjPredEngineAvx2() override = default;
|
~ProjPredEngineAvx2() override = default;
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,36 @@ namespace omath::projectile_prediction
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::optional<Vector3<float>> maybe_calculate_aim_point(const Projectile& projectile,
|
std::optional<Vector3<float>> maybe_calculate_aim_point(const Projectile& projectile,
|
||||||
const Target& target) const override
|
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)
|
for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step)
|
||||||
{
|
{
|
||||||
@@ -70,12 +100,11 @@ namespace omath::projectile_prediction
|
|||||||
time))
|
time))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
return EngineTrait::calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch);
|
return Solution{predicted_target_position, projectile_pitch.value()};
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
const float m_gravity_constant;
|
const float m_gravity_constant;
|
||||||
const float m_simulation_time_step;
|
const float m_simulation_time_step;
|
||||||
const float m_maximum_simulation_time;
|
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;
|
const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
|
||||||
|
|
||||||
if (bullet_gravity == 0.f)
|
const auto launch_origin = projectile.m_origin + projectile.m_launch_offset;
|
||||||
return EngineTrait::calc_direct_pitch_angle(projectile.m_origin, target_position);
|
|
||||||
|
|
||||||
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 = EngineTrait::calc_vector_2d_distance(delta);
|
||||||
const auto distance2d_sqr = distance2d * distance2d;
|
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,
|
bool is_projectile_reached_target(const Vector3<float>& target_position, const Projectile& projectile,
|
||||||
const float pitch, const float time) const noexcept
|
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 =
|
const auto projectile_position =
|
||||||
EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);
|
EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace omath::projectile_prediction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Vector3<float> m_origin;
|
Vector3<float> m_origin;
|
||||||
|
Vector3<float> m_launch_offset{0.f, 0.f, 0.f};
|
||||||
float m_launch_speed{};
|
float m_launch_speed{};
|
||||||
float m_gravity_scale{};
|
float m_gravity_scale{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "omath/3d_primitives/aabb.hpp"
|
||||||
#include "omath/linear_algebra/mat.hpp"
|
#include "omath/linear_algebra/mat.hpp"
|
||||||
#include "omath/linear_algebra/triangle.hpp"
|
#include "omath/linear_algebra/triangle.hpp"
|
||||||
#include "omath/linear_algebra/vector3.hpp"
|
#include "omath/linear_algebra/vector3.hpp"
|
||||||
@@ -36,24 +37,37 @@ namespace omath::projection
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
using FieldOfView = Angle<float, 0.f, 180.f, AngleFlags::Clamped>;
|
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>
|
template<class T, class MatType, class ViewAnglesType, class NumericType>
|
||||||
concept CameraEngineConcept =
|
concept CameraEngineConcept =
|
||||||
requires(const Vector3<float>& cam_origin, const Vector3<float>& look_at, const ViewAnglesType& angles,
|
requires(const Vector3<NumericType>& cam_origin, const Vector3<NumericType>& look_at,
|
||||||
const FieldOfView& fov, const ViewPort& viewport, float znear, float zfar) {
|
const ViewAnglesType& angles, const FieldOfView& fov, const ViewPort& viewport, NumericType z_near,
|
||||||
|
NumericType z_far, NDCDepthRange ndc_depth_range) {
|
||||||
// Presence + return types
|
// Presence + return types
|
||||||
{ T::calc_look_at_angle(cam_origin, look_at) } -> std::same_as<ViewAnglesType>;
|
{ 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_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, z_near, z_far, ndc_depth_range) } -> std::same_as<MatType>;
|
||||||
|
requires std::is_floating_point_v<NumericType>;
|
||||||
// Enforce noexcept as in the trait declaration
|
// Enforce noexcept as in the trait declaration
|
||||||
requires noexcept(T::calc_look_at_angle(cam_origin, look_at));
|
requires noexcept(T::calc_look_at_angle(cam_origin, look_at));
|
||||||
requires noexcept(T::calc_view_matrix(angles, cam_origin));
|
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, z_near, z_far, ndc_depth_range));
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class Mat4X4Type, class ViewAnglesType, class TraitClass, bool inverted_z = false>
|
template<class Mat4X4Type, class ViewAnglesType, class TraitClass,
|
||||||
requires CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType>
|
NDCDepthRange depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE, CameraAxes axes = {},
|
||||||
|
class NumericType = float>
|
||||||
|
requires CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType, NumericType>
|
||||||
class Camera final
|
class Camera final
|
||||||
{
|
{
|
||||||
#ifdef OMATH_BUILD_TESTS
|
#ifdef OMATH_BUILD_TESTS
|
||||||
@@ -69,43 +83,110 @@ namespace omath::projection
|
|||||||
};
|
};
|
||||||
|
|
||||||
~Camera() = default;
|
~Camera() = default;
|
||||||
Camera(const Vector3<float>& position, const ViewAnglesType& view_angles, const ViewPort& view_port,
|
Camera(const Vector3<NumericType>& position, const ViewAnglesType& view_angles, const ViewPort& view_port,
|
||||||
const FieldOfView& fov, const float near, const float far) noexcept
|
const FieldOfView& fov, const NumericType near, const NumericType far) noexcept
|
||||||
: m_view_port(view_port), m_field_of_view(fov), m_far_plane_distance(far), m_near_plane_distance(near),
|
: m_view_port(view_port), m_field_of_view(fov), m_far_plane_distance(far), m_near_plane_distance(near),
|
||||||
m_view_angles(view_angles), m_origin(position)
|
m_view_angles(view_angles), m_origin(position)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void look_at(const Vector3<float>& target)
|
struct ProjectionParams final
|
||||||
|
{
|
||||||
|
FieldOfView fov;
|
||||||
|
NumericType 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 auto 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(NumericType{2} * std::atan(NumericType{1} / f)),
|
||||||
|
f / proj_matrix.at(0, 0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static ViewAnglesType calc_view_angles_from_view_matrix(const Mat4X4Type& view_matrix) noexcept
|
||||||
|
{
|
||||||
|
Vector3<NumericType> 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<NumericType> 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<NumericType>& target)
|
||||||
{
|
{
|
||||||
m_view_angles = TraitClass::calc_look_at_angle(m_origin, target);
|
m_view_angles = TraitClass::calc_look_at_angle(m_origin, target);
|
||||||
m_view_projection_matrix = std::nullopt;
|
m_view_projection_matrix = std::nullopt;
|
||||||
m_view_matrix = std::nullopt;
|
m_view_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAnglesType calc_look_at_angles(const Vector3<NumericType>& look_to) const
|
||||||
|
{
|
||||||
|
return TraitClass::calc_look_at_angle(m_origin, look_to);
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Vector3<float> get_forward() const noexcept
|
Vector3<NumericType> get_forward() const noexcept
|
||||||
{
|
{
|
||||||
const auto& view_matrix = get_view_matrix();
|
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]};
|
return {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Vector3<float> get_right() const noexcept
|
Vector3<NumericType> get_right() const noexcept
|
||||||
{
|
{
|
||||||
const auto& view_matrix = get_view_matrix();
|
const auto& view_matrix = get_view_matrix();
|
||||||
return {view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]};
|
return {view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Vector3<float> get_up() const noexcept
|
Vector3<NumericType> get_up() const noexcept
|
||||||
{
|
{
|
||||||
const auto& view_matrix = get_view_matrix();
|
const auto& view_matrix = get_view_matrix();
|
||||||
return {view_matrix[1, 0], view_matrix[1, 1], view_matrix[1, 2]};
|
return {view_matrix[1, 0], view_matrix[1, 1], view_matrix[1, 2]};
|
||||||
}
|
}
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<NumericType> get_abs_forward() const noexcept
|
||||||
|
{
|
||||||
|
if constexpr (axes.inverted_forward)
|
||||||
|
return -get_forward();
|
||||||
|
return get_forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<NumericType> get_abs_right() const noexcept
|
||||||
|
{
|
||||||
|
if constexpr (axes.inverted_right)
|
||||||
|
return -get_right();
|
||||||
|
return get_right();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<NumericType> get_abs_up() const noexcept
|
||||||
|
{
|
||||||
|
return get_up();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const noexcept
|
[[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const noexcept
|
||||||
{
|
{
|
||||||
@@ -125,8 +206,8 @@ namespace omath::projection
|
|||||||
[[nodiscard]] const Mat4X4Type& get_projection_matrix() const noexcept
|
[[nodiscard]] const Mat4X4Type& get_projection_matrix() const noexcept
|
||||||
{
|
{
|
||||||
if (!m_projection_matrix.has_value())
|
if (!m_projection_matrix.has_value())
|
||||||
m_projection_matrix = TraitClass::calc_projection_matrix(m_field_of_view, m_view_port,
|
m_projection_matrix = TraitClass::calc_projection_matrix(
|
||||||
m_near_plane_distance, m_far_plane_distance);
|
m_field_of_view, m_view_port, m_near_plane_distance, m_far_plane_distance, depth_range);
|
||||||
|
|
||||||
return m_projection_matrix.value();
|
return m_projection_matrix.value();
|
||||||
}
|
}
|
||||||
@@ -138,16 +219,16 @@ namespace omath::projection
|
|||||||
m_projection_matrix = std::nullopt;
|
m_projection_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_near_plane(const float near) noexcept
|
void set_near_plane(const NumericType near_plane) noexcept
|
||||||
{
|
{
|
||||||
m_near_plane_distance = near;
|
m_near_plane_distance = near_plane;
|
||||||
m_view_projection_matrix = std::nullopt;
|
m_view_projection_matrix = std::nullopt;
|
||||||
m_projection_matrix = std::nullopt;
|
m_projection_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_far_plane(const float far) noexcept
|
void set_far_plane(const NumericType far_plane) noexcept
|
||||||
{
|
{
|
||||||
m_far_plane_distance = far;
|
m_far_plane_distance = far_plane;
|
||||||
m_view_projection_matrix = std::nullopt;
|
m_view_projection_matrix = std::nullopt;
|
||||||
m_projection_matrix = std::nullopt;
|
m_projection_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -159,7 +240,7 @@ namespace omath::projection
|
|||||||
m_view_matrix = std::nullopt;
|
m_view_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_origin(const Vector3<float>& origin) noexcept
|
void set_origin(const Vector3<NumericType>& origin) noexcept
|
||||||
{
|
{
|
||||||
m_origin = origin;
|
m_origin = origin;
|
||||||
m_view_projection_matrix = std::nullopt;
|
m_view_projection_matrix = std::nullopt;
|
||||||
@@ -177,12 +258,12 @@ namespace omath::projection
|
|||||||
return m_field_of_view;
|
return m_field_of_view;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const float& get_near_plane() const noexcept
|
[[nodiscard]] const NumericType& get_near_plane() const noexcept
|
||||||
{
|
{
|
||||||
return m_near_plane_distance;
|
return m_near_plane_distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const float& get_far_plane() const noexcept
|
[[nodiscard]] const NumericType& get_far_plane() const noexcept
|
||||||
{
|
{
|
||||||
return m_far_plane_distance;
|
return m_far_plane_distance;
|
||||||
}
|
}
|
||||||
@@ -192,14 +273,14 @@ namespace omath::projection
|
|||||||
return m_view_angles;
|
return m_view_angles;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const Vector3<float>& get_origin() const noexcept
|
[[nodiscard]] const Vector3<NumericType>& get_origin() const noexcept
|
||||||
{
|
{
|
||||||
return m_origin;
|
return m_origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||||
[[nodiscard]] std::expected<Vector3<float>, Error>
|
[[nodiscard]] std::expected<Vector3<NumericType>, Error>
|
||||||
world_to_screen(const Vector3<float>& world_position) const noexcept
|
world_to_screen(const Vector3<NumericType>& world_position) const noexcept
|
||||||
{
|
{
|
||||||
const auto normalized_cords = world_to_view_port(world_position);
|
const auto normalized_cords = world_to_view_port(world_position);
|
||||||
|
|
||||||
@@ -213,15 +294,31 @@ namespace omath::projection
|
|||||||
else
|
else
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
|
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||||
|
[[nodiscard]] std::expected<Vector3<NumericType>, Error>
|
||||||
|
world_to_screen_unclipped(const Vector3<NumericType>& world_position) const noexcept
|
||||||
|
{
|
||||||
|
const auto normalized_cords = world_to_view_port(world_position, ViewPortClipping::MANUAL);
|
||||||
|
|
||||||
[[nodiscard]] bool is_culled_by_frustum(const Triangle<Vector3<float>>& triangle) const noexcept
|
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<NumericType>>& triangle) const noexcept
|
||||||
{
|
{
|
||||||
// Transform to clip space (before perspective divide)
|
// Transform to clip space (before perspective divide)
|
||||||
auto to_clip = [this](const Vector3<float>& point)
|
auto to_clip = [this](const Vector3<NumericType>& point)
|
||||||
{
|
{
|
||||||
auto clip = get_view_projection_matrix()
|
auto clip = get_view_projection_matrix()
|
||||||
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(point);
|
* mat_column_from_vector<NumericType, Mat4X4Type::get_store_ordering()>(point);
|
||||||
return std::array<float, 4>{
|
return std::array<NumericType, 4>{
|
||||||
clip.at(0, 0), // x
|
clip.at(0, 0), // x
|
||||||
clip.at(1, 0), // y
|
clip.at(1, 0), // y
|
||||||
clip.at(2, 0), // z
|
clip.at(2, 0), // z
|
||||||
@@ -234,52 +331,141 @@ namespace omath::projection
|
|||||||
const auto c2 = to_clip(triangle.m_vertex3);
|
const auto c2 = to_clip(triangle.m_vertex3);
|
||||||
|
|
||||||
// If all vertices are behind the camera (w <= 0), trivially reject
|
// If all vertices are behind the camera (w <= 0), trivially reject
|
||||||
if (c0[3] <= 0.f && c1[3] <= 0.f && c2[3] <= 0.f)
|
if (c0[3] <= NumericType{0} && c1[3] <= NumericType{0} && c2[3] <= NumericType{0})
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Helper: all three vertices outside the same clip plane
|
// Helper: all three vertices outside the same clip plane
|
||||||
auto all_outside_plane = [](const int axis, const std::array<float, 4>& a, const std::array<float, 4>& b,
|
auto all_outside_plane = [](const int axis, const std::array<NumericType, 4>& a,
|
||||||
const std::array<float, 4>& c, const bool positive_side)
|
const std::array<NumericType, 4>& b, const std::array<NumericType, 4>& c,
|
||||||
|
const bool positive_side)
|
||||||
{
|
{
|
||||||
if (positive_side)
|
if (positive_side)
|
||||||
return a[axis] > a[3] && b[axis] > b[3] && c[axis] > c[3];
|
return a[axis] > a[3] && b[axis] > b[3] && c[axis] > c[3];
|
||||||
return a[axis] < -a[3] && b[axis] < -b[3] && c[axis] < -c[3];
|
return a[axis] < -a[3] && b[axis] < -b[3] && c[axis] < -c[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clip volume in clip space (OpenGL-style):
|
// Clip volume in clip space:
|
||||||
// -w <= x <= w
|
// -w <= x <= w
|
||||||
// -w <= y <= 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))
|
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))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::expected<Vector3<float>, Error>
|
[[nodiscard]] bool is_aabb_culled_by_frustum(const primitives::Aabb<NumericType>& aabb) const noexcept
|
||||||
world_to_view_port(const Vector3<float>& world_position) 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
|
||||||
|
{
|
||||||
|
NumericType a, b, c, d;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto extract_plane = [&m](const int sign, const int row) -> Plane
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
m.at(3, 0) + static_cast<NumericType>(sign) * m.at(row, 0),
|
||||||
|
m.at(3, 1) + static_cast<NumericType>(sign) * m.at(row, 1),
|
||||||
|
m.at(3, 2) + static_cast<NumericType>(sign) * m.at(row, 2),
|
||||||
|
m.at(3, 3) + static_cast<NumericType>(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 auto px = a >= NumericType{0} ? aabb.max.x : aabb.min.x;
|
||||||
|
const auto py = b >= NumericType{0} ? aabb.max.y : aabb.min.y;
|
||||||
|
const auto pz = c >= NumericType{0} ? aabb.max.z : aabb.min.z;
|
||||||
|
|
||||||
|
if (a * px + b * py + c * pz + d < NumericType{0})
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<Vector3<NumericType>, Error>
|
||||||
|
world_to_view_port(const Vector3<NumericType>& world_position,
|
||||||
|
const ViewPortClipping& clipping = ViewPortClipping::AUTO) const noexcept
|
||||||
{
|
{
|
||||||
auto projected = get_view_projection_matrix()
|
auto projected = get_view_projection_matrix()
|
||||||
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(world_position);
|
* mat_column_from_vector<NumericType, Mat4X4Type::get_store_ordering()>(world_position);
|
||||||
|
|
||||||
const auto& w = projected.at(3, 0);
|
const auto& w = projected.at(3, 0);
|
||||||
if (w <= std::numeric_limits<float>::epsilon())
|
constexpr auto eps = std::numeric_limits<NumericType>::epsilon();
|
||||||
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
if (w <= eps)
|
||||||
|
return std::unexpected(Error::PERSPECTIVE_DIVIDER_LESS_EQ_ZERO);
|
||||||
|
|
||||||
projected /= w;
|
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);
|
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)};
|
// ReSharper disable once CppTooWideScope
|
||||||
|
constexpr auto z_min = depth_range == NDCDepthRange::ZERO_TO_ONE ? NumericType{0} : -NumericType{1};
|
||||||
|
const auto clipped_manually =
|
||||||
|
clipping == ViewPortClipping::MANUAL
|
||||||
|
&& (projected.at(2, 0) < z_min - eps || projected.at(2, 0) > NumericType{1} + eps);
|
||||||
|
if (clipped_manually)
|
||||||
|
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
||||||
|
|
||||||
|
return Vector3<NumericType>{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)};
|
||||||
}
|
}
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::expected<Vector3<float>, Error> view_port_to_screen(const Vector3<float>& ndc) const noexcept
|
std::expected<Vector3<NumericType>, Error> view_port_to_world(const Vector3<NumericType>& ndc) const noexcept
|
||||||
{
|
{
|
||||||
const auto inv_view_proj = get_view_projection_matrix().inverted();
|
const auto inv_view_proj = get_view_projection_matrix().inverted();
|
||||||
|
|
||||||
@@ -287,54 +473,74 @@ namespace omath::projection
|
|||||||
return std::unexpected(Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO);
|
return std::unexpected(Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO);
|
||||||
|
|
||||||
auto inverted_projection =
|
auto inverted_projection =
|
||||||
inv_view_proj.value() * mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(ndc);
|
inv_view_proj.value() * mat_column_from_vector<NumericType, Mat4X4Type::get_store_ordering()>(ndc);
|
||||||
|
|
||||||
const auto& w = inverted_projection.at(3, 0);
|
const auto& w = inverted_projection.at(3, 0);
|
||||||
|
|
||||||
if (std::abs(w) < std::numeric_limits<float>::epsilon())
|
if (std::abs(w) < std::numeric_limits<NumericType>::epsilon())
|
||||||
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
||||||
|
|
||||||
inverted_projection /= w;
|
inverted_projection /= w;
|
||||||
|
|
||||||
return Vector3<float>{inverted_projection.at(0, 0), inverted_projection.at(1, 0),
|
return Vector3<NumericType>{inverted_projection.at(0, 0), inverted_projection.at(1, 0),
|
||||||
inverted_projection.at(2, 0)};
|
inverted_projection.at(2, 0)};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::expected<Vector3<float>, Error> screen_to_world(const Vector3<float>& screen_pos) const noexcept
|
std::expected<Vector3<NumericType>, Error>
|
||||||
|
screen_to_world(const Vector3<NumericType>& 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>
|
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::expected<Vector3<float>, Error> screen_to_world(const Vector2<float>& screen_pos) const noexcept
|
std::expected<Vector3<NumericType>, Error>
|
||||||
|
screen_to_world(const Vector2<NumericType>& screen_pos) const noexcept
|
||||||
{
|
{
|
||||||
const auto& [x, y] = screen_pos;
|
const auto& [x, y] = screen_pos;
|
||||||
return screen_to_world<screen_start>({x, y, 1.f});
|
return screen_to_world<screen_start>({x, y, 1});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ViewPort m_view_port{};
|
ViewPort m_view_port{};
|
||||||
Angle<float, 0.f, 180.f, AngleFlags::Clamped> m_field_of_view;
|
FieldOfView m_field_of_view;
|
||||||
|
|
||||||
mutable std::optional<Mat4X4Type> m_view_projection_matrix;
|
mutable std::optional<Mat4X4Type> m_view_projection_matrix;
|
||||||
mutable std::optional<Mat4X4Type> m_projection_matrix;
|
mutable std::optional<Mat4X4Type> m_projection_matrix;
|
||||||
mutable std::optional<Mat4X4Type> m_view_matrix;
|
mutable std::optional<Mat4X4Type> m_view_matrix;
|
||||||
float m_far_plane_distance;
|
NumericType m_far_plane_distance;
|
||||||
float m_near_plane_distance;
|
NumericType m_near_plane_distance;
|
||||||
|
|
||||||
ViewAnglesType m_view_angles;
|
ViewAnglesType m_view_angles;
|
||||||
Vector3<float> m_origin;
|
Vector3<NumericType> m_origin;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<class Type>
|
template<class Type>
|
||||||
[[nodiscard]] constexpr static bool is_ndc_out_of_bounds(const Type& ndc) noexcept
|
[[nodiscard]] constexpr static bool is_ndc_out_of_bounds(const Type& ndc) noexcept
|
||||||
{
|
{
|
||||||
constexpr auto eps = std::numeric_limits<float>::epsilon();
|
constexpr auto eps = std::numeric_limits<NumericType>::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] < -NumericType{1} - eps || data[0] > NumericType{1} + eps)
|
||||||
|
return true;
|
||||||
|
if (data[1] < -NumericType{1} - eps || data[1] > NumericType{1} + 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<NumericType>::epsilon();
|
||||||
|
if constexpr (depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
|
return z_ndc < -NumericType{1} - eps || z_ndc > NumericType{1} + eps;
|
||||||
|
if constexpr (depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
|
return z_ndc < NumericType{0} - eps || z_ndc > NumericType{1} + eps;
|
||||||
|
|
||||||
|
std::unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
// NDC REPRESENTATION:
|
// NDC REPRESENTATION:
|
||||||
@@ -351,8 +557,8 @@ namespace omath::projection
|
|||||||
v
|
v
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[[nodiscard]] Vector3<float>
|
[[nodiscard]] Vector3<NumericType>
|
||||||
ndc_to_screen_position_from_top_left_corner(const Vector3<float>& ndc) const noexcept
|
ndc_to_screen_position_from_top_left_corner(const Vector3<NumericType>& ndc) const noexcept
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
+------------------------>
|
+------------------------>
|
||||||
@@ -365,11 +571,12 @@ namespace omath::projection
|
|||||||
|
|
|
|
||||||
⌄
|
⌄
|
||||||
*/
|
*/
|
||||||
return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (ndc.y / -2.f + 0.5f) * m_view_port.m_height, ndc.z};
|
return {(ndc.x + NumericType{1}) / NumericType{2} * m_view_port.m_width,
|
||||||
|
(ndc.y / -NumericType{2} + NumericType{0.5}) * m_view_port.m_height, ndc.z};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] Vector3<float>
|
[[nodiscard]] Vector3<NumericType>
|
||||||
ndc_to_screen_position_from_bottom_left_corner(const Vector3<float>& ndc) const noexcept
|
ndc_to_screen_position_from_bottom_left_corner(const Vector3<NumericType>& ndc) const noexcept
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
^
|
^
|
||||||
@@ -382,18 +589,19 @@ namespace omath::projection
|
|||||||
| (0, 0)
|
| (0, 0)
|
||||||
+------------------------>
|
+------------------------>
|
||||||
*/
|
*/
|
||||||
return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (ndc.y / 2.f + 0.5f) * m_view_port.m_height, ndc.z};
|
return {(ndc.x + NumericType{1}) / NumericType{2} * m_view_port.m_width,
|
||||||
|
(ndc.y / NumericType{2} + NumericType{0.5}) * m_view_port.m_height, ndc.z};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||||
[[nodiscard]] Vector3<float> screen_to_ndc(const Vector3<float>& screen_pos) const noexcept
|
[[nodiscard]] Vector3<NumericType> screen_to_ndc(const Vector3<NumericType>& screen_pos) const noexcept
|
||||||
{
|
{
|
||||||
if constexpr (screen_start == ScreenStart::TOP_LEFT_CORNER)
|
if constexpr (screen_start == ScreenStart::TOP_LEFT_CORNER)
|
||||||
return {screen_pos.x / m_view_port.m_width * 2.f - 1.f, 1.f - screen_pos.y / m_view_port.m_height * 2.f,
|
return {screen_pos.x / m_view_port.m_width * NumericType{2} - NumericType{1},
|
||||||
screen_pos.z};
|
NumericType{1} - screen_pos.y / m_view_port.m_height * NumericType{2}, screen_pos.z};
|
||||||
else if constexpr (screen_start == ScreenStart::BOTTOM_LEFT_CORNER)
|
else if constexpr (screen_start == ScreenStart::BOTTOM_LEFT_CORNER)
|
||||||
return {screen_pos.x / m_view_port.m_width * 2.f - 1.f,
|
return {screen_pos.x / m_view_port.m_width * NumericType{2} - NumericType{1},
|
||||||
(screen_pos.y / m_view_port.m_height - 0.5f) * 2.f, screen_pos.z};
|
(screen_pos.y / m_view_port.m_height - NumericType{0.5}) * NumericType{2}, screen_pos.z};
|
||||||
else
|
else
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ namespace omath::projection
|
|||||||
{
|
{
|
||||||
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
|
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
|
||||||
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
|
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
|
||||||
|
PERSPECTIVE_DIVIDER_LESS_EQ_ZERO,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,43 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#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
|
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
|
class InternalReverseEngineeredObject
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
@@ -23,26 +55,150 @@ namespace omath::rev_eng
|
|||||||
return *reinterpret_cast<Type*>(reinterpret_cast<std::uintptr_t>(this) + offset);
|
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)
|
ReturnType call_virtual_method(auto... arg_list)
|
||||||
{
|
{
|
||||||
#ifdef _MSC_VER
|
const auto vtable = *reinterpret_cast<void***>(this);
|
||||||
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
return call_method<ReturnType>(vtable[Id], arg_list...);
|
||||||
#else
|
|
||||||
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
|
|
||||||
#endif
|
|
||||||
return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, arg_list...);
|
|
||||||
}
|
}
|
||||||
template<std::size_t id, class ReturnType>
|
template<std::size_t Id, class ReturnType>
|
||||||
ReturnType call_virtual_method(auto... arg_list) const
|
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
|
#ifdef _MSC_VER
|
||||||
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
using Fn = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
||||||
#else
|
#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
|
#endif
|
||||||
return (*static_cast<VirtualMethodType**>((void*)(this)))[id](
|
|
||||||
const_cast<void*>(static_cast<const void*>(this)), arg_list...);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace omath::rev_eng
|
} // namespace omath::rev_eng
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ namespace omath
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using ArithmeticType = Type;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
constexpr static Angle from_degrees(const Type& degrees) noexcept
|
constexpr static Angle from_degrees(const Type& degrees) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,25 @@
|
|||||||
// Created by Orange on 11/30/2024.
|
// Created by Orange on 11/30/2024.
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "omath/linear_algebra/vector3.hpp"
|
||||||
|
#include <type_traits>
|
||||||
namespace omath
|
namespace omath
|
||||||
{
|
{
|
||||||
template<class PitchType, class YawType, class RollType>
|
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
|
struct ViewAngles
|
||||||
{
|
{
|
||||||
|
using ArithmeticType = PitchType::ArithmeticType;
|
||||||
|
|
||||||
PitchType pitch;
|
PitchType pitch;
|
||||||
YawType yaw;
|
YawType yaw;
|
||||||
RollType roll;
|
RollType roll;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<ArithmeticType> as_vector3() const
|
||||||
|
{
|
||||||
|
return {pitch.as_degrees(), yaw.as_degrees(), roll.as_degrees()};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} // namespace omath
|
} // namespace omath
|
||||||
|
|||||||
@@ -17,8 +17,35 @@ echo "[*] Output dir: ${OUTPUT_DIR}"
|
|||||||
find_llvm_tool() {
|
find_llvm_tool() {
|
||||||
local tool_name="$1"
|
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
|
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
|
if xcrun --find "${tool_name}" &>/dev/null; then
|
||||||
echo "xcrun ${tool_name}"
|
echo "xcrun ${tool_name}"
|
||||||
return 0
|
return 0
|
||||||
@@ -51,6 +78,18 @@ fi
|
|||||||
echo "[*] Using: ${LLVM_PROFDATA}"
|
echo "[*] Using: ${LLVM_PROFDATA}"
|
||||||
echo "[*] Using: ${LLVM_COV}"
|
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
|
# Find test binary
|
||||||
if [[ -z "${TEST_BINARY}" ]]; then
|
if [[ -z "${TEST_BINARY}" ]]; then
|
||||||
for path in \
|
for path in \
|
||||||
|
|||||||
61
scripts/gource-timelapse.sh
Executable file
61
scripts/gource-timelapse.sh
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Gource Timelapse — renders the repository history as a video
|
||||||
|
# Requires: gource, ffmpeg
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# --- Config (override via env vars) ---
|
||||||
|
OUTPUT="${OUTPUT:-gource-timelapse.mp4}"
|
||||||
|
SECONDS_PER_DAY="${SECONDS_PER_DAY:-0.1}"
|
||||||
|
RESOLUTION="${RESOLUTION:-1920x1080}"
|
||||||
|
FPS="${FPS:-60}"
|
||||||
|
TITLE="${TITLE:-omath}"
|
||||||
|
|
||||||
|
# --- Dependency checks ---
|
||||||
|
for cmd in gource ffmpeg; do
|
||||||
|
if ! command -v "$cmd" &>/dev/null; then
|
||||||
|
echo "Error: '$cmd' is not installed."
|
||||||
|
echo " macOS: brew install $cmd"
|
||||||
|
echo " Linux: sudo apt install $cmd"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "----------------------------------------------------"
|
||||||
|
echo "Rendering gource timelapse → $OUTPUT"
|
||||||
|
echo " Resolution : $RESOLUTION"
|
||||||
|
echo " FPS : $FPS"
|
||||||
|
echo " Speed : ${SECONDS_PER_DAY}s per day"
|
||||||
|
echo "----------------------------------------------------"
|
||||||
|
|
||||||
|
gource \
|
||||||
|
--title "$TITLE" \
|
||||||
|
--seconds-per-day "$SECONDS_PER_DAY" \
|
||||||
|
--auto-skip-seconds 0.1 \
|
||||||
|
--time-scale 3 \
|
||||||
|
--max-files 0 \
|
||||||
|
--hide filenames \
|
||||||
|
--date-format "%Y-%m-%d" \
|
||||||
|
--multi-sampling \
|
||||||
|
--bloom-multiplier 0.5 \
|
||||||
|
--elasticity 0.05 \
|
||||||
|
--${RESOLUTION%x*}x${RESOLUTION#*x} \
|
||||||
|
--output-framerate "$FPS" \
|
||||||
|
--output-ppm-stream - \
|
||||||
|
| ffmpeg -y \
|
||||||
|
-r "$FPS" \
|
||||||
|
-f image2pipe \
|
||||||
|
-vcodec ppm \
|
||||||
|
-i - \
|
||||||
|
-vcodec libx264 \
|
||||||
|
-preset fast \
|
||||||
|
-pix_fmt yuv420p \
|
||||||
|
-crf 18 \
|
||||||
|
"$OUTPUT"
|
||||||
|
|
||||||
|
echo "----------------------------------------------------"
|
||||||
|
echo "Done: $OUTPUT"
|
||||||
|
echo "----------------------------------------------------"
|
||||||
@@ -35,8 +35,15 @@ namespace omath::cry_engine
|
|||||||
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
* 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,
|
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
|
} // namespace omath::unity_engine
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ namespace omath::cry_engine
|
|||||||
}
|
}
|
||||||
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
const projection::ViewPort& view_port, const float near,
|
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
|
} // namespace omath::unity_engine
|
||||||
@@ -35,8 +35,16 @@ namespace omath::frostbite_engine
|
|||||||
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
* 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,
|
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
|
} // namespace omath::unity_engine
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ namespace omath::frostbite_engine
|
|||||||
}
|
}
|
||||||
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
const projection::ViewPort& view_port, const float near,
|
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
|
} // namespace omath::unity_engine
|
||||||
@@ -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,
|
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
|
// NOTE: Need magic number to fix fov calculation, since IW engine inherit Quake proj matrix calculation
|
||||||
constexpr auto k_multiply_factor = 0.75f;
|
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;
|
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f) * k_multiply_factor;
|
||||||
|
|
||||||
|
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 {
|
return {
|
||||||
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
|
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
|
||||||
{0, 1.f / (fov_half_tan), 0, 0},
|
{0, 1.f / (fov_half_tan), 0, 0},
|
||||||
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
|
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
|
||||||
{0, 0, 1, 0},
|
{0, 0, 1, 0},
|
||||||
};
|
};
|
||||||
|
std::unreachable();
|
||||||
};
|
};
|
||||||
} // namespace omath::iw_engine
|
} // namespace omath::iw_engine
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ namespace omath::iw_engine
|
|||||||
}
|
}
|
||||||
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
const projection::ViewPort& view_port, const float near,
|
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
|
} // namespace omath::iw_engine
|
||||||
@@ -8,15 +8,15 @@ namespace omath::opengl_engine
|
|||||||
|
|
||||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
|
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
|
||||||
{
|
{
|
||||||
const auto vec
|
const auto vec =
|
||||||
= rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_forward);
|
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)};
|
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
||||||
}
|
}
|
||||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept
|
Vector3<float> right_vector(const ViewAngles& angles) noexcept
|
||||||
{
|
{
|
||||||
const auto vec
|
const auto vec =
|
||||||
= rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_right);
|
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)};
|
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
|
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
|
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);
|
* 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,
|
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 {
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
|
return mat_perspective_right_handed<float, MatStoreType::COLUMN_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
{0, 1.f / (fov_half_tan), 0, 0},
|
field_of_view, aspect_ratio, near, far);
|
||||||
{0, 0, -(far + near) / (far - near), -(2.f * far * near) / (far - near)},
|
|
||||||
{0, 0, -1, 0},
|
std::unreachable();
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} // namespace omath::opengl_engine
|
} // namespace omath::opengl_engine
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ namespace omath::opengl_engine
|
|||||||
}
|
}
|
||||||
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
const projection::ViewPort& view_port, const float near,
|
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
|
} // namespace omath::opengl_engine
|
||||||
@@ -36,18 +36,26 @@ namespace omath::source_engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
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
|
// Source (inherited from Quake) stores FOV as horizontal FOV at a 4:3
|
||||||
constexpr auto k_multiply_factor = 0.75f;
|
// reference aspect. Convert to true vertical FOV, then delegate to the
|
||||||
|
// standard vertical-FOV left-handed builder with the caller's actual
|
||||||
|
// aspect ratio.
|
||||||
|
// vfov = 2 · atan( tan(hfov_4:3 / 2) / (4/3) )
|
||||||
|
constexpr float k_source_reference_aspect = 4.f / 3.f;
|
||||||
|
const float half_hfov_4_3 = angles::degrees_to_radians(field_of_view) / 2.f;
|
||||||
|
const float vfov_deg = angles::radians_to_degrees(
|
||||||
|
2.f * std::atan(std::tan(half_hfov_4_3) / k_source_reference_aspect));
|
||||||
|
|
||||||
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f) * k_multiply_factor;
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
|
return mat_perspective_left_handed<
|
||||||
return {
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
|
vfov_deg, aspect_ratio, near, far);
|
||||||
{0, 1.f / (fov_half_tan), 0, 0},
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
|
return mat_perspective_left_handed<
|
||||||
{0, 0, 1, 0},
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
||||||
};
|
vfov_deg, aspect_ratio, near, far);
|
||||||
|
std::unreachable();
|
||||||
}
|
}
|
||||||
} // namespace omath::source_engine
|
} // namespace omath::source_engine
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ namespace omath::source_engine
|
|||||||
}
|
}
|
||||||
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
const projection::ViewPort& view_port, const float near,
|
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
|
} // namespace omath::source_engine
|
||||||
@@ -35,8 +35,15 @@ namespace omath::unity_engine
|
|||||||
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
* 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,
|
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
|
} // namespace omath::unity_engine
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ namespace omath::unity_engine
|
|||||||
}
|
}
|
||||||
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
const projection::ViewPort& view_port, const float near,
|
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
|
} // namespace omath::unity_engine
|
||||||
@@ -2,41 +2,56 @@
|
|||||||
// Created by Vlad on 3/22/2025.
|
// Created by Vlad on 3/22/2025.
|
||||||
//
|
//
|
||||||
#include "omath/engines/unreal_engine/formulas.hpp"
|
#include "omath/engines/unreal_engine/formulas.hpp"
|
||||||
|
|
||||||
namespace omath::unreal_engine
|
namespace omath::unreal_engine
|
||||||
{
|
{
|
||||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
|
Vector3<double> forward_vector(const ViewAngles& angles) noexcept
|
||||||
{
|
{
|
||||||
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_forward);
|
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_forward);
|
||||||
|
|
||||||
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
||||||
}
|
}
|
||||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept
|
Vector3<double> right_vector(const ViewAngles& angles) noexcept
|
||||||
{
|
{
|
||||||
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
|
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
|
||||||
|
|
||||||
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
||||||
}
|
}
|
||||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept
|
Vector3<double> up_vector(const ViewAngles& angles) noexcept
|
||||||
{
|
{
|
||||||
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_up);
|
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_up);
|
||||||
|
|
||||||
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
||||||
}
|
}
|
||||||
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
|
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept
|
||||||
{
|
{
|
||||||
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), -right_vector(angles),
|
return mat_camera_view<double, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
|
||||||
up_vector(angles), cam_origin);
|
up_vector(angles), cam_origin);
|
||||||
}
|
}
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
|
||||||
{
|
{
|
||||||
return mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.roll)
|
// UE FRotator is intrinsic Z-Y-X (Yaw → Pitch → Roll applied in local
|
||||||
* mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.yaw)
|
// frame), which for column-vector composition is Rz·Ry·Rx.
|
||||||
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
// Pitch and roll axes in omath spin opposite to UE's convention, so
|
||||||
|
// both carry a sign flip.
|
||||||
|
return mat_rotation_axis_z<double, MatStoreType::ROW_MAJOR>(angles.yaw)
|
||||||
|
* mat_rotation_axis_y<double, MatStoreType::ROW_MAJOR>(-angles.pitch)
|
||||||
|
* mat_rotation_axis_x<double, MatStoreType::ROW_MAJOR>(-angles.roll);
|
||||||
}
|
}
|
||||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
|
||||||
const float far) noexcept
|
|
||||||
|
Mat4X4 calc_perspective_projection_matrix(const double field_of_view, const double aspect_ratio, const double near,
|
||||||
|
const double far, const NDCDepthRange ndc_depth_range) noexcept
|
||||||
{
|
{
|
||||||
return mat_perspective_left_handed(field_of_view, aspect_ratio, near, far);
|
// UE stores horizontal FOV in FMinimalViewInfo — use the left-handed
|
||||||
|
// horizontal-FOV builder directly.
|
||||||
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
|
return mat_perspective_left_handed_horizontal_fov<
|
||||||
|
double, 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_horizontal_fov<
|
||||||
|
double, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
||||||
|
field_of_view, aspect_ratio, near, far);
|
||||||
|
std::unreachable();
|
||||||
}
|
}
|
||||||
} // namespace omath::unreal_engine
|
} // namespace omath::unreal_engine
|
||||||
|
|||||||
@@ -6,21 +6,22 @@
|
|||||||
namespace omath::unreal_engine
|
namespace omath::unreal_engine
|
||||||
{
|
{
|
||||||
|
|
||||||
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
|
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<double>& cam_origin, const Vector3<double>& look_at) noexcept
|
||||||
{
|
{
|
||||||
const auto direction = (look_at - cam_origin).normalized();
|
const auto direction = (look_at - cam_origin).normalized();
|
||||||
|
|
||||||
return {PitchAngle::from_radians(-std::asin(direction.z)),
|
return {PitchAngle::from_radians(std::asin(direction.z)),
|
||||||
YawAngle::from_radians(std::atan2(direction.y, direction.x)), RollAngle::from_radians(0.f)};
|
YawAngle::from_radians(std::atan2(direction.y, direction.x)), RollAngle::from_radians(0.f)};
|
||||||
}
|
}
|
||||||
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
|
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept
|
||||||
{
|
{
|
||||||
return unreal_engine::calc_view_matrix(angles, cam_origin);
|
return unreal_engine::calc_view_matrix(angles, cam_origin);
|
||||||
}
|
}
|
||||||
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
const projection::ViewPort& view_port, const float near,
|
const projection::ViewPort& view_port, const double near,
|
||||||
const float far) noexcept
|
const double 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
|
} // namespace omath::unreal_engine
|
||||||
@@ -457,85 +457,414 @@ namespace omath::hud
|
|||||||
m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer)
|
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 ───────────────────────────────────────────────────────
|
// ── widget dispatch ───────────────────────────────────────────────────────
|
||||||
void EntityOverlay::dispatch(const widget::Box& w)
|
void EntityOverlay::dispatch(const widget::Box& box)
|
||||||
{ add_2d_box(w.color, w.fill, w.thickness); }
|
{
|
||||||
|
add_2d_box(box.color, box.fill, box.thickness);
|
||||||
|
}
|
||||||
|
|
||||||
void EntityOverlay::dispatch(const widget::CorneredBox& w)
|
void EntityOverlay::dispatch(const widget::CorneredBox& cornered_box)
|
||||||
{ add_cornered_2d_box(w.color, w.fill, w.corner_ratio, w.thickness); }
|
{
|
||||||
|
add_cornered_2d_box(cornered_box.color, cornered_box.fill, cornered_box.corner_ratio, cornered_box.thickness);
|
||||||
|
}
|
||||||
|
|
||||||
void EntityOverlay::dispatch(const widget::DashedBox& w)
|
void EntityOverlay::dispatch(const widget::DashedBox& dashed_box)
|
||||||
{ add_dashed_box(w.color, w.dash_len, w.gap_len, w.thickness); }
|
{
|
||||||
|
add_dashed_box(dashed_box.color, dashed_box.dash_len, dashed_box.gap_len, dashed_box.thickness);
|
||||||
|
}
|
||||||
|
|
||||||
void EntityOverlay::dispatch(const widget::Skeleton& w)
|
void EntityOverlay::dispatch(const widget::Skeleton& skeleton)
|
||||||
{ add_skeleton(w.color, w.thickness); }
|
{
|
||||||
|
add_skeleton(skeleton.color, skeleton.thickness);
|
||||||
|
}
|
||||||
|
|
||||||
void EntityOverlay::dispatch(const widget::SnapLine& w)
|
void EntityOverlay::dispatch(const widget::SnapLine& snap_line)
|
||||||
{ add_snap_line(w.start, w.color, w.width); }
|
{
|
||||||
|
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 ───────────────────────────────────────────────
|
// ── Side container dispatch ───────────────────────────────────────────────
|
||||||
void EntityOverlay::dispatch(const widget::RightSide& s)
|
void EntityOverlay::dispatch(const widget::RightSide& right_side)
|
||||||
{
|
{
|
||||||
for (const auto& child : s.children)
|
for (const auto& child : right_side.children)
|
||||||
std::visit(widget::Overloaded{
|
std::visit(
|
||||||
[](const widget::None&) {},
|
widget::Overloaded{
|
||||||
|
[](const widget::None&)
|
||||||
|
{
|
||||||
|
},
|
||||||
[this](const widget::Bar& w)
|
[this](const widget::Bar& w)
|
||||||
{ add_right_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset); },
|
{
|
||||||
|
add_right_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset);
|
||||||
|
},
|
||||||
[this](const widget::DashedBar& w)
|
[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); },
|
{
|
||||||
|
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)
|
[this](const widget::Label& w)
|
||||||
{ add_right_label(w.color, w.offset, w.outlined, w.text); },
|
{
|
||||||
|
add_right_label(w.color, w.offset, w.outlined, w.text);
|
||||||
|
},
|
||||||
[this](const widget::Centered<widget::Label>& w)
|
[this](const widget::Centered<widget::Label>& w)
|
||||||
{ add_right_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); },
|
{
|
||||||
}, child);
|
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& s)
|
void EntityOverlay::dispatch(const widget::LeftSide& left_side)
|
||||||
{
|
{
|
||||||
for (const auto& child : s.children)
|
for (const auto& child : left_side.children)
|
||||||
std::visit(widget::Overloaded{
|
std::visit(
|
||||||
[](const widget::None&) {},
|
widget::Overloaded{
|
||||||
|
[](const widget::None&)
|
||||||
|
{
|
||||||
|
},
|
||||||
[this](const widget::Bar& w)
|
[this](const widget::Bar& w)
|
||||||
{ add_left_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset); },
|
{
|
||||||
|
add_left_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset);
|
||||||
|
},
|
||||||
[this](const widget::DashedBar& w)
|
[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); },
|
{
|
||||||
|
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)
|
[this](const widget::Label& w)
|
||||||
{ add_left_label(w.color, w.offset, w.outlined, w.text); },
|
{
|
||||||
|
add_left_label(w.color, w.offset, w.outlined, w.text);
|
||||||
|
},
|
||||||
[this](const widget::Centered<widget::Label>& w)
|
[this](const widget::Centered<widget::Label>& w)
|
||||||
{ add_left_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); },
|
{
|
||||||
}, child);
|
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& s)
|
void EntityOverlay::dispatch(const widget::TopSide& top_side)
|
||||||
{
|
{
|
||||||
for (const auto& child : s.children)
|
for (const auto& child : top_side.children)
|
||||||
std::visit(widget::Overloaded{
|
std::visit(
|
||||||
[](const widget::None&) {},
|
widget::Overloaded{
|
||||||
|
[](const widget::None&)
|
||||||
|
{
|
||||||
|
},
|
||||||
[this](const widget::Bar& w)
|
[this](const widget::Bar& w)
|
||||||
{ add_top_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset); },
|
{
|
||||||
|
add_top_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset);
|
||||||
|
},
|
||||||
[this](const widget::DashedBar& w)
|
[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); },
|
{
|
||||||
|
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)
|
[this](const widget::Label& w)
|
||||||
{ add_top_label(w.color, w.offset, w.outlined, w.text); },
|
{
|
||||||
|
add_top_label(w.color, w.offset, w.outlined, w.text);
|
||||||
|
},
|
||||||
[this](const widget::Centered<widget::Label>& w)
|
[this](const widget::Centered<widget::Label>& w)
|
||||||
{ add_centered_top_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); },
|
{
|
||||||
}, child);
|
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)
|
||||||
void EntityOverlay::dispatch(const widget::BottomSide& s)
|
|
||||||
{
|
{
|
||||||
for (const auto& child : s.children)
|
for (const auto& child : bottom_side.children)
|
||||||
std::visit(widget::Overloaded{
|
std::visit(
|
||||||
[](const widget::None&) {},
|
widget::Overloaded{
|
||||||
|
[](const widget::None&)
|
||||||
|
{
|
||||||
|
},
|
||||||
[this](const widget::Bar& w)
|
[this](const widget::Bar& w)
|
||||||
{ add_bottom_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset); },
|
{
|
||||||
|
add_bottom_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset);
|
||||||
|
},
|
||||||
[this](const widget::DashedBar& w)
|
[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); },
|
{
|
||||||
|
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)
|
[this](const widget::Label& w)
|
||||||
{ add_bottom_label(w.color, w.offset, w.outlined, w.text); },
|
{
|
||||||
|
add_bottom_label(w.color, w.offset, w.outlined, w.text);
|
||||||
|
},
|
||||||
[this](const widget::Centered<widget::Label>& w)
|
[this](const widget::Centered<widget::Label>& w)
|
||||||
{ add_centered_bottom_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); },
|
{
|
||||||
}, child);
|
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
|
} // namespace omath::hud
|
||||||
@@ -42,6 +42,32 @@ namespace omath::hud
|
|||||||
ImGui::GetBackgroundDrawList()->AddRectFilled(min.to_im_vec2(), max.to_im_vec2(), color.to_im_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)
|
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(),
|
ImGui::GetBackgroundDrawList()->AddText(position.to_im_vec2(), color.to_im_color(), text.data(),
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
//
|
//
|
||||||
#ifdef OMATH_ENABLE_LUA
|
#ifdef OMATH_ENABLE_LUA
|
||||||
#include "omath/lua/lua.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/camera.hpp>
|
||||||
#include <omath/engines/frostbite_engine/camera.hpp>
|
#include <omath/engines/frostbite_engine/camera.hpp>
|
||||||
#include <omath/engines/iw_engine/camera.hpp>
|
#include <omath/engines/iw_engine/camera.hpp>
|
||||||
@@ -33,6 +35,8 @@ namespace
|
|||||||
return "world position is out of screen bounds";
|
return "world position is out of screen bounds";
|
||||||
case omath::projection::Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
|
case omath::projection::Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
|
||||||
return "inverse view-projection matrix determinant is 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";
|
return "unknown error";
|
||||||
}
|
}
|
||||||
@@ -74,7 +78,8 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register an engine: alias shared types, register unique Camera
|
// Register an engine: alias shared types, register unique Camera
|
||||||
template<class EngineTraits>
|
template<class EngineTraits, class ArithmeticType = float>
|
||||||
|
requires std::is_arithmetic_v<ArithmeticType>
|
||||||
void register_engine(sol::table& omath_table, const char* subtable_name)
|
void register_engine(sol::table& omath_table, const char* subtable_name)
|
||||||
{
|
{
|
||||||
using PitchAngle = typename EngineTraits::PitchAngle;
|
using PitchAngle = typename EngineTraits::PitchAngle;
|
||||||
@@ -88,9 +93,9 @@ namespace
|
|||||||
|
|
||||||
engine_table.new_usertype<Camera>(
|
engine_table.new_usertype<Camera>(
|
||||||
"Camera",
|
"Camera",
|
||||||
sol::constructors<Camera(const omath::Vector3<float>&, const ViewAngles&,
|
sol::constructors<Camera(const omath::Vector3<ArithmeticType>&, const ViewAngles&,
|
||||||
const omath::projection::ViewPort&, const omath::projection::FieldOfView&,
|
const omath::projection::ViewPort&, const omath::projection::FieldOfView&,
|
||||||
float, float)>(),
|
ArithmeticType, ArithmeticType)>(),
|
||||||
"look_at", &Camera::look_at, "get_forward", &Camera::get_forward, "get_right", &Camera::get_right,
|
"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",
|
"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_view_angles, "get_near_plane", &Camera::get_near_plane, "get_far_plane",
|
||||||
@@ -100,8 +105,8 @@ namespace
|
|||||||
&Camera::set_near_plane, "set_far_plane", &Camera::set_far_plane,
|
&Camera::set_near_plane, "set_far_plane", &Camera::set_far_plane,
|
||||||
|
|
||||||
"world_to_screen",
|
"world_to_screen",
|
||||||
[](const Camera& cam, const omath::Vector3<float>& pos)
|
[](const Camera& cam, const omath::Vector3<ArithmeticType>& pos)
|
||||||
-> std::tuple<sol::optional<omath::Vector3<float>>, sol::optional<std::string>>
|
-> std::tuple<sol::optional<omath::Vector3<ArithmeticType>>, sol::optional<std::string>>
|
||||||
{
|
{
|
||||||
auto result = cam.world_to_screen(pos);
|
auto result = cam.world_to_screen(pos);
|
||||||
if (result)
|
if (result)
|
||||||
@@ -110,8 +115,8 @@ namespace
|
|||||||
},
|
},
|
||||||
|
|
||||||
"screen_to_world",
|
"screen_to_world",
|
||||||
[](const Camera& cam, const omath::Vector3<float>& pos)
|
[](const Camera& cam, const omath::Vector3<ArithmeticType>& pos)
|
||||||
-> std::tuple<sol::optional<omath::Vector3<float>>, sol::optional<std::string>>
|
-> std::tuple<sol::optional<omath::Vector3<ArithmeticType>>, sol::optional<std::string>>
|
||||||
{
|
{
|
||||||
auto result = cam.screen_to_world(pos);
|
auto result = cam.screen_to_world(pos);
|
||||||
if (result)
|
if (result)
|
||||||
@@ -220,7 +225,7 @@ namespace omath::lua
|
|||||||
register_engine<IWEngineTraits>(omath_table, "iw");
|
register_engine<IWEngineTraits>(omath_table, "iw");
|
||||||
register_engine<SourceEngineTraits>(omath_table, "source");
|
register_engine<SourceEngineTraits>(omath_table, "source");
|
||||||
register_engine<UnityEngineTraits>(omath_table, "unity");
|
register_engine<UnityEngineTraits>(omath_table, "unity");
|
||||||
register_engine<UnrealEngineTraits>(omath_table, "unreal");
|
register_engine<UnrealEngineTraits, double>(omath_table, "unreal");
|
||||||
register_engine<CryEngineTraits>(omath_table, "cry");
|
register_engine<CryEngineTraits>(omath_table, "cry");
|
||||||
}
|
}
|
||||||
} // namespace omath::lua::detail
|
} // namespace omath::lua::detail
|
||||||
|
|||||||
@@ -87,11 +87,11 @@ namespace omath::pathfinding
|
|||||||
|
|
||||||
const auto current_node = current_node_it->second;
|
const auto current_node = current_node_it->second;
|
||||||
|
|
||||||
|
closed_list.emplace(current, current_node);
|
||||||
|
|
||||||
if (current == end_vertex)
|
if (current == end_vertex)
|
||||||
return reconstruct_final_path(closed_list, current);
|
return reconstruct_final_path(closed_list, current);
|
||||||
|
|
||||||
closed_list.emplace(current, current_node);
|
|
||||||
|
|
||||||
for (const auto& neighbor: nav_mesh.get_neighbors(current))
|
for (const auto& neighbor: nav_mesh.get_neighbors(current))
|
||||||
{
|
{
|
||||||
if (closed_list.contains(neighbor))
|
if (closed_list.contains(neighbor))
|
||||||
|
|||||||
92
source/pathfinding/walk_bot.cpp
Normal file
92
source/pathfinding/walk_bot.cpp
Normal 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
|
||||||
@@ -21,7 +21,7 @@ namespace omath::projectile_prediction
|
|||||||
const float bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
|
const float bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
|
||||||
const float v0 = projectile.m_launch_speed;
|
const float v0 = projectile.m_launch_speed;
|
||||||
const float v0_sqr = v0 * v0;
|
const float v0_sqr = v0 * v0;
|
||||||
const Vector3 proj_origin = projectile.m_origin;
|
const Vector3 proj_origin = projectile.m_origin + projectile.m_launch_offset;
|
||||||
|
|
||||||
constexpr int SIMD_FACTOR = 8;
|
constexpr int SIMD_FACTOR = 8;
|
||||||
float current_time = m_simulation_time_step;
|
float current_time = m_simulation_time_step;
|
||||||
@@ -124,6 +124,110 @@ namespace omath::projectile_prediction
|
|||||||
std::format("{} AVX2 feature is not enabled!", std::source_location::current().function_name()));
|
std::format("{} AVX2 feature is not enabled!", std::source_location::current().function_name()));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
std::optional<AimAngles>
|
||||||
|
ProjPredEngineAvx2::maybe_calculate_aim_angles([[maybe_unused]] const Projectile& projectile,
|
||||||
|
[[maybe_unused]] const Target& target) const
|
||||||
|
{
|
||||||
|
#if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__)
|
||||||
|
const float bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
|
||||||
|
const float v0 = projectile.m_launch_speed;
|
||||||
|
const Vector3 proj_origin = projectile.m_origin + projectile.m_launch_offset;
|
||||||
|
|
||||||
|
constexpr int SIMD_FACTOR = 8;
|
||||||
|
float current_time = m_simulation_time_step;
|
||||||
|
|
||||||
|
for (; current_time <= m_maximum_simulation_time; current_time += m_simulation_time_step * SIMD_FACTOR)
|
||||||
|
{
|
||||||
|
const __m256 times
|
||||||
|
= _mm256_setr_ps(current_time, current_time + m_simulation_time_step,
|
||||||
|
current_time + m_simulation_time_step * 2, current_time + m_simulation_time_step * 3,
|
||||||
|
current_time + m_simulation_time_step * 4, current_time + m_simulation_time_step * 5,
|
||||||
|
current_time + m_simulation_time_step * 6, current_time + m_simulation_time_step * 7);
|
||||||
|
|
||||||
|
const __m256 target_x
|
||||||
|
= _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.x), times, _mm256_set1_ps(target.m_origin.x));
|
||||||
|
const __m256 target_y
|
||||||
|
= _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.y), times, _mm256_set1_ps(target.m_origin.y));
|
||||||
|
const __m256 times_sq = _mm256_mul_ps(times, times);
|
||||||
|
const __m256 target_z = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.z), times,
|
||||||
|
_mm256_fnmadd_ps(_mm256_set1_ps(0.5f * m_gravity_constant), times_sq,
|
||||||
|
_mm256_set1_ps(target.m_origin.z)));
|
||||||
|
|
||||||
|
const __m256 delta_x = _mm256_sub_ps(target_x, _mm256_set1_ps(proj_origin.x));
|
||||||
|
const __m256 delta_y = _mm256_sub_ps(target_y, _mm256_set1_ps(proj_origin.y));
|
||||||
|
|
||||||
|
const __m256 d_sqr = _mm256_add_ps(_mm256_mul_ps(delta_x, delta_x), _mm256_mul_ps(delta_y, delta_y));
|
||||||
|
const __m256 delta_z = _mm256_sub_ps(target_z, _mm256_set1_ps(proj_origin.z));
|
||||||
|
|
||||||
|
const __m256 bg_times_sq = _mm256_mul_ps(_mm256_set1_ps(bullet_gravity), times_sq);
|
||||||
|
const __m256 term = _mm256_add_ps(delta_z, _mm256_mul_ps(_mm256_set1_ps(0.5f), bg_times_sq));
|
||||||
|
const __m256 term_sq = _mm256_mul_ps(term, term);
|
||||||
|
const __m256 numerator = _mm256_add_ps(d_sqr, term_sq);
|
||||||
|
const __m256 denominator = _mm256_add_ps(times_sq, _mm256_set1_ps(1e-8f));
|
||||||
|
const __m256 required_v0_sqr = _mm256_div_ps(numerator, denominator);
|
||||||
|
|
||||||
|
const __m256 v0_sqr_vec = _mm256_set1_ps(v0 * v0 + 1e-3f);
|
||||||
|
const __m256 mask = _mm256_cmp_ps(required_v0_sqr, v0_sqr_vec, _CMP_LE_OQ);
|
||||||
|
|
||||||
|
const unsigned valid_mask = _mm256_movemask_ps(mask);
|
||||||
|
if (!valid_mask)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
alignas(32) float valid_times[SIMD_FACTOR];
|
||||||
|
_mm256_store_ps(valid_times, times);
|
||||||
|
|
||||||
|
for (int i = 0; i < SIMD_FACTOR; ++i)
|
||||||
|
{
|
||||||
|
if (!(valid_mask & (1 << i)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const float candidate_time = valid_times[i];
|
||||||
|
if (candidate_time > m_maximum_simulation_time)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (float fine_time = candidate_time - m_simulation_time_step * 2;
|
||||||
|
fine_time <= candidate_time + m_simulation_time_step * 2; fine_time += m_simulation_time_step)
|
||||||
|
{
|
||||||
|
if (fine_time < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Vector3 target_pos = target.m_origin + target.m_velocity * fine_time;
|
||||||
|
if (target.m_is_airborne)
|
||||||
|
target_pos.z -= 0.5f * m_gravity_constant * fine_time * fine_time;
|
||||||
|
|
||||||
|
const auto pitch = calculate_pitch(proj_origin, target_pos, bullet_gravity, v0, fine_time);
|
||||||
|
if (!pitch)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Vector3 delta = target_pos - projectile.m_origin;
|
||||||
|
const float yaw = angles::radians_to_degrees(std::atan2(delta.y, delta.x));
|
||||||
|
return AimAngles{*pitch, yaw};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; current_time <= m_maximum_simulation_time; current_time += m_simulation_time_step)
|
||||||
|
{
|
||||||
|
Vector3 target_pos = target.m_origin + target.m_velocity * current_time;
|
||||||
|
if (target.m_is_airborne)
|
||||||
|
target_pos.z -= 0.5f * m_gravity_constant * current_time * current_time;
|
||||||
|
|
||||||
|
const auto pitch = calculate_pitch(proj_origin, target_pos, bullet_gravity, v0, current_time);
|
||||||
|
if (!pitch)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Vector3 delta = target_pos - projectile.m_origin;
|
||||||
|
const float yaw = angles::radians_to_degrees(std::atan2(delta.y, delta.x));
|
||||||
|
return AimAngles{*pitch, yaw};
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::format("{} AVX2 feature is not enabled!", std::source_location::current().function_name()));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
ProjPredEngineAvx2::ProjPredEngineAvx2(const float gravity_constant, const float simulation_time_step,
|
ProjPredEngineAvx2::ProjPredEngineAvx2(const float gravity_constant, const float simulation_time_step,
|
||||||
const float maximum_simulation_time)
|
const float maximum_simulation_time)
|
||||||
: m_gravity_constant(gravity_constant), m_simulation_time_step(simulation_time_step),
|
: m_gravity_constant(gravity_constant), m_simulation_time_step(simulation_time_step),
|
||||||
|
|||||||
@@ -238,3 +238,53 @@ TEST(unit_test_cry_engine, loook_at_random_z_axis)
|
|||||||
}
|
}
|
||||||
EXPECT_LE(failed_points, 100);
|
EXPECT_LE(failed_points, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_cry_engine, ViewAnglesAsVector3Zero)
|
||||||
|
{
|
||||||
|
const omath::cry_engine::ViewAngles angles{};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_cry_engine, ViewAnglesAsVector3Values)
|
||||||
|
{
|
||||||
|
const omath::cry_engine::ViewAngles angles{
|
||||||
|
omath::cry_engine::PitchAngle::from_degrees(45.f),
|
||||||
|
omath::cry_engine::YawAngle::from_degrees(-90.f),
|
||||||
|
omath::cry_engine::RollAngle::from_degrees(30.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 45.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, -90.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 30.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_cry_engine, ViewAnglesAsVector3ClampedPitch)
|
||||||
|
{
|
||||||
|
// Pitch is clamped to [-90, 90]
|
||||||
|
const omath::cry_engine::ViewAngles angles{
|
||||||
|
omath::cry_engine::PitchAngle::from_degrees(120.f),
|
||||||
|
omath::cry_engine::YawAngle::from_degrees(0.f),
|
||||||
|
omath::cry_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 90.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_cry_engine, ViewAnglesAsVector3NormalizedYaw)
|
||||||
|
{
|
||||||
|
// Yaw is normalized to [-180, 180], 270 wraps to -90
|
||||||
|
const omath::cry_engine::ViewAngles angles{
|
||||||
|
omath::cry_engine::PitchAngle::from_degrees(0.f),
|
||||||
|
omath::cry_engine::YawAngle::from_degrees(270.f),
|
||||||
|
omath::cry_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_NEAR(vec.y, -90.f, 0.01f);
|
||||||
|
}
|
||||||
@@ -405,3 +405,232 @@ TEST(unit_test_frostbite_engine, look_at_down)
|
|||||||
std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_up).as_array()))
|
std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_up).as_array()))
|
||||||
EXPECT_NEAR(result, etalon, 0.0001f);
|
EXPECT_NEAR(result, etalon, 0.0001f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ViewAnglesAsVector3Zero)
|
||||||
|
{
|
||||||
|
const omath::frostbite_engine::ViewAngles angles{};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ViewAnglesAsVector3Values)
|
||||||
|
{
|
||||||
|
const omath::frostbite_engine::ViewAngles angles{
|
||||||
|
omath::frostbite_engine::PitchAngle::from_degrees(45.f),
|
||||||
|
omath::frostbite_engine::YawAngle::from_degrees(-90.f),
|
||||||
|
omath::frostbite_engine::RollAngle::from_degrees(30.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 45.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, -90.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 30.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ViewAnglesAsVector3ClampedPitch)
|
||||||
|
{
|
||||||
|
const omath::frostbite_engine::ViewAngles angles{
|
||||||
|
omath::frostbite_engine::PitchAngle::from_degrees(120.f),
|
||||||
|
omath::frostbite_engine::YawAngle::from_degrees(0.f),
|
||||||
|
omath::frostbite_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 90.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ViewAnglesAsVector3NormalizedYaw)
|
||||||
|
{
|
||||||
|
const omath::frostbite_engine::ViewAngles angles{
|
||||||
|
omath::frostbite_engine::PitchAngle::from_degrees(0.f),
|
||||||
|
omath::frostbite_engine::YawAngle::from_degrees(270.f),
|
||||||
|
omath::frostbite_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_NEAR(vec.y, -90.f, 0.01f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// extract_projection_params
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Tolerance: tan/atan round-trip in single precision introduces ~1e-5 rad
|
||||||
|
// error, which is ~5.7e-4 degrees.
|
||||||
|
static constexpr float k_fov_tolerance_deg = 0.001f;
|
||||||
|
static constexpr float k_aspect_tolerance = 1e-5f;
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_BasicRoundTrip)
|
||||||
|
{
|
||||||
|
// Build a matrix with known inputs and verify both outputs are recovered.
|
||||||
|
constexpr float fov_deg = 60.f;
|
||||||
|
constexpr float aspect = 16.f / 9.f;
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.1f, 1000.f, omath::NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_NegOneToOneDepthRange)
|
||||||
|
{
|
||||||
|
// The FOV/aspect encoding in rows 0 and 1 is identical for both NDC
|
||||||
|
// depth ranges, so extraction must work the same way.
|
||||||
|
constexpr float fov_deg = 75.f;
|
||||||
|
constexpr float aspect = 4.f / 3.f;
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.1f, 500.f, omath::NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_Fov45)
|
||||||
|
{
|
||||||
|
constexpr float fov_deg = 45.f;
|
||||||
|
constexpr float aspect = 16.f / 9.f;
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.01f, 1000.f);
|
||||||
|
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_Fov90)
|
||||||
|
{
|
||||||
|
constexpr float fov_deg = 90.f;
|
||||||
|
constexpr float aspect = 16.f / 9.f;
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.01f, 1000.f);
|
||||||
|
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_Fov120)
|
||||||
|
{
|
||||||
|
constexpr float fov_deg = 120.f;
|
||||||
|
constexpr float aspect = 16.f / 9.f;
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.01f, 1000.f);
|
||||||
|
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_AspectRatio_4by3)
|
||||||
|
{
|
||||||
|
constexpr float fov_deg = 60.f;
|
||||||
|
constexpr float aspect = 4.f / 3.f;
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.1f, 500.f);
|
||||||
|
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_AspectRatio_Ultrawide)
|
||||||
|
{
|
||||||
|
constexpr float fov_deg = 90.f;
|
||||||
|
constexpr float aspect = 21.f / 9.f;
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.1f, 500.f);
|
||||||
|
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_AspectRatio_Square)
|
||||||
|
{
|
||||||
|
constexpr float fov_deg = 90.f;
|
||||||
|
constexpr float aspect = 1.f;
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.1f, 500.f);
|
||||||
|
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_FovAndAspectAreIndependent)
|
||||||
|
{
|
||||||
|
// Changing only FOV must not affect recovered aspect ratio, and vice versa.
|
||||||
|
constexpr float aspect = 16.f / 9.f;
|
||||||
|
|
||||||
|
for (const float fov_deg : {45.f, 60.f, 90.f, 110.f})
|
||||||
|
{
|
||||||
|
const auto mat = omath::frostbite_engine::calc_perspective_projection_matrix(
|
||||||
|
fov_deg, aspect, 0.1f, 1000.f);
|
||||||
|
const auto [fov, ar] = omath::frostbite_engine::Camera::extract_projection_params(mat);
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), fov_deg, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_ViaCamera_RoundTrip)
|
||||||
|
{
|
||||||
|
// End-to-end: construct a Camera, retrieve its projection matrix, then
|
||||||
|
// recover the FOV and aspect ratio and compare against the original inputs.
|
||||||
|
constexpr auto fov_in = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
constexpr float aspect = 1920.f / 1080.f;
|
||||||
|
|
||||||
|
const auto cam = omath::frostbite_engine::Camera(
|
||||||
|
{0.f, 0.f, 0.f}, {}, {1920.f, 1080.f}, fov_in, 0.01f, 1000.f);
|
||||||
|
|
||||||
|
const auto [fov_out, ar_out] =
|
||||||
|
omath::frostbite_engine::Camera::extract_projection_params(cam.get_projection_matrix());
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov_out.as_degrees(), fov_in.as_degrees(), k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar_out, aspect, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_ViaCamera_AfterFovChange)
|
||||||
|
{
|
||||||
|
// Verify that the extracted FOV tracks the camera's FOV after set_field_of_view().
|
||||||
|
auto cam = omath::frostbite_engine::Camera(
|
||||||
|
{0.f, 0.f, 0.f}, {}, {1920.f, 1080.f},
|
||||||
|
omath::projection::FieldOfView::from_degrees(60.f), 0.01f, 1000.f);
|
||||||
|
|
||||||
|
cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(110.f));
|
||||||
|
|
||||||
|
const auto [fov, ar] =
|
||||||
|
omath::frostbite_engine::Camera::extract_projection_params(cam.get_projection_matrix());
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), 110.f, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, 1920.f / 1080.f, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_frostbite_engine, ExtractProjectionParams_ViaCamera_AfterViewportChange)
|
||||||
|
{
|
||||||
|
// Verify that the extracted aspect ratio tracks the viewport after set_view_port().
|
||||||
|
auto cam = omath::frostbite_engine::Camera(
|
||||||
|
{0.f, 0.f, 0.f}, {}, {1920.f, 1080.f},
|
||||||
|
omath::projection::FieldOfView::from_degrees(90.f), 0.01f, 1000.f);
|
||||||
|
|
||||||
|
cam.set_view_port({1280.f, 720.f});
|
||||||
|
|
||||||
|
const auto [fov, ar] =
|
||||||
|
omath::frostbite_engine::Camera::extract_projection_params(cam.get_projection_matrix());
|
||||||
|
|
||||||
|
EXPECT_NEAR(fov.as_degrees(), 90.f, k_fov_tolerance_deg);
|
||||||
|
EXPECT_NEAR(ar, 1280.f / 720.f, k_aspect_tolerance);
|
||||||
|
}
|
||||||
|
|||||||
@@ -281,3 +281,53 @@ TEST(unit_test_iw_engine, look_at_down)
|
|||||||
EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f);
|
EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f);
|
||||||
EXPECT_NEAR(dir_vector.y, 0.f, 0.001f);
|
EXPECT_NEAR(dir_vector.y, 0.f, 0.001f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_iw_engine, ViewAnglesAsVector3Zero)
|
||||||
|
{
|
||||||
|
const omath::iw_engine::ViewAngles angles{};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_iw_engine, ViewAnglesAsVector3Values)
|
||||||
|
{
|
||||||
|
const omath::iw_engine::ViewAngles angles{
|
||||||
|
omath::iw_engine::PitchAngle::from_degrees(45.f),
|
||||||
|
omath::iw_engine::YawAngle::from_degrees(-90.f),
|
||||||
|
omath::iw_engine::RollAngle::from_degrees(30.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 45.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, -90.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 30.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_iw_engine, ViewAnglesAsVector3ClampedPitch)
|
||||||
|
{
|
||||||
|
// Pitch is clamped to [-89, 89]
|
||||||
|
const omath::iw_engine::ViewAngles angles{
|
||||||
|
omath::iw_engine::PitchAngle::from_degrees(120.f),
|
||||||
|
omath::iw_engine::YawAngle::from_degrees(0.f),
|
||||||
|
omath::iw_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 89.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_iw_engine, ViewAnglesAsVector3NormalizedYaw)
|
||||||
|
{
|
||||||
|
// Yaw is normalized to [-180, 180], 270 wraps to -90
|
||||||
|
const omath::iw_engine::ViewAngles angles{
|
||||||
|
omath::iw_engine::PitchAngle::from_degrees(0.f),
|
||||||
|
omath::iw_engine::YawAngle::from_degrees(270.f),
|
||||||
|
omath::iw_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_NEAR(vec.y, -90.f, 0.01f);
|
||||||
|
}
|
||||||
@@ -395,3 +395,51 @@ TEST(unit_test_opengl_engine, look_at_down)
|
|||||||
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_up).as_array()))
|
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_up).as_array()))
|
||||||
EXPECT_NEAR(result, etalon, 0.0001f);
|
EXPECT_NEAR(result, etalon, 0.0001f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_opengl, ViewAnglesAsVector3Zero)
|
||||||
|
{
|
||||||
|
const omath::opengl_engine::ViewAngles angles{};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_opengl, ViewAnglesAsVector3Values)
|
||||||
|
{
|
||||||
|
const omath::opengl_engine::ViewAngles angles{
|
||||||
|
omath::opengl_engine::PitchAngle::from_degrees(45.f),
|
||||||
|
omath::opengl_engine::YawAngle::from_degrees(-90.f),
|
||||||
|
omath::opengl_engine::RollAngle::from_degrees(30.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 45.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, -90.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 30.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_opengl, ViewAnglesAsVector3ClampedPitch)
|
||||||
|
{
|
||||||
|
const omath::opengl_engine::ViewAngles angles{
|
||||||
|
omath::opengl_engine::PitchAngle::from_degrees(120.f),
|
||||||
|
omath::opengl_engine::YawAngle::from_degrees(0.f),
|
||||||
|
omath::opengl_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 90.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_opengl, ViewAnglesAsVector3NormalizedYaw)
|
||||||
|
{
|
||||||
|
const omath::opengl_engine::ViewAngles angles{
|
||||||
|
omath::opengl_engine::PitchAngle::from_degrees(0.f),
|
||||||
|
omath::opengl_engine::YawAngle::from_degrees(270.f),
|
||||||
|
omath::opengl_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_NEAR(vec.y, -90.f, 0.01f);
|
||||||
|
}
|
||||||
@@ -423,3 +423,53 @@ TEST(unit_test_source_engine, look_at_down)
|
|||||||
EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f);
|
EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f);
|
||||||
EXPECT_NEAR(dir_vector.y, 0.f, 0.001f);
|
EXPECT_NEAR(dir_vector.y, 0.f, 0.001f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_source_engine, ViewAnglesAsVector3Zero)
|
||||||
|
{
|
||||||
|
const omath::source_engine::ViewAngles angles{};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_source_engine, ViewAnglesAsVector3Values)
|
||||||
|
{
|
||||||
|
const omath::source_engine::ViewAngles angles{
|
||||||
|
omath::source_engine::PitchAngle::from_degrees(45.f),
|
||||||
|
omath::source_engine::YawAngle::from_degrees(-90.f),
|
||||||
|
omath::source_engine::RollAngle::from_degrees(30.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 45.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, -90.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 30.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_source_engine, ViewAnglesAsVector3ClampedPitch)
|
||||||
|
{
|
||||||
|
// Pitch is clamped to [-89, 89]
|
||||||
|
const omath::source_engine::ViewAngles angles{
|
||||||
|
omath::source_engine::PitchAngle::from_degrees(120.f),
|
||||||
|
omath::source_engine::YawAngle::from_degrees(0.f),
|
||||||
|
omath::source_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 89.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_source_engine, ViewAnglesAsVector3NormalizedYaw)
|
||||||
|
{
|
||||||
|
// Yaw is normalized to [-180, 180], 270 wraps to -90
|
||||||
|
const omath::source_engine::ViewAngles angles{
|
||||||
|
omath::source_engine::PitchAngle::from_degrees(0.f),
|
||||||
|
omath::source_engine::YawAngle::from_degrees(270.f),
|
||||||
|
omath::source_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_NEAR(vec.y, -90.f, 0.01f);
|
||||||
|
}
|
||||||
@@ -20,6 +20,11 @@
|
|||||||
#include <omath/engines/unreal_engine/traits/mesh_trait.hpp>
|
#include <omath/engines/unreal_engine/traits/mesh_trait.hpp>
|
||||||
#include <omath/engines/unreal_engine/traits/camera_trait.hpp>
|
#include <omath/engines/unreal_engine/traits/camera_trait.hpp>
|
||||||
|
|
||||||
|
#include <omath/engines/source_engine/traits/pred_engine_trait.hpp>
|
||||||
|
#include <omath/engines/source_engine/traits/camera_trait.hpp>
|
||||||
|
|
||||||
|
#include <omath/engines/cry_engine/traits/camera_trait.hpp>
|
||||||
|
|
||||||
#include <omath/projectile_prediction/projectile.hpp>
|
#include <omath/projectile_prediction/projectile.hpp>
|
||||||
#include <omath/projectile_prediction/target.hpp>
|
#include <omath/projectile_prediction/target.hpp>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -35,6 +40,132 @@ static void expect_matrix_near(const MatT& a, const MatT& b, float eps = 1e-5f)
|
|||||||
EXPECT_NEAR(a.at(r, c), b.at(r, c), eps);
|
EXPECT_NEAR(a.at(r, c), b.at(r, c), eps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Launch offset tests for all engines ──────────────────────────────────────
|
||||||
|
#include <omath/engines/cry_engine/traits/pred_engine_trait.hpp>
|
||||||
|
|
||||||
|
// Helper: verify that zero offset matches default-initialized offset behavior
|
||||||
|
template<typename Trait>
|
||||||
|
static void verify_launch_offset_at_time_zero(const Vector3<float>& origin, const Vector3<float>& offset)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = origin;
|
||||||
|
p.m_launch_offset = offset;
|
||||||
|
p.m_launch_speed = 100.f;
|
||||||
|
p.m_gravity_scale = 1.f;
|
||||||
|
|
||||||
|
const auto pos = Trait::predict_projectile_position(p, 0.f, 0.f, 0.f, 9.81f);
|
||||||
|
const auto expected = origin + offset;
|
||||||
|
EXPECT_NEAR(pos.x, expected.x, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos.y, expected.y, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos.z, expected.z, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Trait>
|
||||||
|
static void verify_zero_offset_matches_default()
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {10.f, 20.f, 30.f};
|
||||||
|
p.m_launch_offset = {0.f, 0.f, 0.f};
|
||||||
|
p.m_launch_speed = 50.f;
|
||||||
|
p.m_gravity_scale = 1.f;
|
||||||
|
|
||||||
|
projectile_prediction::Projectile p2;
|
||||||
|
p2.m_origin = {10.f, 20.f, 30.f};
|
||||||
|
p2.m_launch_speed = 50.f;
|
||||||
|
p2.m_gravity_scale = 1.f;
|
||||||
|
|
||||||
|
const auto pos1 = Trait::predict_projectile_position(p, 15.f, 30.f, 1.f, 9.81f);
|
||||||
|
const auto pos2 = Trait::predict_projectile_position(p2, 15.f, 30.f, 1.f, 9.81f);
|
||||||
|
#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64)
|
||||||
|
constexpr float tol = 1e-6f;
|
||||||
|
#else
|
||||||
|
constexpr float tol = 1e-4f;
|
||||||
|
#endif
|
||||||
|
EXPECT_NEAR(pos1.x, pos2.x, tol);
|
||||||
|
EXPECT_NEAR(pos1.y, pos2.y, tol);
|
||||||
|
EXPECT_NEAR(pos1.z, pos2.z, tol);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LaunchOffsetTests, Source_OffsetAtTimeZero)
|
||||||
|
{
|
||||||
|
verify_launch_offset_at_time_zero<source_engine::PredEngineTrait>({0, 0, 0}, {5, 3, -2});
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, Source_ZeroOffsetMatchesDefault)
|
||||||
|
{
|
||||||
|
verify_zero_offset_matches_default<source_engine::PredEngineTrait>();
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, Frostbite_OffsetAtTimeZero)
|
||||||
|
{
|
||||||
|
verify_launch_offset_at_time_zero<frostbite_engine::PredEngineTrait>({0, 0, 0}, {5, 3, -2});
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, Frostbite_ZeroOffsetMatchesDefault)
|
||||||
|
{
|
||||||
|
verify_zero_offset_matches_default<frostbite_engine::PredEngineTrait>();
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, IW_OffsetAtTimeZero)
|
||||||
|
{
|
||||||
|
verify_launch_offset_at_time_zero<iw_engine::PredEngineTrait>({0, 0, 0}, {5, 3, -2});
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, IW_ZeroOffsetMatchesDefault)
|
||||||
|
{
|
||||||
|
verify_zero_offset_matches_default<iw_engine::PredEngineTrait>();
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, OpenGL_OffsetAtTimeZero)
|
||||||
|
{
|
||||||
|
verify_launch_offset_at_time_zero<opengl_engine::PredEngineTrait>({0, 0, 0}, {5, 3, -2});
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, OpenGL_ZeroOffsetMatchesDefault)
|
||||||
|
{
|
||||||
|
verify_zero_offset_matches_default<opengl_engine::PredEngineTrait>();
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, Unity_OffsetAtTimeZero)
|
||||||
|
{
|
||||||
|
verify_launch_offset_at_time_zero<unity_engine::PredEngineTrait>({0, 0, 0}, {5, 3, -2});
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, Unity_ZeroOffsetMatchesDefault)
|
||||||
|
{
|
||||||
|
verify_zero_offset_matches_default<unity_engine::PredEngineTrait>();
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, Unreal_OffsetAtTimeZero)
|
||||||
|
{
|
||||||
|
verify_launch_offset_at_time_zero<unreal_engine::PredEngineTrait>({0, 0, 0}, {5, 3, -2});
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, Unreal_ZeroOffsetMatchesDefault)
|
||||||
|
{
|
||||||
|
verify_zero_offset_matches_default<unreal_engine::PredEngineTrait>();
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, CryEngine_OffsetAtTimeZero)
|
||||||
|
{
|
||||||
|
verify_launch_offset_at_time_zero<cry_engine::PredEngineTrait>({0, 0, 0}, {5, 3, -2});
|
||||||
|
}
|
||||||
|
TEST(LaunchOffsetTests, CryEngine_ZeroOffsetMatchesDefault)
|
||||||
|
{
|
||||||
|
verify_zero_offset_matches_default<cry_engine::PredEngineTrait>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that offset shifts the projectile position at t>0 as well
|
||||||
|
TEST(LaunchOffsetTests, OffsetShiftsTrajectory)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p_no_offset;
|
||||||
|
p_no_offset.m_origin = {0.f, 0.f, 0.f};
|
||||||
|
p_no_offset.m_launch_speed = 100.f;
|
||||||
|
p_no_offset.m_gravity_scale = 1.f;
|
||||||
|
|
||||||
|
projectile_prediction::Projectile p_with_offset;
|
||||||
|
p_with_offset.m_origin = {0.f, 0.f, 0.f};
|
||||||
|
p_with_offset.m_launch_offset = {10.f, 5.f, -3.f};
|
||||||
|
p_with_offset.m_launch_speed = 100.f;
|
||||||
|
p_with_offset.m_gravity_scale = 1.f;
|
||||||
|
|
||||||
|
const auto pos1 = source_engine::PredEngineTrait::predict_projectile_position(p_no_offset, 20.f, 45.f, 2.f, 9.81f);
|
||||||
|
const auto pos2 = source_engine::PredEngineTrait::predict_projectile_position(p_with_offset, 20.f, 45.f, 2.f, 9.81f);
|
||||||
|
|
||||||
|
// The difference should be exactly the launch offset
|
||||||
|
EXPECT_NEAR(pos2.x - pos1.x, 10.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos2.y - pos1.y, 5.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos2.z - pos1.z, -3.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
// Generic tests for PredEngineTrait behaviour across engines
|
// Generic tests for PredEngineTrait behaviour across engines
|
||||||
TEST(TraitTests, Frostbite_Pred_And_Mesh_And_Camera)
|
TEST(TraitTests, Frostbite_Pred_And_Mesh_And_Camera)
|
||||||
{
|
{
|
||||||
@@ -90,9 +221,14 @@ TEST(TraitTests, Frostbite_Pred_And_Mesh_And_Camera)
|
|||||||
// CameraTrait look at should be callable
|
// CameraTrait look at should be callable
|
||||||
const auto angles = e::CameraTrait::calc_look_at_angle({0, 0, 0}, {0, 1, 1});
|
const auto angles = e::CameraTrait::calc_look_at_angle({0, 0, 0}, {0, 1, 1});
|
||||||
(void)angles;
|
(void)angles;
|
||||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f);
|
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
||||||
expect_matrix_near(proj, expected);
|
expect_matrix_near(proj, expected);
|
||||||
|
|
||||||
|
const auto proj_zo = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
const auto expected_zo = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
expect_matrix_near(proj_zo, expected_zo);
|
||||||
|
EXPECT_NE(proj, proj_zo);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TraitTests, IW_Pred_And_Mesh_And_Camera)
|
TEST(TraitTests, IW_Pred_And_Mesh_And_Camera)
|
||||||
@@ -136,10 +272,15 @@ TEST(TraitTests, IW_Pred_And_Mesh_And_Camera)
|
|||||||
e::ViewAngles va;
|
e::ViewAngles va;
|
||||||
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
||||||
|
|
||||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(45.f), {1920.f, 1080.f}, 0.1f, 1000.f);
|
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(45.f), {1920.f, 1080.f}, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
const auto expected = e::calc_perspective_projection_matrix(45.f, 1920.f / 1080.f, 0.1f, 1000.f);
|
const auto expected = e::calc_perspective_projection_matrix(45.f, 1920.f / 1080.f, 0.1f, 1000.f);
|
||||||
expect_matrix_near(proj, expected);
|
expect_matrix_near(proj, expected);
|
||||||
|
|
||||||
|
const auto proj_zo = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(45.f), {1920.f, 1080.f}, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
const auto expected_zo = e::calc_perspective_projection_matrix(45.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
expect_matrix_near(proj_zo, expected_zo);
|
||||||
|
EXPECT_NE(proj, proj_zo);
|
||||||
|
|
||||||
// non-airborne
|
// non-airborne
|
||||||
t.m_is_airborne = false;
|
t.m_is_airborne = false;
|
||||||
const auto pred_ground_iw = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
const auto pred_ground_iw = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||||
@@ -186,10 +327,15 @@ TEST(TraitTests, OpenGL_Pred_And_Mesh_And_Camera)
|
|||||||
e::ViewAngles va;
|
e::ViewAngles va;
|
||||||
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
||||||
|
|
||||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f);
|
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
||||||
expect_matrix_near(proj, expected);
|
expect_matrix_near(proj, expected);
|
||||||
|
|
||||||
|
const auto proj_zo = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
const auto expected_zo = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
expect_matrix_near(proj_zo, expected_zo);
|
||||||
|
EXPECT_NE(proj, proj_zo);
|
||||||
|
|
||||||
// non-airborne
|
// non-airborne
|
||||||
t.m_is_airborne = false;
|
t.m_is_airborne = false;
|
||||||
const auto pred_ground_gl = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
const auto pred_ground_gl = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||||
@@ -236,10 +382,15 @@ TEST(TraitTests, Unity_Pred_And_Mesh_And_Camera)
|
|||||||
e::ViewAngles va;
|
e::ViewAngles va;
|
||||||
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
||||||
|
|
||||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f);
|
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
||||||
expect_matrix_near(proj, expected);
|
expect_matrix_near(proj, expected);
|
||||||
|
|
||||||
|
const auto proj_zo = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
const auto expected_zo = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
expect_matrix_near(proj_zo, expected_zo);
|
||||||
|
EXPECT_NE(proj, proj_zo);
|
||||||
|
|
||||||
// non-airborne
|
// non-airborne
|
||||||
t.m_is_airborne = false;
|
t.m_is_airborne = false;
|
||||||
const auto pred_ground_unity = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
const auto pred_ground_unity = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||||
@@ -286,12 +437,238 @@ TEST(TraitTests, Unreal_Pred_And_Mesh_And_Camera)
|
|||||||
e::ViewAngles va;
|
e::ViewAngles va;
|
||||||
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
expect_matrix_near(e::MeshTrait::rotation_matrix(va), e::rotation_matrix(va));
|
||||||
|
|
||||||
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f);
|
const auto proj = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
const auto expected = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f);
|
||||||
expect_matrix_near(proj, expected);
|
expect_matrix_near(proj, expected);
|
||||||
|
|
||||||
|
const auto proj_zo = e::CameraTrait::calc_projection_matrix(projection::FieldOfView::from_degrees(60.f), {1280.f, 720.f}, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
const auto expected_zo = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
expect_matrix_near(proj_zo, expected_zo);
|
||||||
|
EXPECT_NE(proj, proj_zo);
|
||||||
|
|
||||||
// non-airborne
|
// non-airborne
|
||||||
t.m_is_airborne = false;
|
t.m_is_airborne = false;
|
||||||
const auto pred_ground_unreal = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
const auto pred_ground_unreal = e::PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||||
EXPECT_NEAR(pred_ground_unreal.x, 4.f, 1e-6f);
|
EXPECT_NEAR(pred_ground_unreal.x, 4.f, 1e-6f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── NDC Depth Range tests for Source and CryEngine camera traits ────────────
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Source_BothDepthRanges)
|
||||||
|
{
|
||||||
|
namespace e = omath::source_engine;
|
||||||
|
|
||||||
|
const auto proj_no = e::CameraTrait::calc_projection_matrix(
|
||||||
|
projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f,
|
||||||
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
const auto expected_no = e::calc_perspective_projection_matrix(
|
||||||
|
90.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
expect_matrix_near(proj_no, expected_no);
|
||||||
|
|
||||||
|
const auto proj_zo = e::CameraTrait::calc_projection_matrix(
|
||||||
|
projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f,
|
||||||
|
NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
const auto expected_zo = e::calc_perspective_projection_matrix(
|
||||||
|
90.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
expect_matrix_near(proj_zo, expected_zo);
|
||||||
|
|
||||||
|
EXPECT_NE(proj_no, proj_zo);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, CryEngine_BothDepthRanges)
|
||||||
|
{
|
||||||
|
namespace e = omath::cry_engine;
|
||||||
|
|
||||||
|
const auto proj_no = e::CameraTrait::calc_projection_matrix(
|
||||||
|
projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f,
|
||||||
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
const auto expected_no = e::calc_perspective_projection_matrix(
|
||||||
|
90.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
expect_matrix_near(proj_no, expected_no);
|
||||||
|
|
||||||
|
const auto proj_zo = e::CameraTrait::calc_projection_matrix(
|
||||||
|
projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f,
|
||||||
|
NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
const auto expected_zo = e::calc_perspective_projection_matrix(
|
||||||
|
90.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
expect_matrix_near(proj_zo, expected_zo);
|
||||||
|
|
||||||
|
EXPECT_NE(proj_no, proj_zo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Verify Z mapping for ZERO_TO_ONE across all engines ─────────────────────
|
||||||
|
|
||||||
|
// Helper: projects a point at given z through a left-handed projection matrix and returns NDC z
|
||||||
|
template<class Type = float, MatStoreType Store = MatStoreType::ROW_MAJOR>
|
||||||
|
static float project_z_lh(const Mat<4, 4, Type, Store>& proj, float z)
|
||||||
|
{
|
||||||
|
auto clip = proj * mat_column_from_vector<Type, Store>({0, 0, static_cast<Type>(z)});
|
||||||
|
return static_cast<float>(clip.at(2, 0) / clip.at(3, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Source_ZeroToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::source_engine;
|
||||||
|
// Source is left-handed
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), 0.0f, 1e-4f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-4f);
|
||||||
|
EXPECT_GT(project_z_lh(proj, 500.f), 0.0f);
|
||||||
|
EXPECT_LT(project_z_lh(proj, 500.f), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, IW_ZeroToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::iw_engine;
|
||||||
|
// IW is left-handed
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), 0.0f, 1e-4f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-4f);
|
||||||
|
EXPECT_GT(project_z_lh(proj, 500.f), 0.0f);
|
||||||
|
EXPECT_LT(project_z_lh(proj, 500.f), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, OpenGL_ZeroToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::opengl_engine;
|
||||||
|
// OpenGL is right-handed (negative z forward), column-major
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
|
||||||
|
// OpenGL engine uses column-major matrices, project manually
|
||||||
|
auto proj_z = [&](float z) {
|
||||||
|
auto clip = proj * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>({0, 0, z});
|
||||||
|
return clip.at(2, 0) / clip.at(3, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_NEAR(proj_z(-0.1f), 0.0f, 1e-4f);
|
||||||
|
EXPECT_NEAR(proj_z(-1000.f), 1.0f, 1e-4f);
|
||||||
|
EXPECT_GT(proj_z(-500.f), 0.0f);
|
||||||
|
EXPECT_LT(proj_z(-500.f), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Frostbite_ZeroToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::frostbite_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), 0.0f, 1e-4f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-4f);
|
||||||
|
EXPECT_GT(project_z_lh(proj, 500.f), 0.0f);
|
||||||
|
EXPECT_LT(project_z_lh(proj, 500.f), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Unity_ZeroToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::unity_engine;
|
||||||
|
// Unity is right-handed, row-major
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
|
||||||
|
auto proj_z = [&](float z) {
|
||||||
|
auto clip = proj * mat_column_from_vector<float>({0, 0, z});
|
||||||
|
return clip.at(2, 0) / clip.at(3, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_NEAR(proj_z(-0.1f), 0.0f, 1e-4f);
|
||||||
|
EXPECT_NEAR(proj_z(-1000.f), 1.0f, 1e-4f);
|
||||||
|
EXPECT_GT(proj_z(-500.f), 0.0f);
|
||||||
|
EXPECT_LT(proj_z(-500.f), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Unreal_ZeroToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::unreal_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), 0.0f, 1e-4f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-4f);
|
||||||
|
EXPECT_GT(project_z_lh(proj, 500.f), 0.0f);
|
||||||
|
EXPECT_LT(project_z_lh(proj, 500.f), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, CryEngine_ZeroToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::cry_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), 0.0f, 1e-4f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-4f);
|
||||||
|
EXPECT_GT(project_z_lh(proj, 500.f), 0.0f);
|
||||||
|
EXPECT_LT(project_z_lh(proj, 500.f), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Verify Z mapping for NEGATIVE_ONE_TO_ONE across all engines ─────────────
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Source_NegativeOneToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::source_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), -1.0f, 1e-3f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, IW_NegativeOneToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::iw_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), -1.0f, 1e-3f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Frostbite_NegativeOneToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::frostbite_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), -1.0f, 1e-3f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Unreal_NegativeOneToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::unreal_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), -1.0f, 1e-3f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, CryEngine_NegativeOneToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::cry_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 0.1f), -1.0f, 1e-3f);
|
||||||
|
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, OpenGL_NegativeOneToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::opengl_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
|
||||||
|
auto proj_z = [&](float z) {
|
||||||
|
auto clip = proj * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>({0, 0, z});
|
||||||
|
return clip.at(2, 0) / clip.at(3, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_NEAR(proj_z(-0.1f), -1.0f, 1e-3f);
|
||||||
|
EXPECT_NEAR(proj_z(-1000.f), 1.0f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NDCDepthRangeTests, Unity_NegativeOneToOne_ZRange)
|
||||||
|
{
|
||||||
|
namespace e = omath::unity_engine;
|
||||||
|
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::NEGATIVE_ONE_TO_ONE);
|
||||||
|
|
||||||
|
auto proj_z = [&](float z) {
|
||||||
|
auto clip = proj * mat_column_from_vector<float>({0, 0, z});
|
||||||
|
return clip.at(2, 0) / clip.at(3, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_NEAR(proj_z(-0.1f), -1.0f, 1e-3f);
|
||||||
|
EXPECT_NEAR(proj_z(-1000.f), 1.0f, 1e-3f);
|
||||||
|
}
|
||||||
|
|||||||
@@ -417,3 +417,51 @@ TEST(unit_test_unity_engine, look_at_down)
|
|||||||
std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_up).as_array()))
|
std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_up).as_array()))
|
||||||
EXPECT_NEAR(result, etalon, 0.0001f);
|
EXPECT_NEAR(result, etalon, 0.0001f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_unity_engine, ViewAnglesAsVector3Zero)
|
||||||
|
{
|
||||||
|
const omath::unity_engine::ViewAngles angles{};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_unity_engine, ViewAnglesAsVector3Values)
|
||||||
|
{
|
||||||
|
const omath::unity_engine::ViewAngles angles{
|
||||||
|
omath::unity_engine::PitchAngle::from_degrees(45.f),
|
||||||
|
omath::unity_engine::YawAngle::from_degrees(-90.f),
|
||||||
|
omath::unity_engine::RollAngle::from_degrees(30.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 45.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, -90.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 30.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_unity_engine, ViewAnglesAsVector3ClampedPitch)
|
||||||
|
{
|
||||||
|
const omath::unity_engine::ViewAngles angles{
|
||||||
|
omath::unity_engine::PitchAngle::from_degrees(120.f),
|
||||||
|
omath::unity_engine::YawAngle::from_degrees(0.f),
|
||||||
|
omath::unity_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 90.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_unity_engine, ViewAnglesAsVector3NormalizedYaw)
|
||||||
|
{
|
||||||
|
const omath::unity_engine::ViewAngles angles{
|
||||||
|
omath::unity_engine::PitchAngle::from_degrees(0.f),
|
||||||
|
omath::unity_engine::YawAngle::from_degrees(270.f),
|
||||||
|
omath::unity_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_NEAR(vec.y, -90.f, 0.01f);
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ TEST(unit_test_unreal_engine, ForwardVectorRotationPitch)
|
|||||||
{
|
{
|
||||||
omath::unreal_engine::ViewAngles angles;
|
omath::unreal_engine::ViewAngles angles;
|
||||||
|
|
||||||
angles.pitch = omath::unreal_engine::PitchAngle::from_degrees(-90.f);
|
angles.pitch = omath::unreal_engine::PitchAngle::from_degrees(90.f);
|
||||||
|
|
||||||
const auto forward = omath::unreal_engine::forward_vector(angles);
|
const auto forward = omath::unreal_engine::forward_vector(angles);
|
||||||
EXPECT_NEAR(forward.x, omath::unreal_engine::k_abs_up.x, 0.00001f);
|
EXPECT_NEAR(forward.x, omath::unreal_engine::k_abs_up.x, 0.00001f);
|
||||||
@@ -44,7 +44,7 @@ TEST(unit_test_unreal_engine, ForwardVectorRotationRoll)
|
|||||||
{
|
{
|
||||||
omath::unreal_engine::ViewAngles angles;
|
omath::unreal_engine::ViewAngles angles;
|
||||||
|
|
||||||
angles.roll = omath::unreal_engine::RollAngle::from_degrees(-90.f);
|
angles.roll = omath::unreal_engine::RollAngle::from_degrees(90.f);
|
||||||
|
|
||||||
const auto forward = omath::unreal_engine::up_vector(angles);
|
const auto forward = omath::unreal_engine::up_vector(angles);
|
||||||
EXPECT_NEAR(forward.x, omath::unreal_engine::k_abs_right.x, 0.00001f);
|
EXPECT_NEAR(forward.x, omath::unreal_engine::k_abs_right.x, 0.00001f);
|
||||||
@@ -111,7 +111,7 @@ TEST(unit_test_unreal_engine, CameraSetAndGetOrigin)
|
|||||||
{
|
{
|
||||||
auto cam = omath::unreal_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f);
|
auto cam = omath::unreal_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f);
|
||||||
|
|
||||||
EXPECT_EQ(cam.get_origin(), omath::Vector3<float>{});
|
EXPECT_EQ(cam.get_origin(), omath::Vector3<double>{});
|
||||||
cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f));
|
cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f));
|
||||||
|
|
||||||
EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f);
|
EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f);
|
||||||
@@ -129,7 +129,7 @@ TEST(unit_test_unreal_engine, loook_at_random_all_axis)
|
|||||||
std::size_t failed_points = 0;
|
std::size_t failed_points = 0;
|
||||||
for (int i = 0; i < 100; i++)
|
for (int i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
const auto position_to_look = omath::Vector3<float>{dist(gen), dist(gen), dist(gen)};
|
const auto position_to_look = omath::Vector3<double>{dist(gen), dist(gen), dist(gen)};
|
||||||
|
|
||||||
if (cam.get_origin().distance_to(position_to_look) < 10)
|
if (cam.get_origin().distance_to(position_to_look) < 10)
|
||||||
continue;
|
continue;
|
||||||
@@ -151,7 +151,7 @@ TEST(unit_test_unreal_engine, loook_at_random_all_axis)
|
|||||||
TEST(unit_test_unreal_engine, loook_at_random_x_axis)
|
TEST(unit_test_unreal_engine, loook_at_random_x_axis)
|
||||||
{
|
{
|
||||||
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
|
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
|
||||||
std::uniform_real_distribution<float> dist(-1000.f, 1000.f);
|
std::uniform_real_distribution<double> dist(-1000.f, 1000.f);
|
||||||
|
|
||||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
auto cam = omath::unreal_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f);
|
auto cam = omath::unreal_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f);
|
||||||
@@ -159,7 +159,7 @@ TEST(unit_test_unreal_engine, loook_at_random_x_axis)
|
|||||||
std::size_t failed_points = 0;
|
std::size_t failed_points = 0;
|
||||||
for (int i = 0; i < 1000; i++)
|
for (int i = 0; i < 1000; i++)
|
||||||
{
|
{
|
||||||
const auto position_to_look = omath::Vector3<float>{dist(gen), dist(gen), dist(gen)};
|
const auto position_to_look = omath::Vector3<double>{dist(gen), dist(gen), dist(gen)};
|
||||||
|
|
||||||
if (cam.get_origin().distance_to(position_to_look) < 10)
|
if (cam.get_origin().distance_to(position_to_look) < 10)
|
||||||
continue;
|
continue;
|
||||||
@@ -190,7 +190,7 @@ TEST(unit_test_unreal_engine, loook_at_random_y_axis)
|
|||||||
std::size_t failed_points = 0;
|
std::size_t failed_points = 0;
|
||||||
for (int i = 0; i < 1000; i++)
|
for (int i = 0; i < 1000; i++)
|
||||||
{
|
{
|
||||||
const auto position_to_look = omath::Vector3<float>{0.f, dist(gen), 0.f};
|
const auto position_to_look = omath::Vector3<double>{0.f, dist(gen), 0.f};
|
||||||
|
|
||||||
if (cam.get_origin().distance_to(position_to_look) < 10)
|
if (cam.get_origin().distance_to(position_to_look) < 10)
|
||||||
continue;
|
continue;
|
||||||
@@ -221,7 +221,7 @@ TEST(unit_test_unreal_engine, loook_at_random_z_axis)
|
|||||||
std::size_t failed_points = 0;
|
std::size_t failed_points = 0;
|
||||||
for (int i = 0; i < 1000; i++)
|
for (int i = 0; i < 1000; i++)
|
||||||
{
|
{
|
||||||
const auto position_to_look = omath::Vector3<float>{0.f, 0.f, dist(gen)};
|
const auto position_to_look = omath::Vector3<double>{0.f, 0.f, dist(gen)};
|
||||||
|
|
||||||
if (cam.get_origin().distance_to(position_to_look) < 10)
|
if (cam.get_origin().distance_to(position_to_look) < 10)
|
||||||
continue;
|
continue;
|
||||||
@@ -418,3 +418,51 @@ TEST(unit_test_unreal_engine, look_at_down)
|
|||||||
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_up).as_array()))
|
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_up).as_array()))
|
||||||
EXPECT_NEAR(result, etalon, 0.0001f);
|
EXPECT_NEAR(result, etalon, 0.0001f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_unreal_engine, ViewAnglesAsVector3Zero)
|
||||||
|
{
|
||||||
|
const omath::unreal_engine::ViewAngles angles{};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_unreal_engine, ViewAnglesAsVector3Values)
|
||||||
|
{
|
||||||
|
const omath::unreal_engine::ViewAngles angles{
|
||||||
|
omath::unreal_engine::PitchAngle::from_degrees(45.f),
|
||||||
|
omath::unreal_engine::YawAngle::from_degrees(-90.f),
|
||||||
|
omath::unreal_engine::RollAngle::from_degrees(30.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 45.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.y, -90.f);
|
||||||
|
EXPECT_FLOAT_EQ(vec.z, 30.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_unreal_engine, ViewAnglesAsVector3ClampedPitch)
|
||||||
|
{
|
||||||
|
const omath::unreal_engine::ViewAngles angles{
|
||||||
|
omath::unreal_engine::PitchAngle::from_degrees(120.f),
|
||||||
|
omath::unreal_engine::YawAngle::from_degrees(0.f),
|
||||||
|
omath::unreal_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(vec.x, 90.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_unreal_engine, ViewAnglesAsVector3NormalizedYaw)
|
||||||
|
{
|
||||||
|
const omath::unreal_engine::ViewAngles angles{
|
||||||
|
omath::unreal_engine::PitchAngle::from_degrees(0.f),
|
||||||
|
omath::unreal_engine::YawAngle::from_degrees(270.f),
|
||||||
|
omath::unreal_engine::RollAngle::from_degrees(0.f)
|
||||||
|
};
|
||||||
|
const auto vec = angles.as_vector3();
|
||||||
|
|
||||||
|
EXPECT_NEAR(vec.y, -90.f, 0.01f);
|
||||||
|
}
|
||||||
@@ -20,19 +20,19 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
# include <unistd.h>
|
#include <fcntl.h>
|
||||||
# include <fcntl.h>
|
#include <unistd.h>
|
||||||
# if defined(__ANDROID__)
|
#if defined(__ANDROID__)
|
||||||
# if __ANDROID_API__ >= 30
|
#if __ANDROID_API__ >= 30
|
||||||
# include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
# define OMATH_TEST_USE_MEMFD 1
|
#define OMATH_TEST_USE_MEMFD 1
|
||||||
# endif
|
#endif
|
||||||
// Android < 30: fall through to tmpfile() path below
|
// Android < 30: fall through to tmpfile() path below
|
||||||
# else
|
#else
|
||||||
// Desktop Linux: memfd_create available since glibc 2.27 / kernel 3.17
|
// Desktop Linux: memfd_create available since glibc 2.27 / kernel 3.17
|
||||||
# include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
# define OMATH_TEST_USE_MEMFD 1
|
#define OMATH_TEST_USE_MEMFD 1
|
||||||
# endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class MemFdFile
|
class MemFdFile
|
||||||
@@ -57,9 +57,11 @@ public:
|
|||||||
MemFdFile(MemFdFile&& o) noexcept
|
MemFdFile(MemFdFile&& o) noexcept
|
||||||
: m_path(std::move(o.m_path))
|
: m_path(std::move(o.m_path))
|
||||||
#if defined(OMATH_TEST_USE_MEMFD)
|
#if defined(OMATH_TEST_USE_MEMFD)
|
||||||
, m_fd(o.m_fd)
|
,
|
||||||
|
m_fd(o.m_fd)
|
||||||
#else
|
#else
|
||||||
, m_temp_path(std::move(o.m_temp_path))
|
,
|
||||||
|
m_temp_path(std::move(o.m_temp_path))
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
#if defined(OMATH_TEST_USE_MEMFD)
|
#if defined(OMATH_TEST_USE_MEMFD)
|
||||||
@@ -69,9 +71,15 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool valid() const { return !m_path.empty(); }
|
[[nodiscard]] bool valid() const
|
||||||
|
{
|
||||||
|
return !m_path.empty();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] const std::filesystem::path& path() const { return m_path; }
|
[[nodiscard]] const std::filesystem::path& path() const
|
||||||
|
{
|
||||||
|
return m_path;
|
||||||
|
}
|
||||||
|
|
||||||
static MemFdFile create(const std::vector<std::uint8_t>& data)
|
static MemFdFile create(const std::vector<std::uint8_t>& data)
|
||||||
{
|
{
|
||||||
@@ -163,25 +171,27 @@ inline std::vector<std::uint8_t> build_minimal_pe(const std::vector<std::uint8_t
|
|||||||
|
|
||||||
std::vector<std::uint8_t> buf(data_off + section_bytes.size(), 0u);
|
std::vector<std::uint8_t> buf(data_off + section_bytes.size(), 0u);
|
||||||
|
|
||||||
buf[0] = 'M'; buf[1] = 'Z';
|
buf[0] = 'M';
|
||||||
|
buf[1] = 'Z';
|
||||||
std::memcpy(buf.data() + 0x3Cu, &e_lfanew, 4);
|
std::memcpy(buf.data() + 0x3Cu, &e_lfanew, 4);
|
||||||
|
|
||||||
buf[nt_off] = 'P'; buf[nt_off + 1] = 'E';
|
buf[nt_off] = 'P';
|
||||||
|
buf[nt_off + 1] = 'E';
|
||||||
|
|
||||||
const std::uint16_t machine = 0x8664u, num_sections = 1u;
|
constexpr std::uint16_t machine = 0x8664u, num_sections = 1u;
|
||||||
std::memcpy(buf.data() + fh_off, &machine, 2);
|
std::memcpy(buf.data() + fh_off, &machine, 2);
|
||||||
std::memcpy(buf.data() + fh_off + 2, &num_sections, 2);
|
std::memcpy(buf.data() + fh_off + 2, &num_sections, 2);
|
||||||
std::memcpy(buf.data() + fh_off + 16, &size_opt, 2);
|
std::memcpy(buf.data() + fh_off + 16, &size_opt, 2);
|
||||||
|
|
||||||
const std::uint16_t magic = 0x20Bu;
|
constexpr std::uint16_t magic = 0x20Bu;
|
||||||
std::memcpy(buf.data() + oh_off, &magic, 2);
|
std::memcpy(buf.data() + oh_off, &magic, 2);
|
||||||
|
|
||||||
const char name[8] = {'.','t','e','x','t',0,0,0};
|
constexpr char name[8] = {'.', 't', 'e', 'x', 't', 0, 0, 0};
|
||||||
std::memcpy(buf.data() + sh_off, name, 8);
|
std::memcpy(buf.data() + sh_off, name, 8);
|
||||||
|
|
||||||
const auto vsize = static_cast<std::uint32_t>(section_bytes.size());
|
const auto vsize = static_cast<std::uint32_t>(section_bytes.size());
|
||||||
const std::uint32_t vaddr = 0x1000u;
|
constexpr std::uint32_t vaddr = 0x1000u;
|
||||||
const auto ptr_raw = static_cast<std::uint32_t>(data_off);
|
constexpr auto ptr_raw = static_cast<std::uint32_t>(data_off);
|
||||||
std::memcpy(buf.data() + sh_off + 8, &vsize, 4);
|
std::memcpy(buf.data() + sh_off + 8, &vsize, 4);
|
||||||
std::memcpy(buf.data() + sh_off + 12, &vaddr, 4);
|
std::memcpy(buf.data() + sh_off + 12, &vaddr, 4);
|
||||||
std::memcpy(buf.data() + sh_off + 16, &vsize, 4);
|
std::memcpy(buf.data() + sh_off + 16, &vsize, 4);
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ TEST(AStarExtra, TrivialNeighbor)
|
|||||||
nav.m_vertex_map[v2] = {v1};
|
nav.m_vertex_map[v2] = {v1};
|
||||||
|
|
||||||
const auto path = Astar::find_path(v1, v2, nav);
|
const auto path = Astar::find_path(v1, v2, nav);
|
||||||
ASSERT_EQ(path.size(), 1u);
|
ASSERT_EQ(path.size(), 2u);
|
||||||
EXPECT_EQ(path.front(), v2);
|
EXPECT_EQ(path.front(), v1);
|
||||||
|
EXPECT_EQ(path.back(), v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AStarExtra, StartEqualsGoal)
|
TEST(AStarExtra, StartEqualsGoal)
|
||||||
@@ -101,7 +102,7 @@ TEST(AStarExtra, LongerPathAvoidsBlock)
|
|||||||
constexpr Vector3<float> goal = idx(2, 1);
|
constexpr Vector3<float> goal = idx(2, 1);
|
||||||
const auto path = Astar::find_path(start, goal, nav);
|
const auto path = Astar::find_path(start, goal, nav);
|
||||||
ASSERT_FALSE(path.empty());
|
ASSERT_FALSE(path.empty());
|
||||||
EXPECT_EQ(path.front(), goal);
|
EXPECT_EQ(path.back(), goal);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AstarTests, TrivialDirectNeighborPath)
|
TEST(AstarTests, TrivialDirectNeighborPath)
|
||||||
@@ -114,8 +115,9 @@ TEST(AstarTests, TrivialDirectNeighborPath)
|
|||||||
nav.m_vertex_map.emplace(v2, std::vector<Vector3<float>>{v1});
|
nav.m_vertex_map.emplace(v2, std::vector<Vector3<float>>{v1});
|
||||||
|
|
||||||
const auto path = Astar::find_path(v1, v2, nav);
|
const auto path = Astar::find_path(v1, v2, nav);
|
||||||
ASSERT_EQ(path.size(), 1u);
|
ASSERT_EQ(path.size(), 2u);
|
||||||
EXPECT_EQ(path.front(), v2);
|
EXPECT_EQ(path.front(), v1);
|
||||||
|
EXPECT_EQ(path.back(), v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AstarTests, NoPathWhenDisconnected)
|
TEST(AstarTests, NoPathWhenDisconnected)
|
||||||
|
|||||||
240
tests/general/unit_test_aabb.cpp
Normal file
240
tests/general/unit_test_aabb.cpp
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
//
|
||||||
|
// Created by Vladislav on 19.04.2026.
|
||||||
|
//
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "omath/3d_primitives/aabb.hpp"
|
||||||
|
|
||||||
|
using AABB = omath::primitives::Aabb<float>;
|
||||||
|
using Vec3 = omath::Vector3<float>;
|
||||||
|
|
||||||
|
// --- center() ---
|
||||||
|
|
||||||
|
TEST(AabbTests, CenterOfSymmetricBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr auto c = box.center();
|
||||||
|
EXPECT_FLOAT_EQ(c.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(c.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(c.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, CenterOfOffsetBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{1.f, 2.f, 3.f}, {3.f, 6.f, 7.f}};
|
||||||
|
constexpr auto c = box.center();
|
||||||
|
EXPECT_FLOAT_EQ(c.x, 2.f);
|
||||||
|
EXPECT_FLOAT_EQ(c.y, 4.f);
|
||||||
|
EXPECT_FLOAT_EQ(c.z, 5.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, CenterOfDegenerateBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{5.f, 5.f, 5.f}, {5.f, 5.f, 5.f}};
|
||||||
|
constexpr auto c = box.center();
|
||||||
|
EXPECT_FLOAT_EQ(c.x, 5.f);
|
||||||
|
EXPECT_FLOAT_EQ(c.y, 5.f);
|
||||||
|
EXPECT_FLOAT_EQ(c.z, 5.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- extents() ---
|
||||||
|
|
||||||
|
TEST(AabbTests, ExtentsOfSymmetricBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-2.f, -3.f, -4.f}, {2.f, 3.f, 4.f}};
|
||||||
|
constexpr auto e = box.extents();
|
||||||
|
EXPECT_FLOAT_EQ(e.x, 2.f);
|
||||||
|
EXPECT_FLOAT_EQ(e.y, 3.f);
|
||||||
|
EXPECT_FLOAT_EQ(e.z, 4.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, ExtentsOfUnitBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}};
|
||||||
|
constexpr auto e = box.extents();
|
||||||
|
EXPECT_FLOAT_EQ(e.x, 1.f);
|
||||||
|
EXPECT_FLOAT_EQ(e.y, 1.f);
|
||||||
|
EXPECT_FLOAT_EQ(e.z, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, ExtentsOfDegenerateBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{3.f, 3.f, 3.f}, {3.f, 3.f, 3.f}};
|
||||||
|
constexpr auto e = box.extents();
|
||||||
|
EXPECT_FLOAT_EQ(e.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(e.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(e.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
using UpAxis = omath::primitives::UpAxis;
|
||||||
|
|
||||||
|
// --- top() ---
|
||||||
|
|
||||||
|
TEST(AabbTests, TopYUpSymmetricBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}};
|
||||||
|
constexpr auto t = box.top<UpAxis::Y>();
|
||||||
|
EXPECT_FLOAT_EQ(t.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(t.y, 2.f);
|
||||||
|
EXPECT_FLOAT_EQ(t.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, TopYUpOffsetBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{1.f, 4.f, 2.f}, {3.f, 10.f, 6.f}};
|
||||||
|
constexpr auto t = box.top<UpAxis::Y>();
|
||||||
|
EXPECT_FLOAT_EQ(t.x, 2.f);
|
||||||
|
EXPECT_FLOAT_EQ(t.y, 10.f);
|
||||||
|
EXPECT_FLOAT_EQ(t.z, 4.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, TopZUpSymmetricBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}};
|
||||||
|
constexpr auto t = box.top<UpAxis::Z>();
|
||||||
|
EXPECT_FLOAT_EQ(t.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(t.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(t.z, 3.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, TopZUpOffsetBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{1.f, 4.f, 2.f}, {3.f, 10.f, 6.f}};
|
||||||
|
constexpr auto t = box.top<UpAxis::Z>();
|
||||||
|
EXPECT_FLOAT_EQ(t.x, 2.f);
|
||||||
|
EXPECT_FLOAT_EQ(t.y, 7.f);
|
||||||
|
EXPECT_FLOAT_EQ(t.z, 6.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, TopDefaultIsYUp)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{0.f, 0.f, 0.f}, {2.f, 4.f, 6.f}};
|
||||||
|
EXPECT_EQ(box.top(), box.top<UpAxis::Y>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- bottom() ---
|
||||||
|
|
||||||
|
TEST(AabbTests, BottomYUpSymmetricBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}};
|
||||||
|
constexpr auto b = box.bottom<UpAxis::Y>();
|
||||||
|
EXPECT_FLOAT_EQ(b.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(b.y, -2.f);
|
||||||
|
EXPECT_FLOAT_EQ(b.z, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, BottomYUpOffsetBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{1.f, 4.f, 2.f}, {3.f, 10.f, 6.f}};
|
||||||
|
constexpr auto b = box.bottom<UpAxis::Y>();
|
||||||
|
EXPECT_FLOAT_EQ(b.x, 2.f);
|
||||||
|
EXPECT_FLOAT_EQ(b.y, 4.f);
|
||||||
|
EXPECT_FLOAT_EQ(b.z, 4.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, BottomZUpSymmetricBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}};
|
||||||
|
constexpr auto b = box.bottom<UpAxis::Z>();
|
||||||
|
EXPECT_FLOAT_EQ(b.x, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(b.y, 0.f);
|
||||||
|
EXPECT_FLOAT_EQ(b.z, -3.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, BottomZUpOffsetBox)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{1.f, 4.f, 2.f}, {3.f, 10.f, 6.f}};
|
||||||
|
constexpr auto b = box.bottom<UpAxis::Z>();
|
||||||
|
EXPECT_FLOAT_EQ(b.x, 2.f);
|
||||||
|
EXPECT_FLOAT_EQ(b.y, 7.f);
|
||||||
|
EXPECT_FLOAT_EQ(b.z, 2.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, BottomDefaultIsYUp)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{0.f, 0.f, 0.f}, {2.f, 4.f, 6.f}};
|
||||||
|
EXPECT_EQ(box.bottom(), box.bottom<UpAxis::Y>());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, TopAndBottomAreSymmetric)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}};
|
||||||
|
EXPECT_FLOAT_EQ(box.top<UpAxis::Y>().y, -box.bottom<UpAxis::Y>().y);
|
||||||
|
EXPECT_FLOAT_EQ(box.top<UpAxis::Z>().z, -box.bottom<UpAxis::Z>().z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- is_collide() ---
|
||||||
|
|
||||||
|
TEST(AabbTests, OverlappingBoxesCollide)
|
||||||
|
{
|
||||||
|
constexpr AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr AABB b{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}};
|
||||||
|
EXPECT_TRUE(a.is_collide(b));
|
||||||
|
EXPECT_TRUE(b.is_collide(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, SeparatedBoxesDoNotCollide)
|
||||||
|
{
|
||||||
|
constexpr AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr AABB b{{2.f, 2.f, 2.f}, {4.f, 4.f, 4.f}};
|
||||||
|
EXPECT_FALSE(a.is_collide(b));
|
||||||
|
EXPECT_FALSE(b.is_collide(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, TouchingFacesCollide)
|
||||||
|
{
|
||||||
|
constexpr AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr AABB b{{1.f, -1.f, -1.f}, {3.f, 1.f, 1.f}};
|
||||||
|
EXPECT_TRUE(a.is_collide(b));
|
||||||
|
EXPECT_TRUE(b.is_collide(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, ContainedBoxCollides)
|
||||||
|
{
|
||||||
|
constexpr AABB outer{{-3.f, -3.f, -3.f}, {3.f, 3.f, 3.f}};
|
||||||
|
constexpr AABB inner{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
EXPECT_TRUE(outer.is_collide(inner));
|
||||||
|
EXPECT_TRUE(inner.is_collide(outer));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, SeparatedOnXAxisDoNotCollide)
|
||||||
|
{
|
||||||
|
constexpr AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr AABB b{{2.f, 0.f, 0.f}, {3.f, 1.f, 1.f}};
|
||||||
|
EXPECT_FALSE(a.is_collide(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, SeparatedOnYAxisDoNotCollide)
|
||||||
|
{
|
||||||
|
constexpr AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr AABB b{{0.f, 2.f, 0.f}, {1.f, 3.f, 1.f}};
|
||||||
|
EXPECT_FALSE(a.is_collide(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, SeparatedOnZAxisDoNotCollide)
|
||||||
|
{
|
||||||
|
constexpr AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr AABB b{{0.f, 0.f, 2.f}, {1.f, 1.f, 3.f}};
|
||||||
|
EXPECT_FALSE(a.is_collide(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, IdenticalBoxesCollide)
|
||||||
|
{
|
||||||
|
constexpr AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
EXPECT_TRUE(a.is_collide(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, DegeneratePointBoxCollidesWhenInsideOther)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr AABB point{{0.f, 0.f, 0.f}, {0.f, 0.f, 0.f}};
|
||||||
|
EXPECT_TRUE(box.is_collide(point));
|
||||||
|
EXPECT_TRUE(point.is_collide(box));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AabbTests, DegeneratePointBoxDoesNotCollideWhenOutside)
|
||||||
|
{
|
||||||
|
constexpr AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
constexpr AABB point{{5.f, 0.f, 0.f}, {5.f, 0.f, 0.f}};
|
||||||
|
EXPECT_FALSE(box.is_collide(point));
|
||||||
|
EXPECT_FALSE(point.is_collide(box));
|
||||||
|
}
|
||||||
275
tests/general/unit_test_cry_pred_engine_trait.cpp
Normal file
275
tests/general/unit_test_cry_pred_engine_trait.cpp
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
//
|
||||||
|
// Created by Vladislav on 20.04.2026.
|
||||||
|
//
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <omath/engines/cry_engine/traits/pred_engine_trait.hpp>
|
||||||
|
#include <omath/projectile_prediction/projectile.hpp>
|
||||||
|
#include <omath/projectile_prediction/target.hpp>
|
||||||
|
|
||||||
|
using namespace omath;
|
||||||
|
using namespace omath::cry_engine;
|
||||||
|
|
||||||
|
// ---- predict_projectile_position ----
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictProjectilePositionAtTimeZero)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {1.f, 2.f, 3.f};
|
||||||
|
p.m_launch_offset = {4.f, 5.f, 6.f};
|
||||||
|
p.m_launch_speed = 100.f;
|
||||||
|
p.m_gravity_scale = 1.f;
|
||||||
|
|
||||||
|
const auto pos = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 0.f, 9.81f);
|
||||||
|
|
||||||
|
// At t=0 no velocity is applied, just origin+offset
|
||||||
|
EXPECT_NEAR(pos.x, 5.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos.y, 7.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos.z, 9.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictProjectilePositionZeroAnglesForwardIsY)
|
||||||
|
{
|
||||||
|
// Cry engine forward = +Y. At pitch=0, yaw=0 the projectile travels along +Y.
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {0.f, 0.f, 0.f};
|
||||||
|
p.m_launch_speed = 10.f;
|
||||||
|
p.m_gravity_scale = 0.f; // no gravity so we isolate direction
|
||||||
|
|
||||||
|
const auto pos = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 9.81f);
|
||||||
|
|
||||||
|
EXPECT_NEAR(pos.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos.y, 10.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictProjectilePositionGravityDropsZ)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {0.f, 0.f, 0.f};
|
||||||
|
p.m_launch_speed = 10.f;
|
||||||
|
p.m_gravity_scale = 1.f;
|
||||||
|
|
||||||
|
const auto pos = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 2.f, 9.81f);
|
||||||
|
|
||||||
|
// z = 0 - (9.81 * 1) * (4) * 0.5 = -19.62
|
||||||
|
EXPECT_NEAR(pos.z, -9.81f * 4.f * 0.5f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictProjectilePositionGravityScaleZeroNoZDrop)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {0.f, 0.f, 0.f};
|
||||||
|
p.m_launch_speed = 10.f;
|
||||||
|
p.m_gravity_scale = 0.f;
|
||||||
|
|
||||||
|
const auto pos = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 3.f, 9.81f);
|
||||||
|
|
||||||
|
EXPECT_NEAR(pos.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictProjectilePositionWithLaunchOffset)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {5.f, 0.f, 0.f};
|
||||||
|
p.m_launch_offset = {0.f, 0.f, 2.f};
|
||||||
|
p.m_launch_speed = 10.f;
|
||||||
|
p.m_gravity_scale = 0.f;
|
||||||
|
|
||||||
|
const auto pos = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 0.f);
|
||||||
|
|
||||||
|
// launch position = {5, 0, 2}, travels along +Y by 10
|
||||||
|
EXPECT_NEAR(pos.x, 5.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos.y, 10.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos.z, 2.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- predict_target_position ----
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictTargetPositionGroundedStationary)
|
||||||
|
{
|
||||||
|
projectile_prediction::Target t;
|
||||||
|
t.m_origin = {10.f, 20.f, 5.f};
|
||||||
|
t.m_velocity = {0.f, 0.f, 0.f};
|
||||||
|
t.m_is_airborne = false;
|
||||||
|
|
||||||
|
const auto pred = PredEngineTrait::predict_target_position(t, 5.f, 9.81f);
|
||||||
|
|
||||||
|
EXPECT_NEAR(pred.x, 10.f, 1e-6f);
|
||||||
|
EXPECT_NEAR(pred.y, 20.f, 1e-6f);
|
||||||
|
EXPECT_NEAR(pred.z, 5.f, 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictTargetPositionGroundedMoving)
|
||||||
|
{
|
||||||
|
projectile_prediction::Target t;
|
||||||
|
t.m_origin = {0.f, 0.f, 0.f};
|
||||||
|
t.m_velocity = {3.f, 4.f, 0.f};
|
||||||
|
t.m_is_airborne = false;
|
||||||
|
|
||||||
|
const auto pred = PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||||
|
|
||||||
|
EXPECT_NEAR(pred.x, 6.f, 1e-6f);
|
||||||
|
EXPECT_NEAR(pred.y, 8.f, 1e-6f);
|
||||||
|
EXPECT_NEAR(pred.z, 0.f, 1e-6f); // grounded — no gravity
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictTargetPositionAirborneGravityDropsZ)
|
||||||
|
{
|
||||||
|
projectile_prediction::Target t;
|
||||||
|
t.m_origin = {0.f, 0.f, 20.f};
|
||||||
|
t.m_velocity = {0.f, 0.f, 0.f};
|
||||||
|
t.m_is_airborne = true;
|
||||||
|
|
||||||
|
const auto pred = PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
|
||||||
|
|
||||||
|
// z = 20 - 9.81 * 4 * 0.5 = 20 - 19.62 = 0.38
|
||||||
|
EXPECT_NEAR(pred.z, 20.f - 9.81f * 4.f * 0.5f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, PredictTargetPositionAirborneMovingWithGravity)
|
||||||
|
{
|
||||||
|
projectile_prediction::Target t;
|
||||||
|
t.m_origin = {0.f, 0.f, 50.f};
|
||||||
|
t.m_velocity = {10.f, 5.f, 0.f};
|
||||||
|
t.m_is_airborne = true;
|
||||||
|
|
||||||
|
const auto pred = PredEngineTrait::predict_target_position(t, 3.f, 9.81f);
|
||||||
|
|
||||||
|
EXPECT_NEAR(pred.x, 30.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pred.y, 15.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pred.z, 50.f - 9.81f * 9.f * 0.5f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- calc_vector_2d_distance ----
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcVector2dDistance_3_4_5)
|
||||||
|
{
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_vector_2d_distance({3.f, 4.f, 999.f}), 5.f, 1e-5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcVector2dDistance_ZeroVector)
|
||||||
|
{
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_vector_2d_distance({0.f, 0.f, 0.f}), 0.f, 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcVector2dDistance_ZIgnored)
|
||||||
|
{
|
||||||
|
// Z does not affect the 2D distance
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_vector_2d_distance({0.f, 5.f, 100.f}),
|
||||||
|
PredEngineTrait::calc_vector_2d_distance({0.f, 5.f, 0.f}), 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- get_vector_height_coordinate ----
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, GetVectorHeightCoordinate_ReturnsZ)
|
||||||
|
{
|
||||||
|
// Cry engine up = +Z
|
||||||
|
EXPECT_FLOAT_EQ(PredEngineTrait::get_vector_height_coordinate({1.f, 2.f, 7.f}), 7.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- calc_direct_pitch_angle ----
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectPitchAngle_Flat)
|
||||||
|
{
|
||||||
|
// Target at same height → pitch = 0
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_direct_pitch_angle({0.f, 0.f, 0.f}, {0.f, 100.f, 0.f}), 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectPitchAngle_LookingUp)
|
||||||
|
{
|
||||||
|
// Target at 45° above (equal XY distance and Z height)
|
||||||
|
// direction to {0, 1, 1} normalized = {0, 0.707, 0.707}, asin(0.707) = 45°
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_direct_pitch_angle({0.f, 0.f, 0.f}, {0.f, 1.f, 1.f}), 45.f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectPitchAngle_LookingDown)
|
||||||
|
{
|
||||||
|
// Target directly below
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_direct_pitch_angle({0.f, 0.f, 10.f}, {0.f, 0.f, 0.f}), -90.f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectPitchAngle_LookingDirectlyUp)
|
||||||
|
{
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_direct_pitch_angle({0.f, 0.f, 0.f}, {0.f, 0.f, 100.f}), 90.f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- calc_direct_yaw_angle ----
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectYawAngle_ForwardAlongY)
|
||||||
|
{
|
||||||
|
// Cry engine forward = +Y → yaw = 0
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_direct_yaw_angle({0.f, 0.f, 0.f}, {0.f, 100.f, 0.f}), 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectYawAngle_AlongPositiveX)
|
||||||
|
{
|
||||||
|
// direction = {1, 0, 0}, yaw = -atan2(1, 0) = -90°
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_direct_yaw_angle({0.f, 0.f, 0.f}, {100.f, 0.f, 0.f}), -90.f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectYawAngle_AlongNegativeX)
|
||||||
|
{
|
||||||
|
// direction = {-1, 0, 0}, yaw = -atan2(-1, 0) = 90°
|
||||||
|
EXPECT_NEAR(PredEngineTrait::calc_direct_yaw_angle({0.f, 0.f, 0.f}, {-100.f, 0.f, 0.f}), 90.f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectYawAngle_BackwardAlongNegY)
|
||||||
|
{
|
||||||
|
// direction = {0, -1, 0}, yaw = -atan2(0, -1) = ±180°
|
||||||
|
const float yaw = PredEngineTrait::calc_direct_yaw_angle({0.f, 0.f, 0.f}, {0.f, -100.f, 0.f});
|
||||||
|
EXPECT_NEAR(std::abs(yaw), 180.f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcDirectYawAngle_OffOriginCamera)
|
||||||
|
{
|
||||||
|
// Same relative direction regardless of camera position
|
||||||
|
const float yaw_a = PredEngineTrait::calc_direct_yaw_angle({0.f, 0.f, 0.f}, {0.f, 100.f, 0.f});
|
||||||
|
const float yaw_b = PredEngineTrait::calc_direct_yaw_angle({50.f, 50.f, 0.f}, {50.f, 150.f, 0.f});
|
||||||
|
EXPECT_NEAR(yaw_a, yaw_b, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- calc_viewpoint_from_angles ----
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcViewpointFromAngles_45Degrees)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {0.f, 0.f, 0.f};
|
||||||
|
p.m_launch_speed = 10.f;
|
||||||
|
|
||||||
|
// Target along +Y at distance 10; pitch=45° → height = 10 * tan(45°) = 10
|
||||||
|
const Vector3<float> target{0.f, 10.f, 0.f};
|
||||||
|
const auto vp = PredEngineTrait::calc_viewpoint_from_angles(p, target, 45.f);
|
||||||
|
|
||||||
|
EXPECT_NEAR(vp.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(vp.y, 10.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(vp.z, 10.f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcViewpointFromAngles_ZeroPitch)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {0.f, 0.f, 5.f};
|
||||||
|
p.m_launch_speed = 1.f;
|
||||||
|
|
||||||
|
const Vector3<float> target{3.f, 4.f, 0.f};
|
||||||
|
const auto vp = PredEngineTrait::calc_viewpoint_from_angles(p, target, 0.f);
|
||||||
|
|
||||||
|
// tan(0) = 0 → viewpoint Z = origin.z + 0 = 5
|
||||||
|
EXPECT_NEAR(vp.x, 3.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(vp.y, 4.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(vp.z, 5.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CryPredEngineTrait, CalcViewpointXYMatchesPredictedTargetXY)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {1.f, 2.f, 3.f};
|
||||||
|
p.m_launch_speed = 50.f;
|
||||||
|
|
||||||
|
const Vector3<float> target{10.f, 20.f, 5.f};
|
||||||
|
const auto vp = PredEngineTrait::calc_viewpoint_from_angles(p, target, 30.f);
|
||||||
|
|
||||||
|
// X and Y always match the predicted target position
|
||||||
|
EXPECT_NEAR(vp.x, target.x, 1e-4f);
|
||||||
|
EXPECT_NEAR(vp.y, target.y, 1e-4f);
|
||||||
|
}
|
||||||
125
tests/general/unit_test_line_tracer_aabb.cpp
Normal file
125
tests/general/unit_test_line_tracer_aabb.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
//
|
||||||
|
// Created by Vlad on 3/25/2025.
|
||||||
|
//
|
||||||
|
#include "omath/collision/line_tracer.hpp"
|
||||||
|
#include "omath/3d_primitives/aabb.hpp"
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using Vec3 = omath::Vector3<float>;
|
||||||
|
using Ray = omath::collision::Ray<>;
|
||||||
|
using LineTracer = omath::collision::LineTracer<>;
|
||||||
|
using AABB = omath::primitives::Aabb<float>;
|
||||||
|
|
||||||
|
static Ray make_ray(Vec3 start, Vec3 end, bool infinite = false)
|
||||||
|
{
|
||||||
|
Ray r;
|
||||||
|
r.start = start;
|
||||||
|
r.end = end;
|
||||||
|
r.infinite_length = infinite;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray passing straight through the center along Z axis
|
||||||
|
TEST(LineTracerAABBTests, HitCenterAlongZ)
|
||||||
|
{
|
||||||
|
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
const auto ray = make_ray({0.f, 0.f, -5.f}, {0.f, 0.f, 5.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.z, -1.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray passing straight through the center along X axis
|
||||||
|
TEST(LineTracerAABBTests, HitCenterAlongX)
|
||||||
|
{
|
||||||
|
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
const auto ray = make_ray({-5.f, 0.f, 0.f}, {5.f, 0.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, -1.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray that misses entirely (too far in Y)
|
||||||
|
TEST(LineTracerAABBTests, MissReturnsEnd)
|
||||||
|
{
|
||||||
|
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
const auto ray = make_ray({0.f, 5.f, -5.f}, {0.f, 5.f, 5.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_EQ(hit, ray.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray that stops short before reaching the box
|
||||||
|
TEST(LineTracerAABBTests, RayTooShortReturnsEnd)
|
||||||
|
{
|
||||||
|
const AABB box{{3.f, -1.f, -1.f}, {5.f, 1.f, 1.f}};
|
||||||
|
const auto ray = make_ray({0.f, 0.f, 0.f}, {2.f, 0.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_EQ(hit, ray.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infinite ray that starts before the box should hit
|
||||||
|
TEST(LineTracerAABBTests, InfiniteRayHits)
|
||||||
|
{
|
||||||
|
const AABB box{{3.f, -1.f, -1.f}, {5.f, 1.f, 1.f}};
|
||||||
|
const auto ray = make_ray({0.f, 0.f, 0.f}, {2.f, 0.f, 0.f}, true);
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 3.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray starting inside the box — t_min=0, so hit point equals ray.start
|
||||||
|
TEST(LineTracerAABBTests, RayStartsInsideBox)
|
||||||
|
{
|
||||||
|
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
const auto ray = make_ray({0.f, 0.f, 0.f}, {0.f, 0.f, 5.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
// t_min is clamped to 0, so hit == start
|
||||||
|
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray parallel to XY plane, pointing along X, at Z outside the box
|
||||||
|
TEST(LineTracerAABBTests, ParallelRayOutsideSlabMisses)
|
||||||
|
{
|
||||||
|
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
// Z component of ray is 3.0 — outside box's Z slab
|
||||||
|
const auto ray = make_ray({-5.f, 0.f, 3.f}, {5.f, 0.f, 3.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_EQ(hit, ray.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray parallel to XY plane, pointing along X, at Z inside the box
|
||||||
|
TEST(LineTracerAABBTests, ParallelRayInsideSlabHits)
|
||||||
|
{
|
||||||
|
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||||
|
const auto ray = make_ray({-5.f, 0.f, 0.f}, {5.f, 0.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, -1.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagonal ray hitting a corner region
|
||||||
|
TEST(LineTracerAABBTests, DiagonalRayHits)
|
||||||
|
{
|
||||||
|
const AABB box{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}};
|
||||||
|
const auto ray = make_ray({-1.f, -1.f, -1.f}, {3.f, 3.f, 3.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
// Entry point should be at (0,0,0)
|
||||||
|
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
@@ -220,8 +220,8 @@ TEST(UnitTestMatStandalone, Equanity)
|
|||||||
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
|
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
|
||||||
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
|
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
|
||||||
|
|
||||||
const auto proj_left_handed = omath::mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1, 1000);
|
const auto proj_left_handed = omath::mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
const auto proj_right_handed = omath::mat_perspective_right_handed(90.f, 16.f / 9.f, 0.1, 1000);
|
const auto proj_right_handed = omath::mat_perspective_right_handed(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
auto ndc_left_handed = proj_left_handed * omath::mat_column_from_vector(left_handed);
|
auto ndc_left_handed = proj_left_handed * omath::mat_column_from_vector(left_handed);
|
||||||
auto ndc_right_handed = proj_right_handed * omath::mat_column_from_vector(right_handed);
|
auto ndc_right_handed = proj_right_handed * omath::mat_column_from_vector(right_handed);
|
||||||
@@ -241,3 +241,125 @@ TEST(UnitTestMatStandalone, MatPerspectiveLeftHanded)
|
|||||||
|
|
||||||
EXPECT_TRUE(projected.at(2, 0) > -1.0f && projected.at(2, 0) < 0.f);
|
EXPECT_TRUE(projected.at(2, 0) > -1.0f && projected.at(2, 0) < 0.f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedZeroToOne)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
|
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
// Near plane point should map to z ~ 0
|
||||||
|
auto near_pt = proj * mat_column_from_vector<float>({0, 0, 0.1f});
|
||||||
|
near_pt /= near_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), 0.0f, 1e-4f);
|
||||||
|
|
||||||
|
// Far plane point should map to z ~ 1
|
||||||
|
auto far_pt = proj * mat_column_from_vector<float>({0, 0, 1000.f});
|
||||||
|
far_pt /= far_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-4f);
|
||||||
|
|
||||||
|
// Mid-range point should be in [0, 1]
|
||||||
|
auto mid_pt = proj * mat_column_from_vector<float>({0, 0, 500.f});
|
||||||
|
mid_pt /= mid_pt.at(3, 0);
|
||||||
|
EXPECT_GT(mid_pt.at(2, 0), 0.0f);
|
||||||
|
EXPECT_LT(mid_pt.at(2, 0), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveRightHandedZeroToOne)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_right_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
|
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
// Near plane point (negative z for right-handed) should map to z ~ 0
|
||||||
|
auto near_pt = proj * mat_column_from_vector<float>({0, 0, -0.1f});
|
||||||
|
near_pt /= near_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), 0.0f, 1e-4f);
|
||||||
|
|
||||||
|
// Far plane point should map to z ~ 1
|
||||||
|
auto far_pt = proj * mat_column_from_vector<float>({0, 0, -1000.f});
|
||||||
|
far_pt /= far_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-4f);
|
||||||
|
|
||||||
|
// Mid-range point should be in [0, 1]
|
||||||
|
auto mid_pt = proj * mat_column_from_vector<float>({0, 0, -500.f});
|
||||||
|
mid_pt /= mid_pt.at(3, 0);
|
||||||
|
EXPECT_GT(mid_pt.at(2, 0), 0.0f);
|
||||||
|
EXPECT_LT(mid_pt.at(2, 0), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveNegativeOneToOneRange)
|
||||||
|
{
|
||||||
|
// Verify existing [-1, 1] behavior with explicit template arg matches default
|
||||||
|
const auto proj_default = mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
const auto proj_explicit = mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
EXPECT_EQ(proj_default, proj_explicit);
|
||||||
|
|
||||||
|
// Near plane should map to z ~ -1
|
||||||
|
auto near_pt = proj_default * mat_column_from_vector<float>({0, 0, 0.1f});
|
||||||
|
near_pt /= near_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), -1.0f, 1e-3f);
|
||||||
|
|
||||||
|
// Far plane should map to z ~ 1
|
||||||
|
auto far_pt = proj_default * mat_column_from_vector<float>({0, 0, 1000.f});
|
||||||
|
far_pt /= far_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveZeroToOneEquanity)
|
||||||
|
{
|
||||||
|
// LH and RH should produce same NDC for mirrored z
|
||||||
|
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
|
||||||
|
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
|
||||||
|
|
||||||
|
const auto proj_lh = mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
|
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
const auto proj_rh = mat_perspective_right_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
|
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
auto ndc_lh = proj_lh * mat_column_from_vector(left_handed);
|
||||||
|
auto ndc_rh = proj_rh * mat_column_from_vector(right_handed);
|
||||||
|
|
||||||
|
ndc_lh /= ndc_lh.at(3, 0);
|
||||||
|
ndc_rh /= ndc_rh.at(3, 0);
|
||||||
|
|
||||||
|
EXPECT_EQ(ndc_lh, ndc_rh);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatOrthoLeftHandedZeroToOne)
|
||||||
|
{
|
||||||
|
const auto ortho = mat_ortho_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
|
-1.f, 1.f, -1.f, 1.f, 0.1f, 100.f);
|
||||||
|
|
||||||
|
// Near plane should map to z ~ 0
|
||||||
|
auto near_pt = ortho * mat_column_from_vector<float>({0, 0, 0.1f});
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), 0.0f, 1e-4f);
|
||||||
|
|
||||||
|
// Far plane should map to z ~ 1
|
||||||
|
auto far_pt = ortho * mat_column_from_vector<float>({0, 0, 100.f});
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatOrthoRightHandedZeroToOne)
|
||||||
|
{
|
||||||
|
const auto ortho = mat_ortho_right_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
|
-1.f, 1.f, -1.f, 1.f, 0.1f, 100.f);
|
||||||
|
|
||||||
|
// Near plane (negative z for RH) should map to z ~ 0
|
||||||
|
auto near_pt = ortho * mat_column_from_vector<float>({0, 0, -0.1f});
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), 0.0f, 1e-4f);
|
||||||
|
|
||||||
|
// Far plane should map to z ~ 1
|
||||||
|
auto far_pt = ortho * mat_column_from_vector<float>({0, 0, -100.f});
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatOrthoNegativeOneToOneDefault)
|
||||||
|
{
|
||||||
|
// Verify explicit [-1, 1] matches default
|
||||||
|
const auto ortho_default = mat_ortho_left_handed(-1.f, 1.f, -1.f, 1.f, 0.1f, 100.f);
|
||||||
|
const auto ortho_explicit = mat_ortho_left_handed<float, MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(-1.f, 1.f, -1.f, 1.f, 0.1f, 100.f);
|
||||||
|
|
||||||
|
EXPECT_EQ(ortho_default, ortho_explicit);
|
||||||
|
}
|
||||||
@@ -53,6 +53,47 @@ TEST(PredEngineTrait, CalcViewpointFromAngles)
|
|||||||
EXPECT_NEAR(vp.z, 10.f, 1e-6f);
|
EXPECT_NEAR(vp.z, 10.f, 1e-6f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(PredEngineTrait, PredictProjectilePositionWithLaunchOffset)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {0.f, 0.f, 0.f};
|
||||||
|
p.m_launch_offset = {5.f, 3.f, -2.f};
|
||||||
|
p.m_launch_speed = 10.f;
|
||||||
|
p.m_gravity_scale = 1.f;
|
||||||
|
|
||||||
|
// At time=0, projectile should be at launch_pos = origin + offset
|
||||||
|
const auto pos_t0 = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 0.f, 9.81f);
|
||||||
|
EXPECT_NEAR(pos_t0.x, 5.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos_t0.y, 3.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(pos_t0.z, -2.f, 1e-4f);
|
||||||
|
|
||||||
|
// At time=1 with zero pitch/yaw, should travel along X from the offset position
|
||||||
|
const auto pos_t1 = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 9.81f);
|
||||||
|
EXPECT_NEAR(pos_t1.x, 5.f + 10.f, 1e-3f);
|
||||||
|
EXPECT_NEAR(pos_t1.y, 3.f, 1e-3f);
|
||||||
|
EXPECT_NEAR(pos_t1.z, -2.f - 9.81f * 0.5f, 1e-3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PredEngineTrait, ZeroLaunchOffsetMatchesOriginalBehavior)
|
||||||
|
{
|
||||||
|
projectile_prediction::Projectile p;
|
||||||
|
p.m_origin = {10.f, 20.f, 30.f};
|
||||||
|
p.m_launch_offset = {0.f, 0.f, 0.f};
|
||||||
|
p.m_launch_speed = 15.f;
|
||||||
|
p.m_gravity_scale = 0.5f;
|
||||||
|
|
||||||
|
projectile_prediction::Projectile p_no_offset;
|
||||||
|
p_no_offset.m_origin = {10.f, 20.f, 30.f};
|
||||||
|
p_no_offset.m_launch_speed = 15.f;
|
||||||
|
p_no_offset.m_gravity_scale = 0.5f;
|
||||||
|
|
||||||
|
const auto pos1 = PredEngineTrait::predict_projectile_position(p, 30.f, 45.f, 2.f, 9.81f);
|
||||||
|
const auto pos2 = PredEngineTrait::predict_projectile_position(p_no_offset, 30.f, 45.f, 2.f, 9.81f);
|
||||||
|
EXPECT_NEAR(pos1.x, pos2.x, 1e-6f);
|
||||||
|
EXPECT_NEAR(pos1.y, pos2.y, 1e-6f);
|
||||||
|
EXPECT_NEAR(pos1.z, pos2.z, 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(PredEngineTrait, DirectAngles)
|
TEST(PredEngineTrait, DirectAngles)
|
||||||
{
|
{
|
||||||
constexpr Vector3<float> origin{0.f, 0.f, 0.f};
|
constexpr Vector3<float> origin{0.f, 0.f, 0.f};
|
||||||
|
|||||||
@@ -16,3 +16,280 @@ TEST(UnitTestPrediction, PredictionTest)
|
|||||||
EXPECT_NEAR(-42.547142, pitch.as_degrees(), 0.01f);
|
EXPECT_NEAR(-42.547142, pitch.as_degrees(), 0.01f);
|
||||||
EXPECT_NEAR(-1.181189, yaw.as_degrees(), 0.01f);
|
EXPECT_NEAR(-1.181189, yaw.as_degrees(), 0.01f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: verify aim_angles match angles derived from aim_point via CameraTrait
|
||||||
|
static void expect_angles_match_aim_point(const omath::projectile_prediction::Projectile& proj,
|
||||||
|
const omath::projectile_prediction::Target& target,
|
||||||
|
float gravity, float step, float max_time, float tolerance,
|
||||||
|
float angle_eps = 0.01f)
|
||||||
|
{
|
||||||
|
const omath::projectile_prediction::ProjPredEngineLegacy engine(gravity, step, max_time, tolerance);
|
||||||
|
|
||||||
|
const auto aim_point = engine.maybe_calculate_aim_point(proj, target);
|
||||||
|
const auto aim_angles = engine.maybe_calculate_aim_angles(proj, target);
|
||||||
|
|
||||||
|
ASSERT_TRUE(aim_point.has_value()) << "aim_point should have a solution";
|
||||||
|
ASSERT_TRUE(aim_angles.has_value()) << "aim_angles should have a solution";
|
||||||
|
|
||||||
|
// Source engine CameraTrait: pitch = -asin(dir.z), yaw = atan2(dir.y, dir.x)
|
||||||
|
// PredEngineTrait: pitch = asin(delta.z / dist), yaw = atan2(delta.y, delta.x)
|
||||||
|
// So aim_angles.pitch == -camera_pitch, aim_angles.yaw == camera_yaw
|
||||||
|
const auto [cam_pitch, cam_yaw, cam_roll] =
|
||||||
|
omath::source_engine::CameraTrait::calc_look_at_angle(proj.m_origin, aim_point.value());
|
||||||
|
|
||||||
|
EXPECT_NEAR(aim_angles->pitch, -cam_pitch.as_degrees(), angle_eps)
|
||||||
|
<< "pitch from aim_angles must match pitch derived from aim_point";
|
||||||
|
EXPECT_NEAR(aim_angles->yaw, cam_yaw.as_degrees(), angle_eps)
|
||||||
|
<< "yaw from aim_angles must match yaw derived from aim_point";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_StaticTarget)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {3, 2, 1}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 5.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_MovingTarget)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {500, 100, 0}, .m_velocity = {-50, 20, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 3000, .m_gravity_scale = 1.0};
|
||||||
|
|
||||||
|
expect_angles_match_aim_point(proj, target, 800, 1.f / 500.f, 30, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_AirborneTarget)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {200, 50, 300}, .m_velocity = {10, -5, -20}, .m_is_airborne = true};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 4000, .m_gravity_scale = 0.5};
|
||||||
|
|
||||||
|
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_HighArc)
|
||||||
|
{
|
||||||
|
// Target nearly directly above — high pitch angle
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {10, 0, 500}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 5000, .m_gravity_scale = 0.3};
|
||||||
|
|
||||||
|
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 5.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_NegativeYaw)
|
||||||
|
{
|
||||||
|
// Target behind and to the left — negative yaw quadrant
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {-200, -150, 10}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 5.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_WithLaunchOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {200, 0, 50}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_offset = {5, 0, -3}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 5.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: simulate projectile flight using aim_angles and verify it reaches the target.
|
||||||
|
// Steps the projectile forward in small increments, simultaneously predicts target position,
|
||||||
|
// and checks that the minimum distance is within hit_tolerance.
|
||||||
|
static void expect_projectile_hits_target(const omath::projectile_prediction::Projectile& proj,
|
||||||
|
const omath::projectile_prediction::Target& target,
|
||||||
|
float gravity, float engine_step, float max_time, float engine_tolerance,
|
||||||
|
float hit_tolerance, float sim_step = 1.f / 2000.f)
|
||||||
|
{
|
||||||
|
using Trait = omath::source_engine::PredEngineTrait;
|
||||||
|
const omath::projectile_prediction::ProjPredEngineLegacy engine(gravity, engine_step, max_time, engine_tolerance);
|
||||||
|
|
||||||
|
const auto aim_angles = engine.maybe_calculate_aim_angles(proj, target);
|
||||||
|
ASSERT_TRUE(aim_angles.has_value()) << "engine must find a solution";
|
||||||
|
|
||||||
|
float min_dist = std::numeric_limits<float>::max();
|
||||||
|
float best_time = 0.f;
|
||||||
|
|
||||||
|
for (float t = 0.f; t <= max_time; t += sim_step)
|
||||||
|
{
|
||||||
|
const auto proj_pos = Trait::predict_projectile_position(proj, aim_angles->pitch, aim_angles->yaw, t, gravity);
|
||||||
|
const auto tgt_pos = Trait::predict_target_position(target, t, gravity);
|
||||||
|
const float dist = proj_pos.distance_to(tgt_pos);
|
||||||
|
|
||||||
|
if (dist < min_dist)
|
||||||
|
{
|
||||||
|
min_dist = dist;
|
||||||
|
best_time = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early exit once distance starts increasing significantly after approaching
|
||||||
|
if (dist > min_dist + hit_tolerance * 10.f && min_dist < hit_tolerance * 100.f)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_LE(min_dist, hit_tolerance)
|
||||||
|
<< "Projectile must reach target. Closest approach: " << min_dist
|
||||||
|
<< " at t=" << best_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Simulation hit tests: no launch offset ─────────────────────────────────
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsStaticTarget_NoOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {3, 2, 1}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsMovingTarget_NoOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {500, 100, 0}, .m_velocity = {-50, 20, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 3000, .m_gravity_scale = 1.0};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 800, 1.f / 500.f, 30, 10.f, 15.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsAirborneTarget_NoOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {200, 50, 300}, .m_velocity = {10, -5, -20}, .m_is_airborne = true};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 4000, .m_gravity_scale = 0.5};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 10.f, 15.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsHighTarget_NoOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {10, 0, 500}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 5000, .m_gravity_scale = 0.3};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsNegativeYawTarget_NoOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {-200, -150, 10}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Simulation hit tests: with launch offset ────────────────────────────────
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsStaticTarget_SmallOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {200, 0, 50}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_offset = {5, 0, -3}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsStaticTarget_LargeXOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {300, 100, 0}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_offset = {20, 0, 0}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsStaticTarget_LargeYOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {150, -200, 30}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_offset = {0, 15, 0}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsStaticTarget_LargeZOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {100, 0, 200}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_offset = {0, 0, -10}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsStaticTarget_AllAxesOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {250, 80, 60}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {10, 5, 20}, .m_launch_offset = {8, -4, -6}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsMovingTarget_WithOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {400, 0, 50}, .m_velocity = {-30, 10, 5}, .m_is_airborne = false};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_offset = {10, -5, 2}, .m_launch_speed = 3000, .m_gravity_scale = 0.8};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 800, 1.f / 500.f, 30, 10.f, 15.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsAirborneTarget_WithOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {150, 80, 250}, .m_velocity = {5, -10, -30}, .m_is_airborne = true};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 50}, .m_launch_offset = {3, 7, -5}, .m_launch_speed = 4000, .m_gravity_scale = 0.5};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 10.f, 15.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProjectileSimulation, HitsNegativeYawTarget_WithOffset)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {-200, -150, 10}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
const omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_offset = {-5, 3, 2}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||||
|
|
||||||
|
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestPrediction, AimAnglesReturnsNulloptWhenNoSolution)
|
||||||
|
{
|
||||||
|
constexpr omath::projectile_prediction::Target target{
|
||||||
|
.m_origin = {100000, 0, 0}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||||
|
constexpr omath::projectile_prediction::Projectile proj = {
|
||||||
|
.m_origin = {0, 0, 0}, .m_launch_speed = 1, .m_gravity_scale = 1};
|
||||||
|
|
||||||
|
const omath::projectile_prediction::ProjPredEngineLegacy engine(9.81f, 0.1f, 2.f, 5.f);
|
||||||
|
|
||||||
|
const auto aim_point = engine.maybe_calculate_aim_point(proj, target);
|
||||||
|
const auto aim_angles = engine.maybe_calculate_aim_angles(proj, target);
|
||||||
|
|
||||||
|
EXPECT_FALSE(aim_point.has_value());
|
||||||
|
EXPECT_FALSE(aim_angles.has_value());
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,6 +46,22 @@ TEST(ProjPredLegacyMore, ZeroGravityUsesDirectPitchAndReturnsViewpoint)
|
|||||||
EXPECT_NEAR(v.z, 3.f, 1e-6f);
|
EXPECT_NEAR(v.z, 3.f, 1e-6f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ProjPredLegacyMore, ZeroGravityAimAnglesReturnsPitchAndYaw)
|
||||||
|
{
|
||||||
|
constexpr Projectile proj{ .m_origin = {0.f, 0.f, 0.f}, .m_launch_speed = 10.f, .m_gravity_scale = 0.f };
|
||||||
|
constexpr Target target{ .m_origin = {100.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
|
||||||
|
|
||||||
|
using Engine = omath::projectile_prediction::ProjPredEngineLegacy<FakeEngineZeroGravity>;
|
||||||
|
const Engine engine(9.8f, 0.1f, 5.f, 1e-3f);
|
||||||
|
|
||||||
|
const auto res = engine.maybe_calculate_aim_angles(proj, target);
|
||||||
|
ASSERT_TRUE(res.has_value());
|
||||||
|
// FakeEngineZeroGravity::calc_direct_pitch_angle returns 12.5f
|
||||||
|
EXPECT_NEAR(res->pitch, 12.5f, 1e-6f);
|
||||||
|
// FakeEngineZeroGravity::calc_direct_yaw_angle returns 0.f
|
||||||
|
EXPECT_NEAR(res->yaw, 0.f, 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
// Fake trait producing no valid launch angle (root < 0)
|
// Fake trait producing no valid launch angle (root < 0)
|
||||||
struct FakeEngineNoSolution
|
struct FakeEngineNoSolution
|
||||||
{
|
{
|
||||||
@@ -69,6 +85,9 @@ TEST(ProjPredLegacyMore, NoSolutionRootReturnsNullopt)
|
|||||||
|
|
||||||
const auto res = engine.maybe_calculate_aim_point(proj, target);
|
const auto res = engine.maybe_calculate_aim_point(proj, target);
|
||||||
EXPECT_FALSE(res.has_value());
|
EXPECT_FALSE(res.has_value());
|
||||||
|
|
||||||
|
const auto angles_res = engine.maybe_calculate_aim_angles(proj, target);
|
||||||
|
EXPECT_FALSE(angles_res.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fake trait where an angle exists but the projectile does not reach target (miss)
|
// Fake trait where an angle exists but the projectile does not reach target (miss)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user