Compare commits

..

86 Commits

Author SHA1 Message Date
orange 63ea93dc1c Merge pull request #198 from orange-cpp/feature/gcem
Feature/gcem
2026-06-15 01:17:23 +03:00
orange 10f5d866bd removed redundant option 2026-06-15 00:57:03 +03:00
orange 92da8bf0a6 fix 2026-06-15 00:45:16 +03:00
orange 47dcee037e removed gcem as dep 2026-06-15 00:37:47 +03:00
orange 75c3f2409d added modules support 2026-06-14 23:57:58 +03:00
orange a2a09fa8a6 removed .cpp for engines since they are no longer used 2026-06-14 23:47:25 +03:00
orange 43823c8fdc fix 2026-06-14 23:26:24 +03:00
orange 098be538f7 fix 2026-06-14 23:17:48 +03:00
orange 91d9335f83 added constexpr engine traits 2026-06-14 22:46:24 +03:00
orange 136cd39496 fix 2026-06-14 22:30:26 +03:00
orange bb618f692f added wrapers 2026-06-14 22:16:39 +03:00
orange 792fb13851 patch 2026-06-12 01:12:13 +03:00
orange 2d38387da9 fix 2026-06-12 01:05:14 +03:00
orange 5f14510e5b made constexpr cryengine camera & fromulas 2026-06-12 00:52:22 +03:00
orange 4a9125549f patch 2026-06-12 00:09:14 +03:00
orange d9acf5eea8 fix 2026-06-12 00:04:12 +03:00
orange 589f440e70 added gcem to pipelines 2026-06-11 23:54:53 +03:00
orange 854b50f317 added mat tests and triangle tests 2026-06-11 23:49:03 +03:00
orange a741fc1485 now add constexpr gcem tests for triangle and mat classes 2026-06-11 23:41:07 +03:00
orange a2be99be50 added more gcem to angle vec2,3 2026-06-11 23:20:18 +03:00
orange 00e7c564fd added gcem 2026-06-11 16:18:52 +03:00
orange 5d9dbec5b8 reverted fix 2026-06-10 15:22:31 +03:00
orange a2c9084e5f incremented version + improved naming 2026-06-10 14:29:18 +03:00
orange ad2523dc3a Merge pull request #197 from orange-cpp/feature/compile_time_patten_parsing
Feature/compile time patten parsing
2026-06-10 05:41:03 +03:00
orange 3599a7d592 fix 2026-06-10 05:22:04 +03:00
orange 00287c7a58 improved file scanners 2026-06-10 04:29:52 +03:00
orange ce589b4f17 added compile time pattern parsing 2026-06-10 04:09:13 +03:00
orange 8f6341e840 fix for lua 2026-06-08 19:22:04 +03:00
va_alpatov b9da58e138 added enity overlay aspect 2026-06-08 03:43:15 +03:00
orange 87896117d4 Merge pull request #196 from orange-cpp/feature/outline
improved outline arguments
2026-06-07 19:39:20 +03:00
va_alpatov de8a540426 applied fix for lua 2026-06-07 19:22:41 +03:00
va_alpatov d1eaee6e6e improved outline arguments 2026-06-07 19:08:01 +03:00
orange b9522fe191 nope 2026-06-03 17:34:06 +03:00
orange 2e1ff91156 added more tests 2026-06-03 16:26:31 +03:00
orange 40b2d2b332 Merge pull request #195 from orange-cpp/feature/rage_engine
added rage engine support
2026-06-03 16:15:43 +03:00
orange 8810ec20a5 added rage into read me 2026-06-03 16:14:13 +03:00
orange b7210dec4c added rage engine support
fix

patch
2026-06-03 15:35:41 +03:00
orange 56ed7e2f6e added outlined option for box 2026-05-23 11:56:52 +03:00
orange 39d0d0683d improvement 2026-05-22 12:35:14 +03:00
orange a04bceaeb6 Merge pull request #194 from orange-cpp/feature/code-style-skill
added code style skill for codex/claude
2026-05-22 12:05:39 +03:00
orange f0fe5821ed added code style skill for codex/claude 2026-05-22 12:05:06 +03:00
orange d8c5ea16fe Merge pull request #193 from orange-cpp/feature/nodiscrad-messages
Feature/nodiscrad messages
2026-05-22 11:50:24 +03:00
orange 848202cbd8 added nodiscard messages 2026-05-22 09:00:32 +03:00
orange 37128d18e7 camera message 2026-05-22 08:50:21 +03:00
orange 8433ef05ca Merge pull request #192 from orange-cpp/feature/lua
added more lua stuff
2026-05-18 11:11:13 +03:00
orange d9f2428e0e fixed for mingw 2026-05-18 10:54:48 +03:00
orange 23d3b7c9f5 windows fix 2026-05-18 10:34:23 +03:00
orange 36a7865b29 fixed arm 2026-05-18 10:28:07 +03:00
orange 2130d02090 patch 2026-05-18 10:22:36 +03:00
orange f602ab6538 Merge pull request #191 from orange-cpp/feature/mat_improvement
added improvement
2026-05-17 10:11:20 +03:00
orange e4087165b9 added safety check 2026-05-17 09:38:19 +03:00
orange cebcfc411d added improvement 2026-05-17 09:08:36 +03:00
orange a4fac65b7c added more lua stuff 2026-05-16 10:33:06 +03:00
va_alpatov 7f88bf8b21 hotfix 2026-05-16 05:35:41 +03:00
orange 93e70667f0 Merge pull request #190 from orange-cpp/feature/more-obb-tests
Feature/more obb tests
2026-05-14 03:05:50 +03:00
orange bdef596f16 added more tests 2026-05-14 02:30:21 +03:00
orange 7a2ac25e8d fixed inconsistant types 2026-05-14 01:37:33 +03:00
orange b6f41ed653 improved naming 2026-05-13 07:51:17 +03:00
orange 5ebba4a630 added constexpr 2026-05-13 07:03:31 +03:00
orange c73afa95cc update 2026-05-13 05:16:10 +03:00
orange 3ca657a048 added codex instructions 2026-05-13 05:12:19 +03:00
orange 9d1de20128 removed gitmodules 2026-05-12 08:23:17 +03:00
orange 6413c5d59c added ray tracer check for obb in line tracer 2026-05-07 05:30:47 +03:00
orange 94f88056cb Merge pull request #189 from orange-cpp/feature/obb
added obb
2026-05-07 05:23:20 +03:00
orange fbc35391c4 added obb 2026-05-07 05:04:35 +03:00
orange 6b637f6267 Merge pull request #188 from orange-cpp/feature/opengl_hook
Feature/opengl hook
2026-05-06 23:41:00 +03:00
orange fa52c9e985 added opengl for linux 2026-05-06 22:00:23 +03:00
orange 6ced4acdb6 removed copying 2026-05-06 20:10:34 +03:00
orange d90164cab8 added opengl hook 2026-05-06 20:05:45 +03:00
orange 29255cbb0e added claud config + skills 2026-05-06 04:04:57 +03:00
orange 8ad936f9f1 added separated mutexes for each call back 2026-05-04 20:55:42 +03:00
orange 57c834ded4 code style fixes 2026-05-04 19:42:39 +03:00
orange e25b1b3fc8 updated version 2026-05-04 06:09:27 +03:00
orange f2794230c3 Merge pull request #187 from orange-cpp/feature/hooking
Feature/hooking
2026-05-04 04:38:22 +03:00
orange 0515236c6c fix 2026-05-04 04:21:29 +03:00
orange 0215b7e0b7 using static for windows 2026-05-04 04:02:19 +03:00
orange 77b0ed3c81 fixed code style 2026-05-04 00:47:20 +03:00
orange 51bf4461ff fixed dx12 overlay 2026-05-04 00:45:48 +03:00
orange 232b48c3dd fixed dx12 hook 2026-05-04 00:10:53 +03:00
orange 105df90d05 decomposed method 2026-05-03 22:16:16 +03:00
orange 3aba53c8f8 fix 2026-05-03 21:59:48 +03:00
orange 71171acf36 added hooking of dx9 2026-05-03 21:58:51 +03:00
orange 1789b1ef51 added dx11 hook 2026-05-03 21:54:03 +03:00
orange 064d0cebbc update 2026-05-03 21:38:31 +03:00
orange 06d2752059 added dx12 hooking 2026-05-03 21:35:08 +03:00
orange 7e55b1d00e code clean up 2026-04-30 02:15:02 +03:00
126 changed files with 9691 additions and 1618 deletions
+92
View File
@@ -0,0 +1,92 @@
---
name: code-style
description: omath project C++ code style derived from .idea/codeStyles/Project.xml and .clang-format. Use when writing, editing, or reviewing C++ code in this repo so formatting and naming match the rest of the codebase.
---
# omath Code Style
Authoritative sources: `.clang-format` (formatting) and `.idea/codeStyles/Project.xml` (Rider/CLion naming + formatting). When in doubt, run clang-format — it is the enforced formatter (`clangFormatSettings.ENABLED = true`).
## Formatting
Base style: LLVM with Stroustrup-style braces.
- **Indent**: 4 spaces, no tabs. Tab width 4. Continuation indent 8.
- **Column limit**: 120.
- **Namespace indentation**: `All` — indent contents of every namespace.
- **Access modifier offset**: -4 (access specifiers sit at the class column; members indent one level deeper).
- **Pointer/reference alignment**: Left, with a space *before* `*` / `&` in declarations: `const Vector3& other`, `Type* ptr`.
- **Include blocks**: Merge. Sort using-declarations.
- **Keep blank lines**: max 2 in code and declarations. No blank line at the start of a block.
- **Align trailing comments**: false.
- **Break before binary operators**: non-assignment.
### Braces (Allman / next-line for everything)
Opening brace on its own line after:
class, struct, union, enum, namespace, function, control statement (`if`/`for`/`while`/`switch`), `case` label, lambda body, `catch`, `else`, `while` (of do-while), extern block.
Empty functions, records, and namespaces still split (`SplitEmptyFunction/Record/Namespace: true`).
### Short-form rules (all disabled)
Never collapse onto one line: blocks, functions, lambdas, `if` statements, loops.
### Templates
`template<class T>` goes on its own line, declaration follows on the next line:
```cpp
template<class Type>
requires std::is_arithmetic_v<Type>
class Vector3 : public Vector2<Type>
{
...
};
```
No space after `template` keyword. `requires` clause is not extra-indented.
### Spaces
- After control-statement keywords (`if (`, `for (`, `while (`).
- **Not** before `(` in function declarations/definitions/calls.
- After C-style cast: `(int) x`.
- Around range-based-for colon: `for (auto& x : xs)`.
- After commas in template args/params.
- Inside empty parens/braces/templates: no.
## Naming
| Kind | Style | Example |
|---|---|---|
| Namespaces | `snake_case` | `omath::pathfinding`, `omath::primitives` |
| Types (class, struct, enum, union, concept, type alias, typedef, template parameter) | `PascalCase` | `Vector3`, `NavigationMesh`, `Astar`, `ContainedType` |
| Functions (free + member) | `snake_case` | `find_path`, `distance_to_sqr`, `create_box` |
| Fields (class/struct/union members) | `snake_case` | `dir_forward`, `nav_mesh` |
| Variables (global, local, lambda) | `snake_case` | `length_value`, `side_size` |
| Parameters | `snake_case` | `dir_right`, `v_other` |
| Macros | `UPPER_SNAKE_CASE` | `OMATH_FOO_BAR` |
| Enumerators | `UPPER_SNAKE_CASE` | `IMPOSSIBLE_BETWEEN_ANGLE`, `WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS` |
Enum types themselves are PascalCase (`enum class Vector3Error`, `enum class Error`); their members are UPPER_SNAKE_CASE.
## Files
- Headers: `.hpp`, sources: `.cpp`. Both use `snake_case` filenames (e.g. `vector3.hpp`, `proj_pred_engine_avx2.hpp`).
- C headers: `.h`, sources: `.c` (no enforced filename style).
- CUDA: `.cu` / `.cuh`.
- C++ modules: `.ixx`, `.mxx`, `.cppm`, `.ccm`, `.cxxm`, `.c++m`.
- Header guard: `#pragma once` only — no `#ifndef` guards.
- File header comment is optional and follows the form `// Created by <name> on <date>`.
## Idioms used throughout the codebase
- Prefer `[[nodiscard]]`, `noexcept`, and `constexpr` on math / value-type methods.
- `namespace omath` is the root; sub-features live in nested namespaces (`omath::collision`, `omath::engines::source_engine`, etc.).
- Closing namespace brace gets a trailing comment: `} // namespace omath::primitives`.
- Use `std::expected<T, E>` with an `enum class …Error` for fallible operations (see `Vector3Error`, `projection::Error`).
## When editing
Match the surrounding style exactly. If a region disagrees with this guide, prefer the existing local style — don't reformat unrelated code (per the project's CLAUDE.md "Surgical Changes" rule). Run clang-format on touched files before committing.
@@ -0,0 +1,67 @@
---
name: karpathy-guidelines
description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria.
license: MIT
---
# Karpathy Guidelines
Behavioral guidelines to reduce common LLM coding mistakes, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
+92
View File
@@ -0,0 +1,92 @@
---
name: code-style
description: omath project C++ code style derived from .idea/codeStyles/Project.xml and .clang-format. Use when writing, editing, or reviewing C++ code in this repo so formatting and naming match the rest of the codebase.
---
# omath Code Style
Authoritative sources: `.clang-format` (formatting) and `.idea/codeStyles/Project.xml` (Rider/CLion naming + formatting). When in doubt, run clang-format — it is the enforced formatter (`clangFormatSettings.ENABLED = true`).
## Formatting
Base style: LLVM with Stroustrup-style braces.
- **Indent**: 4 spaces, no tabs. Tab width 4. Continuation indent 8.
- **Column limit**: 120.
- **Namespace indentation**: `All` — indent contents of every namespace.
- **Access modifier offset**: -4 (access specifiers sit at the class column; members indent one level deeper).
- **Pointer/reference alignment**: Left, with a space *before* `*` / `&` in declarations: `const Vector3& other`, `Type* ptr`.
- **Include blocks**: Merge. Sort using-declarations.
- **Keep blank lines**: max 2 in code and declarations. No blank line at the start of a block.
- **Align trailing comments**: false.
- **Break before binary operators**: non-assignment.
### Braces (Allman / next-line for everything)
Opening brace on its own line after:
class, struct, union, enum, namespace, function, control statement (`if`/`for`/`while`/`switch`), `case` label, lambda body, `catch`, `else`, `while` (of do-while), extern block.
Empty functions, records, and namespaces still split (`SplitEmptyFunction/Record/Namespace: true`).
### Short-form rules (all disabled)
Never collapse onto one line: blocks, functions, lambdas, `if` statements, loops.
### Templates
`template<class T>` goes on its own line, declaration follows on the next line:
```cpp
template<class Type>
requires std::is_arithmetic_v<Type>
class Vector3 : public Vector2<Type>
{
...
};
```
No space after `template` keyword. `requires` clause is not extra-indented.
### Spaces
- After control-statement keywords (`if (`, `for (`, `while (`).
- **Not** before `(` in function declarations/definitions/calls.
- After C-style cast: `(int) x`.
- Around range-based-for colon: `for (auto& x : xs)`.
- After commas in template args/params.
- Inside empty parens/braces/templates: no.
## Naming
| Kind | Style | Example |
|---|---|---|
| Namespaces | `snake_case` | `omath::pathfinding`, `omath::primitives` |
| Types (class, struct, enum, union, concept, type alias, typedef, template parameter) | `PascalCase` | `Vector3`, `NavigationMesh`, `Astar`, `ContainedType` |
| Functions (free + member) | `snake_case` | `find_path`, `distance_to_sqr`, `create_box` |
| Fields (class/struct/union members) | `snake_case` | `dir_forward`, `nav_mesh` |
| Variables (global, local, lambda) | `snake_case` | `length_value`, `side_size` |
| Parameters | `snake_case` | `dir_right`, `v_other` |
| Macros | `UPPER_SNAKE_CASE` | `OMATH_FOO_BAR` |
| Enumerators | `UPPER_SNAKE_CASE` | `IMPOSSIBLE_BETWEEN_ANGLE`, `WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS` |
Enum types themselves are PascalCase (`enum class Vector3Error`, `enum class Error`); their members are UPPER_SNAKE_CASE.
## Files
- Headers: `.hpp`, sources: `.cpp`. Both use `snake_case` filenames (e.g. `vector3.hpp`, `proj_pred_engine_avx2.hpp`).
- C headers: `.h`, sources: `.c` (no enforced filename style).
- CUDA: `.cu` / `.cuh`.
- C++ modules: `.ixx`, `.mxx`, `.cppm`, `.ccm`, `.cxxm`, `.c++m`.
- Header guard: `#pragma once` only — no `#ifndef` guards.
- File header comment is optional and follows the form `// Created by <name> on <date>`.
## Idioms used throughout the codebase
- Prefer `[[nodiscard]]`, `noexcept`, and `constexpr` on math / value-type methods.
- `namespace omath` is the root; sub-features live in nested namespaces (`omath::collision`, `omath::engines::source_engine`, etc.).
- Closing namespace brace gets a trailing comment: `} // namespace omath::primitives`.
- Use `std::expected<T, E>` with an `enum class …Error` for fallible operations (see `Vector3Error`, `projection::Error`).
## When editing
Match the surrounding style exactly. If a region disagrees with this guide, prefer the existing local style — don't reformat unrelated code (per the project's CLAUDE.md "Surgical Changes" rule). Run clang-format on touched files before committing.
@@ -0,0 +1,67 @@
---
name: karpathy-guidelines
description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria.
license: MIT
---
# Karpathy Guidelines
Behavioral guidelines to reduce common LLM coding mistakes, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
View File
+1
View File
@@ -139,6 +139,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOverrideWithDifferentVisibility/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" />
+65
View File
@@ -0,0 +1,65 @@
# AGENTS.md
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
+65
View File
@@ -0,0 +1,65 @@
# CLAUDE.md
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
+44 -6
View File
@@ -1,5 +1,4 @@
cmake_minimum_required(VERSION 3.26) cmake_minimum_required(VERSION 3.26)
file(READ VERSION OMATH_VERSION) file(READ VERSION OMATH_VERSION)
project(omath VERSION ${OMATH_VERSION} LANGUAGES CXX) project(omath VERSION ${OMATH_VERSION} LANGUAGES CXX)
@@ -31,9 +30,11 @@ option(OMATH_SUPRESS_SAFETY_CHECKS
option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF) option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF)
option(OMATH_ENABLE_FORCE_INLINE option(OMATH_ENABLE_FORCE_INLINE
"Will for compiler to make some functions to be force inlined no matter what" ON) "Will for compiler to make some functions to be force inlined no matter what" ON)
option(OMATH_ENABLE_LUA option(OMATH_ENABLE_LUA
"omath bindings for lua" OFF) "omath bindings for lua" OFF)
option(OMATH_ENABLE_HOOKING "omath will HooksManager that can hook DirectX/OpenGL automatically" OFF)
option(OMATH_ENABLE_MODULES "Build omath C++ module interface" OFF)
if(VCPKG_MANIFEST_FEATURES) if(VCPKG_MANIFEST_FEATURES)
foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES) foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
if(omath_feature STREQUAL "imgui") if(omath_feature STREQUAL "imgui")
@@ -48,6 +49,8 @@ if(VCPKG_MANIFEST_FEATURES)
set(OMATH_BUILD_EXAMPLES ON) set(OMATH_BUILD_EXAMPLES ON)
elseif(omath_feature STREQUAL "lua") elseif(omath_feature STREQUAL "lua")
set(OMATH_ENABLE_LUA ON) set(OMATH_ENABLE_LUA ON)
elseif(omath_feature STREQUAL "hooking")
set(OMATH_ENABLE_HOOKING ON)
endif() endif()
endforeach() endforeach()
@@ -78,6 +81,11 @@ if(${PROJECT_IS_TOP_LEVEL})
message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}") message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}")
message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}") message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}")
message(STATUS "[${PROJECT_NAME}]: Lua feature status ${OMATH_ENABLE_LUA}") message(STATUS "[${PROJECT_NAME}]: Lua feature status ${OMATH_ENABLE_LUA}")
message(STATUS "[${PROJECT_NAME}]: Modules feature status ${OMATH_ENABLE_MODULES}")
endif()
if(OMATH_STATIC_MSVC_RUNTIME_LIBRARY)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE STRING "" FORCE)
endif() endif()
file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
@@ -89,6 +97,20 @@ else()
add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS}) add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS})
endif() endif()
if(OMATH_ENABLE_MODULES)
if(CMAKE_VERSION VERSION_LESS 3.28)
message(FATAL_ERROR "OMATH_ENABLE_MODULES requires CMake 3.28 or newer")
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_SCAN_FOR_MODULES ON)
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(${PROJECT_NAME} PRIVATE /wd5244)
endif()
target_sources(
${PROJECT_NAME}
PUBLIC FILE_SET omath_modules TYPE CXX_MODULES BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/modules"
FILES "${CMAKE_CURRENT_SOURCE_DIR}/modules/omath.cppm")
endif()
if (OMATH_ENABLE_LUA) if (OMATH_ENABLE_LUA)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LUA) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LUA)
@@ -100,6 +122,21 @@ if (OMATH_ENABLE_LUA)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOL2_INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} PRIVATE ${SOL2_INCLUDE_DIRS})
endif () endif ()
if (OMATH_ENABLE_HOOKING)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_HOOKING)
find_package(safetyhook CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE safetyhook::safetyhook)
if (WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE d3d9 d3d11 d3d12 dxgi opengl32 gdi32)
elseif (UNIX AND NOT APPLE)
find_package(OpenGL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL ${CMAKE_DL_LIBS})
endif ()
endif ()
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}") target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}")
@@ -147,10 +184,6 @@ set_target_properties(
CXX_STANDARD 23 CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON) CXX_STANDARD_REQUIRED ON)
if(OMATH_STATIC_MSVC_RUNTIME_LIBRARY)
set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY
"MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
if(OMATH_USE_AVX2) if(OMATH_USE_AVX2)
if(MSVC) if(MSVC)
@@ -213,6 +246,10 @@ target_include_directories(
# Installation rules # Installation rules
if(OMATH_ENABLE_MODULES)
set(OMATH_MODULE_FILE_SET FILE_SET omath_modules DESTINATION modules)
endif()
# Install the library # Install the library
install( install(
TARGETS ${PROJECT_NAME} TARGETS ${PROJECT_NAME}
@@ -221,6 +258,7 @@ install(
LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries
RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on
# Windows) # Windows)
${OMATH_MODULE_FILE_SET}
) )
# Install headers as part of omath_component # Install headers as part of omath_component
+10 -6
View File
@@ -56,7 +56,9 @@
"hidden": true, "hidden": true,
"inherits": ["windows-base", "vcpkg-base"], "inherits": ["windows-base", "vcpkg-base"],
"cacheVariables": { "cacheVariables": {
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples" "VCPKG_TARGET_TRIPLET": "x64-windows-static",
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples;hooking",
"OMATH_STATIC_MSVC_RUNTIME_LIBRARY": "ON"
} }
}, },
{ {
@@ -89,9 +91,10 @@
"strategy": "external" "strategy": "external"
}, },
"cacheVariables": { "cacheVariables": {
"VCPKG_TARGET_TRIPLET": "x86-windows", "VCPKG_TARGET_TRIPLET": "x86-windows-static",
"VCPKG_HOST_TRIPLET": "x64-windows", "VCPKG_HOST_TRIPLET": "x64-windows",
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples" "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples",
"OMATH_STATIC_MSVC_RUNTIME_LIBRARY": "ON"
} }
}, },
{ {
@@ -114,9 +117,10 @@
"strategy": "external" "strategy": "external"
}, },
"cacheVariables": { "cacheVariables": {
"VCPKG_TARGET_TRIPLET": "arm64-windows", "VCPKG_TARGET_TRIPLET": "arm64-windows-static",
"VCPKG_HOST_TRIPLET": "arm64-windows", "VCPKG_HOST_TRIPLET": "arm64-windows-static",
"VCPKG_MANIFEST_FEATURES": "tests;imgui;examples" "VCPKG_MANIFEST_FEATURES": "tests;imgui;examples",
"OMATH_STATIC_MSVC_RUNTIME_LIBRARY": "ON"
} }
}, },
{ {
+2 -2
View File
@@ -4,8 +4,8 @@ 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.
- 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.
- Keith O'Hara aka kthohr for a C++ generalized constant expression-based math library that was used as reference.
And a big hand to everyone else who has contributed over the past! And a big hand to everyone else who has contributed over the past!
THANKS! <3 THANKS! <3
+6 -1
View File
@@ -81,7 +81,7 @@ if (auto screen = camera.world_to_screen(world_position)) {
- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces. - **Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! - **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
- **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**. - **Engine support**: Supports coordinate systems of **Source, Rage, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**.
- **Cross platform**: Supports Windows, MacOS and Linux. - **Cross platform**: Supports Windows, MacOS and Linux.
- **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps. - **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps.
- **Scripting**: Supports to make scripts in Lua out of box. - **Scripting**: Supports to make scripts in Lua out of box.
@@ -113,6 +113,10 @@ if (auto screen = camera.world_to_screen(world_position)) {
<br> <br>
![GTA5 Preview]
<br>
![OpenGL Preview] ![OpenGL Preview]
<br> <br>
@@ -144,6 +148,7 @@ if (auto screen = camera.world_to_screen(world_position)) {
[BO2 Preview]: docs/images/showcase/cod_bo2.png [BO2 Preview]: docs/images/showcase/cod_bo2.png
[CS2 Preview]: docs/images/showcase/cs2.jpeg [CS2 Preview]: docs/images/showcase/cs2.jpeg
[TF2 Preview]: docs/images/showcase/tf2.jpg [TF2 Preview]: docs/images/showcase/tf2.jpg
[GTA5 Preview]: https://i.imgur.com/W7T8RhZ.png
[OpenGL Preview]: docs/images/showcase/opengl.png [OpenGL Preview]: docs/images/showcase/opengl.png
<!----------------------------------{ Buttons }---------------------------------> <!----------------------------------{ Buttons }--------------------------------->
[QUICKSTART]: docs/getting_started.md [QUICKSTART]: docs/getting_started.md
+1 -1
View File
@@ -1 +1 @@
5.0.0 5.3.0
+14
View File
@@ -4,6 +4,20 @@ add_subdirectory(example_proj_mat_builder)
add_subdirectory(example_signature_scan) add_subdirectory(example_signature_scan)
add_subdirectory(example_hud) add_subdirectory(example_hud)
if(OMATH_ENABLE_HOOKING AND WIN32)
# Requires imgui with dx9-binding, dx11-binding, dx12-binding, opengl3-binding, win32-binding.
# Install via: vcpkg install imgui[dx9-binding,dx11-binding,dx12-binding,opengl3-binding,win32-binding]
find_package(imgui CONFIG QUIET)
if(imgui_FOUND)
add_subdirectory(example_dx9_hook)
add_subdirectory(example_dx11_hook)
add_subdirectory(example_dx12_hook)
add_subdirectory(example_opengl_hook)
else()
message(STATUS "[omath] imgui not found - hook examples skipped")
endif()
endif()
if(OMATH_ENABLE_VALGRIND) if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(example_projection_matrix_builder) omath_setup_valgrind(example_projection_matrix_builder)
omath_setup_valgrind(example_signature_scan) omath_setup_valgrind(example_signature_scan)
+13
View File
@@ -0,0 +1,13 @@
project(example_dx11_hook)
add_library(${PROJECT_NAME} SHARED dllmain.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 23
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(imgui CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath imgui::imgui d3d11 dxgi)
+154
View File
@@ -0,0 +1,154 @@
#include "omath/hooks/hooks_manager.hpp"
#include <Windows.h>
#include <d3d11.h>
#include <dxgi.h>
#include <imgui.h>
#include <imgui_impl_dx11.h>
#include <imgui_impl_win32.h>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
namespace
{
bool g_initialized = false;
bool g_init_attempted = false;
ID3D11Device* g_device = nullptr;
ID3D11DeviceContext* g_context = nullptr;
ID3D11RenderTargetView* g_render_target_view = nullptr;
void create_render_target(IDXGISwapChain* swap_chain)
{
ID3D11Texture2D* back_buffer = nullptr;
if (FAILED(swap_chain->GetBuffer(0, IID_PPV_ARGS(&back_buffer))))
return;
g_device->CreateRenderTargetView(back_buffer, nullptr, &g_render_target_view);
back_buffer->Release();
}
void init(IDXGISwapChain* swap_chain)
{
g_init_attempted = true;
if (FAILED(swap_chain->GetDevice(IID_PPV_ARGS(&g_device))))
return;
g_device->GetImmediateContext(&g_context);
DXGI_SWAP_CHAIN_DESC desc{};
swap_chain->GetDesc(&desc);
create_render_target(swap_chain);
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().LogFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
ImGui_ImplWin32_Init(desc.OutputWindow);
ImGui_ImplDX11_Init(g_device, g_context);
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_wnd_proc(
[](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT>
{
if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp))
return 0;
return std::nullopt;
});
std::ignore = mgr.hook_wnd_proc(desc.OutputWindow);
g_initialized = true;
}
void on_present(IDXGISwapChain* swap_chain, UINT, UINT)
{
if (!g_initialized)
{
if (!g_init_attempted)
init(swap_chain);
return;
}
if (!g_render_target_view)
create_render_target(swap_chain);
g_context->OMSetRenderTargets(1, &g_render_target_view, nullptr);
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
ImGui::SetNextWindowSize({300.f, 80.f}, ImGuiCond_Once);
ImGui::SetNextWindowPos({10.f, 10.f}, ImGuiCond_Once);
ImGui::Begin("omath | DX11 hook");
ImGui::Text("Hook active");
ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate);
ImGui::End();
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
void on_resize_buffers(IDXGISwapChain*, UINT, UINT, UINT, DXGI_FORMAT, UINT)
{
if (g_render_target_view)
{
g_render_target_view->Release();
g_render_target_view = nullptr;
}
}
} // namespace
BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(h_instance);
CreateThread(
nullptr, 0,
[](LPVOID) -> DWORD
{
while (!GetModuleHandle("d3d11.dll"))
Sleep(100);
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_present(on_present);
mgr.set_on_resize_buffers(on_resize_buffers);
mgr.hook_dx11();
return 0;
},
nullptr, 0, nullptr);
}
else if (reason == DLL_PROCESS_DETACH)
{
auto& mgr = omath::hooks::HooksManager::get();
mgr.unhook_wnd_proc();
mgr.unhook_dx11();
if (g_initialized)
{
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
if (g_render_target_view)
{
g_render_target_view->Release();
g_render_target_view = nullptr;
}
if (g_context)
{
g_context->Release();
g_context = nullptr;
}
if (g_device)
{
g_device->Release();
g_device = nullptr;
}
}
return TRUE;
}
+13
View File
@@ -0,0 +1,13 @@
project(example_dx12_hook)
add_library(${PROJECT_NAME} MODULE dllmain.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 23
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(imgui CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath imgui::imgui d3d12 dxgi)
+254
View File
@@ -0,0 +1,254 @@
#include "omath/hooks/hooks_manager.hpp"
#include <Windows.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <imgui.h>
#include <imgui_impl_dx12.h>
#include <imgui_impl_win32.h>
#include <tuple>
#include <vector>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
bool show_menu = true;
namespace
{
struct frame_context
{
ID3D12Resource* render_target = nullptr;
D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = {};
};
bool g_initialized = false;
bool g_init_attempted = false;
ID3D12Device* g_device = nullptr;
ID3D12CommandQueue* g_command_queue = nullptr;
IDXGISwapChain3* g_swap_chain = nullptr;
ID3D12DescriptorHeap* g_rtv_heap = nullptr;
ID3D12DescriptorHeap* g_srv_heap = nullptr;
ID3D12GraphicsCommandList* g_command_list = nullptr;
ID3D12CommandAllocator* g_command_allocator = nullptr;
std::vector<frame_context> g_frames;
void init(IDXGISwapChain* swap_chain)
{
g_init_attempted = true;
if (FAILED(swap_chain->QueryInterface(IID_PPV_ARGS(&g_swap_chain))))
return;
if (FAILED(swap_chain->GetDevice(IID_PPV_ARGS(&g_device))))
return;
DXGI_SWAP_CHAIN_DESC desc{};
swap_chain->GetDesc(&desc);
const UINT buffer_count = desc.BufferCount;
{
D3D12_DESCRIPTOR_HEAP_DESC heap_desc{};
heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
heap_desc.NumDescriptors = buffer_count;
heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
if (FAILED(g_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&g_srv_heap))))
return;
}
{
D3D12_DESCRIPTOR_HEAP_DESC heap_desc{};
heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
heap_desc.NumDescriptors = buffer_count;
heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
heap_desc.NodeMask = 1;
if (FAILED(g_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&g_rtv_heap))))
return;
}
if (FAILED(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(&g_command_allocator))))
return;
g_frames.resize(buffer_count);
const UINT rtv_size = g_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = g_rtv_heap->GetCPUDescriptorHandleForHeapStart();
for (UINT i = 0; i < buffer_count; ++i)
{
g_frames[i].rtv_handle = rtv_handle;
if (FAILED(swap_chain->GetBuffer(i, IID_PPV_ARGS(&g_frames[i].render_target))))
return;
g_device->CreateRenderTargetView(g_frames[i].render_target, nullptr, rtv_handle);
rtv_handle.ptr += rtv_size;
}
if (FAILED(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_command_allocator, nullptr,
IID_PPV_ARGS(&g_command_list))))
return;
g_command_list->Close();
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().LogFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui_ImplWin32_Init(desc.OutputWindow);
ImGui_ImplDX12_Init(g_device, static_cast<int>(buffer_count), desc.BufferDesc.Format, g_srv_heap,
g_srv_heap->GetCPUDescriptorHandleForHeapStart(),
g_srv_heap->GetGPUDescriptorHandleForHeapStart());
ImGui_ImplDX12_CreateDeviceObjects();
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_wnd_proc(
[](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT>
{
if (!show_menu)
return std::nullopt;
ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp);
return true;
});
std::ignore = mgr.hook_wnd_proc(desc.OutputWindow);
g_initialized = true;
}
void on_execute_command_lists(ID3D12CommandQueue* queue, UINT, ID3D12CommandList* const*)
{
if (!g_command_queue)
g_command_queue = queue;
}
void on_present(IDXGISwapChain* swap_chain, UINT, UINT)
{
if (!g_initialized)
{
if (!g_init_attempted && g_command_queue)
init(swap_chain);
return;
}
if (!g_command_queue)
return;
if (GetAsyncKeyState(VK_INSERT) & 1)
show_menu = !show_menu;
if (!show_menu)
return;
ImGui_ImplDX12_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
ImGui::GetIO().MouseDrawCursor = true;
ImGui::ShowDemoWindow();
ImGui::EndFrame();
const UINT buf_idx = g_swap_chain->GetCurrentBackBufferIndex();
auto& fc = g_frames[buf_idx];
g_command_allocator->Reset();
g_command_list->Reset(g_command_allocator, nullptr);
D3D12_RESOURCE_BARRIER barrier{};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = fc.render_target;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
g_command_list->ResourceBarrier(1, &barrier);
g_command_list->OMSetRenderTargets(1, &fc.rtv_handle, FALSE, nullptr);
g_command_list->SetDescriptorHeaps(1, &g_srv_heap);
ImGui::Render();
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_command_list);
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
g_command_list->ResourceBarrier(1, &barrier);
g_command_list->Close();
ID3D12CommandList* cmd_lists[] = {g_command_list};
g_command_queue->ExecuteCommandLists(1, cmd_lists);
}
void release_dx12_resources()
{
for (auto& fc : g_frames)
{
if (fc.render_target)
{
fc.render_target->Release();
fc.render_target = nullptr;
}
}
g_frames.clear();
if (g_command_allocator)
{
g_command_allocator->Release();
g_command_allocator = nullptr;
}
if (g_command_list)
{
g_command_list->Release();
g_command_list = nullptr;
}
if (g_srv_heap)
{
g_srv_heap->Release();
g_srv_heap = nullptr;
}
if (g_rtv_heap)
{
g_rtv_heap->Release();
g_rtv_heap = nullptr;
}
if (g_swap_chain)
{
g_swap_chain->Release();
g_swap_chain = nullptr;
}
if (g_device)
{
g_device->Release();
g_device = nullptr;
}
}
} // namespace
BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(h_instance);
CreateThread(
nullptr, 0,
[](LPVOID) -> DWORD
{
while (!GetModuleHandle("d3d12.dll"))
Sleep(100);
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_present(on_present);
mgr.set_on_execute_command_lists(on_execute_command_lists);
std::ignore = mgr.hook_dx12();
return 0;
},
nullptr, 0, nullptr);
}
else if (reason == DLL_PROCESS_DETACH)
{
auto& mgr = omath::hooks::HooksManager::get();
mgr.unhook_wnd_proc();
mgr.unhook_dx12();
if (g_initialized)
{
ImGui_ImplDX12_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
release_dx12_resources();
}
return TRUE;
}
+13
View File
@@ -0,0 +1,13 @@
project(example_dx9_hook)
add_library(${PROJECT_NAME} MODULE dllmain.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 23
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(imgui CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath imgui::imgui d3d9)
+107
View File
@@ -0,0 +1,107 @@
#include <Windows.h>
#include <d3d9.h>
#include <imgui.h>
#include <imgui_impl_dx9.h>
#include <imgui_impl_win32.h>
#include "omath/hooks/hooks_manager.hpp"
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
namespace
{
bool g_initialized = false;
bool g_init_attempted = false;
void init(IDirect3DDevice9* device)
{
g_init_attempted = true;
D3DDEVICE_CREATION_PARAMETERS params{};
if (FAILED(device->GetCreationParameters(&params)))
return;
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().LogFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
ImGui_ImplWin32_Init(params.hFocusWindow);
ImGui_ImplDX9_Init(device);
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_wnd_proc([](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT> {
if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp))
return 0;
return std::nullopt;
});
mgr.hook_wnd_proc(params.hFocusWindow);
g_initialized = true;
}
void on_present(IDirect3DDevice9* device, const RECT*, const RECT*, HWND, const RGNDATA*)
{
if (!g_initialized)
{
if (!g_init_attempted)
init(device);
return;
}
ImGui_ImplDX9_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
ImGui::SetNextWindowSize({300.f, 80.f}, ImGuiCond_Once);
ImGui::SetNextWindowPos({10.f, 10.f}, ImGuiCond_Once);
ImGui::Begin("omath | DX9 hook");
ImGui::Text("Hook active");
ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate);
ImGui::End();
ImGui::EndFrame();
ImGui::Render();
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
}
void on_reset(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*)
{
if (g_initialized)
ImGui_ImplDX9_InvalidateDeviceObjects();
}
} // namespace
BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(h_instance);
CreateThread(nullptr, 0, [](LPVOID) -> DWORD
{
while (!GetModuleHandle("d3d9.dll"))
Sleep(100);
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_dx9_present(on_present);
mgr.set_on_dx9_reset(on_reset);
mgr.hook_dx9();
return 0;
}, nullptr, 0, nullptr);
}
else if (reason == DLL_PROCESS_DETACH)
{
auto& mgr = omath::hooks::HooksManager::get();
mgr.unhook_wnd_proc();
mgr.unhook_dx9();
if (g_initialized)
{
ImGui_ImplDX9_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
}
return TRUE;
}
+42 -22
View File
@@ -71,6 +71,7 @@ namespace imgui_desktop::gui
ImGui::SliderFloat("X##ent", &m_entity_x, 100.f, vp->Size.x - 100.f); ImGui::SliderFloat("X##ent", &m_entity_x, 100.f, vp->Size.x - 100.f);
ImGui::SliderFloat("Top Y", &m_entity_top_y, 20.f, m_entity_bottom_y - 20.f); ImGui::SliderFloat("Top Y", &m_entity_top_y, 20.f, m_entity_bottom_y - 20.f);
ImGui::SliderFloat("Bottom Y", &m_entity_bottom_y, m_entity_top_y + 20.f, vp->Size.y - 20.f); ImGui::SliderFloat("Bottom Y", &m_entity_bottom_y, m_entity_top_y + 20.f, vp->Size.y - 20.f);
ImGui::SliderFloat("Aspect", &m_entity_aspect, 1.f, 10.f);
} }
if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen))
@@ -82,6 +83,7 @@ namespace imgui_desktop::gui
ImGui::Checkbox("Dashed", &m_show_dashed_box); ImGui::Checkbox("Dashed", &m_show_dashed_box);
ImGui::ColorEdit4("Color##box", reinterpret_cast<float*>(&m_box_color), ImGuiColorEditFlags_NoInputs); ImGui::ColorEdit4("Color##box", reinterpret_cast<float*>(&m_box_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Fill##box", reinterpret_cast<float*>(&m_box_fill), ImGuiColorEditFlags_NoInputs); ImGui::ColorEdit4("Fill##box", reinterpret_cast<float*>(&m_box_fill), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Outline##box", reinterpret_cast<float*>(&m_box_outline), ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Thickness", &m_box_thickness, 0.5f, 5.f); ImGui::SliderFloat("Thickness", &m_box_thickness, 0.5f, 5.f);
ImGui::SliderFloat("Corner ratio", &m_corner_ratio, 0.05f, 0.5f); ImGui::SliderFloat("Corner ratio", &m_corner_ratio, 0.05f, 0.5f);
ImGui::Separator(); ImGui::Separator();
@@ -195,23 +197,33 @@ namespace imgui_desktop::gui
const DashedBar dbar{m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, const DashedBar dbar{m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width,
m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset}; m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset};
omath::hud::EntityOverlay({m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y}, auto outline_helper = [](const bool is_outline) -> Outlined
{
return is_outline ? Outlined::On : Outlined::Off;
};
omath::hud::EntityOverlay({m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y}, m_entity_aspect,
std::make_shared<omath::hud::ImguiHudRenderer>()) std::make_shared<omath::hud::ImguiHudRenderer>())
.contents( .contents(
// ── Boxes ──────────────────────────────────────────────────── // ── Boxes ────────────────────────────────────────────────────
when(m_show_box, Box{m_box_color, m_box_fill, m_box_thickness}), when(m_show_box, Box{m_box_color, m_box_fill, m_box_outline, m_box_thickness}),
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_box_outline, 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, Label{{0.f, 1.f, 0.f, 1.f},
Label{{0.f, 1.f, 0.f, 1.f}, m_label_offset, m_outlined, "Health: 100/100"}), m_label_offset,
when(m_show_right_labels, outline_helper(m_outlined),
Label{{1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: 125/125"}), "Health: 100/100"}),
when(m_show_right_labels, when(m_show_right_labels, Label{{1.f, 0.f, 0.f, 1.f},
Label{{1.f, 0.f, 1.f, 1.f}, m_label_offset, m_outlined, "*LOCKED*"}), m_label_offset,
outline_helper(m_outlined),
"Shield: 125/125"}),
when(m_show_right_labels, Label{{1.f, 0.f, 1.f, 1.f},
m_label_offset,
outline_helper(m_outlined),
"*LOCKED*"}),
SpaceVertical{10}, SpaceVertical{10},
when(m_show_ring, ProgressRing{m_ring_color, m_ring_bg, m_ring_radius, m_ring_ratio, when(m_show_ring, ProgressRing{m_ring_color, m_ring_bg, m_ring_radius, m_ring_ratio,
@@ -220,33 +232,41 @@ namespace imgui_desktop::gui
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,
m_label_offset, m_outlined, "Armor: 75"}), Label{omath::Color::from_rgba(255, 128, 0, 255), m_label_offset,
when(m_show_left_labels, Label{omath::Color::from_rgba(0, 200, 255, 255), outline_helper(m_outlined), "Armor: 75"}),
m_label_offset, m_outlined, "Level: 42"}), when(m_show_left_labels,
Label{omath::Color::from_rgba(0, 200, 255, 255), m_label_offset,
outline_helper(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,
m_label_offset, m_outlined, "*VISIBLE*"}}), Centered{Label{omath::Color::from_rgba(0, 255, 255, 255), m_label_offset,
outline_helper(m_outlined), "*VISIBLE*"}}),
when(m_show_top_labels, Label{omath::Color::from_rgba(255, 255, 0, 255), m_label_offset, when(m_show_top_labels, Label{omath::Color::from_rgba(255, 255, 0, 255), m_label_offset,
m_outlined, "*SCOPED*"}), outline_helper(m_outlined), "*SCOPED*"}),
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*"}), outline_helper(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,
m_label_offset, m_outlined, "PlayerName"}}), Centered{Label{omath::Color::from_rgba(255, 255, 255, 255), m_label_offset,
outline_helper(m_outlined), "PlayerName"}}),
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, outline_helper(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_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_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_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}));
} }
+2 -2
View File
@@ -26,11 +26,11 @@ namespace imgui_desktop::gui
bool m_opened = true; bool m_opened = true;
// Entity // Entity
float m_entity_x = 550.f, m_entity_top_y = 150.f, m_entity_bottom_y = 450.f; float m_entity_x = 550.f, m_entity_top_y = 150.f, m_entity_bottom_y = 450.f, m_entity_aspect = 4.f;
// Box // Box
omath::Color m_box_color{1.f, 1.f, 1.f, 1.f}; omath::Color m_box_color{1.f, 1.f, 1.f, 1.f};
omath::Color m_box_fill{0.f, 0.f, 0.f, 0.f}; omath::Color m_box_fill{0.f, 0.f, 0.f, 0.f};
omath::Color m_box_outline{0.f, 0.f, 0.f, 0.f};
float m_box_thickness = 1.f, m_corner_ratio = 0.2f; float m_box_thickness = 1.f, m_corner_ratio = 0.2f;
bool m_show_box = true, m_show_cornered_box = true, m_show_dashed_box = false; bool m_show_box = true, m_show_cornered_box = true, m_show_dashed_box = false;
@@ -0,0 +1,13 @@
project(example_opengl_hook)
add_library(${PROJECT_NAME} MODULE dllmain.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 23
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(imgui CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath imgui::imgui opengl32 gdi32)
+116
View File
@@ -0,0 +1,116 @@
#include "omath/hooks/hooks_manager.hpp"
#include <Windows.h>
#include <chrono>
#include <imgui.h>
#include <imgui_impl_opengl3.h>
#include <imgui_impl_win32.h>
#include <optional>
#include <thread>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
namespace
{
bool g_initialized = false;
bool g_init_attempted = false;
bool g_show_menu = true;
constexpr auto g_module_wait_delay = std::chrono::milliseconds{100};
void init(HDC hdc)
{
g_init_attempted = true;
const HWND hwnd = WindowFromDC(hdc);
if (!hwnd)
return;
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().LogFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplOpenGL3_Init();
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_wnd_proc(
[](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT>
{
if (!g_show_menu)
return std::nullopt;
if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp))
return 0;
return std::nullopt;
});
(void)mgr.hook_wnd_proc(hwnd);
g_initialized = true;
}
void on_swap_buffers(HDC hdc)
{
if (!g_initialized)
{
if (!g_init_attempted)
init(hdc);
return;
}
if (GetAsyncKeyState(VK_INSERT) & 1)
g_show_menu = !g_show_menu;
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
if (g_show_menu)
{
ImGui::SetNextWindowSize({300.f, 100.f}, ImGuiCond_Once);
ImGui::SetNextWindowPos({10.f, 10.f}, ImGuiCond_Once);
ImGui::Begin("omath | OpenGL hook");
ImGui::Text("Hook active");
ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate);
ImGui::Text("INSERT toggles this window");
ImGui::End();
}
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
void hook_when_opengl_is_loaded()
{
while (!GetModuleHandle("opengl32.dll"))
std::this_thread::sleep_for(g_module_wait_delay);
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_opengl_swap_buffers(on_swap_buffers);
(void)mgr.hook_opengl();
}
} // namespace
BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(h_instance);
std::thread{hook_when_opengl_is_loaded}.detach();
}
else if (reason == DLL_PROCESS_DETACH)
{
auto& mgr = omath::hooks::HooksManager::get();
mgr.unhook_wnd_proc();
mgr.unhook_opengl();
if (g_initialized)
{
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
}
return true;
}
+37
View File
@@ -0,0 +1,37 @@
//
// Created by Vladislav on 07.05.2026.
//
#pragma once
#include "omath/linear_algebra/vector3.hpp"
#include <array>
#include <type_traits>
namespace omath::primitives
{
// Oriented bounding box: a rectangular cuboid defined by a center, three
// orthonormal local axes, and the half-size along each of those axes.
template<class Type>
requires std::is_floating_point_v<Type>
struct Obb final
{
Vector3<Type> center;
Vector3<Type> axis_x;
Vector3<Type> axis_y;
Vector3<Type> axis_z;
Vector3<Type> half_extents;
[[nodiscard]]
constexpr std::array<Vector3<Type>, 8> vertices() const noexcept
{
const auto ex = axis_x * half_extents.x;
const auto ey = axis_y * half_extents.y;
const auto ez = axis_z * half_extents.z;
return {
center - ex - ey - ez, center + ex - ey - ez, center - ex + ey - ez, center + ex + ey - ez,
center - ex - ey + ez, center + ex - ey + ez, center - ex + ey + ez, center + ex + ey + ez,
};
}
};
} // namespace omath::primitives
+1 -1
View File
@@ -49,7 +49,7 @@ namespace omath::collision
struct Params final struct Params final
{ {
int max_iterations{64}; int max_iterations{64};
FloatingType tolerance{1e-4}; // absolute tolerance on distance growth FloatingType tolerance{1e-4f}; // absolute tolerance on distance growth
}; };
// Precondition: simplex.size()==4 and contains the origin. // Precondition: simplex.size()==4 and contains the origin.
[[nodiscard]] [[nodiscard]]
+57
View File
@@ -4,6 +4,7 @@
#pragma once #pragma once
#include "omath/3d_primitives/aabb.hpp" #include "omath/3d_primitives/aabb.hpp"
#include "omath/3d_primitives/obb.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,6 +37,7 @@ namespace omath::collision
{ {
using TriangleType = Triangle<typename RayType::VectorType>; using TriangleType = Triangle<typename RayType::VectorType>;
using AABBType = primitives::Aabb<typename RayType::VectorType::ContainedType>; using AABBType = primitives::Aabb<typename RayType::VectorType::ContainedType>;
using OBBType = primitives::Obb<typename RayType::VectorType::ContainedType>;
public: public:
LineTracer() = delete; LineTracer() = delete;
@@ -137,6 +139,61 @@ namespace omath::collision
return ray.start + dir * t_hit; return ray.start + dir * t_hit;
} }
// Slab method ray-OBB intersection. Project the ray into the OBB's local frame
// (axes are orthonormal, so the inverse rotation is just a transpose / dot products),
// then run the standard slab test against the local box [-half_extents, +half_extents].
// The ray parameter t is invariant under rigid transform, so the hit point is recovered
// in world space as ray.start + dir * t_hit.
[[nodiscard]]
constexpr static auto get_ray_hit_point(const RayType& ray, const OBBType& obb) noexcept
{
using T = typename RayType::VectorType::ContainedType;
const auto offset = ray.start - obb.center;
const auto dir = ray.direction_vector();
const T local_start[3] = {offset.dot(obb.axis_x), offset.dot(obb.axis_y), offset.dot(obb.axis_z)};
const T local_dir[3] = {dir.dot(obb.axis_x), dir.dot(obb.axis_y), dir.dot(obb.axis_z)};
const T half[3] = {obb.half_extents.x, obb.half_extents.y, obb.half_extents.z};
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& h) -> bool
{
constexpr T k_epsilon = std::numeric_limits<T>::epsilon();
if (std::abs(d) < k_epsilon)
return origin >= -h && origin <= h;
const T inv = T(1) / d;
T t0 = (-h - origin) * inv;
T t1 = (h - 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(local_dir[0], local_start[0], half[0]))
return ray.end;
if (!process_axis(local_dir[1], local_start[1], half[1]))
return ray.end;
if (!process_axis(local_dir[2], local_start[2], half[2]))
return ray.end;
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
@@ -15,7 +15,7 @@ namespace omath::cry_engine
constexpr Vector3<float> k_abs_forward = {0, 1, 0}; constexpr Vector3<float> k_abs_forward = {0, 1, 0};
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; using Mat3X3 = Mat<3, 3, float, MatStoreType::ROW_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>; using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>; using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>; using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
+67 -7
View File
@@ -8,22 +8,82 @@
namespace omath::cry_engine namespace omath::cry_engine
{ {
[[nodiscard]] [[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept; inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.yaw)
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.roll)
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
[[nodiscard]] [[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far, inline constexpr Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept; {
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_origin(const Mat4X4& mat) noexcept
{
return mat_extract_origin(mat);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_scale(const Mat4X4& mat) noexcept
{
return mat_extract_scale(mat);
}
[[nodiscard]]
inline constexpr ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
{
const auto angles = mat_extract_rotation_zyx(mat);
return {
PitchAngle::from_degrees(angles.x),
YawAngle::from_degrees(angles.z),
RollAngle::from_degrees(angles.y),
};
}
[[nodiscard]]
inline constexpr Mat4X4
calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range = NDCDepthRange::ZERO_TO_ONE) noexcept
{
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
std::unreachable();
}
template<class FloatingType> template<class FloatingType>
requires std::is_floating_point_v<FloatingType> requires std::is_floating_point_v<FloatingType>
@@ -4,21 +4,36 @@
#pragma once #pragma once
#include "omath/engines/cry_engine/formulas.hpp" #include "omath/engines/cry_engine/formulas.hpp"
#include "omath/internal/constexpr_math.hpp"
#include "omath/projection/camera.hpp" #include "omath/projection/camera.hpp"
namespace omath::cry_engine namespace omath::cry_engine
{ {
class CameraTrait final class CameraTrait final
{ {
public: public:
[[nodiscard]] [[nodiscard]]
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept; constexpr static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(internal::asin(direction.z)),
YawAngle::from_radians(-internal::atan2(direction.x, direction.y)), RollAngle::from_radians(0.f)};
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; constexpr static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return cry_engine::calc_view_matrix(angles, cam_origin);
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, constexpr static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov,
float near, float far, NDCDepthRange ndc_depth_range) noexcept; const projection::ViewPort& view_port, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near_plane, far_plane,
ndc_depth_range);
}
}; };
} // namespace omath::cry_engine } // namespace omath::cry_engine
@@ -8,22 +8,86 @@
namespace omath::frostbite_engine namespace omath::frostbite_engine
{ {
[[nodiscard]] [[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept; inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
[[nodiscard]] [[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far, inline constexpr Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept; {
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
[[nodiscard]]
inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.roll)
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.yaw)
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_origin(const Mat4X4& mat) noexcept
{
return mat_extract_origin(mat);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_scale(const Mat4X4& mat) noexcept
{
return mat_extract_scale(mat);
}
[[nodiscard]]
inline constexpr ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
{
const auto angles = mat_extract_rotation_zyx(mat);
return {
PitchAngle::from_degrees(angles.x),
YawAngle::from_degrees(angles.y),
RollAngle::from_degrees(angles.z),
};
}
[[nodiscard]]
inline constexpr Mat4X4 calc_perspective_projection_matrix(
const float field_of_view, const float aspect_ratio, const float near_plane, const float far_plane,
const NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept
{
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
std::unreachable();
}
template<class FloatingType> template<class FloatingType>
requires std::is_floating_point_v<FloatingType> requires std::is_floating_point_v<FloatingType>
@@ -4,6 +4,7 @@
#pragma once #pragma once
#include "omath/engines/frostbite_engine/formulas.hpp" #include "omath/engines/frostbite_engine/formulas.hpp"
#include "omath/internal/constexpr_math.hpp"
#include "omath/projection/camera.hpp" #include "omath/projection/camera.hpp"
namespace omath::frostbite_engine namespace omath::frostbite_engine
@@ -12,13 +13,29 @@ namespace omath::frostbite_engine
{ {
public: public:
[[nodiscard]] [[nodiscard]]
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept; constexpr static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(-internal::asin(direction.y)),
YawAngle::from_radians(internal::atan2(direction.x, direction.z)), RollAngle::from_radians(0.f)};
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; constexpr static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return frostbite_engine::calc_view_matrix(angles, cam_origin);
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, constexpr static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov,
float near, float far, NDCDepthRange ndc_depth_range) noexcept; const projection::ViewPort& view_port, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near_plane, far_plane,
ndc_depth_range);
}
}; };
} // namespace omath::unreal_engine } // namespace omath::frostbite_engine
+69 -7
View File
@@ -8,22 +8,84 @@
namespace omath::iw_engine namespace omath::iw_engine
{ {
[[nodiscard]] [[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept; inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
[[nodiscard]] [[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_up);
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far, inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept; {
return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_origin(const Mat4X4& mat) noexcept
{
return mat_extract_origin(mat);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_scale(const Mat4X4& mat) noexcept
{
return mat_extract_scale(mat);
}
[[nodiscard]]
inline constexpr ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
{
const auto angles = mat_extract_rotation_zyx(mat);
return {
PitchAngle::from_degrees(angles.y),
YawAngle::from_degrees(angles.z),
RollAngle::from_degrees(angles.x),
};
}
[[nodiscard]]
inline constexpr Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view(forward_vector(angles), right_vector(angles), up_vector(angles), cam_origin);
}
[[nodiscard]]
inline constexpr Mat4X4 calc_perspective_projection_matrix(
const float field_of_view, const float aspect_ratio, const float near_plane, const float far_plane,
const NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept
{
constexpr float k_source_reference_aspect = 4.f / 3.f;
const auto vertical_fov = angles::horizontal_fov_to_vertical(field_of_view, k_source_reference_aspect);
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
vertical_fov, aspect_ratio, near_plane, far_plane);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
vertical_fov, aspect_ratio, near_plane, far_plane);
std::unreachable();
}
template<class FloatingType> template<class FloatingType>
requires std::is_floating_point_v<FloatingType> requires std::is_floating_point_v<FloatingType>
@@ -3,7 +3,8 @@
// //
#pragma once #pragma once
#include "omath/engines/iw_engine/constants.hpp" #include "omath/engines/iw_engine/formulas.hpp"
#include "omath/internal/constexpr_math.hpp"
#include "omath/projection/camera.hpp" #include "omath/projection/camera.hpp"
namespace omath::iw_engine namespace omath::iw_engine
@@ -12,13 +13,29 @@ namespace omath::iw_engine
{ {
public: public:
[[nodiscard]] [[nodiscard]]
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept; constexpr static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(-internal::asin(direction.z)),
YawAngle::from_radians(internal::atan2(direction.y, direction.x)), RollAngle::from_radians(0.f)};
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; constexpr static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return iw_engine::calc_view_matrix(angles, cam_origin);
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, constexpr static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov,
float near, float far, NDCDepthRange ndc_depth_range) noexcept; const projection::ViewPort& view_port, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near_plane, far_plane,
ndc_depth_range);
}
}; };
} // namespace omath::iw_engine } // namespace omath::iw_engine
@@ -7,22 +7,88 @@
namespace omath::opengl_engine namespace omath::opengl_engine
{ {
[[nodiscard]] [[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept; inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
[[nodiscard]] [[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
const auto vec =
rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_forward);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
[[nodiscard]] [[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
const auto vec =
rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_right);
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_up);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far, inline constexpr Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept; {
return mat_look_at_right_handed(cam_origin, cam_origin + forward_vector(angles), up_vector(angles));
}
[[nodiscard]]
inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::COLUMN_MAJOR>(angles.roll)
* mat_rotation_axis_y<float, MatStoreType::COLUMN_MAJOR>(angles.yaw)
* mat_rotation_axis_x<float, MatStoreType::COLUMN_MAJOR>(angles.pitch);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_origin(const Mat4X4& mat) noexcept
{
return mat_extract_origin(mat);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_scale(const Mat4X4& mat) noexcept
{
return mat_extract_scale(mat);
}
[[nodiscard]]
inline constexpr ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
{
const auto angles = mat_extract_rotation_zyx(mat);
return {
PitchAngle::from_degrees(angles.x),
YawAngle::from_degrees(angles.y),
RollAngle::from_degrees(angles.z),
};
}
[[nodiscard]]
inline constexpr Mat4X4 calc_perspective_projection_matrix(
const float field_of_view, const float aspect_ratio, const float near_plane, const float far_plane,
const NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept
{
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_right_handed_vertical_fov<float, MatStoreType::COLUMN_MAJOR,
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_right_handed_vertical_fov<float, MatStoreType::COLUMN_MAJOR,
NDCDepthRange::ZERO_TO_ONE>(field_of_view, aspect_ratio,
near_plane, far_plane);
std::unreachable();
}
template<class FloatingType> template<class FloatingType>
requires std::is_floating_point_v<FloatingType> requires std::is_floating_point_v<FloatingType>
@@ -3,7 +3,8 @@
// //
#pragma once #pragma once
#include "omath/engines/opengl_engine/constants.hpp" #include "omath/engines/opengl_engine/formulas.hpp"
#include "omath/internal/constexpr_math.hpp"
#include "omath/projection/camera.hpp" #include "omath/projection/camera.hpp"
namespace omath::opengl_engine namespace omath::opengl_engine
@@ -12,13 +13,29 @@ namespace omath::opengl_engine
{ {
public: public:
[[nodiscard]] [[nodiscard]]
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept; constexpr static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(internal::asin(direction.y)),
YawAngle::from_radians(-internal::atan2(direction.x, -direction.z)), RollAngle::from_radians(0.f)};
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; constexpr static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return opengl_engine::calc_view_matrix(angles, cam_origin);
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, constexpr static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov,
float near, float far, NDCDepthRange ndc_depth_range) noexcept; const projection::ViewPort& view_port, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near_plane, far_plane,
ndc_depth_range);
}
}; };
} // namespace omath::opengl_engine } // namespace omath::opengl_engine
@@ -0,0 +1,13 @@
//
// Created by Orange on 6/3/2026.
//
#pragma once
#include "omath/engines/rage_engine/constants.hpp"
#include "omath/projection/camera.hpp"
#include "traits/camera_trait.hpp"
namespace omath::rage_engine
{
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::ZERO_TO_ONE>;
} // namespace omath::rage_engine
@@ -0,0 +1,25 @@
//
// Created by Orange on 6/3/2026.
//
#pragma once
#include "omath/linear_algebra/mat.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <omath/trigonometry/angle.hpp>
#include <omath/trigonometry/view_angles.hpp>
namespace omath::rage_engine
{
constexpr Vector3<float> k_abs_up = {0, 0, 1};
constexpr Vector3<float> k_abs_right = {1, 0, 0};
constexpr Vector3<float> k_abs_forward = {0, 1, 0};
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat3X3 = Mat<3, 3, float, MatStoreType::ROW_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
} // namespace omath::rage_engine
@@ -0,0 +1,140 @@
//
// Created by Orange on 6/3/2026.
//
#pragma once
#include "omath/engines/rage_engine/constants.hpp"
#include <type_traits>
namespace omath::rage_engine
{
[[nodiscard]]
inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
[[nodiscard]]
inline constexpr Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]]
inline constexpr Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]]
inline constexpr Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]]
inline constexpr Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
[[nodiscard]]
inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.yaw)
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.roll)
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_origin(const Mat4X4& mat) noexcept
{
return mat_extract_origin(mat);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_scale(const Mat4X4& mat) noexcept
{
return mat_extract_scale(mat);
}
[[nodiscard]]
inline constexpr ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
{
const auto angles = mat_extract_rotation_zyx(mat);
return {
PitchAngle::from_degrees(angles.x),
YawAngle::from_degrees(angles.z),
RollAngle::from_degrees(angles.y),
};
}
[[nodiscard]]
inline constexpr Mat4X4
calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range = NDCDepthRange::ZERO_TO_ONE) noexcept
{
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
std::unreachable();
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::rage_engine
@@ -0,0 +1,13 @@
//
// Created by Orange on 6/3/2026.
//
#pragma once
#include "constants.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "traits/mesh_trait.hpp"
namespace omath::rage_engine
{
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
} // namespace omath::rage_engine
@@ -0,0 +1,41 @@
//
// Created by Orange on 6/3/2026.
//
#pragma once
#include "omath/engines/rage_engine/formulas.hpp"
#include "omath/internal/constexpr_math.hpp"
#include "omath/projection/camera.hpp"
namespace omath::rage_engine
{
class CameraTrait final
{
public:
[[nodiscard]]
constexpr static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(internal::asin(direction.z)),
YawAngle::from_radians(-internal::atan2(direction.x, direction.y)), RollAngle::from_radians(0.f)};
}
[[nodiscard]]
constexpr static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return rage_engine::calc_view_matrix(angles, cam_origin);
}
[[nodiscard]]
constexpr static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near_plane, far_plane,
ndc_depth_range);
}
};
} // namespace omath::rage_engine
@@ -0,0 +1,20 @@
//
// Created by Orange on 6/3/2026.
//
#pragma once
#include <omath/engines/rage_engine/constants.hpp>
#include <omath/engines/rage_engine/formulas.hpp>
namespace omath::rage_engine
{
class MeshTrait final
{
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
{
return rage_engine::rotation_matrix(rotation);
}
};
} // namespace omath::rage_engine
@@ -0,0 +1,76 @@
//
// Created by Orange on 6/3/2026.
//
#pragma once
#include "omath/engines/rage_engine/formulas.hpp"
#include "omath/projectile_prediction/projectile.hpp"
#include "omath/projectile_prediction/target.hpp"
#include <optional>
namespace omath::rage_engine
{
class PredEngineTrait final
{
public:
constexpr static Vector3<float>
predict_projectile_position(const projectile_prediction::Projectile<float>& projectile, const float pitch,
const float yaw, const float time, const float gravity) noexcept
{
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
auto current_pos = launch_pos
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;
current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
return current_pos;
}
[[nodiscard]]
static constexpr Vector3<float> predict_target_position(const projectile_prediction::Target<float>& target,
const float time, const float gravity) noexcept
{
auto predicted = target.m_origin + target.m_velocity * time;
if (target.m_is_airborne)
predicted.z -= gravity * (time * time) * 0.5f;
return predicted;
}
[[nodiscard]]
static float calc_vector_2d_distance(const Vector3<float>& delta) noexcept
{
return std::sqrt(delta.x * delta.x + delta.y * delta.y);
}
[[nodiscard]]
constexpr static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
{
return vec.z;
}
[[nodiscard]]
static Vector3<float> calc_viewpoint_from_angles(const projectile_prediction::Projectile<float>& projectile,
Vector3<float> predicted_target_position,
const std::optional<float> projectile_pitch) noexcept
{
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value()));
return {predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height};
}
[[nodiscard]]
static float calc_direct_pitch_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto direction = (view_to - origin).normalized();
return angles::radians_to_degrees(std::asin(direction.z));
}
[[nodiscard]]
static float calc_direct_yaw_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto direction = (view_to - origin).normalized();
return angles::radians_to_degrees(-std::atan2(direction.x, direction.y));
};
};
} // namespace omath::rage_engine
@@ -7,22 +7,84 @@
namespace omath::source_engine namespace omath::source_engine
{ {
[[nodiscard]] [[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept; inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
[[nodiscard]] [[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept; inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll);
}
[[nodiscard]] [[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> extract_origin(const Mat4X4& mat) noexcept
{
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; return mat_extract_origin(mat);
}
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far, inline constexpr Vector3<float> extract_scale(const Mat4X4& mat) noexcept
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept; {
return mat_extract_scale(mat);
}
[[nodiscard]]
inline constexpr ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
{
const auto angles = mat_extract_rotation_zyx(mat);
return {
PitchAngle::from_degrees(angles.y),
YawAngle::from_degrees(angles.z),
RollAngle::from_degrees(angles.x),
};
}
[[nodiscard]]
inline constexpr Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]]
inline constexpr Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]]
inline constexpr Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view(forward_vector(angles), right_vector(angles), up_vector(angles), cam_origin);
}
[[nodiscard]]
inline constexpr Mat4X4 calc_perspective_projection_matrix(
const float field_of_view, const float aspect_ratio, const float near_plane, const float far_plane,
const NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept
{
constexpr float k_source_reference_aspect = 4.f / 3.f;
const auto vertical_fov = angles::horizontal_fov_to_vertical(field_of_view, k_source_reference_aspect);
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
vertical_fov, aspect_ratio, near_plane, far_plane);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
vertical_fov, aspect_ratio, near_plane, far_plane);
std::unreachable();
}
template<class FloatingType> template<class FloatingType>
requires std::is_floating_point_v<FloatingType> requires std::is_floating_point_v<FloatingType>
@@ -3,7 +3,8 @@
// //
#pragma once #pragma once
#include "omath/engines/source_engine/constants.hpp" #include "omath/engines/source_engine/formulas.hpp"
#include "omath/internal/constexpr_math.hpp"
#include "omath/projection/camera.hpp" #include "omath/projection/camera.hpp"
namespace omath::source_engine namespace omath::source_engine
@@ -12,13 +13,29 @@ namespace omath::source_engine
{ {
public: public:
[[nodiscard]] [[nodiscard]]
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept; constexpr static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(-internal::asin(direction.z)),
YawAngle::from_radians(internal::atan2(direction.y, direction.x)), RollAngle::from_radians(0.f)};
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; constexpr static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return source_engine::calc_view_matrix(angles, cam_origin);
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, constexpr static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov,
float near, float far, NDCDepthRange ndc_depth_range) noexcept; const projection::ViewPort& view_port, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near_plane, far_plane,
ndc_depth_range);
}
}; };
} // namespace omath::source_engine } // namespace omath::source_engine
@@ -8,22 +8,85 @@
namespace omath::unity_engine namespace omath::unity_engine
{ {
[[nodiscard]] [[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept; inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
[[nodiscard]] [[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; inline constexpr Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far, inline constexpr Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept; {
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(-forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
[[nodiscard]]
inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.roll)
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.yaw)
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_origin(const Mat4X4& mat) noexcept
{
return mat_extract_origin(mat);
}
[[nodiscard]]
inline constexpr Vector3<float> extract_scale(const Mat4X4& mat) noexcept
{
return mat_extract_scale(mat);
}
[[nodiscard]]
inline constexpr ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
{
const auto angles = mat_extract_rotation_zyx(mat);
return {
PitchAngle::from_degrees(angles.x),
YawAngle::from_degrees(angles.y),
RollAngle::from_degrees(angles.z),
};
}
[[nodiscard]]
inline constexpr Mat4X4 calc_perspective_projection_matrix(
const float field_of_view, const float aspect_ratio, const float near_plane, const float far_plane,
const NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept
{
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return omath::mat_perspective_right_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return omath::mat_perspective_right_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
field_of_view, aspect_ratio, near_plane, far_plane);
std::unreachable();
}
template<class FloatingType> template<class FloatingType>
requires std::is_floating_point_v<FloatingType> requires std::is_floating_point_v<FloatingType>
@@ -4,6 +4,7 @@
#pragma once #pragma once
#include "omath/engines/unity_engine/formulas.hpp" #include "omath/engines/unity_engine/formulas.hpp"
#include "omath/internal/constexpr_math.hpp"
#include "omath/projection/camera.hpp" #include "omath/projection/camera.hpp"
namespace omath::unity_engine namespace omath::unity_engine
@@ -12,13 +13,29 @@ namespace omath::unity_engine
{ {
public: public:
[[nodiscard]] [[nodiscard]]
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept; constexpr static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(-internal::asin(direction.y)),
YawAngle::from_radians(internal::atan2(direction.x, direction.z)), RollAngle::from_radians(0.f)};
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept; constexpr static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return unity_engine::calc_view_matrix(angles, cam_origin);
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, constexpr static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov,
float near, float far, NDCDepthRange ndc_depth_range) noexcept; const projection::ViewPort& view_port, const float near_plane,
const float far_plane,
const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near_plane, far_plane,
ndc_depth_range);
}
}; };
} // namespace omath::unity_engine } // namespace omath::unity_engine
@@ -8,22 +8,85 @@
namespace omath::unreal_engine namespace omath::unreal_engine
{ {
[[nodiscard]] [[nodiscard]]
Vector3<double> forward_vector(const ViewAngles& angles) noexcept; inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
[[nodiscard]] [[nodiscard]]
Vector3<double> right_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<double> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Vector3<double> up_vector(const ViewAngles& angles) noexcept; inline constexpr Vector3<double> right_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept; return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; inline constexpr Vector3<double> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
[[nodiscard]] [[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(double field_of_view, double aspect_ratio, double near, double far, inline constexpr Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept; {
return mat_camera_view<double, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
[[nodiscard]]
inline constexpr Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
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);
}
[[nodiscard]]
inline constexpr Vector3<double> extract_origin(const Mat4X4& mat) noexcept
{
return mat_extract_origin(mat);
}
[[nodiscard]]
inline constexpr Vector3<double> extract_scale(const Mat4X4& mat) noexcept
{
return mat_extract_scale(mat);
}
[[nodiscard]]
inline constexpr ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
{
const auto angles = mat_extract_rotation_zyx(mat);
return {
PitchAngle::from_degrees(-angles.y),
YawAngle::from_degrees(angles.z),
RollAngle::from_degrees(-angles.x),
};
}
[[nodiscard]]
inline constexpr Mat4X4 calc_perspective_projection_matrix(
const double field_of_view, const double aspect_ratio, const double near_plane, const double far_plane,
const NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept
{
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_plane, far_plane);
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_plane, far_plane);
std::unreachable();
}
template<class FloatingType> template<class FloatingType>
requires std::is_floating_point_v<FloatingType> requires std::is_floating_point_v<FloatingType>
@@ -4,6 +4,7 @@
#pragma once #pragma once
#include "omath/engines/unreal_engine/formulas.hpp" #include "omath/engines/unreal_engine/formulas.hpp"
#include "omath/internal/constexpr_math.hpp"
#include "omath/projection/camera.hpp" #include "omath/projection/camera.hpp"
namespace omath::unreal_engine namespace omath::unreal_engine
@@ -12,13 +13,29 @@ namespace omath::unreal_engine
{ {
public: public:
[[nodiscard]] [[nodiscard]]
static ViewAngles calc_look_at_angle(const Vector3<double>& cam_origin, const Vector3<double>& look_at) noexcept; constexpr static ViewAngles calc_look_at_angle(const Vector3<double>& cam_origin,
const Vector3<double>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(internal::asin(direction.z)),
YawAngle::from_radians(internal::atan2(direction.y, direction.x)), RollAngle::from_radians(0.f)};
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept; constexpr static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept
{
return unreal_engine::calc_view_matrix(angles, cam_origin);
}
[[nodiscard]] [[nodiscard]]
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, constexpr static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov,
double near, double far, NDCDepthRange ndc_depth_range) noexcept; const projection::ViewPort& view_port, const double near_plane,
const double far_plane,
const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near_plane, far_plane,
ndc_depth_range);
}
}; };
} // namespace omath::unreal_engine } // namespace omath::unreal_engine
+214
View File
@@ -0,0 +1,214 @@
#pragma once
#ifdef OMATH_ENABLE_HOOKING
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <shared_mutex>
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#include <d3d12.h>
#include <d3d9.h>
#include <dxgi.h>
#endif // _WIN32
#ifdef __linux__
#include <GL/glx.h>
#endif // __linux__
#include <safetyhook.hpp>
namespace omath::hooks
{
class HooksManager final
{
HooksManager() = default;
public:
#ifdef _WIN32
// IDXGISwapChain callbacks — shared between DX11 and DX12 (same interface, same signature).
using present_callback = std::function<void(IDXGISwapChain*, UINT, UINT)>;
using resize_buffers_callback = std::function<void(IDXGISwapChain*, UINT, UINT, UINT, DXGI_FORMAT, UINT)>;
using execute_command_lists_callback =
std::function<void(ID3D12CommandQueue*, UINT, ID3D12CommandList* const*)>;
// IDirect3DDevice9 callbacks — DX9 only.
using dx9_present_callback =
std::function<void(IDirect3DDevice9*, const RECT*, const RECT*, HWND, const RGNDATA*)>;
using dx9_reset_callback = std::function<void(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*)>;
using dx9_end_scene_callback = std::function<void(IDirect3DDevice9*)>;
// OpenGL callback — Windows. Fires before the hooked buffer swap function calls the original.
using opengl_swap_buffers_callback = std::function<void(HDC)>;
// Return nullopt to pass the message to the original WndProc; return a value to intercept it.
using wnd_proc_callback = std::function<std::optional<LRESULT>(HWND, UINT, WPARAM, LPARAM)>;
#endif // _WIN32
#ifdef __linux__
// OpenGL/GLX callback — Linux. Fires before glXSwapBuffers calls the original.
using opengl_swap_buffers_callback = std::function<void(Display*, GLXDrawable)>;
#endif // __linux__
template<typename Callback>
using callback_ptr = std::shared_ptr<const Callback>;
[[nodiscard]] static HooksManager& get();
HooksManager(const HooksManager&) = delete;
HooksManager& operator=(const HooksManager&) = delete;
~HooksManager();
#ifdef _WIN32
[[nodiscard]] bool hook_dx9();
void unhook_dx9();
void set_on_dx9_present(dx9_present_callback callback);
void set_on_dx9_reset(dx9_reset_callback callback);
void set_on_dx9_end_scene(dx9_end_scene_callback callback);
[[nodiscard]] bool hook_dx11();
void unhook_dx11();
[[nodiscard]] bool hook_dx12();
void unhook_dx12();
#endif // _WIN32
[[nodiscard]] bool hook_opengl();
void unhook_opengl();
void set_on_opengl_swap_buffers(opengl_swap_buffers_callback callback);
#ifdef _WIN32
// Present and ResizeBuffers callbacks fire for whichever of DX11/DX12 is hooked.
void set_on_present(present_callback callback);
void set_on_resize_buffers(resize_buffers_callback callback);
void set_on_execute_command_lists(execute_command_lists_callback callback);
[[nodiscard]] bool hook_wnd_proc(HWND hwnd);
void unhook_wnd_proc();
void set_on_wnd_proc(wnd_proc_callback callback);
#endif // _WIN32
private:
#ifdef _WIN32
[[nodiscard]]
static HRESULT __stdcall dx9_present_detour(IDirect3DDevice9* p_device, const RECT* p_source_rect,
const RECT* p_dest_rect, HWND h_dest_window_override,
const RGNDATA* p_dirty_region);
[[nodiscard]]
static HRESULT __stdcall dx9_reset_detour(IDirect3DDevice9* p_device,
D3DPRESENT_PARAMETERS* p_presentation_parameters);
[[nodiscard]]
static HRESULT __stdcall dx9_end_scene_detour(IDirect3DDevice9* p_device);
[[nodiscard]]
static HRESULT __stdcall dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags);
[[nodiscard]]
static HRESULT __stdcall dx11_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, UINT width,
UINT height, DXGI_FORMAT new_format, UINT swap_chain_flags);
[[nodiscard]]
static HRESULT __stdcall dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags);
[[nodiscard]]
static HRESULT __stdcall dx12_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, UINT width,
UINT height, DXGI_FORMAT new_format, UINT swap_chain_flags);
static void __stdcall dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue,
UINT num_command_lists,
ID3D12CommandList* const* pp_command_lists);
[[nodiscard]]
static BOOL __stdcall opengl_wgl_swap_buffers_detour(HDC hdc);
[[nodiscard]]
static BOOL __stdcall opengl_swap_buffers_detour(HDC hdc);
[[nodiscard]]
static LRESULT __stdcall wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param);
#endif // _WIN32
#ifdef __linux__
static void opengl_glx_swap_buffers_detour(Display* display, GLXDrawable drawable);
#endif // __linux__
mutable std::shared_mutex m_hook_state_mutex;
#ifdef _WIN32
mutable std::shared_mutex m_dx9_present_mutex;
mutable std::shared_mutex m_dx9_reset_mutex;
mutable std::shared_mutex m_dx9_end_scene_mutex;
mutable std::shared_mutex m_present_mutex;
mutable std::shared_mutex m_resize_buffers_mutex;
mutable std::shared_mutex m_execute_command_lists_mutex;
mutable std::shared_mutex m_wnd_proc_mutex;
#endif // _WIN32
mutable std::shared_mutex m_opengl_swap_buffers_mutex;
#ifdef _WIN32
bool m_is_dx9_hooked = false;
bool m_is_dx11_hooked = false;
bool m_is_dx12_hooked = false;
bool m_is_wnd_proc_hooked = false;
HWND m_hooked_hwnd = nullptr;
WNDPROC m_original_wndproc = nullptr;
safetyhook::InlineHook m_dx9_present_hook;
safetyhook::InlineHook m_dx9_reset_hook;
safetyhook::InlineHook m_dx9_end_scene_hook;
safetyhook::InlineHook m_dx11_present_hook;
safetyhook::InlineHook m_dx11_resize_buffers_hook;
safetyhook::InlineHook m_dx12_present_hook;
safetyhook::InlineHook m_dx12_resize_buffers_hook;
safetyhook::InlineHook m_dx12_execute_command_lists_hook;
safetyhook::InlineHook m_opengl_wgl_swap_buffers_hook;
safetyhook::InlineHook m_opengl_swap_buffers_hook;
#endif // _WIN32
#ifdef __linux__
safetyhook::InlineHook m_opengl_glx_swap_buffers_hook;
#endif // __linux__
bool m_is_opengl_hooked = false;
#ifdef _WIN32
callback_ptr<dx9_present_callback> m_dx9_present_cb;
callback_ptr<dx9_reset_callback> m_dx9_reset_cb;
callback_ptr<dx9_end_scene_callback> m_dx9_end_scene_cb;
callback_ptr<present_callback> m_present_cb;
callback_ptr<resize_buffers_callback> m_resize_buffers_cb;
callback_ptr<execute_command_lists_callback> m_execute_command_lists_cb;
callback_ptr<wnd_proc_callback> m_wnd_proc_cb;
#endif // _WIN32
callback_ptr<opengl_swap_buffers_callback> m_opengl_swap_buffers_cb;
};
} // namespace omath::hooks
#else // !OMATH_ENABLE_HOOKING
namespace omath::hooks
{
class HooksManager final
{
HooksManager() = default;
public:
[[nodiscard]] static HooksManager& get();
HooksManager(const HooksManager&) = delete;
~HooksManager();
};
} // namespace omath::hooks
#endif
+15 -14
View File
@@ -15,14 +15,15 @@ namespace omath::hud
class EntityOverlay final class EntityOverlay final
{ {
public: public:
EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom, EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom, float aspect,
const std::shared_ptr<HudRendererInterface>& renderer); const std::shared_ptr<HudRendererInterface>& renderer);
// ── Boxes ──────────────────────────────────────────────────────── // ── Boxes ────────────────────────────────────────────────────────
EntityOverlay& add_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f}, EntityOverlay& add_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f},
float thickness = 1.f); const Color& outline_color = Color{0.f, 0.f, 0.f, 0.f}, float thickness = 1.f);
EntityOverlay& add_cornered_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f}, EntityOverlay& add_cornered_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f},
const Color& outline_color = Color{0.f, 0.f, 0.f, 0.f},
float corner_ratio_len = 0.2f, float thickness = 1.f); float corner_ratio_len = 0.2f, float thickness = 1.f);
EntityOverlay& add_dashed_box(const Color& color, float dash_len = 8.f, float gap_len = 5.f, EntityOverlay& add_dashed_box(const Color& color, float dash_len = 8.f, float gap_len = 5.f,
@@ -56,22 +57,22 @@ namespace omath::hud
float offset = 5.f); float offset = 5.f);
// ── Labels ─────────────────────────────────────────────────────── // ── Labels ───────────────────────────────────────────────────────
EntityOverlay& add_right_label(const Color& color, float offset, bool outlined, const std::string_view& text); EntityOverlay& add_right_label(const Color& color, float offset, widget::Outlined outlined, const std::string_view& text);
EntityOverlay& add_left_label(const Color& color, float offset, bool outlined, const std::string_view& text); EntityOverlay& add_left_label(const Color& color, float offset, widget::Outlined outlined, const std::string_view& text);
EntityOverlay& add_top_label(const Color& color, float offset, bool outlined, std::string_view text); EntityOverlay& add_top_label(const Color& color, float offset, widget::Outlined outlined, std::string_view text);
EntityOverlay& add_bottom_label(const Color& color, float offset, bool outlined, std::string_view text); EntityOverlay& add_bottom_label(const Color& color, float offset, widget::Outlined outlined, std::string_view text);
EntityOverlay& add_centered_top_label(const Color& color, float offset, bool outlined, EntityOverlay& add_centered_top_label(const Color& color, float offset, widget::Outlined outlined,
const std::string_view& text); const std::string_view& text);
EntityOverlay& add_centered_bottom_label(const Color& color, float offset, bool outlined, EntityOverlay& add_centered_bottom_label(const Color& color, float offset, widget::Outlined outlined,
const std::string_view& text); const std::string_view& text);
template<typename... Args> template<typename... Args>
EntityOverlay& add_right_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt, EntityOverlay& add_right_label(const Color& color, const float offset, const widget::Outlined 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 +80,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_left_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt, EntityOverlay& add_left_label(const Color& color, const float offset, const widget::Outlined 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 +88,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_top_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt, EntityOverlay& add_top_label(const Color& color, const float offset, const widget::Outlined 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 +96,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_bottom_label(const Color& color, const float offset, const bool outlined, EntityOverlay& add_bottom_label(const Color& color, const float offset, const widget::Outlined 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 +104,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_centered_top_label(const Color& color, const float offset, const bool outlined, EntityOverlay& add_centered_top_label(const Color& color, const float offset, const widget::Outlined 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,7 +112,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_centered_bottom_label(const Color& color, const float offset, const bool outlined, EntityOverlay& add_centered_bottom_label(const Color& color, const float offset, const widget::Outlined 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,
+8 -1
View File
@@ -26,13 +26,20 @@ namespace omath::hud::widget
{ {
Color color; Color color;
Color fill{0.f, 0.f, 0.f, 0.f}; Color fill{0.f, 0.f, 0.f, 0.f};
Color outline{0.f, 0.f, 0.f, 0.f};
float thickness = 1.f; float thickness = 1.f;
}; };
enum class Outlined
{
Off,
On,
};
struct CorneredBox struct CorneredBox
{ {
Color color; Color color;
Color fill{0.f, 0.f, 0.f, 0.f}; Color fill{0.f, 0.f, 0.f, 0.f};
Color outline{0.f, 0.f, 0.f, 0.f};
float corner_ratio = 0.2f; float corner_ratio = 0.2f;
float thickness = 1.f; float thickness = 1.f;
}; };
@@ -116,7 +123,7 @@ namespace omath::hud::widget
{ {
Color color; Color color;
float offset; float offset;
bool outlined; Outlined outlined;
std::string_view text; std::string_view text;
}; };
@@ -27,7 +27,7 @@ namespace omath::hud
const Color& tint = Color{1.f, 1.f, 1.f, 1.f}) override; 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; Vector2<float> calc_text_size(const std::string_view& text) override;
}; };
} // namespace omath::hud } // namespace omath::hud
#endif // OMATH_IMGUI_INTEGRATION #endif // OMATH_IMGUI_INTEGRATION
+707
View File
@@ -0,0 +1,707 @@
//
// Created by orange on 6/11/2026.
//
#pragma once
#include <bit>
#include <cmath>
#include <cstdint>
#include <limits>
#include <numbers>
#include <type_traits>
namespace omath::internal
{
// Embedded subset of GCE-Math 1.18.0.
//
// Original project:
// GCE-Math: A C++ generalized constant expression-based math library
// Copyright 2016-2024 Keith O'Hara
// Licensed under the Apache License, Version 2.0.
namespace math_detail
{
using uint_t = unsigned int;
using llint_t = long long int;
using ullint_t = unsigned long long int;
template<class Type>
using limits = std::numeric_limits<Type>;
template<class Type>
constexpr Type pi = std::numbers::pi_v<Type>;
template<class Type>
constexpr Type half_pi = std::numbers::pi_v<Type> / static_cast<Type>(2);
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type abs(const Type value) noexcept
{
return value == Type{0} ? Type{0} : value < Type{0} ? -value : value;
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr int sgn(const Type value) noexcept
{
return value > Type{0} ? 1 : value < Type{0} ? -1 : 0;
}
[[nodiscard]]
constexpr bool is_odd(const llint_t value) noexcept
{
return (value & 1U) != 0;
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool is_nan(const Type value) noexcept
{
return value != value;
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool is_neginf(const Type value) noexcept
{
return value == -limits<Type>::infinity();
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool is_posinf(const Type value) noexcept
{
return value == limits<Type>::infinity();
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool is_inf(const Type value) noexcept
{
return is_neginf(value) || is_posinf(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool is_finite(const Type value) noexcept
{
return !is_nan(value) && !is_inf(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool any_nan(const Type x, const Type y) noexcept
{
return is_nan(x) || is_nan(y);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool any_inf(const Type x, const Type y) noexcept
{
return is_inf(x) || is_inf(y);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool all_finite(const Type x, const Type y) noexcept
{
return is_finite(x) && is_finite(y);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool signbit(const Type value) noexcept
{
if constexpr (std::is_same_v<Type, float>)
return (std::bit_cast<std::uint32_t>(value) & 0x80000000U) != 0;
else if constexpr (std::is_same_v<Type, double>)
return (std::bit_cast<std::uint64_t>(value) & 0x8000000000000000ULL) != 0;
else
return value < Type{0};
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr bool neg_zero(const Type value) noexcept
{
return value == Type{0} && signbit(value);
}
template<class Type, class ExpType>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type pow_integral_compute(const Type base, const ExpType exp_term) noexcept;
template<class Type, class ExpType>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type pow_integral_compute_recur(const Type base, const Type value, const ExpType exp_term) noexcept
{
return exp_term > ExpType{1}
? is_odd(static_cast<llint_t>(exp_term))
? pow_integral_compute_recur(base * base, value * base, exp_term / ExpType{2})
: pow_integral_compute_recur(base * base, value, exp_term / ExpType{2})
: exp_term == ExpType{1} ? value * base
: value;
}
template<class Type, class ExpType>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type pow_integral_sgn_check(const Type base, const ExpType exp_term) noexcept
{
if constexpr (std::is_signed_v<ExpType>)
return exp_term < ExpType{0} ? Type{1} / pow_integral_compute(base, -exp_term)
: pow_integral_compute_recur(base, Type{1}, exp_term);
else
return pow_integral_compute_recur(base, Type{1}, exp_term);
}
template<class Type, class ExpType>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type pow_integral_compute(const Type base, const ExpType exp_term) noexcept
{
return exp_term == ExpType{3} ? base * base * base
: exp_term == ExpType{2} ? base * base
: exp_term == ExpType{1} ? base
: exp_term == ExpType{0} ? Type{1}
: exp_term == limits<ExpType>::min() ? Type{0}
: exp_term == limits<ExpType>::max() ? limits<Type>::infinity()
: pow_integral_sgn_check(base, exp_term);
}
template<class Type, class ExpType>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type pow_integral(const Type base, const ExpType exp_term) noexcept
{
return pow_integral_compute(base, exp_term);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type sqrt_recur(const Type x, const Type xn, const int count) noexcept
{
return abs(xn - x / xn) / (Type{1} + xn) < limits<Type>::min() ? xn
: count < 100 ? sqrt_recur(x, Type{0.5} * (xn + x / xn), count + 1)
: xn;
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type sqrt_simplify(const Type x, const Type m_val) noexcept
{
return x > Type{1e+08} ? sqrt_simplify(x / Type{1e+08}, Type{1e+04} * m_val)
: x > Type{1e+06} ? sqrt_simplify(x / Type{1e+06}, Type{1e+03} * m_val)
: x > Type{1e+04} ? sqrt_simplify(x / Type{1e+04}, Type{1e+02} * m_val)
: x > Type{100} ? sqrt_simplify(x / Type{100}, Type{10} * m_val)
: x > Type{4} ? sqrt_simplify(x / Type{4}, Type{2} * m_val)
: m_val * sqrt_recur(x, x / Type{2}, 0);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type sqrt(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN()
: x < Type{0} ? limits<Type>::quiet_NaN()
: is_posinf(x) ? x
: limits<Type>::min() > abs(x) ? Type{0}
: limits<Type>::min() > abs(Type{1} - x) ? x
: sqrt_simplify(x, Type{1});
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type floor_int(const Type x, const Type x_whole) noexcept
{
return x_whole - static_cast<Type>((x < Type{0}) && (x < x_whole));
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type floor_check_internal(const Type x) noexcept
{
if constexpr (std::is_same_v<Type, float>)
return abs(x) >= 8388608.f ? x : floor_int(x, static_cast<float>(static_cast<int>(x)));
else if constexpr (std::is_same_v<Type, double>)
return abs(x) >= 4503599627370496. ? x : floor_int(x, static_cast<double>(static_cast<llint_t>(x)));
else
return abs(x) >= 9223372036854775808.l
? x
: static_cast<long double>(static_cast<ullint_t>(abs(x))) * sgn(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type floor(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN()
: !is_finite(x) ? x
: limits<Type>::min() > abs(x) ? x
: floor_check_internal(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type trunc_int(const Type x) noexcept
{
return static_cast<Type>(static_cast<llint_t>(x));
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type trunc_check_internal(const Type x) noexcept
{
if constexpr (std::is_same_v<Type, float>)
return abs(x) >= 8388608.f ? x : trunc_int(x);
else if constexpr (std::is_same_v<Type, double>)
return abs(x) >= 4503599627370496. ? x : trunc_int(x);
else
return abs(x) >= 9223372036854775808.l
? x
: static_cast<long double>(static_cast<ullint_t>(abs(x))) * sgn(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type trunc(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN()
: !is_finite(x) ? x
: limits<Type>::min() > abs(x) ? x
: trunc_check_internal(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type fmod(const Type x, const Type y) noexcept
{
return any_nan(x, y) || !all_finite(x, y) || limits<Type>::min() > abs(y) ? limits<Type>::quiet_NaN()
: x - trunc(x / y) * y;
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type tan_series_exp_long(const Type z) noexcept
{
return -Type{1} / z
+ (z / Type{3}
+ (pow_integral(z, 3) / Type{45}
+ (Type{2} * pow_integral(z, 5) / Type{945} + pow_integral(z, 7) / Type{4725})));
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type tan_series_exp(const Type x) noexcept
{
return limits<Type>::min() > abs(x - half_pi<Type>) ? Type{1.633124e+16}
: tan_series_exp_long(x - half_pi<Type>);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type tan_cf_recur(const Type xx, const int max_depth) noexcept
{
Type result = static_cast<Type>(2 * max_depth - 1);
for (int depth = max_depth - 1; depth >= 1; --depth)
result = static_cast<Type>(2 * depth - 1) - xx / result;
return result;
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type tan_cf_main(const Type x) noexcept
{
return x > Type{1.55} && x < Type{1.60} ? tan_series_exp(x)
: x > Type{1.4} ? x / tan_cf_recur(x * x, 45)
: x > Type{1} ? x / tan_cf_recur(x * x, 35)
: x / tan_cf_recur(x * x, 25);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type tan_begin(const Type x, const int count = 0) noexcept
{
return x > pi<Type> ? count > 1 ? limits<Type>::quiet_NaN()
: tan_begin(x - pi<Type> * floor(x / pi<Type>), count + 1)
: tan_cf_main(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type tan(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN()
: limits<Type>::min() > abs(x) ? Type{0}
: x < Type{0} ? -tan_begin(-x)
: tan_begin(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type sin(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN()
: limits<Type>::min() > abs(x) ? Type{0}
: limits<Type>::min() > abs(x - half_pi<Type>) ? Type{1}
: limits<Type>::min() > abs(x + half_pi<Type>) ? -Type{1}
: limits<Type>::min() > abs(x - pi<Type>) ? Type{0}
: limits<Type>::min() > abs(x + pi<Type>)
? -Type{0}
: (Type{2} * tan(x / Type{2})) / (Type{1} + tan(x / Type{2}) * tan(x / Type{2}));
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type cos(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN()
: limits<Type>::min() > abs(x) ? Type{1}
: limits<Type>::min() > abs(x - half_pi<Type>) ? Type{0}
: limits<Type>::min() > abs(x + half_pi<Type>) ? Type{0}
: limits<Type>::min() > abs(x - pi<Type>) ? -Type{1}
: limits<Type>::min() > abs(x + pi<Type>)
? -Type{1}
: (Type{1} - tan(x / Type{2}) * tan(x / Type{2}))
/ (Type{1} + tan(x / Type{2}) * tan(x / Type{2}));
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan_series_order_calc(const Type xx, const Type x_pow, const uint_t order) noexcept
{
return Type{1} / (static_cast<Type>((order - 1) * 4 - 1) * x_pow)
- Type{1} / (static_cast<Type>((order - 1) * 4 + 1) * x_pow * xx);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan_series_order(const Type x, const uint_t order_begin, const uint_t max_order) noexcept
{
if (max_order == 1)
return half_pi<Type> - Type{1} / x;
const Type xx = x * x;
Type result = atan_series_order_calc(xx, pow_integral(x, 4 * max_order - 5), max_order);
auto depth = max_order - 1;
while (depth > order_begin)
{
result += atan_series_order_calc(xx, pow_integral(x, 4 * depth - 5), depth);
--depth;
}
return result + half_pi<Type> - Type{1} / x;
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan_series_main(const Type x) noexcept
{
return x < Type{3} ? atan_series_order(x, 1U, 10U)
: x < Type{4} ? atan_series_order(x, 1U, 9U)
: x < Type{5} ? atan_series_order(x, 1U, 8U)
: x < Type{7} ? atan_series_order(x, 1U, 7U)
: x < Type{11} ? atan_series_order(x, 1U, 6U)
: x < Type{25} ? atan_series_order(x, 1U, 5U)
: x < Type{100} ? atan_series_order(x, 1U, 4U)
: x < Type{1000} ? atan_series_order(x, 1U, 3U)
: atan_series_order(x, 1U, 2U);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan_cf_recur(const Type xx, const uint_t depth_begin, const uint_t max_depth) noexcept
{
auto depth = max_depth - 1;
Type result = static_cast<Type>(2 * (depth + 1) - 1);
while (depth > depth_begin - 1)
{
result = static_cast<Type>(2 * depth - 1) + static_cast<Type>(depth * depth) * xx / result;
--depth;
}
return result;
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan_cf_main(const Type x) noexcept
{
return x < Type{0.5} ? x / atan_cf_recur(x * x, 1U, 15U)
: x < Type{1} ? x / atan_cf_recur(x * x, 1U, 25U)
: x < Type{1.5} ? x / atan_cf_recur(x * x, 1U, 35U)
: x < Type{2} ? x / atan_cf_recur(x * x, 1U, 45U)
: x / atan_cf_recur(x * x, 1U, 52U);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan_begin(const Type x) noexcept
{
return x > Type{2.5} ? atan_series_main(x) : atan_cf_main(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN()
: limits<Type>::min() > abs(x) ? Type{0}
: x < Type{0} ? -atan_begin(-x)
: atan_begin(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan2(const Type y, const Type x) noexcept
{
return any_nan(y, x) ? limits<Type>::quiet_NaN()
: limits<Type>::min() > abs(x) ? limits<Type>::min() > abs(y)
? neg_zero(y) ? neg_zero(x) ? -pi<Type> : -Type{0}
: neg_zero(x) ? pi<Type>
: Type{0}
: y > Type{0} ? half_pi<Type>
: -half_pi<Type>
: x < Type{0} ? y < Type{0} ? atan(y / x) - pi<Type> : atan(y / x) + pi<Type>
: atan(y / x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type asin_compute(const Type x) noexcept
{
return x > Type{1} ? limits<Type>::quiet_NaN()
: limits<Type>::min() > abs(x - Type{1}) ? half_pi<Type>
: limits<Type>::min() > abs(x) ? Type{0}
: atan(x / sqrt(Type{1} - x * x));
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type asin(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN() : x < Type{0} ? -asin_compute(-x) : asin_compute(x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type acos_compute(const Type x) noexcept
{
return abs(x) > Type{1} ? limits<Type>::quiet_NaN()
: limits<Type>::min() > abs(x - Type{1}) ? Type{0}
: limits<Type>::min() > abs(x) ? half_pi<Type>
: atan(sqrt(Type{1} - x * x) / x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type acos(const Type x) noexcept
{
return is_nan(x) ? limits<Type>::quiet_NaN() : x > Type{0} ? acos_compute(x) : pi<Type> - acos_compute(-x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type hypot(const Type x, const Type y) noexcept
{
return any_nan(x, y) ? limits<Type>::quiet_NaN()
: any_inf(x, y) ? limits<Type>::infinity()
: limits<Type>::min() > abs(x) ? abs(y)
: limits<Type>::min() > abs(y) ? abs(x)
: abs(x) * sqrt(Type{1} + (y / x) * (y / x));
}
} // namespace math_detail
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type sin(const Type& value) noexcept
{
if consteval
{
return math_detail::sin(value);
}
return std::sin(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type cos(const Type& value) noexcept
{
if consteval
{
return math_detail::cos(value);
}
return std::cos(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type tan(const Type& value) noexcept
{
if consteval
{
return math_detail::tan(value);
}
return std::tan(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan(const Type& value) noexcept
{
if consteval
{
return math_detail::atan(value);
}
return std::atan(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type atan2(const Type& y, const Type& x) noexcept
{
if consteval
{
return math_detail::atan2(y, x);
}
return std::atan2(y, x);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type asin(const Type& value) noexcept
{
if consteval
{
return math_detail::asin(value);
}
return std::asin(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type acos(const Type& value) noexcept
{
if consteval
{
return math_detail::acos(value);
}
return std::acos(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type sqrt(const Type& value) noexcept
{
if consteval
{
return math_detail::sqrt(value);
}
return std::sqrt(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type hypot(const Type& x, const Type& y) noexcept
{
if consteval
{
return math_detail::hypot(x, y);
}
return std::hypot(x, y);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type hypot(const Type& x, const Type& y, const Type& z) noexcept
{
if consteval
{
return math_detail::sqrt(x * x + y * y + z * z);
}
return std::hypot(x, y, z);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type abs(const Type& value) noexcept
{
if consteval
{
return math_detail::abs(value);
}
return std::abs(value);
}
template<class Type>
requires std::is_floating_point_v<Type>
[[nodiscard]]
constexpr Type fmod(const Type& dividend, const Type& divisor) noexcept
{
if consteval
{
return math_detail::fmod(dividend, divisor);
}
return std::fmod(dividend, divisor);
}
} // namespace omath::internal
+177 -135
View File
@@ -2,13 +2,16 @@
// Created by vlad on 9/29/2024. // Created by vlad on 9/29/2024.
// //
#pragma once #pragma once
#include "omath/internal/constexpr_math.hpp"
#include "vector3.hpp" #include "vector3.hpp"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cmath>
#include <iomanip> #include <iomanip>
#include <numeric> #include <numeric>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <type_traits>
#include <utility> #include <utility>
#ifdef OMATH_USE_AVX2 #ifdef OMATH_USE_AVX2
@@ -43,8 +46,8 @@ namespace omath
ZERO_TO_ONE // DirectX / Vulkan: [0.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);
template<size_t Rows = 0, size_t Columns = 0, class Type = float, MatStoreType StoreType = MatStoreType::ROW_MAJOR> template<size_t Rows = 0, size_t Columns = 0, class Type = float, MatStoreType StoreType = MatStoreType::ROW_MAJOR>
@@ -58,7 +61,7 @@ namespace omath
clear(); clear();
} }
[[nodiscard]] [[nodiscard("You must use store ordering")]]
consteval static MatStoreType get_store_ordering() noexcept consteval static MatStoreType get_store_ordering() noexcept
{ {
return StoreType; return StoreType;
@@ -93,13 +96,13 @@ namespace omath
m_data = other.m_data; m_data = other.m_data;
} }
[[nodiscard]] [[nodiscard("You must use element reference")]]
constexpr Type& operator[](const size_t row, const size_t col) constexpr Type& operator[](const size_t row, const size_t col)
{ {
return at(row, col); return at(row, col);
} }
[[nodiscard]] [[nodiscard("You must use element reference")]]
constexpr const Type& operator[](const size_t row, const size_t col) const constexpr const Type& operator[](const size_t row, const size_t col) const
{ {
return at(row, col); return at(row, col);
@@ -110,25 +113,25 @@ namespace omath
m_data = std::move(other.m_data); m_data = std::move(other.m_data);
} }
[[nodiscard]] [[nodiscard("You must use row count")]]
static constexpr size_t row_count() noexcept static constexpr size_t row_count() noexcept
{ {
return Rows; return Rows;
} }
[[nodiscard]] [[nodiscard("You must use column count")]]
static constexpr size_t columns_count() noexcept static constexpr size_t columns_count() noexcept
{ {
return Columns; return Columns;
} }
[[nodiscard]] [[nodiscard("You must use matrix size")]]
static consteval MatSize size() noexcept static constexpr MatSize size() noexcept
{ {
return {Rows, Columns}; return {Rows, Columns};
} }
[[nodiscard]] [[nodiscard("You must use element reference")]]
constexpr const Type& at(const size_t row_index, const size_t column_index) const constexpr const Type& at(const size_t row_index, const size_t column_index) const
{ {
#if !defined(NDEBUG) && defined(OMATH_SUPRESS_SAFETY_CHECKS) #if !defined(NDEBUG) && defined(OMATH_SUPRESS_SAFETY_CHECKS)
@@ -148,12 +151,13 @@ namespace omath
} }
} }
[[nodiscard]] constexpr Type& at(const size_t row_index, const size_t column_index) [[nodiscard("You must use element reference")]] constexpr Type& at(const size_t row_index,
const size_t column_index)
{ {
return const_cast<Type&>(std::as_const(*this).at(row_index, column_index)); return const_cast<Type&>(std::as_const(*this).at(row_index, column_index));
} }
[[nodiscard]] [[nodiscard("You must use sum of elements")]]
constexpr Type sum() const noexcept constexpr Type sum() const noexcept
{ {
return std::accumulate(m_data.begin(), m_data.end(), static_cast<Type>(0)); return std::accumulate(m_data.begin(), m_data.end(), static_cast<Type>(0));
@@ -170,11 +174,18 @@ namespace omath
} }
// Operator overloading for multiplication with another Mat // Operator overloading for multiplication with another Mat
template<size_t OtherColumns> [[nodiscard]] template<size_t OtherColumns> [[nodiscard("You must use result matrix")]]
constexpr Mat<Rows, OtherColumns, Type, StoreType> constexpr Mat<Rows, OtherColumns, Type, StoreType>
operator*(const Mat<Columns, OtherColumns, Type, StoreType>& other) const operator*(const Mat<Columns, OtherColumns, Type, StoreType>& other) const
{ {
#ifdef OMATH_USE_AVX2 #ifdef OMATH_USE_AVX2
if (std::is_constant_evaluated())
{
if constexpr (StoreType == MatStoreType::ROW_MAJOR)
return cache_friendly_multiply_row_major(other);
else if constexpr (StoreType == MatStoreType::COLUMN_MAJOR)
return cache_friendly_multiply_col_major(other);
}
if constexpr (StoreType == MatStoreType::ROW_MAJOR) if constexpr (StoreType == MatStoreType::ROW_MAJOR)
return avx_multiply_row_major(other); return avx_multiply_row_major(other);
else if constexpr (StoreType == MatStoreType::COLUMN_MAJOR) else if constexpr (StoreType == MatStoreType::COLUMN_MAJOR)
@@ -191,7 +202,11 @@ namespace omath
constexpr Mat& operator*=(const Type& f) noexcept constexpr Mat& operator*=(const Type& f) noexcept
{ {
std::ranges::for_each(m_data, [&f](auto& val) { val *= f; }); std::ranges::for_each(m_data,
[&f](auto& val)
{
val *= f;
});
return *this; return *this;
} }
@@ -201,7 +216,7 @@ namespace omath
return *this = *this * other; return *this = *this * other;
} }
[[nodiscard]] [[nodiscard("You must use result matrix")]]
constexpr Mat operator*(const Type& value) const noexcept constexpr Mat operator*(const Type& value) const noexcept
{ {
Mat result(*this); Mat result(*this);
@@ -211,11 +226,15 @@ namespace omath
constexpr Mat& operator/=(const Type& value) noexcept constexpr Mat& operator/=(const Type& value) noexcept
{ {
std::ranges::for_each(m_data, [&value](auto& val) { val /= value; }); std::ranges::for_each(m_data,
[&value](auto& val)
{
val /= value;
});
return *this; return *this;
} }
[[nodiscard]] [[nodiscard("You must use result matrix")]]
constexpr Mat operator/(const Type& value) const noexcept constexpr Mat operator/(const Type& value) const noexcept
{ {
Mat result(*this); Mat result(*this);
@@ -239,7 +258,7 @@ namespace omath
return *this; return *this;
} }
[[nodiscard]] [[nodiscard("You must use transposed matrix")]]
constexpr Mat<Columns, Rows, Type, StoreType> transposed() const noexcept constexpr Mat<Columns, Rows, Type, StoreType> transposed() const noexcept
{ {
Mat<Columns, Rows, Type, StoreType> transposed; Mat<Columns, Rows, Type, StoreType> transposed;
@@ -250,7 +269,7 @@ namespace omath
return transposed; return transposed;
} }
[[nodiscard]] [[nodiscard("You must use determinant")]]
constexpr Type determinant() const constexpr Type determinant() const
{ {
static_assert(Rows == Columns, "Determinant is only defined for square matrices."); static_assert(Rows == Columns, "Determinant is only defined for square matrices.");
@@ -271,10 +290,11 @@ namespace omath
} }
return det; return det;
} }
else // For no reason MSVC triggers on it as unreachable code so we keep else here.
std::unreachable(); std::unreachable();
} }
[[nodiscard]] [[nodiscard("You must use stripped matrix")]]
constexpr Mat<Rows - 1, Columns - 1, Type, StoreType> strip(const size_t row, const size_t column) const constexpr Mat<Rows - 1, Columns - 1, Type, StoreType> strip(const size_t row, const size_t column) const
{ {
static_assert(Rows - 1 > 0 && Columns - 1 > 0); static_assert(Rows - 1 > 0 && Columns - 1 > 0);
@@ -295,32 +315,32 @@ namespace omath
return result; return result;
} }
[[nodiscard]] [[nodiscard("You must use minor")]]
constexpr Type minor(const size_t row, const size_t column) const constexpr Type minor(const size_t row, const size_t column) const
{ {
return strip(row, column).determinant(); return strip(row, column).determinant();
} }
[[nodiscard]] [[nodiscard("You must use algebraic complement")]]
constexpr Type alg_complement(const size_t row, const size_t column) const constexpr Type alg_complement(const size_t row, const size_t column) const
{ {
const auto minor_value = minor(row, column); const auto minor_value = minor(row, column);
return (row + column + 2) % 2 == 0 ? minor_value : -minor_value; return (row + column + 2) % 2 == 0 ? minor_value : -minor_value;
} }
[[nodiscard]] [[nodiscard("You must use raw array")]]
constexpr const std::array<Type, Rows * Columns>& raw_array() const constexpr const std::array<Type, Rows * Columns>& raw_array() const
{ {
return m_data; return m_data;
} }
[[nodiscard]] [[nodiscard("You must use raw array")]]
constexpr std::array<Type, Rows * Columns>& raw_array() constexpr std::array<Type, Rows * Columns>& raw_array()
{ {
return m_data; return m_data;
} }
[[nodiscard]] [[nodiscard("You must use string representation")]]
std::string to_string() const noexcept std::string to_string() const noexcept
{ {
std::ostringstream oss; std::ostringstream oss;
@@ -342,14 +362,14 @@ namespace omath
return oss.str(); return oss.str();
} }
[[nodiscard]] [[nodiscard("You must use wide string representation")]]
std::wstring to_wstring() const noexcept std::wstring to_wstring() const noexcept
{ {
const auto ascii_string = to_string(); const auto ascii_string = to_string();
return {ascii_string.cbegin(), ascii_string.cend()}; return {ascii_string.cbegin(), ascii_string.cend()};
} }
[[nodiscard]] [[nodiscard("You must use UTF-8 string representation")]]
// ReSharper disable once CppInconsistentNaming // ReSharper disable once CppInconsistentNaming
std::u8string to_u8string() const noexcept std::u8string to_u8string() const noexcept
{ {
@@ -357,20 +377,20 @@ namespace omath
return {ascii_string.cbegin(), ascii_string.cend()}; return {ascii_string.cbegin(), ascii_string.cend()};
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator==(const Mat& mat) const bool operator==(const Mat& mat) const
{ {
return m_data == mat.m_data; return m_data == mat.m_data;
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator!=(const Mat& mat) const bool operator!=(const Mat& mat) const
{ {
return !operator==(mat); return !operator==(mat);
} }
// Static methods that return fixed-size matrices // Static methods that return fixed-size matrices
[[nodiscard]] [[nodiscard("You must use screen matrix")]]
constexpr static Mat<4, 4> to_screen_mat(const Type& screen_width, const Type& screen_height) noexcept constexpr static Mat<4, 4> to_screen_mat(const Type& screen_width, const Type& screen_height) noexcept
{ {
return { return {
@@ -381,12 +401,12 @@ namespace omath
}; };
} }
[[nodiscard]] [[nodiscard("You must use inverted matrix")]]
constexpr std::optional<Mat> inverted() const constexpr std::optional<Mat> inverted() const
{ {
const auto det = determinant(); const auto det = determinant();
if (std::abs(det) < std::numeric_limits<Type>::epsilon()) if (internal::abs(det) < std::numeric_limits<Type>::epsilon())
return std::nullopt; return std::nullopt;
const auto transposed_mat = transposed(); const auto transposed_mat = transposed();
@@ -404,7 +424,7 @@ namespace omath
private: private:
std::array<Type, Rows * Columns> m_data; std::array<Type, Rows * Columns> m_data;
template<size_t OtherColumns> [[nodiscard]] template<size_t OtherColumns> [[nodiscard("You must use result matrix")]]
constexpr Mat<Rows, OtherColumns, Type, MatStoreType::ROW_MAJOR> constexpr Mat<Rows, OtherColumns, Type, MatStoreType::ROW_MAJOR>
cache_friendly_multiply_row_major(const Mat<Columns, OtherColumns, Type, MatStoreType::ROW_MAJOR>& other) const cache_friendly_multiply_row_major(const Mat<Columns, OtherColumns, Type, MatStoreType::ROW_MAJOR>& other) const
{ {
@@ -419,7 +439,7 @@ namespace omath
return result; return result;
} }
template<size_t OtherColumns> [[nodiscard]] template<size_t OtherColumns> [[nodiscard("You must use result matrix")]]
constexpr Mat<Rows, OtherColumns, Type, MatStoreType::COLUMN_MAJOR> cache_friendly_multiply_col_major( constexpr Mat<Rows, OtherColumns, Type, MatStoreType::COLUMN_MAJOR> cache_friendly_multiply_col_major(
const Mat<Columns, OtherColumns, Type, MatStoreType::COLUMN_MAJOR>& other) const const Mat<Columns, OtherColumns, Type, MatStoreType::COLUMN_MAJOR>& other) const
{ {
@@ -434,7 +454,7 @@ namespace omath
return result; return result;
} }
#ifdef OMATH_USE_AVX2 #ifdef OMATH_USE_AVX2
template<size_t OtherColumns> [[nodiscard]] template<size_t OtherColumns> [[nodiscard("You must use result matrix")]]
constexpr Mat<Rows, OtherColumns, Type, MatStoreType::COLUMN_MAJOR> constexpr Mat<Rows, OtherColumns, Type, MatStoreType::COLUMN_MAJOR>
avx_multiply_col_major(const Mat<Columns, OtherColumns, Type, MatStoreType::COLUMN_MAJOR>& other) const avx_multiply_col_major(const Mat<Columns, OtherColumns, Type, MatStoreType::COLUMN_MAJOR>& other) const
{ {
@@ -504,7 +524,7 @@ namespace omath
return result; return result;
} }
template<size_t OtherColumns> [[nodiscard]] template<size_t OtherColumns> [[nodiscard("You must use result matrix")]]
constexpr Mat<Rows, OtherColumns, Type, MatStoreType::ROW_MAJOR> constexpr Mat<Rows, OtherColumns, Type, MatStoreType::ROW_MAJOR>
avx_multiply_row_major(const Mat<Columns, OtherColumns, Type, MatStoreType::ROW_MAJOR>& other) const avx_multiply_row_major(const Mat<Columns, OtherColumns, Type, MatStoreType::ROW_MAJOR>& other) const
{ {
@@ -575,24 +595,23 @@ namespace omath
#endif #endif
}; };
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR> [[nodiscard]] template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR> [[nodiscard("You must use row matrix")]]
constexpr static Mat<1, 4, Type, St> mat_row_from_vector(const Vector3<Type>& vector) noexcept constexpr Mat<1, 4, Type, St> mat_row_from_vector(const Vector3<Type>& vector) noexcept
{ {
return {{vector.x, vector.y, vector.z, 1}}; return {{vector.x, vector.y, vector.z, 1}};
} }
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR> [[nodiscard]] template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR> [[nodiscard("You must use column matrix")]]
constexpr static Mat<4, 1, Type, St> mat_column_from_vector(const Vector3<Type>& vector) noexcept constexpr Mat<4, 1, Type, St> mat_column_from_vector(const Vector3<Type>& vector) noexcept
{ {
return {{vector.x}, {vector.y}, {vector.z}, {1}}; return {{vector.x}, {vector.y}, {vector.z}, {1}};
} }
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR> template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
[[nodiscard]] [[nodiscard("You must use translation matrix")]]
constexpr Mat<4, 4, Type, St> mat_translation(const Vector3<Type>& diff) noexcept constexpr Mat<4, 4, Type, St> mat_translation(const Vector3<Type>& diff) noexcept
{ {
return return {
{
{1, 0, 0, diff.x}, {1, 0, 0, diff.x},
{0, 1, 0, diff.y}, {0, 1, 0, diff.y},
{0, 0, 1, diff.z}, {0, 0, 1, diff.z},
@@ -600,7 +619,7 @@ namespace omath
}; };
} }
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR> template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
[[nodiscard]] [[nodiscard("You must use scale matrix")]]
constexpr Mat<4, 4, Type, St> mat_scale(const Vector3<Type>& scale) noexcept constexpr Mat<4, 4, Type, St> mat_scale(const Vector3<Type>& scale) noexcept
{ {
return { return {
@@ -611,67 +630,101 @@ namespace omath
}; };
} }
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle> template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
[[nodiscard]] [[nodiscard("You must use extracted origin")]]
Mat<4, 4, Type, St> mat_rotation_axis_x(const Angle& angle) noexcept constexpr Vector3<Type> mat_extract_origin(const Mat<4, 4, Type, St>& mat) noexcept
{ {
return return {mat.at(0, 3), mat.at(1, 3), mat.at(2, 3)};
{
{1, 0, 0, 0},
{0, angle.cos(), -angle.sin(), 0},
{0, angle.sin(), angle.cos(), 0},
{0, 0, 0, 1}
};
} }
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle> template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
[[nodiscard]] [[nodiscard("You must use extracted scale")]]
Mat<4, 4, Type, St> mat_rotation_axis_y(const Angle& angle) noexcept constexpr Vector3<Type> mat_extract_scale(const Mat<4, 4, Type, St>& mat) noexcept
{ {
return auto column_length = [](const Type x, const Type y, const Type z)
{ {
{angle.cos(), 0, angle.sin(), 0}, return static_cast<Type>(internal::sqrt(x * x + y * y + z * z));
{0 , 1, 0, 0},
{-angle.sin(), 0, angle.cos(), 0},
{0 , 0, 0, 1}
}; };
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle> const auto scale_x = column_length(mat.at(0, 0), mat.at(1, 0), mat.at(2, 0));
[[nodiscard]] const auto scale_y = column_length(mat.at(0, 1), mat.at(1, 1), mat.at(2, 1));
Mat<4, 4, Type, St> mat_rotation_axis_z(const Angle& angle) noexcept const auto scale_z = column_length(mat.at(0, 2), mat.at(1, 2), mat.at(2, 2));
{
return constexpr auto epsilon = std::numeric_limits<Type>::epsilon();
{
{angle.cos(), -angle.sin(), 0, 0}, return {
{angle.sin(), angle.cos(), 0, 0}, internal::abs(scale_x) < epsilon ? Type{1} : scale_x,
{ 0, 0, 1, 0}, internal::abs(scale_y) < epsilon ? Type{1} : scale_y,
{ 0, 0, 0, 1}, internal::abs(scale_z) < epsilon ? Type{1} : scale_z,
}; };
} }
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR> template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
[[nodiscard]] requires std::is_floating_point_v<Type>
static Mat<4, 4, Type, St> mat_camera_view(const Vector3<Type>& forward, const Vector3<Type>& right, [[nodiscard("You must use extracted rotation")]]
constexpr Vector3<Type> mat_extract_rotation_zyx(const Mat<4, 4, Type, St>& mat) noexcept
{
const auto scale = mat_extract_scale(mat);
const auto m00 = mat.at(0, 0) / scale.x;
const auto m10 = mat.at(1, 0) / scale.x;
const auto m20 = mat.at(2, 0) / scale.x;
const auto m21 = mat.at(2, 1) / scale.y;
const auto m22 = mat.at(2, 2) / scale.z;
return {
angles::radians_to_degrees(internal::atan2(m21, m22)),
angles::radians_to_degrees(internal::asin(std::clamp(-m20, Type{-1}, Type{1}))),
angles::radians_to_degrees(internal::atan2(m10, m00)),
};
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
[[nodiscard("You must use rotation matrix")]]
constexpr Mat<4, 4, Type, St> mat_rotation_axis_x(const Angle& angle) noexcept
{
return {{1, 0, 0, 0}, {0, angle.cos(), -angle.sin(), 0}, {0, angle.sin(), angle.cos(), 0}, {0, 0, 0, 1}};
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
[[nodiscard("You must use rotation matrix")]]
constexpr Mat<4, 4, Type, St> mat_rotation_axis_y(const Angle& angle) noexcept
{
return {{angle.cos(), 0, angle.sin(), 0}, {0, 1, 0, 0}, {-angle.sin(), 0, angle.cos(), 0}, {0, 0, 0, 1}};
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
[[nodiscard("You must use rotation matrix")]]
constexpr Mat<4, 4, Type, St> mat_rotation_axis_z(const Angle& angle) noexcept
{
return {
{angle.cos(), -angle.sin(), 0, 0},
{angle.sin(), angle.cos(), 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
};
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
[[nodiscard("You must use camera view matrix")]]
constexpr Mat<4, 4, Type, St> mat_camera_view(const Vector3<Type>& forward, const Vector3<Type>& right,
const Vector3<Type>& up, const Vector3<Type>& camera_origin) noexcept const Vector3<Type>& up, const Vector3<Type>& camera_origin) noexcept
{ {
return Mat<4, 4, Type, St> return Mat<4, 4, Type, St>{
{
{right.x, right.y, right.z, 0}, {right.x, right.y, right.z, 0},
{up.x, up.y, up.z, 0}, {up.x, up.y, up.z, 0},
{forward.x, forward.y, forward.z, 0}, {forward.x, forward.y, forward.z, 0},
{0, 0, 0, 1}, {0, 0, 0, 1},
} * 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> NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]] [[nodiscard("You must use perspective matrix")]] constexpr Mat<4, 4, Type, St>
Mat<4, 4, Type, St> mat_perspective_left_handed(const Type field_of_view, const Type aspect_ratio, mat_perspective_left_handed_vertical_fov(const Type field_of_view, const Type aspect_ratio, const Type near,
const Type near, const Type far) noexcept const Type far) noexcept
{ {
const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2}); const auto fov_half_tan = internal::tan(angles::degrees_to_radians(field_of_view) / Type{2});
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE) if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}}, 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{1} / fov_half_tan, Type{0}, Type{0}},
@@ -688,11 +741,11 @@ namespace omath
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> NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]] [[nodiscard("You must use perspective matrix")]] constexpr Mat<4, 4, Type, St>
Mat<4, 4, Type, St> mat_perspective_right_handed(const Type field_of_view, const Type aspect_ratio, mat_perspective_right_handed_vertical_fov(const Type field_of_view, const Type aspect_ratio, const Type near,
const Type near, const Type far) noexcept const Type far) noexcept
{ {
const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2}); const auto fov_half_tan = internal::tan(angles::degrees_to_radians(field_of_view) / Type{2});
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE) if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}}, return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}},
@@ -713,12 +766,11 @@ namespace omath
// X and Y scales derived as: X = 1 / tan(hfov/2), Y = aspect / tan(hfov/2). // 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, template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE> NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]] [[nodiscard("You must use perspective matrix")]] constexpr Mat<4, 4, Type, St>
Mat<4, 4, Type, St> mat_perspective_left_handed_horizontal_fov(const Type horizontal_fov, mat_perspective_left_handed_horizontal_fov(const Type horizontal_fov, const Type aspect_ratio, const Type near,
const Type aspect_ratio, const Type near,
const Type far) noexcept const Type far) noexcept
{ {
const auto inv_tan_half_hfov = Type{1} / std::tan(angles::degrees_to_radians(horizontal_fov) / Type{2}); const auto inv_tan_half_hfov = Type{1} / internal::tan(angles::degrees_to_radians(horizontal_fov) / Type{2});
const auto x_axis = inv_tan_half_hfov; const auto x_axis = inv_tan_half_hfov;
const auto y_axis = inv_tan_half_hfov * aspect_ratio; const auto y_axis = inv_tan_half_hfov * aspect_ratio;
@@ -730,7 +782,7 @@ namespace omath
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE) else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return {{x_axis, Type{0}, Type{0}, Type{0}}, return {{x_axis, Type{0}, Type{0}, Type{0}},
{Type{0}, y_axis, 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}, (far + near) / (far - near), -(Type{2} * near * far) / (far - near)},
{Type{0}, Type{0}, Type{1}, Type{0}}}; {Type{0}, Type{0}, Type{1}, Type{0}}};
else else
std::unreachable(); std::unreachable();
@@ -738,12 +790,12 @@ namespace omath
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> NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]] [[nodiscard("You must use perspective matrix")]] constexpr Mat<4, 4, Type, St>
Mat<4, 4, Type, St> mat_perspective_right_handed_horizontal_fov(const Type horizontal_fov, mat_perspective_right_handed_horizontal_fov(const Type horizontal_fov, const Type aspect_ratio, const Type near,
const Type aspect_ratio, const Type near,
const Type far) noexcept const Type far) noexcept
{ {
const auto inv_tan_half_hfov = Type{1} / std::tan(angles::degrees_to_radians(horizontal_fov) / Type{2}); const auto inv_tan_half_hfov = Type{1} / internal::tan(angles::degrees_to_radians(horizontal_fov) / Type{2});
const auto x_axis = inv_tan_half_hfov; const auto x_axis = inv_tan_half_hfov;
const auto y_axis = inv_tan_half_hfov * aspect_ratio; const auto y_axis = inv_tan_half_hfov * aspect_ratio;
@@ -755,63 +807,52 @@ namespace omath
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE) else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
return {{x_axis, Type{0}, Type{0}, Type{0}}, return {{x_axis, Type{0}, Type{0}, Type{0}},
{Type{0}, y_axis, 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}, -(far + near) / (far - near), -(Type{2} * near * far) / (far - near)},
{Type{0}, Type{0}, -Type{1}, Type{0}}}; {Type{0}, Type{0}, -Type{1}, Type{0}}};
else else
std::unreachable(); 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> NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]] [[nodiscard("You must use ortho matrix")]] constexpr Mat<4, 4, Type, St>
Mat<4, 4, Type, St> mat_ortho_left_handed(const Type left, const Type right, const Type bottom, const Type top, mat_ortho_left_handed(const Type left, const Type right, const Type bottom, const Type top, const Type near,
const Type near, const Type far) noexcept const Type far) noexcept
{ {
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE) if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
return 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)},
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)}, {0.f, 0.f, static_cast<Type>(1) / (far - near), -near / (far - near)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)}, {0.f, 0.f, 0.f, 1.f}};
{ 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) 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)},
{ {0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)},
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)}, {0.f, 0.f, static_cast<Type>(2) / (far - near), -(far + near) / (far - near)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)}, {0.f, 0.f, 0.f, 1.f}};
{ 0.f, 0.f, static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
{ 0.f, 0.f, 0.f, 1.f }
};
else else
std::unreachable(); 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> NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
[[nodiscard]] [[nodiscard("You must use ortho matrix")]] constexpr Mat<4, 4, Type, St>
Mat<4, 4, Type, St> mat_ortho_right_handed(const Type left, const Type right, const Type bottom, const Type top, mat_ortho_right_handed(const Type left, const Type right, const Type bottom, const Type top, const Type near,
const Type near, const Type far) noexcept const Type far) noexcept
{ {
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE) if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
return 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)},
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)}, {0.f, 0.f, -static_cast<Type>(1) / (far - near), -near / (far - near)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)}, {0.f, 0.f, 0.f, 1.f}};
{ 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) 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)},
{ {0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)},
{ static_cast<Type>(2) / (right - left), 0.f, 0.f, -(right + left) / (right - left)}, {0.f, 0.f, -static_cast<Type>(2) / (far - near), -(far + near) / (far - near)},
{ 0.f, static_cast<Type>(2) / (top - bottom), 0.f, -(top + bottom) / (top - bottom)}, {0.f, 0.f, 0.f, 1.f}};
{ 0.f, 0.f, -static_cast<Type>(2) / (far - near), -(far + near) / (far - near) },
{ 0.f, 0.f, 0.f, 1.f }
};
else else
std::unreachable(); 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) constexpr Mat<4, 4, T, St> mat_look_at_left_handed(const Vector3<T>& eye, const Vector3<T>& center,
const Vector3<T>& up)
{ {
const Vector3<T> f = (center - eye).normalized(); const Vector3<T> f = (center - eye).normalized();
const Vector3<T> s = f.cross(up).normalized(); const Vector3<T> s = f.cross(up).normalized();
@@ -820,7 +861,8 @@ namespace omath
} }
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_right_handed(const Vector3<T>& eye, const Vector3<T>& center, const Vector3<T>& up) constexpr Mat<4, 4, T, St> mat_look_at_right_handed(const Vector3<T>& eye, const Vector3<T>& center,
const Vector3<T>& up)
{ {
const Vector3<T> f = (center - eye).normalized(); const Vector3<T> f = (center - eye).normalized();
const Vector3<T> s = f.cross(up).normalized(); const Vector3<T> s = f.cross(up).normalized();
@@ -834,14 +876,14 @@ template<size_t Rows, size_t Columns, class Type, omath::MatStoreType StoreType>
struct std::formatter<omath::Mat<Rows, Columns, Type, StoreType>> // NOLINT(*-dcl58-cpp) struct std::formatter<omath::Mat<Rows, Columns, Type, StoreType>> // NOLINT(*-dcl58-cpp)
{ {
using MatType = omath::Mat<Rows, Columns, Type, StoreType>; using MatType = omath::Mat<Rows, Columns, Type, StoreType>;
[[nodiscard]] [[nodiscard("You must use parse iterator")]]
static constexpr auto parse(std::format_parse_context& ctx) static constexpr auto parse(std::format_parse_context& ctx)
{ {
return ctx.begin(); return ctx.begin();
} }
template<class FormatContext> template<class FormatContext>
[[nodiscard]] [[nodiscard("You must use format iterator")]]
static auto format(const MatType& mat, FormatContext& ctx) static auto format(const MatType& mat, FormatContext& ctx)
{ {
if constexpr (std::is_same_v<typename FormatContext::char_type, char>) if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
+4 -4
View File
@@ -2,8 +2,8 @@
// Created by Orange on 11/13/2024. // Created by Orange on 11/13/2024.
// //
#pragma once #pragma once
#include "omath/internal/constexpr_math.hpp"
#include "vector3.hpp" #include "vector3.hpp"
namespace omath namespace omath
{ {
/* /*
@@ -40,13 +40,13 @@ namespace omath
} }
[[nodiscard]] [[nodiscard]]
Vector::ContainedType side_a_length() const constexpr Vector::ContainedType side_a_length() const
{ {
return m_vertex1.distance_to(m_vertex2); return m_vertex1.distance_to(m_vertex2);
} }
[[nodiscard]] [[nodiscard]]
Vector::ContainedType side_b_length() const constexpr Vector::ContainedType side_b_length() const
{ {
return m_vertex3.distance_to(m_vertex2); return m_vertex3.distance_to(m_vertex2);
} }
@@ -69,7 +69,7 @@ namespace omath
const auto side_b = side_b_length(); const auto side_b = side_b_length();
const auto hypot_value = hypot(); const auto hypot_value = hypot();
return std::abs(side_a * side_a + side_b * side_b - hypot_value * hypot_value) <= 0.0001f; return internal::abs(side_a * side_a + side_b * side_b - hypot_value * hypot_value) <= 0.0001f;
} }
[[nodiscard]] [[nodiscard]]
constexpr Vector side_b_vector() const constexpr Vector side_b_vector() const
+53 -35
View File
@@ -3,6 +3,7 @@
// //
#pragma once #pragma once
#include "omath/internal/constexpr_math.hpp"
#include <cmath> #include <cmath>
#include <format> #include <format>
#include <tuple> #include <tuple>
@@ -28,7 +29,7 @@ namespace omath
template<class CastedType> template<class CastedType>
requires std::is_arithmetic_v<CastedType> requires std::is_arithmetic_v<CastedType>
[[nodiscard]] constexpr explicit operator Vector2<CastedType>() const noexcept [[nodiscard("You must use casted vector")]] constexpr explicit operator Vector2<CastedType>() const noexcept
{ {
return {static_cast<CastedType>(x), static_cast<CastedType>(y)}; return {static_cast<CastedType>(x), static_cast<CastedType>(y)};
} }
@@ -37,13 +38,13 @@ namespace omath
} }
// Equality operators // Equality operators
[[nodiscard]] [[nodiscard("You must use comparison result")]]
constexpr bool operator==(const Vector2& other) const noexcept constexpr bool operator==(const Vector2& other) const noexcept
{ {
return x == other.x && y == other.y; return x == other.x && y == other.y;
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
constexpr bool operator!=(const Vector2& other) const noexcept constexpr bool operator!=(const Vector2& other) const noexcept
{ {
return !(*this == other); return !(*this == other);
@@ -115,45 +116,51 @@ namespace omath
} }
// Basic vector operations // Basic vector operations
[[nodiscard]] Type distance_to(const Vector2& other) const noexcept [[nodiscard("You must use distance")]]
constexpr Type distance_to(const Vector2& other) const noexcept
{ {
return std::sqrt(distance_to_sqr(other)); return internal::sqrt(distance_to_sqr(other));
} }
[[nodiscard]] constexpr Type distance_to_sqr(const Vector2& other) const noexcept [[nodiscard("You must use squared distance")]]
constexpr Type distance_to_sqr(const Vector2& other) const noexcept
{ {
return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y); return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y);
} }
[[nodiscard]] constexpr Type dot(const Vector2& other) const noexcept [[nodiscard("You must use dot product")]]
constexpr Type dot(const Vector2& other) const noexcept
{ {
return x * other.x + y * other.y; return x * other.x + y * other.y;
} }
#ifndef _MSC_VER #ifndef _MSC_VER
[[nodiscard]] constexpr Type length() const noexcept [[nodiscard("You must use length")]] constexpr Type length() const noexcept
{ {
return std::hypot(this->x, this->y); return internal::hypot(this->x, this->y);
} }
[[nodiscard]] constexpr Vector2 normalized() const noexcept [[nodiscard("You must use normalized vector")]] constexpr Vector2 normalized() const noexcept
{ {
const Type len = length(); const Type len = length();
return len > 0.f ? *this / len : *this; return len > 0.f ? *this / len : *this;
} }
#else #else
[[nodiscard]] Type length() const noexcept [[nodiscard("You must use length")]]
constexpr Type length() const noexcept
{ {
return std::hypot(x, y); return internal::hypot(x, y);
} }
[[nodiscard]] Vector2 normalized() const noexcept [[nodiscard("You must use normalized vector")]]
constexpr Vector2 normalized() const noexcept
{ {
const Type len = length(); const Type len = length();
return len > static_cast<Type>(0) ? *this / len : *this; return len > static_cast<Type>(0) ? *this / len : *this;
} }
#endif #endif
[[nodiscard]] constexpr Type length_sqr() const noexcept [[nodiscard("You must use squared length")]]
constexpr Type length_sqr() const noexcept
{ {
return x * x + y * y; return x * x + y * y;
} }
@@ -165,80 +172,91 @@ namespace omath
y = y < static_cast<Type>(0) ? -y : y; y = y < static_cast<Type>(0) ? -y : y;
return *this; return *this;
} }
[[nodiscard("You must use absed vector")]]
constexpr Vector2 abs() const noexcept
{
return Vector2{*this}.abs();
}
[[nodiscard]] constexpr Vector2 operator-() const noexcept [[nodiscard("You must use negated vector")]]
constexpr Vector2 operator-() const noexcept
{ {
return {-x, -y}; return {-x, -y};
} }
// Binary arithmetic operators // Binary arithmetic operators
[[nodiscard]] constexpr Vector2 operator+(const Vector2& other) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector2 operator+(const Vector2& other) const noexcept
{ {
return {x + other.x, y + other.y}; return {x + other.x, y + other.y};
} }
[[nodiscard]] constexpr Vector2 operator-(const Vector2& other) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector2 operator-(const Vector2& other) const noexcept
{ {
return {x - other.x, y - other.y}; return {x - other.x, y - other.y};
} }
[[nodiscard]] constexpr Vector2 operator*(const Type& value) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector2 operator*(const Type& value) const noexcept
{ {
return {x * value, y * value}; return {x * value, y * value};
} }
[[nodiscard]] constexpr Vector2 operator/(const Type& value) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector2 operator/(const Type& value) const noexcept
{ {
return {x / value, y / value}; return {x / value, y / value};
} }
// Sum of elements // Sum of elements
[[nodiscard]] constexpr Type sum() const noexcept [[nodiscard("You must use sum of elements")]]
constexpr Type sum() const noexcept
{ {
return x + y; return x + y;
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator<(const Vector2& other) const noexcept constexpr bool operator<(const Vector2& other) const noexcept
{ {
return length() < other.length(); return length() < other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator>(const Vector2& other) const noexcept constexpr bool operator>(const Vector2& other) const noexcept
{ {
return length() > other.length(); return length() > other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator<=(const Vector2& other) const noexcept constexpr bool operator<=(const Vector2& other) const noexcept
{ {
return length() <= other.length(); return length() <= other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator>=(const Vector2& other) const noexcept constexpr bool operator>=(const Vector2& other) const noexcept
{ {
return length() >= other.length(); return length() >= other.length();
} }
[[nodiscard]] [[nodiscard("You must use tuple")]]
constexpr std::tuple<Type, Type> as_tuple() const noexcept constexpr std::tuple<Type, Type> as_tuple() const noexcept
{ {
return std::make_tuple(x, y); return std::make_tuple(x, y);
} }
[[nodiscard]] [[nodiscard("You must use array")]]
constexpr std::array<Type, 2> as_array() const noexcept constexpr std::array<Type, 2> as_array() const noexcept
{ {
return {x, y}; return {x, y};
} }
#ifdef OMATH_IMGUI_INTEGRATION #ifdef OMATH_IMGUI_INTEGRATION
[[nodiscard]] [[nodiscard("You must use ImVec2")]]
constexpr ImVec2 to_im_vec2() const noexcept constexpr ImVec2 to_im_vec2() const noexcept
{ {
return {static_cast<float>(this->x), static_cast<float>(this->y)}; return {static_cast<float>(this->x), static_cast<float>(this->y)};
} }
[[nodiscard]] [[nodiscard("You must use vector from ImVec2")]]
static Vector2 from_im_vec2(const ImVec2& other) noexcept static Vector2 from_im_vec2(const ImVec2& other) noexcept
{ {
return {static_cast<Type>(other.x), static_cast<Type>(other.y)}; return {static_cast<Type>(other.x), static_cast<Type>(other.y)};
@@ -249,7 +267,7 @@ namespace omath
template<> struct std::hash<omath::Vector2<float>> template<> struct std::hash<omath::Vector2<float>>
{ {
[[nodiscard]] [[nodiscard("You must use hash value")]]
std::size_t operator()(const omath::Vector2<float>& vec) const noexcept std::size_t operator()(const omath::Vector2<float>& vec) const noexcept
{ {
std::size_t hash = 0; std::size_t hash = 0;
@@ -265,14 +283,14 @@ template<> struct std::hash<omath::Vector2<float>>
template<class Type> template<class Type>
struct std::formatter<omath::Vector2<Type>> // NOLINT(*-dcl58-cpp) struct std::formatter<omath::Vector2<Type>> // NOLINT(*-dcl58-cpp)
{ {
[[nodiscard]] [[nodiscard("You must use parse iterator")]]
static constexpr auto parse(std::format_parse_context& ctx) static constexpr auto parse(std::format_parse_context& ctx)
{ {
return ctx.begin(); return ctx.begin();
} }
template<class FormatContext> template<class FormatContext>
[[nodiscard]] [[nodiscard("You must use format iterator")]]
static auto format(const omath::Vector2<Type>& vec, FormatContext& ctx) static auto format(const omath::Vector2<Type>& vec, FormatContext& ctx)
{ {
if constexpr (std::is_same_v<typename FormatContext::char_type, char>) if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
+74 -46
View File
@@ -4,6 +4,7 @@
#pragma once #pragma once
#include "omath/internal/constexpr_math.hpp"
#include "omath/linear_algebra/vector2.hpp" #include "omath/linear_algebra/vector2.hpp"
#include "omath/trigonometry/angle.hpp" #include "omath/trigonometry/angle.hpp"
#include <cstdint> #include <cstdint>
@@ -32,17 +33,20 @@ namespace omath
template<class CastedType> template<class CastedType>
requires std::is_arithmetic_v<CastedType> requires std::is_arithmetic_v<CastedType>
[[nodiscard]] constexpr explicit operator Vector3<CastedType>() const noexcept [[nodiscard("You must use casted vector")]]
constexpr explicit operator Vector3<CastedType>() const noexcept
{ {
return {static_cast<CastedType>(this->x), static_cast<CastedType>(this->y), return {static_cast<CastedType>(this->x), static_cast<CastedType>(this->y),
static_cast<CastedType>(this->z)}; static_cast<CastedType>(this->z)};
} }
[[nodiscard]] constexpr bool operator==(const Vector3& other) const noexcept [[nodiscard("You must use comparison result")]]
constexpr bool operator==(const Vector3& other) const noexcept
{ {
return Vector2<Type>::operator==(other) && (other.z == z); return Vector2<Type>::operator==(other) && (other.z == z);
} }
[[nodiscard]] constexpr bool operator!=(const Vector3& other) const noexcept [[nodiscard("You must use comparison result")]]
constexpr bool operator!=(const Vector3& other) const noexcept
{ {
return !(*this == other); return !(*this == other);
} }
@@ -118,130 +122,151 @@ namespace omath
return *this; return *this;
} }
[[nodiscard("You must use absed vector")]]
constexpr Vector3 abs() const noexcept
{
return Vector3{*this}.abs();
}
[[nodiscard]] constexpr Type distance_to_sqr(const Vector3& other) const noexcept [[nodiscard("You must use squared distance")]]
constexpr Type distance_to_sqr(const Vector3& other) const noexcept
{ {
return (*this - other).length_sqr(); return (*this - other).length_sqr();
} }
[[nodiscard]] constexpr Type dot(const Vector3& other) const noexcept [[nodiscard("You must use dot product")]]
constexpr Type dot(const Vector3& other) const noexcept
{ {
return Vector2<Type>::dot(other) + z * other.z; return Vector2<Type>::dot(other) + z * other.z;
} }
#ifndef _MSC_VER #ifndef _MSC_VER
[[nodiscard]] constexpr Type length() const [[nodiscard("You must use length")]] constexpr Type length() const noexcept
{ {
return std::hypot(this->x, this->y, z); return internal::hypot(this->x, this->y, z);
} }
[[nodiscard]] constexpr Type length_2d() const [[nodiscard("You must use 2D length")]] constexpr Type length_2d() const noexcept
{ {
return Vector2<Type>::length(); return Vector2<Type>::length();
} }
[[nodiscard]] Type distance_to(const Vector3& other) const [[nodiscard("You must use distance")]] constexpr Type distance_to(const Vector3& other) const noexcept
{ {
return (*this - other).length(); return (*this - other).length();
} }
[[nodiscard]] constexpr Vector3 normalized() const [[nodiscard("You must use normalized vector")]] constexpr Vector3 normalized() const noexcept
{ {
const Type length_value = this->length(); const Type length_value = this->length();
return length_value != 0 ? *this / length_value : *this; return length_value != 0 ? *this / length_value : *this;
} }
#else #else
[[nodiscard]] Type length() const noexcept [[nodiscard("You must use length")]]
constexpr Type length() const noexcept
{ {
return std::hypot(this->x, this->y, z); return internal::hypot(this->x, this->y, this->z);
} }
[[nodiscard]] Vector3 normalized() const noexcept [[nodiscard("You must use normalized vector")]]
constexpr Vector3 normalized() const noexcept
{ {
const Type len = this->length(); const Type len = this->length();
return len != static_cast<Type>(0) ? *this / len : *this; return len != static_cast<Type>(0) ? *this / len : *this;
} }
[[nodiscard]] Type length_2d() const noexcept [[nodiscard("You must use 2D length")]]
constexpr Type length_2d() const noexcept
{ {
return Vector2<Type>::length(); return Vector2<Type>::length();
} }
[[nodiscard]] Type distance_to(const Vector3& v_other) const noexcept [[nodiscard("You must use distance")]]
constexpr Type distance_to(const Vector3& v_other) const noexcept
{ {
return (*this - v_other).length(); return (*this - v_other).length();
} }
#endif #endif
[[nodiscard]] constexpr Type length_sqr() const noexcept [[nodiscard("You must use squared length")]]
constexpr Type length_sqr() const noexcept
{ {
return Vector2<Type>::length_sqr() + z * z; return Vector2<Type>::length_sqr() + z * z;
} }
[[nodiscard]] constexpr Vector3 operator-() const noexcept [[nodiscard("You must use negated vector")]]
constexpr Vector3 operator-() const noexcept
{ {
return {-this->x, -this->y, -z}; return {-this->x, -this->y, -z};
} }
[[nodiscard]] constexpr Vector3 operator+(const Vector3& other) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector3 operator+(const Vector3& other) const noexcept
{ {
return {this->x + other.x, this->y + other.y, z + other.z}; return {this->x + other.x, this->y + other.y, z + other.z};
} }
[[nodiscard]] constexpr Vector3 operator-(const Vector3& other) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector3 operator-(const Vector3& other) const noexcept
{ {
return {this->x - other.x, this->y - other.y, z - other.z}; return {this->x - other.x, this->y - other.y, z - other.z};
} }
[[nodiscard]] constexpr Vector3 operator*(const Type& value) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector3 operator*(const Type& value) const noexcept
{ {
return {this->x * value, this->y * value, z * value}; return {this->x * value, this->y * value, z * value};
} }
[[nodiscard]] constexpr Vector3 operator*(const Vector3& other) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector3 operator*(const Vector3& other) const noexcept
{ {
return {this->x * other.x, this->y * other.y, z * other.z}; return {this->x * other.x, this->y * other.y, z * other.z};
} }
[[nodiscard]] constexpr Vector3 operator/(const Type& value) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector3 operator/(const Type& value) const noexcept
{ {
return {this->x / value, this->y / value, z / value}; return {this->x / value, this->y / value, z / value};
} }
[[nodiscard]] constexpr Vector3 operator/(const Vector3& other) const noexcept [[nodiscard("You must use result vector")]]
constexpr Vector3 operator/(const Vector3& other) const noexcept
{ {
return {this->x / other.x, this->y / other.y, z / other.z}; return {this->x / other.x, this->y / other.y, z / other.z};
} }
[[nodiscard]] constexpr Vector3 cross(const Vector3& other) const noexcept [[nodiscard("You must use cross product")]]
constexpr Vector3 cross(const Vector3& other) const noexcept
{ {
return {this->y * other.z - z * other.y, z * other.x - this->x * other.z, return {this->y * other.z - z * other.y, z * other.x - this->x * other.z,
this->x * other.y - this->y * other.x}; this->x * other.y - this->y * other.x};
} }
[[nodiscard]] constexpr Type sum() const noexcept [[nodiscard("You must use sum of elements")]]
constexpr Type sum() const noexcept
{ {
return sum_2d() + z; return sum_2d() + z;
} }
[[nodiscard]] [[nodiscard("You must use direction check result")]]
bool point_to_same_direction(const Vector3& other) const constexpr bool point_to_same_direction(const Vector3& other) const
{ {
return dot(other) > static_cast<Type>(0); return dot(other) > static_cast<Type>(0);
} }
[[nodiscard]] std::expected<Angle<float, 0.f, 180.f, AngleFlags::Clamped>, Vector3Error> [[nodiscard("You must use angle between vectors")]]
constexpr std::expected<Angle<float, 0.f, 180.f, AngleFlags::Clamped>, Vector3Error>
angle_between(const Vector3& other) const noexcept angle_between(const Vector3& other) const noexcept
{ {
const auto bottom = length() * other.length(); const auto bottom = length() * other.length();
if (bottom == static_cast<Type>(0)) if (bottom == static_cast<Type>(0))
return std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE); return std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE);
return Angle<float, 0.f, 180.f, AngleFlags::Clamped>::from_radians(internal::acos(dot(other) / bottom));
return Angle<float, 0.f, 180.f, AngleFlags::Clamped>::from_radians(std::acos(dot(other) / bottom));
} }
[[nodiscard]] bool is_perpendicular(const Vector3& other, [[nodiscard("You must use perpendicularity check result")]]
Type epsilon = static_cast<Type>(0.0001)) const noexcept constexpr bool is_perpendicular(const Vector3& other, Type epsilon = static_cast<Type>(0.0001)) const noexcept
{ {
if (const auto angle = angle_between(other)) if (const auto angle = angle_between(other))
return std::abs(angle->as_degrees() - static_cast<Type>(90)) <= epsilon; return std::abs(angle->as_degrees() - static_cast<Type>(90)) <= epsilon;
@@ -249,41 +274,43 @@ namespace omath
return false; return false;
} }
[[nodiscard]] constexpr Type sum_2d() const noexcept [[nodiscard("You must use 2D sum")]]
constexpr Type sum_2d() const noexcept
{ {
return Vector2<Type>::sum(); return Vector2<Type>::sum();
} }
[[nodiscard]] constexpr std::tuple<Type, Type, Type> as_tuple() const noexcept [[nodiscard("You must use tuple")]]
constexpr std::tuple<Type, Type, Type> as_tuple() const noexcept
{ {
return std::make_tuple(this->x, this->y, z); return std::make_tuple(this->x, this->y, z);
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator<(const Vector3& other) const noexcept constexpr bool operator<(const Vector3& other) const noexcept
{ {
return length() < other.length(); return length() < other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator>(const Vector3& other) const noexcept constexpr bool operator>(const Vector3& other) const noexcept
{ {
return length() > other.length(); return length() > other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator<=(const Vector3& other) const noexcept constexpr bool operator<=(const Vector3& other) const noexcept
{ {
return length() <= other.length(); return length() <= other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator>=(const Vector3& other) const noexcept constexpr bool operator>=(const Vector3& other) const noexcept
{ {
return length() >= other.length(); return length() >= other.length();
} }
[[nodiscard]] [[nodiscard("You must use array")]]
constexpr std::array<Type, 3> as_array() const noexcept constexpr std::array<Type, 3> as_array() const noexcept
{ {
return {this->x, this->y, z}; return {this->x, this->y, z};
@@ -293,7 +320,8 @@ namespace omath
template<> struct std::hash<omath::Vector3<float>> template<> struct std::hash<omath::Vector3<float>>
{ {
[[nodiscard]] // NOTE: Cannot be constexpr because of MSVC
[[nodiscard("You must use hash value")]]
std::size_t operator()(const omath::Vector3<float>& vec) const noexcept std::size_t operator()(const omath::Vector3<float>& vec) const noexcept
{ {
std::size_t hash = 0; std::size_t hash = 0;
@@ -310,14 +338,14 @@ template<> struct std::hash<omath::Vector3<float>>
template<class Type> template<class Type>
struct std::formatter<omath::Vector3<Type>> // NOLINT(*-dcl58-cpp) struct std::formatter<omath::Vector3<Type>> // NOLINT(*-dcl58-cpp)
{ {
[[nodiscard]] [[nodiscard("You must use parse iterator")]]
static constexpr auto parse(std::format_parse_context& ctx) static constexpr auto parse(std::format_parse_context& ctx)
{ {
return ctx.begin(); return ctx.begin();
} }
template<class FormatContext> template<class FormatContext>
[[nodiscard]] [[nodiscard("You must use format iterator")]]
static auto format(const omath::Vector3<Type>& vec, FormatContext& ctx) static auto format(const omath::Vector3<Type>& vec, FormatContext& ctx)
{ {
if constexpr (std::is_same_v<typename FormatContext::char_type, char>) if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
+31 -24
View File
@@ -24,19 +24,19 @@ namespace omath
template<class CastedType> template<class CastedType>
requires std::is_arithmetic_v<CastedType> requires std::is_arithmetic_v<CastedType>
[[nodiscard]] constexpr explicit operator Vector4<CastedType>() const noexcept [[nodiscard("You must use casted vector")]] constexpr explicit operator Vector4<CastedType>() const noexcept
{ {
return {static_cast<CastedType>(this->x), static_cast<CastedType>(this->y), return {static_cast<CastedType>(this->x), static_cast<CastedType>(this->y),
static_cast<CastedType>(this->z), static_cast<CastedType>(this->w)}; static_cast<CastedType>(this->z), static_cast<CastedType>(this->w)};
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
constexpr bool operator==(const Vector4& other) const noexcept constexpr bool operator==(const Vector4& other) const noexcept
{ {
return Vector3<Type>::operator==(other) && w == other.w; return Vector3<Type>::operator==(other) && w == other.w;
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
constexpr bool operator!=(const Vector4& other) const noexcept constexpr bool operator!=(const Vector4& other) const noexcept
{ {
return !(*this == other); return !(*this == other);
@@ -89,17 +89,19 @@ namespace omath
return *this; return *this;
} }
[[nodiscard]] constexpr Type length_sqr() const noexcept [[nodiscard("You must use squared length")]]
constexpr Type length_sqr() const noexcept
{ {
return Vector3<Type>::length_sqr() + w * w; return Vector3<Type>::length_sqr() + w * w;
} }
[[nodiscard]] constexpr Type dot(const Vector4& other) const noexcept [[nodiscard("You must use dot product")]]
constexpr Type dot(const Vector4& other) const noexcept
{ {
return Vector3<Type>::dot(other) + w * other.w; return Vector3<Type>::dot(other) + w * other.w;
} }
[[nodiscard]] Type length() const noexcept [[nodiscard("You must use length")]] Type length() const noexcept
{ {
return std::sqrt(length_sqr()); return std::sqrt(length_sqr());
} }
@@ -111,6 +113,11 @@ namespace omath
return *this; return *this;
} }
[[nodiscard("You must use absed vector")]]
constexpr Vector4 abs() const noexcept
{
return Vector4{*this}.abs();
}
constexpr Vector4& clamp(const Type& min, const Type& max) noexcept constexpr Vector4& clamp(const Type& min, const Type& max) noexcept
{ {
this->x = std::clamp(this->x, min, max); this->x = std::clamp(this->x, min, max);
@@ -120,86 +127,86 @@ namespace omath
return *this; return *this;
} }
[[nodiscard]] [[nodiscard("You must use negated vector")]]
constexpr Vector4 operator-() const noexcept constexpr Vector4 operator-() const noexcept
{ {
return {-this->x, -this->y, -this->z, -w}; return {-this->x, -this->y, -this->z, -w};
} }
[[nodiscard]] [[nodiscard("You must use result vector")]]
constexpr Vector4 operator+(const Vector4& other) const noexcept constexpr Vector4 operator+(const Vector4& other) const noexcept
{ {
return {this->x + other.x, this->y + other.y, this->z + other.z, w + other.w}; return {this->x + other.x, this->y + other.y, this->z + other.z, w + other.w};
} }
[[nodiscard]] [[nodiscard("You must use result vector")]]
constexpr Vector4 operator-(const Vector4& other) const noexcept constexpr Vector4 operator-(const Vector4& other) const noexcept
{ {
return {this->x - other.x, this->y - other.y, this->z - other.z, w - other.w}; return {this->x - other.x, this->y - other.y, this->z - other.z, w - other.w};
} }
[[nodiscard]] [[nodiscard("You must use result vector")]]
constexpr Vector4 operator*(const Type& value) const noexcept constexpr Vector4 operator*(const Type& value) const noexcept
{ {
return {this->x * value, this->y * value, this->z * value, w * value}; return {this->x * value, this->y * value, this->z * value, w * value};
} }
[[nodiscard]] [[nodiscard("You must use result vector")]]
constexpr Vector4 operator*(const Vector4& other) const noexcept constexpr Vector4 operator*(const Vector4& other) const noexcept
{ {
return {this->x * other.x, this->y * other.y, this->z * other.z, w * other.w}; return {this->x * other.x, this->y * other.y, this->z * other.z, w * other.w};
} }
[[nodiscard]] [[nodiscard("You must use result vector")]]
constexpr Vector4 operator/(const Type& value) const noexcept constexpr Vector4 operator/(const Type& value) const noexcept
{ {
return {this->x / value, this->y / value, this->z / value, w / value}; return {this->x / value, this->y / value, this->z / value, w / value};
} }
[[nodiscard]] [[nodiscard("You must use result vector")]]
constexpr Vector4 operator/(const Vector4& other) const noexcept constexpr Vector4 operator/(const Vector4& other) const noexcept
{ {
return {this->x / other.x, this->y / other.y, this->z / other.z, w / other.w}; return {this->x / other.x, this->y / other.y, this->z / other.z, w / other.w};
} }
[[nodiscard]] [[nodiscard("You must use sum of elements")]]
constexpr Type sum() const noexcept constexpr Type sum() const noexcept
{ {
return Vector3<Type>::sum() + w; return Vector3<Type>::sum() + w;
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator<(const Vector4& other) const noexcept bool operator<(const Vector4& other) const noexcept
{ {
return length() < other.length(); return length() < other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator>(const Vector4& other) const noexcept bool operator>(const Vector4& other) const noexcept
{ {
return length() > other.length(); return length() > other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator<=(const Vector4& other) const noexcept bool operator<=(const Vector4& other) const noexcept
{ {
return length() <= other.length(); return length() <= other.length();
} }
[[nodiscard]] [[nodiscard("You must use comparison result")]]
bool operator>=(const Vector4& other) const noexcept bool operator>=(const Vector4& other) const noexcept
{ {
return length() >= other.length(); return length() >= other.length();
} }
[[nodiscard]] [[nodiscard("You must use array")]]
constexpr std::array<Type, 4> as_array() const noexcept constexpr std::array<Type, 4> as_array() const noexcept
{ {
return {this->x, this->y, this->z, w}; return {this->x, this->y, this->z, w};
} }
#ifdef OMATH_IMGUI_INTEGRATION #ifdef OMATH_IMGUI_INTEGRATION
[[nodiscard]] [[nodiscard("You must use ImVec4")]]
constexpr ImVec4 to_im_vec4() const noexcept constexpr ImVec4 to_im_vec4() const noexcept
{ {
return { return {
@@ -209,7 +216,7 @@ namespace omath
static_cast<float>(w), static_cast<float>(w),
}; };
} }
[[nodiscard]] [[nodiscard("You must use vector from ImVec4")]]
static Vector4<float> from_im_vec4(const ImVec4& other) noexcept static Vector4<float> from_im_vec4(const ImVec4& other) noexcept
{ {
return {static_cast<Type>(other.x), static_cast<Type>(other.y), static_cast<Type>(other.z)}; return {static_cast<Type>(other.x), static_cast<Type>(other.y), static_cast<Type>(other.z)};
@@ -220,7 +227,7 @@ namespace omath
template<> struct std::hash<omath::Vector4<float>> template<> struct std::hash<omath::Vector4<float>>
{ {
[[nodiscard]] [[nodiscard("You must use hash value")]]
std::size_t operator()(const omath::Vector4<float>& vec) const noexcept std::size_t operator()(const omath::Vector4<float>& vec) const noexcept
{ {
std::size_t hash = 0; std::size_t hash = 0;
@@ -237,13 +244,13 @@ template<> struct std::hash<omath::Vector4<float>>
template<class Type> template<class Type>
struct std::formatter<omath::Vector4<Type>> // NOLINT(*-dcl58-cpp) struct std::formatter<omath::Vector4<Type>> // NOLINT(*-dcl58-cpp)
{ {
[[nodiscard]] [[nodiscard("You must use parse iterator")]]
static constexpr auto parse(std::format_parse_context& ctx) static constexpr auto parse(std::format_parse_context& ctx)
{ {
return ctx.begin(); return ctx.begin();
} }
template<class FormatContext> template<class FormatContext>
[[nodiscard]] [[nodiscard("You must use format iterator")]]
static auto format(const omath::Vector4<Type>& vec, FormatContext& ctx) static auto format(const omath::Vector4<Type>& vec, FormatContext& ctx)
{ {
if constexpr (std::is_same_v<typename FormatContext::char_type, char>) if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
+5
View File
@@ -15,8 +15,13 @@ namespace omath::lua
static void register_vec2(sol::table& omath_table); static void register_vec2(sol::table& omath_table);
static void register_vec3(sol::table& omath_table); static void register_vec3(sol::table& omath_table);
static void register_vec4(sol::table& omath_table); static void register_vec4(sol::table& omath_table);
static void register_matrices(sol::table& omath_table);
static void register_quaternion(sol::table& omath_table);
static void register_color(sol::table& omath_table); static void register_color(sol::table& omath_table);
static void register_hud(sol::table& omath_table);
static void register_triangle(sol::table& omath_table); static void register_triangle(sol::table& omath_table);
static void register_3d_primitives(sol::table& omath_table);
static void register_collision(sol::table& omath_table);
static void register_shared_types(sol::table& omath_table); static void register_shared_types(sol::table& omath_table);
static void register_engines(sol::table& omath_table); static void register_engines(sol::table& omath_table);
static void register_pattern_scan(sol::table& omath_table); static void register_pattern_scan(sol::table& omath_table);
+6
View File
@@ -87,6 +87,12 @@
#include "omath/engines/frostbite_engine/traits/camera_trait.hpp" #include "omath/engines/frostbite_engine/traits/camera_trait.hpp"
#include "omath/engines/frostbite_engine/traits/pred_engine_trait.hpp" #include "omath/engines/frostbite_engine/traits/pred_engine_trait.hpp"
// RAGE Engine
#include "omath/engines/rage_engine/constants.hpp"
#include "omath/engines/rage_engine/formulas.hpp"
#include "omath/engines/rage_engine/camera.hpp"
#include "omath/engines/rage_engine/traits/camera_trait.hpp"
#include "omath/engines/rage_engine/traits/pred_engine_trait.hpp"
// Unreal Engine // Unreal Engine
#include "omath/engines/unreal_engine/constants.hpp" #include "omath/engines/unreal_engine/constants.hpp"
+206 -108
View File
@@ -5,6 +5,8 @@
#pragma once #pragma once
#include "omath/3d_primitives/aabb.hpp" #include "omath/3d_primitives/aabb.hpp"
#include "omath/3d_primitives/obb.hpp"
#include "omath/internal/constexpr_math.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"
@@ -31,7 +33,7 @@ namespace omath::projection
float m_width; float m_width;
float m_height; float m_height;
[[nodiscard]] constexpr float aspect_ratio() const [[nodiscard("You must use aspect ratio")]] constexpr float aspect_ratio() const
{ {
return m_width / m_height; return m_width / m_height;
} }
@@ -83,8 +85,9 @@ namespace omath::projection
}; };
~Camera() = default; ~Camera() = default;
Camera(const Vector3<NumericType>& position, const ViewAnglesType& view_angles, const ViewPort& view_port, constexpr Camera(const Vector3<NumericType>& position, const ViewAnglesType& view_angles,
const FieldOfView& fov, const NumericType near, const NumericType far) noexcept const ViewPort& view_port, 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)
{ {
@@ -100,18 +103,19 @@ namespace omath::projection
// built by any of the engine traits. Both variants (ZERO_TO_ONE and // 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 // NEGATIVE_ONE_TO_ONE) share the same m[0,0]/m[1,1] layout, so this works
// regardless of the NDC depth range. // regardless of the NDC depth range.
[[nodiscard]] [[nodiscard("You must use extracted projection params")]]
static ProjectionParams extract_projection_params(const Mat4X4Type& proj_matrix) noexcept constexpr static ProjectionParams extract_projection_params(const Mat4X4Type& proj_matrix) noexcept
{ {
// m[1,1] == 1 / tan(fov/2) => fov = 2 * atan(1 / m[1,1]) // m[1,1] == 1 / tan(fov/2) => fov = 2 * atan(1 / m[1,1])
const auto f = proj_matrix.at(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] // 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)), const auto fov_radians = NumericType{2} * internal::atan(NumericType{1} / f);
return {FieldOfView::from_radians(static_cast<typename FieldOfView::ArithmeticType>(fov_radians)),
f / proj_matrix.at(0, 0)}; f / proj_matrix.at(0, 0)};
} }
[[nodiscard]] [[nodiscard("You must use calculated view angles")]]
static ViewAnglesType calc_view_angles_from_view_matrix(const Mat4X4Type& view_matrix) noexcept constexpr 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]}; Vector3<NumericType> forward_vector = {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]};
if constexpr (axes.inverted_forward) if constexpr (axes.inverted_forward)
@@ -119,8 +123,8 @@ namespace omath::projection
return TraitClass::calc_look_at_angle({}, forward_vector); return TraitClass::calc_look_at_angle({}, forward_vector);
} }
[[nodiscard]] [[nodiscard("You must use calculated origin")]]
static Vector3<NumericType> calc_origin_from_view_matrix(const Mat4X4Type& view_matrix) noexcept constexpr 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. // The view matrix is R * T(-origin), so the last column stores t = -R * origin.
// Recovering origin: origin = -R^T * t // Recovering origin: origin = -R^T * t
@@ -134,61 +138,99 @@ namespace omath::projection
}; };
} }
void look_at(const Vector3<NumericType>& target) constexpr 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]] [[nodiscard("You must use calculated look-at angles")]]
ViewAnglesType calc_look_at_angles(const Vector3<NumericType>& look_to) const constexpr ViewAnglesType calc_look_at_angles(const Vector3<NumericType>& look_to) const
{ {
return TraitClass::calc_look_at_angle(m_origin, look_to); return TraitClass::calc_look_at_angle(m_origin, look_to);
} }
[[nodiscard]] [[nodiscard("You must use forward vector")]]
Vector3<NumericType> get_forward() const noexcept constexpr Vector3<NumericType> get_forward() const noexcept
{ {
if consteval
{
const auto view_matrix = calc_view_matrix();
return {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]};
}
const auto& view_matrix = get_view_matrix(); const auto& view_matrix = get_view_matrix();
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("You must use right vector")]]
Vector3<NumericType> get_right() const noexcept constexpr Vector3<NumericType> get_right() const noexcept
{ {
if consteval
{
const auto view_matrix = calc_view_matrix();
return {view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]};
}
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("You must use up vector")]]
Vector3<NumericType> get_up() const noexcept constexpr Vector3<NumericType> get_up() const noexcept
{ {
if consteval
{
const auto view_matrix = calc_view_matrix();
return {view_matrix[1, 0], view_matrix[1, 1], view_matrix[1, 2]};
}
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]] [[nodiscard("You must use absolute forward vector")]]
Vector3<NumericType> get_abs_forward() const noexcept constexpr Vector3<NumericType> get_abs_forward() const noexcept
{ {
if constexpr (axes.inverted_forward) if constexpr (axes.inverted_forward)
return -get_forward(); return -get_forward();
return get_forward(); return get_forward();
} }
[[nodiscard]] [[nodiscard("You must use absolute right vector")]]
Vector3<NumericType> get_abs_right() const noexcept constexpr Vector3<NumericType> get_abs_right() const noexcept
{ {
if constexpr (axes.inverted_right) if constexpr (axes.inverted_right)
return -get_right(); return -get_right();
return get_right(); return get_right();
} }
[[nodiscard]] [[nodiscard("You must use absolute up vector")]]
Vector3<NumericType> get_abs_up() const noexcept constexpr Vector3<NumericType> get_abs_up() const noexcept
{ {
return get_up(); return get_up();
} }
[[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const noexcept [[nodiscard("You must use calculated view-projection matrix")]]
constexpr Mat4X4Type calc_view_projection_matrix() const noexcept
{
return calc_projection_matrix() * calc_view_matrix();
}
[[nodiscard("You must use calculated view matrix")]]
constexpr Mat4X4Type calc_view_matrix() const noexcept
{
return TraitClass::calc_view_matrix(m_view_angles, m_origin);
}
[[nodiscard("You must use calculated projection matrix")]]
constexpr Mat4X4Type calc_projection_matrix() const noexcept
{
return TraitClass::calc_projection_matrix(m_field_of_view, m_view_port, m_near_plane_distance,
m_far_plane_distance, depth_range);
}
[[nodiscard("You must use view-projection matrix")]]
constexpr const Mat4X4Type& get_view_projection_matrix() const noexcept
{ {
if (!m_view_projection_matrix.has_value()) if (!m_view_projection_matrix.has_value())
m_view_projection_matrix = get_projection_matrix() * get_view_matrix(); m_view_projection_matrix = get_projection_matrix() * get_view_matrix();
@@ -196,90 +238,90 @@ namespace omath::projection
return m_view_projection_matrix.value(); return m_view_projection_matrix.value();
} }
[[nodiscard]] const Mat4X4Type& get_view_matrix() const noexcept [[nodiscard("You must use view matrix")]] constexpr const Mat4X4Type& get_view_matrix() const noexcept
{ {
if (!m_view_matrix.has_value()) if (!m_view_matrix.has_value())
m_view_matrix = TraitClass::calc_view_matrix(m_view_angles, m_origin); m_view_matrix = calc_view_matrix();
return m_view_matrix.value(); return m_view_matrix.value();
} }
[[nodiscard]] const Mat4X4Type& get_projection_matrix() const noexcept [[nodiscard("You must use projection matrix")]] constexpr 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_projection_matrix = calc_projection_matrix();
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();
} }
void set_field_of_view(const FieldOfView& fov) noexcept constexpr void set_field_of_view(const FieldOfView& fov) noexcept
{ {
m_field_of_view = fov; m_field_of_view = fov;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_projection_matrix = std::nullopt; m_projection_matrix = std::nullopt;
} }
void set_near_plane(const NumericType near_plane) noexcept constexpr void set_near_plane(const NumericType near_plane) noexcept
{ {
m_near_plane_distance = near_plane; 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 NumericType far_plane) noexcept constexpr void set_far_plane(const NumericType far_plane) noexcept
{ {
m_far_plane_distance = far_plane; 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;
} }
void set_view_angles(const ViewAnglesType& view_angles) noexcept constexpr void set_view_angles(const ViewAnglesType& view_angles) noexcept
{ {
m_view_angles = view_angles; m_view_angles = view_angles;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_view_matrix = std::nullopt; m_view_matrix = std::nullopt;
} }
void set_origin(const Vector3<NumericType>& origin) noexcept constexpr 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;
m_view_matrix = std::nullopt; m_view_matrix = std::nullopt;
} }
void set_view_port(const ViewPort& view_port) noexcept constexpr void set_view_port(const ViewPort& view_port) noexcept
{ {
m_view_port = view_port; m_view_port = view_port;
m_view_projection_matrix = std::nullopt; m_view_projection_matrix = std::nullopt;
m_projection_matrix = std::nullopt; m_projection_matrix = std::nullopt;
} }
[[nodiscard]] const FieldOfView& get_field_of_view() const noexcept [[nodiscard("You must use field of view")]] constexpr const FieldOfView& get_field_of_view() const noexcept
{ {
return m_field_of_view; return m_field_of_view;
} }
[[nodiscard]] const NumericType& get_near_plane() const noexcept [[nodiscard("You must use near plane")]] constexpr const NumericType& get_near_plane() const noexcept
{ {
return m_near_plane_distance; return m_near_plane_distance;
} }
[[nodiscard]] const NumericType& get_far_plane() const noexcept [[nodiscard("You must use far plane")]] constexpr const NumericType& get_far_plane() const noexcept
{ {
return m_far_plane_distance; return m_far_plane_distance;
} }
[[nodiscard]] const ViewAnglesType& get_view_angles() const noexcept [[nodiscard("You must use view angles")]] constexpr const ViewAnglesType& get_view_angles() const noexcept
{ {
return m_view_angles; return m_view_angles;
} }
[[nodiscard]] const Vector3<NumericType>& get_origin() const noexcept [[nodiscard("You must use origin")]] constexpr 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<NumericType>, Error> [[nodiscard("You must use screen position")]] constexpr std::expected<Vector3<NumericType>, Error>
world_to_screen(const Vector3<NumericType>& 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);
@@ -295,7 +337,7 @@ namespace omath::projection
std::unreachable(); std::unreachable();
} }
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER> template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
[[nodiscard]] std::expected<Vector3<NumericType>, Error> [[nodiscard("You must use unclipped screen position")]] constexpr std::expected<Vector3<NumericType>, Error>
world_to_screen_unclipped(const Vector3<NumericType>& world_position) const noexcept world_to_screen_unclipped(const Vector3<NumericType>& world_position) const noexcept
{ {
const auto normalized_cords = world_to_view_port(world_position, ViewPortClipping::MANUAL); const auto normalized_cords = world_to_view_port(world_position, ViewPortClipping::MANUAL);
@@ -311,7 +353,8 @@ namespace omath::projection
std::unreachable(); std::unreachable();
} }
[[nodiscard]] bool is_culled_by_frustum(const Triangle<Vector3<NumericType>>& triangle) const noexcept [[nodiscard("You must use frustum culling result")]] constexpr 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<NumericType>& point) auto to_clip = [this](const Vector3<NumericType>& point)
@@ -378,51 +421,12 @@ namespace omath::projection
return false; return false;
} }
[[nodiscard]] bool is_aabb_culled_by_frustum(const primitives::Aabb<NumericType>& aabb) const noexcept [[nodiscard("You must use AABB frustum culling result")]] constexpr bool
is_aabb_culled_by_frustum(const primitives::Aabb<NumericType>& aabb) const noexcept
{ {
const auto& m = get_view_projection_matrix();
// Gribb-Hartmann: extract 6 frustum planes from the view-projection matrix.
// Each plane is (a, b, c, d) such that ax + by + cz + d >= 0 means inside.
// For a 4x4 matrix with rows r0..r3:
// Left = r3 + r0
// Right = r3 - r0
// Bottom = r3 + r1
// Top = r3 - r1
// Near = r3 + r2 ([-1,1]) or r2 ([0,1])
// Far = r3 - r2
struct Plane final
{
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 // 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. // (the "positive vertex"). If it's outside, the entire AABB is outside.
for (const auto& [a, b, c, d] : planes) for (const auto& [a, b, c, d] : extract_frustum_planes())
{ {
const auto px = a >= NumericType{0} ? aabb.max.x : aabb.min.x; 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 py = b >= NumericType{0} ? aabb.max.y : aabb.min.y;
@@ -435,13 +439,33 @@ namespace omath::projection
return false; return false;
} }
[[nodiscard]] std::expected<Vector3<NumericType>, Error> [[nodiscard("You must use OBB frustum culling result")]] constexpr bool
is_obb_culled_by_frustum(const primitives::Obb<NumericType>& obb) const noexcept
{
// For each plane, project the OBB extents onto the plane normal to get the
// effective radius, then test the center's signed distance against it.
for (const auto& [a, b, c, d] : extract_frustum_planes())
{
const Vector3<NumericType> normal{a, b, c};
const auto center_distance = normal.dot(obb.center) + d;
const auto radius = obb.half_extents.x * internal::abs(normal.dot(obb.axis_x))
+ obb.half_extents.y * internal::abs(normal.dot(obb.axis_y))
+ obb.half_extents.z * internal::abs(normal.dot(obb.axis_z));
if (center_distance + radius < NumericType{0})
return true;
}
return false;
}
[[nodiscard("You must use view port position")]] constexpr std::expected<Vector3<NumericType>, Error>
world_to_view_port(const Vector3<NumericType>& world_position, world_to_view_port(const Vector3<NumericType>& world_position,
const ViewPortClipping& clipping = ViewPortClipping::AUTO) const noexcept const ViewPortClipping& clipping = ViewPortClipping::AUTO) const noexcept
{ {
auto projected = get_view_projection_matrix() auto project_to_view_port = [&clipping](auto projected) -> std::expected<Vector3<NumericType>, Error>
* 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);
constexpr auto eps = std::numeric_limits<NumericType>::epsilon(); constexpr auto eps = std::numeric_limits<NumericType>::epsilon();
if (w <= eps) if (w <= eps)
@@ -450,7 +474,8 @@ namespace omath::projection
projected /= w; projected /= w;
// ReSharper disable once CppTooWideScope // ReSharper disable once CppTooWideScope
const auto clipped_automatically = clipping == ViewPortClipping::AUTO && is_ndc_out_of_bounds(projected); const auto clipped_automatically =
clipping == ViewPortClipping::AUTO && is_ndc_out_of_bounds(projected);
if (clipped_automatically) 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);
@@ -463,40 +488,65 @@ namespace omath::projection
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); 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)}; return Vector3<NumericType>{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)};
} };
[[nodiscard]]
std::expected<Vector3<NumericType>, Error> view_port_to_world(const Vector3<NumericType>& ndc) const noexcept if consteval
{ {
const auto inv_view_proj = get_view_projection_matrix().inverted(); auto projected =
calc_view_projection_matrix()
* mat_column_from_vector<NumericType, Mat4X4Type::get_store_ordering()>(world_position);
return project_to_view_port(projected);
}
auto projected = get_view_projection_matrix()
* mat_column_from_vector<NumericType, Mat4X4Type::get_store_ordering()>(world_position);
return project_to_view_port(projected);
}
[[nodiscard("You must use world position")]]
constexpr std::expected<Vector3<NumericType>, Error>
view_port_to_world(const Vector3<NumericType>& ndc) const noexcept
{
auto view_port_to_world =
[&ndc](const Mat4X4Type& view_projection) -> std::expected<Vector3<NumericType>, Error>
{
const auto inv_view_proj = view_projection.inverted();
if (!inv_view_proj) if (!inv_view_proj)
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()
inv_view_proj.value() * mat_column_from_vector<NumericType, Mat4X4Type::get_store_ordering()>(ndc); * 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<NumericType>::epsilon()) if (internal::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<NumericType>{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)};
};
if consteval
{
return view_port_to_world(calc_view_projection_matrix());
}
return view_port_to_world(get_view_projection_matrix());
} }
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER> template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
[[nodiscard]] [[nodiscard("You must use world position")]]
std::expected<Vector3<NumericType>, Error> constexpr std::expected<Vector3<NumericType>, Error>
screen_to_world(const Vector3<NumericType>& screen_pos) const noexcept screen_to_world(const Vector3<NumericType>& screen_pos) const noexcept
{ {
return view_port_to_world(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("You must use world position")]]
std::expected<Vector3<NumericType>, Error> constexpr std::expected<Vector3<NumericType>, Error>
screen_to_world(const Vector2<NumericType>& screen_pos) const noexcept screen_to_world(const Vector2<NumericType>& screen_pos) const noexcept
{ {
const auto& [x, y] = screen_pos; const auto& [x, y] = screen_pos;
@@ -517,8 +567,55 @@ namespace omath::projection
Vector3<NumericType> m_origin; Vector3<NumericType> m_origin;
private: private:
struct FrustumPlane final
{
NumericType a, b, c, d;
};
// 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
[[nodiscard("You must use frustum planes")]] constexpr std::array<FrustumPlane, 6>
extract_frustum_planes() const noexcept
{
const auto& m = get_view_projection_matrix();
const auto extract_plane = [&m](const int sign, const int row) -> FrustumPlane
{
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<FrustumPlane, 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);
return planes;
}
template<class Type> template<class Type>
[[nodiscard]] constexpr static bool is_ndc_out_of_bounds(const Type& ndc) noexcept [[nodiscard("You must use NDC bounds check result")]] constexpr static bool
is_ndc_out_of_bounds(const Type& ndc) noexcept
{ {
constexpr auto eps = std::numeric_limits<NumericType>::epsilon(); constexpr auto eps = std::numeric_limits<NumericType>::epsilon();
@@ -531,7 +628,7 @@ namespace omath::projection
return is_ndc_z_value_out_of_bounds(data[2]); return is_ndc_z_value_out_of_bounds(data[2]);
} }
template<class ZType> template<class ZType>
[[nodiscard]] [[nodiscard("You must use NDC z bounds check result")]]
constexpr static bool is_ndc_z_value_out_of_bounds(const ZType& z_ndc) noexcept constexpr static bool is_ndc_z_value_out_of_bounds(const ZType& z_ndc) noexcept
{ {
constexpr auto eps = std::numeric_limits<NumericType>::epsilon(); constexpr auto eps = std::numeric_limits<NumericType>::epsilon();
@@ -557,7 +654,7 @@ namespace omath::projection
v v
*/ */
[[nodiscard]] Vector3<NumericType> [[nodiscard("You must use screen position")]] constexpr Vector3<NumericType>
ndc_to_screen_position_from_top_left_corner(const Vector3<NumericType>& ndc) const noexcept ndc_to_screen_position_from_top_left_corner(const Vector3<NumericType>& ndc) const noexcept
{ {
/* /*
@@ -575,7 +672,7 @@ namespace omath::projection
(ndc.y / -NumericType{2} + NumericType{0.5}) * m_view_port.m_height, ndc.z}; (ndc.y / -NumericType{2} + NumericType{0.5}) * m_view_port.m_height, ndc.z};
} }
[[nodiscard]] Vector3<NumericType> [[nodiscard("You must use screen position")]] constexpr Vector3<NumericType>
ndc_to_screen_position_from_bottom_left_corner(const Vector3<NumericType>& ndc) const noexcept ndc_to_screen_position_from_bottom_left_corner(const Vector3<NumericType>& ndc) const noexcept
{ {
/* /*
@@ -594,7 +691,8 @@ namespace omath::projection
} }
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER> template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
[[nodiscard]] Vector3<NumericType> screen_to_ndc(const Vector3<NumericType>& screen_pos) const noexcept [[nodiscard("You must use NDC position")]] constexpr 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 * NumericType{2} - NumericType{1}, return {screen_pos.x / m_view_port.m_width * NumericType{2} - NumericType{1},
+10 -9
View File
@@ -3,6 +3,7 @@
// //
#pragma once #pragma once
#include "omath/internal/constexpr_math.hpp"
#include "omath/trigonometry/angles.hpp" #include "omath/trigonometry/angles.hpp"
#include <algorithm> #include <algorithm>
#include <format> #include <format>
@@ -70,31 +71,31 @@ namespace omath
} }
[[nodiscard]] [[nodiscard]]
Type sin() const noexcept constexpr Type sin() const noexcept
{ {
return std::sin(as_radians()); return internal::sin(as_radians());
} }
[[nodiscard]] [[nodiscard]]
Type cos() const noexcept constexpr Type cos() const noexcept
{ {
return std::cos(as_radians()); return internal::cos(as_radians());
} }
[[nodiscard]] [[nodiscard]]
Type tan() const noexcept constexpr Type tan() const noexcept
{ {
return std::tan(as_radians()); return internal::tan(as_radians());
} }
[[nodiscard]] [[nodiscard]]
Type atan() const noexcept constexpr Type atan() const noexcept
{ {
return std::atan(as_radians()); return internal::atan(as_radians());
} }
[[nodiscard]] [[nodiscard]]
Type cot() const noexcept constexpr Type cot() const noexcept
{ {
return cos() / sin(); return cos() / sin();
} }
+8 -6
View File
@@ -3,6 +3,7 @@
// //
#pragma once #pragma once
#include "omath/internal/constexpr_math.hpp"
#include <cmath> #include <cmath>
#include <numbers> #include <numbers>
@@ -24,37 +25,38 @@ namespace omath::angles
template<class Type> template<class Type>
requires std::is_floating_point_v<Type> requires std::is_floating_point_v<Type>
[[nodiscard]] Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) noexcept [[nodiscard]] constexpr Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) noexcept
{ {
const auto fov_rad = degrees_to_radians(horizontal_fov); const auto fov_rad = degrees_to_radians(horizontal_fov);
const auto vert_fov = static_cast<Type>(2) * std::atan(std::tan(fov_rad / static_cast<Type>(2)) / aspect); const auto vert_fov =
static_cast<Type>(2) * internal::atan(internal::tan(fov_rad / static_cast<Type>(2)) / aspect);
return radians_to_degrees(vert_fov); return radians_to_degrees(vert_fov);
} }
template<class Type> template<class Type>
requires std::is_floating_point_v<Type> requires std::is_floating_point_v<Type>
[[nodiscard]] Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) noexcept [[nodiscard]] constexpr Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) noexcept
{ {
const auto fov_as_radians = degrees_to_radians(vertical_fov); const auto fov_as_radians = degrees_to_radians(vertical_fov);
const auto horizontal_fov = const auto horizontal_fov =
static_cast<Type>(2) * std::atan(std::tan(fov_as_radians / static_cast<Type>(2)) * aspect); static_cast<Type>(2) * internal::atan(internal::tan(fov_as_radians / static_cast<Type>(2)) * aspect);
return radians_to_degrees(horizontal_fov); return radians_to_degrees(horizontal_fov);
} }
template<class Type> template<class Type>
requires std::is_arithmetic_v<Type> requires std::is_arithmetic_v<Type>
[[nodiscard]] Type wrap_angle(const Type& angle, const Type& min, const Type& max) noexcept [[nodiscard]] constexpr Type wrap_angle(const Type& angle, const Type& min, const Type& max) noexcept
{ {
if (angle <= max && angle >= min) if (angle <= max && angle >= min)
return angle; return angle;
const Type range = max - min; const Type range = max - min;
Type wrapped_angle = std::fmod(angle - min, range); Type wrapped_angle = internal::fmod(angle - min, range);
if (wrapped_angle < 0) if (wrapped_angle < 0)
wrapped_angle += range; wrapped_angle += range;
+1 -1
View File
@@ -18,7 +18,7 @@ namespace omath
RollType roll; RollType roll;
[[nodiscard]] [[nodiscard]]
Vector3<ArithmeticType> as_vector3() const constexpr Vector3<ArithmeticType> as_vector3() const
{ {
return {pitch.as_degrees(), yaw.as_degrees(), roll.as_degrees()}; return {pitch.as_degrees(), yaw.as_degrees(), roll.as_degrees()};
} }
+62 -1
View File
@@ -2,12 +2,13 @@
// Created by Vladislav on 30.12.2025. // Created by Vladislav on 30.12.2025.
// //
#pragma once #pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include "section_scan_result.hpp"
namespace omath namespace omath
{ {
class ElfPatternScanner final class ElfPatternScanner final
@@ -18,14 +19,74 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern, scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
}; };
} // namespace omath } // namespace omath
+62 -1
View File
@@ -2,12 +2,13 @@
// Created by Copilot on 04.02.2026. // Created by Copilot on 04.02.2026.
// //
#pragma once #pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include "section_scan_result.hpp"
namespace omath namespace omath
{ {
class MachOPatternScanner final class MachOPatternScanner final
@@ -18,14 +19,74 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = "__text"); const std::string_view& target_section_name = "__text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = "__text"); const std::string_view& target_section_name = "__text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern, scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = "__text"); const std::string_view& target_section_name = "__text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
}; };
} // namespace omath } // namespace omath
+128 -5
View File
@@ -3,9 +3,12 @@
// //
#pragma once #pragma once
#include <algorithm>
#include <array>
#include <expected> #include <expected>
#include <optional> #include <optional>
#include <span> #include <span>
#include <stdexcept>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -15,6 +18,8 @@ class unit_test_pattern_scan_corner_case_1_Test;
class unit_test_pattern_scan_corner_case_2_Test; class unit_test_pattern_scan_corner_case_2_Test;
class unit_test_pattern_scan_corner_case_3_Test; class unit_test_pattern_scan_corner_case_3_Test;
class unit_test_pattern_scan_corner_case_4_Test; class unit_test_pattern_scan_corner_case_4_Test;
class unit_test_pattern_scan_consteval_read_test_Test;
class unit_test_pattern_scan_consteval_spacing_and_case_Test;
// ReSharper restore CppInconsistentNaming // ReSharper restore CppInconsistentNaming
namespace omath namespace omath
{ {
@@ -29,8 +34,22 @@ namespace omath
friend unit_test_pattern_scan_corner_case_2_Test; friend unit_test_pattern_scan_corner_case_2_Test;
friend unit_test_pattern_scan_corner_case_3_Test; friend unit_test_pattern_scan_corner_case_3_Test;
friend unit_test_pattern_scan_corner_case_4_Test; friend unit_test_pattern_scan_corner_case_4_Test;
friend unit_test_pattern_scan_consteval_read_test_Test;
friend unit_test_pattern_scan_consteval_spacing_and_case_Test;
public: public:
template<std::size_t N>
struct ConstevalPattern final
{
char value[N]{};
// ReSharper disable once CppNonExplicitConvertingConstructor
constexpr ConstevalPattern(const char (&text)[N]) // NOLINT(*-explicit-constructor)
{
std::ranges::copy(text, value);
}
};
[[nodiscard]] [[nodiscard]]
static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& range, static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& range,
const std::string_view& pattern); const std::string_view& pattern);
@@ -49,9 +68,26 @@ namespace omath
if (!parsed_pattern) [[unlikely]] if (!parsed_pattern) [[unlikely]]
return end; return end;
return scan_for_parsed_pattern(begin, end, parsed_pattern.value());
}
template<ConstevalPattern Pattern, class IteratorType>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_pattern(const IteratorType& begin, const IteratorType& end)
{
constexpr auto parsed_pattern = parse_pattern<Pattern>();
return scan_for_parsed_pattern(begin, end, parsed_pattern);
}
private:
template<class IteratorType, class ParsedPattern>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_parsed_pattern(const IteratorType& begin, const IteratorType& end,
const ParsedPattern& parsed_pattern)
{
const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end)); const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end));
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern->size()); const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern.size());
const std::ptrdiff_t scan_size = whole_range_size - pattern_size; const std::ptrdiff_t scan_size = whole_range_size - pattern_size;
if (scan_size < 0) if (scan_size < 0)
@@ -61,9 +97,9 @@ namespace omath
{ {
bool found = true; bool found = true;
for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern->size()); j++) for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern.size()); j++)
{ {
found = parsed_pattern->at(j) == std::nullopt || parsed_pattern->at(j) == *(begin + i + j); found = parsed_pattern.at(j) == std::nullopt || parsed_pattern.at(j) == *(begin + i + j);
if (!found) if (!found)
break; break;
@@ -73,10 +109,97 @@ namespace omath
} }
return end; return end;
} }
private:
[[nodiscard]] [[nodiscard]]
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError> static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
parse_pattern(const std::string_view& pattern_string); parse_pattern(const std::string_view& pattern_string);
[[nodiscard]]
constexpr static bool is_space(const char c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
[[nodiscard]]
constexpr static int hex_value(const char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
template<ConstevalPattern Pattern>
[[nodiscard]]
static consteval std::size_t signature_size()
{
std::size_t count = 0;
bool in_token = false;
for (std::size_t i = 0; i + 1 < sizeof(Pattern.value); ++i)
{
if (is_space(Pattern.value[i]))
{
in_token = false;
}
else if (!in_token)
{
++count;
in_token = true;
}
}
return count;
}
template<ConstevalPattern Pattern>
static consteval std::array<std::optional<std::byte>, signature_size<Pattern>()> parse_pattern()
{
std::array<std::optional<std::byte>, signature_size<Pattern>()> result{};
std::size_t out = 0;
std::size_t i = 0;
while (i + 1 < sizeof(Pattern.value))
{
while (i + 1 < sizeof(Pattern.value) && is_space(Pattern.value[i]))
++i;
const std::size_t token_start = i;
while (i + 1 < sizeof(Pattern.value) && !is_space(Pattern.value[i]))
++i;
const std::size_t token_size = i - token_start;
if (token_size == 0)
continue;
// ReSharper disable once CppTooWideScope
const bool is_wildcard = (token_size == 1 || token_size == 2) && Pattern.value[token_start] == '?';
if (is_wildcard)
{
if (token_size == 2 && Pattern.value[token_start + 1] != '?')
throw std::logic_error("invalid wildcard token");
result[out++] = std::nullopt;
continue;
}
if (token_size != 2)
throw std::logic_error("invalid byte token");
const int high = hex_value(Pattern.value[token_start]);
const int low = hex_value(Pattern.value[token_start + 1]);
if (high < 0 || low < 0)
throw std::logic_error("invalid hex byte");
result[out++] = static_cast<std::byte>((high << 4) | low);
}
return result;
}
}; };
} // namespace omath } // namespace omath
+62 -1
View File
@@ -3,12 +3,13 @@
// //
#pragma once #pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include "section_scan_result.hpp"
namespace omath namespace omath
{ {
@@ -20,14 +21,74 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern, scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
}; };
} // namespace omath } // namespace omath
+59
View File
@@ -0,0 +1,59 @@
module;
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <filesystem>
#include <format>
#include <functional>
#include <initializer_list>
#include <iomanip>
#include <iterator>
#include <limits>
#include <memory>
#include <memory_resource>
#include <numbers>
#include <numeric>
#include <optional>
#include <queue>
#include <ranges>
#include <span>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
#if defined(OMATH_USE_AVX2)
#include <immintrin.h>
#endif
#if defined(OMATH_IMGUI_INTEGRATION)
#include <imgui.h>
#endif
#if defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#elif defined(__APPLE__)
#include <mach-o/dyld.h>
#elif defined(__linux__) || defined(__unix__)
#include <link.h>
#endif
export module omath;
export {
#include "omath/omath.hpp"
}
-49
View File
@@ -1,49 +0,0 @@
//
// Created by Vlad on 3/22/2025.
//
#include "omath/engines/cry_engine/formulas.hpp"
namespace omath::cry_engine
{
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.yaw)
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.roll)
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near, far);
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
@@ -1,27 +0,0 @@
//
// Created by Vlad on 8/11/2025.
//
#include "omath/engines/cry_engine/traits/camera_trait.hpp"
namespace omath::cry_engine
{
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(std::asin(direction.z)),
YawAngle::from_radians(-std::atan2(direction.x, direction.y)), RollAngle::from_radians(0.f)};
}
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return cry_engine::calc_view_matrix(angles, cam_origin);
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::unity_engine
@@ -1,50 +0,0 @@
//
// Created by Vlad on 3/22/2025.
//
#include "omath/engines/frostbite_engine/formulas.hpp"
namespace omath::frostbite_engine
{
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.roll)
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.yaw)
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near, far);
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
@@ -1,27 +0,0 @@
//
// Created by Vlad on 8/11/2025.
//
#include "omath/engines/frostbite_engine/traits/camera_trait.hpp"
namespace omath::frostbite_engine
{
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(-std::asin(direction.y)),
YawAngle::from_radians(std::atan2(direction.x, direction.z)), RollAngle::from_radians(0.f)};
}
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return frostbite_engine::calc_view_matrix(angles, cam_origin);
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::unity_engine
-61
View File
@@ -1,61 +0,0 @@
//
// Created by Vlad on 3/19/2025.
//
#include "omath/engines/iw_engine/formulas.hpp"
namespace omath::iw_engine
{
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll);
}
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view(forward_vector(angles), right_vector(angles), up_vector(angles), cam_origin);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
// InfinityWard Engine (inherited from Quake) stores FOV as horizontal FOV at a 4:3
// 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));
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed<
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
vfov_deg, 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>(
vfov_deg, aspect_ratio, near, far);
std::unreachable();
};
} // namespace omath::iw_engine
@@ -1,27 +0,0 @@
//
// Created by Vlad on 8/11/2025.
//
#include "omath/engines/iw_engine/traits/camera_trait.hpp"
#include "omath/engines/iw_engine/formulas.hpp"
namespace omath::iw_engine
{
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(-std::asin(direction.z)),
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
{
return iw_engine::calc_view_matrix(angles, cam_origin);
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::iw_engine
-52
View File
@@ -1,52 +0,0 @@
//
// Created by Vlad on 3/19/2025.
//
#include "omath/engines/opengl_engine/formulas.hpp"
namespace omath::opengl_engine
{
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
const auto vec =
rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_forward);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
const auto vec =
rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_right);
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
}
Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
const auto vec = rotation_matrix(angles) * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>(k_abs_up);
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
{
return mat_look_at_right_handed(cam_origin, cam_origin + forward_vector(angles), up_vector(angles));
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::COLUMN_MAJOR>(angles.roll)
* mat_rotation_axis_y<float, MatStoreType::COLUMN_MAJOR>(angles.yaw)
* mat_rotation_axis_x<float, MatStoreType::COLUMN_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
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);
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_right_handed<float, MatStoreType::COLUMN_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
field_of_view, aspect_ratio, near, far);
std::unreachable();
}
} // namespace omath::opengl_engine
@@ -1,28 +0,0 @@
//
// Created by Vlad on 8/11/2025.
//
#include "omath/engines/opengl_engine/traits/camera_trait.hpp"
#include "omath/engines/opengl_engine/formulas.hpp"
namespace omath::opengl_engine
{
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(std::asin(direction.y)),
YawAngle::from_radians(-std::atan2(direction.x, -direction.z)), RollAngle::from_radians(0.f)};
}
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return opengl_engine::calc_view_matrix(angles, cam_origin);
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::opengl_engine
-61
View File
@@ -1,61 +0,0 @@
//
// Created by Vlad on 3/19/2025.
//
#include <omath/engines/source_engine/formulas.hpp>
namespace omath::source_engine
{
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll);
}
Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view(forward_vector(angles), right_vector(angles), up_vector(angles), cam_origin);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
// Source (inherited from Quake) stores FOV as horizontal FOV at a 4:3
// 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));
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
return mat_perspective_left_handed<
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
vfov_deg, 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>(
vfov_deg, aspect_ratio, near, far);
std::unreachable();
}
} // namespace omath::source_engine
@@ -1,28 +0,0 @@
//
// Created by Vlad on 8/11/2025.
//
#include "omath/engines/source_engine/traits/camera_trait.hpp"
#include "omath/engines/source_engine/formulas.hpp"
namespace omath::source_engine
{
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(-std::asin(direction.z)),
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
{
return source_engine::calc_view_matrix(angles, cam_origin);
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::source_engine
-49
View File
@@ -1,49 +0,0 @@
//
// Created by Vlad on 3/22/2025.
//
#include "omath/engines/unity_engine/formulas.hpp"
namespace omath::unity_engine
{
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<float> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(-forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.roll)
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.yaw)
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
}
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
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
@@ -1,27 +0,0 @@
//
// Created by Vlad on 8/11/2025.
//
#include "omath/engines/unity_engine/traits/camera_trait.hpp"
namespace omath::unity_engine
{
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
{
const auto direction = (look_at - cam_origin).normalized();
return {PitchAngle::from_radians(-std::asin(direction.y)),
YawAngle::from_radians(std::atan2(direction.x, direction.z)), RollAngle::from_radians(0.f)};
}
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
{
return unity_engine::calc_view_matrix(angles, cam_origin);
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const float near,
const float far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::unity_engine
-57
View File
@@ -1,57 +0,0 @@
//
// Created by Vlad on 3/22/2025.
//
#include "omath/engines/unreal_engine/formulas.hpp"
namespace omath::unreal_engine
{
Vector3<double> forward_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<double> right_vector(const ViewAngles& angles) noexcept
{
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)};
}
Vector3<double> up_vector(const ViewAngles& angles) noexcept
{
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)};
}
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept
{
return mat_camera_view<double, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
up_vector(angles), cam_origin);
}
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
{
// UE FRotator is intrinsic Z-Y-X (Yaw → Pitch → Roll applied in local
// frame), which for column-vector composition is Rz·Ry·Rx.
// 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 double field_of_view, const double aspect_ratio, const double near,
const double far, const NDCDepthRange ndc_depth_range) noexcept
{
// 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
@@ -1,27 +0,0 @@
//
// Created by Vlad on 8/11/2025.
//
#include "omath/engines/unreal_engine/traits/camera_trait.hpp"
namespace omath::unreal_engine
{
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();
return {PitchAngle::from_radians(std::asin(direction.z)),
YawAngle::from_radians(std::atan2(direction.y, direction.x)), RollAngle::from_radians(0.f)};
}
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<double>& cam_origin) noexcept
{
return unreal_engine::calc_view_matrix(angles, cam_origin);
}
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port, const double near,
const double far, const NDCDepthRange ndc_depth_range) noexcept
{
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
ndc_depth_range);
}
} // namespace omath::unreal_engine
+742
View File
@@ -0,0 +1,742 @@
#include "omath/hooks/hooks_manager.hpp"
#ifdef OMATH_ENABLE_HOOKING
#ifdef _WIN32
#include <d3d11.h>
#endif // _WIN32
#ifdef __linux__
#include <dlfcn.h>
#endif // __linux__
namespace
{
#ifdef _WIN32
thread_local bool g_is_inside_opengl_swap_buffers = false;
class DummyWindow final
{
WNDCLASSEX m_window_class{};
HWND m_window_handle = nullptr;
public:
DummyWindow()
{
m_window_class.cbSize = sizeof(WNDCLASSEX);
m_window_class.style = CS_HREDRAW | CS_VREDRAW;
m_window_class.lpfnWndProc = DefWindowProc;
m_window_class.hInstance = GetModuleHandle(nullptr);
m_window_class.lpszClassName = "OM";
RegisterClassEx(&m_window_class);
m_window_handle = CreateWindow(m_window_class.lpszClassName, "Dummy", WS_OVERLAPPEDWINDOW, 0, 0, 100, 100,
nullptr, nullptr, m_window_class.hInstance, nullptr);
}
~DummyWindow()
{
if (m_window_handle)
DestroyWindow(m_window_handle);
UnregisterClass(m_window_class.lpszClassName, m_window_class.hInstance);
}
[[nodiscard]] HWND handle() const
{
return m_window_handle;
}
[[nodiscard]] bool valid() const
{
return m_window_handle != nullptr;
}
};
void* vtable_fn(void* com_obj, std::size_t index)
{
return (*reinterpret_cast<void***>(com_obj))[index];
}
void* module_proc(const char* module_name, const char* proc_name)
{
const HMODULE module = GetModuleHandle(module_name);
if (!module)
return nullptr;
return reinterpret_cast<void*>(GetProcAddress(module, proc_name));
}
struct dx12_vtable_fns
{
void* present;
void* resize_buffers;
void* execute_command_lists;
};
// RAII wrapper so all early-return paths release COM objects automatically.
struct dx12_com_objects
{
IDXGIFactory* factory = nullptr;
ID3D12Device* device = nullptr;
ID3D12CommandQueue* command_queue = nullptr;
ID3D12CommandAllocator* command_allocator = nullptr;
ID3D12GraphicsCommandList* command_list = nullptr;
IDXGISwapChain* swap_chain = nullptr;
dx12_com_objects() = default;
dx12_com_objects(const dx12_com_objects&) = delete;
dx12_com_objects& operator=(const dx12_com_objects&) = delete;
~dx12_com_objects()
{
if (swap_chain)
swap_chain->Release();
if (command_list)
command_list->Release();
if (command_allocator)
command_allocator->Release();
if (command_queue)
command_queue->Release();
if (device)
device->Release();
if (factory)
factory->Release();
}
};
std::optional<dx12_vtable_fns> read_dx12_vtable_fns(HWND hwnd)
{
using create_dxgi_factory_fn = HRESULT(__stdcall*)(REFIID, void**);
using d3d12_create_device_fn = HRESULT(__stdcall*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**);
const HMODULE d3d12_module = GetModuleHandle("d3d12.dll");
const HMODULE dxgi_module = GetModuleHandle("dxgi.dll");
if (!d3d12_module || !dxgi_module)
return std::nullopt;
const auto create_dxgi_factory =
reinterpret_cast<create_dxgi_factory_fn>(GetProcAddress(dxgi_module, "CreateDXGIFactory"));
const auto d3d12_create_device =
reinterpret_cast<d3d12_create_device_fn>(GetProcAddress(d3d12_module, "D3D12CreateDevice"));
if (!create_dxgi_factory || !d3d12_create_device)
return std::nullopt;
dx12_com_objects objs;
if (FAILED(create_dxgi_factory(__uuidof(IDXGIFactory), reinterpret_cast<void**>(&objs.factory))))
return std::nullopt;
IDXGIAdapter* adapter = nullptr;
if (objs.factory->EnumAdapters(0, &adapter) == DXGI_ERROR_NOT_FOUND)
return std::nullopt;
const HRESULT device_hr = d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device),
reinterpret_cast<void**>(&objs.device));
adapter->Release();
if (FAILED(device_hr))
return std::nullopt;
D3D12_COMMAND_QUEUE_DESC queue_desc{};
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
if (FAILED(objs.device->CreateCommandQueue(&queue_desc, __uuidof(ID3D12CommandQueue),
reinterpret_cast<void**>(&objs.command_queue))))
return std::nullopt;
if (FAILED(objs.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
reinterpret_cast<void**>(&objs.command_allocator))))
return std::nullopt;
if (FAILED(objs.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, objs.command_allocator, nullptr,
__uuidof(ID3D12GraphicsCommandList),
reinterpret_cast<void**>(&objs.command_list))))
return std::nullopt;
DXGI_SWAP_CHAIN_DESC swap_chain_desc{};
swap_chain_desc.BufferDesc.Width = 100;
swap_chain_desc.BufferDesc.Height = 100;
swap_chain_desc.BufferDesc.RefreshRate = {60, 1};
swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.SampleDesc = {1, 0};
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.BufferCount = 2;
swap_chain_desc.OutputWindow = hwnd;
swap_chain_desc.Windowed = TRUE;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
if (FAILED(objs.factory->CreateSwapChain(objs.command_queue, &swap_chain_desc, &objs.swap_chain)))
return std::nullopt;
// objs destructor releases all COM objects after we capture the addresses.
return dx12_vtable_fns{
vtable_fn(objs.swap_chain, 8), // IDXGISwapChain::Present
vtable_fn(objs.swap_chain, 13), // IDXGISwapChain::ResizeBuffers
vtable_fn(objs.command_queue, 10), // ID3D12CommandQueue::ExecuteCommandLists
};
}
#endif // _WIN32
} // namespace
namespace omath::hooks
{
HooksManager& HooksManager::get()
{
static HooksManager obj;
return obj;
}
HooksManager::~HooksManager()
{
#ifdef _WIN32
unhook_wnd_proc();
unhook_dx9();
unhook_dx11();
unhook_dx12();
#endif // _WIN32
unhook_opengl();
}
#ifdef _WIN32
bool HooksManager::hook_dx9()
{
std::unique_lock lock(m_hook_state_mutex);
if (m_is_dx9_hooked)
return true;
const DummyWindow window;
if (!window.valid())
return false;
const HMODULE d3d9_module = GetModuleHandle("d3d9.dll");
if (!d3d9_module)
return false;
using direct3d_create9_fn = IDirect3D9*(__stdcall*)(UINT);
const auto direct3d_create9 =
reinterpret_cast<direct3d_create9_fn>(GetProcAddress(d3d9_module, "Direct3DCreate9"));
if (!direct3d_create9)
return false;
IDirect3D9* d3d9 = direct3d_create9(D3D_SDK_VERSION);
if (!d3d9)
return false;
D3DPRESENT_PARAMETERS pp{};
pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
pp.hDeviceWindow = window.handle();
pp.Windowed = TRUE;
IDirect3DDevice9* device = nullptr;
if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window.handle(),
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &pp, &device)))
{
d3d9->Release();
return false;
}
// IDirect3DDevice9 vtable indices (from IUnknown base):
// Reset = 16
// Present = 17
// EndScene = 42
m_dx9_present_hook =
safetyhook::create_inline(vtable_fn(device, 17), reinterpret_cast<void*>(&dx9_present_detour));
m_dx9_reset_hook = safetyhook::create_inline(vtable_fn(device, 16), reinterpret_cast<void*>(&dx9_reset_detour));
m_dx9_end_scene_hook =
safetyhook::create_inline(vtable_fn(device, 42), reinterpret_cast<void*>(&dx9_end_scene_detour));
device->Release();
d3d9->Release();
if (!m_dx9_present_hook || !m_dx9_reset_hook || !m_dx9_end_scene_hook)
{
m_dx9_present_hook = {};
m_dx9_reset_hook = {};
m_dx9_end_scene_hook = {};
return false;
}
m_is_dx9_hooked = true;
return true;
}
void HooksManager::unhook_dx9()
{
std::unique_lock lock(m_hook_state_mutex);
m_dx9_present_hook = {};
m_dx9_reset_hook = {};
m_dx9_end_scene_hook = {};
m_is_dx9_hooked = false;
}
void HooksManager::set_on_dx9_present(dx9_present_callback callback)
{
std::unique_lock lock(m_dx9_present_mutex);
m_dx9_present_cb = callback ? std::make_shared<dx9_present_callback>(std::move(callback)) : nullptr;
}
void HooksManager::set_on_dx9_reset(dx9_reset_callback callback)
{
std::unique_lock lock(m_dx9_reset_mutex);
m_dx9_reset_cb = callback ? std::make_shared<dx9_reset_callback>(std::move(callback)) : nullptr;
}
void HooksManager::set_on_dx9_end_scene(dx9_end_scene_callback callback)
{
std::unique_lock lock(m_dx9_end_scene_mutex);
m_dx9_end_scene_cb = callback ? std::make_shared<dx9_end_scene_callback>(std::move(callback)) : nullptr;
}
bool HooksManager::hook_dx11()
{
std::unique_lock lock(m_hook_state_mutex);
if (m_is_dx11_hooked)
return true;
const DummyWindow window;
if (!window.valid())
return false;
const HMODULE d3d11_module = GetModuleHandle("d3d11.dll");
if (!d3d11_module)
return false;
using d3d11_create_device_and_swap_chain_fn =
HRESULT(__stdcall*)(IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT, const D3D_FEATURE_LEVEL*, UINT, UINT,
const DXGI_SWAP_CHAIN_DESC*, IDXGISwapChain**, ID3D11Device**, D3D_FEATURE_LEVEL*,
ID3D11DeviceContext**);
const auto create_device_and_swap_chain = reinterpret_cast<d3d11_create_device_and_swap_chain_fn>(
GetProcAddress(d3d11_module, "D3D11CreateDeviceAndSwapChain"));
if (!create_device_and_swap_chain)
return false;
DXGI_SWAP_CHAIN_DESC swap_chain_desc{};
swap_chain_desc.BufferDesc.Width = 100;
swap_chain_desc.BufferDesc.Height = 100;
swap_chain_desc.BufferDesc.RefreshRate = {60, 1};
swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.SampleDesc = {1, 0};
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.BufferCount = 1;
swap_chain_desc.OutputWindow = window.handle();
swap_chain_desc.Windowed = TRUE;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
constexpr D3D_FEATURE_LEVEL feature_levels[] = {D3D_FEATURE_LEVEL_11_0};
ID3D11Device* device = nullptr;
ID3D11DeviceContext* device_context = nullptr;
IDXGISwapChain* swap_chain = nullptr;
if (FAILED(create_device_and_swap_chain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, feature_levels, 1,
D3D11_SDK_VERSION, &swap_chain_desc, &swap_chain, &device, nullptr,
&device_context)))
return false;
m_dx11_present_hook = safetyhook::create_inline(vtable_fn(swap_chain, 8), // IDXGISwapChain::Present
reinterpret_cast<void*>(&dx11_present_detour));
m_dx11_resize_buffers_hook =
safetyhook::create_inline(vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers
reinterpret_cast<void*>(&dx11_resize_buffers_detour));
swap_chain->Release();
device_context->Release();
device->Release();
if (!m_dx11_present_hook || !m_dx11_resize_buffers_hook)
{
m_dx11_present_hook = {};
m_dx11_resize_buffers_hook = {};
return false;
}
m_is_dx11_hooked = true;
return true;
}
void HooksManager::unhook_dx11()
{
std::unique_lock lock(m_hook_state_mutex);
m_dx11_present_hook = {};
m_dx11_resize_buffers_hook = {};
m_is_dx11_hooked = false;
}
bool HooksManager::hook_dx12()
{
std::unique_lock lock(m_hook_state_mutex);
if (m_is_dx12_hooked)
return true;
const DummyWindow window;
if (!window.valid())
return false;
const auto fns = read_dx12_vtable_fns(window.handle());
if (!fns)
return false;
m_dx12_present_hook = safetyhook::create_inline(fns->present, reinterpret_cast<void*>(&dx12_present_detour));
m_dx12_resize_buffers_hook =
safetyhook::create_inline(fns->resize_buffers, reinterpret_cast<void*>(&dx12_resize_buffers_detour));
m_dx12_execute_command_lists_hook = safetyhook::create_inline(
fns->execute_command_lists, reinterpret_cast<void*>(&dx12_execute_command_lists_detour));
if (!m_dx12_present_hook || !m_dx12_resize_buffers_hook || !m_dx12_execute_command_lists_hook)
{
m_dx12_present_hook = {};
m_dx12_resize_buffers_hook = {};
m_dx12_execute_command_lists_hook = {};
return false;
}
m_is_dx12_hooked = true;
return true;
}
void HooksManager::unhook_dx12()
{
std::unique_lock lock(m_hook_state_mutex);
m_dx12_present_hook = {};
m_dx12_resize_buffers_hook = {};
m_dx12_execute_command_lists_hook = {};
m_is_dx12_hooked = false;
}
bool HooksManager::hook_opengl()
{
std::unique_lock lock(m_hook_state_mutex);
if (m_is_opengl_hooked)
return true;
if (void* wgl_swap_buffers = module_proc("opengl32.dll", "wglSwapBuffers"))
{
m_opengl_wgl_swap_buffers_hook = safetyhook::create_inline(
wgl_swap_buffers, reinterpret_cast<void*>(&opengl_wgl_swap_buffers_detour));
}
if (void* swap_buffers = module_proc("gdi32.dll", "SwapBuffers"))
{
m_opengl_swap_buffers_hook =
safetyhook::create_inline(swap_buffers, reinterpret_cast<void*>(&opengl_swap_buffers_detour));
}
if (!m_opengl_wgl_swap_buffers_hook && !m_opengl_swap_buffers_hook)
{
m_opengl_wgl_swap_buffers_hook = {};
m_opengl_swap_buffers_hook = {};
return false;
}
m_is_opengl_hooked = true;
return true;
}
void HooksManager::unhook_opengl()
{
std::unique_lock lock(m_hook_state_mutex);
m_opengl_wgl_swap_buffers_hook = {};
m_opengl_swap_buffers_hook = {};
m_is_opengl_hooked = false;
}
void HooksManager::set_on_present(present_callback callback)
{
std::unique_lock lock(m_present_mutex);
m_present_cb = callback ? std::make_shared<present_callback>(std::move(callback)) : nullptr;
}
void HooksManager::set_on_resize_buffers(resize_buffers_callback callback)
{
std::unique_lock lock(m_resize_buffers_mutex);
m_resize_buffers_cb = callback ? std::make_shared<resize_buffers_callback>(std::move(callback)) : nullptr;
}
void HooksManager::set_on_execute_command_lists(execute_command_lists_callback callback)
{
std::unique_lock lock(m_execute_command_lists_mutex);
m_execute_command_lists_cb =
callback ? std::make_shared<execute_command_lists_callback>(std::move(callback)) : nullptr;
}
bool HooksManager::hook_wnd_proc(HWND hwnd)
{
std::unique_lock lock(m_hook_state_mutex);
if (m_is_wnd_proc_hooked)
return true;
const auto prev = reinterpret_cast<WNDPROC>(
SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&wnd_proc_detour)));
if (!prev)
return false;
m_hooked_hwnd = hwnd;
m_original_wndproc = prev;
m_is_wnd_proc_hooked = true;
return true;
}
void HooksManager::unhook_wnd_proc()
{
std::unique_lock lock(m_hook_state_mutex);
if (!m_is_wnd_proc_hooked)
return;
SetWindowLongPtr(m_hooked_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_original_wndproc));
m_hooked_hwnd = nullptr;
m_original_wndproc = nullptr;
m_is_wnd_proc_hooked = false;
}
void HooksManager::set_on_wnd_proc(wnd_proc_callback callback)
{
std::unique_lock lock(m_wnd_proc_mutex);
m_wnd_proc_cb = callback ? std::make_shared<wnd_proc_callback>(std::move(callback)) : nullptr;
}
// Detour implementations: copy a shared_ptr to the callback under shared lock, call it unlocked,
// then call original. This avoids copying captured lambda state every frame and still avoids
// a deadlock if the callback itself calls set_on_*().
HRESULT __stdcall HooksManager::dx9_present_detour(IDirect3DDevice9* p_device, const RECT* p_source_rect,
const RECT* p_dest_rect, HWND h_dest_window_override,
const RGNDATA* p_dirty_region)
{
auto& mgr = get();
callback_ptr<dx9_present_callback> cb;
{
std::shared_lock lock(mgr.m_dx9_present_mutex);
cb = mgr.m_dx9_present_cb;
}
if (cb)
(*cb)(p_device, p_source_rect, p_dest_rect, h_dest_window_override, p_dirty_region);
return mgr.m_dx9_present_hook.call<HRESULT>(p_device, p_source_rect, p_dest_rect, h_dest_window_override,
p_dirty_region);
}
HRESULT __stdcall HooksManager::dx9_reset_detour(IDirect3DDevice9* p_device,
D3DPRESENT_PARAMETERS* p_presentation_parameters)
{
auto& mgr = get();
callback_ptr<dx9_reset_callback> cb;
{
std::shared_lock lock(mgr.m_dx9_reset_mutex);
cb = mgr.m_dx9_reset_cb;
}
if (cb)
(*cb)(p_device, p_presentation_parameters);
return mgr.m_dx9_reset_hook.call<HRESULT>(p_device, p_presentation_parameters);
}
HRESULT __stdcall HooksManager::dx9_end_scene_detour(IDirect3DDevice9* p_device)
{
auto& mgr = get();
callback_ptr<dx9_end_scene_callback> cb;
{
std::shared_lock lock(mgr.m_dx9_end_scene_mutex);
cb = mgr.m_dx9_end_scene_cb;
}
if (cb)
(*cb)(p_device);
return mgr.m_dx9_end_scene_hook.call<HRESULT>(p_device);
}
HRESULT __stdcall HooksManager::dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags)
{
auto& mgr = get();
callback_ptr<present_callback> cb;
{
std::shared_lock lock(mgr.m_present_mutex);
cb = mgr.m_present_cb;
}
if (cb)
(*cb)(p_swap_chain, sync_interval, flags);
return mgr.m_dx11_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags);
}
HRESULT __stdcall HooksManager::dx11_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count,
UINT width, UINT height, DXGI_FORMAT new_format,
UINT swap_chain_flags)
{
auto& mgr = get();
callback_ptr<resize_buffers_callback> cb;
{
std::shared_lock lock(mgr.m_resize_buffers_mutex);
cb = mgr.m_resize_buffers_cb;
}
if (cb)
(*cb)(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags);
return mgr.m_dx11_resize_buffers_hook.call<HRESULT>(p_swap_chain, buffer_count, width, height, new_format,
swap_chain_flags);
}
HRESULT __stdcall HooksManager::dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags)
{
auto& mgr = get();
callback_ptr<present_callback> cb;
{
std::shared_lock lock(mgr.m_present_mutex);
cb = mgr.m_present_cb;
}
if (cb)
(*cb)(p_swap_chain, sync_interval, flags);
return mgr.m_dx12_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags);
}
HRESULT __stdcall HooksManager::dx12_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count,
UINT width, UINT height, DXGI_FORMAT new_format,
UINT swap_chain_flags)
{
auto& mgr = get();
callback_ptr<resize_buffers_callback> cb;
{
std::shared_lock lock(mgr.m_resize_buffers_mutex);
cb = mgr.m_resize_buffers_cb;
}
if (cb)
(*cb)(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags);
return mgr.m_dx12_resize_buffers_hook.call<HRESULT>(p_swap_chain, buffer_count, width, height, new_format,
swap_chain_flags);
}
void __stdcall HooksManager::dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue,
UINT num_command_lists,
ID3D12CommandList* const* pp_command_lists)
{
auto& mgr = get();
callback_ptr<execute_command_lists_callback> cb;
{
std::shared_lock lock(mgr.m_execute_command_lists_mutex);
cb = mgr.m_execute_command_lists_cb;
}
if (cb)
(*cb)(p_command_queue, num_command_lists, pp_command_lists);
mgr.m_dx12_execute_command_lists_hook.call<void>(p_command_queue, num_command_lists, pp_command_lists);
}
BOOL __stdcall HooksManager::opengl_wgl_swap_buffers_detour(HDC hdc)
{
auto& mgr = get();
if (!g_is_inside_opengl_swap_buffers)
return mgr.m_opengl_wgl_swap_buffers_hook.call<BOOL>(hdc);
g_is_inside_opengl_swap_buffers = true;
callback_ptr<opengl_swap_buffers_callback> cb;
{
std::shared_lock lock(mgr.m_opengl_swap_buffers_mutex);
cb = mgr.m_opengl_swap_buffers_cb;
}
if (cb)
(*cb)(hdc);
const BOOL result = mgr.m_opengl_wgl_swap_buffers_hook.call<BOOL>(hdc);
g_is_inside_opengl_swap_buffers = false;
return result;
}
BOOL __stdcall HooksManager::opengl_swap_buffers_detour(HDC hdc)
{
auto& mgr = get();
if (g_is_inside_opengl_swap_buffers)
return mgr.m_opengl_swap_buffers_hook.call<BOOL>(hdc);
g_is_inside_opengl_swap_buffers = true;
callback_ptr<opengl_swap_buffers_callback> cb;
{
std::shared_lock lock(mgr.m_opengl_swap_buffers_mutex);
cb = mgr.m_opengl_swap_buffers_cb;
}
if (cb)
(*cb)(hdc);
const BOOL result = mgr.m_opengl_swap_buffers_hook.call<BOOL>(hdc);
g_is_inside_opengl_swap_buffers = false;
return result;
}
LRESULT __stdcall HooksManager::wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param)
{
const auto& mgr = get();
callback_ptr<wnd_proc_callback> cb;
WNDPROC original;
{
std::shared_lock lock(mgr.m_wnd_proc_mutex);
cb = mgr.m_wnd_proc_cb;
original = mgr.m_original_wndproc;
}
if (cb)
{
if (const auto result = (*cb)(hwnd, msg, w_param, l_param))
return *result;
}
return CallWindowProc(original, hwnd, msg, w_param, l_param);
}
#endif // _WIN32
#ifdef __linux__
bool HooksManager::hook_opengl()
{
std::unique_lock lock(m_hook_state_mutex);
if (m_is_opengl_hooked)
return true;
void* glx_swap_buffers = dlsym(RTLD_DEFAULT, "glXSwapBuffers");
if (!glx_swap_buffers)
return false;
m_opengl_glx_swap_buffers_hook = safetyhook::create_inline(
glx_swap_buffers, reinterpret_cast<void*>(&opengl_glx_swap_buffers_detour));
if (!m_opengl_glx_swap_buffers_hook)
return false;
m_is_opengl_hooked = true;
return true;
}
void HooksManager::unhook_opengl()
{
std::unique_lock lock(m_hook_state_mutex);
m_opengl_glx_swap_buffers_hook = {};
m_is_opengl_hooked = false;
}
void HooksManager::opengl_glx_swap_buffers_detour(Display* display, GLXDrawable drawable)
{
auto& mgr = get();
callback_ptr<opengl_swap_buffers_callback> cb;
{
std::shared_lock lock(mgr.m_opengl_swap_buffers_mutex);
cb = mgr.m_opengl_swap_buffers_cb;
}
if (cb)
(*cb)(display, drawable);
mgr.m_opengl_glx_swap_buffers_hook.call<void>(display, drawable);
}
#endif // __linux__
void HooksManager::set_on_opengl_swap_buffers(opengl_swap_buffers_callback callback)
{
std::unique_lock lock(m_opengl_swap_buffers_mutex);
m_opengl_swap_buffers_cb =
callback ? std::make_shared<opengl_swap_buffers_callback>(std::move(callback)) : nullptr;
}
} // namespace omath::hooks
#else // !OMATH_ENABLE_HOOKING
namespace omath::hooks
{
HooksManager& HooksManager::get()
{
static HooksManager obj;
return obj;
}
HooksManager::~HooksManager() = default;
} // namespace omath::hooks
#endif
+49 -38
View File
@@ -5,10 +5,14 @@
namespace omath::hud namespace omath::hud
{ {
EntityOverlay& EntityOverlay::add_2d_box(const Color& box_color, const Color& fill_color, const float thickness) EntityOverlay& EntityOverlay::add_2d_box(const Color& box_color, const Color& fill_color,
const Color& outline_color, const float thickness)
{ {
const auto points = m_canvas.as_array(); const auto points = m_canvas.as_array();
if (outline_color.value().w > 0.f)
m_renderer->add_polyline({points.data(), points.size()}, outline_color, thickness + 2.f);
m_renderer->add_polyline({points.data(), points.size()}, box_color, thickness); m_renderer->add_polyline({points.data(), points.size()}, box_color, thickness);
if (fill_color.value().w > 0.f) if (fill_color.value().w > 0.f)
@@ -17,41 +21,46 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_cornered_2d_box(const Color& box_color, const Color& fill_color, EntityOverlay& EntityOverlay::add_cornered_2d_box(const Color& box_color, const Color& fill_color,
const float corner_ratio_len, const float thickness) const Color& outline_color, const float corner_ratio_len,
const float thickness)
{ {
const auto corner_line_length = const auto corner_line_length =
std::abs((m_canvas.top_left_corner - m_canvas.top_right_corner).x * corner_ratio_len); std::abs((m_canvas.top_left_corner - m_canvas.top_right_corner).x * corner_ratio_len);
if (fill_color.value().w > 0.f) if (fill_color.value().w > 0.f)
add_2d_box(fill_color, fill_color); add_2d_box(fill_color, fill_color);
const auto draw_corner_line = [&](const Vector2<float>& a, const Vector2<float>& b)
{
if (outline_color.value().w > 0.f)
m_renderer->add_line(a, b, outline_color, thickness + 2.f);
m_renderer->add_line(a, b, box_color, thickness);
};
// Left Side // Left Side
m_renderer->add_line(m_canvas.top_left_corner, draw_corner_line(m_canvas.top_left_corner,
m_canvas.top_left_corner + Vector2<float>{corner_line_length, 0.f}, box_color, thickness); m_canvas.top_left_corner + Vector2<float>{corner_line_length, 0.f});
m_renderer->add_line(m_canvas.top_left_corner, draw_corner_line(m_canvas.top_left_corner,
m_canvas.top_left_corner + Vector2<float>{0.f, corner_line_length}, box_color, thickness); m_canvas.top_left_corner + Vector2<float>{0.f, corner_line_length});
m_renderer->add_line(m_canvas.bottom_left_corner, draw_corner_line(m_canvas.bottom_left_corner,
m_canvas.bottom_left_corner - Vector2<float>{0.f, corner_line_length}, box_color, m_canvas.bottom_left_corner - Vector2<float>{0.f, corner_line_length});
thickness);
m_renderer->add_line(m_canvas.bottom_left_corner, draw_corner_line(m_canvas.bottom_left_corner,
m_canvas.bottom_left_corner + Vector2<float>{corner_line_length, 0.f}, box_color, m_canvas.bottom_left_corner + Vector2<float>{corner_line_length, 0.f});
thickness);
// Right Side // Right Side
m_renderer->add_line(m_canvas.top_right_corner, draw_corner_line(m_canvas.top_right_corner,
m_canvas.top_right_corner - Vector2<float>{corner_line_length, 0.f}, box_color, thickness); m_canvas.top_right_corner - Vector2<float>{corner_line_length, 0.f});
m_renderer->add_line(m_canvas.top_right_corner, draw_corner_line(m_canvas.top_right_corner,
m_canvas.top_right_corner + Vector2<float>{0.f, corner_line_length}, box_color, thickness); m_canvas.top_right_corner + Vector2<float>{0.f, corner_line_length});
m_renderer->add_line(m_canvas.bottom_right_corner, draw_corner_line(m_canvas.bottom_right_corner,
m_canvas.bottom_right_corner - Vector2<float>{0.f, corner_line_length}, box_color, m_canvas.bottom_right_corner - Vector2<float>{0.f, corner_line_length});
thickness);
m_renderer->add_line(m_canvas.bottom_right_corner, draw_corner_line(m_canvas.bottom_right_corner,
m_canvas.bottom_right_corner - Vector2<float>{corner_line_length, 0.f}, box_color, m_canvas.bottom_right_corner - Vector2<float>{corner_line_length, 0.f});
thickness);
return *this; return *this;
} }
@@ -89,10 +98,11 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_right_label(const Color& color, const float offset,
const widget::Outlined outlined,
const std::string_view& text) const std::string_view& text)
{ {
if (outlined) if (outlined == widget::Outlined::On)
draw_outlined_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text); draw_outlined_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text);
else else
m_renderer->add_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text.data()); m_renderer->add_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text.data());
@@ -101,12 +111,12 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_top_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_top_label(const Color& color, const float offset, const widget::Outlined outlined,
const std::string_view text) const std::string_view text)
{ {
m_text_cursor_top.y -= m_renderer->calc_text_size(text.data()).y; m_text_cursor_top.y -= m_renderer->calc_text_size(text.data()).y;
if (outlined) if (outlined == widget::Outlined::On)
draw_outlined_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text); draw_outlined_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text);
else else
m_renderer->add_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text.data()); m_renderer->add_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text.data());
@@ -383,12 +393,12 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_bottom_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_bottom_label(const Color& color, const float offset, const widget::Outlined outlined,
const std::string_view text) const std::string_view text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
if (outlined) if (outlined == widget::Outlined::On)
draw_outlined_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text); draw_outlined_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text);
else else
m_renderer->add_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text); m_renderer->add_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text);
@@ -398,13 +408,13 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_left_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_left_label(const Color& color, const float offset, const widget::Outlined outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
const auto pos = m_text_cursor_left + Vector2<float>{-(offset + text_size.x), 0.f}; const auto pos = m_text_cursor_left + Vector2<float>{-(offset + text_size.x), 0.f};
if (outlined) if (outlined == widget::Outlined::On)
draw_outlined_text(pos, color, text); draw_outlined_text(pos, color, text);
else else
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
@@ -414,7 +424,7 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const widget::Outlined outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
@@ -422,7 +432,7 @@ namespace omath::hud
m_canvas.bottom_left_corner.x + (m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x) / 2.f; m_canvas.bottom_left_corner.x + (m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x) / 2.f;
const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_bottom.y + offset}; const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_bottom.y + offset};
if (outlined) if (outlined == widget::Outlined::On)
draw_outlined_text(pos, color, text); draw_outlined_text(pos, color, text);
else else
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
@@ -432,7 +442,7 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_centered_top_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_centered_top_label(const Color& color, const float offset, const widget::Outlined outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
@@ -442,7 +452,7 @@ namespace omath::hud
m_text_cursor_top.y -= text_size.y; m_text_cursor_top.y -= text_size.y;
const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_top.y - offset}; const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_top.y - offset};
if (outlined) if (outlined == widget::Outlined::On)
draw_outlined_text(pos, color, text); draw_outlined_text(pos, color, text);
else else
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
@@ -450,9 +460,9 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay::EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom, EntityOverlay::EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom, const float aspect,
const std::shared_ptr<HudRendererInterface>& renderer) const std::shared_ptr<HudRendererInterface>& renderer)
: m_canvas(top, bottom), m_text_cursor_right(m_canvas.top_right_corner), : m_canvas(top, bottom, aspect), m_text_cursor_right(m_canvas.top_right_corner),
m_text_cursor_top(m_canvas.top_left_corner), m_text_cursor_bottom(m_canvas.bottom_left_corner), m_text_cursor_top(m_canvas.top_left_corner), m_text_cursor_bottom(m_canvas.bottom_left_corner),
m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer) m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer)
{ {
@@ -591,12 +601,13 @@ namespace omath::hud
// ── widget dispatch ─────────────────────────────────────────────────────── // ── widget dispatch ───────────────────────────────────────────────────────
void EntityOverlay::dispatch(const widget::Box& box) void EntityOverlay::dispatch(const widget::Box& box)
{ {
add_2d_box(box.color, box.fill, box.thickness); add_2d_box(box.color, box.fill, box.outline, box.thickness);
} }
void EntityOverlay::dispatch(const widget::CorneredBox& cornered_box) void EntityOverlay::dispatch(const widget::CorneredBox& cornered_box)
{ {
add_cornered_2d_box(cornered_box.color, cornered_box.fill, cornered_box.corner_ratio, cornered_box.thickness); add_cornered_2d_box(cornered_box.color, cornered_box.fill, cornered_box.outline, cornered_box.corner_ratio,
cornered_box.thickness);
} }
void EntityOverlay::dispatch(const widget::DashedBox& dashed_box) void EntityOverlay::dispatch(const widget::DashedBox& dashed_box)
+5
View File
@@ -17,8 +17,13 @@ namespace omath::lua
register_vec2(omath_table); register_vec2(omath_table);
register_vec3(omath_table); register_vec3(omath_table);
register_vec4(omath_table); register_vec4(omath_table);
register_matrices(omath_table);
register_quaternion(omath_table);
register_color(omath_table); register_color(omath_table);
register_hud(omath_table);
register_triangle(omath_table); register_triangle(omath_table);
register_3d_primitives(omath_table);
register_collision(omath_table);
register_shared_types(omath_table); register_shared_types(omath_table);
register_engines(omath_table); register_engines(omath_table);
register_pattern_scan(omath_table); register_pattern_scan(omath_table);
+304
View File
@@ -0,0 +1,304 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <omath/3d_primitives/aabb.hpp>
#include <omath/3d_primitives/obb.hpp>
#include <omath/collision/collider_interface.hpp>
#include <omath/collision/epa_algorithm.hpp>
#include <omath/collision/gjk_algorithm.hpp>
#include <omath/collision/line_tracer.hpp>
#include <omath/linear_algebra/triangle.hpp>
#include <omath/linear_algebra/vector3.hpp>
#include <sol/sol.hpp>
#include <algorithm>
#include <stdexcept>
#include <vector>
namespace
{
using Vec3f = omath::Vector3<float>;
using Triangle3f = omath::Triangle<Vec3f>;
using Ray3f = omath::collision::Ray<Vec3f>;
using LineTracer3f = omath::collision::LineTracer<Ray3f>;
using Aabbf = omath::primitives::Aabb<float>;
using Obbf = omath::primitives::Obb<float>;
template<class Object, class Value>
auto lua_field(Value Object::* member)
{
return sol::property(
[member](const Object& object) -> const Value&
{
return object.*member;
},
[member](Object& object, const Value& value)
{
object.*member = value;
});
}
class LuaConvexCollider final : public omath::collision::ColliderInterface<Vec3f>
{
public:
using VectorType = Vec3f;
explicit LuaConvexCollider(std::vector<Vec3f> vertices, const Vec3f& origin = {})
: m_vertices(std::move(vertices)), m_origin(origin)
{
if (m_vertices.empty())
throw std::invalid_argument("convex collider must contain at least one vertex");
}
[[nodiscard]]
Vec3f find_abs_furthest_vertex_position(const Vec3f& direction) const override
{
const auto furthest = std::ranges::max_element(
m_vertices,
[&direction](const Vec3f& first, const Vec3f& second)
{
return first.dot(direction) < second.dot(direction);
});
return m_origin + *furthest;
}
[[nodiscard]]
const Vec3f& get_origin() const override
{
return m_origin;
}
void set_origin(const Vec3f& new_origin) override
{
m_origin = new_origin;
}
[[nodiscard]]
std::size_t vertex_count() const noexcept
{
return m_vertices.size();
}
[[nodiscard]]
const std::vector<Vec3f>& vertices() const noexcept
{
return m_vertices;
}
private:
std::vector<Vec3f> m_vertices;
Vec3f m_origin;
};
std::vector<Vec3f> vec3_table_to_vector(const sol::table& points)
{
std::vector<Vec3f> result;
for (std::size_t i = 1;; ++i)
{
const auto point = points[i].get<sol::optional<Vec3f>>();
if (!point)
break;
result.push_back(*point);
}
return result;
}
sol::table vec3_array_to_table(const auto& points, sol::this_state state)
{
sol::state_view lua(state);
sol::table result = lua.create_table(static_cast<int>(points.size()), 0);
for (std::size_t i = 0; i < points.size(); ++i)
result[i + 1] = points[i];
return result;
}
Vec3f aabb_top(const Aabbf& aabb, const omath::primitives::UpAxis axis)
{
switch (axis)
{
case omath::primitives::UpAxis::X:
return aabb.top<omath::primitives::UpAxis::X>();
case omath::primitives::UpAxis::Y:
return aabb.top<omath::primitives::UpAxis::Y>();
case omath::primitives::UpAxis::Z:
return aabb.top<omath::primitives::UpAxis::Z>();
}
std::unreachable();
}
Vec3f aabb_bottom(const Aabbf& aabb, const omath::primitives::UpAxis axis)
{
switch (axis)
{
case omath::primitives::UpAxis::X:
return aabb.bottom<omath::primitives::UpAxis::X>();
case omath::primitives::UpAxis::Y:
return aabb.bottom<omath::primitives::UpAxis::Y>();
case omath::primitives::UpAxis::Z:
return aabb.bottom<omath::primitives::UpAxis::Z>();
}
std::unreachable();
}
bool ray_hits_triangle(const Ray3f& ray, const Triangle3f& triangle)
{
return !(LineTracer3f::get_ray_hit_point(ray, triangle) == ray.end);
}
bool ray_hits_aabb(const Ray3f& ray, const Aabbf& aabb)
{
return !(LineTracer3f::get_ray_hit_point(ray, aabb) == ray.end);
}
bool ray_hits_obb(const Ray3f& ray, const Obbf& obb)
{
return !(LineTracer3f::get_ray_hit_point(ray, obb) == ray.end);
}
} // namespace
namespace omath::lua
{
void LuaInterpreter::register_3d_primitives(sol::table& omath_table)
{
auto primitives_table = omath_table["primitives"].get_or_create<sol::table>();
primitives_table.new_enum("UpAxis", "X", omath::primitives::UpAxis::X, "Y", omath::primitives::UpAxis::Y, "Z",
omath::primitives::UpAxis::Z);
primitives_table.new_usertype<Aabbf>(
"Aabb",
sol::factories([]() { return Aabbf{}; },
[](const Vec3f& min, const Vec3f& max) { return Aabbf{min, max}; }),
"min", lua_field(&Aabbf::min), "max", lua_field(&Aabbf::max), "center", &Aabbf::center, "extents",
&Aabbf::extents,
"top",
[](const Aabbf& aabb, sol::optional<omath::primitives::UpAxis> axis)
{
return aabb_top(aabb, axis.value_or(omath::primitives::UpAxis::Y));
},
"bottom",
[](const Aabbf& aabb, sol::optional<omath::primitives::UpAxis> axis)
{
return aabb_bottom(aabb, axis.value_or(omath::primitives::UpAxis::Y));
},
"is_collide", &Aabbf::is_collide,
"as_table",
[](const Aabbf& aabb, sol::this_state state) -> sol::table
{
sol::state_view lua(state);
sol::table result = lua.create_table();
result["min"] = aabb.min;
result["max"] = aabb.max;
return result;
});
primitives_table.new_usertype<Obbf>(
"Obb",
sol::factories(
[]()
{
return Obbf{};
},
[](const Vec3f& center, const Vec3f& axis_x, const Vec3f& axis_y, const Vec3f& axis_z,
const Vec3f& half_extents)
{
return Obbf{center, axis_x, axis_y, axis_z, half_extents};
}),
"center", lua_field(&Obbf::center), "axis_x", lua_field(&Obbf::axis_x), "axis_y",
lua_field(&Obbf::axis_y), "axis_z", lua_field(&Obbf::axis_z), "half_extents",
lua_field(&Obbf::half_extents),
"vertices",
[](const Obbf& obb, sol::this_state state)
{
return vec3_array_to_table(obb.vertices(), state);
});
}
void LuaInterpreter::register_collision(sol::table& omath_table)
{
auto collision_table = omath_table["collision"].get_or_create<sol::table>();
collision_table.new_usertype<Ray3f>(
"Ray",
sol::factories([]() { return Ray3f{}; },
[](const Vec3f& start, const Vec3f& end) { return Ray3f{start, end}; },
[](const Vec3f& start, const Vec3f& end, const bool infinite_length)
{ return Ray3f{start, end, infinite_length}; }),
"start", lua_field(&Ray3f::start), "end", lua_field(&Ray3f::end), "infinite_length",
lua_field(&Ray3f::infinite_length),
"direction_vector", &Ray3f::direction_vector, "direction_vector_normalized",
&Ray3f::direction_vector_normalized);
collision_table.new_usertype<LuaConvexCollider>(
"ConvexCollider",
sol::factories([](const sol::table& vertices) { return LuaConvexCollider(vec3_table_to_vector(vertices)); },
[](const sol::table& vertices, const Vec3f& origin)
{ return LuaConvexCollider(vec3_table_to_vector(vertices), origin); }),
"find_abs_furthest_vertex_position", &LuaConvexCollider::find_abs_furthest_vertex_position,
"get_origin", &LuaConvexCollider::get_origin, "set_origin", &LuaConvexCollider::set_origin,
"vertex_count", &LuaConvexCollider::vertex_count,
"vertices",
[](const LuaConvexCollider& collider, sol::this_state state)
{
return vec3_array_to_table(collider.vertices(), state);
});
collision_table.new_usertype<LineTracer3f>(
"LineTracer", sol::no_constructor, "can_trace_line", &LineTracer3f::can_trace_line,
"get_ray_hit_point",
sol::overload(
[](const Ray3f& ray, const Triangle3f& triangle)
{
return LineTracer3f::get_ray_hit_point(ray, triangle);
},
[](const Ray3f& ray, const Aabbf& aabb)
{
return LineTracer3f::get_ray_hit_point(ray, aabb);
},
[](const Ray3f& ray, const Obbf& obb)
{
return LineTracer3f::get_ray_hit_point(ray, obb);
}),
"ray_hits_triangle", &ray_hits_triangle, "ray_hits_aabb", &ray_hits_aabb, "ray_hits_obb",
&ray_hits_obb);
collision_table["gjk_support_vertex"] =
[](const LuaConvexCollider& collider_a, const LuaConvexCollider& collider_b, const Vec3f& direction)
{ return omath::collision::GjkAlgorithm<LuaConvexCollider>::find_support_vertex(collider_a, collider_b, direction); };
collision_table["gjk_collide"] = [](const LuaConvexCollider& collider_a, const LuaConvexCollider& collider_b)
{ return omath::collision::GjkAlgorithm<LuaConvexCollider>::is_collide(collider_a, collider_b); };
collision_table["epa_solve"] = [](const LuaConvexCollider& collider_a, const LuaConvexCollider& collider_b,
sol::this_state state) -> sol::object
{
using Gjk = omath::collision::GjkAlgorithm<LuaConvexCollider>;
using Epa = omath::collision::Epa<LuaConvexCollider>;
sol::state_view lua(state);
const auto gjk = Gjk::is_collide_with_simplex_info(collider_a, collider_b);
if (!gjk.hit)
return sol::nil;
const auto epa = Epa::solve(collider_a, collider_b, gjk.simplex);
if (!epa)
return sol::nil;
sol::table result = lua.create_table();
result["normal"] = epa->normal;
result["penetration_vector"] = epa->penetration_vector;
result["depth"] = epa->depth;
result["iterations"] = epa->iterations;
result["num_vertices"] = epa->num_vertices;
result["num_faces"] = epa->num_faces;
return sol::make_object(lua, result);
};
}
} // namespace omath::lua
#endif
+53 -15
View File
@@ -9,11 +9,14 @@
#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>
#include <omath/engines/opengl_engine/camera.hpp> #include <omath/engines/opengl_engine/camera.hpp>
#include <omath/engines/rage_engine/camera.hpp>
#include <omath/engines/source_engine/camera.hpp> #include <omath/engines/source_engine/camera.hpp>
#include <omath/engines/unity_engine/camera.hpp> #include <omath/engines/unity_engine/camera.hpp>
#include <omath/engines/unreal_engine/camera.hpp> #include <omath/engines/unreal_engine/camera.hpp>
#include <sol/sol.hpp> #include <sol/sol.hpp>
#include <string_view> #include <string_view>
#include <type_traits>
#include <utility>
namespace namespace
{ {
@@ -85,44 +88,72 @@ namespace
using PitchAngle = typename EngineTraits::PitchAngle; using PitchAngle = typename EngineTraits::PitchAngle;
using ViewAngles = typename EngineTraits::ViewAngles; using ViewAngles = typename EngineTraits::ViewAngles;
using Camera = typename EngineTraits::Camera; using Camera = typename EngineTraits::Camera;
using Mat4X4 = std::remove_cvref_t<decltype(std::declval<const Camera&>().get_view_matrix())>;
auto engine_table = omath_table[subtable_name].get_or_create<sol::table>(); auto engine_table = omath_table[subtable_name].get_or_create<sol::table>();
auto types = omath_table["_types"].get<sol::table>(); auto types = omath_table["_types"].get<sol::table>();
set_engine_aliases<PitchAngle, ViewAngles>(engine_table, types); set_engine_aliases<PitchAngle, ViewAngles>(engine_table, types);
engine_table.new_usertype<Camera>( auto camera_type = engine_table.new_usertype<Camera>(
"Camera", "Camera",
sol::constructors<Camera(const omath::Vector3<ArithmeticType>&, 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&,
ArithmeticType, ArithmeticType)>(), ArithmeticType, ArithmeticType)>());
"look_at", &Camera::look_at, "get_forward", &Camera::get_forward, "get_right", &Camera::get_right,
"get_up", &Camera::get_up, "get_origin", &Camera::get_origin, "get_view_angles",
&Camera::get_view_angles, "get_near_plane", &Camera::get_near_plane, "get_far_plane",
&Camera::get_far_plane, "get_field_of_view", &Camera::get_field_of_view, "set_origin",
&Camera::set_origin, "set_view_angles", &Camera::set_view_angles, "set_view_port",
&Camera::set_view_port, "set_field_of_view", &Camera::set_field_of_view, "set_near_plane",
&Camera::set_near_plane, "set_far_plane", &Camera::set_far_plane,
"world_to_screen", camera_type["look_at"] = &Camera::look_at;
[](const Camera& cam, const omath::Vector3<ArithmeticType>& pos) camera_type["get_forward"] = &Camera::get_forward;
camera_type["get_right"] = &Camera::get_right;
camera_type["get_up"] = &Camera::get_up;
camera_type["get_origin"] = &Camera::get_origin;
camera_type["get_view_angles"] = &Camera::get_view_angles;
camera_type["get_near_plane"] = &Camera::get_near_plane;
camera_type["get_far_plane"] = &Camera::get_far_plane;
camera_type["get_field_of_view"] = &Camera::get_field_of_view;
camera_type["set_origin"] = &Camera::set_origin;
camera_type["set_view_angles"] = &Camera::set_view_angles;
camera_type["set_view_port"] = &Camera::set_view_port;
camera_type["set_field_of_view"] = &Camera::set_field_of_view;
camera_type["set_near_plane"] = &Camera::set_near_plane;
camera_type["set_far_plane"] = &Camera::set_far_plane;
camera_type["get_view_matrix"] = [](const Camera& cam) -> Mat4X4
{
return cam.get_view_matrix();
};
camera_type["get_projection_matrix"] = [](const Camera& cam) -> Mat4X4
{
return cam.get_projection_matrix();
};
camera_type["get_view_projection_matrix"] = [](const Camera& cam) -> Mat4X4
{
return cam.get_view_projection_matrix();
};
camera_type["extract_projection_params"] = [](const Mat4X4& projection_matrix)
{
const auto params = Camera::extract_projection_params(projection_matrix);
return std::make_tuple(params.fov, params.aspect_ratio);
};
camera_type["calc_view_angles_from_view_matrix"] = &Camera::calc_view_angles_from_view_matrix;
camera_type["calc_origin_from_view_matrix"] = &Camera::calc_origin_from_view_matrix;
camera_type["world_to_screen"] = [](const Camera& cam, const omath::Vector3<ArithmeticType>& pos)
-> std::tuple<sol::optional<omath::Vector3<ArithmeticType>>, 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)
return {*result, sol::nullopt}; return {*result, sol::nullopt};
return {sol::nullopt, projection_error_to_string(result.error())}; return {sol::nullopt, projection_error_to_string(result.error())};
}, };
"screen_to_world", camera_type["screen_to_world"] = [](const Camera& cam, const omath::Vector3<ArithmeticType>& pos)
[](const Camera& cam, const omath::Vector3<ArithmeticType>& pos)
-> std::tuple<sol::optional<omath::Vector3<ArithmeticType>>, 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)
return {*result, sol::nullopt}; return {*result, sol::nullopt};
return {sol::nullopt, projection_error_to_string(result.error())}; return {sol::nullopt, projection_error_to_string(result.error())};
}); };
} }
// ---- Engine trait structs ----------------------------------------------- // ---- Engine trait structs -----------------------------------------------
@@ -151,6 +182,12 @@ namespace
using ViewAngles = omath::source_engine::ViewAngles; using ViewAngles = omath::source_engine::ViewAngles;
using Camera = omath::source_engine::Camera; using Camera = omath::source_engine::Camera;
}; };
struct RageEngineTraits
{
using PitchAngle = omath::rage_engine::PitchAngle;
using ViewAngles = omath::rage_engine::ViewAngles;
using Camera = omath::rage_engine::Camera;
};
struct UnityEngineTraits struct UnityEngineTraits
{ {
using PitchAngle = omath::unity_engine::PitchAngle; using PitchAngle = omath::unity_engine::PitchAngle;
@@ -224,6 +261,7 @@ namespace omath::lua
register_engine<FrostbiteEngineTraits>(omath_table, "frostbite"); register_engine<FrostbiteEngineTraits>(omath_table, "frostbite");
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<RageEngineTraits>(omath_table, "rage");
register_engine<UnityEngineTraits>(omath_table, "unity"); register_engine<UnityEngineTraits>(omath_table, "unity");
register_engine<UnrealEngineTraits, double>(omath_table, "unreal"); register_engine<UnrealEngineTraits, double>(omath_table, "unreal");
register_engine<CryEngineTraits>(omath_table, "cry"); register_engine<CryEngineTraits>(omath_table, "cry");
+525
View File
@@ -0,0 +1,525 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <omath/hud/canvas_box.hpp>
#include <omath/hud/entity_overlay.hpp>
#include <sol/sol.hpp>
#include <any>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
namespace
{
[[noreturn]]
void throw_lua_error(sol::protected_function_result& result)
{
sol::error err = result;
throw err;
}
class LuaHudRenderer final : public omath::hud::HudRendererInterface
{
public:
explicit LuaHudRenderer(sol::table callbacks): m_callbacks(std::move(callbacks))
{
}
void add_line(const omath::Vector2<float>& line_start, const omath::Vector2<float>& line_end,
const omath::Color& color, const float thickness) override
{
call_optional("add_line", line_start, line_end, color, thickness);
}
void add_polyline(const std::span<const omath::Vector2<float>>& vertexes, const omath::Color& color,
const float thickness) override
{
call_optional("add_polyline", make_points_table(vertexes), color, thickness);
}
void add_filled_polyline(const std::span<const omath::Vector2<float>>& vertexes,
const omath::Color& color) override
{
call_optional("add_filled_polyline", make_points_table(vertexes), color);
}
void add_rectangle(const omath::Vector2<float>& min, const omath::Vector2<float>& max,
const omath::Color& color) override
{
call_optional("add_rectangle", min, max, color);
}
void add_filled_rectangle(const omath::Vector2<float>& min, const omath::Vector2<float>& max,
const omath::Color& color) override
{
call_optional("add_filled_rectangle", min, max, color);
}
void add_circle(const omath::Vector2<float>& center, const float radius, const omath::Color& color,
const float thickness, const int segments) override
{
call_optional("add_circle", center, radius, color, thickness, segments);
}
void add_filled_circle(const omath::Vector2<float>& center, const float radius, const omath::Color& color,
const int segments) override
{
call_optional("add_filled_circle", center, radius, color, segments);
}
void add_arc(const omath::Vector2<float>& center, const float radius, const float a_min, const float a_max,
const omath::Color& color, const float thickness, const int segments) override
{
call_optional("add_arc", center, radius, a_min, a_max, color, thickness, segments);
}
void add_image(const std::any& texture_id, const omath::Vector2<float>& min, const omath::Vector2<float>& max,
const omath::Color& tint) override
{
const auto callback = callback_for("add_image");
if (!callback)
return;
sol::object texture = sol::nil;
if (const auto lua_object = std::any_cast<sol::object>(&texture_id))
texture = *lua_object;
auto result = (*callback)(texture, min, max, tint);
if (!result.valid())
throw_lua_error(result);
}
void add_text(const omath::Vector2<float>& position, const omath::Color& color,
const std::string_view& text) override
{
call_optional("add_text", position, color, std::string{text});
}
[[nodiscard]]
omath::Vector2<float> calc_text_size(const std::string_view& text) override
{
const auto callback = callback_for("calc_text_size");
if (!callback)
return {};
auto result = (*callback)(std::string{text});
if (!result.valid())
throw_lua_error(result);
return result.get<omath::Vector2<float>>();
}
private:
sol::main_table m_callbacks;
[[nodiscard]]
sol::optional<sol::protected_function> callback_for(const char* name) const
{
const sol::object callback = m_callbacks[name];
if (!callback.valid() || callback == sol::nil)
return sol::nullopt;
return callback.as<sol::protected_function>();
}
template<class... Args>
void call_optional(const char* name, Args&&... args) const
{
const auto callback = callback_for(name);
if (!callback)
return;
auto result = (*callback)(std::forward<Args>(args)...);
if (!result.valid())
throw_lua_error(result);
}
[[nodiscard]]
sol::table make_points_table(const std::span<const omath::Vector2<float>>& vertexes) const
{
sol::state_view lua(m_callbacks.lua_state());
sol::table points = lua.create_table(static_cast<int>(vertexes.size()), 0);
for (std::size_t i = 0; i < vertexes.size(); ++i)
points[i + 1] = vertexes[i];
return points;
}
};
[[nodiscard]]
omath::Color transparent_black()
{
return {0.f, 0.f, 0.f, 0.f};
}
[[nodiscard]]
omath::Color opaque_white()
{
return {1.f, 1.f, 1.f, 1.f};
}
[[nodiscard]]
omath::hud::widget::Outlined outlined_from_bool(const bool outlined)
{
return outlined ? omath::hud::widget::Outlined::On : omath::hud::widget::Outlined::Off;
}
[[nodiscard]]
omath::hud::EntityOverlay make_overlay(const omath::Vector2<float>& top, const omath::Vector2<float>& bottom,
const std::shared_ptr<LuaHudRenderer>& renderer)
{
if (!renderer)
throw std::invalid_argument("hud renderer must not be nil");
return {top, bottom, 4.f, renderer};
}
[[nodiscard]]
std::any texture_id_from_lua_object(const sol::object& texture_id)
{
return texture_id;
}
std::vector<omath::Vector2<float>> points_from_table(const sol::table& points)
{
std::vector<omath::Vector2<float>> result;
for (std::size_t i = 1;; ++i)
{
const auto point = points[i].get<sol::optional<omath::Vector2<float>>>();
if (!point)
break;
result.push_back(*point);
}
return result;
}
template<class Object, class Value>
auto lua_field(Value Object::* member)
{
return sol::property(
[member](const Object& object) -> const Value&
{
return object.*member;
},
[member](Object& object, const Value& value)
{
object.*member = value;
});
}
} // namespace
namespace omath::lua
{
void LuaInterpreter::register_hud(sol::table& omath_table)
{
auto hud_table = omath_table["hud"].get_or_create<sol::table>();
hud_table.new_usertype<omath::hud::CanvasBox>(
"CanvasBox",
sol::factories([](const omath::Vector2<float>& top, const omath::Vector2<float>& bottom)
{ return omath::hud::CanvasBox(top, bottom); },
[](const omath::Vector2<float>& top, const omath::Vector2<float>& bottom,
const float ratio) { return omath::hud::CanvasBox(top, bottom, ratio); }),
"top_left_corner", lua_field(&omath::hud::CanvasBox::top_left_corner), "top_right_corner",
lua_field(&omath::hud::CanvasBox::top_right_corner), "bottom_left_corner",
lua_field(&omath::hud::CanvasBox::bottom_left_corner), "bottom_right_corner",
lua_field(&omath::hud::CanvasBox::bottom_right_corner),
"as_table",
[](const omath::hud::CanvasBox& box, sol::this_state s) -> sol::table
{
sol::state_view lua(s);
sol::table t = lua.create_table(4, 0);
const auto points = box.as_array();
for (std::size_t i = 0; i < points.size(); ++i)
t[i + 1] = points[i];
return t;
});
hud_table.new_usertype<LuaHudRenderer>(
"Renderer",
sol::factories([](const sol::table& callbacks)
{ return std::make_shared<LuaHudRenderer>(callbacks); }),
"add_line", &LuaHudRenderer::add_line,
"add_polyline",
[](LuaHudRenderer& renderer, const sol::table& points, const omath::Color& color,
const float thickness)
{
const auto vertices = points_from_table(points);
renderer.add_polyline({vertices.data(), vertices.size()}, color, thickness);
},
"add_filled_polyline",
[](LuaHudRenderer& renderer, const sol::table& points, const omath::Color& color)
{
const auto vertices = points_from_table(points);
renderer.add_filled_polyline({vertices.data(), vertices.size()}, color);
},
"add_rectangle", &LuaHudRenderer::add_rectangle, "add_filled_rectangle",
&LuaHudRenderer::add_filled_rectangle, "add_circle",
[](LuaHudRenderer& renderer, const omath::Vector2<float>& center, const float radius,
const omath::Color& color, const float thickness, sol::optional<int> segments)
{
renderer.add_circle(center, radius, color, thickness, segments.value_or(0));
},
"add_filled_circle",
[](LuaHudRenderer& renderer, const omath::Vector2<float>& center, const float radius,
const omath::Color& color, sol::optional<int> segments)
{
renderer.add_filled_circle(center, radius, color, segments.value_or(0));
},
"add_arc",
[](LuaHudRenderer& renderer, const omath::Vector2<float>& center, const float radius,
const float a_min, const float a_max, const omath::Color& color, const float thickness,
sol::optional<int> segments)
{
renderer.add_arc(center, radius, a_min, a_max, color, thickness, segments.value_or(0));
},
"add_image",
[](LuaHudRenderer& renderer, const sol::object& texture_id, const omath::Vector2<float>& min,
const omath::Vector2<float>& max, sol::optional<omath::Color> tint)
{
renderer.add_image(texture_id_from_lua_object(texture_id), min, max,
tint.value_or(opaque_white()));
},
"add_text", &LuaHudRenderer::add_text, "calc_text_size", &LuaHudRenderer::calc_text_size);
hud_table.new_usertype<omath::hud::EntityOverlay>(
"EntityOverlay",
sol::factories(&make_overlay),
"add_2d_box",
[](omath::hud::EntityOverlay& overlay, const omath::Color& box_color,
sol::optional<omath::Color> fill_color, sol::optional<omath::Color> outline_color,
sol::optional<float> thickness) -> omath::hud::EntityOverlay&
{
return overlay.add_2d_box(box_color, fill_color.value_or(transparent_black()),
outline_color.value_or(transparent_black()), thickness.value_or(1.f));
},
"add_cornered_2d_box",
[](omath::hud::EntityOverlay& overlay, const omath::Color& box_color,
sol::optional<omath::Color> fill_color, sol::optional<omath::Color> outline_color,
sol::optional<float> corner_ratio_len,
sol::optional<float> thickness) -> omath::hud::EntityOverlay&
{
return overlay.add_cornered_2d_box(box_color, fill_color.value_or(transparent_black()),
outline_color.value_or(transparent_black()),
corner_ratio_len.value_or(0.2f), thickness.value_or(1.f));
},
"add_dashed_box",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, sol::optional<float> dash_len,
sol::optional<float> gap_len, sol::optional<float> thickness) -> omath::hud::EntityOverlay&
{
return overlay.add_dashed_box(color, dash_len.value_or(8.f), gap_len.value_or(5.f),
thickness.value_or(1.f));
},
"add_right_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float width, const float ratio,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_right_bar(color, outline_color, bg_color, width, ratio, offset.value_or(5.f));
},
"add_left_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float width, const float ratio,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_left_bar(color, outline_color, bg_color, width, ratio, offset.value_or(5.f));
},
"add_top_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float height, const float ratio,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_top_bar(color, outline_color, bg_color, height, ratio, offset.value_or(5.f));
},
"add_bottom_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float height, const float ratio,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_bar(color, outline_color, bg_color, height, ratio, offset.value_or(5.f));
},
"add_right_dashed_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float width, const float ratio, const float dash_len,
const float gap_len, sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_right_dashed_bar(color, outline_color, bg_color, width, ratio, dash_len, gap_len,
offset.value_or(5.f));
},
"add_left_dashed_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float width, const float ratio, const float dash_len,
const float gap_len, sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_left_dashed_bar(color, outline_color, bg_color, width, ratio, dash_len, gap_len,
offset.value_or(5.f));
},
"add_top_dashed_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float height, const float ratio, const float dash_len,
const float gap_len, sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_top_dashed_bar(color, outline_color, bg_color, height, ratio, dash_len, gap_len,
offset.value_or(5.f));
},
"add_bottom_dashed_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float height, const float ratio, const float dash_len,
const float gap_len, sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_dashed_bar(color, outline_color, bg_color, height, ratio, dash_len,
gap_len, offset.value_or(5.f));
},
"add_right_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_right_label(color, offset, outlined_from_bool(outlined), text);
},
"add_left_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_left_label(color, offset, outlined_from_bool(outlined), text);
},
"add_top_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_top_label(color, offset, outlined_from_bool(outlined), text);
},
"add_bottom_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_label(color, offset, outlined_from_bool(outlined), text);
},
"add_centered_top_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_centered_top_label(color, offset, outlined_from_bool(outlined), text);
},
"add_centered_bottom_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_centered_bottom_label(color, offset, outlined_from_bool(outlined), text);
},
"add_right_space_vertical", &omath::hud::EntityOverlay::add_right_space_vertical,
"add_right_space_horizontal", &omath::hud::EntityOverlay::add_right_space_horizontal,
"add_left_space_vertical", &omath::hud::EntityOverlay::add_left_space_vertical,
"add_left_space_horizontal", &omath::hud::EntityOverlay::add_left_space_horizontal,
"add_top_space_vertical", &omath::hud::EntityOverlay::add_top_space_vertical,
"add_top_space_horizontal", &omath::hud::EntityOverlay::add_top_space_horizontal,
"add_bottom_space_vertical", &omath::hud::EntityOverlay::add_bottom_space_vertical,
"add_bottom_space_horizontal", &omath::hud::EntityOverlay::add_bottom_space_horizontal,
"add_right_progress_ring",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& bg,
const float radius, const float ratio, sol::optional<float> thickness, sol::optional<float> offset,
sol::optional<int> segments) -> omath::hud::EntityOverlay&
{
return overlay.add_right_progress_ring(color, bg, radius, ratio, thickness.value_or(2.f),
offset.value_or(5.f), segments.value_or(0));
},
"add_left_progress_ring",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& bg,
const float radius, const float ratio, sol::optional<float> thickness, sol::optional<float> offset,
sol::optional<int> segments) -> omath::hud::EntityOverlay&
{
return overlay.add_left_progress_ring(color, bg, radius, ratio, thickness.value_or(2.f),
offset.value_or(5.f), segments.value_or(0));
},
"add_top_progress_ring",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& bg,
const float radius, const float ratio, sol::optional<float> thickness, sol::optional<float> offset,
sol::optional<int> segments) -> omath::hud::EntityOverlay&
{
return overlay.add_top_progress_ring(color, bg, radius, ratio, thickness.value_or(2.f),
offset.value_or(5.f), segments.value_or(0));
},
"add_bottom_progress_ring",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& bg,
const float radius, const float ratio, sol::optional<float> thickness, sol::optional<float> offset,
sol::optional<int> segments) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_progress_ring(color, bg, radius, ratio, thickness.value_or(2.f),
offset.value_or(5.f), segments.value_or(0));
},
"add_right_icon",
[](omath::hud::EntityOverlay& overlay, const sol::object& texture_id, const float width,
const float height, sol::optional<omath::Color> tint,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_right_icon(texture_id_from_lua_object(texture_id), width, height,
tint.value_or(opaque_white()), offset.value_or(5.f));
},
"add_left_icon",
[](omath::hud::EntityOverlay& overlay, const sol::object& texture_id, const float width,
const float height, sol::optional<omath::Color> tint,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_left_icon(texture_id_from_lua_object(texture_id), width, height,
tint.value_or(opaque_white()), offset.value_or(5.f));
},
"add_top_icon",
[](omath::hud::EntityOverlay& overlay, const sol::object& texture_id, const float width,
const float height, sol::optional<omath::Color> tint,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_top_icon(texture_id_from_lua_object(texture_id), width, height,
tint.value_or(opaque_white()), offset.value_or(5.f));
},
"add_bottom_icon",
[](omath::hud::EntityOverlay& overlay, const sol::object& texture_id, const float width,
const float height, sol::optional<omath::Color> tint,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_icon(texture_id_from_lua_object(texture_id), width, height,
tint.value_or(opaque_white()), offset.value_or(5.f));
},
"add_snap_line", &omath::hud::EntityOverlay::add_snap_line, "add_skeleton",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color,
sol::optional<float> thickness) -> omath::hud::EntityOverlay&
{
return overlay.add_skeleton(color, thickness.value_or(1.f));
},
"add_scan_marker",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color,
sol::optional<omath::Color> outline, sol::optional<float> outline_thickness)
-> omath::hud::EntityOverlay&
{
return overlay.contents(omath::hud::widget::ScanMarker{
color, outline.value_or(transparent_black()), outline_thickness.value_or(1.f)});
},
"add_aim_dot",
[](omath::hud::EntityOverlay& overlay, const omath::Vector2<float>& position,
const omath::Color& color, sol::optional<float> radius) -> omath::hud::EntityOverlay&
{
return overlay.contents(omath::hud::widget::AimDot{position, color, radius.value_or(3.f)});
},
"add_projectile_aim",
[](omath::hud::EntityOverlay& overlay, const omath::Vector2<float>& position,
const omath::Color& color, sol::optional<float> size, sol::optional<float> line_size,
sol::optional<omath::hud::widget::ProjectileAim::Figure> figure) -> omath::hud::EntityOverlay&
{
return overlay.contents(omath::hud::widget::ProjectileAim{
position, color, size.value_or(3.f), line_size.value_or(1.f),
figure.value_or(omath::hud::widget::ProjectileAim::Figure::SQUARE)});
});
hud_table.new_enum("ProjectileAimFigure", "CIRCLE", omath::hud::widget::ProjectileAim::Figure::CIRCLE,
"SQUARE", omath::hud::widget::ProjectileAim::Figure::SQUARE);
}
} // namespace omath::lua
#endif
+156
View File
@@ -0,0 +1,156 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <omath/linear_algebra/mat.hpp>
#include <sol/sol.hpp>
#include <stdexcept>
namespace
{
template<std::size_t Limit>
std::size_t checked_index(const int index)
{
if (index < 0 || index >= static_cast<int>(Limit))
throw std::out_of_range("matrix index is out of range");
return static_cast<std::size_t>(index);
}
template<std::size_t Rows, std::size_t Columns, class Type, omath::MatStoreType StoreType>
omath::Mat<Rows, Columns, Type, StoreType> mat_from_rows(const sol::table& rows)
{
omath::Mat<Rows, Columns, Type, StoreType> result;
for (std::size_t row = 0; row < Rows; ++row)
{
const auto row_table = rows[row + 1].get<sol::optional<sol::table>>();
if (!row_table)
throw std::invalid_argument("matrix rows must be tables");
for (std::size_t column = 0; column < Columns; ++column)
{
const auto value = (*row_table)[column + 1].get<sol::optional<Type>>();
if (!value)
throw std::invalid_argument("matrix row has missing value");
result.at(row, column) = *value;
}
}
return result;
}
template<std::size_t Rows, std::size_t Columns, class Type, omath::MatStoreType StoreType>
sol::table mat_as_table(const omath::Mat<Rows, Columns, Type, StoreType>& mat, sol::this_state state)
{
sol::state_view lua(state);
sol::table rows = lua.create_table();
for (std::size_t row = 0; row < Rows; ++row)
{
sol::table row_table = lua.create_table();
for (std::size_t column = 0; column < Columns; ++column)
row_table[column + 1] = mat.at(row, column);
rows[row + 1] = row_table;
}
return rows;
}
template<std::size_t Size, class Type, omath::MatStoreType StoreType, bool RegisterMat4Helpers = false>
void register_square_mat(sol::table& omath_table, const char* name)
{
using MatType = omath::Mat<Size, Size, Type, StoreType>;
auto type = omath_table.new_usertype<MatType>(
name, sol::constructors<MatType()>(),
"from_rows", &mat_from_rows<Size, Size, Type, StoreType>, "row_count",
[]()
{
return Size;
},
"columns_count",
[]()
{
return Size;
},
"at",
[](const MatType& mat, const int row, const int column)
{
return mat.at(checked_index<Size>(row), checked_index<Size>(column));
},
"set_at",
[](MatType& mat, const int row, const int column, const Type value)
{
mat.at(checked_index<Size>(row), checked_index<Size>(column)) = value;
return mat;
},
sol::meta_function::multiplication,
sol::overload(
[](const MatType& lhs, const MatType& rhs)
{
return lhs * rhs;
},
[](const MatType& mat, const Type scalar)
{
return mat * scalar;
},
[](const Type scalar, const MatType& mat)
{
return mat * scalar;
}),
sol::meta_function::division,
[](const MatType& mat, const Type scalar)
{
return mat / scalar;
},
sol::meta_function::equal_to, &MatType::operator==, sol::meta_function::to_string, &MatType::to_string,
"transposed", &MatType::transposed, "determinant", &MatType::determinant, "sum", &MatType::sum, "clear",
&MatType::clear, "set", &MatType::set, "inverted",
[](const MatType& mat) -> sol::optional<MatType>
{
auto result = mat.inverted();
if (!result)
return sol::nullopt;
return *result;
},
"as_table", &mat_as_table<Size, Size, Type, StoreType>);
if constexpr (RegisterMat4Helpers)
{
type["to_screen_mat"] = [](const Type screen_width, const Type screen_height)
{
return MatType{
{screen_width / Type{2}, Type{0}, Type{0}, Type{0}},
{Type{0}, -screen_height / Type{2}, Type{0}, Type{0}},
{Type{0}, Type{0}, Type{1}, Type{0}},
{screen_width / Type{2}, screen_height / Type{2}, Type{0}, Type{1}},
};
};
type["translation"] = &omath::mat_translation<Type, StoreType>;
type["scale"] = &omath::mat_scale<Type, StoreType>;
type["look_at_left_handed"] = &omath::mat_look_at_left_handed<Type, StoreType>;
type["look_at_right_handed"] = &omath::mat_look_at_right_handed<Type, StoreType>;
type["perspective_left_handed_vertical_fov"] =
&omath::mat_perspective_left_handed_vertical_fov<Type, StoreType>;
type["perspective_right_handed_vertical_fov"] =
&omath::mat_perspective_right_handed_vertical_fov<Type, StoreType>;
}
}
} // namespace
namespace omath::lua
{
void LuaInterpreter::register_matrices(sol::table& omath_table)
{
register_square_mat<3, float, omath::MatStoreType::ROW_MAJOR>(omath_table, "Mat3");
register_square_mat<4, float, omath::MatStoreType::ROW_MAJOR, true>(omath_table, "Mat4");
register_square_mat<4, float, omath::MatStoreType::COLUMN_MAJOR, true>(omath_table, "Mat4ColumnMajor");
register_square_mat<4, double, omath::MatStoreType::ROW_MAJOR>(omath_table, "Mat4d");
}
} // namespace omath::lua
#endif
+95
View File
@@ -0,0 +1,95 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <omath/linear_algebra/quaternion.hpp>
#include <sol/sol.hpp>
namespace omath::lua
{
void LuaInterpreter::register_quaternion(sol::table& omath_table)
{
using Quatf = omath::Quaternion<float>;
using Vec3f = omath::Vector3<float>;
omath_table.new_usertype<Quatf>(
"Quaternion", sol::constructors<Quatf(), Quatf(float, float, float, float)>(),
"from_axis_angle", &Quatf::from_axis_angle,
"x",
sol::property(
[](const Quatf& q)
{
return q.x;
},
[](Quatf& q, float val)
{
q.x = val;
}),
"y",
sol::property(
[](const Quatf& q)
{
return q.y;
},
[](Quatf& q, float val)
{
q.y = val;
}),
"z",
sol::property(
[](const Quatf& q)
{
return q.z;
},
[](Quatf& q, float val)
{
q.z = val;
}),
"w",
sol::property(
[](const Quatf& q)
{
return q.w;
},
[](Quatf& q, float val)
{
q.w = val;
}),
sol::meta_function::addition, sol::resolve<Quatf(const Quatf&) const>(&Quatf::operator+),
sol::meta_function::multiplication,
sol::overload(sol::resolve<Quatf(const Quatf&) const>(&Quatf::operator*),
sol::resolve<Quatf(const float&) const>(&Quatf::operator*),
[](float s, const Quatf& q)
{
return q * s;
}),
sol::meta_function::unary_minus, sol::resolve<Quatf() const>(&Quatf::operator-),
sol::meta_function::equal_to, &Quatf::operator==, sol::meta_function::to_string,
[](const Quatf& q)
{
return std::format("Quaternion({}, {}, {}, {})", q.x, q.y, q.z, q.w);
},
"conjugate", &Quatf::conjugate, "dot", &Quatf::dot, "length", &Quatf::length, "length_sqr",
&Quatf::length_sqr, "normalized", &Quatf::normalized, "inverse", &Quatf::inverse, "rotate",
sol::resolve<Vec3f(const Vec3f&) const>(&Quatf::rotate), "to_rotation_matrix3",
&Quatf::to_rotation_matrix3, "to_rotation_matrix4", &Quatf::to_rotation_matrix4,
"as_table",
[](const Quatf& q, sol::this_state s) -> sol::table
{
sol::state_view lua(s);
sol::table t = lua.create_table();
t["x"] = q.x;
t["y"] = q.y;
t["z"] = q.z;
t["w"] = q.w;
return t;
});
}
} // namespace omath::lua
#endif
+92 -8
View File
@@ -304,8 +304,8 @@ namespace
elf_headers); elf_headers);
} }
template<class FileHeader, class SectionHeader> template<class FileHeader, class SectionHeader, class ScanPattern>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern, std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern,
const std::string_view target_section_name) const std::string_view target_section_name)
{ {
const auto* file_header = reinterpret_cast<const FileHeader*>(base); const auto* file_header = reinterpret_cast<const FileHeader*>(base);
@@ -336,13 +336,14 @@ namespace
continue; continue;
const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr); const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr);
const auto* section_end = section_begin + static_cast<std::size_t>(section->sh_size); const auto scan_range =
std::span<const std::byte>{section_begin, static_cast<std::size_t>(section->sh_size)};
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern); const auto target_offset = scan_pattern(scan_range);
if (scan_result == section_end) if (!target_offset.has_value())
return std::nullopt; return std::nullopt;
return reinterpret_cast<std::uintptr_t>(scan_result); return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value());
} }
return std::nullopt; return std::nullopt;
@@ -374,15 +375,58 @@ namespace omath
if (!arch.has_value()) [[unlikely]] if (!arch.has_value()) [[unlikely]]
return std::nullopt; return std::nullopt;
const auto scan_pattern =
[&pattern](const std::span<const std::byte> section_data) -> std::optional<std::ptrdiff_t>
{
const auto scan_result =
PatternScanner::scan_for_pattern(section_data.begin(), section_data.end(), pattern);
if (scan_result == section_data.end())
return std::nullopt;
return scan_result - section_data.begin();
};
if (arch == FileArch::x64) if (arch == FileArch::x64)
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address), return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address),
pattern, target_section_name); scan_pattern, target_section_name);
if (arch == FileArch::x32) if (arch == FileArch::x32)
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address), return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address),
pattern, target_section_name); scan_pattern, target_section_name);
std::unreachable(); std::unreachable();
} }
std::optional<std::uintptr_t>
ElfPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
constexpr std::string_view valid_elf_signature = "\x7F"
"ELF";
if (std::string_view{reinterpret_cast<const char*>(base), valid_elf_signature.size()} != valid_elf_signature)
return std::nullopt;
const auto ei_class_value = static_cast<uint8_t>(base[ei_class]);
const auto arch = ei_class_value == elfclass64 ? FileArch::x64
: ei_class_value == elfclass32 ? FileArch::x32
: std::optional<FileArch>{};
if (!arch.has_value()) [[unlikely]]
return std::nullopt;
if (arch == FileArch::x64)
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(base, scan_pattern, target_section_name);
if (arch == FileArch::x32)
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(base, scan_pattern, target_section_name);
std::unreachable();
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern, const std::string_view& pattern,
@@ -405,6 +449,26 @@ namespace omath
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_elf_section_by_name(path_to_file, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data, ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern, const std::string_view& pattern,
@@ -427,4 +491,24 @@ namespace omath
.raw_base_addr = section->raw_base_addr, .raw_base_addr = section->raw_base_addr,
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_elf_section_by_name_from_memory(file_data, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath } // namespace omath
+89 -10
View File
@@ -316,8 +316,8 @@ namespace
data, section_name); data, section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32) if (magic == mh_magic_32 || magic == mh_cigam_32)
return extract_section_from_memory_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(data, return extract_section_from_memory_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(
section_name); data, section_name);
return std::nullopt; return std::nullopt;
} }
@@ -344,8 +344,9 @@ namespace
return extract_section_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(file, section_name); return extract_section_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(file, section_name);
} }
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd> template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd,
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern, class ScanPattern>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern,
const std::string_view target_section_name) const std::string_view target_section_name)
{ {
const auto* header = reinterpret_cast<const HeaderType*>(base); const auto* header = reinterpret_cast<const HeaderType*>(base);
@@ -374,12 +375,13 @@ namespace
continue; continue;
} }
const auto* section_begin = base + static_cast<std::size_t>(section->addr); const auto* section_begin = base + static_cast<std::size_t>(section->addr);
const auto* section_end = section_begin + static_cast<std::size_t>(section->size); const auto scan_range =
std::span<const std::byte>{section_begin, static_cast<std::size_t>(section->size)};
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern); const auto target_offset = scan_pattern(scan_range);
if (scan_result != section_end) if (target_offset.has_value())
return reinterpret_cast<std::uintptr_t>(scan_result); return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value());
} }
} }
@@ -404,12 +406,48 @@ namespace omath
std::uint32_t magic{}; std::uint32_t magic{};
std::memcpy(&magic, base, sizeof(magic)); std::memcpy(&magic, base, sizeof(magic));
const auto scan_pattern =
[&pattern](const std::span<const std::byte> section_data) -> std::optional<std::ptrdiff_t>
{
const auto scan_result =
PatternScanner::scan_for_pattern(section_data.begin(), section_data.end(), pattern);
if (scan_result == section_data.end())
return std::nullopt;
return scan_result - section_data.begin();
};
if (magic == mh_magic_64 || magic == mh_cigam_64) if (magic == mh_magic_64 || magic == mh_cigam_64)
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, pattern, return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, scan_pattern,
target_section_name); target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32) if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, pattern, return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, scan_pattern,
target_section_name);
return std::nullopt;
}
std::optional<std::uintptr_t>
MachOPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
std::uint32_t magic{};
std::memcpy(&magic, base, sizeof(magic));
if (magic == mh_magic_64 || magic == mh_cigam_64)
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, scan_pattern,
target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, scan_pattern,
target_section_name); target_section_name);
return std::nullopt; return std::nullopt;
@@ -438,6 +476,27 @@ namespace omath
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto macho_section = get_macho_section_by_name(path_to_file, target_section_name);
if (!macho_section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset =
scan_pattern(std::span<const std::byte>{macho_section->data.data(), macho_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = macho_section->virtual_base_addr,
.raw_base_addr = macho_section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data, MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern, const std::string_view& pattern,
@@ -460,4 +519,24 @@ namespace omath
.raw_base_addr = section->raw_base_addr, .raw_base_addr = section->raw_base_addr,
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_macho_section_by_name_from_memory(file_data, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath } // namespace omath
+109 -5
View File
@@ -234,7 +234,11 @@ namespace
constexpr bool invalid_nt_header_file(const NtHeaderVariant& variant) constexpr bool invalid_nt_header_file(const NtHeaderVariant& variant)
{ {
constexpr std::uint32_t nt_hdr_magic = 0x4550; constexpr std::uint32_t nt_hdr_magic = 0x4550;
return std::visit([&nt_hdr_magic](const auto& header) -> bool { return header.signature != nt_hdr_magic; }, return std::visit(
[&nt_hdr_magic](const auto& header) -> bool
{
return header.signature != nt_hdr_magic;
},
variant); variant);
} }
@@ -262,8 +266,7 @@ namespace
if (nt_off + sizeof(ImageNtHeaders<NtArchitecture::x32_bit>) > data.size()) if (nt_off + sizeof(ImageNtHeaders<NtArchitecture::x32_bit>) > data.size())
return std::nullopt; return std::nullopt;
const auto* x86_hdrs = const auto* x86_hdrs = reinterpret_cast<const ImageNtHeaders<NtArchitecture::x32_bit>*>(data.data() + nt_off);
reinterpret_cast<const ImageNtHeaders<NtArchitecture::x32_bit>*>(data.data() + nt_off);
NtHeaderVariant nt_headers; NtHeaderVariant nt_headers;
if (x86_hdrs->optional_header.magic == opt_hdr32_magic) if (x86_hdrs->optional_header.magic == opt_hdr32_magic)
@@ -284,8 +287,8 @@ namespace
[&data, &section_name, nt_off](const auto& concrete_headers) -> std::optional<ExtractedSection> [&data, &section_name, nt_off](const auto& concrete_headers) -> std::optional<ExtractedSection>
{ {
constexpr std::size_t sig_size = sizeof(concrete_headers.signature); constexpr std::size_t sig_size = sizeof(concrete_headers.signature);
const auto section_table_off = nt_off + sig_size + sizeof(FileHeader) const auto section_table_off =
+ concrete_headers.file_header.size_optional_header; nt_off + sig_size + sizeof(FileHeader) + concrete_headers.file_header.size_optional_header;
for (std::size_t i = 0; i < concrete_headers.file_header.num_sections; ++i) for (std::size_t i = 0; i < concrete_headers.file_header.num_sections; ++i)
{ {
@@ -435,6 +438,65 @@ namespace omath
}, },
nt_header_variant.value()); nt_header_variant.value());
} }
std::optional<std::uintptr_t>
PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto base_address = reinterpret_cast<std::uintptr_t>(module_base_address);
const auto* base_bytes = static_cast<const std::byte*>(module_base_address);
if (!base_address)
return std::nullopt;
const auto* dos_header = static_cast<const DosHeader*>(module_base_address);
if (invalid_dos_header_file(*dos_header)) [[unlikely]]
return std::nullopt;
const auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address);
if (!nt_header_variant) [[unlikely]]
return std::nullopt;
return std::visit(
[base_bytes, base_address, lfanew = dos_header->e_lfanew, &target_section_name,
scan_pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
{
constexpr std::size_t signature_size = sizeof(nt_header.signature);
const auto section_table_off = static_cast<std::size_t>(lfanew) + signature_size
+ sizeof(FileHeader) + nt_header.file_header.size_optional_header;
const auto* section_table = reinterpret_cast<const SectionHeader*>(base_bytes + section_table_off);
for (std::size_t i = 0; i < nt_header.file_header.num_sections; ++i)
{
const auto* section = section_table + i;
if (std::string_view{section->name} != target_section_name
|| section->size_raw_data == 0)
continue;
const auto section_size = section->virtual_size != 0
? static_cast<std::size_t>(section->virtual_size)
: static_cast<std::size_t>(section->size_raw_data);
const auto* section_begin =
reinterpret_cast<const std::byte*>(base_address + section->virtual_address);
const auto scan_range = std::span<const std::byte>{section_begin, section_size};
const auto target_offset = scan_pattern(scan_range);
if (target_offset.has_value())
return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value());
}
return std::nullopt;
},
nt_header_variant.value());
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern, const std::string_view& pattern,
@@ -457,6 +519,27 @@ namespace omath
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto pe_section = extract_section_from_pe_file(path_to_file, target_section_name);
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset =
scan_pattern(std::span<const std::byte>{pe_section->data.data(), pe_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data, PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern, const std::string_view& pattern,
@@ -479,4 +562,25 @@ namespace omath
.raw_base_addr = pe_section->raw_base_addr, .raw_base_addr = pe_section->raw_base_addr,
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto pe_section = extract_section_from_pe_memory(file_data, target_section_name);
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset =
scan_pattern(std::span<const std::byte>{pe_section->data.data(), pe_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath } // namespace omath
+5
View File
@@ -7,6 +7,11 @@ include(GoogleTest)
file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/general/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/engines/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.hpp") file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/general/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/engines/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.hpp")
add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES} main.cpp) add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES} main.cpp)
if(OMATH_ENABLE_MODULES)
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_SCAN_FOR_MODULES ON)
target_sources(${PROJECT_NAME} PRIVATE modules/unit_test_module.cpp)
endif()
set_target_properties( set_target_properties(
${PROJECT_NAME} ${PROJECT_NAME}
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
+98 -14
View File
@@ -9,6 +9,96 @@
#include <ranges> #include <ranges>
using namespace omath; using namespace omath;
namespace
{
constexpr bool close_to(const float actual, const float expected, const float epsilon)
{
const float diff = actual - expected;
return (diff < 0.0f ? -diff : diff) <= epsilon;
}
constexpr bool vec_close_to(const Vector3<float>& actual, const Vector3<float>& expected, const float epsilon)
{
return close_to(actual.x, expected.x, epsilon) && close_to(actual.y, expected.y, epsilon)
&& close_to(actual.z, expected.z, epsilon);
}
} // namespace
static_assert(
[]
{
constexpr omath::cry_engine::ViewAngles angles{};
constexpr auto forward = omath::cry_engine::forward_vector(angles);
constexpr auto right = omath::cry_engine::right_vector(angles);
constexpr auto up = omath::cry_engine::up_vector(angles);
constexpr auto rotation = omath::cry_engine::rotation_matrix(angles);
return vec_close_to(forward, omath::cry_engine::k_abs_forward, 1e-5f)
&& vec_close_to(right, omath::cry_engine::k_abs_right, 1e-5f)
&& vec_close_to(up, omath::cry_engine::k_abs_up, 1e-5f) && close_to(rotation.at(0, 0), 1.0f, 1e-5f)
&& close_to(rotation.at(1, 1), 1.0f, 1e-5f) && close_to(rotation.at(2, 2), 1.0f, 1e-5f)
&& close_to(rotation.at(3, 3), 1.0f, 1e-5f);
}(),
"CryEngine basis vectors should be constexpr with embedded constexpr math");
static_assert(
[]
{
constexpr auto view = omath::cry_engine::calc_view_matrix({}, {});
return close_to(view.at(0, 0), 1.0f, 1e-5f) && close_to(view.at(1, 2), 1.0f, 1e-5f)
&& close_to(view.at(2, 1), 1.0f, 1e-5f) && close_to(view.at(3, 3), 1.0f, 1e-5f);
}(),
"CryEngine view matrix should be constexpr with embedded constexpr math");
static_assert(
[]
{
constexpr auto transform = mat_translation<float, MatStoreType::ROW_MAJOR>({1.0f, 2.0f, 3.0f})
* mat_scale<float, MatStoreType::ROW_MAJOR>({2.0f, 3.0f, 4.0f});
constexpr auto origin = omath::cry_engine::extract_origin(transform);
constexpr auto scale = omath::cry_engine::extract_scale(transform);
return vec_close_to(origin, {1.0f, 2.0f, 3.0f}, 1e-5f) && vec_close_to(scale, {2.0f, 3.0f, 4.0f}, 1e-5f);
}(),
"CryEngine transform extraction should be constexpr with embedded constexpr math");
static_assert(
[]
{
constexpr auto projection = omath::cry_engine::calc_perspective_projection_matrix(
90.0f, 1.0f, 1.0f, 11.0f, NDCDepthRange::ZERO_TO_ONE);
return close_to(projection.at(0, 0), 1.0f, 1e-5f) && close_to(projection.at(1, 1), 1.0f, 1e-5f)
&& close_to(projection.at(2, 2), 1.1f, 1e-5f) && close_to(projection.at(2, 3), -1.1f, 1e-5f)
&& close_to(projection.at(3, 2), 1.0f, 1e-5f);
}(),
"CryEngine projection matrix should be constexpr with embedded constexpr math");
static_assert(
[]
{
constexpr auto angles =
omath::cry_engine::CameraTrait::calc_look_at_angle({}, omath::cry_engine::k_abs_forward);
constexpr auto view = omath::cry_engine::CameraTrait::calc_view_matrix(angles, {});
constexpr auto projection = omath::cry_engine::CameraTrait::calc_projection_matrix(
projection::FieldOfView::from_degrees(90.0f), {1.0f, 1.0f}, 1.0f, 11.0f,
NDCDepthRange::ZERO_TO_ONE);
return close_to(angles.pitch.as_degrees(), 0.0f, 1e-5f) && close_to(angles.yaw.as_degrees(), 0.0f, 1e-5f)
&& close_to(angles.roll.as_degrees(), 0.0f, 1e-5f) && close_to(view.at(0, 0), 1.0f, 1e-5f)
&& close_to(view.at(2, 1), 1.0f, 1e-5f) && close_to(projection.at(0, 0), 1.0f, 1e-5f)
&& close_to(projection.at(1, 1), 1.0f, 1e-5f) && close_to(projection.at(2, 2), 1.1f, 1e-5f)
&& close_to(projection.at(2, 3), -1.1f, 1e-5f) && close_to(projection.at(3, 2), 1.0f, 1e-5f);
}(),
"CryEngine CameraTrait should be constexpr with embedded constexpr math");
static_assert(omath::cry_engine::units_to_centimeters(100.0f) == 1.0f);
static_assert(omath::cry_engine::units_to_meters(2.0f) == 2.0f);
static_assert(omath::cry_engine::units_to_kilometers(2000.0f) == 2.0f);
static_assert(omath::cry_engine::centimeters_to_units(1.0f) == 100.0f);
static_assert(omath::cry_engine::meters_to_units(2.0f) == 2.0f);
static_assert(omath::cry_engine::kilometers_to_units(2.0f) == 2000.0f);
TEST(unit_test_cry_engine, look_at_forward) TEST(unit_test_cry_engine, look_at_forward)
{ {
const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_forward); const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_forward);
@@ -29,11 +119,11 @@ TEST(unit_test_cry_engine, look_at_right)
} }
TEST(unit_test_cry_engine, look_at_up) TEST(unit_test_cry_engine, look_at_up)
{ {
const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_right); const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_up);
// ReSharper disable once CppTooWideScopeInitStatement // ReSharper disable once CppTooWideScopeInitStatement
const auto dir_vector = cry_engine::forward_vector(angles); const auto dir_vector = cry_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_right.as_array())) for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_up.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f); EXPECT_NEAR(result, etalon, 0.0001f);
} }
@@ -251,11 +341,9 @@ TEST(unit_test_cry_engine, ViewAnglesAsVector3Zero)
TEST(unit_test_cry_engine, ViewAnglesAsVector3Values) TEST(unit_test_cry_engine, ViewAnglesAsVector3Values)
{ {
const omath::cry_engine::ViewAngles angles{ const omath::cry_engine::ViewAngles angles{omath::cry_engine::PitchAngle::from_degrees(45.f),
omath::cry_engine::PitchAngle::from_degrees(45.f),
omath::cry_engine::YawAngle::from_degrees(-90.f), omath::cry_engine::YawAngle::from_degrees(-90.f),
omath::cry_engine::RollAngle::from_degrees(30.f) omath::cry_engine::RollAngle::from_degrees(30.f)};
};
const auto vec = angles.as_vector3(); const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 45.f); EXPECT_FLOAT_EQ(vec.x, 45.f);
@@ -266,11 +354,9 @@ TEST(unit_test_cry_engine, ViewAnglesAsVector3Values)
TEST(unit_test_cry_engine, ViewAnglesAsVector3ClampedPitch) TEST(unit_test_cry_engine, ViewAnglesAsVector3ClampedPitch)
{ {
// Pitch is clamped to [-90, 90] // Pitch is clamped to [-90, 90]
const omath::cry_engine::ViewAngles angles{ const omath::cry_engine::ViewAngles angles{omath::cry_engine::PitchAngle::from_degrees(120.f),
omath::cry_engine::PitchAngle::from_degrees(120.f),
omath::cry_engine::YawAngle::from_degrees(0.f), omath::cry_engine::YawAngle::from_degrees(0.f),
omath::cry_engine::RollAngle::from_degrees(0.f) omath::cry_engine::RollAngle::from_degrees(0.f)};
};
const auto vec = angles.as_vector3(); const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 90.f); EXPECT_FLOAT_EQ(vec.x, 90.f);
@@ -279,11 +365,9 @@ TEST(unit_test_cry_engine, ViewAnglesAsVector3ClampedPitch)
TEST(unit_test_cry_engine, ViewAnglesAsVector3NormalizedYaw) TEST(unit_test_cry_engine, ViewAnglesAsVector3NormalizedYaw)
{ {
// Yaw is normalized to [-180, 180], 270 wraps to -90 // Yaw is normalized to [-180, 180], 270 wraps to -90
const omath::cry_engine::ViewAngles angles{ const omath::cry_engine::ViewAngles angles{omath::cry_engine::PitchAngle::from_degrees(0.f),
omath::cry_engine::PitchAngle::from_degrees(0.f),
omath::cry_engine::YawAngle::from_degrees(270.f), omath::cry_engine::YawAngle::from_degrees(270.f),
omath::cry_engine::RollAngle::from_degrees(0.f) omath::cry_engine::RollAngle::from_degrees(0.f)};
};
const auto vec = angles.as_vector3(); const auto vec = angles.as_vector3();
EXPECT_NEAR(vec.y, -90.f, 0.01f); EXPECT_NEAR(vec.y, -90.f, 0.01f);
+101
View File
@@ -0,0 +1,101 @@
//
// Created by Orange on 6/3/2026.
//
#include <gtest/gtest.h>
#include <omath/engines/rage_engine/camera.hpp>
#include <omath/engines/rage_engine/constants.hpp>
#include <omath/engines/rage_engine/formulas.hpp>
#include <omath/engines/rage_engine/traits/mesh_trait.hpp>
#include <omath/engines/rage_engine/traits/pred_engine_trait.hpp>
#include <omath/projectile_prediction/projectile.hpp>
#include <ranges>
#include <type_traits>
using namespace omath;
static_assert(std::is_same_v<rage_engine::Mat4X4::ContainedType, float>);
static void expect_rage_vector_near(const Vector3<float>& actual, const Vector3<float>& expected)
{
for (const auto& [result, etalon] : std::views::zip(actual.as_array(), expected.as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_rage_engine, ForwardVector)
{
const auto forward = rage_engine::forward_vector({});
EXPECT_EQ(forward, rage_engine::k_abs_forward);
}
TEST(unit_test_rage_engine, RightVector)
{
const auto right = rage_engine::right_vector({});
EXPECT_EQ(right, rage_engine::k_abs_right);
}
TEST(unit_test_rage_engine, UpVector)
{
const auto up = rage_engine::up_vector({});
EXPECT_EQ(up, rage_engine::k_abs_up);
}
TEST(unit_test_rage_engine, LookAtForward)
{
const auto angles = rage_engine::CameraTrait::calc_look_at_angle({}, rage_engine::k_abs_forward);
expect_rage_vector_near(rage_engine::forward_vector(angles), rage_engine::k_abs_forward);
}
TEST(unit_test_rage_engine, LookAtRight)
{
const auto angles = rage_engine::CameraTrait::calc_look_at_angle({}, rage_engine::k_abs_right);
expect_rage_vector_near(rage_engine::forward_vector(angles), rage_engine::k_abs_right);
}
TEST(unit_test_rage_engine, LookAtUp)
{
const auto angles = rage_engine::CameraTrait::calc_look_at_angle({}, rage_engine::k_abs_up);
expect_rage_vector_near(rage_engine::forward_vector(angles), rage_engine::k_abs_up);
}
TEST(unit_test_rage_engine, ProjectTargetMovedFromCamera)
{
constexpr auto fov = projection::FieldOfView::from_degrees(60.f);
const auto cam = rage_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.01f, 1000.f);
const auto projected = cam.world_to_screen({0, 10.f, 0});
ASSERT_TRUE(projected.has_value());
EXPECT_NEAR(projected->x, 640.f, 0.0001f);
EXPECT_NEAR(projected->y, 360.f, 0.0001f);
}
TEST(unit_test_rage_engine, PredEngineTraitUsesZAsHeight)
{
projectile_prediction::Projectile<float> projectile;
projectile.m_origin = {0.f, 0.f, 0.f};
projectile.m_launch_speed = 10.f;
projectile.m_gravity_scale = 1.f;
const auto pos = rage_engine::PredEngineTrait::predict_projectile_position(projectile, 0.f, 0.f, 1.f, 9.81f);
EXPECT_NEAR(pos.x, 0.f, 0.0001f);
EXPECT_NEAR(pos.y, 10.f, 0.0001f);
EXPECT_NEAR(pos.z, -9.81f * 0.5f, 0.0001f);
EXPECT_NEAR(rage_engine::PredEngineTrait::get_vector_height_coordinate({1.f, 2.f, 3.f}), 3.f, 0.0001f);
}
TEST(unit_test_rage_engine, MeshTraitForwardsRotationMatrix)
{
const rage_engine::ViewAngles angles{
rage_engine::PitchAngle::from_degrees(20.f),
rage_engine::YawAngle::from_degrees(-35.f),
rage_engine::RollAngle::from_degrees(15.f),
};
EXPECT_EQ(rage_engine::MeshTrait::rotation_matrix(angles), rage_engine::rotation_matrix(angles));
}
+138 -69
View File
@@ -1,30 +1,32 @@
// Tests for engine trait headers to improve header coverage // Tests for engine trait headers to improve header coverage
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <omath/engines/frostbite_engine/traits/pred_engine_trait.hpp> #include <omath/engines/cry_engine/camera.hpp>
#include <omath/engines/frostbite_engine/traits/mesh_trait.hpp>
#include <omath/engines/frostbite_engine/traits/camera_trait.hpp>
#include <omath/engines/iw_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/iw_engine/traits/mesh_trait.hpp>
#include <omath/engines/iw_engine/traits/camera_trait.hpp>
#include <omath/engines/opengl_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/opengl_engine/traits/mesh_trait.hpp>
#include <omath/engines/opengl_engine/traits/camera_trait.hpp>
#include <omath/engines/unity_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/unity_engine/traits/mesh_trait.hpp>
#include <omath/engines/unity_engine/traits/camera_trait.hpp>
#include <omath/engines/unreal_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/unreal_engine/traits/mesh_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/engines/cry_engine/traits/camera_trait.hpp>
#include <omath/engines/frostbite_engine/camera.hpp>
#include <omath/engines/frostbite_engine/traits/camera_trait.hpp>
#include <omath/engines/frostbite_engine/traits/mesh_trait.hpp>
#include <omath/engines/frostbite_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/iw_engine/camera.hpp>
#include <omath/engines/iw_engine/traits/camera_trait.hpp>
#include <omath/engines/iw_engine/traits/mesh_trait.hpp>
#include <omath/engines/iw_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/opengl_engine/camera.hpp>
#include <omath/engines/opengl_engine/traits/camera_trait.hpp>
#include <omath/engines/opengl_engine/traits/mesh_trait.hpp>
#include <omath/engines/opengl_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/rage_engine/camera.hpp>
#include <omath/engines/rage_engine/traits/camera_trait.hpp>
#include <omath/engines/source_engine/camera.hpp>
#include <omath/engines/source_engine/traits/camera_trait.hpp>
#include <omath/engines/source_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/unity_engine/camera.hpp>
#include <omath/engines/unity_engine/traits/camera_trait.hpp>
#include <omath/engines/unity_engine/traits/mesh_trait.hpp>
#include <omath/engines/unity_engine/traits/pred_engine_trait.hpp>
#include <omath/engines/unreal_engine/camera.hpp>
#include <omath/engines/unreal_engine/traits/camera_trait.hpp>
#include <omath/engines/unreal_engine/traits/mesh_trait.hpp>
#include <omath/engines/unreal_engine/traits/pred_engine_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>
@@ -41,6 +43,41 @@ static void expect_matrix_near(const MatT& a, const MatT& b, float eps = 1e-5f)
} }
// ── Launch offset tests for all engines ────────────────────────────────────── // ── Launch offset tests for all engines ──────────────────────────────────────
template<class MatType, class NumericType>
constexpr bool matrix_has_non_zero_element(const MatType& mat)
{
for (std::size_t r = 0; r < 4; ++r)
for (std::size_t c = 0; c < 4; ++c)
if (mat.at(r, c) != NumericType{0})
return true;
return false;
}
template<class CameraType, class NumericType>
constexpr bool camera_can_calculate_matrices_at_compile_time()
{
using FieldOfView = projection::FieldOfView;
CameraType camera{{}, {}, {1280.f, 720.f}, FieldOfView::from_degrees(90.f), NumericType{0.01}, NumericType{1000}};
const auto view = camera.get_view_matrix();
const auto projection = camera.get_projection_matrix();
const auto view_projection = camera.get_view_projection_matrix();
return matrix_has_non_zero_element<decltype(view), NumericType>(view)
&& matrix_has_non_zero_element<decltype(projection), NumericType>(projection)
&& matrix_has_non_zero_element<decltype(view_projection), NumericType>(view_projection);
}
static_assert(camera_can_calculate_matrices_at_compile_time<source_engine::Camera, float>());
static_assert(camera_can_calculate_matrices_at_compile_time<iw_engine::Camera, float>());
static_assert(camera_can_calculate_matrices_at_compile_time<frostbite_engine::Camera, float>());
static_assert(camera_can_calculate_matrices_at_compile_time<opengl_engine::Camera, float>());
static_assert(camera_can_calculate_matrices_at_compile_time<unity_engine::Camera, float>());
static_assert(camera_can_calculate_matrices_at_compile_time<cry_engine::Camera, float>());
static_assert(camera_can_calculate_matrices_at_compile_time<rage_engine::Camera, float>());
static_assert(camera_can_calculate_matrices_at_compile_time<unreal_engine::Camera, double>());
#include <omath/engines/cry_engine/traits/pred_engine_trait.hpp> #include <omath/engines/cry_engine/traits/pred_engine_trait.hpp>
// Helper: verify that zero offset matches default-initialized offset behavior // Helper: verify that zero offset matches default-initialized offset behavior
@@ -74,8 +111,10 @@ static void verify_zero_offset_matches_default()
p2.m_launch_speed = static_cast<AT>(50); p2.m_launch_speed = static_cast<AT>(50);
p2.m_gravity_scale = static_cast<AT>(1); p2.m_gravity_scale = static_cast<AT>(1);
const auto pos1 = Trait::predict_projectile_position(p, static_cast<AT>(15), static_cast<AT>(30), static_cast<AT>(1), static_cast<AT>(9.81)); const auto pos1 = Trait::predict_projectile_position(p, static_cast<AT>(15), static_cast<AT>(30),
const auto pos2 = Trait::predict_projectile_position(p2, static_cast<AT>(15), static_cast<AT>(30), static_cast<AT>(1), static_cast<AT>(9.81)); static_cast<AT>(1), static_cast<AT>(9.81));
const auto pos2 = Trait::predict_projectile_position(p2, static_cast<AT>(15), static_cast<AT>(30),
static_cast<AT>(1), static_cast<AT>(9.81));
#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) #if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64)
constexpr double tol = 1e-6; constexpr double tol = 1e-6;
#else #else
@@ -158,7 +197,8 @@ TEST(LaunchOffsetTests, OffsetShiftsTrajectory)
p_with_offset.m_gravity_scale = 1.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 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); 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 // The difference should be exactly the launch offset
EXPECT_NEAR(pos2.x - pos1.x, 10.f, 1e-4f); EXPECT_NEAR(pos2.x - pos1.x, 10.f, 1e-4f);
@@ -221,12 +261,16 @@ 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, NDCDepthRange::NEGATIVE_ONE_TO_ONE); 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 proj_zo = e::CameraTrait::calc_projection_matrix(
const auto expected_zo = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE); 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_matrix_near(proj_zo, expected_zo);
EXPECT_NE(proj, proj_zo); EXPECT_NE(proj, proj_zo);
} }
@@ -272,12 +316,16 @@ 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, NDCDepthRange::NEGATIVE_ONE_TO_ONE); 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 proj_zo = e::CameraTrait::calc_projection_matrix(
const auto expected_zo = e::calc_perspective_projection_matrix(45.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE); 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_matrix_near(proj_zo, expected_zo);
EXPECT_NE(proj, proj_zo); EXPECT_NE(proj, proj_zo);
@@ -327,12 +375,16 @@ 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, NDCDepthRange::NEGATIVE_ONE_TO_ONE); 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 proj_zo = e::CameraTrait::calc_projection_matrix(
const auto expected_zo = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE); 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_matrix_near(proj_zo, expected_zo);
EXPECT_NE(proj, proj_zo); EXPECT_NE(proj, proj_zo);
@@ -382,12 +434,16 @@ 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, NDCDepthRange::NEGATIVE_ONE_TO_ONE); 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 proj_zo = e::CameraTrait::calc_projection_matrix(
const auto expected_zo = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE); 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_matrix_near(proj_zo, expected_zo);
EXPECT_NE(proj, proj_zo); EXPECT_NE(proj, proj_zo);
@@ -437,12 +493,16 @@ 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, NDCDepthRange::NEGATIVE_ONE_TO_ONE); 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 proj_zo = e::CameraTrait::calc_projection_matrix(
const auto expected_zo = e::calc_perspective_projection_matrix(60.f, 1280.f / 720.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE); 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_matrix_near(proj_zo, expected_zo);
EXPECT_NE(proj, proj_zo); EXPECT_NE(proj, proj_zo);
@@ -458,18 +518,17 @@ TEST(NDCDepthRangeTests, Source_BothDepthRanges)
{ {
namespace e = omath::source_engine; namespace e = omath::source_engine;
const auto proj_no = e::CameraTrait::calc_projection_matrix( const auto proj_no =
projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f, 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); 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); expect_matrix_near(proj_no, expected_no);
const auto proj_zo = e::CameraTrait::calc_projection_matrix( const auto proj_zo = e::CameraTrait::calc_projection_matrix(
projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f, projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
NDCDepthRange::ZERO_TO_ONE); const auto expected_zo =
const auto expected_zo = e::calc_perspective_projection_matrix( e::calc_perspective_projection_matrix(90.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
90.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
expect_matrix_near(proj_zo, expected_zo); expect_matrix_near(proj_zo, expected_zo);
EXPECT_NE(proj_no, proj_zo); EXPECT_NE(proj_no, proj_zo);
@@ -479,18 +538,17 @@ TEST(NDCDepthRangeTests, CryEngine_BothDepthRanges)
{ {
namespace e = omath::cry_engine; namespace e = omath::cry_engine;
const auto proj_no = e::CameraTrait::calc_projection_matrix( const auto proj_no =
projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f, 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); 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); expect_matrix_near(proj_no, expected_no);
const auto proj_zo = e::CameraTrait::calc_projection_matrix( const auto proj_zo = e::CameraTrait::calc_projection_matrix(
projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f, projection::FieldOfView::from_degrees(90.f), {1920.f, 1080.f}, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
NDCDepthRange::ZERO_TO_ONE); const auto expected_zo =
const auto expected_zo = e::calc_perspective_projection_matrix( e::calc_perspective_projection_matrix(90.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
90.f, 1920.f / 1080.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE);
expect_matrix_near(proj_zo, expected_zo); expect_matrix_near(proj_zo, expected_zo);
EXPECT_NE(proj_no, proj_zo); EXPECT_NE(proj_no, proj_zo);
@@ -537,7 +595,8 @@ TEST(NDCDepthRangeTests, OpenGL_ZeroToOne_ZRange)
const auto proj = e::calc_perspective_projection_matrix(90.f, 16.f / 9.f, 0.1f, 1000.f, NDCDepthRange::ZERO_TO_ONE); 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 // OpenGL engine uses column-major matrices, project manually
auto proj_z = [&](float z) { auto proj_z = [&](float z)
{
auto clip = proj * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>({0, 0, z}); auto clip = proj * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>({0, 0, z});
return clip.at(2, 0) / clip.at(3, 0); return clip.at(2, 0) / clip.at(3, 0);
}; };
@@ -565,7 +624,8 @@ TEST(NDCDepthRangeTests, Unity_ZeroToOne_ZRange)
// Unity is right-handed, row-major // 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); 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 proj_z = [&](float z)
{
auto clip = proj * mat_column_from_vector<float>({0, 0, z}); auto clip = proj * mat_column_from_vector<float>({0, 0, z});
return clip.at(2, 0) / clip.at(3, 0); return clip.at(2, 0) / clip.at(3, 0);
}; };
@@ -603,7 +663,8 @@ TEST(NDCDepthRangeTests, CryEngine_ZeroToOne_ZRange)
TEST(NDCDepthRangeTests, Source_NegativeOneToOne_ZRange) TEST(NDCDepthRangeTests, Source_NegativeOneToOne_ZRange)
{ {
namespace e = omath::source_engine; 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); 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, 0.1f), -1.0f, 1e-3f);
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f); EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
@@ -612,7 +673,8 @@ TEST(NDCDepthRangeTests, Source_NegativeOneToOne_ZRange)
TEST(NDCDepthRangeTests, IW_NegativeOneToOne_ZRange) TEST(NDCDepthRangeTests, IW_NegativeOneToOne_ZRange)
{ {
namespace e = omath::iw_engine; 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); 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, 0.1f), -1.0f, 1e-3f);
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f); EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
@@ -621,7 +683,8 @@ TEST(NDCDepthRangeTests, IW_NegativeOneToOne_ZRange)
TEST(NDCDepthRangeTests, Frostbite_NegativeOneToOne_ZRange) TEST(NDCDepthRangeTests, Frostbite_NegativeOneToOne_ZRange)
{ {
namespace e = omath::frostbite_engine; 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); 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, 0.1f), -1.0f, 1e-3f);
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f); EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
@@ -630,7 +693,8 @@ TEST(NDCDepthRangeTests, Frostbite_NegativeOneToOne_ZRange)
TEST(NDCDepthRangeTests, Unreal_NegativeOneToOne_ZRange) TEST(NDCDepthRangeTests, Unreal_NegativeOneToOne_ZRange)
{ {
namespace e = omath::unreal_engine; 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); 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, 0.1f), -1.0f, 1e-3f);
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f); EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
@@ -639,7 +703,8 @@ TEST(NDCDepthRangeTests, Unreal_NegativeOneToOne_ZRange)
TEST(NDCDepthRangeTests, CryEngine_NegativeOneToOne_ZRange) TEST(NDCDepthRangeTests, CryEngine_NegativeOneToOne_ZRange)
{ {
namespace e = omath::cry_engine; 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); 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, 0.1f), -1.0f, 1e-3f);
EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f); EXPECT_NEAR(project_z_lh(proj, 1000.f), 1.0f, 1e-3f);
@@ -648,9 +713,11 @@ TEST(NDCDepthRangeTests, CryEngine_NegativeOneToOne_ZRange)
TEST(NDCDepthRangeTests, OpenGL_NegativeOneToOne_ZRange) TEST(NDCDepthRangeTests, OpenGL_NegativeOneToOne_ZRange)
{ {
namespace e = omath::opengl_engine; 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); 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 proj_z = [&](float z)
{
auto clip = proj * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>({0, 0, z}); auto clip = proj * mat_column_from_vector<float, MatStoreType::COLUMN_MAJOR>({0, 0, z});
return clip.at(2, 0) / clip.at(3, 0); return clip.at(2, 0) / clip.at(3, 0);
}; };
@@ -662,9 +729,11 @@ TEST(NDCDepthRangeTests, OpenGL_NegativeOneToOne_ZRange)
TEST(NDCDepthRangeTests, Unity_NegativeOneToOne_ZRange) TEST(NDCDepthRangeTests, Unity_NegativeOneToOne_ZRange)
{ {
namespace e = omath::unity_engine; 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); 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 proj_z = [&](float z)
{
auto clip = proj * mat_column_from_vector<float>({0, 0, z}); auto clip = proj * mat_column_from_vector<float>({0, 0, z});
return clip.at(2, 0) / clip.at(3, 0); return clip.at(2, 0) / clip.at(3, 0);
}; };
@@ -0,0 +1,126 @@
#include <gtest/gtest.h>
#include <omath/engines/cry_engine/formulas.hpp>
#include <omath/engines/frostbite_engine/formulas.hpp>
#include <omath/engines/iw_engine/formulas.hpp>
#include <omath/engines/opengl_engine/formulas.hpp>
#include <omath/engines/source_engine/formulas.hpp>
#include <omath/engines/unity_engine/formulas.hpp>
#include <omath/engines/unreal_engine/formulas.hpp>
template<class Type>
static void expect_vector_near(const omath::Vector3<Type>& actual, const omath::Vector3<Type>& expected,
const double epsilon)
{
EXPECT_NEAR(static_cast<double>(actual.x), static_cast<double>(expected.x), epsilon);
EXPECT_NEAR(static_cast<double>(actual.y), static_cast<double>(expected.y), epsilon);
EXPECT_NEAR(static_cast<double>(actual.z), static_cast<double>(expected.z), epsilon);
}
template<class Mat4X4, class ViewAnglesType, class RotationMatrixFn, class ExtractOriginFn, class ExtractScaleFn,
class ExtractRotationAnglesFn>
static void verify_transform_extractors(const omath::Vector3<typename Mat4X4::ContainedType>& origin,
const omath::Vector3<typename Mat4X4::ContainedType>& scale,
const ViewAnglesType& angles, RotationMatrixFn rotation_matrix,
ExtractOriginFn extract_origin, ExtractScaleFn extract_scale,
ExtractRotationAnglesFn extract_rotation_angles)
{
using Type = typename Mat4X4::ContainedType;
constexpr auto store_ordering = Mat4X4::get_store_ordering();
const auto transform = omath::mat_translation<Type, store_ordering>(origin) * rotation_matrix(angles)
* omath::mat_scale<Type, store_ordering>(scale);
expect_vector_near(extract_origin(transform), origin, 1e-4);
expect_vector_near(extract_scale(transform), scale, 1e-4);
expect_vector_near(extract_rotation_angles(transform).as_vector3(), angles.as_vector3(), 1e-3);
}
TEST(TransformExtraction, SourceEngine)
{
namespace e = omath::source_engine;
const e::ViewAngles angles{
e::PitchAngle::from_degrees(20.f),
e::YawAngle::from_degrees(-35.f),
e::RollAngle::from_degrees(15.f),
};
verify_transform_extractors<e::Mat4X4>({12.f, -3.f, 8.f}, {2.f, 3.f, 4.f}, angles, e::rotation_matrix,
e::extract_origin, e::extract_scale, e::extract_rotation_angles);
}
TEST(TransformExtraction, IwEngine)
{
namespace e = omath::iw_engine;
const e::ViewAngles angles{
e::PitchAngle::from_degrees(-18.f),
e::YawAngle::from_degrees(42.f),
e::RollAngle::from_degrees(-11.f),
};
verify_transform_extractors<e::Mat4X4>({-7.f, 5.f, 13.f}, {1.5f, 2.5f, 3.5f}, angles, e::rotation_matrix,
e::extract_origin, e::extract_scale, e::extract_rotation_angles);
}
TEST(TransformExtraction, UnrealEngine)
{
namespace e = omath::unreal_engine;
const e::ViewAngles angles{
e::PitchAngle::from_degrees(30.0),
e::YawAngle::from_degrees(45.0),
e::RollAngle::from_degrees(-20.0),
};
verify_transform_extractors<e::Mat4X4>({100.0, -50.0, 25.0}, {2.0, 4.0, 6.0}, angles, e::rotation_matrix,
e::extract_origin, e::extract_scale, e::extract_rotation_angles);
}
TEST(TransformExtraction, UnityEngine)
{
namespace e = omath::unity_engine;
const e::ViewAngles angles{
e::PitchAngle::from_degrees(15.f),
e::YawAngle::from_degrees(-25.f),
e::RollAngle::from_degrees(35.f),
};
verify_transform_extractors<e::Mat4X4>({4.f, 9.f, -2.f}, {0.5f, 3.f, 7.f}, angles, e::rotation_matrix,
e::extract_origin, e::extract_scale, e::extract_rotation_angles);
}
TEST(TransformExtraction, FrostbiteEngine)
{
namespace e = omath::frostbite_engine;
const e::ViewAngles angles{
e::PitchAngle::from_degrees(-12.f),
e::YawAngle::from_degrees(33.f),
e::RollAngle::from_degrees(-27.f),
};
verify_transform_extractors<e::Mat4X4>({6.f, -8.f, 10.f}, {1.25f, 2.75f, 4.25f}, angles, e::rotation_matrix,
e::extract_origin, e::extract_scale, e::extract_rotation_angles);
}
TEST(TransformExtraction, CryEngine)
{
namespace e = omath::cry_engine;
const e::ViewAngles angles{
e::PitchAngle::from_degrees(-18.f),
e::YawAngle::from_degrees(40.f),
e::RollAngle::from_degrees(22.f),
};
verify_transform_extractors<e::Mat4X4>({3.f, 14.f, -6.f}, {2.f, 5.f, 8.f}, angles, e::rotation_matrix,
e::extract_origin, e::extract_scale, e::extract_rotation_angles);
}
TEST(TransformExtraction, OpenGlEngine)
{
namespace e = omath::opengl_engine;
const e::ViewAngles angles{
e::PitchAngle::from_degrees(24.f),
e::YawAngle::from_degrees(-31.f),
e::RollAngle::from_degrees(17.f),
};
verify_transform_extractors<e::Mat4X4>({-9.f, 2.f, 11.f}, {3.f, 6.f, 9.f}, angles, e::rotation_matrix,
e::extract_origin, e::extract_scale, e::extract_rotation_angles);
}

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