mirror of
https://github.com/orange-cpp/omath.git
synced 2026-06-09 08:44:35 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56ed7e2f6e | |||
| 39d0d0683d | |||
| a04bceaeb6 | |||
| f0fe5821ed | |||
| d8c5ea16fe | |||
| 848202cbd8 | |||
| 37128d18e7 | |||
| 8433ef05ca | |||
| d9f2428e0e | |||
| 23d3b7c9f5 | |||
| 36a7865b29 | |||
| 2130d02090 | |||
| f602ab6538 | |||
| e4087165b9 | |||
| cebcfc411d | |||
| a4fac65b7c | |||
| 7f88bf8b21 | |||
| 93e70667f0 | |||
| bdef596f16 | |||
| 7a2ac25e8d | |||
| b6f41ed653 | |||
| 5ebba4a630 | |||
| c73afa95cc | |||
| 3ca657a048 | |||
| 9d1de20128 | |||
| 6413c5d59c | |||
| 94f88056cb | |||
| fbc35391c4 | |||
| 6b637f6267 | |||
| fa52c9e985 | |||
| 6ced4acdb6 | |||
| d90164cab8 | |||
| 29255cbb0e | |||
| 8ad936f9f1 | |||
| 57c834ded4 |
@@ -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.
|
||||||
@@ -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.
|
||||||
Generated
+1
@@ -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" />
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
+5
-2
@@ -32,7 +32,7 @@ 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 automatically" OFF)
|
option(OMATH_ENABLE_HOOKING "omath will HooksManager that can hook DirectX/OpenGL automatically" 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)
|
||||||
@@ -113,7 +113,10 @@ if (OMATH_ENABLE_HOOKING)
|
|||||||
target_link_libraries(${PROJECT_NAME} PRIVATE safetyhook::safetyhook)
|
target_link_libraries(${PROJECT_NAME} PRIVATE safetyhook::safetyhook)
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE d3d9 d3d11 d3d12 dxgi)
|
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 ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ add_subdirectory(example_signature_scan)
|
|||||||
add_subdirectory(example_hud)
|
add_subdirectory(example_hud)
|
||||||
|
|
||||||
if(OMATH_ENABLE_HOOKING AND WIN32)
|
if(OMATH_ENABLE_HOOKING AND WIN32)
|
||||||
# Requires imgui with dx9-binding, dx11-binding, dx12-binding, win32-binding.
|
# Requires imgui with dx9-binding, dx11-binding, dx12-binding, opengl3-binding, win32-binding.
|
||||||
# Install via: vcpkg install imgui[dx9-binding,dx11-binding,dx12-binding,win32-binding]
|
# Install via: vcpkg install imgui[dx9-binding,dx11-binding,dx12-binding,opengl3-binding,win32-binding]
|
||||||
find_package(imgui CONFIG QUIET)
|
find_package(imgui CONFIG QUIET)
|
||||||
if(imgui_FOUND)
|
if(imgui_FOUND)
|
||||||
add_subdirectory(example_dx9_hook)
|
add_subdirectory(example_dx9_hook)
|
||||||
add_subdirectory(example_dx11_hook)
|
add_subdirectory(example_dx11_hook)
|
||||||
add_subdirectory(example_dx12_hook)
|
add_subdirectory(example_dx12_hook)
|
||||||
|
add_subdirectory(example_opengl_hook)
|
||||||
else()
|
else()
|
||||||
message(STATUS "[omath] imgui not found — DX hook examples skipped")
|
message(STATUS "[omath] imgui not found - hook examples skipped")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "omath/hooks/hooks_manager.hpp"
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <d3d11.h>
|
#include <d3d11.h>
|
||||||
#include <dxgi.h>
|
#include <dxgi.h>
|
||||||
@@ -5,8 +6,6 @@
|
|||||||
#include <imgui_impl_dx11.h>
|
#include <imgui_impl_dx11.h>
|
||||||
#include <imgui_impl_win32.h>
|
#include <imgui_impl_win32.h>
|
||||||
|
|
||||||
#include "omath/hooks/hooks_manager.hpp"
|
|
||||||
|
|
||||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
|
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -51,12 +50,14 @@ namespace
|
|||||||
ImGui_ImplDX11_Init(g_device, g_context);
|
ImGui_ImplDX11_Init(g_device, g_context);
|
||||||
|
|
||||||
auto& mgr = omath::hooks::HooksManager::get();
|
auto& mgr = omath::hooks::HooksManager::get();
|
||||||
mgr.set_on_wnd_proc([](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT> {
|
mgr.set_on_wnd_proc(
|
||||||
|
[](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT>
|
||||||
|
{
|
||||||
if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp))
|
if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp))
|
||||||
return 0;
|
return 0;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
});
|
});
|
||||||
mgr.hook_wnd_proc(desc.OutputWindow);
|
std::ignore = mgr.hook_wnd_proc(desc.OutputWindow);
|
||||||
|
|
||||||
g_initialized = true;
|
g_initialized = true;
|
||||||
}
|
}
|
||||||
@@ -105,7 +106,9 @@ BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
|
|||||||
if (reason == DLL_PROCESS_ATTACH)
|
if (reason == DLL_PROCESS_ATTACH)
|
||||||
{
|
{
|
||||||
DisableThreadLibraryCalls(h_instance);
|
DisableThreadLibraryCalls(h_instance);
|
||||||
CreateThread(nullptr, 0, [](LPVOID) -> DWORD
|
CreateThread(
|
||||||
|
nullptr, 0,
|
||||||
|
[](LPVOID) -> DWORD
|
||||||
{
|
{
|
||||||
while (!GetModuleHandle("d3d11.dll"))
|
while (!GetModuleHandle("d3d11.dll"))
|
||||||
Sleep(100);
|
Sleep(100);
|
||||||
@@ -115,7 +118,8 @@ BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
|
|||||||
mgr.set_on_resize_buffers(on_resize_buffers);
|
mgr.set_on_resize_buffers(on_resize_buffers);
|
||||||
mgr.hook_dx11();
|
mgr.hook_dx11();
|
||||||
return 0;
|
return 0;
|
||||||
}, nullptr, 0, nullptr);
|
},
|
||||||
|
nullptr, 0, nullptr);
|
||||||
}
|
}
|
||||||
else if (reason == DLL_PROCESS_DETACH)
|
else if (reason == DLL_PROCESS_DETACH)
|
||||||
{
|
{
|
||||||
@@ -130,9 +134,21 @@ BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
|
|||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_render_target_view) { g_render_target_view->Release(); g_render_target_view = nullptr; }
|
if (g_render_target_view)
|
||||||
if (g_context) { g_context->Release(); g_context = nullptr; }
|
{
|
||||||
if (g_device) { g_device->Release(); g_device = nullptr; }
|
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;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,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();
|
||||||
@@ -199,9 +200,9 @@ namespace imgui_desktop::gui
|
|||||||
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),
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace imgui_desktop::gui
|
|||||||
// 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)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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]]
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -21,9 +21,18 @@ namespace omath::cry_engine
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
||||||
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
NDCDepthRange ndc_depth_range = NDCDepthRange::ZERO_TO_ONE) noexcept;
|
||||||
|
|
||||||
template<class FloatingType>
|
template<class FloatingType>
|
||||||
requires std::is_floating_point_v<FloatingType>
|
requires std::is_floating_point_v<FloatingType>
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ namespace omath::frostbite_engine
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
||||||
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ namespace omath::iw_engine
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ namespace omath::opengl_engine
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
||||||
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ namespace omath::source_engine
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ namespace omath::unity_engine
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
||||||
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ namespace omath::unreal_engine
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<double> extract_origin(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<double> extract_scale(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Mat4X4 calc_perspective_projection_matrix(double field_of_view, double aspect_ratio, double near, double far,
|
Mat4X4 calc_perspective_projection_matrix(double field_of_view, double aspect_ratio, double near, double far,
|
||||||
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef OMATH_ENABLE_HOOKING
|
#ifdef OMATH_ENABLE_HOOKING
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#endif
|
#endif
|
||||||
@@ -13,9 +15,15 @@
|
|||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <dxgi.h>
|
|
||||||
#include <d3d9.h>
|
|
||||||
#include <d3d12.h>
|
#include <d3d12.h>
|
||||||
|
#include <d3d9.h>
|
||||||
|
#include <dxgi.h>
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <GL/glx.h>
|
||||||
|
#endif // __linux__
|
||||||
|
|
||||||
#include <safetyhook.hpp>
|
#include <safetyhook.hpp>
|
||||||
|
|
||||||
namespace omath::hooks
|
namespace omath::hooks
|
||||||
@@ -23,25 +31,42 @@ namespace omath::hooks
|
|||||||
class HooksManager final
|
class HooksManager final
|
||||||
{
|
{
|
||||||
HooksManager() = default;
|
HooksManager() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
#ifdef _WIN32
|
||||||
// IDXGISwapChain callbacks — shared between DX11 and DX12 (same interface, same signature).
|
// IDXGISwapChain callbacks — shared between DX11 and DX12 (same interface, same signature).
|
||||||
using present_callback = std::function<void(IDXGISwapChain*, UINT, UINT)>;
|
using present_callback = std::function<void(IDXGISwapChain*, UINT, UINT)>;
|
||||||
using resize_buffers_callback = std::function<void(IDXGISwapChain*, UINT, UINT, UINT, DXGI_FORMAT, 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*)>;
|
using execute_command_lists_callback =
|
||||||
|
std::function<void(ID3D12CommandQueue*, UINT, ID3D12CommandList* const*)>;
|
||||||
|
|
||||||
// IDirect3DDevice9 callbacks — DX9 only.
|
// IDirect3DDevice9 callbacks — DX9 only.
|
||||||
using dx9_present_callback = std::function<void(IDirect3DDevice9*, const RECT*, const RECT*, HWND, const RGNDATA*)>;
|
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_reset_callback = std::function<void(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*)>;
|
||||||
using dx9_end_scene_callback = std::function<void(IDirect3DDevice9*)>;
|
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.
|
// 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)>;
|
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();
|
[[nodiscard]] static HooksManager& get();
|
||||||
HooksManager(const HooksManager&) = delete;
|
HooksManager(const HooksManager&) = delete;
|
||||||
HooksManager& operator=(const HooksManager&) = delete;
|
HooksManager& operator=(const HooksManager&) = delete;
|
||||||
~HooksManager();
|
~HooksManager();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
[[nodiscard]] bool hook_dx9();
|
[[nodiscard]] bool hook_dx9();
|
||||||
void unhook_dx9();
|
void unhook_dx9();
|
||||||
void set_on_dx9_present(dx9_present_callback callback);
|
void set_on_dx9_present(dx9_present_callback callback);
|
||||||
@@ -53,7 +78,13 @@ namespace omath::hooks
|
|||||||
|
|
||||||
[[nodiscard]] bool hook_dx12();
|
[[nodiscard]] bool hook_dx12();
|
||||||
void unhook_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.
|
// Present and ResizeBuffers callbacks fire for whichever of DX11/DX12 is hooked.
|
||||||
void set_on_present(present_callback callback);
|
void set_on_present(present_callback callback);
|
||||||
void set_on_resize_buffers(resize_buffers_callback callback);
|
void set_on_resize_buffers(resize_buffers_callback callback);
|
||||||
@@ -62,32 +93,64 @@ namespace omath::hooks
|
|||||||
[[nodiscard]] bool hook_wnd_proc(HWND hwnd);
|
[[nodiscard]] bool hook_wnd_proc(HWND hwnd);
|
||||||
void unhook_wnd_proc();
|
void unhook_wnd_proc();
|
||||||
void set_on_wnd_proc(wnd_proc_callback callback);
|
void set_on_wnd_proc(wnd_proc_callback callback);
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifdef _WIN32
|
||||||
|
[[nodiscard]]
|
||||||
static HRESULT __stdcall dx9_present_detour(IDirect3DDevice9* p_device, const RECT* p_source_rect,
|
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 RECT* p_dest_rect, HWND h_dest_window_override,
|
||||||
const RGNDATA* p_dirty_region);
|
const RGNDATA* p_dirty_region);
|
||||||
|
[[nodiscard]]
|
||||||
static HRESULT __stdcall dx9_reset_detour(IDirect3DDevice9* p_device,
|
static HRESULT __stdcall dx9_reset_detour(IDirect3DDevice9* p_device,
|
||||||
D3DPRESENT_PARAMETERS* p_presentation_parameters);
|
D3DPRESENT_PARAMETERS* p_presentation_parameters);
|
||||||
|
[[nodiscard]]
|
||||||
static HRESULT __stdcall dx9_end_scene_detour(IDirect3DDevice9* p_device);
|
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);
|
static HRESULT __stdcall dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags);
|
||||||
static HRESULT __stdcall dx11_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count,
|
[[nodiscard]]
|
||||||
UINT width, UINT height, DXGI_FORMAT new_format,
|
static HRESULT __stdcall dx11_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, UINT width,
|
||||||
UINT swap_chain_flags);
|
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);
|
static HRESULT __stdcall dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags);
|
||||||
static HRESULT __stdcall dx12_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count,
|
[[nodiscard]]
|
||||||
UINT width, UINT height, DXGI_FORMAT new_format,
|
static HRESULT __stdcall dx12_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, UINT width,
|
||||||
UINT swap_chain_flags);
|
UINT height, DXGI_FORMAT new_format, UINT swap_chain_flags);
|
||||||
static void __stdcall dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue,
|
static void __stdcall dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue,
|
||||||
UINT num_command_lists,
|
UINT num_command_lists,
|
||||||
ID3D12CommandList* const* pp_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);
|
static LRESULT __stdcall wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param);
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
mutable std::shared_mutex m_mutex;
|
#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_dx9_hooked = false;
|
||||||
bool m_is_dx11_hooked = false;
|
bool m_is_dx11_hooked = false;
|
||||||
bool m_is_dx12_hooked = false;
|
bool m_is_dx12_hooked = false;
|
||||||
@@ -107,16 +170,31 @@ namespace omath::hooks
|
|||||||
safetyhook::InlineHook m_dx12_resize_buffers_hook;
|
safetyhook::InlineHook m_dx12_resize_buffers_hook;
|
||||||
safetyhook::InlineHook m_dx12_execute_command_lists_hook;
|
safetyhook::InlineHook m_dx12_execute_command_lists_hook;
|
||||||
|
|
||||||
dx9_present_callback m_dx9_present_cb;
|
safetyhook::InlineHook m_opengl_wgl_swap_buffers_hook;
|
||||||
dx9_reset_callback m_dx9_reset_cb;
|
safetyhook::InlineHook m_opengl_swap_buffers_hook;
|
||||||
dx9_end_scene_callback m_dx9_end_scene_cb;
|
#endif // _WIN32
|
||||||
|
|
||||||
present_callback m_present_cb;
|
#ifdef __linux__
|
||||||
resize_buffers_callback m_resize_buffers_cb;
|
safetyhook::InlineHook m_opengl_glx_swap_buffers_hook;
|
||||||
execute_command_lists_callback m_execute_command_lists_cb;
|
#endif // __linux__
|
||||||
wnd_proc_callback m_wnd_proc_cb;
|
|
||||||
|
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
|
#else // !OMATH_ENABLE_HOOKING
|
||||||
|
|
||||||
@@ -125,11 +203,12 @@ namespace omath::hooks
|
|||||||
class HooksManager final
|
class HooksManager final
|
||||||
{
|
{
|
||||||
HooksManager() = default;
|
HooksManager() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] static HooksManager& get();
|
[[nodiscard]] static HooksManager& get();
|
||||||
HooksManager(const HooksManager&) = delete;
|
HooksManager(const HooksManager&) = delete;
|
||||||
~HooksManager();
|
~HooksManager();
|
||||||
};
|
};
|
||||||
}
|
} // namespace omath::hooks
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ namespace omath::hud
|
|||||||
|
|
||||||
// ── 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,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ 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 corner_ratio = 0.2f;
|
float corner_ratio = 0.2f;
|
||||||
float thickness = 1.f;
|
float thickness = 1.f;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#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>
|
||||||
@@ -58,7 +59,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 +94,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 +111,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 +149,12 @@ 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,7 +171,7 @@ 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
|
||||||
{
|
{
|
||||||
@@ -201,7 +202,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);
|
||||||
@@ -215,7 +216,7 @@ namespace omath
|
|||||||
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 +240,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 +251,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 +272,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 +297,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 +344,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 +359,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,7 +383,7 @@ 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();
|
||||||
@@ -404,7 +406,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 +421,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 +436,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 +506,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,20 +577,20 @@ 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 static 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 static 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
|
||||||
@@ -600,7 +602,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,8 +613,55 @@ namespace omath
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
|
||||||
|
[[nodiscard("You must use extracted origin")]]
|
||||||
|
constexpr Vector3<Type> mat_extract_origin(const Mat<4, 4, Type, St>& mat) noexcept
|
||||||
|
{
|
||||||
|
return {mat.at(0, 3), mat.at(1, 3), mat.at(2, 3)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
|
||||||
|
[[nodiscard("You must use extracted scale")]]
|
||||||
|
Vector3<Type> mat_extract_scale(const Mat<4, 4, Type, St>& mat) noexcept
|
||||||
|
{
|
||||||
|
auto column_length = [](const Type x, const Type y, const Type z) {
|
||||||
|
return static_cast<Type>(std::sqrt(x * x + y * y + z * z));
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto scale_x = column_length(mat.at(0, 0), mat.at(1, 0), mat.at(2, 0));
|
||||||
|
const auto scale_y = column_length(mat.at(0, 1), mat.at(1, 1), mat.at(2, 1));
|
||||||
|
const auto scale_z = column_length(mat.at(0, 2), mat.at(1, 2), mat.at(2, 2));
|
||||||
|
|
||||||
|
constexpr auto epsilon = std::numeric_limits<Type>::epsilon();
|
||||||
|
|
||||||
|
return {
|
||||||
|
std::abs(scale_x) < epsilon ? Type{1} : scale_x,
|
||||||
|
std::abs(scale_y) < epsilon ? Type{1} : scale_y,
|
||||||
|
std::abs(scale_z) < epsilon ? Type{1} : scale_z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
|
||||||
|
requires std::is_floating_point_v<Type>
|
||||||
|
[[nodiscard("You must use extracted rotation")]]
|
||||||
|
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(std::atan2(m21, m22)),
|
||||||
|
angles::radians_to_degrees(std::asin(std::clamp(-m20, Type{-1}, Type{1}))),
|
||||||
|
angles::radians_to_degrees(std::atan2(m10, m00)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
|
||||||
[[nodiscard]]
|
[[nodiscard("You must use rotation matrix")]]
|
||||||
Mat<4, 4, Type, St> mat_rotation_axis_x(const Angle& angle) noexcept
|
Mat<4, 4, Type, St> mat_rotation_axis_x(const Angle& angle) noexcept
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
@@ -625,7 +674,7 @@ namespace omath
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
|
||||||
[[nodiscard]]
|
[[nodiscard("You must use rotation matrix")]]
|
||||||
Mat<4, 4, Type, St> mat_rotation_axis_y(const Angle& angle) noexcept
|
Mat<4, 4, Type, St> mat_rotation_axis_y(const Angle& angle) noexcept
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
@@ -638,7 +687,7 @@ namespace omath
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
|
||||||
[[nodiscard]]
|
[[nodiscard("You must use rotation matrix")]]
|
||||||
Mat<4, 4, Type, St> mat_rotation_axis_z(const Angle& angle) noexcept
|
Mat<4, 4, Type, St> mat_rotation_axis_z(const Angle& angle) noexcept
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
@@ -651,7 +700,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 camera view matrix")]]
|
||||||
static Mat<4, 4, Type, St> mat_camera_view(const Vector3<Type>& forward, const Vector3<Type>& right,
|
static 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
|
||||||
{
|
{
|
||||||
@@ -666,8 +715,8 @@ 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")]]
|
||||||
Mat<4, 4, Type, St> mat_perspective_left_handed(const Type field_of_view, const Type aspect_ratio,
|
Mat<4, 4, Type, St> mat_perspective_left_handed_vertical_fov(const Type field_of_view, const Type aspect_ratio,
|
||||||
const Type near, const Type far) noexcept
|
const Type near, 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 = std::tan(angles::degrees_to_radians(field_of_view) / Type{2});
|
||||||
@@ -688,8 +737,8 @@ 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")]]
|
||||||
Mat<4, 4, Type, St> mat_perspective_right_handed(const Type field_of_view, const Type aspect_ratio,
|
Mat<4, 4, Type, St> mat_perspective_right_handed_vertical_fov(const Type field_of_view, const Type aspect_ratio,
|
||||||
const Type near, const Type far) noexcept
|
const Type near, 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 = std::tan(angles::degrees_to_radians(field_of_view) / Type{2});
|
||||||
@@ -713,7 +762,7 @@ 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")]]
|
||||||
Mat<4, 4, Type, St> mat_perspective_left_handed_horizontal_fov(const Type horizontal_fov,
|
Mat<4, 4, Type, St> 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
|
||||||
@@ -730,7 +779,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,7 +787,7 @@ 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")]]
|
||||||
Mat<4, 4, Type, St> mat_perspective_right_handed_horizontal_fov(const Type horizontal_fov,
|
Mat<4, 4, Type, St> 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
|
||||||
@@ -755,14 +804,14 @@ 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")]]
|
||||||
Mat<4, 4, Type, St> mat_ortho_left_handed(const Type left, const Type right, const Type bottom, const Type top,
|
Mat<4, 4, Type, St> mat_ortho_left_handed(const Type left, const Type right, const Type bottom, const Type top,
|
||||||
const Type near, const Type far) noexcept
|
const Type near, const Type far) noexcept
|
||||||
{
|
{
|
||||||
@@ -787,7 +836,7 @@ 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 ortho matrix")]]
|
||||||
Mat<4, 4, Type, St> mat_ortho_right_handed(const Type left, const Type right, const Type bottom, const Type top,
|
Mat<4, 4, Type, St> mat_ortho_right_handed(const Type left, const Type right, const Type bottom, const Type top,
|
||||||
const Type near, const Type far) noexcept
|
const Type near, const Type far) noexcept
|
||||||
{
|
{
|
||||||
@@ -834,14 +883,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>)
|
||||||
|
|||||||
@@ -28,7 +28,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 +37,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 +115,51 @@ namespace omath
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Basic vector operations
|
// Basic vector operations
|
||||||
[[nodiscard]] Type distance_to(const Vector2& other) const noexcept
|
[[nodiscard("You must use distance")]]
|
||||||
|
Type distance_to(const Vector2& other) const noexcept
|
||||||
{
|
{
|
||||||
return std::sqrt(distance_to_sqr(other));
|
return std::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 std::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")]]
|
||||||
|
Type length() const noexcept
|
||||||
{
|
{
|
||||||
return std::hypot(x, y);
|
return std::hypot(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] Vector2 normalized() const noexcept
|
[[nodiscard("You must use normalized vector")]]
|
||||||
|
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 +171,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
|
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
|
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
|
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
|
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 +266,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 +282,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>)
|
||||||
|
|||||||
@@ -32,17 +32,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,118 +121,140 @@ 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
|
||||||
{
|
{
|
||||||
return std::hypot(this->x, this->y, z);
|
return std::hypot(this->x, this->y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr Type length_2d() const
|
[[nodiscard("You must use 2D length")]] constexpr Type length_2d() const
|
||||||
{
|
{
|
||||||
return Vector2<Type>::length();
|
return Vector2<Type>::length();
|
||||||
}
|
}
|
||||||
[[nodiscard]] Type distance_to(const Vector3& other) const
|
[[nodiscard("You must use distance")]] Type distance_to(const Vector3& other) const
|
||||||
{
|
{
|
||||||
return (*this - other).length();
|
return (*this - other).length();
|
||||||
}
|
}
|
||||||
[[nodiscard]] constexpr Vector3 normalized() const
|
[[nodiscard("You must use normalized vector")]] constexpr Vector3 normalized() const
|
||||||
{
|
{
|
||||||
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")]]
|
||||||
|
Type length() const noexcept
|
||||||
{
|
{
|
||||||
return std::hypot(this->x, this->y, z);
|
return std::hypot(this->x, this->y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] Vector3 normalized() const noexcept
|
[[nodiscard("You must use normalized vector")]]
|
||||||
|
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")]]
|
||||||
|
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")]]
|
||||||
|
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
|
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")]]
|
||||||
|
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();
|
||||||
@@ -240,8 +265,8 @@ namespace omath
|
|||||||
return Angle<float, 0.f, 180.f, AngleFlags::Clamped>::from_radians(std::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
|
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
|
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
|
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
|
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
|
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,7 @@ namespace omath
|
|||||||
|
|
||||||
template<> struct std::hash<omath::Vector3<float>>
|
template<> struct std::hash<omath::Vector3<float>>
|
||||||
{
|
{
|
||||||
[[nodiscard]]
|
[[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 +337,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>)
|
||||||
|
|||||||
@@ -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>)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -5,6 +5,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/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 +32,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;
|
||||||
}
|
}
|
||||||
@@ -100,17 +101,18 @@ 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
|
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} * std::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
|
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]};
|
||||||
@@ -119,7 +121,7 @@ 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
|
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.
|
||||||
@@ -140,33 +142,33 @@ namespace omath::projection
|
|||||||
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
|
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
|
Vector3<NumericType> get_forward() const noexcept
|
||||||
{
|
{
|
||||||
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
|
Vector3<NumericType> get_right() const noexcept
|
||||||
{
|
{
|
||||||
const auto& view_matrix = get_view_matrix();
|
const auto& view_matrix = get_view_matrix();
|
||||||
return {view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]};
|
return {view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard("You must use up vector")]]
|
||||||
Vector3<NumericType> get_up() const noexcept
|
Vector3<NumericType> get_up() const noexcept
|
||||||
{
|
{
|
||||||
const auto& view_matrix = get_view_matrix();
|
const auto& view_matrix = get_view_matrix();
|
||||||
return {view_matrix[1, 0], view_matrix[1, 1], view_matrix[1, 2]};
|
return {view_matrix[1, 0], view_matrix[1, 1], view_matrix[1, 2]};
|
||||||
}
|
}
|
||||||
[[nodiscard]]
|
[[nodiscard("You must use absolute forward vector")]]
|
||||||
Vector3<NumericType> get_abs_forward() const noexcept
|
Vector3<NumericType> get_abs_forward() const noexcept
|
||||||
{
|
{
|
||||||
if constexpr (axes.inverted_forward)
|
if constexpr (axes.inverted_forward)
|
||||||
@@ -174,7 +176,7 @@ namespace omath::projection
|
|||||||
return get_forward();
|
return get_forward();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard("You must use absolute right vector")]]
|
||||||
Vector3<NumericType> get_abs_right() const noexcept
|
Vector3<NumericType> get_abs_right() const noexcept
|
||||||
{
|
{
|
||||||
if constexpr (axes.inverted_right)
|
if constexpr (axes.inverted_right)
|
||||||
@@ -182,13 +184,14 @@ namespace omath::projection
|
|||||||
return get_right();
|
return get_right();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard("You must use absolute up vector")]]
|
||||||
Vector3<NumericType> get_abs_up() const noexcept
|
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 view-projection matrix")]]
|
||||||
|
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,14 +199,14 @@ 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")]] 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 = TraitClass::calc_view_matrix(m_view_angles, m_origin);
|
||||||
|
|
||||||
return m_view_matrix.value();
|
return m_view_matrix.value();
|
||||||
}
|
}
|
||||||
[[nodiscard]] const Mat4X4Type& get_projection_matrix() const noexcept
|
[[nodiscard("You must use projection matrix")]] 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 = TraitClass::calc_projection_matrix(
|
||||||
@@ -253,33 +256,33 @@ namespace omath::projection
|
|||||||
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")]] 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")]] 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")]] 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")]] 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")]] 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")]] 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 +298,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")]] 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 +314,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")]] 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 +382,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")]] 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,7 +400,28 @@ namespace omath::projection
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::expected<Vector3<NumericType>, Error>
|
[[nodiscard("You must use OBB frustum culling result")]] 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 * std::abs(normal.dot(obb.axis_x))
|
||||||
|
+ obb.half_extents.y * std::abs(normal.dot(obb.axis_y))
|
||||||
|
+ obb.half_extents.z * std::abs(normal.dot(obb.axis_z));
|
||||||
|
|
||||||
|
if (center_distance + radius < NumericType{0})
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard("You must use view port position")]] 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
|
||||||
{
|
{
|
||||||
@@ -464,7 +450,7 @@ namespace omath::projection
|
|||||||
|
|
||||||
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]]
|
[[nodiscard("You must use world position")]]
|
||||||
std::expected<Vector3<NumericType>, Error> view_port_to_world(const Vector3<NumericType>& ndc) const noexcept
|
std::expected<Vector3<NumericType>, Error> view_port_to_world(const Vector3<NumericType>& ndc) const noexcept
|
||||||
{
|
{
|
||||||
const auto inv_view_proj = get_view_projection_matrix().inverted();
|
const auto inv_view_proj = get_view_projection_matrix().inverted();
|
||||||
@@ -487,7 +473,7 @@ namespace omath::projection
|
|||||||
}
|
}
|
||||||
|
|
||||||
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>
|
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
|
||||||
{
|
{
|
||||||
@@ -495,7 +481,7 @@ namespace omath::projection
|
|||||||
}
|
}
|
||||||
|
|
||||||
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>
|
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
|
||||||
{
|
{
|
||||||
@@ -517,8 +503,54 @@ 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")]] 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 +563,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 +589,7 @@ namespace omath::projection
|
|||||||
v
|
v
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[[nodiscard]] Vector3<NumericType>
|
[[nodiscard("You must use screen position")]] 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 +607,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")]] 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 +626,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")]] 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},
|
||||||
|
|||||||
@@ -34,16 +34,37 @@ namespace omath::cry_engine
|
|||||||
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.roll)
|
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.roll)
|
||||||
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_origin(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_scale(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
||||||
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
||||||
{
|
{
|
||||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, 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, far);
|
field_of_view, aspect_ratio, near, far);
|
||||||
|
|
||||||
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, 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, far);
|
field_of_view, aspect_ratio, near, far);
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
} // namespace omath::unity_engine
|
} // namespace omath::cry_engine
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ namespace omath::cry_engine
|
|||||||
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
|
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
|
||||||
ndc_depth_range);
|
ndc_depth_range);
|
||||||
}
|
}
|
||||||
} // namespace omath::unity_engine
|
} // namespace omath::cry_engine
|
||||||
@@ -34,15 +34,36 @@ namespace omath::frostbite_engine
|
|||||||
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.yaw)
|
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.yaw)
|
||||||
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_origin(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_scale(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
||||||
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
||||||
{
|
{
|
||||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, 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, far);
|
field_of_view, aspect_ratio, near, far);
|
||||||
|
|
||||||
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, 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, far);
|
field_of_view, aspect_ratio, near, far);
|
||||||
|
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
|
|||||||
@@ -30,6 +30,26 @@ namespace omath::iw_engine
|
|||||||
return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll);
|
return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_origin(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_scale(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
|
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
|
||||||
{
|
{
|
||||||
return mat_camera_view(forward_vector(angles), right_vector(angles), up_vector(angles), cam_origin);
|
return mat_camera_view(forward_vector(angles), right_vector(angles), up_vector(angles), cam_origin);
|
||||||
@@ -47,11 +67,11 @@ namespace omath::iw_engine
|
|||||||
const auto vertical_fov = angles::horizontal_fov_to_vertical(field_of_view, k_source_reference_aspect);
|
const auto vertical_fov = angles::horizontal_fov_to_vertical(field_of_view, k_source_reference_aspect);
|
||||||
|
|
||||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
return mat_perspective_left_handed<
|
return mat_perspective_left_handed_vertical_fov<
|
||||||
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
vertical_fov, aspect_ratio, near, far);
|
vertical_fov, aspect_ratio, near, far);
|
||||||
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
return mat_perspective_left_handed<
|
return mat_perspective_left_handed_vertical_fov<
|
||||||
float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
||||||
vertical_fov, aspect_ratio, near, far);
|
vertical_fov, aspect_ratio, near, far);
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
|
|||||||
@@ -36,15 +36,36 @@ namespace omath::opengl_engine
|
|||||||
* mat_rotation_axis_y<float, MatStoreType::COLUMN_MAJOR>(angles.yaw)
|
* mat_rotation_axis_y<float, MatStoreType::COLUMN_MAJOR>(angles.yaw)
|
||||||
* mat_rotation_axis_x<float, MatStoreType::COLUMN_MAJOR>(angles.pitch);
|
* mat_rotation_axis_x<float, MatStoreType::COLUMN_MAJOR>(angles.pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_origin(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_scale(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
||||||
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
||||||
{
|
{
|
||||||
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
return mat_perspective_right_handed<float, MatStoreType::COLUMN_MAJOR, 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, far);
|
field_of_view, aspect_ratio, near, far);
|
||||||
|
|
||||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
return mat_perspective_right_handed<float, MatStoreType::COLUMN_MAJOR, 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, far);
|
field_of_view, aspect_ratio, near, far);
|
||||||
|
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
|
|||||||
@@ -17,6 +17,26 @@ namespace omath::source_engine
|
|||||||
return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll);
|
return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_origin(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_scale(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept
|
Vector3<float> right_vector(const ViewAngles& angles) noexcept
|
||||||
{
|
{
|
||||||
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
|
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
|
||||||
@@ -47,11 +67,11 @@ namespace omath::source_engine
|
|||||||
const auto vertical_fov = angles::horizontal_fov_to_vertical(field_of_view, k_source_reference_aspect);
|
const auto vertical_fov = angles::horizontal_fov_to_vertical(field_of_view, k_source_reference_aspect);
|
||||||
|
|
||||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
return mat_perspective_left_handed<
|
return mat_perspective_left_handed_vertical_fov<
|
||||||
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
vertical_fov, aspect_ratio, near, far);
|
vertical_fov, aspect_ratio, near, far);
|
||||||
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
return mat_perspective_left_handed<
|
return mat_perspective_left_handed_vertical_fov<
|
||||||
float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
||||||
vertical_fov, aspect_ratio, near, far);
|
vertical_fov, aspect_ratio, near, far);
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
|
|||||||
@@ -34,14 +34,35 @@ namespace omath::unity_engine
|
|||||||
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.yaw)
|
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.yaw)
|
||||||
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_origin(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_origin(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3<float> extract_scale(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_scale(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
||||||
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
||||||
{
|
{
|
||||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
return omath::mat_perspective_right_handed<float, MatStoreType::ROW_MAJOR, 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, far);
|
field_of_view, aspect_ratio, near, far);
|
||||||
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
return omath::mat_perspective_right_handed<float, MatStoreType::ROW_MAJOR,
|
return omath::mat_perspective_right_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
|
||||||
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(field_of_view, aspect_ratio,
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(field_of_view, aspect_ratio,
|
||||||
near, far);
|
near, far);
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
|
|||||||
@@ -39,6 +39,26 @@ namespace omath::unreal_engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vector3<double> extract_origin(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_origin(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3<double> extract_scale(const Mat4X4& mat) noexcept
|
||||||
|
{
|
||||||
|
return mat_extract_scale(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Mat4X4 calc_perspective_projection_matrix(const double field_of_view, const double aspect_ratio, const double near,
|
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
|
const double far, const NDCDepthRange ndc_depth_range) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
+262
-108
@@ -1,10 +1,20 @@
|
|||||||
#include "omath/hooks/hooks_manager.hpp"
|
#include "omath/hooks/hooks_manager.hpp"
|
||||||
|
|
||||||
#ifdef OMATH_ENABLE_HOOKING
|
#ifdef OMATH_ENABLE_HOOKING
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
#include <d3d11.h>
|
#include <d3d11.h>
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#endif // __linux__
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
thread_local bool g_is_inside_opengl_swap_buffers = false;
|
||||||
|
|
||||||
class DummyWindow final
|
class DummyWindow final
|
||||||
{
|
{
|
||||||
WNDCLASSEX m_window_class{};
|
WNDCLASSEX m_window_class{};
|
||||||
@@ -19,8 +29,8 @@ namespace
|
|||||||
m_window_class.hInstance = GetModuleHandle(nullptr);
|
m_window_class.hInstance = GetModuleHandle(nullptr);
|
||||||
m_window_class.lpszClassName = "OM";
|
m_window_class.lpszClassName = "OM";
|
||||||
RegisterClassEx(&m_window_class);
|
RegisterClassEx(&m_window_class);
|
||||||
m_window_handle = CreateWindow(m_window_class.lpszClassName, "Dummy", WS_OVERLAPPEDWINDOW,
|
m_window_handle = CreateWindow(m_window_class.lpszClassName, "Dummy", WS_OVERLAPPEDWINDOW, 0, 0, 100, 100,
|
||||||
0, 0, 100, 100, nullptr, nullptr, m_window_class.hInstance, nullptr);
|
nullptr, nullptr, m_window_class.hInstance, nullptr);
|
||||||
}
|
}
|
||||||
~DummyWindow()
|
~DummyWindow()
|
||||||
{
|
{
|
||||||
@@ -28,8 +38,14 @@ namespace
|
|||||||
DestroyWindow(m_window_handle);
|
DestroyWindow(m_window_handle);
|
||||||
UnregisterClass(m_window_class.lpszClassName, m_window_class.hInstance);
|
UnregisterClass(m_window_class.lpszClassName, m_window_class.hInstance);
|
||||||
}
|
}
|
||||||
[[nodiscard]] HWND handle() const { return m_window_handle; }
|
[[nodiscard]] HWND handle() const
|
||||||
[[nodiscard]] bool valid() const { return m_window_handle != nullptr; }
|
{
|
||||||
|
return m_window_handle;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool valid() const
|
||||||
|
{
|
||||||
|
return m_window_handle != nullptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void* vtable_fn(void* com_obj, std::size_t index)
|
void* vtable_fn(void* com_obj, std::size_t index)
|
||||||
@@ -37,6 +53,15 @@ namespace
|
|||||||
return (*reinterpret_cast<void***>(com_obj))[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
|
struct dx12_vtable_fns
|
||||||
{
|
{
|
||||||
void* present;
|
void* present;
|
||||||
@@ -60,12 +85,18 @@ namespace
|
|||||||
|
|
||||||
~dx12_com_objects()
|
~dx12_com_objects()
|
||||||
{
|
{
|
||||||
if (swap_chain) swap_chain->Release();
|
if (swap_chain)
|
||||||
if (command_list) command_list->Release();
|
swap_chain->Release();
|
||||||
if (command_allocator) command_allocator->Release();
|
if (command_list)
|
||||||
if (command_queue) command_queue->Release();
|
command_list->Release();
|
||||||
if (device) device->Release();
|
if (command_allocator)
|
||||||
if (factory) factory->Release();
|
command_allocator->Release();
|
||||||
|
if (command_queue)
|
||||||
|
command_queue->Release();
|
||||||
|
if (device)
|
||||||
|
device->Release();
|
||||||
|
if (factory)
|
||||||
|
factory->Release();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,10 +110,10 @@ namespace
|
|||||||
if (!d3d12_module || !dxgi_module)
|
if (!d3d12_module || !dxgi_module)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
const auto create_dxgi_factory = reinterpret_cast<create_dxgi_factory_fn>(
|
const auto create_dxgi_factory =
|
||||||
GetProcAddress(dxgi_module, "CreateDXGIFactory"));
|
reinterpret_cast<create_dxgi_factory_fn>(GetProcAddress(dxgi_module, "CreateDXGIFactory"));
|
||||||
const auto d3d12_create_device = reinterpret_cast<d3d12_create_device_fn>(
|
const auto d3d12_create_device =
|
||||||
GetProcAddress(d3d12_module, "D3D12CreateDevice"));
|
reinterpret_cast<d3d12_create_device_fn>(GetProcAddress(d3d12_module, "D3D12CreateDevice"));
|
||||||
|
|
||||||
if (!create_dxgi_factory || !d3d12_create_device)
|
if (!create_dxgi_factory || !d3d12_create_device)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -96,8 +127,7 @@ namespace
|
|||||||
if (objs.factory->EnumAdapters(0, &adapter) == DXGI_ERROR_NOT_FOUND)
|
if (objs.factory->EnumAdapters(0, &adapter) == DXGI_ERROR_NOT_FOUND)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
const HRESULT device_hr = d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0,
|
const HRESULT device_hr = d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device),
|
||||||
__uuidof(ID3D12Device),
|
|
||||||
reinterpret_cast<void**>(&objs.device));
|
reinterpret_cast<void**>(&objs.device));
|
||||||
adapter->Release();
|
adapter->Release();
|
||||||
if (FAILED(device_hr))
|
if (FAILED(device_hr))
|
||||||
@@ -110,13 +140,12 @@ namespace
|
|||||||
reinterpret_cast<void**>(&objs.command_queue))))
|
reinterpret_cast<void**>(&objs.command_queue))))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
if (FAILED(objs.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
|
if (FAILED(objs.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
|
||||||
__uuidof(ID3D12CommandAllocator),
|
|
||||||
reinterpret_cast<void**>(&objs.command_allocator))))
|
reinterpret_cast<void**>(&objs.command_allocator))))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
if (FAILED(objs.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, objs.command_allocator,
|
if (FAILED(objs.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, objs.command_allocator, nullptr,
|
||||||
nullptr, __uuidof(ID3D12GraphicsCommandList),
|
__uuidof(ID3D12GraphicsCommandList),
|
||||||
reinterpret_cast<void**>(&objs.command_list))))
|
reinterpret_cast<void**>(&objs.command_list))))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
@@ -143,6 +172,7 @@ namespace
|
|||||||
vtable_fn(objs.command_queue, 10), // ID3D12CommandQueue::ExecuteCommandLists
|
vtable_fn(objs.command_queue, 10), // ID3D12CommandQueue::ExecuteCommandLists
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
#endif // _WIN32
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace omath::hooks
|
namespace omath::hooks
|
||||||
@@ -155,15 +185,19 @@ namespace omath::hooks
|
|||||||
|
|
||||||
HooksManager::~HooksManager()
|
HooksManager::~HooksManager()
|
||||||
{
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
unhook_wnd_proc();
|
unhook_wnd_proc();
|
||||||
unhook_dx9();
|
unhook_dx9();
|
||||||
unhook_dx11();
|
unhook_dx11();
|
||||||
unhook_dx12();
|
unhook_dx12();
|
||||||
|
#endif // _WIN32
|
||||||
|
unhook_opengl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
bool HooksManager::hook_dx9()
|
bool HooksManager::hook_dx9()
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_hook_state_mutex);
|
||||||
if (m_is_dx9_hooked)
|
if (m_is_dx9_hooked)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -176,8 +210,8 @@ namespace omath::hooks
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
using direct3d_create9_fn = IDirect3D9*(__stdcall*)(UINT);
|
using direct3d_create9_fn = IDirect3D9*(__stdcall*)(UINT);
|
||||||
const auto direct3d_create9 = reinterpret_cast<direct3d_create9_fn>(
|
const auto direct3d_create9 =
|
||||||
GetProcAddress(d3d9_module, "Direct3DCreate9"));
|
reinterpret_cast<direct3d_create9_fn>(GetProcAddress(d3d9_module, "Direct3DCreate9"));
|
||||||
if (!direct3d_create9)
|
if (!direct3d_create9)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -202,17 +236,13 @@ namespace omath::hooks
|
|||||||
// Reset = 16
|
// Reset = 16
|
||||||
// Present = 17
|
// Present = 17
|
||||||
// EndScene = 42
|
// EndScene = 42
|
||||||
m_dx9_present_hook = safetyhook::create_inline(
|
m_dx9_present_hook =
|
||||||
vtable_fn(device, 17),
|
safetyhook::create_inline(vtable_fn(device, 17), reinterpret_cast<void*>(&dx9_present_detour));
|
||||||
reinterpret_cast<void*>(&dx9_present_detour));
|
|
||||||
|
|
||||||
m_dx9_reset_hook = safetyhook::create_inline(
|
m_dx9_reset_hook = safetyhook::create_inline(vtable_fn(device, 16), reinterpret_cast<void*>(&dx9_reset_detour));
|
||||||
vtable_fn(device, 16),
|
|
||||||
reinterpret_cast<void*>(&dx9_reset_detour));
|
|
||||||
|
|
||||||
m_dx9_end_scene_hook = safetyhook::create_inline(
|
m_dx9_end_scene_hook =
|
||||||
vtable_fn(device, 42),
|
safetyhook::create_inline(vtable_fn(device, 42), reinterpret_cast<void*>(&dx9_end_scene_detour));
|
||||||
reinterpret_cast<void*>(&dx9_end_scene_detour));
|
|
||||||
|
|
||||||
device->Release();
|
device->Release();
|
||||||
d3d9->Release();
|
d3d9->Release();
|
||||||
@@ -231,7 +261,7 @@ namespace omath::hooks
|
|||||||
|
|
||||||
void HooksManager::unhook_dx9()
|
void HooksManager::unhook_dx9()
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_hook_state_mutex);
|
||||||
m_dx9_present_hook = {};
|
m_dx9_present_hook = {};
|
||||||
m_dx9_reset_hook = {};
|
m_dx9_reset_hook = {};
|
||||||
m_dx9_end_scene_hook = {};
|
m_dx9_end_scene_hook = {};
|
||||||
@@ -240,25 +270,25 @@ namespace omath::hooks
|
|||||||
|
|
||||||
void HooksManager::set_on_dx9_present(dx9_present_callback callback)
|
void HooksManager::set_on_dx9_present(dx9_present_callback callback)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_dx9_present_mutex);
|
||||||
m_dx9_present_cb = std::move(callback);
|
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)
|
void HooksManager::set_on_dx9_reset(dx9_reset_callback callback)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_dx9_reset_mutex);
|
||||||
m_dx9_reset_cb = std::move(callback);
|
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)
|
void HooksManager::set_on_dx9_end_scene(dx9_end_scene_callback callback)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_dx9_end_scene_mutex);
|
||||||
m_dx9_end_scene_cb = std::move(callback);
|
m_dx9_end_scene_cb = callback ? std::make_shared<dx9_end_scene_callback>(std::move(callback)) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HooksManager::hook_dx11()
|
bool HooksManager::hook_dx11()
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_hook_state_mutex);
|
||||||
if (m_is_dx11_hooked)
|
if (m_is_dx11_hooked)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -271,10 +301,9 @@ namespace omath::hooks
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
using d3d11_create_device_and_swap_chain_fn =
|
using d3d11_create_device_and_swap_chain_fn =
|
||||||
HRESULT(__stdcall*)(IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT,
|
HRESULT(__stdcall*)(IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT, const D3D_FEATURE_LEVEL*, UINT, UINT,
|
||||||
const D3D_FEATURE_LEVEL*, UINT, UINT,
|
const DXGI_SWAP_CHAIN_DESC*, IDXGISwapChain**, ID3D11Device**, D3D_FEATURE_LEVEL*,
|
||||||
const DXGI_SWAP_CHAIN_DESC*, IDXGISwapChain**,
|
ID3D11DeviceContext**);
|
||||||
ID3D11Device**, D3D_FEATURE_LEVEL*, ID3D11DeviceContext**);
|
|
||||||
|
|
||||||
const auto create_device_and_swap_chain = reinterpret_cast<d3d11_create_device_and_swap_chain_fn>(
|
const auto create_device_and_swap_chain = reinterpret_cast<d3d11_create_device_and_swap_chain_fn>(
|
||||||
GetProcAddress(d3d11_module, "D3D11CreateDeviceAndSwapChain"));
|
GetProcAddress(d3d11_module, "D3D11CreateDeviceAndSwapChain"));
|
||||||
@@ -298,18 +327,16 @@ namespace omath::hooks
|
|||||||
ID3D11DeviceContext* device_context = nullptr;
|
ID3D11DeviceContext* device_context = nullptr;
|
||||||
IDXGISwapChain* swap_chain = nullptr;
|
IDXGISwapChain* swap_chain = nullptr;
|
||||||
|
|
||||||
if (FAILED(create_device_and_swap_chain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
|
if (FAILED(create_device_and_swap_chain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, feature_levels, 1,
|
||||||
feature_levels, 1, D3D11_SDK_VERSION,
|
D3D11_SDK_VERSION, &swap_chain_desc, &swap_chain, &device, nullptr,
|
||||||
&swap_chain_desc, &swap_chain,
|
&device_context)))
|
||||||
&device, nullptr, &device_context)))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
m_dx11_present_hook = safetyhook::create_inline(
|
m_dx11_present_hook = safetyhook::create_inline(vtable_fn(swap_chain, 8), // IDXGISwapChain::Present
|
||||||
vtable_fn(swap_chain, 8), // IDXGISwapChain::Present
|
|
||||||
reinterpret_cast<void*>(&dx11_present_detour));
|
reinterpret_cast<void*>(&dx11_present_detour));
|
||||||
|
|
||||||
m_dx11_resize_buffers_hook = safetyhook::create_inline(
|
m_dx11_resize_buffers_hook =
|
||||||
vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers
|
safetyhook::create_inline(vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers
|
||||||
reinterpret_cast<void*>(&dx11_resize_buffers_detour));
|
reinterpret_cast<void*>(&dx11_resize_buffers_detour));
|
||||||
|
|
||||||
swap_chain->Release();
|
swap_chain->Release();
|
||||||
@@ -329,7 +356,7 @@ namespace omath::hooks
|
|||||||
|
|
||||||
void HooksManager::unhook_dx11()
|
void HooksManager::unhook_dx11()
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_hook_state_mutex);
|
||||||
m_dx11_present_hook = {};
|
m_dx11_present_hook = {};
|
||||||
m_dx11_resize_buffers_hook = {};
|
m_dx11_resize_buffers_hook = {};
|
||||||
m_is_dx11_hooked = false;
|
m_is_dx11_hooked = false;
|
||||||
@@ -337,7 +364,7 @@ namespace omath::hooks
|
|||||||
|
|
||||||
bool HooksManager::hook_dx12()
|
bool HooksManager::hook_dx12()
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_hook_state_mutex);
|
||||||
if (m_is_dx12_hooked)
|
if (m_is_dx12_hooked)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -349,17 +376,13 @@ namespace omath::hooks
|
|||||||
if (!fns)
|
if (!fns)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
m_dx12_present_hook = safetyhook::create_inline(
|
m_dx12_present_hook = safetyhook::create_inline(fns->present, reinterpret_cast<void*>(&dx12_present_detour));
|
||||||
fns->present,
|
|
||||||
reinterpret_cast<void*>(&dx12_present_detour));
|
|
||||||
|
|
||||||
m_dx12_resize_buffers_hook = safetyhook::create_inline(
|
m_dx12_resize_buffers_hook =
|
||||||
fns->resize_buffers,
|
safetyhook::create_inline(fns->resize_buffers, reinterpret_cast<void*>(&dx12_resize_buffers_detour));
|
||||||
reinterpret_cast<void*>(&dx12_resize_buffers_detour));
|
|
||||||
|
|
||||||
m_dx12_execute_command_lists_hook = safetyhook::create_inline(
|
m_dx12_execute_command_lists_hook = safetyhook::create_inline(
|
||||||
fns->execute_command_lists,
|
fns->execute_command_lists, reinterpret_cast<void*>(&dx12_execute_command_lists_detour));
|
||||||
reinterpret_cast<void*>(&dx12_execute_command_lists_detour));
|
|
||||||
|
|
||||||
if (!m_dx12_present_hook || !m_dx12_resize_buffers_hook || !m_dx12_execute_command_lists_hook)
|
if (!m_dx12_present_hook || !m_dx12_resize_buffers_hook || !m_dx12_execute_command_lists_hook)
|
||||||
{
|
{
|
||||||
@@ -375,34 +398,72 @@ namespace omath::hooks
|
|||||||
|
|
||||||
void HooksManager::unhook_dx12()
|
void HooksManager::unhook_dx12()
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_hook_state_mutex);
|
||||||
m_dx12_present_hook = {};
|
m_dx12_present_hook = {};
|
||||||
m_dx12_resize_buffers_hook = {};
|
m_dx12_resize_buffers_hook = {};
|
||||||
m_dx12_execute_command_lists_hook = {};
|
m_dx12_execute_command_lists_hook = {};
|
||||||
m_is_dx12_hooked = false;
|
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)
|
void HooksManager::set_on_present(present_callback callback)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_present_mutex);
|
||||||
m_present_cb = std::move(callback);
|
m_present_cb = callback ? std::make_shared<present_callback>(std::move(callback)) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HooksManager::set_on_resize_buffers(resize_buffers_callback callback)
|
void HooksManager::set_on_resize_buffers(resize_buffers_callback callback)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_resize_buffers_mutex);
|
||||||
m_resize_buffers_cb = std::move(callback);
|
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)
|
void HooksManager::set_on_execute_command_lists(execute_command_lists_callback callback)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_execute_command_lists_mutex);
|
||||||
m_execute_command_lists_cb = std::move(callback);
|
m_execute_command_lists_cb =
|
||||||
|
callback ? std::make_shared<execute_command_lists_callback>(std::move(callback)) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HooksManager::hook_wnd_proc(HWND hwnd)
|
bool HooksManager::hook_wnd_proc(HWND hwnd)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_hook_state_mutex);
|
||||||
if (m_is_wnd_proc_hooked)
|
if (m_is_wnd_proc_hooked)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -419,7 +480,7 @@ namespace omath::hooks
|
|||||||
|
|
||||||
void HooksManager::unhook_wnd_proc()
|
void HooksManager::unhook_wnd_proc()
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_hook_state_mutex);
|
||||||
if (!m_is_wnd_proc_hooked)
|
if (!m_is_wnd_proc_hooked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -431,66 +492,67 @@ namespace omath::hooks
|
|||||||
|
|
||||||
void HooksManager::set_on_wnd_proc(wnd_proc_callback callback)
|
void HooksManager::set_on_wnd_proc(wnd_proc_callback callback)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_wnd_proc_mutex);
|
||||||
m_wnd_proc_cb = std::move(callback);
|
m_wnd_proc_cb = callback ? std::make_shared<wnd_proc_callback>(std::move(callback)) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detour implementations: copy callback under shared lock, call it unlocked,
|
// Detour implementations: copy a shared_ptr to the callback under shared lock, call it unlocked,
|
||||||
// then call original. This avoids a deadlock if the callback itself calls set_on_*().
|
// 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,
|
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 RECT* p_dest_rect, HWND h_dest_window_override,
|
||||||
const RGNDATA* p_dirty_region)
|
const RGNDATA* p_dirty_region)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
dx9_present_callback cb;
|
callback_ptr<dx9_present_callback> cb;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_dx9_present_mutex);
|
||||||
cb = mgr.m_dx9_present_cb;
|
cb = mgr.m_dx9_present_cb;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cb(p_device, p_source_rect, p_dest_rect, h_dest_window_override, p_dirty_region);
|
(*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,
|
return mgr.m_dx9_present_hook.call<HRESULT>(p_device, p_source_rect, p_dest_rect, h_dest_window_override,
|
||||||
h_dest_window_override, p_dirty_region);
|
p_dirty_region);
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT __stdcall HooksManager::dx9_reset_detour(IDirect3DDevice9* p_device,
|
HRESULT __stdcall HooksManager::dx9_reset_detour(IDirect3DDevice9* p_device,
|
||||||
D3DPRESENT_PARAMETERS* p_presentation_parameters)
|
D3DPRESENT_PARAMETERS* p_presentation_parameters)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
dx9_reset_callback cb;
|
callback_ptr<dx9_reset_callback> cb;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_dx9_reset_mutex);
|
||||||
cb = mgr.m_dx9_reset_cb;
|
cb = mgr.m_dx9_reset_cb;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cb(p_device, p_presentation_parameters);
|
(*cb)(p_device, p_presentation_parameters);
|
||||||
return mgr.m_dx9_reset_hook.call<HRESULT>(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)
|
HRESULT __stdcall HooksManager::dx9_end_scene_detour(IDirect3DDevice9* p_device)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
dx9_end_scene_callback cb;
|
callback_ptr<dx9_end_scene_callback> cb;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_dx9_end_scene_mutex);
|
||||||
cb = mgr.m_dx9_end_scene_cb;
|
cb = mgr.m_dx9_end_scene_cb;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cb(p_device);
|
(*cb)(p_device);
|
||||||
return mgr.m_dx9_end_scene_hook.call<HRESULT>(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)
|
HRESULT __stdcall HooksManager::dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
present_callback cb;
|
callback_ptr<present_callback> cb;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_present_mutex);
|
||||||
cb = mgr.m_present_cb;
|
cb = mgr.m_present_cb;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cb(p_swap_chain, sync_interval, flags);
|
(*cb)(p_swap_chain, sync_interval, flags);
|
||||||
return mgr.m_dx11_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags);
|
return mgr.m_dx11_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,27 +561,27 @@ namespace omath::hooks
|
|||||||
UINT swap_chain_flags)
|
UINT swap_chain_flags)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
resize_buffers_callback cb;
|
callback_ptr<resize_buffers_callback> cb;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_resize_buffers_mutex);
|
||||||
cb = mgr.m_resize_buffers_cb;
|
cb = mgr.m_resize_buffers_cb;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cb(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags);
|
(*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,
|
return mgr.m_dx11_resize_buffers_hook.call<HRESULT>(p_swap_chain, buffer_count, width, height, new_format,
|
||||||
new_format, swap_chain_flags);
|
swap_chain_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT __stdcall HooksManager::dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags)
|
HRESULT __stdcall HooksManager::dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
present_callback cb;
|
callback_ptr<present_callback> cb;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_present_mutex);
|
||||||
cb = mgr.m_present_cb;
|
cb = mgr.m_present_cb;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cb(p_swap_chain, sync_interval, flags);
|
(*cb)(p_swap_chain, sync_interval, flags);
|
||||||
return mgr.m_dx12_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags);
|
return mgr.m_dx12_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,15 +590,15 @@ namespace omath::hooks
|
|||||||
UINT swap_chain_flags)
|
UINT swap_chain_flags)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
resize_buffers_callback cb;
|
callback_ptr<resize_buffers_callback> cb;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_resize_buffers_mutex);
|
||||||
cb = mgr.m_resize_buffers_cb;
|
cb = mgr.m_resize_buffers_cb;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cb(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags);
|
(*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,
|
return mgr.m_dx12_resize_buffers_hook.call<HRESULT>(p_swap_chain, buffer_count, width, height, new_format,
|
||||||
new_format, swap_chain_flags);
|
swap_chain_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __stdcall HooksManager::dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue,
|
void __stdcall HooksManager::dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue,
|
||||||
@@ -544,33 +606,125 @@ namespace omath::hooks
|
|||||||
ID3D12CommandList* const* pp_command_lists)
|
ID3D12CommandList* const* pp_command_lists)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
execute_command_lists_callback cb;
|
callback_ptr<execute_command_lists_callback> cb;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_execute_command_lists_mutex);
|
||||||
cb = mgr.m_execute_command_lists_cb;
|
cb = mgr.m_execute_command_lists_cb;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
cb(p_command_queue, num_command_lists, pp_command_lists);
|
(*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);
|
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)
|
LRESULT __stdcall HooksManager::wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param)
|
||||||
{
|
{
|
||||||
auto& mgr = get();
|
auto& mgr = get();
|
||||||
wnd_proc_callback cb;
|
callback_ptr<wnd_proc_callback> cb;
|
||||||
WNDPROC original;
|
WNDPROC original;
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mgr.m_mutex);
|
std::shared_lock lock(mgr.m_wnd_proc_mutex);
|
||||||
cb = mgr.m_wnd_proc_cb;
|
cb = mgr.m_wnd_proc_cb;
|
||||||
original = mgr.m_original_wndproc;
|
original = mgr.m_original_wndproc;
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
{
|
{
|
||||||
if (const auto result = cb(hwnd, msg, w_param, l_param))
|
if (const auto result = (*cb)(hwnd, msg, w_param, l_param))
|
||||||
return *result;
|
return *result;
|
||||||
}
|
}
|
||||||
return CallWindowProc(original, hwnd, msg, w_param, l_param);
|
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
|
} // namespace omath::hooks
|
||||||
|
|
||||||
#else // !OMATH_ENABLE_HOOKING
|
#else // !OMATH_ENABLE_HOOKING
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -591,12 +600,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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
+45
-15
@@ -14,6 +14,8 @@
|
|||||||
#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 +87,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 -----------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,519 @@
|
|||||||
|
//
|
||||||
|
// 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::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, 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, 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, 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, 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, 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, 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, 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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -29,11 +29,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
//
|
||||||
|
// Created by Vladislav on 07.05.2026.
|
||||||
|
//
|
||||||
|
#include "omath/3d_primitives/obb.hpp"
|
||||||
|
#include "omath/collision/line_tracer.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <numbers>
|
||||||
|
|
||||||
|
using Vec3 = omath::Vector3<float>;
|
||||||
|
using Ray = omath::collision::Ray<>;
|
||||||
|
using LineTracer = omath::collision::LineTracer<>;
|
||||||
|
using OBB = omath::primitives::Obb<float>;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
Ray make_ray(const Vec3 start, const Vec3 end, const bool infinite = false)
|
||||||
|
{
|
||||||
|
Ray r;
|
||||||
|
r.start = start;
|
||||||
|
r.end = end;
|
||||||
|
r.infinite_length = infinite;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr OBB axis_aligned_obb(const Vec3& center, const Vec3& half_extents) noexcept
|
||||||
|
{
|
||||||
|
return OBB{center, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}, half_extents};
|
||||||
|
}
|
||||||
|
|
||||||
|
OBB rotated_around_z(const Vec3& center, const Vec3& half_extents, const float radians) noexcept
|
||||||
|
{
|
||||||
|
const auto c = std::cos(radians);
|
||||||
|
const auto s = std::sin(radians);
|
||||||
|
return OBB{center, {c, s, 0.f}, {-s, c, 0.f}, {0.f, 0.f, 1.f}, half_extents};
|
||||||
|
}
|
||||||
|
|
||||||
|
OBB rotated_around_y(const Vec3& center, const Vec3& half_extents, const float radians) noexcept
|
||||||
|
{
|
||||||
|
const auto c = std::cos(radians);
|
||||||
|
const auto s = std::sin(radians);
|
||||||
|
return OBB{center, {c, 0.f, -s}, {0.f, 1.f, 0.f}, {s, 0.f, c}, half_extents};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// --- axis-aligned OBB behaves like AABB ---
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, AxisAlignedHitAlongZ)
|
||||||
|
{
|
||||||
|
const auto box = axis_aligned_obb({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({0.f, 0.f, -5.f}, {0.f, 0.f, 5.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, -1.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, AxisAlignedHitAlongX)
|
||||||
|
{
|
||||||
|
const auto box = axis_aligned_obb({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({-5.f, 0.f, 0.f}, {5.f, 0.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, -1.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, MissReturnsEnd)
|
||||||
|
{
|
||||||
|
const auto box = axis_aligned_obb({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({0.f, 5.f, -5.f}, {0.f, 5.f, 5.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_EQ(hit, ray.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RayTooShortReturnsEnd)
|
||||||
|
{
|
||||||
|
const auto box = axis_aligned_obb({4.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({0.f, 0.f, 0.f}, {2.f, 0.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_EQ(hit, ray.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, InfiniteRayHits)
|
||||||
|
{
|
||||||
|
const auto box = axis_aligned_obb({4.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({0.f, 0.f, 0.f}, {2.f, 0.f, 0.f}, true);
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 3.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RayStartsInsideBox)
|
||||||
|
{
|
||||||
|
const auto box = axis_aligned_obb({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({0.f, 0.f, 0.f}, {5.f, 0.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RayBehindBoxReturnsEnd)
|
||||||
|
{
|
||||||
|
const auto box = axis_aligned_obb({4.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({10.f, 0.f, 0.f}, {20.f, 0.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_EQ(hit, ray.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- rotated OBB ---
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RotatedBoxHitOnRotatedFace)
|
||||||
|
{
|
||||||
|
// Box centred at the origin, rotated 45° around Z. After rotation, the box's "near" face
|
||||||
|
// (originally x=-1) is now perpendicular to the (1, 1, 0)/√2 direction. A ray approaching
|
||||||
|
// from +X (along world -X) first hits the box at the rotated face — at x = √2 ≈ 1.414.
|
||||||
|
const auto box = rotated_around_z({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, std::numbers::pi_v<float> / 4.f);
|
||||||
|
const auto ray = make_ray({5.f, 0.f, 0.f}, {-5.f, 0.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, std::numbers::sqrt2_v<float>, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RotatedAroundYBoxHitOnRotatedFace)
|
||||||
|
{
|
||||||
|
const auto box = rotated_around_y({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, std::numbers::pi_v<float> / 4.f);
|
||||||
|
const auto ray = make_ray({0.f, 0.f, 5.f}, {0.f, 0.f, -5.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, std::numbers::sqrt2_v<float>, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RotatedBoxMissesWhereAabbWouldHit)
|
||||||
|
{
|
||||||
|
// A unit cube rotated 45° around Z has an XY footprint that is a diamond reaching
|
||||||
|
// (±√2, 0) and (0, ±√2). The AABB envelope spans x,y ∈ [-√2, √2], but at y just below √2
|
||||||
|
// the diamond is essentially a point. A ray at y = 1.43 is outside the diamond entirely
|
||||||
|
// (|x| + |y| ≤ √2 ⇒ |x| ≤ √2 - 1.43 < 0), yet it would still pass through the AABB
|
||||||
|
// envelope of the rotated box.
|
||||||
|
const auto box = rotated_around_z({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, std::numbers::pi_v<float> / 4.f);
|
||||||
|
const auto ray = make_ray({-5.f, 1.43f, 0.f}, {5.f, 1.43f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_EQ(hit, ray.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RotatedThinBoxHitFromTheSide)
|
||||||
|
{
|
||||||
|
// Long, thin axis-aligned slab along X, rotated 90° around Z so it now points along Y.
|
||||||
|
// A ray from +X straight back along -X must miss (the slab is thin in X), but a ray along
|
||||||
|
// -Y from +Y must hit.
|
||||||
|
const auto box = rotated_around_z({0.f, 0.f, 0.f}, {5.f, 0.5f, 1.f}, std::numbers::pi_v<float> / 2.f);
|
||||||
|
|
||||||
|
const auto ray_along_x = make_ray({10.f, 0.f, 0.f}, {-10.f, 0.f, 0.f});
|
||||||
|
const auto hit_x = LineTracer::get_ray_hit_point(ray_along_x, box);
|
||||||
|
EXPECT_NE(hit_x, ray_along_x.end);
|
||||||
|
EXPECT_NEAR(hit_x.x, 0.5f, 1e-4f); // hit on the rotated slab's narrow side
|
||||||
|
|
||||||
|
const auto ray_along_y = make_ray({0.f, 10.f, 0.f}, {0.f, -10.f, 0.f});
|
||||||
|
const auto hit_y = LineTracer::get_ray_hit_point(ray_along_y, box);
|
||||||
|
EXPECT_NE(hit_y, ray_along_y.end);
|
||||||
|
EXPECT_NEAR(hit_y.y, 5.f, 1e-4f); // hit on the long end at y=+5
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RotatedAndTranslatedBoxHit)
|
||||||
|
{
|
||||||
|
const auto box = rotated_around_z({10.f, 5.f, 0.f}, {1.f, 1.f, 1.f}, std::numbers::pi_v<float> / 4.f);
|
||||||
|
// Ray approaches the rotated box from +X.
|
||||||
|
const auto ray = make_ray({20.f, 5.f, 0.f}, {0.f, 5.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 10.f + std::numbers::sqrt2_v<float>, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 5.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, RayStartsInsideRotatedBox)
|
||||||
|
{
|
||||||
|
const auto box = rotated_around_z({2.f, 3.f, 0.f}, {2.f, 1.f, 1.f}, std::numbers::pi_v<float> / 6.f);
|
||||||
|
const auto ray = make_ray({2.f, 3.f, 0.f}, {10.f, 3.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 2.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 3.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, TangentRayHitsRotatedBox)
|
||||||
|
{
|
||||||
|
const auto box = rotated_around_z({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, std::numbers::pi_v<float> / 4.f);
|
||||||
|
const auto ray = make_ray({-5.f, std::numbers::sqrt2_v<float>, 0.f},
|
||||||
|
{5.f, std::numbers::sqrt2_v<float>, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, std::numbers::sqrt2_v<float>, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, DegeneratePlaneBoxCanBeHit)
|
||||||
|
{
|
||||||
|
const auto box = axis_aligned_obb({0.f, 0.f, 0.f}, {1.f, 1.f, 0.f});
|
||||||
|
const auto ray = make_ray({0.f, 0.f, -5.f}, {0.f, 0.f, 5.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||||
|
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, ParallelRayOutsideMisses)
|
||||||
|
{
|
||||||
|
// Ray runs parallel to a slab face, completely outside the slab.
|
||||||
|
const auto box = axis_aligned_obb({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({-5.f, 2.f, 0.f}, {5.f, 2.f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_EQ(hit, ray.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, ParallelRayInsideHits)
|
||||||
|
{
|
||||||
|
// Ray runs parallel to a slab face but inside the slab — should still hit the entry plane.
|
||||||
|
const auto box = axis_aligned_obb({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
const auto ray = make_ray({-5.f, 0.5f, 0.f}, {5.f, 0.5f, 0.f});
|
||||||
|
|
||||||
|
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||||
|
EXPECT_NE(hit, ray.end);
|
||||||
|
EXPECT_NEAR(hit.x, -1.f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineTracerOBBTests, MatchesAabbForAxisAlignedBox)
|
||||||
|
{
|
||||||
|
using AABB = omath::primitives::Aabb<float>;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
Vec3 center;
|
||||||
|
Vec3 half;
|
||||||
|
Vec3 ray_start;
|
||||||
|
Vec3 ray_end;
|
||||||
|
} cases[] = {
|
||||||
|
{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, {-5.f, 0.f, 0.f}, {5.f, 0.f, 0.f}},
|
||||||
|
{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, {0.f, -5.f, 0.f}, {0.f, 5.f, 0.f}},
|
||||||
|
{{4.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, {0.f, 0.f, 0.f}, {2.f, 0.f, 0.f}}, // too short
|
||||||
|
{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, {-5.f, 5.f, 0.f}, {5.f, 5.f, 0.f}}, // miss
|
||||||
|
{{2.f, 3.f, -1.f}, {0.5f, 0.5f, 0.5f}, {0.f, 0.f, 0.f}, {10.f, 15.f, -5.f}}, // diagonal
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& [center, half, start, end]: cases)
|
||||||
|
{
|
||||||
|
const AABB aabb{center - half, center + half};
|
||||||
|
const auto obb = axis_aligned_obb(center, half);
|
||||||
|
const auto ray = make_ray(start, end);
|
||||||
|
|
||||||
|
const auto aabb_hit = LineTracer::get_ray_hit_point(ray, aabb);
|
||||||
|
const auto obb_hit = LineTracer::get_ray_hit_point(ray, obb);
|
||||||
|
|
||||||
|
EXPECT_NEAR(aabb_hit.x, obb_hit.x, 1e-4f);
|
||||||
|
EXPECT_NEAR(aabb_hit.y, obb_hit.y, 1e-4f);
|
||||||
|
EXPECT_NEAR(aabb_hit.z, obb_hit.z, 1e-4f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// UnitTestMat.cpp
|
// UnitTestMat.cpp
|
||||||
#include "omath/linear_algebra/mat.hpp"
|
#include "omath/linear_algebra/mat.hpp"
|
||||||
#include "omath/linear_algebra/vector3.hpp"
|
#include "omath/linear_algebra/vector3.hpp"
|
||||||
|
#include "omath/trigonometry/angles.hpp"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
using namespace omath;
|
using namespace omath;
|
||||||
@@ -220,8 +221,8 @@ TEST(UnitTestMatStandalone, Equanity)
|
|||||||
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
|
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
|
||||||
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
|
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
|
||||||
|
|
||||||
const auto proj_left_handed = omath::mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
const auto proj_left_handed = omath::mat_perspective_left_handed_vertical_fov(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
const auto proj_right_handed = omath::mat_perspective_right_handed(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
const auto proj_right_handed = omath::mat_perspective_right_handed_vertical_fov(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
auto ndc_left_handed = proj_left_handed * omath::mat_column_from_vector(left_handed);
|
auto ndc_left_handed = proj_left_handed * omath::mat_column_from_vector(left_handed);
|
||||||
auto ndc_right_handed = proj_right_handed * omath::mat_column_from_vector(right_handed);
|
auto ndc_right_handed = proj_right_handed * omath::mat_column_from_vector(right_handed);
|
||||||
@@ -233,7 +234,7 @@ TEST(UnitTestMatStandalone, Equanity)
|
|||||||
}
|
}
|
||||||
TEST(UnitTestMatStandalone, MatPerspectiveLeftHanded)
|
TEST(UnitTestMatStandalone, MatPerspectiveLeftHanded)
|
||||||
{
|
{
|
||||||
const auto perspective_proj = mat_perspective_left_handed(90.f, 16.f/9.f, 0.1f, 1000.f);
|
const auto perspective_proj = mat_perspective_left_handed_vertical_fov(90.f, 16.f/9.f, 0.1f, 1000.f);
|
||||||
auto projected = perspective_proj
|
auto projected = perspective_proj
|
||||||
* mat_column_from_vector<float>({0, 0, 0.1001});
|
* mat_column_from_vector<float>({0, 0, 0.1001});
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@ TEST(UnitTestMatStandalone, MatPerspectiveLeftHanded)
|
|||||||
|
|
||||||
TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedZeroToOne)
|
TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedZeroToOne)
|
||||||
{
|
{
|
||||||
const auto proj = mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
const auto proj = mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
// Near plane point should map to z ~ 0
|
// Near plane point should map to z ~ 0
|
||||||
@@ -266,7 +267,7 @@ TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedZeroToOne)
|
|||||||
|
|
||||||
TEST(UnitTestMatStandalone, MatPerspectiveRightHandedZeroToOne)
|
TEST(UnitTestMatStandalone, MatPerspectiveRightHandedZeroToOne)
|
||||||
{
|
{
|
||||||
const auto proj = mat_perspective_right_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
const auto proj = mat_perspective_right_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
// Near plane point (negative z for right-handed) should map to z ~ 0
|
// Near plane point (negative z for right-handed) should map to z ~ 0
|
||||||
@@ -289,8 +290,8 @@ TEST(UnitTestMatStandalone, MatPerspectiveRightHandedZeroToOne)
|
|||||||
TEST(UnitTestMatStandalone, MatPerspectiveNegativeOneToOneRange)
|
TEST(UnitTestMatStandalone, MatPerspectiveNegativeOneToOneRange)
|
||||||
{
|
{
|
||||||
// Verify existing [-1, 1] behavior with explicit template arg matches default
|
// Verify existing [-1, 1] behavior with explicit template arg matches default
|
||||||
const auto proj_default = mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
const auto proj_default = mat_perspective_left_handed_vertical_fov(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
const auto proj_explicit = mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR,
|
const auto proj_explicit = mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
|
||||||
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
EXPECT_EQ(proj_default, proj_explicit);
|
EXPECT_EQ(proj_default, proj_explicit);
|
||||||
@@ -306,15 +307,174 @@ TEST(UnitTestMatStandalone, MatPerspectiveNegativeOneToOneRange)
|
|||||||
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f);
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveRightHandedNegOneToOne)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_right_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
// Near plane (negative z for RH) should map to z ~ -1
|
||||||
|
auto near_pt = proj * mat_column_from_vector<float>({0, 0, -0.1f});
|
||||||
|
near_pt /= near_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), -1.0f, 1e-3f);
|
||||||
|
|
||||||
|
// Far plane should map to z ~ 1
|
||||||
|
auto far_pt = proj * mat_column_from_vector<float>({0, 0, -1000.f});
|
||||||
|
far_pt /= far_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f);
|
||||||
|
|
||||||
|
// Mid-range point should be in (-1, 1)
|
||||||
|
auto mid_pt = proj * mat_column_from_vector<float>({0, 0, -500.f});
|
||||||
|
mid_pt /= mid_pt.at(3, 0);
|
||||||
|
EXPECT_GT(mid_pt.at(2, 0), -1.0f);
|
||||||
|
EXPECT_LT(mid_pt.at(2, 0), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedHorizontalFovZeroToOne)
|
||||||
|
{
|
||||||
|
// hfov=90 deg, aspect=16/9 => tan(hfov/2)=1, so x_axis=1 and y_axis=aspect
|
||||||
|
const auto proj = mat_perspective_left_handed_horizontal_fov<float, MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange::ZERO_TO_ONE>(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
// Near plane should map to z ~ 0
|
||||||
|
auto near_pt = proj * mat_column_from_vector<float>({0, 0, 0.1f});
|
||||||
|
near_pt /= near_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), 0.0f, 1e-4f);
|
||||||
|
|
||||||
|
// Far plane should map to z ~ 1
|
||||||
|
auto far_pt = proj * mat_column_from_vector<float>({0, 0, 1000.f});
|
||||||
|
far_pt /= far_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-4f);
|
||||||
|
|
||||||
|
// Right edge of horizontal frustum at near plane (view_x = tan(hfov/2)*near = 0.1)
|
||||||
|
auto right_edge = proj * mat_column_from_vector<float>({0.1f, 0, 0.1f});
|
||||||
|
right_edge /= right_edge.at(3, 0);
|
||||||
|
EXPECT_NEAR(right_edge.at(0, 0), 1.0f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedHorizontalFovNegOneToOne)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_left_handed_horizontal_fov<float, MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
auto near_pt = proj * mat_column_from_vector<float>({0, 0, 0.1f});
|
||||||
|
near_pt /= near_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), -1.0f, 1e-3f);
|
||||||
|
|
||||||
|
auto far_pt = proj * mat_column_from_vector<float>({0, 0, 1000.f});
|
||||||
|
far_pt /= far_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f);
|
||||||
|
|
||||||
|
auto right_edge = proj * mat_column_from_vector<float>({0.1f, 0, 0.1f});
|
||||||
|
right_edge /= right_edge.at(3, 0);
|
||||||
|
EXPECT_NEAR(right_edge.at(0, 0), 1.0f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveRightHandedHorizontalFovZeroToOne)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_right_handed_horizontal_fov<float, MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange::ZERO_TO_ONE>(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
auto near_pt = proj * mat_column_from_vector<float>({0, 0, -0.1f});
|
||||||
|
near_pt /= near_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), 0.0f, 1e-4f);
|
||||||
|
|
||||||
|
auto far_pt = proj * mat_column_from_vector<float>({0, 0, -1000.f});
|
||||||
|
far_pt /= far_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-4f);
|
||||||
|
|
||||||
|
auto right_edge = proj * mat_column_from_vector<float>({0.1f, 0, -0.1f});
|
||||||
|
right_edge /= right_edge.at(3, 0);
|
||||||
|
EXPECT_NEAR(right_edge.at(0, 0), 1.0f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveRightHandedHorizontalFovNegOneToOne)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_right_handed_horizontal_fov<float, MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
auto near_pt = proj * mat_column_from_vector<float>({0, 0, -0.1f});
|
||||||
|
near_pt /= near_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(near_pt.at(2, 0), -1.0f, 1e-3f);
|
||||||
|
|
||||||
|
auto far_pt = proj * mat_column_from_vector<float>({0, 0, -1000.f});
|
||||||
|
far_pt /= far_pt.at(3, 0);
|
||||||
|
EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f);
|
||||||
|
|
||||||
|
auto right_edge = proj * mat_column_from_vector<float>({0.1f, 0, -0.1f});
|
||||||
|
right_edge /= right_edge.at(3, 0);
|
||||||
|
EXPECT_NEAR(right_edge.at(0, 0), 1.0f, 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveHorizontalVsVerticalFovEquivalence)
|
||||||
|
{
|
||||||
|
constexpr float hfov_deg = 90.f;
|
||||||
|
constexpr float aspect = 16.f / 9.f;
|
||||||
|
const float vfov_deg = angles::horizontal_fov_to_vertical(hfov_deg, aspect);
|
||||||
|
|
||||||
|
const auto proj_h = mat_perspective_left_handed_horizontal_fov(hfov_deg, aspect, 0.1f, 1000.f);
|
||||||
|
const auto proj_v = mat_perspective_left_handed_vertical_fov(vfov_deg, aspect, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 4; ++i)
|
||||||
|
for (size_t j = 0; j < 4; ++j)
|
||||||
|
EXPECT_NEAR(proj_h.at(i, j), proj_v.at(i, j), 1e-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handedness contract: clip_w sign tells front-of-camera vs behind.
|
||||||
|
// LH: +z view-space is in front (clip_w > 0), -z is behind (clip_w < 0).
|
||||||
|
// RH: -z view-space is in front (clip_w > 0), +z is behind (clip_w < 0).
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedVerticalFovHandedness)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_left_handed_vertical_fov(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
const auto in_front = proj * mat_column_from_vector<float>({0, 0, 1.f});
|
||||||
|
const auto behind = proj * mat_column_from_vector<float>({0, 0, -1.f});
|
||||||
|
|
||||||
|
EXPECT_GT(in_front.at(3, 0), 0.0f);
|
||||||
|
EXPECT_LT(behind.at(3, 0), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveRightHandedVerticalFovHandedness)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_right_handed_vertical_fov(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
const auto in_front = proj * mat_column_from_vector<float>({0, 0, -1.f});
|
||||||
|
const auto behind = proj * mat_column_from_vector<float>({0, 0, 1.f});
|
||||||
|
|
||||||
|
EXPECT_GT(in_front.at(3, 0), 0.0f);
|
||||||
|
EXPECT_LT(behind.at(3, 0), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedHorizontalFovHandedness)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_left_handed_horizontal_fov(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
const auto in_front = proj * mat_column_from_vector<float>({0, 0, 1.f});
|
||||||
|
const auto behind = proj * mat_column_from_vector<float>({0, 0, -1.f});
|
||||||
|
|
||||||
|
EXPECT_GT(in_front.at(3, 0), 0.0f);
|
||||||
|
EXPECT_LT(behind.at(3, 0), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestMatStandalone, MatPerspectiveRightHandedHorizontalFovHandedness)
|
||||||
|
{
|
||||||
|
const auto proj = mat_perspective_right_handed_horizontal_fov(90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
|
const auto in_front = proj * mat_column_from_vector<float>({0, 0, -1.f});
|
||||||
|
const auto behind = proj * mat_column_from_vector<float>({0, 0, 1.f});
|
||||||
|
|
||||||
|
EXPECT_GT(in_front.at(3, 0), 0.0f);
|
||||||
|
EXPECT_LT(behind.at(3, 0), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(UnitTestMatStandalone, MatPerspectiveZeroToOneEquanity)
|
TEST(UnitTestMatStandalone, MatPerspectiveZeroToOneEquanity)
|
||||||
{
|
{
|
||||||
// LH and RH should produce same NDC for mirrored z
|
// LH and RH should produce same NDC for mirrored z
|
||||||
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
|
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
|
||||||
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
|
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
|
||||||
|
|
||||||
const auto proj_lh = mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
const auto proj_lh = mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
const auto proj_rh = mat_perspective_right_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
const auto proj_rh = mat_perspective_right_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
90.f, 16.f / 9.f, 0.1f, 1000.f);
|
||||||
|
|
||||||
auto ndc_lh = proj_lh * mat_column_from_vector(left_handed);
|
auto ndc_lh = proj_lh * mat_column_from_vector(left_handed);
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
//
|
||||||
|
// Created by Vladislav on 07.05.2026.
|
||||||
|
//
|
||||||
|
#include <cmath>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <numbers>
|
||||||
|
#include <omath/3d_primitives/obb.hpp>
|
||||||
|
#include <omath/engines/opengl_engine/camera.hpp>
|
||||||
|
#include <omath/engines/source_engine/camera.hpp>
|
||||||
|
#include <omath/engines/unity_engine/camera.hpp>
|
||||||
|
#include <omath/projection/camera.hpp>
|
||||||
|
|
||||||
|
using ObbF = omath::primitives::Obb<float>;
|
||||||
|
using ObbD = omath::primitives::Obb<double>;
|
||||||
|
using Vec3F = omath::Vector3<float>;
|
||||||
|
using Vec3D = omath::Vector3<double>;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr ObbF axis_aligned_obb(const Vec3F& center, const Vec3F& half_extents) noexcept
|
||||||
|
{
|
||||||
|
return ObbF{center, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}, half_extents};
|
||||||
|
}
|
||||||
|
|
||||||
|
ObbF rotated_around_z(const Vec3F& center, const Vec3F& half_extents, const float radians) noexcept
|
||||||
|
{
|
||||||
|
const auto c = std::cos(radians);
|
||||||
|
const auto s = std::sin(radians);
|
||||||
|
return ObbF{center, {c, s, 0.f}, {-s, c, 0.f}, {0.f, 0.f, 1.f}, half_extents};
|
||||||
|
}
|
||||||
|
|
||||||
|
ObbF rotated_around_y(const Vec3F& center, const Vec3F& half_extents, const float radians) noexcept
|
||||||
|
{
|
||||||
|
const auto c = std::cos(radians);
|
||||||
|
const auto s = std::sin(radians);
|
||||||
|
return ObbF{center, {c, 0.f, -s}, {0.f, 1.f, 0.f}, {s, 0.f, c}, half_extents};
|
||||||
|
}
|
||||||
|
|
||||||
|
void expect_vec_near(const Vec3F& actual, const Vec3F& expected, const float epsilon = 1e-5f)
|
||||||
|
{
|
||||||
|
EXPECT_NEAR(actual.x, expected.x, epsilon);
|
||||||
|
EXPECT_NEAR(actual.y, expected.y, epsilon);
|
||||||
|
EXPECT_NEAR(actual.z, expected.z, epsilon);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// --- struct-level tests ---
|
||||||
|
|
||||||
|
TEST(ObbTests, VerticesOfAxisAlignedUnitBox)
|
||||||
|
{
|
||||||
|
constexpr auto box = axis_aligned_obb({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f});
|
||||||
|
constexpr auto v = box.vertices();
|
||||||
|
|
||||||
|
EXPECT_EQ(v[0], (Vec3F{-1.f, -1.f, -1.f}));
|
||||||
|
EXPECT_EQ(v[1], (Vec3F{1.f, -1.f, -1.f}));
|
||||||
|
EXPECT_EQ(v[2], (Vec3F{-1.f, 1.f, -1.f}));
|
||||||
|
EXPECT_EQ(v[3], (Vec3F{1.f, 1.f, -1.f}));
|
||||||
|
EXPECT_EQ(v[4], (Vec3F{-1.f, -1.f, 1.f}));
|
||||||
|
EXPECT_EQ(v[5], (Vec3F{1.f, -1.f, 1.f}));
|
||||||
|
EXPECT_EQ(v[6], (Vec3F{-1.f, 1.f, 1.f}));
|
||||||
|
EXPECT_EQ(v[7], (Vec3F{1.f, 1.f, 1.f}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, VerticesOfTranslatedBox)
|
||||||
|
{
|
||||||
|
constexpr auto box = axis_aligned_obb({10.f, 20.f, 30.f}, {1.f, 2.f, 3.f});
|
||||||
|
constexpr auto v = box.vertices();
|
||||||
|
|
||||||
|
EXPECT_EQ(v[0], (Vec3F{9.f, 18.f, 27.f}));
|
||||||
|
EXPECT_EQ(v[7], (Vec3F{11.f, 22.f, 33.f}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, VerticesOfRotatedBox)
|
||||||
|
{
|
||||||
|
constexpr auto pi = std::numbers::pi_v<float>;
|
||||||
|
const auto box = rotated_around_z({0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}, pi / 2.f);
|
||||||
|
const auto v = box.vertices();
|
||||||
|
|
||||||
|
// After 90° rotation around Z, local +X maps to world +Y, local +Y maps to world -X.
|
||||||
|
// The eight vertices are still the same eight points (a cube is symmetric), but their
|
||||||
|
// ordering changes. Check that the corner set as a whole is still |coord| == 1.
|
||||||
|
for (const auto& corner : v)
|
||||||
|
{
|
||||||
|
EXPECT_NEAR(std::abs(corner.x), 1.f, 1e-5f);
|
||||||
|
EXPECT_NEAR(std::abs(corner.y), 1.f, 1e-5f);
|
||||||
|
EXPECT_NEAR(std::abs(corner.z), 1.f, 1e-5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, VerticesOfTranslatedNonUniformRotatedBox)
|
||||||
|
{
|
||||||
|
const auto box = rotated_around_z({2.f, 3.f, 4.f}, {2.f, 1.f, 0.5f}, std::numbers::pi_v<float> / 2.f);
|
||||||
|
const auto v = box.vertices();
|
||||||
|
|
||||||
|
expect_vec_near(v[0], {3.f, 1.f, 3.5f});
|
||||||
|
expect_vec_near(v[1], {3.f, 5.f, 3.5f});
|
||||||
|
expect_vec_near(v[2], {1.f, 1.f, 3.5f});
|
||||||
|
expect_vec_near(v[3], {1.f, 5.f, 3.5f});
|
||||||
|
expect_vec_near(v[4], {3.f, 1.f, 4.5f});
|
||||||
|
expect_vec_near(v[5], {3.f, 5.f, 4.5f});
|
||||||
|
expect_vec_near(v[6], {1.f, 1.f, 4.5f});
|
||||||
|
expect_vec_near(v[7], {1.f, 5.f, 4.5f});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, VerticesCollapseWhenOneExtentIsZero)
|
||||||
|
{
|
||||||
|
constexpr auto box = axis_aligned_obb({1.f, 2.f, 3.f}, {2.f, 0.f, 4.f});
|
||||||
|
constexpr auto v = box.vertices();
|
||||||
|
|
||||||
|
EXPECT_EQ(v[0], v[2]);
|
||||||
|
EXPECT_EQ(v[1], v[3]);
|
||||||
|
EXPECT_EQ(v[4], v[6]);
|
||||||
|
EXPECT_EQ(v[5], v[7]);
|
||||||
|
|
||||||
|
EXPECT_EQ(v[0], (Vec3F{-1.f, 2.f, -1.f}));
|
||||||
|
EXPECT_EQ(v[1], (Vec3F{3.f, 2.f, -1.f}));
|
||||||
|
EXPECT_EQ(v[4], (Vec3F{-1.f, 2.f, 7.f}));
|
||||||
|
EXPECT_EQ(v[5], (Vec3F{3.f, 2.f, 7.f}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, DoublePrecisionInstantiation)
|
||||||
|
{
|
||||||
|
constexpr ObbD box{{0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, {2.0, 3.0, 4.0}};
|
||||||
|
constexpr auto v = box.vertices();
|
||||||
|
EXPECT_DOUBLE_EQ(v[0].x, -2.0);
|
||||||
|
EXPECT_DOUBLE_EQ(v[0].y, -3.0);
|
||||||
|
EXPECT_DOUBLE_EQ(v[0].z, -4.0);
|
||||||
|
EXPECT_DOUBLE_EQ(v[7].x, 2.0);
|
||||||
|
EXPECT_DOUBLE_EQ(v[7].y, 3.0);
|
||||||
|
EXPECT_DOUBLE_EQ(v[7].z, 4.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- frustum culling tests (Source Engine: +X forward, +Y left, +Z up) ---
|
||||||
|
|
||||||
|
TEST(ObbTests, AxisAlignedInFrontNotCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({100.f, 0.f, 0.f}, {10.f, 1.f, 1.f});
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, AxisAlignedBehindCameraCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({-150.f, 0.f, 0.f}, {50.f, 1.f, 1.f});
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, AxisAlignedBeyondFarPlaneCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({1750.f, 0.f, 0.f}, {250.f, 1.f, 1.f});
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, AxisAlignedStraddlingFarPlaneNotCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({1005.f, 0.f, 0.f}, {10.f, 1.f, 1.f});
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, AxisAlignedFarLeftCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({100.f, 4500.f, 0.f}, {10.f, 500.f, 1.f});
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, AxisAlignedFarRightCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({100.f, -4500.f, 0.f}, {10.f, 500.f, 1.f});
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, AxisAlignedAboveCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({100.f, 0.f, 5500.f}, {10.f, 1.f, 500.f});
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, AxisAlignedBelowCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({100.f, 0.f, -5500.f}, {10.f, 1.f, 500.f});
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, MatchesAabbForAxisAlignedBox)
|
||||||
|
{
|
||||||
|
// For axis-aligned OBBs, the result must agree with is_aabb_culled_by_frustum.
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
const struct
|
||||||
|
{
|
||||||
|
Vec3F center;
|
||||||
|
Vec3F half;
|
||||||
|
} cases[] = {
|
||||||
|
{{100.f, 0.f, 0.f}, {10.f, 1.f, 1.f}}, // in front
|
||||||
|
{{-150.f, 0.f, 0.f}, {50.f, 1.f, 1.f}}, // behind
|
||||||
|
{{1750.f, 0.f, 0.f}, {250.f, 1.f, 1.f}}, // beyond far
|
||||||
|
{{100.f, 4500.f, 0.f}, {10.f, 500.f, 1.f}}, // far left
|
||||||
|
{{0.f, 0.f, 0.f}, {500.f, 500.f, 500.f}}, // encloses camera
|
||||||
|
{{275.f, 0.f, 0.f}, {225.f, 1.f, 1.f}}, // straddles near
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& [center, half]: cases)
|
||||||
|
{
|
||||||
|
const omath::primitives::Aabb<float> aabb{center - half, center + half};
|
||||||
|
const auto obb = axis_aligned_obb(center, half);
|
||||||
|
EXPECT_EQ(cam.is_obb_culled_by_frustum(obb), cam.is_aabb_culled_by_frustum(aabb))
|
||||||
|
<< "mismatch for center (" << center.x << "," << center.y << "," << center.z << ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, RotationCanPullBoxIntoFrustum)
|
||||||
|
{
|
||||||
|
// Tall thin column sitting just outside the +Y frustum boundary at X=50.
|
||||||
|
// Axis-aligned: every corner has Y≈100 at X≈50, all outside the +Y plane → culled.
|
||||||
|
// Rotated 90° around world Y: the 50-unit extent now points along world +X, so the rod
|
||||||
|
// sweeps forward to X≈100 where the +Y plane is far more permissive — front end inside,
|
||||||
|
// box no longer fully outside → not culled.
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr Vec3F center{50.f, 100.f, 0.f};
|
||||||
|
constexpr Vec3F half{1.f, 1.f, 50.f};
|
||||||
|
|
||||||
|
constexpr auto axis_aligned = axis_aligned_obb(center, half);
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(axis_aligned));
|
||||||
|
|
||||||
|
const auto rotated = rotated_around_y(center, half, std::numbers::pi_v<float> / 2.f);
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(rotated));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, RotationCanPushBoxOutOfFrustum)
|
||||||
|
{
|
||||||
|
// Long forward-pointing rod whose front end pokes into the frustum near the +Y boundary.
|
||||||
|
// Axis-aligned (long along X): the front end at X≈100 has Y=129 just inside the +Y plane,
|
||||||
|
// so part of the rod is visible → not culled.
|
||||||
|
// Rotated 90° around Z: the rod's long axis now points along world Y, so all corners
|
||||||
|
// shift to Y∈[80,180] at X≈50 — every corner is outside the +Y plane → culled.
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr Vec3F center{50.f, 130.f, 0.f};
|
||||||
|
constexpr Vec3F half{50.f, 1.f, 1.f};
|
||||||
|
|
||||||
|
constexpr auto axis_aligned = axis_aligned_obb(center, half);
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(axis_aligned));
|
||||||
|
|
||||||
|
const auto rotated = rotated_around_z(center, half, std::numbers::pi_v<float> / 2.f);
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(rotated));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, RotatedBoxStraddlingFrustumNotCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
// Box centred in front, rotated 30° — clearly straddles into the frustum.
|
||||||
|
const auto obb = rotated_around_z({200.f, 0.f, 0.f}, {50.f, 50.f, 50.f},
|
||||||
|
std::numbers::pi_v<float> / 6.f);
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, OpenGlEngineRotatedInFrontNotCulled)
|
||||||
|
{
|
||||||
|
// OpenGL: -Z forward, COLUMN_MAJOR, NEGATIVE_ONE_TO_ONE
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f);
|
||||||
|
|
||||||
|
const auto obb = rotated_around_z({0.f, 0.f, -100.f}, {5.f, 5.f, 5.f},
|
||||||
|
std::numbers::pi_v<float> / 4.f);
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, OpenGlEngineBehindCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f);
|
||||||
|
|
||||||
|
constexpr auto obb = axis_aligned_obb({0.f, 0.f, 100.f}, {5.f, 5.f, 5.f});
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, OpenGlEngineStraddlingFarPlaneNotCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 100.f);
|
||||||
|
|
||||||
|
const auto obb = rotated_around_z({0.f, 0.f, -105.f}, {5.f, 5.f, 10.f},
|
||||||
|
std::numbers::pi_v<float> / 4.f);
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, UnityEngineBeyondFarCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
|
||||||
|
const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 500.f);
|
||||||
|
|
||||||
|
const auto obb = rotated_around_z({0.f, 0.f, 700.f}, {5.f, 5.f, 5.f},
|
||||||
|
std::numbers::pi_v<float> / 4.f);
|
||||||
|
EXPECT_TRUE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, DegenerateZeroVolumeInsideNotCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
// Zero-extent OBB — collapses to a point, but still must not be culled if the centre is inside.
|
||||||
|
constexpr auto obb = axis_aligned_obb({100.f, 0.f, 0.f}, {0.f, 0.f, 0.f});
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ObbTests, EnclosingCameraNotCulled)
|
||||||
|
{
|
||||||
|
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||||
|
0.01f, 1000.f);
|
||||||
|
|
||||||
|
// Huge rotated box that fully encloses the camera origin.
|
||||||
|
const auto obb = rotated_around_z({0.f, 0.f, 0.f}, {500.f, 500.f, 500.f},
|
||||||
|
std::numbers::pi_v<float> / 5.f);
|
||||||
|
EXPECT_FALSE(cam.is_obb_culled_by_frustum(obb));
|
||||||
|
}
|
||||||
@@ -240,6 +240,38 @@ TEST_F(UnitTestVector2, Abs_ZeroValues)
|
|||||||
EXPECT_FLOAT_EQ(v3.y, 0.0f);
|
EXPECT_FLOAT_EQ(v3.y, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector2, Abs_Const_ReturnsAbsoluteCopy)
|
||||||
|
{
|
||||||
|
const Vector2 v3(-1.0f, -2.0f);
|
||||||
|
const Vector2 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector2, Abs_Const_DoesNotMutateSource)
|
||||||
|
{
|
||||||
|
const Vector2 v3(-1.0f, -2.0f);
|
||||||
|
(void)v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(v3.x, -1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(v3.y, -2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector2, Abs_Const_PositiveValues)
|
||||||
|
{
|
||||||
|
const Vector2 v3(1.0f, 2.0f);
|
||||||
|
const Vector2 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector2, Abs_Const_MixedSigns)
|
||||||
|
{
|
||||||
|
const Vector2 v3(-3.5f, 4.5f);
|
||||||
|
const Vector2 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 3.5f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 4.5f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestVector2, Sum)
|
TEST_F(UnitTestVector2, Sum)
|
||||||
{
|
{
|
||||||
constexpr float sum = Vector2(1.0f, 2.0f).sum();
|
constexpr float sum = Vector2(1.0f, 2.0f).sum();
|
||||||
|
|||||||
@@ -244,6 +244,42 @@ TEST_F(UnitTestVector3, Abs)
|
|||||||
EXPECT_FLOAT_EQ(v3.z, 3.0f);
|
EXPECT_FLOAT_EQ(v3.z, 3.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector3, Abs_Const_ReturnsAbsoluteCopy)
|
||||||
|
{
|
||||||
|
const Vector3 v3(-1.0f, -2.0f, -3.0f);
|
||||||
|
const Vector3 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.z, 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector3, Abs_Const_DoesNotMutateSource)
|
||||||
|
{
|
||||||
|
const Vector3 v3(-1.0f, -2.0f, -3.0f);
|
||||||
|
(void)v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(v3.x, -1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(v3.y, -2.0f);
|
||||||
|
EXPECT_FLOAT_EQ(v3.z, -3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector3, Abs_Const_PositiveValues)
|
||||||
|
{
|
||||||
|
const Vector3 v3(1.0f, 2.0f, 3.0f);
|
||||||
|
const Vector3 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.z, 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector3, Abs_Const_MixedSigns)
|
||||||
|
{
|
||||||
|
const Vector3 v3(-1.5f, 2.5f, -3.5f);
|
||||||
|
const Vector3 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 1.5f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 2.5f);
|
||||||
|
EXPECT_FLOAT_EQ(result.z, 3.5f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestVector3, Sum)
|
TEST_F(UnitTestVector3, Sum)
|
||||||
{
|
{
|
||||||
constexpr auto sum = Vector3(1.0f, 2.0f, 3.0f).sum();
|
constexpr auto sum = Vector3(1.0f, 2.0f, 3.0f).sum();
|
||||||
|
|||||||
@@ -226,6 +226,46 @@ TEST_F(UnitTestVector4, Abs)
|
|||||||
EXPECT_FLOAT_EQ(v3.w, 4.0f);
|
EXPECT_FLOAT_EQ(v3.w, 4.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector4, Abs_Const_ReturnsAbsoluteCopy)
|
||||||
|
{
|
||||||
|
const Vector4 v3(-1.0f, -2.0f, -3.0f, -4.0f);
|
||||||
|
const Vector4 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.z, 3.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.w, 4.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector4, Abs_Const_DoesNotMutateSource)
|
||||||
|
{
|
||||||
|
const Vector4 v3(-1.0f, -2.0f, -3.0f, -4.0f);
|
||||||
|
(void)v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(v3.x, -1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(v3.y, -2.0f);
|
||||||
|
EXPECT_FLOAT_EQ(v3.z, -3.0f);
|
||||||
|
EXPECT_FLOAT_EQ(v3.w, -4.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector4, Abs_Const_PositiveValues)
|
||||||
|
{
|
||||||
|
const Vector4 v3(1.0f, 2.0f, 3.0f, 4.0f);
|
||||||
|
const Vector4 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.z, 3.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.w, 4.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UnitTestVector4, Abs_Const_MixedSigns)
|
||||||
|
{
|
||||||
|
const Vector4 v3(-1.5f, 2.5f, -3.5f, 4.5f);
|
||||||
|
const Vector4 result = v3.abs();
|
||||||
|
EXPECT_FLOAT_EQ(result.x, 1.5f);
|
||||||
|
EXPECT_FLOAT_EQ(result.y, 2.5f);
|
||||||
|
EXPECT_FLOAT_EQ(result.z, 3.5f);
|
||||||
|
EXPECT_FLOAT_EQ(result.w, 4.5f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestVector4, Sum)
|
TEST_F(UnitTestVector4, Sum)
|
||||||
{
|
{
|
||||||
constexpr float sum = Vector4(1.0f, 2.0f, 3.0f, 4.0f).sum();
|
constexpr float sum = Vector4(1.0f, 2.0f, 3.0f, 4.0f).sum();
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-4) end
|
||||||
|
|
||||||
|
local function cube_points()
|
||||||
|
return {
|
||||||
|
omath.Vec3.new(-1, -1, -1),
|
||||||
|
omath.Vec3.new(1, -1, -1),
|
||||||
|
omath.Vec3.new(-1, 1, -1),
|
||||||
|
omath.Vec3.new(1, 1, -1),
|
||||||
|
omath.Vec3.new(-1, -1, 1),
|
||||||
|
omath.Vec3.new(1, -1, 1),
|
||||||
|
omath.Vec3.new(-1, 1, 1),
|
||||||
|
omath.Vec3.new(1, 1, 1),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Aabb_constructor_and_fields()
|
||||||
|
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(1, 2, 3))
|
||||||
|
assert(aabb.min.x == -1 and aabb.max.z == 3)
|
||||||
|
aabb.max = omath.Vec3.new(2, 3, 4)
|
||||||
|
assert(aabb.max.x == 2 and aabb.max.y == 3 and aabb.max.z == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Aabb_center_extents()
|
||||||
|
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(3, 6, 9))
|
||||||
|
local center = aabb:center()
|
||||||
|
local extents = aabb:extents()
|
||||||
|
assert(center.x == 1 and center.y == 2 and center.z == 3)
|
||||||
|
assert(extents.x == 2 and extents.y == 4 and extents.z == 6)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Aabb_top_bottom_default_axis()
|
||||||
|
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(3, 6, 9))
|
||||||
|
assert(aabb:top().y == 6)
|
||||||
|
assert(aabb:bottom().y == -2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Aabb_top_bottom_explicit_axis()
|
||||||
|
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(3, 6, 9))
|
||||||
|
assert(aabb:top(omath.primitives.UpAxis.X).x == 3)
|
||||||
|
assert(aabb:bottom(omath.primitives.UpAxis.Z).z == -3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Aabb_is_collide()
|
||||||
|
local a = omath.primitives.Aabb.new(omath.Vec3.new(-1, -1, -1), omath.Vec3.new(1, 1, 1))
|
||||||
|
local b = omath.primitives.Aabb.new(omath.Vec3.new(0, 0, 0), omath.Vec3.new(2, 2, 2))
|
||||||
|
local c = omath.primitives.Aabb.new(omath.Vec3.new(3, 3, 3), omath.Vec3.new(4, 4, 4))
|
||||||
|
assert(a:is_collide(b))
|
||||||
|
assert(not a:is_collide(c))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Aabb_as_table()
|
||||||
|
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(1, 2, 3))
|
||||||
|
local t = aabb:as_table()
|
||||||
|
assert(t.min.x == -1 and t.max.z == 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Obb_constructor_and_vertices()
|
||||||
|
local obb = omath.primitives.Obb.new(
|
||||||
|
omath.Vec3.new(0, 0, 0),
|
||||||
|
omath.Vec3.new(1, 0, 0),
|
||||||
|
omath.Vec3.new(0, 1, 0),
|
||||||
|
omath.Vec3.new(0, 0, 1),
|
||||||
|
omath.Vec3.new(1, 2, 3)
|
||||||
|
)
|
||||||
|
local vertices = obb:vertices()
|
||||||
|
assert(#vertices == 8)
|
||||||
|
assert(vertices[1].x == -1 and vertices[1].y == -2 and vertices[1].z == -3)
|
||||||
|
assert(vertices[8].x == 1 and vertices[8].y == 2 and vertices[8].z == 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Ray_constructor_and_direction()
|
||||||
|
local ray = omath.collision.Ray.new(omath.Vec3.new(1, 2, 3), omath.Vec3.new(4, 6, 3), true)
|
||||||
|
local dir = ray:direction_vector()
|
||||||
|
local normal = ray:direction_vector_normalized()
|
||||||
|
assert(ray.infinite_length)
|
||||||
|
assert(dir.x == 3 and dir.y == 4 and dir.z == 0)
|
||||||
|
assert(approx(normal:length(), 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_LineTracer_triangle_hit()
|
||||||
|
local tri = omath.Triangle.new(omath.Vec3.new(0, 0, 0), omath.Vec3.new(2, 0, 0), omath.Vec3.new(0, 2, 0))
|
||||||
|
local ray = omath.collision.Ray.new(omath.Vec3.new(0.5, 0.5, -1), omath.Vec3.new(0.5, 0.5, 1))
|
||||||
|
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, tri)
|
||||||
|
assert(omath.collision.LineTracer.ray_hits_triangle(ray, tri))
|
||||||
|
assert(not omath.collision.LineTracer.can_trace_line(ray, tri))
|
||||||
|
assert(approx(hit.x, 0.5) and approx(hit.y, 0.5) and approx(hit.z, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_LineTracer_triangle_miss()
|
||||||
|
local tri = omath.Triangle.new(omath.Vec3.new(0, 0, 0), omath.Vec3.new(1, 0, 0), omath.Vec3.new(0, 1, 0))
|
||||||
|
local ray = omath.collision.Ray.new(omath.Vec3.new(2, 2, -1), omath.Vec3.new(2, 2, 1))
|
||||||
|
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, tri)
|
||||||
|
assert(not omath.collision.LineTracer.ray_hits_triangle(ray, tri))
|
||||||
|
assert(omath.collision.LineTracer.can_trace_line(ray, tri))
|
||||||
|
assert(hit == ray["end"])
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_LineTracer_aabb_hit()
|
||||||
|
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -1, -1), omath.Vec3.new(1, 1, 1))
|
||||||
|
local ray = omath.collision.Ray.new(omath.Vec3.new(0, 0, -3), omath.Vec3.new(0, 0, 3))
|
||||||
|
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, aabb)
|
||||||
|
assert(omath.collision.LineTracer.ray_hits_aabb(ray, aabb))
|
||||||
|
assert(approx(hit.x, 0) and approx(hit.y, 0) and approx(hit.z, -1))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_LineTracer_aabb_miss()
|
||||||
|
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -1, -1), omath.Vec3.new(1, 1, 1))
|
||||||
|
local ray = omath.collision.Ray.new(omath.Vec3.new(0, 3, -3), omath.Vec3.new(0, 3, 3))
|
||||||
|
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, aabb)
|
||||||
|
assert(not omath.collision.LineTracer.ray_hits_aabb(ray, aabb))
|
||||||
|
assert(hit == ray["end"])
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_LineTracer_obb_hit()
|
||||||
|
local obb = omath.primitives.Obb.new(
|
||||||
|
omath.Vec3.new(0, 0, 0),
|
||||||
|
omath.Vec3.new(1, 0, 0),
|
||||||
|
omath.Vec3.new(0, 1, 0),
|
||||||
|
omath.Vec3.new(0, 0, 1),
|
||||||
|
omath.Vec3.new(1, 1, 1)
|
||||||
|
)
|
||||||
|
local ray = omath.collision.Ray.new(omath.Vec3.new(0, 0, -3), omath.Vec3.new(0, 0, 3))
|
||||||
|
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, obb)
|
||||||
|
assert(omath.collision.LineTracer.ray_hits_obb(ray, obb))
|
||||||
|
assert(approx(hit.z, -1))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_ConvexCollider_vertices_and_support()
|
||||||
|
local collider = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(2, 0, 0))
|
||||||
|
assert(collider:vertex_count() == 8)
|
||||||
|
assert(#collider:vertices() == 8)
|
||||||
|
local support = collider:find_abs_furthest_vertex_position(omath.Vec3.new(1, 0, 0))
|
||||||
|
assert(support.x == 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Gjk_support_vertex()
|
||||||
|
local a = omath.collision.ConvexCollider.new(cube_points())
|
||||||
|
local b = omath.collision.ConvexCollider.new(cube_points())
|
||||||
|
local support = omath.collision.gjk_support_vertex(a, b, omath.Vec3.new(1, 0, 0))
|
||||||
|
assert(support.x == 2 and support.y == 0 and support.z == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Gjk_collide_true()
|
||||||
|
local a = omath.collision.ConvexCollider.new(cube_points())
|
||||||
|
local b = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(1, 0, 0))
|
||||||
|
assert(omath.collision.gjk_collide(a, b))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Gjk_collide_false()
|
||||||
|
local a = omath.collision.ConvexCollider.new(cube_points())
|
||||||
|
local b = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(5, 0, 0))
|
||||||
|
assert(not omath.collision.gjk_collide(a, b))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Epa_solve_hit()
|
||||||
|
local a = omath.collision.ConvexCollider.new(cube_points())
|
||||||
|
local b = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(1.5, 0, 0))
|
||||||
|
local result = omath.collision.epa_solve(a, b)
|
||||||
|
assert(result ~= nil)
|
||||||
|
assert(result.depth > 0)
|
||||||
|
assert(approx(result.normal:length(), 1))
|
||||||
|
assert(approx(result.penetration_vector:length(), result.depth))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Collision_Epa_solve_miss()
|
||||||
|
local a = omath.collision.ConvexCollider.new(cube_points())
|
||||||
|
local b = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(5, 0, 0))
|
||||||
|
assert(omath.collision.epa_solve(a, b) == nil)
|
||||||
|
end
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-4) end
|
||||||
|
|
||||||
|
local function color() return omath.Color.new(1, 0, 0, 1) end
|
||||||
|
local function fill() return omath.Color.new(0, 0, 0, 0.5) end
|
||||||
|
local function outline() return omath.Color.new(0, 0, 0, 1) end
|
||||||
|
local function bg() return omath.Color.new(0.2, 0.2, 0.2, 1) end
|
||||||
|
|
||||||
|
local function renderer_with(commands)
|
||||||
|
return omath.hud.Renderer.new({
|
||||||
|
add_line = function(a, b, c, thickness)
|
||||||
|
table.insert(commands, { kind = "line", a = a, b = b, color = c, thickness = thickness })
|
||||||
|
end,
|
||||||
|
add_polyline = function(points, c, thickness)
|
||||||
|
table.insert(commands, { kind = "polyline", points = points, color = c, thickness = thickness })
|
||||||
|
end,
|
||||||
|
add_filled_polyline = function(points, c)
|
||||||
|
table.insert(commands, { kind = "filled_polyline", points = points, color = c })
|
||||||
|
end,
|
||||||
|
add_rectangle = function(min, max, c)
|
||||||
|
table.insert(commands, { kind = "rectangle", min = min, max = max, color = c })
|
||||||
|
end,
|
||||||
|
add_filled_rectangle = function(min, max, c)
|
||||||
|
table.insert(commands, { kind = "filled_rectangle", min = min, max = max, color = c })
|
||||||
|
end,
|
||||||
|
add_circle = function(center, radius, c, thickness, segments)
|
||||||
|
table.insert(commands, {
|
||||||
|
kind = "circle",
|
||||||
|
center = center,
|
||||||
|
radius = radius,
|
||||||
|
color = c,
|
||||||
|
thickness = thickness,
|
||||||
|
segments = segments,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
add_filled_circle = function(center, radius, c, segments)
|
||||||
|
table.insert(commands, { kind = "filled_circle", center = center, radius = radius, color = c, segments = segments })
|
||||||
|
end,
|
||||||
|
add_arc = function(center, radius, a_min, a_max, c, thickness, segments)
|
||||||
|
table.insert(commands, {
|
||||||
|
kind = "arc",
|
||||||
|
center = center,
|
||||||
|
radius = radius,
|
||||||
|
a_min = a_min,
|
||||||
|
a_max = a_max,
|
||||||
|
color = c,
|
||||||
|
thickness = thickness,
|
||||||
|
segments = segments,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
add_image = function(texture, min, max, tint)
|
||||||
|
table.insert(commands, { kind = "image", texture = texture, min = min, max = max, tint = tint })
|
||||||
|
end,
|
||||||
|
add_text = function(pos, c, text)
|
||||||
|
table.insert(commands, { kind = "text", pos = pos, color = c, text = text })
|
||||||
|
end,
|
||||||
|
calc_text_size = function(text)
|
||||||
|
return omath.Vec2.new(#text * 6, 10)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function overlay(commands)
|
||||||
|
return omath.hud.EntityOverlay.new(omath.Vec2.new(100, 10), omath.Vec2.new(100, 50), renderer_with(commands))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_CanvasBox_default_ratio()
|
||||||
|
local box = omath.hud.CanvasBox.new(omath.Vec2.new(100, 10), omath.Vec2.new(100, 50))
|
||||||
|
assert(approx(box.top_left_corner.x, 90) and approx(box.top_left_corner.y, 10))
|
||||||
|
assert(approx(box.top_right_corner.x, 110) and approx(box.bottom_right_corner.y, 50))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_CanvasBox_custom_ratio()
|
||||||
|
local box = omath.hud.CanvasBox.new(omath.Vec2.new(100, 10), omath.Vec2.new(100, 50), 2)
|
||||||
|
assert(approx(box.top_left_corner.x, 80) and approx(box.bottom_right_corner.x, 120))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_CanvasBox_as_table()
|
||||||
|
local points = omath.hud.CanvasBox.new(omath.Vec2.new(100, 10), omath.Vec2.new(100, 50)):as_table()
|
||||||
|
assert(#points == 4)
|
||||||
|
assert(approx(points[1].x, 90) and approx(points[3].x, 110))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_Renderer_callbacks()
|
||||||
|
local commands = {}
|
||||||
|
local renderer = renderer_with(commands)
|
||||||
|
renderer:add_line(omath.Vec2.new(1, 2), omath.Vec2.new(3, 4), color(), 2)
|
||||||
|
renderer:add_text(omath.Vec2.new(5, 6), color(), "hp")
|
||||||
|
local size = renderer:calc_text_size("abcd")
|
||||||
|
assert(#commands == 2)
|
||||||
|
assert(commands[1].kind == "line" and commands[2].text == "hp")
|
||||||
|
assert(size.x == 24 and size.y == 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_Renderer_polyline_table()
|
||||||
|
local commands = {}
|
||||||
|
local renderer = renderer_with(commands)
|
||||||
|
renderer:add_polyline({ omath.Vec2.new(1, 1), omath.Vec2.new(2, 2) }, color(), 3)
|
||||||
|
assert(commands[1].kind == "polyline")
|
||||||
|
assert(#commands[1].points == 2 and commands[1].points[2].x == 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_Renderer_circle_defaults()
|
||||||
|
local commands = {}
|
||||||
|
local renderer = renderer_with(commands)
|
||||||
|
renderer:add_circle(omath.Vec2.new(1, 2), 5, color(), 2)
|
||||||
|
renderer:add_filled_circle(omath.Vec2.new(3, 4), 6, color())
|
||||||
|
assert(commands[1].kind == "circle" and commands[1].segments == 0)
|
||||||
|
assert(commands[2].kind == "filled_circle" and commands[2].segments == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_2d_box()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_2d_box(color(), fill(), outline(), 2)
|
||||||
|
assert(#commands == 3)
|
||||||
|
assert(commands[1].kind == "polyline" and approx(commands[1].thickness, 4))
|
||||||
|
assert(commands[2].kind == "polyline" and approx(commands[2].thickness, 2) and #commands[2].points == 4)
|
||||||
|
assert(commands[3].kind == "filled_polyline")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_cornered_2d_box()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_cornered_2d_box(color(), fill(), outline(), 0.25, 2)
|
||||||
|
assert(#commands == 18)
|
||||||
|
assert(commands[1].kind == "polyline" and commands[2].kind == "filled_polyline")
|
||||||
|
assert(commands[3].kind == "line" and approx(commands[3].thickness, 4))
|
||||||
|
assert(commands[4].kind == "line" and approx(commands[4].thickness, 2))
|
||||||
|
assert(commands[18].kind == "line")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_dashed_box()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_dashed_box(color(), 8, 5, 1)
|
||||||
|
assert(#commands > 4)
|
||||||
|
assert(commands[1].kind == "line")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_bar()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_right_bar(color(), outline(), bg(), 4, 0.5)
|
||||||
|
assert(#commands == 3)
|
||||||
|
assert(commands[1].kind == "filled_rectangle" and commands[2].kind == "filled_rectangle")
|
||||||
|
assert(commands[3].kind == "rectangle")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_dashed_bar()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_bottom_dashed_bar(color(), outline(), bg(), 4, 0.5, 8, 5)
|
||||||
|
assert(#commands >= 3)
|
||||||
|
assert(commands[1].kind == "filled_rectangle")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_label()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_right_label(color(), 5, false, "HP")
|
||||||
|
assert(#commands == 1)
|
||||||
|
assert(commands[1].kind == "text" and commands[1].text == "HP")
|
||||||
|
assert(approx(commands[1].pos.x, 115))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_outlined_label()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_left_label(color(), 5, true, "HP")
|
||||||
|
assert(#commands == 9)
|
||||||
|
assert(commands[9].kind == "text" and commands[9].text == "HP")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_centered_label()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_centered_top_label(color(), 5, false, "HP")
|
||||||
|
assert(#commands == 1)
|
||||||
|
assert(commands[1].kind == "text")
|
||||||
|
assert(approx(commands[1].pos.x, 94))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_spaces()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_right_space_vertical(10):add_right_label(color(), 5, false, "HP")
|
||||||
|
assert(#commands == 1)
|
||||||
|
assert(approx(commands[1].pos.y, 20))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_progress_ring()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_right_progress_ring(color(), bg(), 6, 0.25, 2, 5, 16)
|
||||||
|
assert(#commands == 2)
|
||||||
|
assert(commands[1].kind == "circle" and commands[2].kind == "arc")
|
||||||
|
assert(commands[2].segments == 16)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_progress_ring_defaults()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_right_progress_ring(color(), bg(), 6, 0.25)
|
||||||
|
assert(#commands == 2)
|
||||||
|
assert(commands[1].thickness == 2 and commands[1].segments == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_icon()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_right_icon("texture-id", 8, 6, color(), 5)
|
||||||
|
assert(#commands == 1)
|
||||||
|
assert(commands[1].kind == "image" and commands[1].texture == "texture-id")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_snap_line()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_snap_line(omath.Vec2.new(0, 0), color(), 2)
|
||||||
|
assert(#commands == 1)
|
||||||
|
assert(commands[1].kind == "line")
|
||||||
|
assert(approx(commands[1].b.x, 100) and approx(commands[1].b.y, 50))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_skeleton()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_skeleton(color())
|
||||||
|
assert(#commands == 15)
|
||||||
|
assert(commands[1].kind == "line")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_scan_marker()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_scan_marker(color(), outline(), 2)
|
||||||
|
assert(#commands == 2)
|
||||||
|
assert(commands[1].kind == "filled_polyline" and #commands[1].points == 3)
|
||||||
|
assert(commands[2].kind == "polyline" and commands[2].thickness == 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_aim_dot()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_aim_dot(omath.Vec2.new(5, 6), color())
|
||||||
|
assert(#commands == 1)
|
||||||
|
assert(commands[1].kind == "filled_circle" and commands[1].radius == 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_projectile_aim_circle()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_projectile_aim(omath.Vec2.new(5, 6), color(), 4, 2, omath.hud.ProjectileAimFigure.CIRCLE)
|
||||||
|
assert(#commands == 2)
|
||||||
|
assert(commands[1].kind == "line")
|
||||||
|
assert(commands[2].kind == "filled_circle" and commands[2].radius == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hud_EntityOverlay_add_projectile_aim_square_default()
|
||||||
|
local commands = {}
|
||||||
|
overlay(commands):add_projectile_aim(omath.Vec2.new(5, 6), color())
|
||||||
|
assert(#commands == 2)
|
||||||
|
assert(commands[1].kind == "line")
|
||||||
|
assert(commands[2].kind == "filled_rectangle")
|
||||||
|
end
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-5) end
|
||||||
|
|
||||||
|
function Mat4_Constructor_default()
|
||||||
|
local m = omath.Mat4.new()
|
||||||
|
assert(m:row_count() == 4 and m:columns_count() == 4)
|
||||||
|
assert(m:at(0, 0) == 0 and m:at(3, 3) == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_FromRows()
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{5, 6, 7, 8},
|
||||||
|
{9, 10, 11, 12},
|
||||||
|
{13, 14, 15, 16},
|
||||||
|
})
|
||||||
|
assert(m:at(0, 0) == 1 and m:at(3, 3) == 16)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_SetAt()
|
||||||
|
local m = omath.Mat4.new()
|
||||||
|
m:set_at(2, 3, 42)
|
||||||
|
assert(m:at(2, 3) == 42)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_SetAndClear()
|
||||||
|
local m = omath.Mat4.new()
|
||||||
|
m:set(2)
|
||||||
|
assert(m:sum() == 32)
|
||||||
|
m:clear()
|
||||||
|
assert(m:sum() == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Multiplication_matrix()
|
||||||
|
local identity = omath.Mat4.from_rows({
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1},
|
||||||
|
})
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{5, 6, 7, 8},
|
||||||
|
{9, 10, 11, 12},
|
||||||
|
{13, 14, 15, 16},
|
||||||
|
})
|
||||||
|
local result = m * identity
|
||||||
|
assert(result == m)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Multiplication_scalar()
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1},
|
||||||
|
}) * 2
|
||||||
|
assert(m:at(0, 0) == 2 and m:at(3, 3) == 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Multiplication_scalar_reversed()
|
||||||
|
local m = 2 * omath.Mat4.from_rows({
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1},
|
||||||
|
})
|
||||||
|
assert(m:at(0, 0) == 2 and m:at(3, 3) == 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Division_scalar()
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{2, 0, 0, 0},
|
||||||
|
{0, 2, 0, 0},
|
||||||
|
{0, 0, 2, 0},
|
||||||
|
{0, 0, 0, 2},
|
||||||
|
}) / 2
|
||||||
|
assert(m:at(0, 0) == 1 and m:at(3, 3) == 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Transposed()
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{1, 2, 0, 0},
|
||||||
|
{3, 4, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1},
|
||||||
|
}):transposed()
|
||||||
|
assert(m:at(0, 1) == 3 and m:at(1, 0) == 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Determinant()
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{0, 2, 0, 0},
|
||||||
|
{0, 0, 3, 0},
|
||||||
|
{0, 0, 0, 4},
|
||||||
|
})
|
||||||
|
assert(m:determinant() == 24)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Inverted_success()
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{2, 0, 0, 0},
|
||||||
|
{0, 2, 0, 0},
|
||||||
|
{0, 0, 2, 0},
|
||||||
|
{0, 0, 0, 2},
|
||||||
|
})
|
||||||
|
local inv = m:inverted()
|
||||||
|
assert(inv ~= nil)
|
||||||
|
assert(approx(inv:at(0, 0), 0.5) and approx(inv:at(3, 3), 0.5))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Inverted_singular()
|
||||||
|
local m = omath.Mat4.new()
|
||||||
|
assert(m:inverted() == nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_ToScreenMat()
|
||||||
|
local m = omath.Mat4.to_screen_mat(800, 600)
|
||||||
|
assert(m:at(0, 0) == 400 and m:at(1, 1) == -300 and m:at(3, 1) == 300)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Translation()
|
||||||
|
local m = omath.Mat4.translation(omath.Vec3.new(1, 2, 3))
|
||||||
|
assert(m:at(0, 3) == 1 and m:at(1, 3) == 2 and m:at(2, 3) == 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Scale()
|
||||||
|
local m = omath.Mat4.scale(omath.Vec3.new(2, 3, 4))
|
||||||
|
assert(m:at(0, 0) == 2 and m:at(1, 1) == 3 and m:at(2, 2) == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_Perspective()
|
||||||
|
local m = omath.Mat4.perspective_left_handed_vertical_fov(90, 16 / 9, 0.1, 1000)
|
||||||
|
assert(m:at(0, 0) > 0 and m:at(1, 1) > 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_AsTable()
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{5, 6, 7, 8},
|
||||||
|
{9, 10, 11, 12},
|
||||||
|
{13, 14, 15, 16},
|
||||||
|
})
|
||||||
|
local t = m:as_table()
|
||||||
|
assert(t[1][1] == 1 and t[4][4] == 16)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4_ToString()
|
||||||
|
local m = omath.Mat4.from_rows({
|
||||||
|
{1, 2, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1},
|
||||||
|
})
|
||||||
|
assert(string.find(tostring(m), "%[%[") ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat3_FromRows()
|
||||||
|
local m = omath.Mat3.from_rows({
|
||||||
|
{1, 2, 3},
|
||||||
|
{0, 1, 4},
|
||||||
|
{5, 6, 0},
|
||||||
|
})
|
||||||
|
assert(m:determinant() == 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4ColumnMajor_FromRows()
|
||||||
|
local m = omath.Mat4ColumnMajor.from_rows({
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{5, 6, 7, 8},
|
||||||
|
{9, 10, 11, 12},
|
||||||
|
{13, 14, 15, 16},
|
||||||
|
})
|
||||||
|
assert(m:at(0, 1) == 2 and m:at(3, 2) == 15)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4ColumnMajor_ToScreenMat()
|
||||||
|
local m = omath.Mat4ColumnMajor.to_screen_mat(800, 600)
|
||||||
|
assert(m:at(0, 0) == 400 and m:at(1, 1) == -300 and m:at(3, 1) == 300)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4ColumnMajor_Translation()
|
||||||
|
local m = omath.Mat4ColumnMajor.translation(omath.Vec3.new(1, 2, 3))
|
||||||
|
assert(m:at(0, 3) == 1 and m:at(1, 3) == 2 and m:at(2, 3) == 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Mat4d_FromRows()
|
||||||
|
local m = omath.Mat4d.from_rows({
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{0, 2, 0, 0},
|
||||||
|
{0, 0, 3, 0},
|
||||||
|
{0, 0, 0, 4},
|
||||||
|
})
|
||||||
|
assert(m:determinant() == 24)
|
||||||
|
end
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-5) end
|
||||||
|
|
||||||
|
function Quaternion_Constructor_default()
|
||||||
|
local q = omath.Quaternion.new()
|
||||||
|
assert(q.x == 0 and q.y == 0 and q.z == 0 and q.w == 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Constructor_xyzw()
|
||||||
|
local q = omath.Quaternion.new(1, 2, 3, 4)
|
||||||
|
assert(q.x == 1 and q.y == 2 and q.z == 3 and q.w == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Field_mutation()
|
||||||
|
local q = omath.Quaternion.new()
|
||||||
|
q.x = 1; q.y = 2; q.z = 3; q.w = 4
|
||||||
|
assert(q.x == 1 and q.y == 2 and q.z == 3 and q.w == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_FromAxisAngle_zero_angle()
|
||||||
|
local q = omath.Quaternion.from_axis_angle(omath.Vec3.new(0, 0, 1), 0)
|
||||||
|
assert(approx(q.x, 0) and approx(q.y, 0) and approx(q.z, 0) and approx(q.w, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Addition()
|
||||||
|
local q = omath.Quaternion.new(1, 2, 3, 4) + omath.Quaternion.new(4, 3, 2, 1)
|
||||||
|
assert(q.x == 5 and q.y == 5 and q.z == 5 and q.w == 5)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Multiplication_scalar()
|
||||||
|
local q = omath.Quaternion.new(1, 2, 3, 4) * 2
|
||||||
|
assert(q.x == 2 and q.y == 4 and q.z == 6 and q.w == 8)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Multiplication_scalar_reversed()
|
||||||
|
local q = 2 * omath.Quaternion.new(1, 2, 3, 4)
|
||||||
|
assert(q.x == 2 and q.y == 4 and q.z == 6 and q.w == 8)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Multiplication_quaternion()
|
||||||
|
local i = omath.Quaternion.new(1, 0, 0, 0)
|
||||||
|
local j = omath.Quaternion.new(0, 1, 0, 0)
|
||||||
|
local k = i * j
|
||||||
|
assert(k.x == 0 and k.y == 0 and k.z == 1 and k.w == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_UnaryMinus()
|
||||||
|
local q = -omath.Quaternion.new(1, -2, 3, -4)
|
||||||
|
assert(q.x == -1 and q.y == 2 and q.z == -3 and q.w == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_EqualTo_true()
|
||||||
|
assert(omath.Quaternion.new(1, 2, 3, 4) == omath.Quaternion.new(1, 2, 3, 4))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_EqualTo_false()
|
||||||
|
assert(not (omath.Quaternion.new(1, 2, 3, 4) == omath.Quaternion.new(1, 2, 3, 5)))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_ToString()
|
||||||
|
assert(tostring(omath.Quaternion.new(1, 2, 3, 4)) == "Quaternion(1, 2, 3, 4)")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Conjugate()
|
||||||
|
local q = omath.Quaternion.new(1, 2, 3, 4):conjugate()
|
||||||
|
assert(q.x == -1 and q.y == -2 and q.z == -3 and q.w == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Dot()
|
||||||
|
assert(omath.Quaternion.new(1, 0, 0, 0):dot(omath.Quaternion.new(0, 1, 0, 0)) == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Length()
|
||||||
|
assert(approx(omath.Quaternion.new(1, 1, 1, 1):length(), 2))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_LengthSqr()
|
||||||
|
assert(omath.Quaternion.new(1, 2, 3, 4):length_sqr() == 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Normalized()
|
||||||
|
local q = omath.Quaternion.new(1, 1, 1, 1):normalized()
|
||||||
|
assert(approx(q:length(), 1))
|
||||||
|
assert(approx(q.x, 0.5) and approx(q.y, 0.5) and approx(q.z, 0.5) and approx(q.w, 0.5))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Inverse()
|
||||||
|
local q = omath.Quaternion.from_axis_angle(omath.Vec3.new(0, 0, 1), math.pi / 3)
|
||||||
|
local identity = q * q:inverse()
|
||||||
|
assert(approx(identity.x, 0) and approx(identity.y, 0) and approx(identity.z, 0) and approx(identity.w, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_Rotate()
|
||||||
|
local q = omath.Quaternion.from_axis_angle(omath.Vec3.new(0, 0, 1), math.pi / 2)
|
||||||
|
local v = q:rotate(omath.Vec3.new(1, 0, 0))
|
||||||
|
assert(approx(v.x, 0, 1e-4) and approx(v.y, 1, 1e-4) and approx(v.z, 0, 1e-4))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_ToRotationMatrix3()
|
||||||
|
local m = omath.Quaternion.new():to_rotation_matrix3()
|
||||||
|
assert(m:at(0, 0) == 1 and m:at(1, 1) == 1 and m:at(2, 2) == 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_ToRotationMatrix4()
|
||||||
|
local m = omath.Quaternion.new():to_rotation_matrix4()
|
||||||
|
assert(m:at(0, 0) == 1 and m:at(1, 1) == 1 and m:at(2, 2) == 1 and m:at(3, 3) == 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Quaternion_AsTable()
|
||||||
|
local t = omath.Quaternion.new(1, 2, 3, 4):as_table()
|
||||||
|
assert(t.x == 1 and t.y == 2 and t.z == 3 and t.w == 4)
|
||||||
|
end
|
||||||
@@ -175,6 +175,46 @@ function Source_Camera_get_up()
|
|||||||
assert(approx(make_camera():get_up():length(), 1.0))
|
assert(approx(make_camera():get_up():length(), 1.0))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Source_Camera_get_view_matrix()
|
||||||
|
local view = make_camera():get_view_matrix()
|
||||||
|
assert(view ~= nil)
|
||||||
|
assert(view:row_count() == 4 and view:columns_count() == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Source_Camera_get_projection_matrix()
|
||||||
|
local projection = make_camera():get_projection_matrix()
|
||||||
|
assert(projection ~= nil)
|
||||||
|
assert(projection:at(0, 0) > 0 and projection:at(1, 1) > 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Source_Camera_get_view_projection_matrix()
|
||||||
|
local view_projection = make_camera():get_view_projection_matrix()
|
||||||
|
assert(view_projection ~= nil)
|
||||||
|
assert(view_projection:row_count() == 4 and view_projection:columns_count() == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Source_Camera_extract_projection_params()
|
||||||
|
local projection = make_camera():get_projection_matrix()
|
||||||
|
local fov, aspect_ratio = omath.source.Camera.extract_projection_params(projection)
|
||||||
|
assert(fov ~= nil)
|
||||||
|
assert(approx(aspect_ratio, 1920 / 1080))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Source_Camera_calc_view_angles_from_view_matrix()
|
||||||
|
local cam = make_camera()
|
||||||
|
local view = cam:get_view_matrix()
|
||||||
|
local angles = omath.source.Camera.calc_view_angles_from_view_matrix(view)
|
||||||
|
assert(approx(angles.pitch:as_degrees(), 0))
|
||||||
|
assert(approx(angles.yaw:as_degrees(), 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Source_Camera_calc_origin_from_view_matrix()
|
||||||
|
local cam = make_camera()
|
||||||
|
cam:set_origin(omath.Vec3.new(1, 2, 3))
|
||||||
|
local origin = omath.source.Camera.calc_origin_from_view_matrix(cam:get_view_matrix())
|
||||||
|
assert(approx(origin.x, 1) and approx(origin.y, 2) and approx(origin.z, 3))
|
||||||
|
end
|
||||||
|
|
||||||
function Source_Camera_world_to_screen_success()
|
function Source_Camera_world_to_screen_success()
|
||||||
local cam = make_camera()
|
local cam = make_camera()
|
||||||
cam:look_at(omath.Vec3.new(1, 0, 0))
|
cam:look_at(omath.Vec3.new(1, 0, 0))
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Created by orange on 07.03.2026.
|
||||||
|
//
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <lua.hpp>
|
||||||
|
#include <omath/lua/lua.hpp>
|
||||||
|
|
||||||
|
class LuaCollision : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
lua_State* L = nullptr;
|
||||||
|
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
L = luaL_newstate();
|
||||||
|
luaL_openlibs(L);
|
||||||
|
omath::lua::LuaInterpreter::register_lib(L);
|
||||||
|
if (luaL_dofile(L, LUA_SCRIPTS_DIR "/collision_tests.lua") != LUA_OK)
|
||||||
|
FAIL() << lua_tostring(L, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override { lua_close(L); }
|
||||||
|
|
||||||
|
void check(const char* func_name)
|
||||||
|
{
|
||||||
|
lua_getglobal(L, func_name);
|
||||||
|
if (lua_pcall(L, 0, 0, 0) != LUA_OK)
|
||||||
|
{
|
||||||
|
FAIL() << lua_tostring(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LuaCollision, Aabb_constructor_and_fields) { check("Collision_Aabb_constructor_and_fields"); }
|
||||||
|
TEST_F(LuaCollision, Aabb_center_extents) { check("Collision_Aabb_center_extents"); }
|
||||||
|
TEST_F(LuaCollision, Aabb_top_bottom_default_axis) { check("Collision_Aabb_top_bottom_default_axis"); }
|
||||||
|
TEST_F(LuaCollision, Aabb_top_bottom_explicit_axis) { check("Collision_Aabb_top_bottom_explicit_axis"); }
|
||||||
|
TEST_F(LuaCollision, Aabb_is_collide) { check("Collision_Aabb_is_collide"); }
|
||||||
|
TEST_F(LuaCollision, Aabb_as_table) { check("Collision_Aabb_as_table"); }
|
||||||
|
TEST_F(LuaCollision, Obb_constructor_and_vertices) { check("Collision_Obb_constructor_and_vertices"); }
|
||||||
|
TEST_F(LuaCollision, Ray_constructor_and_direction) { check("Collision_Ray_constructor_and_direction"); }
|
||||||
|
TEST_F(LuaCollision, LineTracer_triangle_hit) { check("Collision_LineTracer_triangle_hit"); }
|
||||||
|
TEST_F(LuaCollision, LineTracer_triangle_miss) { check("Collision_LineTracer_triangle_miss"); }
|
||||||
|
TEST_F(LuaCollision, LineTracer_aabb_hit) { check("Collision_LineTracer_aabb_hit"); }
|
||||||
|
TEST_F(LuaCollision, LineTracer_aabb_miss) { check("Collision_LineTracer_aabb_miss"); }
|
||||||
|
TEST_F(LuaCollision, LineTracer_obb_hit) { check("Collision_LineTracer_obb_hit"); }
|
||||||
|
TEST_F(LuaCollision, ConvexCollider_vertices_support) { check("Collision_ConvexCollider_vertices_and_support"); }
|
||||||
|
TEST_F(LuaCollision, Gjk_support_vertex) { check("Collision_Gjk_support_vertex"); }
|
||||||
|
TEST_F(LuaCollision, Gjk_collide_true) { check("Collision_Gjk_collide_true"); }
|
||||||
|
TEST_F(LuaCollision, Gjk_collide_false) { check("Collision_Gjk_collide_false"); }
|
||||||
|
TEST_F(LuaCollision, Epa_solve_hit) { check("Collision_Epa_solve_hit"); }
|
||||||
|
TEST_F(LuaCollision, Epa_solve_miss) { check("Collision_Epa_solve_miss"); }
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// Created by orange on 07.03.2026.
|
||||||
|
//
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <lua.hpp>
|
||||||
|
#include <omath/lua/lua.hpp>
|
||||||
|
|
||||||
|
class LuaHud : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
lua_State* L = nullptr;
|
||||||
|
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
L = luaL_newstate();
|
||||||
|
luaL_openlibs(L);
|
||||||
|
omath::lua::LuaInterpreter::register_lib(L);
|
||||||
|
if (luaL_dofile(L, LUA_SCRIPTS_DIR "/hud_tests.lua") != LUA_OK)
|
||||||
|
FAIL() << lua_tostring(L, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override { lua_close(L); }
|
||||||
|
|
||||||
|
void check(const char* func_name)
|
||||||
|
{
|
||||||
|
lua_getglobal(L, func_name);
|
||||||
|
if (lua_pcall(L, 0, 0, 0) != LUA_OK)
|
||||||
|
{
|
||||||
|
FAIL() << lua_tostring(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LuaHud, CanvasBox_default_ratio) { check("Hud_CanvasBox_default_ratio"); }
|
||||||
|
TEST_F(LuaHud, CanvasBox_custom_ratio) { check("Hud_CanvasBox_custom_ratio"); }
|
||||||
|
TEST_F(LuaHud, CanvasBox_as_table) { check("Hud_CanvasBox_as_table"); }
|
||||||
|
TEST_F(LuaHud, Renderer_callbacks) { check("Hud_Renderer_callbacks"); }
|
||||||
|
TEST_F(LuaHud, Renderer_polyline_table) { check("Hud_Renderer_polyline_table"); }
|
||||||
|
TEST_F(LuaHud, Renderer_circle_defaults) { check("Hud_Renderer_circle_defaults"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_2d_box) { check("Hud_EntityOverlay_add_2d_box"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_cornered_2d_box) { check("Hud_EntityOverlay_add_cornered_2d_box"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_dashed_box) { check("Hud_EntityOverlay_add_dashed_box"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_bar) { check("Hud_EntityOverlay_add_bar"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_dashed_bar) { check("Hud_EntityOverlay_add_dashed_bar"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_label) { check("Hud_EntityOverlay_add_label"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_outlined_label) { check("Hud_EntityOverlay_add_outlined_label"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_centered_label) { check("Hud_EntityOverlay_add_centered_label"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_spaces) { check("Hud_EntityOverlay_add_spaces"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_progress_ring) { check("Hud_EntityOverlay_add_progress_ring"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_progress_ring_defaults) { check("Hud_EntityOverlay_add_progress_ring_defaults"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_icon) { check("Hud_EntityOverlay_add_icon"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_snap_line) { check("Hud_EntityOverlay_add_snap_line"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_skeleton) { check("Hud_EntityOverlay_add_skeleton"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_scan_marker) { check("Hud_EntityOverlay_add_scan_marker"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_aim_dot) { check("Hud_EntityOverlay_add_aim_dot"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_projectile_aim_circle) { check("Hud_EntityOverlay_add_projectile_aim_circle"); }
|
||||||
|
TEST_F(LuaHud, EntityOverlay_add_projectile_aim_square_default) { check("Hud_EntityOverlay_add_projectile_aim_square_default"); }
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
//
|
||||||
|
// Created by orange on 07.03.2026.
|
||||||
|
//
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <lua.hpp>
|
||||||
|
#include <omath/lua/lua.hpp>
|
||||||
|
|
||||||
|
class LuaMat : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
lua_State* L = nullptr;
|
||||||
|
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
L = luaL_newstate();
|
||||||
|
luaL_openlibs(L);
|
||||||
|
omath::lua::LuaInterpreter::register_lib(L);
|
||||||
|
if (luaL_dofile(L, LUA_SCRIPTS_DIR "/mat_tests.lua") != LUA_OK)
|
||||||
|
FAIL() << lua_tostring(L, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override
|
||||||
|
{
|
||||||
|
lua_close(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
void check(const char* func_name)
|
||||||
|
{
|
||||||
|
lua_getglobal(L, func_name);
|
||||||
|
if (lua_pcall(L, 0, 0, 0) != LUA_OK)
|
||||||
|
{
|
||||||
|
FAIL() << lua_tostring(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LuaMat, Constructor_default)
|
||||||
|
{
|
||||||
|
check("Mat4_Constructor_default");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, FromRows)
|
||||||
|
{
|
||||||
|
check("Mat4_FromRows");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, SetAt)
|
||||||
|
{
|
||||||
|
check("Mat4_SetAt");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, SetAndClear)
|
||||||
|
{
|
||||||
|
check("Mat4_SetAndClear");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Multiplication_matrix)
|
||||||
|
{
|
||||||
|
check("Mat4_Multiplication_matrix");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Multiplication_scalar)
|
||||||
|
{
|
||||||
|
check("Mat4_Multiplication_scalar");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Multiplication_scalar_reversed)
|
||||||
|
{
|
||||||
|
check("Mat4_Multiplication_scalar_reversed");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Division_scalar)
|
||||||
|
{
|
||||||
|
check("Mat4_Division_scalar");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Transposed)
|
||||||
|
{
|
||||||
|
check("Mat4_Transposed");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Determinant)
|
||||||
|
{
|
||||||
|
check("Mat4_Determinant");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Inverted_success)
|
||||||
|
{
|
||||||
|
check("Mat4_Inverted_success");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Inverted_singular)
|
||||||
|
{
|
||||||
|
check("Mat4_Inverted_singular");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, ToScreenMat)
|
||||||
|
{
|
||||||
|
check("Mat4_ToScreenMat");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Translation)
|
||||||
|
{
|
||||||
|
check("Mat4_Translation");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Scale)
|
||||||
|
{
|
||||||
|
check("Mat4_Scale");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Perspective)
|
||||||
|
{
|
||||||
|
check("Mat4_Perspective");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, AsTable)
|
||||||
|
{
|
||||||
|
check("Mat4_AsTable");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, ToString)
|
||||||
|
{
|
||||||
|
check("Mat4_ToString");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Mat3_FromRows)
|
||||||
|
{
|
||||||
|
check("Mat3_FromRows");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Mat4ColumnMajor_FromRows)
|
||||||
|
{
|
||||||
|
check("Mat4ColumnMajor_FromRows");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Mat4ColumnMajor_ToScreenMat)
|
||||||
|
{
|
||||||
|
check("Mat4ColumnMajor_ToScreenMat");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Mat4ColumnMajor_Translation)
|
||||||
|
{
|
||||||
|
check("Mat4ColumnMajor_Translation");
|
||||||
|
}
|
||||||
|
TEST_F(LuaMat, Mat4d_FromRows)
|
||||||
|
{
|
||||||
|
check("Mat4d_FromRows");
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
//
|
||||||
|
// Created by orange on 07.03.2026.
|
||||||
|
//
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <lua.hpp>
|
||||||
|
#include <omath/lua/lua.hpp>
|
||||||
|
|
||||||
|
class LuaQuaternion : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
lua_State* L = nullptr;
|
||||||
|
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
L = luaL_newstate();
|
||||||
|
luaL_openlibs(L);
|
||||||
|
omath::lua::LuaInterpreter::register_lib(L);
|
||||||
|
if (luaL_dofile(L, LUA_SCRIPTS_DIR "/quaternion_tests.lua") != LUA_OK)
|
||||||
|
FAIL() << lua_tostring(L, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override
|
||||||
|
{
|
||||||
|
lua_close(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
void check(const char* func_name)
|
||||||
|
{
|
||||||
|
lua_getglobal(L, func_name);
|
||||||
|
if (lua_pcall(L, 0, 0, 0) != LUA_OK)
|
||||||
|
{
|
||||||
|
FAIL() << lua_tostring(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LuaQuaternion, Constructor_default)
|
||||||
|
{
|
||||||
|
check("Quaternion_Constructor_default");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Constructor_xyzw)
|
||||||
|
{
|
||||||
|
check("Quaternion_Constructor_xyzw");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Field_mutation)
|
||||||
|
{
|
||||||
|
check("Quaternion_Field_mutation");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, FromAxisAngle_zero_angle)
|
||||||
|
{
|
||||||
|
check("Quaternion_FromAxisAngle_zero_angle");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Addition)
|
||||||
|
{
|
||||||
|
check("Quaternion_Addition");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Multiplication_scalar)
|
||||||
|
{
|
||||||
|
check("Quaternion_Multiplication_scalar");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Multiplication_scalar_reversed)
|
||||||
|
{
|
||||||
|
check("Quaternion_Multiplication_scalar_reversed");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Multiplication_quaternion)
|
||||||
|
{
|
||||||
|
check("Quaternion_Multiplication_quaternion");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, UnaryMinus)
|
||||||
|
{
|
||||||
|
check("Quaternion_UnaryMinus");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, EqualTo_true)
|
||||||
|
{
|
||||||
|
check("Quaternion_EqualTo_true");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, EqualTo_false)
|
||||||
|
{
|
||||||
|
check("Quaternion_EqualTo_false");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, ToString)
|
||||||
|
{
|
||||||
|
check("Quaternion_ToString");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Conjugate)
|
||||||
|
{
|
||||||
|
check("Quaternion_Conjugate");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Dot)
|
||||||
|
{
|
||||||
|
check("Quaternion_Dot");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Length)
|
||||||
|
{
|
||||||
|
check("Quaternion_Length");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, LengthSqr)
|
||||||
|
{
|
||||||
|
check("Quaternion_LengthSqr");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Normalized)
|
||||||
|
{
|
||||||
|
check("Quaternion_Normalized");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Inverse)
|
||||||
|
{
|
||||||
|
check("Quaternion_Inverse");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, Rotate)
|
||||||
|
{
|
||||||
|
check("Quaternion_Rotate");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, ToRotationMatrix3)
|
||||||
|
{
|
||||||
|
check("Quaternion_ToRotationMatrix3");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, ToRotationMatrix4)
|
||||||
|
{
|
||||||
|
check("Quaternion_ToRotationMatrix4");
|
||||||
|
}
|
||||||
|
TEST_F(LuaQuaternion, AsTable)
|
||||||
|
{
|
||||||
|
check("Quaternion_AsTable");
|
||||||
|
}
|
||||||
@@ -74,6 +74,12 @@ TEST_F(LuaSourceEngine, Camera_look_at) { check("Source_Camera_l
|
|||||||
TEST_F(LuaSourceEngine, Camera_get_forward) { check("Source_Camera_get_forward"); }
|
TEST_F(LuaSourceEngine, Camera_get_forward) { check("Source_Camera_get_forward"); }
|
||||||
TEST_F(LuaSourceEngine, Camera_get_right) { check("Source_Camera_get_right"); }
|
TEST_F(LuaSourceEngine, Camera_get_right) { check("Source_Camera_get_right"); }
|
||||||
TEST_F(LuaSourceEngine, Camera_get_up) { check("Source_Camera_get_up"); }
|
TEST_F(LuaSourceEngine, Camera_get_up) { check("Source_Camera_get_up"); }
|
||||||
|
TEST_F(LuaSourceEngine, Camera_get_view_matrix) { check("Source_Camera_get_view_matrix"); }
|
||||||
|
TEST_F(LuaSourceEngine, Camera_get_projection_matrix) { check("Source_Camera_get_projection_matrix"); }
|
||||||
|
TEST_F(LuaSourceEngine, Camera_get_view_projection_matrix) { check("Source_Camera_get_view_projection_matrix"); }
|
||||||
|
TEST_F(LuaSourceEngine, Camera_extract_projection_params) { check("Source_Camera_extract_projection_params"); }
|
||||||
|
TEST_F(LuaSourceEngine, Camera_calc_view_angles_from_view_matrix) { check("Source_Camera_calc_view_angles_from_view_matrix"); }
|
||||||
|
TEST_F(LuaSourceEngine, Camera_calc_origin_from_view_matrix) { check("Source_Camera_calc_origin_from_view_matrix"); }
|
||||||
TEST_F(LuaSourceEngine, Camera_world_to_screen_success) { check("Source_Camera_world_to_screen_success"); }
|
TEST_F(LuaSourceEngine, Camera_world_to_screen_success) { check("Source_Camera_world_to_screen_success"); }
|
||||||
TEST_F(LuaSourceEngine, Camera_world_to_screen_error) { check("Source_Camera_world_to_screen_error"); }
|
TEST_F(LuaSourceEngine, Camera_world_to_screen_error) { check("Source_Camera_world_to_screen_error"); }
|
||||||
TEST_F(LuaSourceEngine, Camera_screen_to_world) { check("Source_Camera_screen_to_world"); }
|
TEST_F(LuaSourceEngine, Camera_screen_to_world) { check("Source_Camera_screen_to_world"); }
|
||||||
|
|||||||
Reference in New Issue
Block a user