Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db9200d7b5 | |||
| d894b62773 | |||
| ade281cdd2 | |||
| 9bb94ee33b | |||
| 754b370d8b | |||
| d3e379e71b | |||
| 6ed40a7bc1 | |||
|
|
f3d5f84d2c | ||
|
|
0b89c1d36d | ||
|
|
b8ed0bd5a5 | ||
| b9ae356e57 | |||
|
|
e351b64355 | ||
|
|
f4df88bb7a | ||
| 8142d800c7 | |||
| 95c0873b8c | |||
| d12a2611b8 | |||
| 9212b1f51f | |||
| 56505cf3e1 | |||
| 0510dd8328 | |||
| ce9758c86b |
@@ -1,6 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.26)
|
cmake_minimum_required(VERSION 3.26)
|
||||||
|
|
||||||
project(omath VERSION 4.0.0 LANGUAGES CXX)
|
file(READ VERSION OMATH_VERSION)
|
||||||
|
project(omath VERSION ${OMATH_VERSION} LANGUAGES CXX)
|
||||||
|
|
||||||
include(CMakePackageConfigHelpers)
|
include(CMakePackageConfigHelpers)
|
||||||
include(CheckCXXCompilerFlag)
|
include(CheckCXXCompilerFlag)
|
||||||
|
|||||||
81
README.md
@@ -1,6 +1,6 @@
|
|||||||
<div align = center>
|
<div align = center>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -43,25 +43,52 @@ It provides the latest features, is highly customizable, has all for cheat devel
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# Features
|
## 🚀 Quick Example
|
||||||
- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
|
|
||||||
- **Versatility**: Includes a wide array of mathematical functions and algorithms.
|
```cpp
|
||||||
- **Ease of Use**: Simplified interface for convenient integration into various projects.
|
#include <omath/omath.hpp>
|
||||||
- **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
|
|
||||||
- **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
|
using namespace omath;
|
||||||
- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
|
|
||||||
- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
|
// 3D vector operations
|
||||||
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
|
Vector3<float> a{1, 2, 3};
|
||||||
- **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL**.
|
Vector3<float> b{4, 5, 6};
|
||||||
- **Cross platform**: Supports Windows, MacOS and Linux.
|
|
||||||
- **Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps.
|
auto dot = a.dot(b); // 32.0
|
||||||
|
auto cross = a.cross(b); // (-3, 6, -3)
|
||||||
|
auto distance = a.distance_to(b); // ~5.196
|
||||||
|
auto normalized = a.normalized(); // Unit vector
|
||||||
|
|
||||||
|
// World-to-screen projection (Source Engine example)
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
Camera camera(position, angles, viewport, fov, near_plane, far_plane);
|
||||||
|
|
||||||
|
if (auto screen = camera.world_to_screen(world_position)) {
|
||||||
|
// Draw at screen->x, screen->y
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**[➡️ See more examples and tutorials][TUTORIALS]**
|
||||||
|
|
||||||
|
# ✨ Features
|
||||||
|
- **🚀 Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
|
||||||
|
- **🎯 Versatility**: Includes a wide array of mathematical functions and algorithms.
|
||||||
|
- **✅ Ease of Use**: Simplified interface for convenient integration into various projects.
|
||||||
|
- **🎮 Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
|
||||||
|
- **📐 3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
|
||||||
|
- **💥 Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
|
||||||
|
- **📦 No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
|
||||||
|
- **🔧 Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
|
||||||
|
- **🎯 Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL**.
|
||||||
|
- **🌍 Cross platform**: Supports Windows, MacOS and Linux.
|
||||||
|
- **🔍 Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps.
|
||||||
<div align = center>
|
<div align = center>
|
||||||
|
|
||||||
# Gallery
|
# Gallery
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
[](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
|
[](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@@ -84,17 +111,35 @@ It provides the latest features, is highly customizable, has all for cheat devel
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
- **[Getting Started Guide](https://libomath.org/getting_started/)** - Installation and first steps
|
||||||
|
- **[API Overview](https://libomath.org/api_overview/)** - Complete API reference
|
||||||
|
- **[Tutorials](https://libomath.org/tutorials/)** - Step-by-step guides
|
||||||
|
- **[FAQ](https://libomath.org/faq/)** - Common questions and answers
|
||||||
|
- **[Troubleshooting](https://libomath.org/troubleshooting/)** - Solutions to common issues
|
||||||
|
- **[Best Practices](https://libomath.org/best_practices/)** - Guidelines for effective usage
|
||||||
|
|
||||||
|
## 🤝 Community & Support
|
||||||
|
|
||||||
|
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
|
||||||
|
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
|
||||||
|
- **Issues**: [Report bugs or request features](https://github.com/orange-cpp/omath/issues)
|
||||||
|
- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines
|
||||||
|
|
||||||
# 💘 Acknowledgments
|
# 💘 Acknowledgments
|
||||||
- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors)
|
- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors)
|
||||||
|
|
||||||
<!----------------------------------{ Images }--------------------------------->
|
<!----------------------------------{ Images }--------------------------------->
|
||||||
[APEX Preview]: .github/images/showcase/apex.png
|
[APEX Preview]: docs/images/showcase/apex.png
|
||||||
[BO2 Preview]: .github/images/showcase/cod_bo2.png
|
[BO2 Preview]: docs/images/showcase/cod_bo2.png
|
||||||
[CS2 Preview]: .github/images/showcase/cs2.jpeg
|
[CS2 Preview]: docs/images/showcase/cs2.jpeg
|
||||||
[TF2 Preview]: .github/images/showcase/tf2.jpg
|
[TF2 Preview]: docs/images/showcase/tf2.jpg
|
||||||
<!----------------------------------{ Buttons }--------------------------------->
|
<!----------------------------------{ Buttons }--------------------------------->
|
||||||
|
[QUICKSTART]: docs/getting_started.md
|
||||||
[INSTALL]: INSTALL.md
|
[INSTALL]: INSTALL.md
|
||||||
[DOCUMENTATION]: http://libomath.org
|
[DOCUMENTATION]: http://libomath.org
|
||||||
|
[TUTORIALS]: docs/tutorials.md
|
||||||
[CONTRIBUTING]: CONTRIBUTING.md
|
[CONTRIBUTING]: CONTRIBUTING.md
|
||||||
[EXAMPLES]: examples
|
[EXAMPLES]: examples
|
||||||
[SPONSOR]: https://boosty.to/orangecpp/purchase/3568644?ssource=DIRECT&share=subscription_link
|
[SPONSOR]: https://boosty.to/orangecpp/purchase/3568644?ssource=DIRECT&share=subscription_link
|
||||||
|
|||||||
118
docs/3d_primitives/box.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# `omath::primitives::create_box` — Build an oriented box as 12 triangles
|
||||||
|
|
||||||
|
> Header: your project’s `primitives/box.hpp` (declares `create_box`)
|
||||||
|
> Namespace: `omath::primitives`
|
||||||
|
> Depends on: `omath::Triangle<omath::Vector3<float>>`, `omath::Vector3<float>`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
[[nodiscard]]
|
||||||
|
std::array<Triangle<Vector3<float>>, 12>
|
||||||
|
create_box(const Vector3<float>& top,
|
||||||
|
const Vector3<float>& bottom,
|
||||||
|
const Vector3<float>& dir_forward,
|
||||||
|
const Vector3<float>& dir_right,
|
||||||
|
float ratio = 4.f) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
Constructs a **rectangular cuboid (“box”)** oriented in 3D space and returns its surface as **12 triangles** (2 per face × 6 faces). The box’s central axis runs from `bottom` → `top`. The **up** direction is inferred from that segment; the **forward** and **right** directions define the box’s orientation around that axis.
|
||||||
|
|
||||||
|
The lateral half-extents are derived from the axis length and `ratio`:
|
||||||
|
|
||||||
|
> Let `H = |top - bottom|`. Lateral half-size ≈ `H / ratio` along both `dir_forward` and `dir_right`
|
||||||
|
> (i.e., the cross-section is a square of side `2H/ratio`).
|
||||||
|
|
||||||
|
> **Note:** This describes the intended behavior from the interface. If you rely on a different sizing rule, document it next to your implementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
* `top`
|
||||||
|
Center of the **top face**.
|
||||||
|
|
||||||
|
* `bottom`
|
||||||
|
Center of the **bottom face**.
|
||||||
|
|
||||||
|
* `dir_forward`
|
||||||
|
A direction that orients the box around its up axis. Should be **non-zero** and **not collinear** with `top - bottom`.
|
||||||
|
|
||||||
|
* `dir_right`
|
||||||
|
A direction roughly orthogonal to both `dir_forward` and `top - bottom`. Used to fully fix orientation.
|
||||||
|
|
||||||
|
* `ratio` (default `4.0f`)
|
||||||
|
Controls thickness relative to height. Larger values → thinner box.
|
||||||
|
With the default rule above, half-extent = `|top-bottom|/ratio`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Return value
|
||||||
|
|
||||||
|
`std::array<Triangle<Vector3<float>>, 12>` — the six faces of the box, triangulated.
|
||||||
|
Winding is intended to be **outward-facing** (right-handed coordinates). Do not rely on a specific **face ordering**; treat the array as opaque unless your implementation guarantees an order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected math & robustness
|
||||||
|
|
||||||
|
* Define `u = normalize(top - bottom)`.
|
||||||
|
* Re-orthonormalize the basis to avoid skew:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
f = normalize(dir_forward - u * u.dot(dir_forward)); // drop any up component
|
||||||
|
r = normalize(u.cross(f)); // right-handed basis
|
||||||
|
// (Optionally recompute f = r.cross(u) for orthogonality)
|
||||||
|
```
|
||||||
|
* Half-extents: `h = length(top - bottom) / ratio; hf = h * f; hr = h * r`.
|
||||||
|
* Corners (top): `t±r±f = top ± hr ± hf`; (bottom): `b±r±f = bottom ± hr ± hf`.
|
||||||
|
* Triangulate each face with consistent CCW winding when viewed from outside.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using omath::Vector3;
|
||||||
|
using omath::Triangle;
|
||||||
|
using omath::primitives::create_box;
|
||||||
|
|
||||||
|
// Axis from bottom to top (height 2)
|
||||||
|
Vector3<float> bottom{0, 0, 0};
|
||||||
|
Vector3<float> top {0, 2, 0};
|
||||||
|
|
||||||
|
// Orientation around the axis
|
||||||
|
Vector3<float> forward{0, 0, 1};
|
||||||
|
Vector3<float> right {1, 0, 0};
|
||||||
|
|
||||||
|
// Ratio 4 → lateral half-size = height/4 = 0.5
|
||||||
|
auto tris = create_box(top, bottom, forward, right, 4.0f);
|
||||||
|
|
||||||
|
// Use the triangles (normals, rendering, collision, etc.)
|
||||||
|
for (const auto& tri : tris) {
|
||||||
|
auto n = tri.calculate_normal();
|
||||||
|
(void)n;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage notes & pitfalls
|
||||||
|
|
||||||
|
* **Degenerate axis**: If `top == bottom`, the box is undefined (zero height). Guard against this.
|
||||||
|
* **Directions**: Provide **non-zero**, **reasonably orthogonal** `dir_forward`/`dir_right`. A robust implementation should project/normalize internally, but callers should still pass sensible inputs.
|
||||||
|
* **Winding**: If your renderer or collision expects a specific winding, verify with a unit test and flip vertex order per face if necessary.
|
||||||
|
* **Thickness policy**: This doc assumes both lateral half-extents equal `|top-bottom|/ratio`. If your implementation diverges (e.g., separate forward/right ratios), document it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath::Triangle` (vertex utilities: normals, centroid, etc.)
|
||||||
|
* `omath::Vector3` (geometry operations used by the construction)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 31 Oct 2025*
|
||||||
98
docs/3d_primitives/plane.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# `omath::primitives::create_plane` — Build an oriented quad (2 triangles)
|
||||||
|
|
||||||
|
> Header: your project’s `primitives/plane.hpp`
|
||||||
|
> Namespace: `omath::primitives`
|
||||||
|
> Depends on: `omath::Triangle<omath::Vector3<float>>`, `omath::Vector3<float>`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
[[nodiscard]]
|
||||||
|
std::array<Triangle<Vector3<float>>, 2>
|
||||||
|
create_plane(const Vector3<float>& vertex_a,
|
||||||
|
const Vector3<float>& vertex_b,
|
||||||
|
const Vector3<float>& direction,
|
||||||
|
float size) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
Creates a **rectangle (quad)** in 3D oriented by the edge **A→B** and a second in-plane **direction**. The quad is returned as **two triangles** suitable for rendering or collision.
|
||||||
|
|
||||||
|
* Edge axis: `e = vertex_b - vertex_a`
|
||||||
|
* Width axis: “direction”, **projected to be perpendicular to `e`** so the quad is planar and well-formed.
|
||||||
|
* Normal (by right-hand rule): `n ∝ e × width`.
|
||||||
|
|
||||||
|
> **Sizing convention**
|
||||||
|
> Typical construction uses **half-width = `size`** along the (normalized, orthogonalized) *direction*, i.e. the total width is `2*size`.
|
||||||
|
> If your implementation interprets `size` as full width, adjust your expectations accordingly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
* `vertex_a`, `vertex_b` — two adjacent quad vertices defining the **long edge** of the plane.
|
||||||
|
* `direction` — a vector indicating the **cross-edge direction** within the plane (does not need to be orthogonal or normalized).
|
||||||
|
* `size` — **half-width** of the quad along the (processed) `direction`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Return
|
||||||
|
|
||||||
|
`std::array<Triangle<Vector3<float>>, 2>` — the quad triangulated (consistent CCW winding, outward normal per `e × width`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Robust construction (expected math)
|
||||||
|
|
||||||
|
1. `e = vertex_b - vertex_a`
|
||||||
|
2. Make `d` perpendicular to `e`:
|
||||||
|
|
||||||
|
```
|
||||||
|
d = direction - e * (e.dot(direction) / e.length_sqr());
|
||||||
|
if (d.length_sqr() == 0) pick an arbitrary perpendicular to e
|
||||||
|
d = d.normalized();
|
||||||
|
```
|
||||||
|
3. Offsets: `w = d * size`
|
||||||
|
4. Four corners:
|
||||||
|
|
||||||
|
```
|
||||||
|
A0 = vertex_a - w; A1 = vertex_a + w;
|
||||||
|
B0 = vertex_b - w; B1 = vertex_b + w;
|
||||||
|
```
|
||||||
|
5. Triangles (CCW when viewed from +normal):
|
||||||
|
|
||||||
|
```
|
||||||
|
T0 = Triangle{ A0, A1, B1 }
|
||||||
|
T1 = Triangle{ A0, B1, B0 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using omath::Vector3;
|
||||||
|
using omath::Triangle;
|
||||||
|
using omath::primitives::create_plane;
|
||||||
|
|
||||||
|
Vector3<float> a{ -1, 0, -1 }; // edge start
|
||||||
|
Vector3<float> b{ 1, 0, -1 }; // edge end
|
||||||
|
Vector3<float> dir{ 0, 0, 1 }; // cross-edge direction within the plane (roughly +Z)
|
||||||
|
float half_width = 2.0f;
|
||||||
|
|
||||||
|
auto quad = create_plane(a, b, dir, half_width);
|
||||||
|
|
||||||
|
// e.g., compute normals
|
||||||
|
for (const auto& tri : quad) {
|
||||||
|
auto n = tri.calculate_normal(); (void)n;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & edge cases
|
||||||
|
|
||||||
|
* **Degenerate edge**: if `vertex_a == vertex_b`, the plane is undefined.
|
||||||
|
* **Collinearity**: if `direction` is parallel to `vertex_b - vertex_a`, the function must choose an alternate perpendicular; expect a fallback.
|
||||||
|
* **Winding**: If your renderer expects a specific face order, verify and swap the two vertices in each triangle as needed.
|
||||||
527
docs/api_overview.md
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
# API Overview
|
||||||
|
|
||||||
|
This document provides a high-level overview of OMath's API, organized by functionality area.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module Organization
|
||||||
|
|
||||||
|
OMath is organized into several logical modules:
|
||||||
|
|
||||||
|
### Core Mathematics
|
||||||
|
- **Linear Algebra** - Vectors, matrices, triangles
|
||||||
|
- **Trigonometry** - Angles, view angles, trigonometric functions
|
||||||
|
- **3D Primitives** - Boxes, planes, geometric shapes
|
||||||
|
|
||||||
|
### Game Development
|
||||||
|
- **Collision Detection** - Ray tracing, intersection tests
|
||||||
|
- **Projectile Prediction** - Ballistics and aim-assist calculations
|
||||||
|
- **Projection** - Camera systems and world-to-screen transformations
|
||||||
|
- **Pathfinding** - A* algorithm, navigation meshes
|
||||||
|
|
||||||
|
### Engine Support
|
||||||
|
- **Source Engine** - Valve's Source Engine (CS:GO, TF2, etc.)
|
||||||
|
- **Unity Engine** - Unity game engine
|
||||||
|
- **Unreal Engine** - Epic's Unreal Engine
|
||||||
|
- **Frostbite Engine** - EA's Frostbite Engine
|
||||||
|
- **IW Engine** - Infinity Ward's engine (Call of Duty)
|
||||||
|
- **OpenGL Engine** - Canonical OpenGL coordinate system
|
||||||
|
|
||||||
|
### Utilities
|
||||||
|
- **Color** - RGBA color representation
|
||||||
|
- **Pattern Scanning** - Memory pattern search (wildcards, PE files)
|
||||||
|
- **Reverse Engineering** - Internal/external memory manipulation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Types
|
||||||
|
|
||||||
|
### Vectors
|
||||||
|
|
||||||
|
All vector types are template-based and support arithmetic types.
|
||||||
|
|
||||||
|
| Type | Description | Key Methods |
|
||||||
|
|------|-------------|-------------|
|
||||||
|
| `Vector2<T>` | 2D vector | `length()`, `normalized()`, `dot()`, `distance_to()` |
|
||||||
|
| `Vector3<T>` | 3D vector | `length()`, `normalized()`, `dot()`, `cross()`, `angle_between()` |
|
||||||
|
| `Vector4<T>` | 4D vector | Extends Vector3 with `w` component |
|
||||||
|
|
||||||
|
**Common aliases:**
|
||||||
|
```cpp
|
||||||
|
using Vec2f = Vector2<float>;
|
||||||
|
using Vec3f = Vector3<float>;
|
||||||
|
using Vec4f = Vector4<float>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Component-wise arithmetic (+, -, *, /)
|
||||||
|
- Scalar multiplication/division
|
||||||
|
- Dot and cross products
|
||||||
|
- Safe normalization (returns original if length is zero)
|
||||||
|
- Distance calculations
|
||||||
|
- Angle calculations with error handling
|
||||||
|
- Hash support for `float` variants
|
||||||
|
- `std::formatter` support
|
||||||
|
|
||||||
|
### Matrices
|
||||||
|
|
||||||
|
| Type | Description | Key Methods |
|
||||||
|
|------|-------------|-------------|
|
||||||
|
| `Mat4X4` | 4×4 matrix | `identity()`, `transpose()`, `determinant()`, `inverse()` |
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- Transformation matrices
|
||||||
|
- View matrices
|
||||||
|
- Projection matrices
|
||||||
|
- Model-view-projection pipelines
|
||||||
|
|
||||||
|
### Angles
|
||||||
|
|
||||||
|
Strong-typed angle system with compile-time range enforcement:
|
||||||
|
|
||||||
|
| Type | Range | Description |
|
||||||
|
|------|-------|-------------|
|
||||||
|
| `Angle<T, Min, Max, Flags>` | Custom | Generic angle type with bounds |
|
||||||
|
| `PitchAngle` | [-89°, 89°] | Vertical camera rotation |
|
||||||
|
| `YawAngle` | [-180°, 180°] | Horizontal camera rotation |
|
||||||
|
| `RollAngle` | [-180°, 180°] | Camera roll |
|
||||||
|
| `ViewAngles` | - | Composite pitch/yaw/roll |
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Automatic normalization/clamping based on flags
|
||||||
|
- Conversions between degrees and radians
|
||||||
|
- Type-safe arithmetic
|
||||||
|
- Prevents common angle bugs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projection System
|
||||||
|
|
||||||
|
### Camera
|
||||||
|
|
||||||
|
Generic camera template that works with any engine trait:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class MatrixType, class AnglesType, class EngineTrait>
|
||||||
|
class Camera;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Engine-specific cameras:**
|
||||||
|
```cpp
|
||||||
|
omath::source_engine::Camera // Source Engine
|
||||||
|
omath::unity_engine::Camera // Unity
|
||||||
|
omath::unreal_engine::Camera // Unreal
|
||||||
|
omath::frostbite_engine::Camera // Frostbite
|
||||||
|
omath::iw_engine::Camera // IW Engine
|
||||||
|
omath::opengl_engine::Camera // OpenGL
|
||||||
|
```
|
||||||
|
|
||||||
|
**Core methods:**
|
||||||
|
- `world_to_screen(Vector3<float>)` - Project 3D point to 2D screen
|
||||||
|
- `get_view_matrix()` - Get current view matrix
|
||||||
|
- `get_projection_matrix()` - Get current projection matrix
|
||||||
|
- `update(position, angles)` - Update camera state
|
||||||
|
|
||||||
|
**Supporting types:**
|
||||||
|
- `ViewPort` - Screen dimensions and aspect ratio
|
||||||
|
- `FieldOfView` - FOV in degrees with validation
|
||||||
|
- `ProjectionError` - Error codes for projection failures
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Collision Detection
|
||||||
|
|
||||||
|
### LineTracer
|
||||||
|
|
||||||
|
Ray-casting and line tracing utilities:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::collision {
|
||||||
|
class LineTracer;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Ray-triangle intersection
|
||||||
|
- Ray-plane intersection
|
||||||
|
- Ray-box intersection
|
||||||
|
- Distance calculations
|
||||||
|
- Normal calculations at hit points
|
||||||
|
|
||||||
|
### 3D Primitives
|
||||||
|
|
||||||
|
| Type | Description | Key Methods |
|
||||||
|
|------|-------------|-------------|
|
||||||
|
| `Plane` | Infinite plane | `intersects_ray()`, `distance_to_point()` |
|
||||||
|
| `Box` | Axis-aligned bounding box | `contains()`, `intersects()` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projectile Prediction
|
||||||
|
|
||||||
|
### Interfaces
|
||||||
|
|
||||||
|
**`ProjPredEngineInterface`** - Base interface for all prediction engines
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
virtual std::optional<Vector3<float>>
|
||||||
|
maybe_calculate_aim_point(const Projectile&, const Target&) const = 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementations
|
||||||
|
|
||||||
|
| Engine | Description | Optimizations |
|
||||||
|
|--------|-------------|---------------|
|
||||||
|
| `ProjPredEngineLegacy` | Standard implementation | Portable, works everywhere |
|
||||||
|
| `ProjPredEngineAVX2` | AVX2 optimized | 2-4x faster on modern CPUs |
|
||||||
|
|
||||||
|
### Supporting Types
|
||||||
|
|
||||||
|
**`Projectile`** - Defines projectile properties:
|
||||||
|
```cpp
|
||||||
|
struct Projectile {
|
||||||
|
Vector3<float> origin;
|
||||||
|
float speed;
|
||||||
|
Vector3<float> gravity;
|
||||||
|
// ... additional properties
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**`Target`** - Defines target state:
|
||||||
|
```cpp
|
||||||
|
struct Target {
|
||||||
|
Vector3<float> position;
|
||||||
|
Vector3<float> velocity;
|
||||||
|
// ... additional properties
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pathfinding
|
||||||
|
|
||||||
|
### A* Algorithm
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::pathfinding {
|
||||||
|
template<typename NodeType>
|
||||||
|
class AStar;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Generic node type support
|
||||||
|
- Customizable heuristics
|
||||||
|
- Efficient priority queue implementation
|
||||||
|
- Path reconstruction
|
||||||
|
|
||||||
|
### Navigation Mesh
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::pathfinding {
|
||||||
|
class NavigationMesh;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Triangle-based navigation
|
||||||
|
- Neighbor connectivity
|
||||||
|
- Walkable area definitions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Engine Traits
|
||||||
|
|
||||||
|
Each game engine has a trait system providing engine-specific math:
|
||||||
|
|
||||||
|
### CameraTrait
|
||||||
|
|
||||||
|
Implements camera math for an engine:
|
||||||
|
- `calc_look_at_angle()` - Calculate angles to look at a point
|
||||||
|
- `calc_view_matrix()` - Build view matrix from angles and position
|
||||||
|
- `calc_projection_matrix()` - Build projection matrix from FOV and viewport
|
||||||
|
|
||||||
|
### PredEngineTrait
|
||||||
|
|
||||||
|
Provides physics/ballistics specific to an engine:
|
||||||
|
- Gravity vectors
|
||||||
|
- Coordinate system conventions
|
||||||
|
- Unit conversions
|
||||||
|
- Physics parameters
|
||||||
|
|
||||||
|
### Available Traits
|
||||||
|
|
||||||
|
| Engine | Camera Trait | Pred Engine Trait | Constants | Formulas |
|
||||||
|
|--------|--------------|-------------------|-----------|----------|
|
||||||
|
| Source Engine | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| Unity Engine | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| Unreal Engine | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| Frostbite | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| IW Engine | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| OpenGL | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- See `docs/engines/<engine_name>/` for detailed per-engine docs
|
||||||
|
- Each engine has separate docs for camera_trait, pred_engine_trait, constants, and formulas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Utility Functions
|
||||||
|
|
||||||
|
### Color
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Color {
|
||||||
|
uint8_t r, g, b, a;
|
||||||
|
|
||||||
|
// Conversions
|
||||||
|
static Color from_hsv(float h, float s, float v);
|
||||||
|
static Color from_hex(uint32_t hex);
|
||||||
|
uint32_t to_hex() const;
|
||||||
|
|
||||||
|
// Blending
|
||||||
|
Color blend(const Color& other, float t) const;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern Scanning
|
||||||
|
|
||||||
|
**Binary pattern search with wildcards:**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Pattern with wildcards (?? = any byte)
|
||||||
|
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
|
||||||
|
|
||||||
|
// Scan memory
|
||||||
|
auto result = pattern_scan(memory_buffer, pattern);
|
||||||
|
if (result) {
|
||||||
|
std::cout << "Found at offset: " << result->offset << "\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**PE file scanning:**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
PEPatternScanner scanner("target.exe");
|
||||||
|
if (auto addr = scanner.scan_pattern(pattern)) {
|
||||||
|
std::cout << "Found at RVA: " << *addr << "\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reverse Engineering
|
||||||
|
|
||||||
|
**External memory access:**
|
||||||
|
```cpp
|
||||||
|
ExternalRevObject process("game.exe");
|
||||||
|
Vector3<float> position = process.read<Vector3<float>>(address);
|
||||||
|
process.write(address, new_position);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Internal memory access:**
|
||||||
|
```cpp
|
||||||
|
InternalRevObject memory;
|
||||||
|
auto value = memory.read<float>(address);
|
||||||
|
memory.write(address, new_value);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Concepts and Constraints
|
||||||
|
|
||||||
|
OMath uses C++20 concepts for type safety:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class T>
|
||||||
|
concept Arithmetic = std::is_arithmetic_v<T>;
|
||||||
|
|
||||||
|
template<class EngineTrait>
|
||||||
|
concept CameraEngineConcept = requires(EngineTrait t) {
|
||||||
|
{ t.calc_look_at_angle(...) } -> /* returns angles */;
|
||||||
|
{ t.calc_view_matrix(...) } -> /* returns matrix */;
|
||||||
|
{ t.calc_projection_matrix(...) } -> /* returns matrix */;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
OMath uses modern C++ error handling:
|
||||||
|
|
||||||
|
### std::expected (C++23)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::expected<Angle<...>, Vector3Error>
|
||||||
|
angle_between(const Vector3& other) const;
|
||||||
|
|
||||||
|
if (auto angle = v1.angle_between(v2)) {
|
||||||
|
// Success: use *angle
|
||||||
|
} else {
|
||||||
|
// Error: angle.error() gives Vector3Error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### std::optional
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::optional<Vector2<float>>
|
||||||
|
world_to_screen(const Vector3<float>& world);
|
||||||
|
|
||||||
|
if (auto screen = camera.world_to_screen(pos)) {
|
||||||
|
// Success: use screen->x, screen->y
|
||||||
|
} else {
|
||||||
|
// Point not visible
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Codes
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class ProjectionError {
|
||||||
|
SUCCESS = 0,
|
||||||
|
POINT_BEHIND_CAMERA,
|
||||||
|
INVALID_VIEWPORT,
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### constexpr Support
|
||||||
|
|
||||||
|
Most operations are `constexpr` where possible:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector3<float> v{1, 2, 3};
|
||||||
|
constexpr auto len_sq = v.length_sqr(); // Computed at compile time
|
||||||
|
```
|
||||||
|
|
||||||
|
### AVX2 Optimizations
|
||||||
|
|
||||||
|
Use AVX2 variants when available:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Standard: portable but slower
|
||||||
|
ProjPredEngineLegacy legacy_engine;
|
||||||
|
|
||||||
|
// AVX2: 2-4x faster on modern CPUs
|
||||||
|
ProjPredEngineAVX2 fast_engine;
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use AVX2:**
|
||||||
|
- Modern Intel/AMD processors (2013+)
|
||||||
|
- Performance-critical paths
|
||||||
|
- Batch operations
|
||||||
|
|
||||||
|
**When to use Legacy:**
|
||||||
|
- Older processors
|
||||||
|
- ARM platforms
|
||||||
|
- Guaranteed compatibility
|
||||||
|
|
||||||
|
### Cache Efficiency
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: contiguous storage
|
||||||
|
std::vector<Vector3<float>> positions;
|
||||||
|
|
||||||
|
// Good: structure of arrays for SIMD
|
||||||
|
struct Particles {
|
||||||
|
std::vector<float> x, y, z;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Platform Support
|
||||||
|
|
||||||
|
| Platform | Support | Notes |
|
||||||
|
|----------|---------|-------|
|
||||||
|
| Windows | ✓ | MSVC, Clang, GCC |
|
||||||
|
| Linux | ✓ | GCC, Clang |
|
||||||
|
| macOS | ✓ | Clang |
|
||||||
|
|
||||||
|
**Minimum requirements:**
|
||||||
|
- C++20 compiler
|
||||||
|
- C++23 recommended for `std::expected`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Thread Safety
|
||||||
|
|
||||||
|
- **Vector/Matrix types**: Thread-safe (immutable operations)
|
||||||
|
- **Camera**: Not thread-safe (mutable state)
|
||||||
|
- **Pattern scanning**: Thread-safe (read-only operations)
|
||||||
|
- **Memory access**: Depends on OS/process synchronization
|
||||||
|
|
||||||
|
**Thread-safe example:**
|
||||||
|
```cpp
|
||||||
|
// Safe: each thread gets its own camera
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
for (int i = 0; i < num_threads; ++i) {
|
||||||
|
threads.emplace_back([i]() {
|
||||||
|
Camera camera = /* create camera */;
|
||||||
|
// Use camera in this thread
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Use Type Aliases
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Vec3f = omath::Vector3<float>;
|
||||||
|
using Mat4 = omath::Mat4X4;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Prefer constexpr When Possible
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr auto compute_at_compile_time() {
|
||||||
|
Vector3<float> v{1, 2, 3};
|
||||||
|
return v.length_sqr();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Check Optional/Expected Results
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good
|
||||||
|
if (auto result = camera.world_to_screen(pos)) {
|
||||||
|
use(*result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad - may crash
|
||||||
|
auto result = camera.world_to_screen(pos);
|
||||||
|
use(result->x); // Undefined behavior if nullopt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Use Engine-Specific Types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: uses correct coordinate system
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
Camera camera = /* ... */;
|
||||||
|
|
||||||
|
// Bad: mixing engine types
|
||||||
|
using UnityCamera = omath::unity_engine::Camera;
|
||||||
|
using SourceAngles = omath::source_engine::ViewAngles;
|
||||||
|
UnityCamera camera{pos, SourceAngles{}}; // Wrong!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Getting Started Guide](getting_started.md)
|
||||||
|
- [Installation Instructions](install.md)
|
||||||
|
- [Examples Directory](../examples/)
|
||||||
|
- Individual module documentation in respective folders
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
532
docs/best_practices.md
Normal file
@@ -0,0 +1,532 @@
|
|||||||
|
# Best Practices
|
||||||
|
|
||||||
|
Guidelines for using OMath effectively and avoiding common pitfalls.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Organization
|
||||||
|
|
||||||
|
### Use Type Aliases
|
||||||
|
|
||||||
|
Define clear type aliases for commonly used types:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Clear and concise
|
||||||
|
using Vec3f = omath::Vector3<float>;
|
||||||
|
using Vec2f = omath::Vector2<float>;
|
||||||
|
using Mat4 = omath::Mat4X4;
|
||||||
|
|
||||||
|
Vec3f position{1.0f, 2.0f, 3.0f};
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Avoid: Verbose and repetitive
|
||||||
|
omath::Vector3<float> position{1.0f, 2.0f, 3.0f};
|
||||||
|
omath::Vector3<float> velocity{0.0f, 0.0f, 0.0f};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Namespace Usage
|
||||||
|
|
||||||
|
Be selective with `using namespace`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Specific namespace for your engine
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
|
||||||
|
// Good: Import specific types
|
||||||
|
using omath::Vector3;
|
||||||
|
using omath::Vector2;
|
||||||
|
|
||||||
|
// Avoid: Too broad
|
||||||
|
using namespace omath; // Imports everything
|
||||||
|
```
|
||||||
|
|
||||||
|
### Include What You Use
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Include specific headers
|
||||||
|
#include <omath/linear_algebra/vector3.hpp>
|
||||||
|
#include <omath/projection/camera.hpp>
|
||||||
|
|
||||||
|
// Okay for development
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
|
||||||
|
// Production: Include only what you need
|
||||||
|
// to reduce compile times
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Always Check Optional Results
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Check before using
|
||||||
|
if (auto screen = camera.world_to_screen(world_pos)) {
|
||||||
|
draw_at(screen->x, screen->y);
|
||||||
|
} else {
|
||||||
|
// Handle point not visible
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: Unchecked access can crash
|
||||||
|
auto screen = camera.world_to_screen(world_pos);
|
||||||
|
draw_at(screen->x, screen->y); // Undefined behavior if nullopt!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handle Expected Errors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Handle error case
|
||||||
|
if (auto angle = v1.angle_between(v2)) {
|
||||||
|
use_angle(*angle);
|
||||||
|
} else {
|
||||||
|
switch (angle.error()) {
|
||||||
|
case Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE:
|
||||||
|
// Handle zero-length vector
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: Assume success
|
||||||
|
auto angle = v1.angle_between(v2);
|
||||||
|
use_angle(*angle); // Throws if error!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validate Inputs
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Validate before expensive operations
|
||||||
|
bool is_valid_projectile(const Projectile& proj) {
|
||||||
|
return proj.speed > 0.0f &&
|
||||||
|
std::isfinite(proj.speed) &&
|
||||||
|
std::isfinite(proj.origin.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_valid_projectile(proj) && is_valid_target(target)) {
|
||||||
|
auto aim = engine.maybe_calculate_aim_point(proj, target);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
### Use constexpr When Possible
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Computed at compile time
|
||||||
|
constexpr Vector3<float> gravity{0.0f, 0.0f, -9.81f};
|
||||||
|
constexpr float max_range = 1000.0f;
|
||||||
|
constexpr float max_range_sq = max_range * max_range;
|
||||||
|
|
||||||
|
// Use in runtime calculations
|
||||||
|
if (position.length_sqr() < max_range_sq) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prefer Squared Distance
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Avoids expensive sqrt
|
||||||
|
constexpr float max_dist_sq = 100.0f * 100.0f;
|
||||||
|
for (const auto& entity : entities) {
|
||||||
|
if (entity.pos.distance_to_sqr(player_pos) < max_dist_sq) {
|
||||||
|
// Process nearby entity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid: Unnecessary sqrt calls
|
||||||
|
constexpr float max_dist = 100.0f;
|
||||||
|
for (const auto& entity : entities) {
|
||||||
|
if (entity.pos.distance_to(player_pos) < max_dist) {
|
||||||
|
// More expensive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cache Expensive Calculations
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Update camera once per frame
|
||||||
|
void update_frame() {
|
||||||
|
camera.update(current_position, current_angles);
|
||||||
|
|
||||||
|
// All projections use cached matrices
|
||||||
|
for (const auto& entity : entities) {
|
||||||
|
if (auto screen = camera.world_to_screen(entity.pos)) {
|
||||||
|
draw_entity(screen->x, screen->y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: Camera recreated each call
|
||||||
|
for (const auto& entity : entities) {
|
||||||
|
Camera cam(pos, angles, viewport, fov, near, far); // Expensive!
|
||||||
|
auto screen = cam.world_to_screen(entity.pos);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Choose the Right Engine
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Use AVX2 when available
|
||||||
|
#ifdef __AVX2__
|
||||||
|
using Engine = ProjPredEngineAVX2;
|
||||||
|
#else
|
||||||
|
using Engine = ProjPredEngineLegacy;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Engine prediction_engine;
|
||||||
|
|
||||||
|
// Or runtime detection
|
||||||
|
Engine* create_best_engine() {
|
||||||
|
if (cpu_supports_avx2()) {
|
||||||
|
return new ProjPredEngineAVX2();
|
||||||
|
}
|
||||||
|
return new ProjPredEngineLegacy();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Minimize Allocations
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Reuse vectors
|
||||||
|
std::vector<Vector3<float>> positions;
|
||||||
|
positions.reserve(expected_count);
|
||||||
|
|
||||||
|
// In loop
|
||||||
|
positions.clear(); // Doesn't deallocate
|
||||||
|
for (...) {
|
||||||
|
positions.push_back(compute_position());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: Allocate every time
|
||||||
|
for (...) {
|
||||||
|
std::vector<Vector3<float>> positions; // Allocates each iteration
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Type Safety
|
||||||
|
|
||||||
|
### Use Strong Angle Types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Type-safe angles
|
||||||
|
PitchAngle pitch = PitchAngle::from_degrees(45.0f);
|
||||||
|
YawAngle yaw = YawAngle::from_degrees(90.0f);
|
||||||
|
ViewAngles angles{pitch, yaw, RollAngle::from_degrees(0.0f)};
|
||||||
|
|
||||||
|
// Bad: Raw floats lose safety
|
||||||
|
float pitch = 45.0f; // No range checking
|
||||||
|
float yaw = 90.0f; // Can go out of bounds
|
||||||
|
```
|
||||||
|
|
||||||
|
### Match Engine Types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Use matching types from same engine
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
Camera camera = /* ... */;
|
||||||
|
ViewAngles angles = /* ... */;
|
||||||
|
|
||||||
|
// Bad: Mixing engine types
|
||||||
|
using UnityCamera = omath::unity_engine::Camera;
|
||||||
|
using SourceAngles = omath::source_engine::ViewAngles;
|
||||||
|
UnityCamera camera{pos, SourceAngles{}, ...}; // May cause issues!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Template Type Parameters
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Explicit and clear
|
||||||
|
Vector3<float> position;
|
||||||
|
Vector3<double> high_precision_pos;
|
||||||
|
|
||||||
|
// Okay: Use default float
|
||||||
|
Vector3<> position; // Defaults to float
|
||||||
|
|
||||||
|
// Avoid: Mixing types unintentionally
|
||||||
|
Vector3<float> a;
|
||||||
|
Vector3<double> b;
|
||||||
|
auto result = a + b; // Type mismatch!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing & Validation
|
||||||
|
|
||||||
|
### Test Edge Cases
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void test_projection() {
|
||||||
|
Camera camera = setup_camera();
|
||||||
|
|
||||||
|
// Test normal case
|
||||||
|
assert(camera.world_to_screen({100, 100, 100}).has_value());
|
||||||
|
|
||||||
|
// Test edge cases
|
||||||
|
assert(!camera.world_to_screen({0, 0, -100}).has_value()); // Behind
|
||||||
|
assert(!camera.world_to_screen({1e10, 0, 0}).has_value()); // Too far
|
||||||
|
|
||||||
|
// Test boundaries
|
||||||
|
Vector3<float> at_near{0, 0, camera.near_plane() + 0.1f};
|
||||||
|
assert(camera.world_to_screen(at_near).has_value());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validate Assumptions
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void validate_game_data() {
|
||||||
|
// Validate FOV
|
||||||
|
float fov = read_game_fov();
|
||||||
|
assert(fov > 1.0f && fov < 179.0f);
|
||||||
|
|
||||||
|
// Validate positions
|
||||||
|
Vector3<float> pos = read_player_position();
|
||||||
|
assert(std::isfinite(pos.x));
|
||||||
|
assert(std::isfinite(pos.y));
|
||||||
|
assert(std::isfinite(pos.z));
|
||||||
|
|
||||||
|
// Validate viewport
|
||||||
|
ViewPort vp = read_viewport();
|
||||||
|
assert(vp.width > 0 && vp.height > 0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Assertions
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Catch errors early in development
|
||||||
|
void shoot_projectile(const Projectile& proj) {
|
||||||
|
assert(proj.speed > 0.0f && "Projectile speed must be positive");
|
||||||
|
assert(std::isfinite(proj.origin.length()) && "Invalid projectile origin");
|
||||||
|
|
||||||
|
// Continue with logic
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add debug-only checks
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (!is_valid_input(data)) {
|
||||||
|
std::cerr << "Warning: Invalid input detected\n";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Memory & Resources
|
||||||
|
|
||||||
|
### RAII for Resources
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Automatic cleanup
|
||||||
|
class GameOverlay {
|
||||||
|
Camera camera_;
|
||||||
|
std::vector<Entity> entities_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
GameOverlay(/* ... */) : camera_(/* ... */) {
|
||||||
|
entities_.reserve(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resources cleaned up automatically
|
||||||
|
~GameOverlay() = default;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid Unnecessary Copies
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Pass by const reference
|
||||||
|
void draw_entities(const std::vector<Vector3<float>>& positions) {
|
||||||
|
for (const auto& pos : positions) {
|
||||||
|
// Process position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: Copies entire vector
|
||||||
|
void draw_entities(std::vector<Vector3<float>> positions) {
|
||||||
|
// Expensive copy!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good: Move when transferring ownership
|
||||||
|
std::vector<Vector3<float>> compute_positions();
|
||||||
|
auto positions = compute_positions(); // Move, not copy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Structured Bindings
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Clear and concise
|
||||||
|
if (auto [success, screen_pos] = try_project(world_pos); success) {
|
||||||
|
draw_at(screen_pos.x, screen_pos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good: Decompose results
|
||||||
|
auto [x, y, z] = position.as_tuple();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### Document Assumptions
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Clear documentation
|
||||||
|
/**
|
||||||
|
* Projects world position to screen space.
|
||||||
|
*
|
||||||
|
* @param world_pos Position in world coordinates (meters)
|
||||||
|
* @return Screen position if visible, nullopt if behind camera or out of view
|
||||||
|
*
|
||||||
|
* @note Assumes camera.update() was called this frame
|
||||||
|
* @note Screen coordinates are in viewport space [0, width] x [0, height]
|
||||||
|
*/
|
||||||
|
std::optional<Vector2<float>> project(const Vector3<float>& world_pos);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explain Non-Obvious Code
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Explain the math
|
||||||
|
// Use squared distance to avoid expensive sqrt
|
||||||
|
// max_range = 100.0 → max_range_sq = 10000.0
|
||||||
|
constexpr float max_range_sq = 100.0f * 100.0f;
|
||||||
|
if (dist_sq < max_range_sq) {
|
||||||
|
// Entity is in range
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explain engine-specific quirks
|
||||||
|
// Source Engine uses Z-up coordinates, but angles are in degrees
|
||||||
|
// Pitch: [-89, 89], Yaw: [-180, 180], Roll: [-180, 180]
|
||||||
|
ViewAngles angles{pitch, yaw, roll};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
### Add Debug Visualization
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#ifndef NDEBUG
|
||||||
|
void debug_draw_projection() {
|
||||||
|
// Draw camera frustum
|
||||||
|
draw_frustum(camera);
|
||||||
|
|
||||||
|
// Draw world axes
|
||||||
|
draw_line({0,0,0}, {100,0,0}, Color::Red); // X
|
||||||
|
draw_line({0,0,0}, {0,100,0}, Color::Green); // Y
|
||||||
|
draw_line({0,0,0}, {0,0,100}, Color::Blue); // Z
|
||||||
|
|
||||||
|
// Draw projected points
|
||||||
|
for (const auto& entity : entities) {
|
||||||
|
if (auto screen = camera.world_to_screen(entity.pos)) {
|
||||||
|
draw_cross(screen->x, screen->y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Important Values
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void debug_projection_failure(const Vector3<float>& pos) {
|
||||||
|
std::cerr << "Projection failed for position: "
|
||||||
|
<< pos.x << ", " << pos.y << ", " << pos.z << "\n";
|
||||||
|
|
||||||
|
auto view_matrix = camera.get_view_matrix();
|
||||||
|
std::cerr << "View matrix:\n";
|
||||||
|
// Print matrix...
|
||||||
|
|
||||||
|
std::cerr << "Camera position: "
|
||||||
|
<< camera.position().x << ", "
|
||||||
|
<< camera.position().y << ", "
|
||||||
|
<< camera.position().z << "\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Debug Builds
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# CMakeLists.txt
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
target_compile_definitions(your_target PRIVATE
|
||||||
|
DEBUG_PROJECTION=1
|
||||||
|
VALIDATE_INPUTS=1
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#ifdef DEBUG_PROJECTION
|
||||||
|
std::cout << "Projecting: " << world_pos << "\n";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef VALIDATE_INPUTS
|
||||||
|
assert(std::isfinite(world_pos.length()));
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Platform Considerations
|
||||||
|
|
||||||
|
### Cross-Platform Code
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Platform-agnostic
|
||||||
|
constexpr float PI = 3.14159265359f;
|
||||||
|
|
||||||
|
// Avoid: Platform-specific
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Windows-only code
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handle Different Compilers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Good: Compiler-agnostic
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
// MSVC-specific
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
// GCC/Clang-specific
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Use OMath's built-in compatibility
|
||||||
|
// It handles compiler differences automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Key principles:**
|
||||||
|
1. **Safety first**: Always check optional/expected results
|
||||||
|
2. **Performance matters**: Use constexpr, avoid allocations, cache results
|
||||||
|
3. **Type safety**: Use strong types, match engine types
|
||||||
|
4. **Clear code**: Use aliases, document assumptions, explain non-obvious logic
|
||||||
|
5. **Test thoroughly**: Validate inputs, test edge cases, add assertions
|
||||||
|
6. **Debug effectively**: Add visualization, log values, use debug builds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Troubleshooting Guide](troubleshooting.md)
|
||||||
|
- [FAQ](faq.md)
|
||||||
|
- [API Overview](api_overview.md)
|
||||||
|
- [Tutorials](tutorials.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
181
docs/collision/line_tracer.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# `omath::collision::Ray` & `LineTracer` — Ray–Triangle intersection (Möller–Trumbore)
|
||||||
|
|
||||||
|
> Headers: your project’s `ray.hpp` (includes `omath/linear_algebra/triangle.hpp`, `omath/linear_algebra/vector3.hpp`)
|
||||||
|
> Namespace: `omath::collision`
|
||||||
|
> Depends on: `omath::Vector3<float>`, `omath::Triangle<Vector3<float>>`
|
||||||
|
> Algorithm: **Möller–Trumbore** ray–triangle intersection (no allocation)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
These types provide a minimal, fast path to test and compute intersections between a **ray or line segment** and a **single triangle**:
|
||||||
|
|
||||||
|
* `Ray` — start/end points plus a flag to treat the ray as **infinite** (half-line) or a **finite segment**.
|
||||||
|
* `LineTracer` — static helpers:
|
||||||
|
|
||||||
|
* `can_trace_line(ray, triangle)` → `true` if they intersect.
|
||||||
|
* `get_ray_hit_point(ray, triangle)` → the hit point (precondition: intersection exists).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `Ray`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Ray {
|
||||||
|
public:
|
||||||
|
omath::Vector3<float> start; // ray origin
|
||||||
|
omath::Vector3<float> end; // end point (for finite segment) or a point along the direction
|
||||||
|
bool infinite_length = false;
|
||||||
|
|
||||||
|
[[nodiscard]] omath::Vector3<float> direction_vector() const noexcept;
|
||||||
|
[[nodiscard]] omath::Vector3<float> direction_vector_normalized() const noexcept;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Semantics
|
||||||
|
|
||||||
|
* **Direction**: `direction_vector() == end - start`.
|
||||||
|
The normalized variant returns a unit vector (or `{0,0,0}` if the direction length is zero).
|
||||||
|
* **Extent**:
|
||||||
|
|
||||||
|
* `infinite_length == true` → treat as a **semi-infinite ray** from `start` along `direction`.
|
||||||
|
* `infinite_length == false` → treat as a **closed segment** from `start` to `end`.
|
||||||
|
|
||||||
|
> Tip: For an infinite ray that points along some vector `d`, set `end = start + d`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `LineTracer`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class LineTracer {
|
||||||
|
public:
|
||||||
|
LineTracer() = delete;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static bool can_trace_line(
|
||||||
|
const Ray& ray,
|
||||||
|
const omath::Triangle<omath::Vector3<float>>& triangle
|
||||||
|
) noexcept;
|
||||||
|
|
||||||
|
// Möller–Trumbore intersection
|
||||||
|
[[nodiscard]]
|
||||||
|
static omath::Vector3<float> get_ray_hit_point(
|
||||||
|
const Ray& ray,
|
||||||
|
const omath::Triangle<omath::Vector3<float>>& triangle
|
||||||
|
) noexcept;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Behavior & contract
|
||||||
|
|
||||||
|
* **Intersection test**: `can_trace_line` returns `true` iff the ray/segment intersects the triangle (within the ray’s extent).
|
||||||
|
* **Hit point**: `get_ray_hit_point` **assumes** there is an intersection.
|
||||||
|
Call **only after** `can_trace_line(...) == true`. Otherwise the result is unspecified.
|
||||||
|
* **Triangle winding**: Standard Möller–Trumbore works with either winding; no backface culling is implied here.
|
||||||
|
* **Degenerate inputs**: A zero-length ray or degenerate triangle yields **no hit** under typical Möller–Trumbore tolerances.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick examples
|
||||||
|
|
||||||
|
### 1) Segment vs triangle
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using omath::Vector3;
|
||||||
|
using omath::Triangle;
|
||||||
|
using omath::collision::Ray;
|
||||||
|
using omath::collision::LineTracer;
|
||||||
|
|
||||||
|
Triangle<Vector3<float>> tri(
|
||||||
|
Vector3<float>{0, 0, 0},
|
||||||
|
Vector3<float>{1, 0, 0},
|
||||||
|
Vector3<float>{0, 1, 0}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ray seg;
|
||||||
|
seg.start = {0.25f, 0.25f, 1.0f};
|
||||||
|
seg.end = {0.25f, 0.25f,-1.0f};
|
||||||
|
seg.infinite_length = false; // finite segment
|
||||||
|
|
||||||
|
if (LineTracer::can_trace_line(seg, tri)) {
|
||||||
|
Vector3<float> hit = LineTracer::get_ray_hit_point(seg, tri);
|
||||||
|
// use hit
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) Infinite ray
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Ray ray;
|
||||||
|
ray.start = {0.5f, 0.5f, 1.0f};
|
||||||
|
ray.end = ray.start + Vector3<float>{0, 0, -1}; // direction only
|
||||||
|
ray.infinite_length = true;
|
||||||
|
|
||||||
|
bool hit = LineTracer::can_trace_line(ray, tri);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & edge cases
|
||||||
|
|
||||||
|
* **Normalization**: `direction_vector_normalized()` returns `{0,0,0}` for a zero-length direction (safe, but unusable for tracing).
|
||||||
|
* **Precision**: The underlying algorithm uses EPS thresholds to reject nearly parallel cases; results near edges can be sensitive to floating-point error. If you need robust edge inclusion/exclusion, document and enforce a policy (e.g., inclusive barycentric range with small epsilon).
|
||||||
|
* **Hit location**: The point returned by `get_ray_hit_point` lies **on the triangle plane** and within its area by construction (when `can_trace_line` is `true`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API summary
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::collision {
|
||||||
|
|
||||||
|
class Ray {
|
||||||
|
public:
|
||||||
|
Vector3<float> start, end;
|
||||||
|
bool infinite_length = false;
|
||||||
|
|
||||||
|
[[nodiscard]] Vector3<float> direction_vector() const noexcept;
|
||||||
|
[[nodiscard]] Vector3<float> direction_vector_normalized() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LineTracer {
|
||||||
|
public:
|
||||||
|
LineTracer() = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] static bool can_trace_line(
|
||||||
|
const Ray&,
|
||||||
|
const omath::Triangle<omath::Vector3<float>>&
|
||||||
|
) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] static Vector3<float> get_ray_hit_point(
|
||||||
|
const Ray&,
|
||||||
|
const omath::Triangle<omath::Vector3<float>>&
|
||||||
|
) noexcept; // precondition: can_trace_line(...) == true
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::collision
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation hints (if you extend it)
|
||||||
|
|
||||||
|
* Expose a variant that returns **barycentric coordinates** `(u, v, w)` alongside the hit point to support texture lookup or edge tests.
|
||||||
|
* Provide an overload returning `std::optional<Vector3<float>>` (or `expected`) for safer one-shot queries without a separate test call.
|
||||||
|
* If you need backface culling, add a flag or dedicated function (reject hits where the signed distance is negative with respect to triangle normal).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Plane Documentation](../3d_primitives/plane.md) - Ray-plane intersection
|
||||||
|
- [Box Documentation](../3d_primitives/box.md) - AABB collision detection
|
||||||
|
- [Triangle Documentation](../linear_algebra/triangle.md) - Triangle primitives
|
||||||
|
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
|
||||||
|
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
108
docs/engines/frostbite/camera_trait.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# `omath::frostbite_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||||
|
|
||||||
|
> Header: `omath/engines/frostbite_engine/traits/camera_trait.hpp` • Impl: `omath/engines/frostbite_engine/traits/camera_trait.cpp`
|
||||||
|
> Namespace: `omath::frostbite_engine`
|
||||||
|
> Purpose: provide Frostbite-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`CameraTrait` exposes three `static` functions:
|
||||||
|
|
||||||
|
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `-asin(dir.y)` and **yaw** as `atan2(dir.x, dir.z)`; **roll** is `0`. Pitch/yaw are returned using the project’s strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||||
|
* `calc_view_matrix(angles, origin)` – delegates to Frostbite formulas `frostbite_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||||
|
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||||
|
|
||||||
|
The trait’s types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Frostbite engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::frostbite_engine {
|
||||||
|
|
||||||
|
class CameraTrait final {
|
||||||
|
public:
|
||||||
|
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||||
|
static ViewAngles
|
||||||
|
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||||
|
const Vector3<float>& look_at) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix for given angles and origin.
|
||||||
|
static Mat4X4
|
||||||
|
calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||||
|
static Mat4X4
|
||||||
|
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
|
const projection::ViewPort& view_port,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::frostbite_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Behavior & conventions
|
||||||
|
|
||||||
|
* **Angles from look-at**:
|
||||||
|
|
||||||
|
```
|
||||||
|
dir = normalize(look_at - origin)
|
||||||
|
pitch = -asin(dir.y) // +Y is up
|
||||||
|
yaw = atan2(dir.x, dir.z)
|
||||||
|
roll = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||||
|
|
||||||
|
* **View matrix**: built by the Frostbite engine helper `frostbite_engine::calc_view_matrix(angles, origin)` to match the engine’s handedness and axis conventions.
|
||||||
|
|
||||||
|
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using with `projection::Camera`
|
||||||
|
|
||||||
|
Create a camera whose math is driven by this trait:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4 = Mat4X4; // from Frostbite math headers
|
||||||
|
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||||
|
using FBcam = omath::projection::Camera<Mat4, Angs, omath::frostbite_engine::CameraTrait>;
|
||||||
|
|
||||||
|
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||||
|
auto fov = omath::projection::FieldOfView::from_degrees(70.f);
|
||||||
|
|
||||||
|
FBcam cam(
|
||||||
|
/*position*/ {0.f, 1.7f, -3.f},
|
||||||
|
/*angles*/ omath::frostbite_engine::CameraTrait::calc_look_at_angle({0,1.7f,-3},{0,1.7f,0}),
|
||||||
|
/*viewport*/ vp,
|
||||||
|
/*fov*/ fov,
|
||||||
|
/*near*/ 0.1f,
|
||||||
|
/*far*/ 1000.f
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & tips
|
||||||
|
|
||||||
|
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project’s angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||||
|
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||||
|
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* Frostbite math helpers in `omath/engines/frostbite_engine/formulas.hpp` (view/projection builders used above).
|
||||||
|
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||||
59
docs/engines/frostbite/constants.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
Nice! A clean little “types + constants” header. A few quick fixes and polish:
|
||||||
|
|
||||||
|
## Issues / suggestions
|
||||||
|
|
||||||
|
1. **Mat3X3 alias is wrong**
|
||||||
|
|
||||||
|
* You wrote `using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;`
|
||||||
|
* That should be `Mat<3, 3, ...>`.
|
||||||
|
|
||||||
|
2. **`constexpr` globals in a header → make them `inline constexpr`**
|
||||||
|
|
||||||
|
* Since this is in a header included by multiple TUs, use `inline constexpr` to avoid ODR/link issues (C++17+).
|
||||||
|
|
||||||
|
3. **Consider column-major vs row-major**
|
||||||
|
|
||||||
|
* Most game/graphics stacks (GLSL/HLSL, many engines) lean column-major and column vectors. If the rest of your math lib or shaders assume column-major, align these typedefs now to avoid silent transposes later. If row-major is intentional, all good—just be consistent.
|
||||||
|
|
||||||
|
4. **Naming consistency**
|
||||||
|
|
||||||
|
* If you prefer `k_` prefix, keep it; otherwise consider `kAbsUp`/`ABS_UP` to match your codebase’s conventions.
|
||||||
|
|
||||||
|
5. **`Mat1X3` as a “row vector”**
|
||||||
|
|
||||||
|
* If you actually use it as a 3-component vector, consider just `Vector3<float>` (clearer) and reserve `Mat1X3` for real row-vector math.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tidied version
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Created by Vlad on 10/21/2025.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "omath/linear_algebra/mat.hpp"
|
||||||
|
#include "omath/linear_algebra/vector3.hpp"
|
||||||
|
#include <omath/trigonometry/angle.hpp>
|
||||||
|
#include <omath/trigonometry/view_angles.hpp>
|
||||||
|
|
||||||
|
namespace omath::frostbite_engine
|
||||||
|
{
|
||||||
|
// Inline to avoid ODR across translation units
|
||||||
|
inline constexpr Vector3<float> k_abs_up = {0.0f, 1.0f, 0.0f};
|
||||||
|
inline constexpr Vector3<float> k_abs_right = {1.0f, 0.0f, 0.0f};
|
||||||
|
inline constexpr Vector3<float> k_abs_forward = {0.0f, 0.0f, 1.0f};
|
||||||
|
|
||||||
|
// NOTE: verify row/column major matches the rest of your engine
|
||||||
|
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat3X3 = Mat<3, 3, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
|
||||||
|
using PitchAngle = Angle<float, -90.0f, 90.0f, AngleFlags::Clamped >;
|
||||||
|
using YawAngle = Angle<float,-180.0f, 180.0f, AngleFlags::Normalized>;
|
||||||
|
using RollAngle = Angle<float,-180.0f, 180.0f, AngleFlags::Normalized>;
|
||||||
|
|
||||||
|
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||||
|
} // namespace omath::frostbite_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
If you share how your matrices multiply vectors (row vs column) and your world handedness, I can double-check the axis constants and angle normalization to make sure yaw/pitch signs line up with your camera and `atan2` usage.
|
||||||
0
docs/engines/frostbite/formulas.md
Normal file
105
docs/engines/frostbite/pred_engine_trait.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Created by Vlad on 8/6/2025.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath> // sqrt, hypot, tan, asin, atan2
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "omath/engines/frostbite_engine/formulas.hpp"
|
||||||
|
#include "omath/projectile_prediction/projectile.hpp"
|
||||||
|
#include "omath/projectile_prediction/target.hpp"
|
||||||
|
|
||||||
|
namespace omath::frostbite_engine
|
||||||
|
{
|
||||||
|
class PredEngineTrait final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Predict projectile position given launch angles (degrees), time (s), and world gravity (m/s^2).
|
||||||
|
// Note: kept runtime function; remove constexpr to avoid CTAD surprises across toolchains.
|
||||||
|
static Vector3<float> predict_projectile_position(
|
||||||
|
const projectile_prediction::Projectile& projectile,
|
||||||
|
float pitch_deg, float yaw_deg,
|
||||||
|
float time, float gravity) noexcept
|
||||||
|
{
|
||||||
|
// Engine convention: negative pitch looks up (your original used -pitch).
|
||||||
|
const auto fwd = forward_vector({
|
||||||
|
PitchAngle::from_degrees(-pitch_deg),
|
||||||
|
YawAngle::from_degrees(yaw_deg),
|
||||||
|
RollAngle::from_degrees(0.0f)
|
||||||
|
});
|
||||||
|
|
||||||
|
Vector3<float> pos =
|
||||||
|
projectile.m_origin +
|
||||||
|
fwd * (projectile.m_launch_speed * time);
|
||||||
|
|
||||||
|
// s = 1/2 a t^2 downward
|
||||||
|
pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static Vector3<float> predict_target_position(
|
||||||
|
const projectile_prediction::Target& target,
|
||||||
|
float time, float gravity) noexcept
|
||||||
|
{
|
||||||
|
Vector3<float> predicted = target.m_origin + target.m_velocity * time;
|
||||||
|
|
||||||
|
if (target.m_is_airborne) {
|
||||||
|
// If targets also have a gravity scale in your model, multiply here.
|
||||||
|
predicted.y -= gravity * (time * time) * 0.5f;
|
||||||
|
}
|
||||||
|
return predicted;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static float calc_vector_2d_distance(const Vector3<float>& delta) noexcept
|
||||||
|
{
|
||||||
|
// More stable than sqrt(x*x + z*z)
|
||||||
|
return std::hypot(delta.x, delta.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
|
||||||
|
{
|
||||||
|
return vec.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes a viewpoint above the predicted target, using an optional projectile pitch.
|
||||||
|
// If pitch is absent, we leave Y unchanged (or you can choose a sensible default).
|
||||||
|
[[nodiscard]]
|
||||||
|
static Vector3<float> calc_viewpoint_from_angles(
|
||||||
|
const projectile_prediction::Projectile& projectile,
|
||||||
|
const Vector3<float>& predicted_target_position,
|
||||||
|
const std::optional<float> projectile_pitch_deg) noexcept
|
||||||
|
{
|
||||||
|
// Lateral separation from projectile to target (X/Z plane).
|
||||||
|
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
|
||||||
|
|
||||||
|
float y = predicted_target_position.y;
|
||||||
|
if (projectile_pitch_deg.has_value()) {
|
||||||
|
const float pitch_rad = angles::degrees_to_radians(*projectile_pitch_deg);
|
||||||
|
const float height = delta2d * std::tan(pitch_rad);
|
||||||
|
y += height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the target's Z, not the projectile's Z (likely bugfix).
|
||||||
|
return { predicted_target_position.x, y, predicted_target_position.z };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to maybe_calculate_projectile_launch_pitch_angle spec: +89° up, -89° down.
|
||||||
|
[[nodiscard]]
|
||||||
|
static float calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept
|
||||||
|
{
|
||||||
|
const auto direction = (view_to - origin).normalized();
|
||||||
|
return angles::radians_to_degrees(std::asin(direction.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static float calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept
|
||||||
|
{
|
||||||
|
const auto direction = (view_to - origin).normalized();
|
||||||
|
return angles::radians_to_degrees(std::atan2(direction.x, direction.z));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace omath::frostbite_engine
|
||||||
109
docs/engines/iw_engine/camera_trait.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# `omath::iw_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||||
|
|
||||||
|
> Header: `omath/engines/iw_engine/traits/camera_trait.hpp` • Impl: `omath/engines/iw_engine/traits/camera_trait.cpp`
|
||||||
|
> Namespace: `omath::iw_engine`
|
||||||
|
> Purpose: provide IW Engine (Call of Duty)-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`CameraTrait` exposes three `static` functions:
|
||||||
|
|
||||||
|
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||||
|
* `calc_view_matrix(angles, origin)` – delegates to IW Engine formulas `iw_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||||
|
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||||
|
|
||||||
|
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the IW Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::iw_engine {
|
||||||
|
|
||||||
|
class CameraTrait final {
|
||||||
|
public:
|
||||||
|
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||||
|
static ViewAngles
|
||||||
|
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||||
|
const Vector3<float>& look_at) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix for given angles and origin.
|
||||||
|
static Mat4X4
|
||||||
|
calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||||
|
static Mat4X4
|
||||||
|
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
|
const projection::ViewPort& view_port,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::iw_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Behavior & conventions
|
||||||
|
|
||||||
|
* **Angles from look-at** (Z-up coordinate system):
|
||||||
|
|
||||||
|
```
|
||||||
|
dir = normalize(look_at - origin)
|
||||||
|
pitch = asin(dir.z) // +Z is up
|
||||||
|
yaw = atan2(dir.y, dir.x) // horizontal rotation
|
||||||
|
roll = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||||
|
|
||||||
|
* **View matrix**: built by the IW Engine helper `iw_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
|
||||||
|
|
||||||
|
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using with `projection::Camera`
|
||||||
|
|
||||||
|
Create a camera whose math is driven by this trait:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4 = Mat4X4; // from IW Engine math headers
|
||||||
|
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||||
|
using IWcam = omath::projection::Camera<Mat4, Angs, omath::iw_engine::CameraTrait>;
|
||||||
|
|
||||||
|
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||||
|
auto fov = omath::projection::FieldOfView::from_degrees(65.f);
|
||||||
|
|
||||||
|
IWcam cam(
|
||||||
|
/*position*/ {500.f, 200.f, 100.f},
|
||||||
|
/*angles*/ omath::iw_engine::CameraTrait::calc_look_at_angle({500,200,100},{0,0,100}),
|
||||||
|
/*viewport*/ vp,
|
||||||
|
/*fov*/ fov,
|
||||||
|
/*near*/ 0.1f,
|
||||||
|
/*far*/ 5000.f
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & tips
|
||||||
|
|
||||||
|
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||||
|
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||||
|
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||||
|
* IW Engine uses **Z-up**: pitch angles control vertical look, positive = up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* IW Engine math helpers in `omath/engines/iw_engine/formulas.hpp` (view/projection builders used above).
|
||||||
|
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||||
77
docs/engines/iw_engine/constants.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# `omath::iw_engine` — types & constants
|
||||||
|
|
||||||
|
> Header: `omath/engines/iw_engine/constants.hpp`
|
||||||
|
> Namespace: `omath::iw_engine`
|
||||||
|
> Purpose: define IW Engine (Call of Duty) coordinate system, matrix types, and angle ranges
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The **IW Engine** (Infinity Ward Engine, used in Call of Duty series) uses a **Z-up, right-handed** coordinate system:
|
||||||
|
|
||||||
|
* **Up** = `{0, 0, 1}` (Z-axis)
|
||||||
|
* **Right** = `{0, -1, 0}` (negative Y-axis)
|
||||||
|
* **Forward** = `{1, 0, 0}` (X-axis)
|
||||||
|
|
||||||
|
Matrices are **row-major**. Angles are **clamped pitch** (±89°) and **normalized yaw/roll** (±180°).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::iw_engine {
|
||||||
|
constexpr Vector3<float> k_abs_up = {0, 0, 1};
|
||||||
|
constexpr Vector3<float> k_abs_right = {0, -1, 0};
|
||||||
|
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These basis vectors define the engine's **world coordinate frame**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Matrix types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Angle types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using PitchAngle = Angle<float, -89.f, 89.f, AngleFlags::Clamped>;
|
||||||
|
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
|
||||||
|
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **PitchAngle**: clamped to **[-89°, +89°]** (looking down vs. up)
|
||||||
|
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||||
|
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||||
|
|
||||||
|
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coordinate system notes
|
||||||
|
|
||||||
|
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
|
||||||
|
* **Right-handed**: cross product `forward × right = up` holds
|
||||||
|
* This matches **IW Engine** (Call of Duty series: Modern Warfare, Black Ops, etc.) conventions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/iw_engine/formulas.hpp` — view/projection matrix builders
|
||||||
|
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||||
|
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||||
135
docs/engines/iw_engine/formulas.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# `omath::iw_engine` — formulas & matrix helpers
|
||||||
|
|
||||||
|
> Header: `omath/engines/iw_engine/formulas.hpp`
|
||||||
|
> Namespace: `omath::iw_engine`
|
||||||
|
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for IW Engine (Call of Duty)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This header provides **IW Engine**-specific math for:
|
||||||
|
|
||||||
|
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||||
|
* **Rotation matrices** from Euler angles
|
||||||
|
* **View matrices** (camera transforms)
|
||||||
|
* **Perspective projection** matrices
|
||||||
|
|
||||||
|
All functions respect IW Engine's **Z-up, right-handed** coordinate system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::iw_engine {
|
||||||
|
|
||||||
|
// Compute forward direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute right direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute up direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build 3x3 rotation matrix from angles
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix (camera space transform)
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection matrix
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||||
|
float aspect_ratio,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
|
||||||
|
} // namespace omath::iw_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Direction vectors
|
||||||
|
|
||||||
|
Given camera angles (pitch/yaw/roll):
|
||||||
|
|
||||||
|
* `forward_vector(angles)` → unit vector pointing where the camera looks
|
||||||
|
* `right_vector(angles)` → unit vector pointing to the camera's right
|
||||||
|
* `up_vector(angles)` → unit vector pointing upward relative to the camera
|
||||||
|
|
||||||
|
These are used for movement, aim direction, and building coordinate frames.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rotation & view matrices
|
||||||
|
|
||||||
|
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
|
||||||
|
* `calc_view_matrix(angles, origin)` → camera view matrix
|
||||||
|
|
||||||
|
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perspective projection
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||||
|
fov_degrees, // vertical field of view (e.g., 65)
|
||||||
|
aspect_ratio, // width / height (e.g., 16/9)
|
||||||
|
near_plane, // e.g., 0.1
|
||||||
|
far_plane // e.g., 5000.0
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::iw_engine;
|
||||||
|
|
||||||
|
// Camera setup
|
||||||
|
ViewAngles angles = {
|
||||||
|
PitchAngle::from_degrees(-10.0f),
|
||||||
|
YawAngle::from_degrees(90.0f),
|
||||||
|
RollAngle::from_degrees(0.0f)
|
||||||
|
};
|
||||||
|
Vector3<float> cam_pos{500.0f, 200.0f, 100.0f};
|
||||||
|
|
||||||
|
// Compute direction
|
||||||
|
auto forward = forward_vector(angles);
|
||||||
|
auto right = right_vector(angles);
|
||||||
|
auto up = up_vector(angles);
|
||||||
|
|
||||||
|
// Build matrices
|
||||||
|
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||||
|
auto proj_mat = calc_perspective_projection_matrix(65.0f, 16.0f/9.0f, 0.1f, 5000.0f);
|
||||||
|
|
||||||
|
// Use view_mat and proj_mat for rendering...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||||
|
* **Pitch**: positive = looking up, negative = looking down
|
||||||
|
* **Yaw**: increases counter-clockwise from the +X axis
|
||||||
|
* **Coordinate system**: Z-up, X-forward, Y-right (negative in code convention)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/iw_engine/constants.hpp` — coordinate frame & angle types
|
||||||
|
* `omath/engines/iw_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||||
|
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||||
198
docs/engines/iw_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# `omath::iw_engine::PredEngineTrait` — projectile prediction trait
|
||||||
|
|
||||||
|
> Header: `omath/engines/iw_engine/traits/pred_engine_trait.hpp`
|
||||||
|
> Namespace: `omath::iw_engine`
|
||||||
|
> Purpose: provide IW Engine (Call of Duty)-specific projectile and target prediction for ballistic calculations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||||
|
|
||||||
|
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||||
|
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||||
|
* `calc_vector_2d_distance` – horizontal distance (X/Y plane, ignoring Z)
|
||||||
|
* `get_vector_height_coordinate` – extracts vertical coordinate (Z in IW Engine)
|
||||||
|
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||||
|
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||||
|
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||||
|
|
||||||
|
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::iw_engine {
|
||||||
|
|
||||||
|
class PredEngineTrait final {
|
||||||
|
public:
|
||||||
|
// Predict projectile position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||||
|
float pitch, float yaw, float time,
|
||||||
|
float gravity) noexcept;
|
||||||
|
|
||||||
|
// Predict target position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_target_position(const projectile_prediction::Target& target,
|
||||||
|
float time, float gravity) noexcept;
|
||||||
|
|
||||||
|
// Compute horizontal (2D) distance
|
||||||
|
static float
|
||||||
|
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||||
|
|
||||||
|
// Get vertical coordinate (Z in IW Engine)
|
||||||
|
static constexpr float
|
||||||
|
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||||
|
|
||||||
|
// Compute aim point from angles
|
||||||
|
static Vector3<float>
|
||||||
|
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||||
|
Vector3<float> predicted_target_position,
|
||||||
|
std::optional<float> projectile_pitch) noexcept;
|
||||||
|
|
||||||
|
// Compute pitch angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
|
||||||
|
// Compute yaw angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::iw_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projectile prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_projectile_position(
|
||||||
|
projectile, // initial position, speed, gravity scale
|
||||||
|
pitch_deg, // launch pitch (positive = up)
|
||||||
|
yaw_deg, // launch yaw
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant (e.g., 800 units/s²)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes:
|
||||||
|
|
||||||
|
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||||
|
2. Initial velocity: `forward * launch_speed`
|
||||||
|
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Z component only)
|
||||||
|
|
||||||
|
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Target prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_target_position(
|
||||||
|
target, // position, velocity, airborne flag
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple linear extrapolation plus gravity if target is airborne:
|
||||||
|
|
||||||
|
```
|
||||||
|
predicted = origin + velocity * time
|
||||||
|
if (airborne)
|
||||||
|
predicted.z -= 0.5 * gravity * time²
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Distance & height helpers
|
||||||
|
|
||||||
|
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.y²)` (horizontal distance)
|
||||||
|
* `get_vector_height_coordinate(vec)` → `vec.z` (vertical coordinate in IW Engine)
|
||||||
|
|
||||||
|
Used to compute ballistic arc parameters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aim angle calculation
|
||||||
|
|
||||||
|
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `asin(Δz / distance)` converted to degrees
|
||||||
|
- Positive = looking up, negative = looking down
|
||||||
|
|
||||||
|
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `atan2(Δy, Δx)` converted to degrees
|
||||||
|
- Horizontal rotation around Z-axis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Viewpoint from angles
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||||
|
projectile,
|
||||||
|
predicted_target_pos,
|
||||||
|
optional_pitch_deg
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Coordinate system**: Z-up (height increases with Z)
|
||||||
|
* **Angles**: pitch in [-89°, +89°], yaw in [-180°, +180°]
|
||||||
|
* **Gravity**: applied downward along -Z axis
|
||||||
|
* **Pitch convention**: +89° = straight up, -89° = straight down
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::iw_engine;
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
Projectile proj{
|
||||||
|
.m_origin = {0, 0, 100},
|
||||||
|
.m_launch_speed = 1200.0f,
|
||||||
|
.m_gravity_scale = 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
Target tgt{
|
||||||
|
.m_origin = {800, 300, 100},
|
||||||
|
.m_velocity = {15, 8, 0},
|
||||||
|
.m_is_airborne = false
|
||||||
|
};
|
||||||
|
|
||||||
|
float gravity = 800.0f;
|
||||||
|
float time = 0.5f;
|
||||||
|
|
||||||
|
// Predict where target will be
|
||||||
|
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||||
|
|
||||||
|
// Compute aim angles
|
||||||
|
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||||
|
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||||
|
|
||||||
|
// Predict projectile position with those angles
|
||||||
|
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/iw_engine/formulas.hpp` — direction vectors and matrix builders
|
||||||
|
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||||
|
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||||
|
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||||
110
docs/engines/opengl_engine/camera_trait.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# `omath::opengl_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||||
|
|
||||||
|
> Header: `omath/engines/opengl_engine/traits/camera_trait.hpp` • Impl: `omath/engines/opengl_engine/traits/camera_trait.cpp`
|
||||||
|
> Namespace: `omath::opengl_engine`
|
||||||
|
> Purpose: provide OpenGL-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`CameraTrait` exposes three `static` functions:
|
||||||
|
|
||||||
|
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.y)` and **yaw** as `-atan2(dir.x, -dir.z)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||||
|
* `calc_view_matrix(angles, origin)` – delegates to OpenGL formulas `opengl_engine::calc_view_matrix`, producing a `Mat4X4` view matrix (column-major) for the given angles and origin.
|
||||||
|
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||||
|
|
||||||
|
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the OpenGL math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::opengl_engine {
|
||||||
|
|
||||||
|
class CameraTrait final {
|
||||||
|
public:
|
||||||
|
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||||
|
static ViewAngles
|
||||||
|
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||||
|
const Vector3<float>& look_at) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix for given angles and origin (column-major).
|
||||||
|
static Mat4X4
|
||||||
|
calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection from FOV (deg), viewport, near/far (column-major).
|
||||||
|
static Mat4X4
|
||||||
|
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
|
const projection::ViewPort& view_port,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::opengl_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Behavior & conventions
|
||||||
|
|
||||||
|
* **Angles from look-at** (Y-up, -Z forward coordinate system):
|
||||||
|
|
||||||
|
```
|
||||||
|
dir = normalize(look_at - origin)
|
||||||
|
pitch = asin(dir.y) // +Y is up
|
||||||
|
yaw = -atan2(dir.x, -dir.z) // horizontal rotation
|
||||||
|
roll = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||||
|
|
||||||
|
* **View matrix**: built by the OpenGL helper `opengl_engine::calc_view_matrix(angles, origin)` to match OpenGL's right-handed, Y-up, -Z forward conventions. Matrix is **column-major**.
|
||||||
|
|
||||||
|
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix (column-major).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using with `projection::Camera`
|
||||||
|
|
||||||
|
Create a camera whose math is driven by this trait:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4 = Mat4X4; // from OpenGL math headers (column-major)
|
||||||
|
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||||
|
using GLcam = omath::projection::Camera<Mat4, Angs, omath::opengl_engine::CameraTrait>;
|
||||||
|
|
||||||
|
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||||
|
auto fov = omath::projection::FieldOfView::from_degrees(45.f);
|
||||||
|
|
||||||
|
GLcam cam(
|
||||||
|
/*position*/ {5.f, 3.f, 5.f},
|
||||||
|
/*angles*/ omath::opengl_engine::CameraTrait::calc_look_at_angle({5,3,5},{0,0,0}),
|
||||||
|
/*viewport*/ vp,
|
||||||
|
/*fov*/ fov,
|
||||||
|
/*near*/ 0.1f,
|
||||||
|
/*far*/ 100.f
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & tips
|
||||||
|
|
||||||
|
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||||
|
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||||
|
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||||
|
* OpenGL uses **Y-up, -Z forward**: pitch angles control vertical look (positive = up), yaw controls horizontal rotation.
|
||||||
|
* Matrices are **column-major** (no transpose needed for OpenGL shaders).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* OpenGL math helpers in `omath/engines/opengl_engine/formulas.hpp` (view/projection builders used above).
|
||||||
|
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||||
78
docs/engines/opengl_engine/constants.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# `omath::opengl_engine` — types & constants
|
||||||
|
|
||||||
|
> Header: `omath/engines/opengl_engine/constants.hpp`
|
||||||
|
> Namespace: `omath::opengl_engine`
|
||||||
|
> Purpose: define OpenGL coordinate system, matrix types, and angle ranges
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The **OpenGL Engine** uses a **Y-up, right-handed** coordinate system:
|
||||||
|
|
||||||
|
* **Up** = `{0, 1, 0}` (Y-axis)
|
||||||
|
* **Right** = `{1, 0, 0}` (X-axis)
|
||||||
|
* **Forward** = `{0, 0, -1}` (negative Z-axis)
|
||||||
|
|
||||||
|
Matrices are **column-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::opengl_engine {
|
||||||
|
constexpr Vector3<float> k_abs_up = {0, 1, 0};
|
||||||
|
constexpr Vector3<float> k_abs_right = {1, 0, 0};
|
||||||
|
constexpr Vector3<float> k_abs_forward = {0, 0, -1};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These basis vectors define the engine's **world coordinate frame**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Matrix types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4X4 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>;
|
||||||
|
using Mat3X3 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>;
|
||||||
|
using Mat1X3 = Mat<1, 3, float, MatStoreType::COLUMN_MAJOR>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Column-major** storage means columns are contiguous in memory. This matches OpenGL's native matrix layout and shader expectations (GLSL).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Angle types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
|
||||||
|
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
|
||||||
|
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
|
||||||
|
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||||
|
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||||
|
|
||||||
|
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coordinate system notes
|
||||||
|
|
||||||
|
* **Y-up**: gravity points along `-Y`, height increases with `+Y`
|
||||||
|
* **Right-handed**: cross product `right × up = forward` (forward is `-Z`)
|
||||||
|
* **Forward = -Z**: the camera looks down the negative Z-axis (OpenGL convention)
|
||||||
|
* This matches **OpenGL** conventions for 3D graphics pipelines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/opengl_engine/formulas.hpp` — view/projection matrix builders
|
||||||
|
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||||
|
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||||
140
docs/engines/opengl_engine/formulas.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# `omath::opengl_engine` — formulas & matrix helpers
|
||||||
|
|
||||||
|
> Header: `omath/engines/opengl_engine/formulas.hpp`
|
||||||
|
> Namespace: `omath::opengl_engine`
|
||||||
|
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for OpenGL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This header provides **OpenGL**-specific math for:
|
||||||
|
|
||||||
|
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||||
|
* **Rotation matrices** from Euler angles
|
||||||
|
* **View matrices** (camera transforms)
|
||||||
|
* **Perspective projection** matrices
|
||||||
|
|
||||||
|
All functions respect OpenGL's **Y-up, right-handed** coordinate system with **forward = -Z**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::opengl_engine {
|
||||||
|
|
||||||
|
// Compute forward direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute right direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute up direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build 3x3 rotation matrix from angles
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix (camera space transform)
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection matrix
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||||
|
float aspect_ratio,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
|
||||||
|
} // namespace omath::opengl_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Direction vectors
|
||||||
|
|
||||||
|
Given camera angles (pitch/yaw/roll):
|
||||||
|
|
||||||
|
* `forward_vector(angles)` → unit vector pointing where the camera looks (typically `-Z` direction)
|
||||||
|
* `right_vector(angles)` → unit vector pointing to the camera's right (`+X` direction)
|
||||||
|
* `up_vector(angles)` → unit vector pointing upward relative to the camera (`+Y` direction)
|
||||||
|
|
||||||
|
These are used for movement, aim direction, and building coordinate frames.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rotation & view matrices
|
||||||
|
|
||||||
|
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles (column-major)
|
||||||
|
* `calc_view_matrix(angles, origin)` → camera view matrix (column-major)
|
||||||
|
|
||||||
|
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||||
|
|
||||||
|
**Note**: Matrices are **column-major** to match OpenGL/GLSL conventions. No transpose needed when uploading to shaders.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perspective projection
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||||
|
fov_degrees, // vertical field of view (e.g., 45)
|
||||||
|
aspect_ratio, // width / height (e.g., 16/9)
|
||||||
|
near_plane, // e.g., 0.1
|
||||||
|
far_plane // e.g., 100.0
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Produces a **perspective projection matrix** suitable for OpenGL rendering. Combined with the view matrix, this implements the standard camera transform chain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::opengl_engine;
|
||||||
|
|
||||||
|
// Camera setup
|
||||||
|
ViewAngles angles = {
|
||||||
|
PitchAngle::from_degrees(-20.0f),
|
||||||
|
YawAngle::from_degrees(135.0f),
|
||||||
|
RollAngle::from_degrees(0.0f)
|
||||||
|
};
|
||||||
|
Vector3<float> cam_pos{5.0f, 3.0f, 5.0f};
|
||||||
|
|
||||||
|
// Compute direction
|
||||||
|
auto forward = forward_vector(angles);
|
||||||
|
auto right = right_vector(angles);
|
||||||
|
auto up = up_vector(angles);
|
||||||
|
|
||||||
|
// Build matrices (column-major for OpenGL)
|
||||||
|
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||||
|
auto proj_mat = calc_perspective_projection_matrix(45.0f, 16.0f/9.0f, 0.1f, 100.0f);
|
||||||
|
|
||||||
|
// Upload to OpenGL shaders (no transpose needed)
|
||||||
|
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view_mat.data());
|
||||||
|
glUniformMatrix4fv(proj_loc, 1, GL_FALSE, proj_mat.data());
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||||
|
* **Pitch**: positive = looking up, negative = looking down
|
||||||
|
* **Yaw**: increases counter-clockwise from the -Z axis
|
||||||
|
* **Coordinate system**: Y-up, -Z-forward, X-right (right-handed)
|
||||||
|
* **Matrix storage**: column-major (matches OpenGL/GLSL)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/opengl_engine/constants.hpp` — coordinate frame & angle types
|
||||||
|
* `omath/engines/opengl_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||||
|
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||||
199
docs/engines/opengl_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# `omath::opengl_engine::PredEngineTrait` — projectile prediction trait
|
||||||
|
|
||||||
|
> Header: `omath/engines/opengl_engine/traits/pred_engine_trait.hpp`
|
||||||
|
> Namespace: `omath::opengl_engine`
|
||||||
|
> Purpose: provide OpenGL-specific projectile and target prediction for ballistic calculations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||||
|
|
||||||
|
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||||
|
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||||
|
* `calc_vector_2d_distance` – horizontal distance (X/Z plane, ignoring Y)
|
||||||
|
* `get_vector_height_coordinate` – extracts vertical coordinate (Y in OpenGL)
|
||||||
|
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||||
|
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||||
|
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||||
|
|
||||||
|
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::opengl_engine {
|
||||||
|
|
||||||
|
class PredEngineTrait final {
|
||||||
|
public:
|
||||||
|
// Predict projectile position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||||
|
float pitch, float yaw, float time,
|
||||||
|
float gravity) noexcept;
|
||||||
|
|
||||||
|
// Predict target position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_target_position(const projectile_prediction::Target& target,
|
||||||
|
float time, float gravity) noexcept;
|
||||||
|
|
||||||
|
// Compute horizontal (2D) distance
|
||||||
|
static float
|
||||||
|
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||||
|
|
||||||
|
// Get vertical coordinate (Y in OpenGL)
|
||||||
|
static constexpr float
|
||||||
|
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||||
|
|
||||||
|
// Compute aim point from angles
|
||||||
|
static Vector3<float>
|
||||||
|
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||||
|
Vector3<float> predicted_target_position,
|
||||||
|
std::optional<float> projectile_pitch) noexcept;
|
||||||
|
|
||||||
|
// Compute pitch angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
|
||||||
|
// Compute yaw angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::opengl_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projectile prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_projectile_position(
|
||||||
|
projectile, // initial position, speed, gravity scale
|
||||||
|
pitch_deg, // launch pitch (positive = up)
|
||||||
|
yaw_deg, // launch yaw
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant (e.g., 9.81 m/s²)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes:
|
||||||
|
|
||||||
|
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||||
|
2. Initial velocity: `forward * launch_speed`
|
||||||
|
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component only)
|
||||||
|
|
||||||
|
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Target prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_target_position(
|
||||||
|
target, // position, velocity, airborne flag
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple linear extrapolation plus gravity if target is airborne:
|
||||||
|
|
||||||
|
```
|
||||||
|
predicted = origin + velocity * time
|
||||||
|
if (airborne)
|
||||||
|
predicted.y -= 0.5 * gravity * time²
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Distance & height helpers
|
||||||
|
|
||||||
|
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.z²)` (horizontal distance)
|
||||||
|
* `get_vector_height_coordinate(vec)` → `vec.y` (vertical coordinate in OpenGL)
|
||||||
|
|
||||||
|
Used to compute ballistic arc parameters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aim angle calculation
|
||||||
|
|
||||||
|
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `asin(Δy / distance)` converted to degrees (direction normalized first)
|
||||||
|
- Positive = looking up, negative = looking down
|
||||||
|
|
||||||
|
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `-atan2(Δx, -Δz)` converted to degrees (direction normalized first)
|
||||||
|
- Horizontal rotation around Y-axis (accounts for -Z forward convention)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Viewpoint from angles
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||||
|
projectile,
|
||||||
|
predicted_target_pos,
|
||||||
|
optional_pitch_deg
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset. Result has adjusted Y coordinate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Coordinate system**: Y-up, -Z forward (height increases with Y)
|
||||||
|
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
|
||||||
|
* **Gravity**: applied downward along -Y axis
|
||||||
|
* **Pitch convention**: +90° = straight up, -90° = straight down
|
||||||
|
* **Forward direction**: negative Z-axis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::opengl_engine;
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
Projectile proj{
|
||||||
|
.m_origin = {0, 2, 0},
|
||||||
|
.m_launch_speed = 30.0f,
|
||||||
|
.m_gravity_scale = 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
Target tgt{
|
||||||
|
.m_origin = {10, 2, -15},
|
||||||
|
.m_velocity = {0.5f, 0, -1.0f},
|
||||||
|
.m_is_airborne = false
|
||||||
|
};
|
||||||
|
|
||||||
|
float gravity = 9.81f;
|
||||||
|
float time = 0.5f;
|
||||||
|
|
||||||
|
// Predict where target will be
|
||||||
|
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||||
|
|
||||||
|
// Compute aim angles
|
||||||
|
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||||
|
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||||
|
|
||||||
|
// Predict projectile position with those angles
|
||||||
|
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/opengl_engine/formulas.hpp` — direction vectors and matrix builders
|
||||||
|
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||||
|
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||||
|
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||||
113
docs/engines/source_engine/camera_trait.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# `omath::source_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||||
|
|
||||||
|
> Header: `omath/engines/source_engine/traits/camera_trait.hpp` • Impl: `omath/engines/source_engine/traits/camera_trait.cpp`
|
||||||
|
> Namespace: `omath::source_engine`
|
||||||
|
> Purpose: provide Source Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`CameraTrait` exposes three `static` functions:
|
||||||
|
|
||||||
|
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||||
|
* `calc_view_matrix(angles, origin)` – delegates to Source Engine formulas `source_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||||
|
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||||
|
|
||||||
|
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Source Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::source_engine {
|
||||||
|
|
||||||
|
class CameraTrait final {
|
||||||
|
public:
|
||||||
|
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||||
|
static ViewAngles
|
||||||
|
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||||
|
const Vector3<float>& look_at) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix for given angles and origin.
|
||||||
|
static Mat4X4
|
||||||
|
calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||||
|
static Mat4X4
|
||||||
|
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
|
const projection::ViewPort& view_port,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::source_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Behavior & conventions
|
||||||
|
|
||||||
|
* **Angles from look-at** (Z-up coordinate system):
|
||||||
|
|
||||||
|
```
|
||||||
|
dir = normalize(look_at - origin)
|
||||||
|
pitch = asin(dir.z) // +Z is up
|
||||||
|
yaw = atan2(dir.y, dir.x) // horizontal rotation
|
||||||
|
roll = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||||
|
|
||||||
|
* **View matrix**: built by the Source Engine helper `source_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
|
||||||
|
|
||||||
|
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using with `projection::Camera`
|
||||||
|
|
||||||
|
Create a camera whose math is driven by this trait:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4 = Mat4X4; // from Source Engine math headers
|
||||||
|
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||||
|
using SEcam = omath::projection::Camera<Mat4, Angs, omath::source_engine::CameraTrait>;
|
||||||
|
|
||||||
|
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||||
|
auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
|
||||||
|
SEcam cam(
|
||||||
|
/*position*/ {100.f, 50.f, 80.f},
|
||||||
|
/*angles*/ omath::source_engine::CameraTrait::calc_look_at_angle({100,50,80},{0,0,80}),
|
||||||
|
/*viewport*/ vp,
|
||||||
|
/*fov*/ fov,
|
||||||
|
/*near*/ 0.1f,
|
||||||
|
/*far*/ 1000.f
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & tips
|
||||||
|
|
||||||
|
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||||
|
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||||
|
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||||
|
* Source Engine uses **Z-up**: pitch angles control vertical look, positive = up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* [Source Engine Formulas](formulas.md) - View/projection matrix builders
|
||||||
|
* [Source Engine Constants](constants.md) - Engine-specific constants
|
||||||
|
* [Source Engine Pred Engine Trait](pred_engine_trait.md) - Projectile prediction for Source Engine
|
||||||
|
* [Generic Camera Documentation](../../projection/camera.md) - Camera base class
|
||||||
|
* [Getting Started Guide](../../getting_started.md) - Quick start with OMath
|
||||||
|
* [Tutorials - World-to-Screen](../../tutorials.md#tutorial-2-world-to-screen-projection) - Projection tutorial
|
||||||
77
docs/engines/source_engine/constants.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# `omath::source_engine` — types & constants
|
||||||
|
|
||||||
|
> Header: `omath/engines/source_engine/constants.hpp`
|
||||||
|
> Namespace: `omath::source_engine`
|
||||||
|
> Purpose: define Source Engine coordinate system, matrix types, and angle ranges
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The **Source Engine** uses a **Z-up, right-handed** coordinate system:
|
||||||
|
|
||||||
|
* **Up** = `{0, 0, 1}` (Z-axis)
|
||||||
|
* **Right** = `{0, -1, 0}` (negative Y-axis)
|
||||||
|
* **Forward** = `{1, 0, 0}` (X-axis)
|
||||||
|
|
||||||
|
Matrices are **row-major**. Angles are **clamped pitch** (±89°) and **normalized yaw/roll** (±180°).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::source_engine {
|
||||||
|
constexpr Vector3<float> k_abs_up = {0, 0, 1};
|
||||||
|
constexpr Vector3<float> k_abs_right = {0, -1, 0};
|
||||||
|
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These basis vectors define the engine's **world coordinate frame**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Matrix types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Angle types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using PitchAngle = Angle<float, -89.f, 89.f, AngleFlags::Clamped>;
|
||||||
|
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
|
||||||
|
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **PitchAngle**: clamped to **[-89°, +89°]** (looking down vs. up)
|
||||||
|
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||||
|
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||||
|
|
||||||
|
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coordinate system notes
|
||||||
|
|
||||||
|
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
|
||||||
|
* **Right-handed**: cross product `forward × right = up` holds
|
||||||
|
* This matches **Source Engine** (Half-Life 2, TF2, CS:GO, etc.) conventions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/source_engine/formulas.hpp` — view/projection matrix builders
|
||||||
|
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||||
|
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||||
135
docs/engines/source_engine/formulas.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# `omath::source_engine` — formulas & matrix helpers
|
||||||
|
|
||||||
|
> Header: `omath/engines/source_engine/formulas.hpp`
|
||||||
|
> Namespace: `omath::source_engine`
|
||||||
|
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Source Engine
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This header provides **Source Engine**-specific math for:
|
||||||
|
|
||||||
|
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||||
|
* **Rotation matrices** from Euler angles
|
||||||
|
* **View matrices** (camera transforms)
|
||||||
|
* **Perspective projection** matrices
|
||||||
|
|
||||||
|
All functions respect Source Engine's **Z-up, right-handed** coordinate system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::source_engine {
|
||||||
|
|
||||||
|
// Compute forward direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute right direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute up direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build 3x3 rotation matrix from angles
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix (camera space transform)
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection matrix
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||||
|
float aspect_ratio,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
|
||||||
|
} // namespace omath::source_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Direction vectors
|
||||||
|
|
||||||
|
Given camera angles (pitch/yaw/roll):
|
||||||
|
|
||||||
|
* `forward_vector(angles)` → unit vector pointing where the camera looks
|
||||||
|
* `right_vector(angles)` → unit vector pointing to the camera's right
|
||||||
|
* `up_vector(angles)` → unit vector pointing upward relative to the camera
|
||||||
|
|
||||||
|
These are used for movement, aim direction, and building coordinate frames.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rotation & view matrices
|
||||||
|
|
||||||
|
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
|
||||||
|
* `calc_view_matrix(angles, origin)` → camera view matrix
|
||||||
|
|
||||||
|
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perspective projection
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||||
|
fov_degrees, // vertical field of view (e.g., 90)
|
||||||
|
aspect_ratio, // width / height (e.g., 16/9)
|
||||||
|
near_plane, // e.g., 0.1
|
||||||
|
far_plane // e.g., 1000.0
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
|
||||||
|
// Camera setup
|
||||||
|
ViewAngles angles = {
|
||||||
|
PitchAngle::from_degrees(-15.0f),
|
||||||
|
YawAngle::from_degrees(45.0f),
|
||||||
|
RollAngle::from_degrees(0.0f)
|
||||||
|
};
|
||||||
|
Vector3<float> cam_pos{100.0f, 50.0f, 80.0f};
|
||||||
|
|
||||||
|
// Compute direction
|
||||||
|
auto forward = forward_vector(angles);
|
||||||
|
auto right = right_vector(angles);
|
||||||
|
auto up = up_vector(angles);
|
||||||
|
|
||||||
|
// Build matrices
|
||||||
|
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||||
|
auto proj_mat = calc_perspective_projection_matrix(90.0f, 16.0f/9.0f, 0.1f, 1000.0f);
|
||||||
|
|
||||||
|
// Use view_mat and proj_mat for rendering...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||||
|
* **Pitch**: positive = looking up, negative = looking down
|
||||||
|
* **Yaw**: increases counter-clockwise from the +X axis
|
||||||
|
* **Coordinate system**: Z-up, X-forward, Y-right (negative in code convention)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/source_engine/constants.hpp` — coordinate frame & angle types
|
||||||
|
* `omath/engines/source_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||||
|
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||||
198
docs/engines/source_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# `omath::source_engine::PredEngineTrait` — projectile prediction trait
|
||||||
|
|
||||||
|
> Header: `omath/engines/source_engine/traits/pred_engine_trait.hpp`
|
||||||
|
> Namespace: `omath::source_engine`
|
||||||
|
> Purpose: provide Source Engine-specific projectile and target prediction for ballistic calculations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||||
|
|
||||||
|
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||||
|
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||||
|
* `calc_vector_2d_distance` – horizontal distance (X/Y plane, ignoring Z)
|
||||||
|
* `get_vector_height_coordinate` – extracts vertical coordinate (Z in Source Engine)
|
||||||
|
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||||
|
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||||
|
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||||
|
|
||||||
|
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::source_engine {
|
||||||
|
|
||||||
|
class PredEngineTrait final {
|
||||||
|
public:
|
||||||
|
// Predict projectile position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||||
|
float pitch, float yaw, float time,
|
||||||
|
float gravity) noexcept;
|
||||||
|
|
||||||
|
// Predict target position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_target_position(const projectile_prediction::Target& target,
|
||||||
|
float time, float gravity) noexcept;
|
||||||
|
|
||||||
|
// Compute horizontal (2D) distance
|
||||||
|
static float
|
||||||
|
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||||
|
|
||||||
|
// Get vertical coordinate (Z in Source Engine)
|
||||||
|
static constexpr float
|
||||||
|
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||||
|
|
||||||
|
// Compute aim point from angles
|
||||||
|
static Vector3<float>
|
||||||
|
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||||
|
Vector3<float> predicted_target_position,
|
||||||
|
std::optional<float> projectile_pitch) noexcept;
|
||||||
|
|
||||||
|
// Compute pitch angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
|
||||||
|
// Compute yaw angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::source_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projectile prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_projectile_position(
|
||||||
|
projectile, // initial position, speed, gravity scale
|
||||||
|
pitch_deg, // launch pitch (positive = up)
|
||||||
|
yaw_deg, // launch yaw
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant (e.g., 800 units/s²)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes:
|
||||||
|
|
||||||
|
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||||
|
2. Initial velocity: `forward * launch_speed`
|
||||||
|
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Z component only)
|
||||||
|
|
||||||
|
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Target prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_target_position(
|
||||||
|
target, // position, velocity, airborne flag
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple linear extrapolation plus gravity if target is airborne:
|
||||||
|
|
||||||
|
```
|
||||||
|
predicted = origin + velocity * time
|
||||||
|
if (airborne)
|
||||||
|
predicted.z -= 0.5 * gravity * time²
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Distance & height helpers
|
||||||
|
|
||||||
|
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.y²)` (horizontal distance)
|
||||||
|
* `get_vector_height_coordinate(vec)` → `vec.z` (vertical coordinate in Source Engine)
|
||||||
|
|
||||||
|
Used to compute ballistic arc parameters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aim angle calculation
|
||||||
|
|
||||||
|
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `asin(Δz / distance)` converted to degrees
|
||||||
|
- Positive = looking up, negative = looking down
|
||||||
|
|
||||||
|
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `atan2(Δy, Δx)` converted to degrees
|
||||||
|
- Horizontal rotation around Z-axis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Viewpoint from angles
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||||
|
projectile,
|
||||||
|
predicted_target_pos,
|
||||||
|
optional_pitch_deg
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Coordinate system**: Z-up (height increases with Z)
|
||||||
|
* **Angles**: pitch in [-89°, +89°], yaw in [-180°, +180°]
|
||||||
|
* **Gravity**: applied downward along -Z axis
|
||||||
|
* **Pitch convention**: +89° = straight up, -89° = straight down
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
Projectile proj{
|
||||||
|
.m_origin = {0, 0, 100},
|
||||||
|
.m_launch_speed = 1000.0f,
|
||||||
|
.m_gravity_scale = 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
Target tgt{
|
||||||
|
.m_origin = {500, 200, 100},
|
||||||
|
.m_velocity = {10, 5, 0},
|
||||||
|
.m_is_airborne = false
|
||||||
|
};
|
||||||
|
|
||||||
|
float gravity = 800.0f;
|
||||||
|
float time = 0.5f;
|
||||||
|
|
||||||
|
// Predict where target will be
|
||||||
|
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||||
|
|
||||||
|
// Compute aim angles
|
||||||
|
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||||
|
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||||
|
|
||||||
|
// Predict projectile position with those angles
|
||||||
|
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/source_engine/formulas.hpp` — direction vectors and matrix builders
|
||||||
|
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||||
|
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||||
|
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||||
109
docs/engines/unity_engine/camera_trait.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# `omath::unity_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||||
|
|
||||||
|
> Header: `omath/engines/unity_engine/traits/camera_trait.hpp` • Impl: `omath/engines/unity_engine/traits/camera_trait.cpp`
|
||||||
|
> Namespace: `omath::unity_engine`
|
||||||
|
> Purpose: provide Unity Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`CameraTrait` exposes three `static` functions:
|
||||||
|
|
||||||
|
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.y)` and **yaw** as `atan2(dir.x, dir.z)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||||
|
* `calc_view_matrix(angles, origin)` – delegates to Unity Engine formulas `unity_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||||
|
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||||
|
|
||||||
|
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Unity Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::unity_engine {
|
||||||
|
|
||||||
|
class CameraTrait final {
|
||||||
|
public:
|
||||||
|
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||||
|
static ViewAngles
|
||||||
|
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||||
|
const Vector3<float>& look_at) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix for given angles and origin.
|
||||||
|
static Mat4X4
|
||||||
|
calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||||
|
static Mat4X4
|
||||||
|
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
|
const projection::ViewPort& view_port,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::unity_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Behavior & conventions
|
||||||
|
|
||||||
|
* **Angles from look-at** (Y-up coordinate system):
|
||||||
|
|
||||||
|
```
|
||||||
|
dir = normalize(look_at - origin)
|
||||||
|
pitch = asin(dir.y) // +Y is up
|
||||||
|
yaw = atan2(dir.x, dir.z) // horizontal rotation
|
||||||
|
roll = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||||
|
|
||||||
|
* **View matrix**: built by the Unity Engine helper `unity_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
|
||||||
|
|
||||||
|
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using with `projection::Camera`
|
||||||
|
|
||||||
|
Create a camera whose math is driven by this trait:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4 = Mat4X4; // from Unity Engine math headers
|
||||||
|
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||||
|
using UEcam = omath::projection::Camera<Mat4, Angs, omath::unity_engine::CameraTrait>;
|
||||||
|
|
||||||
|
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||||
|
auto fov = omath::projection::FieldOfView::from_degrees(60.f);
|
||||||
|
|
||||||
|
UEcam cam(
|
||||||
|
/*position*/ {10.f, 5.f, -10.f},
|
||||||
|
/*angles*/ omath::unity_engine::CameraTrait::calc_look_at_angle({10,5,-10},{0,5,0}),
|
||||||
|
/*viewport*/ vp,
|
||||||
|
/*fov*/ fov,
|
||||||
|
/*near*/ 0.3f,
|
||||||
|
/*far*/ 1000.f
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & tips
|
||||||
|
|
||||||
|
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||||
|
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||||
|
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||||
|
* Unity Engine uses **Y-up**: pitch angles control vertical look, positive = up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* Unity Engine math helpers in `omath/engines/unity_engine/formulas.hpp` (view/projection builders used above).
|
||||||
|
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||||
77
docs/engines/unity_engine/constants.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# `omath::unity_engine` — types & constants
|
||||||
|
|
||||||
|
> Header: `omath/engines/unity_engine/constants.hpp`
|
||||||
|
> Namespace: `omath::unity_engine`
|
||||||
|
> Purpose: define Unity Engine coordinate system, matrix types, and angle ranges
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The **Unity Engine** uses a **Y-up, left-handed** coordinate system:
|
||||||
|
|
||||||
|
* **Up** = `{0, 1, 0}` (Y-axis)
|
||||||
|
* **Right** = `{1, 0, 0}` (X-axis)
|
||||||
|
* **Forward** = `{0, 0, 1}` (Z-axis)
|
||||||
|
|
||||||
|
Matrices are **row-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::unity_engine {
|
||||||
|
constexpr Vector3<float> k_abs_up = {0, 1, 0};
|
||||||
|
constexpr Vector3<float> k_abs_right = {1, 0, 0};
|
||||||
|
constexpr Vector3<float> k_abs_forward = {0, 0, 1};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These basis vectors define the engine's **world coordinate frame**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Matrix types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Angle types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
|
||||||
|
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
|
||||||
|
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
|
||||||
|
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||||
|
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||||
|
|
||||||
|
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coordinate system notes
|
||||||
|
|
||||||
|
* **Y-up**: gravity points along `-Y`, height increases with `+Y`
|
||||||
|
* **Left-handed**: cross product `forward × right = up` with left-hand rule
|
||||||
|
* This matches **Unity Engine** conventions for 3D games and simulations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/unity_engine/formulas.hpp` — view/projection matrix builders
|
||||||
|
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||||
|
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||||
135
docs/engines/unity_engine/formulas.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# `omath::unity_engine` — formulas & matrix helpers
|
||||||
|
|
||||||
|
> Header: `omath/engines/unity_engine/formulas.hpp`
|
||||||
|
> Namespace: `omath::unity_engine`
|
||||||
|
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Unity Engine
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This header provides **Unity Engine**-specific math for:
|
||||||
|
|
||||||
|
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||||
|
* **Rotation matrices** from Euler angles
|
||||||
|
* **View matrices** (camera transforms)
|
||||||
|
* **Perspective projection** matrices
|
||||||
|
|
||||||
|
All functions respect Unity Engine's **Y-up, left-handed** coordinate system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::unity_engine {
|
||||||
|
|
||||||
|
// Compute forward direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute right direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute up direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build 3x3 rotation matrix from angles
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix (camera space transform)
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection matrix
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||||
|
float aspect_ratio,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
|
||||||
|
} // namespace omath::unity_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Direction vectors
|
||||||
|
|
||||||
|
Given camera angles (pitch/yaw/roll):
|
||||||
|
|
||||||
|
* `forward_vector(angles)` → unit vector pointing where the camera looks
|
||||||
|
* `right_vector(angles)` → unit vector pointing to the camera's right
|
||||||
|
* `up_vector(angles)` → unit vector pointing upward relative to the camera
|
||||||
|
|
||||||
|
These are used for movement, aim direction, and building coordinate frames.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rotation & view matrices
|
||||||
|
|
||||||
|
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
|
||||||
|
* `calc_view_matrix(angles, origin)` → camera view matrix
|
||||||
|
|
||||||
|
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perspective projection
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||||
|
fov_degrees, // vertical field of view (e.g., 60)
|
||||||
|
aspect_ratio, // width / height (e.g., 16/9)
|
||||||
|
near_plane, // e.g., 0.3
|
||||||
|
far_plane // e.g., 1000.0
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::unity_engine;
|
||||||
|
|
||||||
|
// Camera setup
|
||||||
|
ViewAngles angles = {
|
||||||
|
PitchAngle::from_degrees(-15.0f),
|
||||||
|
YawAngle::from_degrees(45.0f),
|
||||||
|
RollAngle::from_degrees(0.0f)
|
||||||
|
};
|
||||||
|
Vector3<float> cam_pos{10.0f, 5.0f, -10.0f};
|
||||||
|
|
||||||
|
// Compute direction
|
||||||
|
auto forward = forward_vector(angles);
|
||||||
|
auto right = right_vector(angles);
|
||||||
|
auto up = up_vector(angles);
|
||||||
|
|
||||||
|
// Build matrices
|
||||||
|
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||||
|
auto proj_mat = calc_perspective_projection_matrix(60.0f, 16.0f/9.0f, 0.3f, 1000.0f);
|
||||||
|
|
||||||
|
// Use view_mat and proj_mat for rendering...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||||
|
* **Pitch**: positive = looking up, negative = looking down
|
||||||
|
* **Yaw**: increases counter-clockwise from the +Z axis
|
||||||
|
* **Coordinate system**: Y-up, Z-forward, X-right (left-handed)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/unity_engine/constants.hpp` — coordinate frame & angle types
|
||||||
|
* `omath/engines/unity_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||||
|
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||||
198
docs/engines/unity_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# `omath::unity_engine::PredEngineTrait` — projectile prediction trait
|
||||||
|
|
||||||
|
> Header: `omath/engines/unity_engine/traits/pred_engine_trait.hpp`
|
||||||
|
> Namespace: `omath::unity_engine`
|
||||||
|
> Purpose: provide Unity Engine-specific projectile and target prediction for ballistic calculations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||||
|
|
||||||
|
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||||
|
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||||
|
* `calc_vector_2d_distance` – horizontal distance (X/Z plane, ignoring Y)
|
||||||
|
* `get_vector_height_coordinate` – extracts vertical coordinate (Y in Unity Engine)
|
||||||
|
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||||
|
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||||
|
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||||
|
|
||||||
|
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::unity_engine {
|
||||||
|
|
||||||
|
class PredEngineTrait final {
|
||||||
|
public:
|
||||||
|
// Predict projectile position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||||
|
float pitch, float yaw, float time,
|
||||||
|
float gravity) noexcept;
|
||||||
|
|
||||||
|
// Predict target position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_target_position(const projectile_prediction::Target& target,
|
||||||
|
float time, float gravity) noexcept;
|
||||||
|
|
||||||
|
// Compute horizontal (2D) distance
|
||||||
|
static float
|
||||||
|
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||||
|
|
||||||
|
// Get vertical coordinate (Y in Unity Engine)
|
||||||
|
static constexpr float
|
||||||
|
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||||
|
|
||||||
|
// Compute aim point from angles
|
||||||
|
static Vector3<float>
|
||||||
|
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||||
|
Vector3<float> predicted_target_position,
|
||||||
|
std::optional<float> projectile_pitch) noexcept;
|
||||||
|
|
||||||
|
// Compute pitch angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
|
||||||
|
// Compute yaw angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::unity_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projectile prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_projectile_position(
|
||||||
|
projectile, // initial position, speed, gravity scale
|
||||||
|
pitch_deg, // launch pitch (positive = up)
|
||||||
|
yaw_deg, // launch yaw
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant (e.g., 9.81 m/s²)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes:
|
||||||
|
|
||||||
|
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||||
|
2. Initial velocity: `forward * launch_speed`
|
||||||
|
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component only)
|
||||||
|
|
||||||
|
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Target prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_target_position(
|
||||||
|
target, // position, velocity, airborne flag
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple linear extrapolation plus gravity if target is airborne:
|
||||||
|
|
||||||
|
```
|
||||||
|
predicted = origin + velocity * time
|
||||||
|
if (airborne)
|
||||||
|
predicted.y -= 0.5 * gravity * time²
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Distance & height helpers
|
||||||
|
|
||||||
|
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.z²)` (horizontal distance)
|
||||||
|
* `get_vector_height_coordinate(vec)` → `vec.y` (vertical coordinate in Unity Engine)
|
||||||
|
|
||||||
|
Used to compute ballistic arc parameters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aim angle calculation
|
||||||
|
|
||||||
|
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `asin(Δy / distance)` converted to degrees (direction normalized first)
|
||||||
|
- Positive = looking up, negative = looking down
|
||||||
|
|
||||||
|
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `atan2(Δx, Δz)` converted to degrees (direction normalized first)
|
||||||
|
- Horizontal rotation around Y-axis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Viewpoint from angles
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||||
|
projectile,
|
||||||
|
predicted_target_pos,
|
||||||
|
optional_pitch_deg
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset. Result has adjusted Y coordinate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Coordinate system**: Y-up (height increases with Y)
|
||||||
|
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
|
||||||
|
* **Gravity**: applied downward along -Y axis
|
||||||
|
* **Pitch convention**: +90° = straight up, -90° = straight down
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::unity_engine;
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
Projectile proj{
|
||||||
|
.m_origin = {0, 2, 0},
|
||||||
|
.m_launch_speed = 50.0f,
|
||||||
|
.m_gravity_scale = 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
Target tgt{
|
||||||
|
.m_origin = {20, 2, 15},
|
||||||
|
.m_velocity = {1, 0, 0.5f},
|
||||||
|
.m_is_airborne = false
|
||||||
|
};
|
||||||
|
|
||||||
|
float gravity = 9.81f;
|
||||||
|
float time = 0.5f;
|
||||||
|
|
||||||
|
// Predict where target will be
|
||||||
|
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||||
|
|
||||||
|
// Compute aim angles
|
||||||
|
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||||
|
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||||
|
|
||||||
|
// Predict projectile position with those angles
|
||||||
|
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/unity_engine/formulas.hpp` — direction vectors and matrix builders
|
||||||
|
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||||
|
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||||
|
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||||
109
docs/engines/unreal_engine/camera_trait.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# `omath::unreal_engine::CameraTrait` — plug-in trait for `projection::Camera`
|
||||||
|
|
||||||
|
> Header: `omath/engines/unreal_engine/traits/camera_trait.hpp` • Impl: `omath/engines/unreal_engine/traits/camera_trait.cpp`
|
||||||
|
> Namespace: `omath::unreal_engine`
|
||||||
|
> Purpose: provide Unreal Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`CameraTrait` exposes three `static` functions:
|
||||||
|
|
||||||
|
* `calc_look_at_angle(origin, look_at)` – computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
|
||||||
|
* `calc_view_matrix(angles, origin)` – delegates to Unreal Engine formulas `unreal_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
|
||||||
|
* `calc_projection_matrix(fov, viewport, near, far)` – builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
|
||||||
|
|
||||||
|
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Unreal Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::unreal_engine {
|
||||||
|
|
||||||
|
class CameraTrait final {
|
||||||
|
public:
|
||||||
|
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
|
||||||
|
static ViewAngles
|
||||||
|
calc_look_at_angle(const Vector3<float>& cam_origin,
|
||||||
|
const Vector3<float>& look_at) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix for given angles and origin.
|
||||||
|
static Mat4X4
|
||||||
|
calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection from FOV (deg), viewport, near/far.
|
||||||
|
static Mat4X4
|
||||||
|
calc_projection_matrix(const projection::FieldOfView& fov,
|
||||||
|
const projection::ViewPort& view_port,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::unreal_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Behavior & conventions
|
||||||
|
|
||||||
|
* **Angles from look-at** (Z-up coordinate system):
|
||||||
|
|
||||||
|
```
|
||||||
|
dir = normalize(look_at - origin)
|
||||||
|
pitch = asin(dir.z) // +Z is up
|
||||||
|
yaw = atan2(dir.y, dir.x) // horizontal rotation
|
||||||
|
roll = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
|
||||||
|
|
||||||
|
* **View matrix**: built by the Unreal Engine helper `unreal_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
|
||||||
|
|
||||||
|
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using with `projection::Camera`
|
||||||
|
|
||||||
|
Create a camera whose math is driven by this trait:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4 = Mat4X4; // from Unreal Engine math headers
|
||||||
|
using Angs = ViewAngles; // pitch/yaw/roll type
|
||||||
|
using UEcam = omath::projection::Camera<Mat4, Angs, omath::unreal_engine::CameraTrait>;
|
||||||
|
|
||||||
|
omath::projection::ViewPort vp{1920.f, 1080.f};
|
||||||
|
auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||||
|
|
||||||
|
UEcam cam(
|
||||||
|
/*position*/ {1000.f, 500.f, 200.f},
|
||||||
|
/*angles*/ omath::unreal_engine::CameraTrait::calc_look_at_angle({1000,500,200},{0,0,200}),
|
||||||
|
/*viewport*/ vp,
|
||||||
|
/*fov*/ fov,
|
||||||
|
/*near*/ 10.f,
|
||||||
|
/*far*/ 100000.f
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & tips
|
||||||
|
|
||||||
|
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
|
||||||
|
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
|
||||||
|
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
|
||||||
|
* Unreal Engine uses **Z-up**: pitch angles control vertical look, positive = up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* Unreal Engine math helpers in `omath/engines/unreal_engine/formulas.hpp` (view/projection builders used above).
|
||||||
|
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).
|
||||||
77
docs/engines/unreal_engine/constants.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# `omath::unreal_engine` — types & constants
|
||||||
|
|
||||||
|
> Header: `omath/engines/unreal_engine/constants.hpp`
|
||||||
|
> Namespace: `omath::unreal_engine`
|
||||||
|
> Purpose: define Unreal Engine coordinate system, matrix types, and angle ranges
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The **Unreal Engine** uses a **Z-up, left-handed** coordinate system:
|
||||||
|
|
||||||
|
* **Up** = `{0, 0, 1}` (Z-axis)
|
||||||
|
* **Right** = `{0, 1, 0}` (Y-axis)
|
||||||
|
* **Forward** = `{1, 0, 0}` (X-axis)
|
||||||
|
|
||||||
|
Matrices are **row-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::unreal_engine {
|
||||||
|
constexpr Vector3<float> k_abs_up = {0, 0, 1};
|
||||||
|
constexpr Vector3<float> k_abs_right = {0, 1, 0};
|
||||||
|
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These basis vectors define the engine's **world coordinate frame**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Matrix types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Angle types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
|
||||||
|
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
|
||||||
|
|
||||||
|
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
|
||||||
|
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
|
||||||
|
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
|
||||||
|
|
||||||
|
`ViewAngles` bundles all three into a single type for camera/view transforms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coordinate system notes
|
||||||
|
|
||||||
|
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
|
||||||
|
* **Left-handed**: cross product `forward × right = up` with left-hand rule
|
||||||
|
* This matches **Unreal Engine** conventions for 3D games and simulations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/unreal_engine/formulas.hpp` — view/projection matrix builders
|
||||||
|
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
|
||||||
|
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper
|
||||||
135
docs/engines/unreal_engine/formulas.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# `omath::unreal_engine` — formulas & matrix helpers
|
||||||
|
|
||||||
|
> Header: `omath/engines/unreal_engine/formulas.hpp`
|
||||||
|
> Namespace: `omath::unreal_engine`
|
||||||
|
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Unreal Engine
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This header provides **Unreal Engine**-specific math for:
|
||||||
|
|
||||||
|
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
|
||||||
|
* **Rotation matrices** from Euler angles
|
||||||
|
* **View matrices** (camera transforms)
|
||||||
|
* **Perspective projection** matrices
|
||||||
|
|
||||||
|
All functions respect Unreal Engine's **Z-up, left-handed** coordinate system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::unreal_engine {
|
||||||
|
|
||||||
|
// Compute forward direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute right direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Compute up direction from pitch/yaw/roll
|
||||||
|
[[nodiscard]]
|
||||||
|
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build 3x3 rotation matrix from angles
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||||
|
|
||||||
|
// Build view matrix (camera space transform)
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_view_matrix(const ViewAngles& angles,
|
||||||
|
const Vector3<float>& cam_origin) noexcept;
|
||||||
|
|
||||||
|
// Build perspective projection matrix
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
|
||||||
|
float aspect_ratio,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
|
||||||
|
} // namespace omath::unreal_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Direction vectors
|
||||||
|
|
||||||
|
Given camera angles (pitch/yaw/roll):
|
||||||
|
|
||||||
|
* `forward_vector(angles)` → unit vector pointing where the camera looks
|
||||||
|
* `right_vector(angles)` → unit vector pointing to the camera's right
|
||||||
|
* `up_vector(angles)` → unit vector pointing upward relative to the camera
|
||||||
|
|
||||||
|
These are used for movement, aim direction, and building coordinate frames.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rotation & view matrices
|
||||||
|
|
||||||
|
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
|
||||||
|
* `calc_view_matrix(angles, origin)` → camera view matrix
|
||||||
|
|
||||||
|
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perspective projection
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat4X4 proj = calc_perspective_projection_matrix(
|
||||||
|
fov_degrees, // vertical field of view (e.g., 90)
|
||||||
|
aspect_ratio, // width / height (e.g., 16/9)
|
||||||
|
near_plane, // e.g., 10.0
|
||||||
|
far_plane // e.g., 100000.0
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::unreal_engine;
|
||||||
|
|
||||||
|
// Camera setup
|
||||||
|
ViewAngles angles = {
|
||||||
|
PitchAngle::from_degrees(-20.0f),
|
||||||
|
YawAngle::from_degrees(45.0f),
|
||||||
|
RollAngle::from_degrees(0.0f)
|
||||||
|
};
|
||||||
|
Vector3<float> cam_pos{1000.0f, 500.0f, 200.0f};
|
||||||
|
|
||||||
|
// Compute direction
|
||||||
|
auto forward = forward_vector(angles);
|
||||||
|
auto right = right_vector(angles);
|
||||||
|
auto up = up_vector(angles);
|
||||||
|
|
||||||
|
// Build matrices
|
||||||
|
auto view_mat = calc_view_matrix(angles, cam_pos);
|
||||||
|
auto proj_mat = calc_perspective_projection_matrix(90.0f, 16.0f/9.0f, 10.0f, 100000.0f);
|
||||||
|
|
||||||
|
// Use view_mat and proj_mat for rendering...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
|
||||||
|
* **Pitch**: positive = looking up, negative = looking down
|
||||||
|
* **Yaw**: increases counter-clockwise from the +X axis
|
||||||
|
* **Coordinate system**: Z-up, X-forward, Y-right (left-handed)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/unreal_engine/constants.hpp` — coordinate frame & angle types
|
||||||
|
* `omath/engines/unreal_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
|
||||||
|
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas
|
||||||
200
docs/engines/unreal_engine/pred_engine_trait.md
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
# `omath::unreal_engine::PredEngineTrait` — projectile prediction trait
|
||||||
|
|
||||||
|
> Header: `omath/engines/unreal_engine/traits/pred_engine_trait.hpp`
|
||||||
|
> Namespace: `omath::unreal_engine`
|
||||||
|
> Purpose: provide Unreal Engine-specific projectile and target prediction for ballistic calculations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
|
||||||
|
|
||||||
|
* `predict_projectile_position` – computes where a projectile will be after `time` seconds
|
||||||
|
* `predict_target_position` – computes where a moving target will be after `time` seconds
|
||||||
|
* `calc_vector_2d_distance` – horizontal distance (X/Y plane, ignoring Z)
|
||||||
|
* `get_vector_height_coordinate` – extracts vertical coordinate (Y in Unreal Engine, note: code uses Z)
|
||||||
|
* `calc_viewpoint_from_angles` – computes aim point given pitch angle
|
||||||
|
* `calc_direct_pitch_angle` – pitch angle to look from origin to target
|
||||||
|
* `calc_direct_yaw_angle` – yaw angle to look from origin to target
|
||||||
|
|
||||||
|
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::unreal_engine {
|
||||||
|
|
||||||
|
class PredEngineTrait final {
|
||||||
|
public:
|
||||||
|
// Predict projectile position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_projectile_position(const projectile_prediction::Projectile& projectile,
|
||||||
|
float pitch, float yaw, float time,
|
||||||
|
float gravity) noexcept;
|
||||||
|
|
||||||
|
// Predict target position after `time` seconds
|
||||||
|
static constexpr Vector3<float>
|
||||||
|
predict_target_position(const projectile_prediction::Target& target,
|
||||||
|
float time, float gravity) noexcept;
|
||||||
|
|
||||||
|
// Compute horizontal (2D) distance
|
||||||
|
static float
|
||||||
|
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
|
||||||
|
|
||||||
|
// Get vertical coordinate (implementation returns Y, but UE is Z-up)
|
||||||
|
static constexpr float
|
||||||
|
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
|
||||||
|
|
||||||
|
// Compute aim point from angles
|
||||||
|
static Vector3<float>
|
||||||
|
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
|
||||||
|
Vector3<float> predicted_target_position,
|
||||||
|
std::optional<float> projectile_pitch) noexcept;
|
||||||
|
|
||||||
|
// Compute pitch angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_pitch_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
|
||||||
|
// Compute yaw angle to look at target
|
||||||
|
static float
|
||||||
|
calc_direct_yaw_angle(const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& view_to) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::unreal_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projectile prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_projectile_position(
|
||||||
|
projectile, // initial position, speed, gravity scale
|
||||||
|
pitch_deg, // launch pitch (positive = up)
|
||||||
|
yaw_deg, // launch yaw
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant (e.g., 980 cm/s²)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes:
|
||||||
|
|
||||||
|
1. Forward vector from pitch/yaw (using `forward_vector`)
|
||||||
|
2. Initial velocity: `forward * launch_speed`
|
||||||
|
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component per implementation, though UE is Z-up)
|
||||||
|
|
||||||
|
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Target prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto pos = PredEngineTrait::predict_target_position(
|
||||||
|
target, // position, velocity, airborne flag
|
||||||
|
time, // time in seconds
|
||||||
|
gravity // gravity constant
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple linear extrapolation plus gravity if target is airborne:
|
||||||
|
|
||||||
|
```
|
||||||
|
predicted = origin + velocity * time
|
||||||
|
if (airborne)
|
||||||
|
predicted.y -= 0.5 * gravity * time² // Note: implementation uses Y
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Distance & height helpers
|
||||||
|
|
||||||
|
* `calc_vector_2d_distance(delta)` → `sqrt(delta.x² + delta.z²)` (horizontal distance in X/Z plane)
|
||||||
|
* `get_vector_height_coordinate(vec)` → `vec.y` (implementation returns Y; UE convention is Z-up)
|
||||||
|
|
||||||
|
Used to compute ballistic arc parameters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aim angle calculation
|
||||||
|
|
||||||
|
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `asin(Δz / distance)` converted to degrees (direction normalized first)
|
||||||
|
- Positive = looking up, negative = looking down
|
||||||
|
|
||||||
|
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
|
||||||
|
- Formula: `atan2(Δy, Δx)` converted to degrees (direction normalized first)
|
||||||
|
- Horizontal rotation around Z-axis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Viewpoint from angles
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
|
||||||
|
projectile,
|
||||||
|
predicted_target_pos,
|
||||||
|
optional_pitch_deg
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **Coordinate system**: Z-up (height increases with Z)
|
||||||
|
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
|
||||||
|
* **Gravity**: applied downward (implementation uses Y component, but UE is Z-up)
|
||||||
|
* **Pitch convention**: +90° = straight up, -90° = straight down
|
||||||
|
|
||||||
|
**Note**: Some implementation details (gravity application to Y coordinate) may need adjustment for full Unreal Engine Z-up consistency.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::unreal_engine;
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
Projectile proj{
|
||||||
|
.m_origin = {0, 0, 200},
|
||||||
|
.m_launch_speed = 5000.0f,
|
||||||
|
.m_gravity_scale = 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
Target tgt{
|
||||||
|
.m_origin = {2000, 1000, 200},
|
||||||
|
.m_velocity = {50, 20, 0},
|
||||||
|
.m_is_airborne = false
|
||||||
|
};
|
||||||
|
|
||||||
|
float gravity = 980.0f; // cm/s² in Unreal units
|
||||||
|
float time = 0.5f;
|
||||||
|
|
||||||
|
// Predict where target will be
|
||||||
|
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
|
||||||
|
|
||||||
|
// Compute aim angles
|
||||||
|
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
|
||||||
|
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
|
||||||
|
|
||||||
|
// Predict projectile position with those angles
|
||||||
|
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath/engines/unreal_engine/formulas.hpp` — direction vectors and matrix builders
|
||||||
|
* `omath/projectile_prediction/projectile.hpp` — `Projectile` struct
|
||||||
|
* `omath/projectile_prediction/target.hpp` — `Target` struct
|
||||||
|
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`
|
||||||
406
docs/faq.md
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
# FAQ
|
||||||
|
|
||||||
|
Common questions and answers about OMath.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## General Questions
|
||||||
|
|
||||||
|
### What is OMath?
|
||||||
|
|
||||||
|
OMath is a modern C++ math library designed for game development, graphics programming, and high-performance computing. It provides:
|
||||||
|
- Vector and matrix operations
|
||||||
|
- 3D projection and camera systems
|
||||||
|
- Projectile prediction
|
||||||
|
- Collision detection
|
||||||
|
- Support for multiple game engines (Source, Unity, Unreal, etc.)
|
||||||
|
- Pattern scanning utilities
|
||||||
|
|
||||||
|
### Why choose OMath over other math libraries?
|
||||||
|
|
||||||
|
- **Modern C++**: Uses C++20/23 features (concepts, `constexpr`, `std::expected`)
|
||||||
|
- **No legacy code**: Built from scratch without legacy baggage
|
||||||
|
- **Game engine support**: Pre-configured for Source, Unity, Unreal, Frostbite, etc.
|
||||||
|
- **Zero dependencies**: No external dependencies needed (except for testing)
|
||||||
|
- **Performance**: AVX2 optimizations available
|
||||||
|
- **Type safety**: Strong typing prevents common errors
|
||||||
|
- **Cross-platform**: Works on Windows, Linux, and macOS
|
||||||
|
|
||||||
|
### Is OMath suitable for production use?
|
||||||
|
|
||||||
|
Yes! OMath is production-ready and used in various projects. It has:
|
||||||
|
- Comprehensive test coverage
|
||||||
|
- Clear error handling
|
||||||
|
- Well-documented API
|
||||||
|
- Active maintenance and community support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation & Setup
|
||||||
|
|
||||||
|
### How do I install OMath?
|
||||||
|
|
||||||
|
Three main methods:
|
||||||
|
|
||||||
|
**vcpkg (recommended):**
|
||||||
|
```bash
|
||||||
|
vcpkg install orange-math
|
||||||
|
```
|
||||||
|
|
||||||
|
**xrepo:**
|
||||||
|
```bash
|
||||||
|
xrepo install omath
|
||||||
|
```
|
||||||
|
|
||||||
|
**From source:**
|
||||||
|
See [Installation Guide](install.md)
|
||||||
|
|
||||||
|
### What are the minimum requirements?
|
||||||
|
|
||||||
|
- **Compiler**: C++20 support required
|
||||||
|
- GCC 10+
|
||||||
|
- Clang 11+
|
||||||
|
- MSVC 2019 16.10+
|
||||||
|
- **CMake**: 3.15+ (if building from source)
|
||||||
|
- **Platform**: Windows, Linux, or macOS
|
||||||
|
|
||||||
|
### Do I need C++23?
|
||||||
|
|
||||||
|
C++23 is **recommended** but not required. Some features like `std::expected` work better with C++23, but fallbacks are available for C++20.
|
||||||
|
|
||||||
|
### Can I use OMath in a C++17 project?
|
||||||
|
|
||||||
|
No, OMath requires C++20 minimum due to use of concepts, `constexpr` enhancements, and other C++20 features.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage Questions
|
||||||
|
|
||||||
|
### How do I include OMath in my project?
|
||||||
|
|
||||||
|
**Full library:**
|
||||||
|
```cpp
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Specific components:**
|
||||||
|
```cpp
|
||||||
|
#include <omath/linear_algebra/vector3.hpp>
|
||||||
|
#include <omath/engines/source_engine/camera.hpp>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Which game engine should I use?
|
||||||
|
|
||||||
|
Choose based on your target game or application:
|
||||||
|
|
||||||
|
| Engine | Use For |
|
||||||
|
|--------|---------|
|
||||||
|
| **Source Engine** | CS:GO, TF2, CS2, Half-Life, Portal, L4D |
|
||||||
|
| **Unity Engine** | Unity games (many indie and mobile games) |
|
||||||
|
| **Unreal Engine** | Fortnite, Unreal games |
|
||||||
|
| **Frostbite** | Battlefield, Star Wars games (EA titles) |
|
||||||
|
| **IW Engine** | Call of Duty series |
|
||||||
|
| **OpenGL** | Custom OpenGL applications, generic 3D |
|
||||||
|
|
||||||
|
### How do I switch between engines?
|
||||||
|
|
||||||
|
Just change the namespace:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Source Engine
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
Camera cam = /* ... */;
|
||||||
|
|
||||||
|
// Unity Engine
|
||||||
|
using namespace omath::unity_engine;
|
||||||
|
Camera cam = /* ... */;
|
||||||
|
```
|
||||||
|
|
||||||
|
Each engine has the same API but different coordinate system handling.
|
||||||
|
|
||||||
|
### What if my game isn't listed?
|
||||||
|
|
||||||
|
Use the **OpenGL engine** as a starting point - it uses canonical OpenGL conventions. You may need to adjust coordinate transformations based on your specific game.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Questions
|
||||||
|
|
||||||
|
### Should I use the AVX2 or Legacy engine?
|
||||||
|
|
||||||
|
**Use AVX2 if:**
|
||||||
|
- Target modern CPUs (2013+)
|
||||||
|
- Need maximum performance
|
||||||
|
- Can accept reduced compatibility
|
||||||
|
|
||||||
|
**Use Legacy if:**
|
||||||
|
- Need broad compatibility
|
||||||
|
- Target older CPUs or ARM
|
||||||
|
- Unsure about target hardware
|
||||||
|
|
||||||
|
The API is identical - just change the class:
|
||||||
|
```cpp
|
||||||
|
// Legacy (compatible)
|
||||||
|
ProjPredEngineLegacy engine;
|
||||||
|
|
||||||
|
// AVX2 (faster)
|
||||||
|
ProjPredEngineAVX2 engine;
|
||||||
|
```
|
||||||
|
|
||||||
|
### How much faster is AVX2?
|
||||||
|
|
||||||
|
Typically 2-4x faster for projectile prediction calculations, depending on the CPU and specific use case.
|
||||||
|
|
||||||
|
### Are vector operations constexpr?
|
||||||
|
|
||||||
|
Yes! Most operations are `constexpr` and can be evaluated at compile-time:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector3<float> v{1, 2, 3};
|
||||||
|
constexpr auto len_sq = v.length_sqr(); // Computed at compile time
|
||||||
|
```
|
||||||
|
|
||||||
|
### Is OMath thread-safe?
|
||||||
|
|
||||||
|
- **Immutable operations** (vector math, etc.) are thread-safe
|
||||||
|
- **Mutable state** (Camera updates) is NOT thread-safe
|
||||||
|
- Use separate instances per thread or synchronize access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### `world_to_screen()` always returns `nullopt`
|
||||||
|
|
||||||
|
Check:
|
||||||
|
1. **Is the point behind the camera?** Points behind the camera cannot be projected.
|
||||||
|
2. **Are near/far planes correct?** Ensure `near < far` and both are positive.
|
||||||
|
3. **Is FOV valid?** FOV should be between 1° and 179°.
|
||||||
|
4. **Are camera angles normalized?** Use engine-provided angle types.
|
||||||
|
|
||||||
|
### Angles are wrapping incorrectly
|
||||||
|
|
||||||
|
Use the correct angle type:
|
||||||
|
```cpp
|
||||||
|
// Good: uses proper angle type
|
||||||
|
PitchAngle pitch = PitchAngle::from_degrees(45.0f);
|
||||||
|
|
||||||
|
// Bad: raw float loses normalization
|
||||||
|
float pitch = 45.0f;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Projection seems mirrored or inverted
|
||||||
|
|
||||||
|
You may be using the wrong engine trait. Each engine has different coordinate conventions:
|
||||||
|
- **Source/Unity**: Z-up
|
||||||
|
- **Unreal**: Z-up, different handedness
|
||||||
|
- **OpenGL**: Y-up
|
||||||
|
|
||||||
|
Ensure you're using the trait matching your game.
|
||||||
|
|
||||||
|
### Pattern scanning finds multiple matches
|
||||||
|
|
||||||
|
This is normal! Patterns may appear multiple times. Solutions:
|
||||||
|
1. Make the pattern more specific (more bytes, fewer wildcards)
|
||||||
|
2. Use additional context (nearby code patterns)
|
||||||
|
3. Verify each match programmatically
|
||||||
|
|
||||||
|
### Projectile prediction returns `nullopt`
|
||||||
|
|
||||||
|
Common reasons:
|
||||||
|
1. **Target too fast**: Target velocity exceeds projectile speed
|
||||||
|
2. **Out of range**: Distance exceeds max flight time
|
||||||
|
3. **Invalid input**: Check projectile speed > 0
|
||||||
|
4. **Gravity too strong**: Projectile can't reach target height
|
||||||
|
|
||||||
|
### Compilation errors about `std::expected`
|
||||||
|
|
||||||
|
If using C++20 (not C++23), you may need a backport library like `tl::expected`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# CMakeLists.txt
|
||||||
|
find_package(tl-expected CONFIG REQUIRED)
|
||||||
|
target_link_libraries(your_target PRIVATE tl::expected)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or upgrade to C++23 if possible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Questions
|
||||||
|
|
||||||
|
### Can I use OMath with DirectX/OpenGL/Vulkan?
|
||||||
|
|
||||||
|
Yes! OMath matrices and vectors work with all graphics APIs. Use:
|
||||||
|
- **OpenGL**: `opengl_engine` traits
|
||||||
|
- **DirectX**: Use appropriate engine trait or OpenGL as base
|
||||||
|
- **Vulkan**: Use OpenGL traits as starting point
|
||||||
|
|
||||||
|
### Does OMath support quaternions?
|
||||||
|
|
||||||
|
Not currently. Quaternion support may be added in future versions. For now, use euler angles (ViewAngles) or convert manually.
|
||||||
|
|
||||||
|
### Can I extend OMath with custom engine traits?
|
||||||
|
|
||||||
|
Yes! Implement the `CameraEngineConcept`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class MyEngineTrait {
|
||||||
|
public:
|
||||||
|
static ViewAngles calc_look_at_angle(
|
||||||
|
const Vector3<float>& origin,
|
||||||
|
const Vector3<float>& target
|
||||||
|
);
|
||||||
|
|
||||||
|
static Mat4X4 calc_view_matrix(
|
||||||
|
const ViewAngles& angles,
|
||||||
|
const Vector3<float>& origin
|
||||||
|
);
|
||||||
|
|
||||||
|
static Mat4X4 calc_projection_matrix(
|
||||||
|
const FieldOfView& fov,
|
||||||
|
const ViewPort& viewport,
|
||||||
|
float near, float far
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use with Camera
|
||||||
|
using MyCamera = Camera<Mat4X4, ViewAngles, MyEngineTrait>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Does OMath support SIMD for vector operations?
|
||||||
|
|
||||||
|
AVX2 support is available for projectile prediction. General vector SIMD may be added in future versions. The library already compiles to efficient code with compiler optimizations enabled.
|
||||||
|
|
||||||
|
### Can I use OMath for machine learning?
|
||||||
|
|
||||||
|
OMath is optimized for game development and graphics, not ML. For machine learning, consider libraries like Eigen or xtensor which are designed for that domain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debugging Questions
|
||||||
|
|
||||||
|
### How do I print vectors?
|
||||||
|
|
||||||
|
OMath provides `std::formatter` support:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Vector3<float> v{1, 2, 3};
|
||||||
|
std::cout << std::format("{}", v) << "\n"; // Prints: [1, 2, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
### How do I visualize projection problems?
|
||||||
|
|
||||||
|
1. Check if `world_to_screen()` succeeds
|
||||||
|
2. Print camera matrices:
|
||||||
|
```cpp
|
||||||
|
auto view = camera.get_view_matrix();
|
||||||
|
auto proj = camera.get_projection_matrix();
|
||||||
|
// Print matrix values
|
||||||
|
```
|
||||||
|
3. Test with known good points (e.g., origin, simple positions)
|
||||||
|
4. Verify viewport and FOV values
|
||||||
|
|
||||||
|
### How can I debug pattern scanning?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
PatternView pattern{"48 8B 05 ?? ?? ?? ??"};
|
||||||
|
|
||||||
|
// Print pattern details
|
||||||
|
std::cout << "Pattern length: " << pattern.size() << "\n";
|
||||||
|
std::cout << "Pattern bytes: ";
|
||||||
|
for (auto byte : pattern) {
|
||||||
|
if (byte.has_value()) {
|
||||||
|
std::cout << std::hex << (int)*byte << " ";
|
||||||
|
} else {
|
||||||
|
std::cout << "?? ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### How can I contribute to OMath?
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](https://github.com/orange-cpp/omath/blob/master/CONTRIBUTING.md) for guidelines. Contributions welcome:
|
||||||
|
- Bug fixes
|
||||||
|
- New features
|
||||||
|
- Documentation improvements
|
||||||
|
- Test coverage
|
||||||
|
- Examples
|
||||||
|
|
||||||
|
### Where do I report bugs?
|
||||||
|
|
||||||
|
[GitHub Issues](https://github.com/orange-cpp/omath/issues)
|
||||||
|
|
||||||
|
Please include:
|
||||||
|
- OMath version
|
||||||
|
- Compiler and version
|
||||||
|
- Minimal reproducible example
|
||||||
|
- Expected vs actual behavior
|
||||||
|
|
||||||
|
### How do I request a feature?
|
||||||
|
|
||||||
|
Open a GitHub issue with:
|
||||||
|
- Use case description
|
||||||
|
- Proposed API (if applicable)
|
||||||
|
- Why existing features don't meet your needs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License & Legal
|
||||||
|
|
||||||
|
### What license does OMath use?
|
||||||
|
|
||||||
|
OMath uses a custom "libomath" license. See [LICENSE](https://github.com/orange-cpp/omath/blob/master/LICENSE) for full details.
|
||||||
|
|
||||||
|
### Can I use OMath in commercial projects?
|
||||||
|
|
||||||
|
Check the LICENSE file for commercial use terms.
|
||||||
|
|
||||||
|
### Can I use OMath for game cheating/hacking?
|
||||||
|
|
||||||
|
OMath is a math library and can be used for various purposes. However:
|
||||||
|
- Using it to cheat in online games may violate game ToS
|
||||||
|
- Creating cheats may be illegal in your jurisdiction
|
||||||
|
- The developers do not condone cheating in online games
|
||||||
|
|
||||||
|
Use responsibly and ethically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
### Where can I get help?
|
||||||
|
|
||||||
|
- **Documentation**: [http://libomath.org](http://libomath.org)
|
||||||
|
- **Discord**: [Join community](https://discord.gg/eDgdaWbqwZ)
|
||||||
|
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
|
||||||
|
- **GitHub Issues**: [Report bugs/ask questions](https://github.com/orange-cpp/omath/issues)
|
||||||
|
|
||||||
|
### Is there a Discord/community?
|
||||||
|
|
||||||
|
Yes! Join our Discord: [https://discord.gg/eDgdaWbqwZ](https://discord.gg/eDgdaWbqwZ)
|
||||||
|
|
||||||
|
### Are there video tutorials?
|
||||||
|
|
||||||
|
Check our [YouTube channel](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ) for demonstrations and tutorials.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Didn't find your answer?
|
||||||
|
|
||||||
|
- Search the [documentation](index.md)
|
||||||
|
- Check [tutorials](tutorials.md)
|
||||||
|
- Ask on [Discord](https://discord.gg/eDgdaWbqwZ)
|
||||||
|
- Open a [GitHub issue](https://github.com/orange-cpp/omath/issues)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
305
docs/getting_started.md
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
# Getting Started
|
||||||
|
Welcome to OMath! This guide will help you get up and running with the library quickly.
|
||||||
|
|
||||||
|
## What is OMath?
|
||||||
|
|
||||||
|
OMath is a modern, blazingly fast C++ math library designed for:
|
||||||
|
- **Game development** and cheat development
|
||||||
|
- **Graphics programming** (DirectX/OpenGL/Vulkan)
|
||||||
|
- **3D applications** with support for multiple game engines
|
||||||
|
- **High-performance computing** with AVX2 optimizations
|
||||||
|
|
||||||
|
Key features:
|
||||||
|
- 100% independent, no legacy C++ code
|
||||||
|
- Fully `constexpr` template-based design
|
||||||
|
- Zero additional dependencies (except for unit tests)
|
||||||
|
- Cross-platform (Windows, macOS, Linux)
|
||||||
|
- Built-in support for Source, Unity, Unreal, Frostbite, IWEngine, and OpenGL coordinate systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Choose one of the following methods to install OMath:
|
||||||
|
|
||||||
|
### Using vcpkg (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vcpkg install orange-math
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in your CMakeLists.txt:
|
||||||
|
```cmake
|
||||||
|
find_package(omath CONFIG REQUIRED)
|
||||||
|
target_link_libraries(your_target PRIVATE omath::omath)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using xrepo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xrepo install omath
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in your xmake.lua:
|
||||||
|
```lua
|
||||||
|
add_requires("omath")
|
||||||
|
target("your_target")
|
||||||
|
add_packages("omath")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building from Source
|
||||||
|
|
||||||
|
See the detailed [Installation Guide](install.md) for complete instructions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Example
|
||||||
|
|
||||||
|
Here's a simple example to get you started:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
// Create 3D vectors
|
||||||
|
Vector3<float> a{1.0f, 2.0f, 3.0f};
|
||||||
|
Vector3<float> b{4.0f, 5.0f, 6.0f};
|
||||||
|
|
||||||
|
// Vector operations
|
||||||
|
auto sum = a + b; // Vector addition
|
||||||
|
auto dot_product = a.dot(b); // Dot product: 32.0
|
||||||
|
auto cross_product = a.cross(b); // Cross product: (-3, 6, -3)
|
||||||
|
auto length = a.length(); // Length: ~3.74
|
||||||
|
auto normalized = a.normalized(); // Unit vector
|
||||||
|
|
||||||
|
std::cout << "Sum: [" << sum.x << ", " << sum.y << ", " << sum.z << "]\n";
|
||||||
|
std::cout << "Dot product: " << dot_product << "\n";
|
||||||
|
std::cout << "Length: " << length << "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
### 1. Vectors
|
||||||
|
|
||||||
|
OMath provides 2D, 3D, and 4D vector types:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
Vector2<float> vec2{1.0f, 2.0f};
|
||||||
|
Vector3<float> vec3{1.0f, 2.0f, 3.0f};
|
||||||
|
Vector4<float> vec4{1.0f, 2.0f, 3.0f, 4.0f};
|
||||||
|
```
|
||||||
|
|
||||||
|
All vector types support:
|
||||||
|
- Arithmetic operations (+, -, *, /)
|
||||||
|
- Dot and cross products (where applicable)
|
||||||
|
- Length and distance calculations
|
||||||
|
- Normalization
|
||||||
|
- Component-wise operations
|
||||||
|
|
||||||
|
See: [Vector2](linear_algebra/vector2.md), [Vector3](linear_algebra/vector3.md), [Vector4](linear_algebra/vector4.md)
|
||||||
|
|
||||||
|
### 2. Matrices
|
||||||
|
|
||||||
|
4x4 matrices for transformations:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
Mat4X4 matrix = Mat4X4::identity();
|
||||||
|
// Use for transformations, projections, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
See: [Matrix Documentation](linear_algebra/mat.md)
|
||||||
|
|
||||||
|
### 3. Angles
|
||||||
|
|
||||||
|
Strong-typed angle system with automatic range management:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
auto angle = Angle<float, 0.0f, 360.0f>::from_degrees(45.0f);
|
||||||
|
auto radians = angle.as_radians();
|
||||||
|
|
||||||
|
// View angles for camera systems
|
||||||
|
ViewAngles view{
|
||||||
|
PitchAngle::from_degrees(-10.0f),
|
||||||
|
YawAngle::from_degrees(90.0f),
|
||||||
|
RollAngle::from_degrees(0.0f)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
See: [Angle](trigonometry/angle.md), [View Angles](trigonometry/view_angles.md)
|
||||||
|
|
||||||
|
### 4. 3D Projection
|
||||||
|
|
||||||
|
Built-in camera and projection systems:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath;
|
||||||
|
using namespace omath::projection;
|
||||||
|
|
||||||
|
ViewPort viewport{1920.0f, 1080.0f};
|
||||||
|
auto fov = FieldOfView::from_degrees(90.0f);
|
||||||
|
|
||||||
|
// Example using Source Engine
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
Camera cam(
|
||||||
|
Vector3<float>{0, 0, 100}, // Position
|
||||||
|
ViewAngles{}, // Angles
|
||||||
|
viewport,
|
||||||
|
fov,
|
||||||
|
0.1f, // near plane
|
||||||
|
1000.0f // far plane
|
||||||
|
);
|
||||||
|
|
||||||
|
// Project 3D point to 2D screen
|
||||||
|
Vector3<float> world_pos{100, 50, 75};
|
||||||
|
if (auto screen_pos = cam.world_to_screen(world_pos)) {
|
||||||
|
std::cout << "Screen: " << screen_pos->x << ", " << screen_pos->y << "\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See: [Camera](projection/camera.md)
|
||||||
|
|
||||||
|
### 5. Game Engine Support
|
||||||
|
|
||||||
|
OMath provides pre-configured traits for major game engines:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Source Engine
|
||||||
|
#include <omath/engines/source_engine/camera.hpp>
|
||||||
|
using SourceCamera = omath::source_engine::Camera;
|
||||||
|
|
||||||
|
// Unity Engine
|
||||||
|
#include <omath/engines/unity_engine/camera.hpp>
|
||||||
|
using UnityCamera = omath::unity_engine::Camera;
|
||||||
|
|
||||||
|
// Unreal Engine
|
||||||
|
#include <omath/engines/unreal_engine/camera.hpp>
|
||||||
|
using UnrealCamera = omath::unreal_engine::Camera;
|
||||||
|
|
||||||
|
// And more: OpenGL, Frostbite, IWEngine
|
||||||
|
```
|
||||||
|
|
||||||
|
Each engine has its own coordinate system conventions automatically handled.
|
||||||
|
|
||||||
|
See: Engine-specific docs in [engines/](engines/) folder
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### World-to-Screen Projection
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath;
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
|
||||||
|
Camera cam = /* initialize camera */;
|
||||||
|
Vector3<float> enemy_position{100, 200, 50};
|
||||||
|
|
||||||
|
if (auto screen = cam.world_to_screen(enemy_position)) {
|
||||||
|
// Draw ESP box at screen->x, screen->y
|
||||||
|
std::cout << "Enemy on screen at: " << screen->x << ", " << screen->y << "\n";
|
||||||
|
} else {
|
||||||
|
// Enemy not visible (behind camera or outside frustum)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Projectile Prediction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
Projectile bullet{
|
||||||
|
Vector3<float>{0, 0, 0}, // shooter position
|
||||||
|
1000.0f, // muzzle velocity (m/s)
|
||||||
|
Vector3<float>{0, 0, -9.81f} // gravity
|
||||||
|
};
|
||||||
|
|
||||||
|
Target enemy{
|
||||||
|
Vector3<float>{100, 200, 50}, // position
|
||||||
|
Vector3<float>{10, 0, 0} // velocity
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate where to aim
|
||||||
|
ProjPredEngineLegacy engine;
|
||||||
|
if (auto aim_point = engine.maybe_calculate_aim_point(bullet, enemy)) {
|
||||||
|
// Aim at *aim_point to hit moving target
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See: [Projectile Prediction](projectile_prediction/projectile_engine.md)
|
||||||
|
|
||||||
|
### Collision Detection
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
// Ray-plane intersection
|
||||||
|
Plane ground{
|
||||||
|
Vector3<float>{0, 0, 0}, // point on plane
|
||||||
|
Vector3<float>{0, 0, 1} // normal (pointing up)
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3<float> ray_origin{0, 0, 100};
|
||||||
|
Vector3<float> ray_direction{0, 0, -1};
|
||||||
|
|
||||||
|
if (auto hit = ground.intersects_ray(ray_origin, ray_direction)) {
|
||||||
|
std::cout << "Hit ground at: " << hit->x << ", " << hit->y << ", " << hit->z << "\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See: [Collision Detection](collision/line_tracer.md)
|
||||||
|
|
||||||
|
### Pattern Scanning
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/utility/pattern_scan.hpp>
|
||||||
|
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
std::vector<uint8_t> memory = /* ... */;
|
||||||
|
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
|
||||||
|
|
||||||
|
if (auto result = pattern_scan(memory, pattern)) {
|
||||||
|
std::cout << "Pattern found at offset: " << result->offset << "\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See: [Pattern Scanning](utility/pattern_scan.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that you have the basics, explore these topics:
|
||||||
|
|
||||||
|
1. **[API Reference](index.md)** - Complete API documentation
|
||||||
|
2. **[Examples](../examples/)** - Working code examples
|
||||||
|
3. **[Engine-Specific Features](engines/)** - Deep dive into game engine support
|
||||||
|
4. **[Advanced Topics](#)** - Performance optimization, custom traits, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
- **Documentation**: [http://libomath.org](http://libomath.org)
|
||||||
|
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
|
||||||
|
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
|
||||||
|
- **Issues**: [GitHub Issues](https://github.com/orange-cpp/omath/issues)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 454 KiB After Width: | Height: | Size: 454 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 644 KiB After Width: | Height: | Size: 644 KiB |
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 324 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
246
docs/index.md
@@ -1,17 +1,239 @@
|
|||||||
# Welcome to MkDocs
|
<div class="center-text">
|
||||||
|
<!-- Banner -->
|
||||||
|
<p>
|
||||||
|
<img src="images/logos/omath_logo_macro.png" alt="omath banner">
|
||||||
|
</p>
|
||||||
|
|
||||||
For full documentation visit [mkdocs.org](https://www.mkdocs.org).
|
<!-- Badges -->
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/license-libomath-orange" alt="license: libomath">
|
||||||
|
<img src="https://img.shields.io/github/contributors/orange-cpp/omath" alt="GitHub contributors">
|
||||||
|
<img src="https://img.shields.io/github/languages/top/orange-cpp/omath" alt="Top language">
|
||||||
|
<a href="https://www.codefactor.io/repository/github/orange-cpp/omath">
|
||||||
|
<img src="https://www.codefactor.io/repository/github/orange-cpp/omath/badge" alt="CodeFactor">
|
||||||
|
</a>
|
||||||
|
<img src="https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml" alt="GitHub Actions Workflow Status">
|
||||||
|
<a href="https://repology.org/project/orange-math/versions">
|
||||||
|
<img src="https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg" alt="Vcpkg package">
|
||||||
|
</a>
|
||||||
|
<img src="https://img.shields.io/github/forks/orange-cpp/omath" alt="GitHub forks">
|
||||||
|
<a href="https://discord.gg/eDgdaWbqwZ">
|
||||||
|
<img src="https://dcbadge.limes.pink/api/server/https://discord.gg/eDgdaWbqwZ?style=flat" alt="Join us on Discord">
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/orangennotes">
|
||||||
|
<img src="https://img.shields.io/badge/Telegram-2CA5E0?style=flat-squeare&logo=telegram&logoColor=white" alt="Telegram">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
## Commands
|
OMath is a 100% independent, constexpr template blazingly fast math library that doesn't have legacy C++ code.
|
||||||
|
|
||||||
* `mkdocs new [dir-name]` - Create a new project.
|
It provides the latest features, is highly customizable, has all for cheat development, DirectX/OpenGL/Vulkan support, premade support for different game engines, much more constexpr stuff than in other libraries and more...
|
||||||
* `mkdocs serve` - Start the live-reloading docs server.
|
|
||||||
* `mkdocs build` - Build the documentation site.
|
|
||||||
* `mkdocs -h` - Print help message and exit.
|
|
||||||
|
|
||||||
## Project layout
|
---
|
||||||
|
|
||||||
mkdocs.yml # The configuration file.
|
## 🚀 Quick Start
|
||||||
docs/
|
|
||||||
index.md # The documentation homepage.
|
**New to OMath?** Start here:
|
||||||
... # Other markdown pages, images and other files.
|
|
||||||
|
- **[Getting Started Guide](getting_started.md)** - Installation and first steps
|
||||||
|
- **[API Overview](api_overview.md)** - High-level API reference
|
||||||
|
- **[Installation Instructions](install.md)** - Detailed setup guide
|
||||||
|
|
||||||
|
**Quick example:**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
Vector3<float> a{1, 2, 3};
|
||||||
|
Vector3<float> b{4, 5, 6};
|
||||||
|
|
||||||
|
auto dot = a.dot(b); // 32.0
|
||||||
|
auto cross = a.cross(b); // (-3, 6, -3)
|
||||||
|
auto distance = a.distance_to(b); // ~5.196
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation Structure
|
||||||
|
|
||||||
|
### Core Mathematics
|
||||||
|
|
||||||
|
**Linear Algebra**
|
||||||
|
- [Vector2](linear_algebra/vector2.md) - 2D vectors with full operator support
|
||||||
|
- [Vector3](linear_algebra/vector3.md) - 3D vectors, dot/cross products, angles
|
||||||
|
- [Vector4](linear_algebra/vector4.md) - 4D vectors (homogeneous coordinates)
|
||||||
|
- [Mat4X4](linear_algebra/mat.md) - 4×4 matrices for transformations
|
||||||
|
- [Triangle](linear_algebra/triangle.md) - Triangle primitive and utilities
|
||||||
|
|
||||||
|
**Trigonometry**
|
||||||
|
- [Angle](trigonometry/angle.md) - Strong-typed angle system with range enforcement
|
||||||
|
- [Angles](trigonometry/angles.md) - Angle utilities and conversions
|
||||||
|
- [View Angles](trigonometry/view_angles.md) - Pitch/Yaw/Roll for camera systems
|
||||||
|
|
||||||
|
**3D Primitives**
|
||||||
|
- [Box](3d_primitives/box.md) - Axis-aligned bounding boxes
|
||||||
|
- [Plane](3d_primitives/plane.md) - Infinite planes and intersections
|
||||||
|
|
||||||
|
### Game Development Features
|
||||||
|
|
||||||
|
**Projection & Camera**
|
||||||
|
- [Camera](projection/camera.md) - Generic camera system with engine traits
|
||||||
|
- [Error Codes](projection/error_codes.md) - Projection error handling
|
||||||
|
|
||||||
|
**Collision Detection**
|
||||||
|
- [Line Tracer](collision/line_tracer.md) - Ray-triangle, ray-plane intersections
|
||||||
|
|
||||||
|
**Projectile Prediction**
|
||||||
|
- [Projectile Engine Interface](projectile_prediction/projectile_engine.md) - Base interface
|
||||||
|
- [Projectile](projectile_prediction/projectile.md) - Projectile properties
|
||||||
|
- [Target](projectile_prediction/target.md) - Target state representation
|
||||||
|
- [Legacy Engine](projectile_prediction/proj_pred_engine_legacy.md) - Standard implementation
|
||||||
|
- [AVX2 Engine](projectile_prediction/proj_pred_engine_avx2.md) - Optimized implementation
|
||||||
|
|
||||||
|
**Pathfinding**
|
||||||
|
- [A* Algorithm](pathfinding/a_star.md) - A* pathfinding implementation
|
||||||
|
- [Navigation Mesh](pathfinding/navigation_mesh.md) - Triangle-based navigation
|
||||||
|
|
||||||
|
### Game Engine Support
|
||||||
|
|
||||||
|
OMath provides built-in support for multiple game engines with proper coordinate system handling:
|
||||||
|
|
||||||
|
**Source Engine** (Valve - CS:GO, TF2, etc.)
|
||||||
|
- [Camera Trait](engines/source_engine/camera_trait.md)
|
||||||
|
- [Pred Engine Trait](engines/source_engine/pred_engine_trait.md)
|
||||||
|
- [Constants](engines/source_engine/constants.md)
|
||||||
|
- [Formulas](engines/source_engine/formulas.md)
|
||||||
|
|
||||||
|
**Unity Engine**
|
||||||
|
- [Camera Trait](engines/unity_engine/camera_trait.md)
|
||||||
|
- [Pred Engine Trait](engines/unity_engine/pred_engine_trait.md)
|
||||||
|
- [Constants](engines/unity_engine/constants.md)
|
||||||
|
- [Formulas](engines/unity_engine/formulas.md)
|
||||||
|
|
||||||
|
**Unreal Engine** (Epic Games)
|
||||||
|
- [Camera Trait](engines/unreal_engine/camera_trait.md)
|
||||||
|
- [Pred Engine Trait](engines/unreal_engine/pred_engine_trait.md)
|
||||||
|
- [Constants](engines/unreal_engine/constants.md)
|
||||||
|
- [Formulas](engines/unreal_engine/formulas.md)
|
||||||
|
|
||||||
|
**Frostbite Engine** (EA - Battlefield, etc.)
|
||||||
|
- [Camera Trait](engines/frostbite/camera_trait.md)
|
||||||
|
- [Pred Engine Trait](engines/frostbite/pred_engine_trait.md)
|
||||||
|
- [Constants](engines/frostbite/constants.md)
|
||||||
|
- [Formulas](engines/frostbite/formulas.md)
|
||||||
|
|
||||||
|
**IW Engine** (Infinity Ward - Call of Duty)
|
||||||
|
- [Camera Trait](engines/iw_engine/camera_trait.md)
|
||||||
|
- [Pred Engine Trait](engines/iw_engine/pred_engine_trait.md)
|
||||||
|
- [Constants](engines/iw_engine/constants.md)
|
||||||
|
- [Formulas](engines/iw_engine/formulas.md)
|
||||||
|
|
||||||
|
**OpenGL Engine** (Canonical OpenGL)
|
||||||
|
- [Camera Trait](engines/opengl_engine/camera_trait.md)
|
||||||
|
- [Pred Engine Trait](engines/opengl_engine/pred_engine_trait.md)
|
||||||
|
- [Constants](engines/opengl_engine/constants.md)
|
||||||
|
- [Formulas](engines/opengl_engine/formulas.md)
|
||||||
|
|
||||||
|
### Utilities
|
||||||
|
|
||||||
|
**Color**
|
||||||
|
- [Color](utility/color.md) - RGBA color with conversions
|
||||||
|
|
||||||
|
**Pattern Scanning & Memory**
|
||||||
|
- [Pattern Scan](utility/pattern_scan.md) - Binary pattern search with wildcards
|
||||||
|
- [PE Pattern Scan](utility/pe_pattern_scan.md) - PE file pattern scanning
|
||||||
|
|
||||||
|
**Reverse Engineering**
|
||||||
|
- [External Rev Object](rev_eng/external_rev_object.md) - External process memory access
|
||||||
|
- [Internal Rev Object](rev_eng/internal_rev_object.md) - Internal memory access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Key Features
|
||||||
|
|
||||||
|
- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
|
||||||
|
- **Versatility**: Includes a wide array of mathematical functions and algorithms.
|
||||||
|
- **Ease of Use**: Simplified interface for convenient integration into various projects.
|
||||||
|
- **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
|
||||||
|
- **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
|
||||||
|
- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
|
||||||
|
- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
|
||||||
|
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
|
||||||
|
- **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL**.
|
||||||
|
- **Cross platform**: Supports Windows, MacOS and Linux.
|
||||||
|
- **Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Common Use Cases
|
||||||
|
|
||||||
|
### World-to-Screen Projection
|
||||||
|
Project 3D world coordinates to 2D screen space for ESP overlays, UI elements, or visualization.
|
||||||
|
|
||||||
|
### Projectile Prediction
|
||||||
|
Calculate aim points for moving targets considering projectile speed, gravity, and target velocity.
|
||||||
|
|
||||||
|
### Collision Detection
|
||||||
|
Perform ray-casting, line tracing, and intersection tests for hit detection and physics.
|
||||||
|
|
||||||
|
### Pattern Scanning
|
||||||
|
Search for byte patterns in memory for reverse engineering, modding, or tool development.
|
||||||
|
|
||||||
|
### Pathfinding
|
||||||
|
Find optimal paths through 3D spaces using A* algorithm and navigation meshes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 Gallery
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
[](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
![APEX Preview]
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
![BO2 Preview]
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
![CS2 Preview]
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
![TF2 Preview]
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Community & Support
|
||||||
|
|
||||||
|
- **Documentation**: [http://libomath.org](http://libomath.org)
|
||||||
|
- **GitHub**: [orange-cpp/omath](https://github.com/orange-cpp/omath)
|
||||||
|
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
|
||||||
|
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
|
||||||
|
- **Issues**: [Report bugs or request features](https://github.com/orange-cpp/omath/issues)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Contributing
|
||||||
|
|
||||||
|
OMath is open source and welcomes contributions! See [CONTRIBUTING.md](https://github.com/orange-cpp/omath/blob/master/CONTRIBUTING.md) for guidelines.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
|
|
||||||
|
<!----------------------------------{ Images }--------------------------------->
|
||||||
|
[APEX Preview]: images/showcase/apex.png
|
||||||
|
[BO2 Preview]: images/showcase/cod_bo2.png
|
||||||
|
[CS2 Preview]: images/showcase/cs2.jpeg
|
||||||
|
[TF2 Preview]: images/showcase/tf2.jpg
|
||||||
68
docs/install.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg
|
||||||
|
**Note**: Support vcpkg for package management
|
||||||
|
1. Install [vcpkg](https://github.com/microsoft/vcpkg)
|
||||||
|
2. Run the following command to install the orange-math package:
|
||||||
|
```
|
||||||
|
vcpkg install orange-math
|
||||||
|
```
|
||||||
|
CMakeLists.txt
|
||||||
|
```cmake
|
||||||
|
find_package(omath CONFIG REQUIRED)
|
||||||
|
target_link_libraries(main PRIVATE omath::omath)
|
||||||
|
```
|
||||||
|
For detailed commands on installing different versions and more information, please refer to Microsoft's [official instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/overview).
|
||||||
|
|
||||||
|
## <img width="28px" src="https://xmake.io/assets/img/logo.svg" /> Using xrepo
|
||||||
|
**Note**: Support xrepo for package management
|
||||||
|
1. Install [xmake](https://xmake.io/)
|
||||||
|
2. Run the following command to install the omath package:
|
||||||
|
```
|
||||||
|
xrepo install omath
|
||||||
|
```
|
||||||
|
xmake.lua
|
||||||
|
```xmake
|
||||||
|
add_requires("omath")
|
||||||
|
target("...")
|
||||||
|
add_packages("omath")
|
||||||
|
```
|
||||||
|
|
||||||
|
## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake
|
||||||
|
1. **Preparation**
|
||||||
|
|
||||||
|
Install needed tools: cmake, clang, git, msvc (windows only).
|
||||||
|
|
||||||
|
1. **Linux:**
|
||||||
|
```bash
|
||||||
|
sudo pacman -Sy cmake ninja clang git
|
||||||
|
```
|
||||||
|
2. **MacOS:**
|
||||||
|
```bash
|
||||||
|
brew install llvm git cmake ninja
|
||||||
|
```
|
||||||
|
3. **Windows:**
|
||||||
|
|
||||||
|
Install Visual Studio from [here](https://visualstudio.microsoft.com/downloads/) and Git from [here](https://git-scm.com/downloads).
|
||||||
|
|
||||||
|
Use x64 Native Tools shell to execute needed commands down below.
|
||||||
|
2. **Clone the repository:**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/orange-cpp/omath.git
|
||||||
|
```
|
||||||
|
3. **Navigate to the project directory:**
|
||||||
|
```bash
|
||||||
|
cd omath
|
||||||
|
```
|
||||||
|
4. **Build the project using CMake:**
|
||||||
|
```bash
|
||||||
|
cmake --preset windows-release -S .
|
||||||
|
cmake --build cmake-build/build/windows-release --target omath -j 6
|
||||||
|
```
|
||||||
|
Use **\<platform\>-\<build configuration\>** preset to build suitable version for yourself. Like **windows-release** or **linux-release**.
|
||||||
|
|
||||||
|
| Platform Name | Build Config |
|
||||||
|
|---------------|---------------|
|
||||||
|
| windows | release/debug |
|
||||||
|
| linux | release/debug |
|
||||||
|
| darwin | release/debug |
|
||||||
428
docs/linear_algebra/mat.md
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
# `omath::Mat` — Matrix class (C++20/23)
|
||||||
|
|
||||||
|
> Header: your project’s `mat.hpp` (requires `vector3.hpp`)
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Requires: **C++23** (uses multi-parameter `operator[]`)
|
||||||
|
> SIMD (optional): define **`OMATH_USE_AVX2`** to enable AVX2-accelerated multiplication for `float`/`double`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`omath::Mat<Rows, Columns, Type, StoreType>` is a compile-time, fixed-size matrix with:
|
||||||
|
|
||||||
|
* **Row/column counts** as template parameters (no heap allocations).
|
||||||
|
* **Row-major** or **column-major** storage (compile-time via `MatStoreType`).
|
||||||
|
* **Arithmetic** and **linear algebra**: matrix × matrix, scalar ops, transpose, determinant, inverse (optional), etc.
|
||||||
|
* **Transform helpers**: translation, axis rotations, look-at, perspective & orthographic projections.
|
||||||
|
* **I/O helpers**: `to_string`/`to_wstring`/`to_u8string` and `std::formatter` specializations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template parameters
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
| ----------- | ------------------------------------------------------------------------ | ----------- |
|
||||||
|
| `Rows` | Number of rows (size_t, compile-time) | — |
|
||||||
|
| `Columns` | Number of columns (size_t, compile-time) | — |
|
||||||
|
| `Type` | Element type (arithmetic) | `float` |
|
||||||
|
| `StoreType` | Storage order: `MatStoreType::ROW_MAJOR` or `MatStoreType::COLUMN_MAJOR` | `ROW_MAJOR` |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class MatStoreType : uint8_t { ROW_MAJOR = 0, COLUMN_MAJOR };
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "mat.hpp"
|
||||||
|
using omath::Mat;
|
||||||
|
|
||||||
|
// 4x4 float, row-major
|
||||||
|
Mat<4,4> I = {
|
||||||
|
{1,0,0,0},
|
||||||
|
{0,1,0,0},
|
||||||
|
{0,0,1,0},
|
||||||
|
{0,0,0,1},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Multiply 4x4 transforms
|
||||||
|
Mat<4,4> A = { {1,2,3,0},{0,1,4,0},{5,6,0,0},{0,0,0,1} };
|
||||||
|
Mat<4,4> B = { {2,0,0,0},{0,2,0,0},{0,0,2,0},{0,0,0,1} };
|
||||||
|
Mat<4,4> C = A * B; // matrix × matrix
|
||||||
|
|
||||||
|
// Scalar ops
|
||||||
|
auto D = C * 0.5f; // scale all entries
|
||||||
|
|
||||||
|
// Indexing (C++23 multi-parameter operator[])
|
||||||
|
float a03 = A[0,3]; // same as A.at(0,3)
|
||||||
|
A[1,2] = 42.0f;
|
||||||
|
|
||||||
|
// Transpose, determinant, inverse
|
||||||
|
auto AT = A.transposed();
|
||||||
|
float det = A.determinant(); // only for square matrices
|
||||||
|
auto inv = A.inverted(); // std::optional<Mat>; std::nullopt if non-invertible
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> Multiplication requires the **same** `StoreType` and `Type` on both operands, and dimensions must match at compile time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Construction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat(); // zero-initialized
|
||||||
|
Mat(std::initializer_list<std::initializer_list<Type>> rows);
|
||||||
|
explicit Mat(const Type* raw_data); // copies Rows*Columns elements
|
||||||
|
Mat(const Mat&); Mat(Mat&&);
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Zeroing/setting**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
m.clear(); // set all entries to 0
|
||||||
|
m.set(3.14f); // set all entries to a value
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Shape & metadata**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat<>::row_count(); // constexpr size_t
|
||||||
|
Mat<>::columns_count(); // constexpr size_t
|
||||||
|
Mat<>::size(); // constexpr MatSize {rows, columns}
|
||||||
|
Mat<>::get_store_ordering(); // constexpr MatStoreType
|
||||||
|
using ContainedType = Type; // alias
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Element access
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
T& at(size_t r, size_t c);
|
||||||
|
T const& at(size_t r, size_t c) const;
|
||||||
|
|
||||||
|
T& operator[](size_t r, size_t c); // C++23
|
||||||
|
T const& operator[](size_t r, size_t c) const; // C++23
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Bounds checking**
|
||||||
|
> In debug builds you may enable/disable range checks via your compile-time macros (see the source guard around `at()`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arithmetic
|
||||||
|
|
||||||
|
* **Matrix × matrix**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// (Rows x Columns) * (Columns x OtherColumns) -> (Rows x OtherColumns)
|
||||||
|
template<size_t OtherColumns>
|
||||||
|
Mat<Rows, OtherColumns, Type, StoreType>
|
||||||
|
operator*(const Mat<Columns, OtherColumns, Type, StoreType>&) const;
|
||||||
|
```
|
||||||
|
|
||||||
|
* Complexity: `O(Rows * Columns * OtherColumns)`.
|
||||||
|
* AVX2-accelerated when `OMATH_USE_AVX2` is defined and `Type` is `float` or `double`.
|
||||||
|
|
||||||
|
* **Scalars**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat operator*(const Type& s) const; Mat& operator*=(const Type& s);
|
||||||
|
Mat operator/(const Type& s) const; Mat& operator/=(const Type& s);
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Transpose**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat<Columns, Rows, Type, StoreType> transposed() const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Determinant (square only)**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Type determinant() const; // 1x1, 2x2 fast path; larger uses Laplace expansion
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Inverse (square only)**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::optional<Mat> inverted() const; // nullopt if det == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Minors & cofactors (square only)**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mat<Rows-1, Columns-1, Type, StoreType> strip(size_t r, size_t c) const;
|
||||||
|
Type minor(size_t r, size_t c) const;
|
||||||
|
Type alg_complement(size_t r, size_t c) const; // cofactor
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Utilities**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Type sum() const noexcept;
|
||||||
|
auto& raw_array(); // std::array<Type, Rows*Columns>&
|
||||||
|
auto const& raw_array() const;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Comparison / formatting**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool operator==(const Mat&) const;
|
||||||
|
bool operator!=(const Mat&) const;
|
||||||
|
|
||||||
|
std::string to_string() const noexcept;
|
||||||
|
std::wstring to_wstring() const noexcept;
|
||||||
|
std::u8string to_u8string() const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
// std::formatter specialization provided for char, wchar_t, char8_t
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage order notes
|
||||||
|
|
||||||
|
- **Row-major**: `index = row * Columns + column`
|
||||||
|
- **Column-major**: `index = row + column * Rows`
|
||||||
|
|
||||||
|
Choose one **consistently** across your math types and shader conventions. Mixed orders are supported by the type system but not for cross-multiplying (store types must match).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Transform helpers
|
||||||
|
|
||||||
|
### From vectors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||||
|
Mat<1,4,T,St> mat_row_from_vector(const Vector3<T>& v);
|
||||||
|
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||||
|
Mat<4,1,T,St> mat_column_from_vector(const Vector3<T>& v);
|
||||||
|
````
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||||
|
Mat<4,4,T,St> mat_translation(const Vector3<T>& d) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Axis rotations
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Angle type must provide angle.cos() and angle.sin()
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
|
||||||
|
Mat<4,4,T,St> mat_rotation_axis_x(const Angle& a) noexcept;
|
||||||
|
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
|
||||||
|
Mat<4,4,T,St> mat_rotation_axis_y(const Angle& a) noexcept;
|
||||||
|
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
|
||||||
|
Mat<4,4,T,St> mat_rotation_axis_z(const Angle& a) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Camera/view
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||||
|
Mat<4,4,T,St> mat_camera_view(const Vector3<T>& forward,
|
||||||
|
const Vector3<T>& right,
|
||||||
|
const Vector3<T>& up,
|
||||||
|
const Vector3<T>& camera_origin) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Perspective projections
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||||
|
Mat<4,4,T,St> mat_perspective_left_handed (float fov_deg, float aspect, float near, float far) noexcept;
|
||||||
|
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||||
|
Mat<4,4,T,St> mat_perspective_right_handed(float fov_deg, float aspect, float near, float far) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Orthographic projections
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||||
|
Mat<4,4,T,St> mat_ortho_left_handed (T left, T right, T bottom, T top, T near, T far) noexcept;
|
||||||
|
|
||||||
|
template<class T=float, MatStoreType St=ROW_MAJOR>
|
||||||
|
Mat<4,4,T,St> mat_ortho_right_handed(T left, T right, T bottom, T top, T near, T far) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Look-at matrices
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class T=float, MatStoreType St=COLUMN_MAJOR>
|
||||||
|
Mat<4,4,T,St> mat_look_at_left_handed (const Vector3<T>& eye,
|
||||||
|
const Vector3<T>& center,
|
||||||
|
const Vector3<T>& up);
|
||||||
|
|
||||||
|
template<class T=float, MatStoreType St=COLUMN_MAJOR>
|
||||||
|
Mat<4,4,T,St> mat_look_at_right_handed(const Vector3<T>& eye,
|
||||||
|
const Vector3<T>& center,
|
||||||
|
const Vector3<T>& up);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Screen-space helper
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class Type=float>
|
||||||
|
static constexpr Mat<4,4> to_screen_mat(const Type& screen_w, const Type& screen_h) noexcept;
|
||||||
|
// Maps NDC to screen space (origin top-left, y down)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### 1) Building a left-handed camera and perspective
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using V3 = omath::Vector3<float>;
|
||||||
|
using M4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
|
||||||
|
|
||||||
|
V3 eye{0, 1, -5}, center{0, 0, 0}, up{0, 1, 0};
|
||||||
|
M4 view = omath::mat_look_at_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(eye, center, up);
|
||||||
|
|
||||||
|
float fov = 60.f, aspect = 16.f/9.f, n = 0.1f, f = 100.f;
|
||||||
|
M4 proj = omath::mat_perspective_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(fov, aspect, n, f);
|
||||||
|
|
||||||
|
// final VP
|
||||||
|
M4 vp = proj * view;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) Inverting a transform safely
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
omath::Mat<4,4> T = omath::mat_translation(omath::Vector3<float>{2,3,4});
|
||||||
|
if (auto inv = T.inverted()) {
|
||||||
|
// use *inv
|
||||||
|
} else {
|
||||||
|
// handle non-invertible
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) Formatting for logs
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
omath::Mat<2,2> A = { {1,2},{3,4} };
|
||||||
|
std::string s = A.to_string(); // "[[ 1.000, 2.000]\n [ 3.000, 4.000]]"
|
||||||
|
std::string f = std::format("A = {}", A); // uses std::formatter
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
* **Cache-friendly kernels** per storage order when AVX2 is not enabled.
|
||||||
|
* **AVX2 path** (`OMATH_USE_AVX2`) for `float`/`double` implements FMAs with 256-bit vectors for both row-major and column-major multiplication.
|
||||||
|
* Complexity for `A(R×K) * B(K×C)`: **O(RKC)** regardless of storage order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constraints & concepts
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<typename M1, typename M2>
|
||||||
|
concept MatTemplateEqual =
|
||||||
|
(M1::rows == M2::rows) &&
|
||||||
|
(M1::columns == M2::columns) &&
|
||||||
|
std::is_same_v<typename M1::value_type, typename M2::value_type> &&
|
||||||
|
(M1::store_type == M2::store_type);
|
||||||
|
```
|
||||||
|
|
||||||
|
> Use this concept to constrain generic functions that operate on like-shaped matrices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
* `std::invalid_argument` — initializer list dimensions mismatch.
|
||||||
|
* `std::out_of_range` — out-of-bounds in `at()` when bounds checking is active (see source guard).
|
||||||
|
* `inverted()` does **not** throw; returns `std::nullopt` if `determinant() == 0`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build switches
|
||||||
|
|
||||||
|
* **`OMATH_USE_AVX2`** — enable AVX2 vectorized multiplication paths (`<immintrin.h>` required).
|
||||||
|
* **Debug checks** — the `at()` method contains a conditional range check; refer to the preprocessor guard in the code to enable/disable in your configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known requirements & interoperability
|
||||||
|
|
||||||
|
* **C++23** is required for multi-parameter `operator[]`. If you target pre-C++23, use `at(r,c)` instead.
|
||||||
|
* All binary operations require matching `Type` and `StoreType`. Convert explicitly if needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath::Vector3<T>`
|
||||||
|
* Projection helpers: `mat_perspective_*`, `mat_ortho_*`
|
||||||
|
* View helpers: `mat_look_at_*`, `mat_camera_view`
|
||||||
|
* Construction helpers: `mat_row_from_vector`, `mat_column_from_vector`, `mat_translation`, `mat_rotation_axis_*`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: API summary (signatures)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Core
|
||||||
|
Mat(); Mat(const Mat&); Mat(Mat&&);
|
||||||
|
Mat(std::initializer_list<std::initializer_list<Type>>);
|
||||||
|
explicit Mat(const Type* raw);
|
||||||
|
Mat& operator=(const Mat&); Mat& operator=(Mat&&);
|
||||||
|
|
||||||
|
static constexpr size_t row_count();
|
||||||
|
static constexpr size_t columns_count();
|
||||||
|
static consteval MatSize size();
|
||||||
|
static constexpr MatStoreType get_store_ordering();
|
||||||
|
|
||||||
|
T& at(size_t r, size_t c);
|
||||||
|
T const& at(size_t r, size_t c) const;
|
||||||
|
T& operator[](size_t r, size_t c);
|
||||||
|
T const& operator[](size_t r, size_t c) const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void set(const Type& v);
|
||||||
|
Type sum() const noexcept;
|
||||||
|
|
||||||
|
template<size_t OC> Mat<Rows,OC,Type,StoreType> operator*(const Mat<Columns,OC,Type,StoreType>&) const;
|
||||||
|
Mat& operator*=(const Type&); Mat operator*(const Type&) const;
|
||||||
|
Mat& operator/=(const Type&); Mat operator/(const Type&) const;
|
||||||
|
|
||||||
|
Mat<Columns,Rows,Type,StoreType> transposed() const noexcept;
|
||||||
|
Type determinant() const; // square only
|
||||||
|
std::optional<Mat> inverted() const; // square only
|
||||||
|
|
||||||
|
Mat<Rows-1,Columns-1,Type,StoreType> strip(size_t r, size_t c) const;
|
||||||
|
Type minor(size_t r, size_t c) const;
|
||||||
|
Type alg_complement(size_t r, size_t c) const;
|
||||||
|
|
||||||
|
auto& raw_array(); auto const& raw_array() const;
|
||||||
|
std::string to_string() const noexcept;
|
||||||
|
std::wstring to_wstring() const noexcept;
|
||||||
|
std::u8string to_u8string() const noexcept;
|
||||||
|
|
||||||
|
bool operator==(const Mat&) const;
|
||||||
|
bool operator!=(const Mat&) const;
|
||||||
|
|
||||||
|
// Helpers (see sections above)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 31 Oct 2025*
|
||||||
173
docs/linear_algebra/triangle.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# `omath::Triangle` — Simple 3D triangle utility
|
||||||
|
|
||||||
|
> Header: your project’s `triangle.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Depends on: `omath::Vector3<float>` (from `vector3.hpp`)
|
||||||
|
|
||||||
|
A tiny helper around three `Vector3<float>` vertices with convenience methods for normals, edge vectors/lengths, a right-angle test (at **`v2`**), and the triangle centroid.
|
||||||
|
|
||||||
|
> **Note on the template parameter**
|
||||||
|
>
|
||||||
|
> The class is declared as `template<class Vector> class Triangle`, but the stored vertices are concretely `Vector3<float>`. In practice this type is currently **fixed to `Vector3<float>`**. You can ignore the template parameter or refactor to store `Vector` if you intend true genericity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vertex layout & naming
|
||||||
|
|
||||||
|
```
|
||||||
|
v1
|
||||||
|
|\
|
||||||
|
| \
|
||||||
|
a | \ hypot = |v1 - v3|
|
||||||
|
| \
|
||||||
|
v2 -- v3
|
||||||
|
b
|
||||||
|
|
||||||
|
a = |v1 - v2| (side_a_length)
|
||||||
|
b = |v3 - v2| (side_b_length)
|
||||||
|
```
|
||||||
|
|
||||||
|
* **`side_a_vector()`** = `v1 - v2` (points from v2 → v1)
|
||||||
|
* **`side_b_vector()`** = `v3 - v2` (points from v2 → v3)
|
||||||
|
* **Right-angle check** uses `a² + b² ≈ hypot²` with an epsilon of `1e-4`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "triangle.hpp"
|
||||||
|
using omath::Vector3;
|
||||||
|
using omath::Triangle;
|
||||||
|
|
||||||
|
Triangle<void> tri( // template arg unused; any placeholder ok
|
||||||
|
Vector3<float>{0,0,0}, // v1
|
||||||
|
Vector3<float>{0,0,1}, // v2 (right angle is tested at v2)
|
||||||
|
Vector3<float>{1,0,1} // v3
|
||||||
|
);
|
||||||
|
|
||||||
|
auto n = tri.calculate_normal(); // unit normal (right-handed: (v3-v2) × (v1-v2))
|
||||||
|
float a = tri.side_a_length(); // |v1 - v2|
|
||||||
|
float b = tri.side_b_length(); // |v3 - v2|
|
||||||
|
float hyp = tri.hypot(); // |v1 - v3|
|
||||||
|
bool rect = tri.is_rectangular(); // true if ~right triangle at v2
|
||||||
|
auto C = tri.mid_point(); // centroid (average of v1,v2,v3)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data members
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector3<float> m_vertex1; // v1
|
||||||
|
Vector3<float> m_vertex2; // v2 (the corner tested by is_rectangular)
|
||||||
|
Vector3<float> m_vertex3; // v3
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Triangle() = default;
|
||||||
|
constexpr Triangle(const Vector3<float>& v1,
|
||||||
|
const Vector3<float>& v2,
|
||||||
|
const Vector3<float>& v3);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Normal (unit) using right-handed cross product:
|
||||||
|
// n = (v3 - v2) × (v1 - v2), then normalized()
|
||||||
|
[[nodiscard]] constexpr Vector3<float> calculate_normal() const;
|
||||||
|
|
||||||
|
// Edge lengths with the naming from the diagram
|
||||||
|
[[nodiscard]] float side_a_length() const; // |v1 - v2|
|
||||||
|
[[nodiscard]] float side_b_length() const; // |v3 - v2|
|
||||||
|
|
||||||
|
// Edge vectors (from v2 to the other vertex)
|
||||||
|
[[nodiscard]] constexpr Vector3<float> side_a_vector() const; // v1 - v2
|
||||||
|
[[nodiscard]] constexpr Vector3<float> side_b_vector() const; // v3 - v2
|
||||||
|
|
||||||
|
// Hypotenuse length between v1 and v3
|
||||||
|
[[nodiscard]] constexpr float hypot() const; // |v1 - v3|
|
||||||
|
|
||||||
|
// Right-triangle check at vertex v2 (Pythagoras with epsilon 1e-4)
|
||||||
|
[[nodiscard]] constexpr bool is_rectangular() const;
|
||||||
|
|
||||||
|
// Centroid of the triangle (average of the 3 vertices)
|
||||||
|
[[nodiscard]] constexpr Vector3<float> mid_point() const; // actually the centroid
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notes & edge cases
|
||||||
|
|
||||||
|
* **Normal direction** follows the right-hand rule for the ordered vertices `{v2 → v3} × {v2 → v1}`.
|
||||||
|
Swap vertex order to flip the normal.
|
||||||
|
* **Degenerate triangles** (collinear or overlapping vertices) yield a **zero vector** normal (since `normalized()` of the zero vector returns the zero vector in your math types).
|
||||||
|
* **`mid_point()` is the centroid**, not the midpoint of any single edge. If you need the midpoint of edge `v1–v2`, use `(m_vertex1 + m_vertex2) * 0.5f`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Area and plane from existing API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto a = tri.side_a_vector();
|
||||||
|
const auto b = tri.side_b_vector();
|
||||||
|
const auto n = b.cross(a); // unnormalized normal
|
||||||
|
float area = 0.5f * n.length(); // triangle area
|
||||||
|
|
||||||
|
// Plane equation n̂·(x - v2) = 0
|
||||||
|
auto nhat = n.length() > 0 ? n / n.length() : n;
|
||||||
|
float d = -nhat.dot(tri.m_vertex2);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project a point onto the triangle’s plane
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector3<float> p{0.3f, 1.0f, 0.7f};
|
||||||
|
auto n = tri.calculate_normal();
|
||||||
|
float t = n.dot(tri.m_vertex2 - p); // signed distance along normal
|
||||||
|
auto projected = p + n * t; // on-plane projection
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API summary (signatures)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Triangle final {
|
||||||
|
public:
|
||||||
|
constexpr Triangle();
|
||||||
|
constexpr Triangle(const Vector3<float>& v1,
|
||||||
|
const Vector3<float>& v2,
|
||||||
|
const Vector3<float>& v3);
|
||||||
|
|
||||||
|
Vector3<float> m_vertex1, m_vertex2, m_vertex3;
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3<float> calculate_normal() const;
|
||||||
|
[[nodiscard]] float side_a_length() const;
|
||||||
|
[[nodiscard]] float side_b_length() const;
|
||||||
|
[[nodiscard]] constexpr Vector3<float> side_a_vector() const;
|
||||||
|
[[nodiscard]] constexpr Vector3<float> side_b_vector() const;
|
||||||
|
[[nodiscard]] constexpr float hypot() const;
|
||||||
|
[[nodiscard]] constexpr bool is_rectangular() const;
|
||||||
|
[[nodiscard]] constexpr Vector3<float> mid_point() const;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Suggestions (optional improvements)
|
||||||
|
|
||||||
|
* If generic vectors are intended, store `Vector m_vertex*;` and constrain `Vector` to the required ops (`-`, `cross`, `normalized`, `distance_to`, `+`, `/`).
|
||||||
|
* Consider renaming `mid_point()` → `centroid()` to avoid ambiguity.
|
||||||
|
* Expose an `area()` helper and (optionally) a barycentric coordinate routine if you plan to use this in rasterization or intersection tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 31 Oct 2025*
|
||||||
300
docs/linear_algebra/vector2.md
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
# `omath::Vector2` — 2D vector (C++20/23)
|
||||||
|
|
||||||
|
> Header: your project’s `vector2.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
|
||||||
|
|
||||||
|
`Vector2<Type>` is a lightweight, POD-like 2D math type with arithmetic, geometry helpers, comparisons, hashing (for `float`), optional ImGui interop, and `std::formatter` support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "vector2.hpp"
|
||||||
|
using omath::Vector2;
|
||||||
|
|
||||||
|
using Vec2f = Vector2<float>;
|
||||||
|
|
||||||
|
Vec2f a{3.f, 4.f};
|
||||||
|
Vec2f b{1.f, 2.f};
|
||||||
|
|
||||||
|
auto d = a.distance_to(b); // ≈ 3.1623
|
||||||
|
auto dot = a.dot(b); // 11
|
||||||
|
auto len = a.length(); // 5
|
||||||
|
auto unit_a = a.normalized(); // (0.6, 0.8)
|
||||||
|
|
||||||
|
// Component-wise mutate
|
||||||
|
Vec2f c{2, 3};
|
||||||
|
c *= b; // c -> (2*1, 3*2) = (2, 6)
|
||||||
|
|
||||||
|
// Scalar ops (non-mutating + mutating)
|
||||||
|
auto scaled = a * 0.5f; // (1.5, 2)
|
||||||
|
a *= 2.f; // (6, 8)
|
||||||
|
|
||||||
|
// Ordering by length()
|
||||||
|
bool shorter = (b < a);
|
||||||
|
|
||||||
|
// Formatted printing
|
||||||
|
std::string s = std::format("a = {}", a); // "a = [6, 8]"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Members
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Type x{0};
|
||||||
|
Type y{0};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector2(); // (0,0)
|
||||||
|
constexpr Vector2(const Type& x, const Type& y) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Equality & ordering
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr bool operator==(const Vector2&) const noexcept; // component-wise equality
|
||||||
|
constexpr bool operator!=(const Vector2&) const noexcept;
|
||||||
|
|
||||||
|
bool operator< (const Vector2&) const noexcept; // compares by length()
|
||||||
|
bool operator> (const Vector2&) const noexcept;
|
||||||
|
bool operator<=(const Vector2&) const noexcept;
|
||||||
|
bool operator>=(const Vector2&) const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `<`, `>`, `<=`, `>=` order vectors by **magnitude** (not lexicographically).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arithmetic
|
||||||
|
|
||||||
|
### With another vector (component-wise, **mutating**)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector2& operator+=(const Vector2&) noexcept;
|
||||||
|
Vector2& operator-=(const Vector2&) noexcept;
|
||||||
|
Vector2& operator*=(const Vector2&) noexcept; // Hadamard product (x*=x, y*=y)
|
||||||
|
Vector2& operator/=(const Vector2&) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
> Non-mutating `v * u` / `v / u` (vector × vector) are **not** provided.
|
||||||
|
> Use `v *= u` (mutating) or build a new vector explicitly.
|
||||||
|
|
||||||
|
### With a scalar
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector2& operator*=(const Type& v) noexcept;
|
||||||
|
Vector2& operator/=(const Type& v) noexcept;
|
||||||
|
Vector2& operator+=(const Type& v) noexcept;
|
||||||
|
Vector2& operator-=(const Type& v) noexcept;
|
||||||
|
|
||||||
|
constexpr Vector2 operator*(const Type& v) const noexcept;
|
||||||
|
constexpr Vector2 operator/(const Type& v) const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Binary (+/−) with another vector (non-mutating)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector2 operator+(const Vector2&) const noexcept;
|
||||||
|
constexpr Vector2 operator-(const Vector2&) const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unary
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector2 operator-() const noexcept; // negation
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Geometry & helpers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Type distance_to (const Vector2&) const noexcept; // sqrt of squared distance
|
||||||
|
constexpr Type distance_to_sqr(const Vector2&) const noexcept;
|
||||||
|
|
||||||
|
constexpr Type dot(const Vector2&) const noexcept;
|
||||||
|
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
constexpr Type length() const noexcept; // uses std::hypot; constexpr on non-MSVC
|
||||||
|
constexpr Vector2 normalized() const noexcept; // returns *this if length==0
|
||||||
|
#else
|
||||||
|
Type length() const noexcept;
|
||||||
|
Vector2 normalized() const noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr Type length_sqr() const noexcept; // x*x + y*y
|
||||||
|
Vector2& abs() noexcept; // component-wise absolute (constexpr-friendly impl)
|
||||||
|
|
||||||
|
constexpr Type sum() const noexcept; // x + y
|
||||||
|
constexpr std::tuple<Type, Type> as_tuple() const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ImGui integration (optional)
|
||||||
|
|
||||||
|
Define `OMATH_IMGUI_INTEGRATION` **before** including the header.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#ifdef OMATH_IMGUI_INTEGRATION
|
||||||
|
constexpr ImVec2 to_im_vec2() const noexcept; // {float(x), float(y)}
|
||||||
|
static Vector2 from_im_vec2(const ImVec2&) noexcept;
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hashing & formatting
|
||||||
|
|
||||||
|
* **Hash (for `Vector2<float>`)**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<> struct std::hash<omath::Vector2<float>> {
|
||||||
|
std::size_t operator()(const omath::Vector2<float>&) const noexcept;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::unordered_set<omath::Vector2<float>> set;
|
||||||
|
set.insert({1.f, 2.f});
|
||||||
|
```
|
||||||
|
|
||||||
|
* **`std::formatter`** (for any `Type`)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// prints "[x, y]" for char / wchar_t / char8_t
|
||||||
|
template<class Type>
|
||||||
|
struct std::formatter<omath::Vector2<Type>>;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & invariants
|
||||||
|
|
||||||
|
* `Type` must be arithmetic (e.g., `float`, `double`, `int`, …).
|
||||||
|
* `normalized()` returns the input unchanged if `length() == 0`.
|
||||||
|
* `abs()` uses a constexpr-friendly implementation (not `std::abs`) to allow compile-time evaluation.
|
||||||
|
* On MSVC, `length()`/`normalized()` are not `constexpr` due to library constraints; they’re still `noexcept`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Component-wise operations and scalar scaling
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
omath::Vector2<float> u{2, 3}, v{4, 5};
|
||||||
|
|
||||||
|
u += v; // (6, 8)
|
||||||
|
u -= v; // (2, 3)
|
||||||
|
u *= v; // (8, 15) Hadamard product (mutates u)
|
||||||
|
auto w = v * 2.0f; // (8, 10) non-mutating scalar multiply
|
||||||
|
```
|
||||||
|
|
||||||
|
### Geometry helpers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
omath::Vector2<double> p{0.0, 0.0}, q{3.0, 4.0};
|
||||||
|
|
||||||
|
auto dsq = p.distance_to_sqr(q); // 25
|
||||||
|
auto d = p.distance_to(q); // 5
|
||||||
|
auto dot = p.dot(q); // 0
|
||||||
|
auto uq = q.normalized(); // (0.6, 0.8)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using as a key in unordered containers (`float`)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::unordered_map<omath::Vector2<float>, int> counts;
|
||||||
|
counts[{1.f, 2.f}] = 42;
|
||||||
|
```
|
||||||
|
|
||||||
|
### ImGui interop
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define OMATH_IMGUI_INTEGRATION
|
||||||
|
#include "vector2.hpp"
|
||||||
|
|
||||||
|
omath::Vector2<float> v{10, 20};
|
||||||
|
ImVec2 iv = v.to_im_vec2();
|
||||||
|
v = omath::Vector2<float>::from_im_vec2(iv);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API summary (signatures)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Constructors
|
||||||
|
constexpr Vector2();
|
||||||
|
constexpr Vector2(const Type& x, const Type& y) noexcept;
|
||||||
|
|
||||||
|
// Equality & ordering
|
||||||
|
constexpr bool operator==(const Vector2&) const noexcept;
|
||||||
|
constexpr bool operator!=(const Vector2&) const noexcept;
|
||||||
|
bool operator< (const Vector2&) const noexcept;
|
||||||
|
bool operator> (const Vector2&) const noexcept;
|
||||||
|
bool operator<=(const Vector2&) const noexcept;
|
||||||
|
bool operator>=(const Vector2&) const noexcept;
|
||||||
|
|
||||||
|
// Compound (vector/vector and scalar)
|
||||||
|
Vector2& operator+=(const Vector2&) noexcept;
|
||||||
|
Vector2& operator-=(const Vector2&) noexcept;
|
||||||
|
Vector2& operator*=(const Vector2&) noexcept;
|
||||||
|
Vector2& operator/=(const Vector2&) noexcept;
|
||||||
|
Vector2& operator*=(const Type&) noexcept;
|
||||||
|
Vector2& operator/=(const Type&) noexcept;
|
||||||
|
Vector2& operator+=(const Type&) noexcept;
|
||||||
|
Vector2& operator-=(const Type&) noexcept;
|
||||||
|
|
||||||
|
// Non-mutating arithmetic
|
||||||
|
constexpr Vector2 operator+(const Vector2&) const noexcept;
|
||||||
|
constexpr Vector2 operator-(const Vector2&) const noexcept;
|
||||||
|
constexpr Vector2 operator*(const Type&) const noexcept;
|
||||||
|
constexpr Vector2 operator/(const Type&) const noexcept;
|
||||||
|
constexpr Vector2 operator-() const noexcept;
|
||||||
|
|
||||||
|
// Geometry
|
||||||
|
Type distance_to(const Vector2&) const noexcept;
|
||||||
|
constexpr Type distance_to_sqr(const Vector2&) const noexcept;
|
||||||
|
constexpr Type dot(const Vector2&) const noexcept;
|
||||||
|
Type length() const noexcept; // constexpr on non-MSVC
|
||||||
|
Vector2 normalized() const noexcept; // constexpr on non-MSVC
|
||||||
|
constexpr Type length_sqr() const noexcept;
|
||||||
|
Vector2& abs() noexcept;
|
||||||
|
constexpr Type sum() const noexcept;
|
||||||
|
constexpr std::tuple<Type,Type> as_tuple() const noexcept;
|
||||||
|
|
||||||
|
// ImGui (optional)
|
||||||
|
#ifdef OMATH_IMGUI_INTEGRATION
|
||||||
|
constexpr ImVec2 to_im_vec2() const noexcept;
|
||||||
|
static Vector2 from_im_vec2(const ImVec2&) noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Hash (float) and formatter are specialized in the header
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Vector3 Documentation](vector3.md) - 3D vector operations
|
||||||
|
- [Vector4 Documentation](vector4.md) - 4D vector operations
|
||||||
|
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
|
||||||
|
- [Tutorials](../tutorials.md) - Step-by-step examples
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
307
docs/linear_algebra/vector3.md
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
# `omath::Vector3` — 3D vector (C++20/23)
|
||||||
|
|
||||||
|
> Header: your project’s `vector3.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
|
||||||
|
> Depends on: `omath::Vector2<Type>` (base class), `omath::Angle` (for `angle_between`)
|
||||||
|
> C++: uses `std::expected` ⇒ **C++23** recommended (or a backport)
|
||||||
|
|
||||||
|
`Vector3<Type>` extends `Vector2<Type>` with a `z` component and 3D operations: arithmetic, geometry (dot, cross, distance), normalization, angle-between with robust error signaling, hashing (for `float`) and `std::formatter` support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "vector3.hpp"
|
||||||
|
using omath::Vector3;
|
||||||
|
|
||||||
|
using Vec3f = Vector3<float>;
|
||||||
|
|
||||||
|
Vec3f a{3, 4, 0};
|
||||||
|
Vec3f b{1, 2, 2};
|
||||||
|
|
||||||
|
auto d = a.distance_to(b); // Euclidean distance
|
||||||
|
auto dot = a.dot(b); // 3*1 + 4*2 + 0*2 = 11
|
||||||
|
auto cr = a.cross(b); // (8, -6, 2)
|
||||||
|
auto len = a.length(); // hypot(x,y,z)
|
||||||
|
auto unit = a.normalized(); // safe normalize (returns a if length==0)
|
||||||
|
|
||||||
|
if (auto ang = a.angle_between(b)) {
|
||||||
|
float deg = ang->as_degrees(); // [0, 180], clamped
|
||||||
|
} else {
|
||||||
|
// vectors have zero length -> no defined angle
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data members
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Type x{0}; // inherited from Vector2<Type>
|
||||||
|
Type y{0}; // inherited from Vector2<Type>
|
||||||
|
Type z{0};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector3() noexcept;
|
||||||
|
constexpr Vector3(const Type& x, const Type& y, const Type& z) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Equality & ordering
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr bool operator==(const Vector3&) const noexcept; // component-wise
|
||||||
|
constexpr bool operator!=(const Vector3&) const noexcept;
|
||||||
|
|
||||||
|
bool operator< (const Vector3&) const noexcept; // compare by length()
|
||||||
|
bool operator> (const Vector3&) const noexcept;
|
||||||
|
bool operator<=(const Vector3&) const noexcept;
|
||||||
|
bool operator>=(const Vector3&) const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** Ordering uses **magnitude**, not lexicographic order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arithmetic (mutating)
|
||||||
|
|
||||||
|
Component-wise with another vector:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector3& operator+=(const Vector3&) noexcept;
|
||||||
|
Vector3& operator-=(const Vector3&) noexcept;
|
||||||
|
Vector3& operator*=(const Vector3&) noexcept; // Hadamard product
|
||||||
|
Vector3& operator/=(const Vector3&) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
With a scalar:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector3& operator*=(const Type& v) noexcept;
|
||||||
|
Vector3& operator/=(const Type& v) noexcept;
|
||||||
|
Vector3& operator+=(const Type& v) noexcept;
|
||||||
|
Vector3& operator-=(const Type& v) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arithmetic (non-mutating)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector3 operator-() const noexcept;
|
||||||
|
constexpr Vector3 operator+(const Vector3&) const noexcept;
|
||||||
|
constexpr Vector3 operator-(const Vector3&) const noexcept;
|
||||||
|
constexpr Vector3 operator*(const Vector3&) const noexcept; // Hadamard
|
||||||
|
constexpr Vector3 operator/(const Vector3&) const noexcept; // Hadamard
|
||||||
|
constexpr Vector3 operator*(const Type& scalar) const noexcept;
|
||||||
|
constexpr Vector3 operator/(const Type& scalar) const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Geometry & helpers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Distances & lengths
|
||||||
|
Type distance_to(const Vector3&) const; // sqrt of squared distance
|
||||||
|
constexpr Type distance_to_sqr(const Vector3&) const noexcept;
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
constexpr Type length() const; // hypot(x,y,z)
|
||||||
|
constexpr Type length_2d() const; // 2D length from base
|
||||||
|
constexpr Vector3 normalized() const; // returns *this if length==0
|
||||||
|
#else
|
||||||
|
Type length() const noexcept;
|
||||||
|
Type length_2d() const noexcept;
|
||||||
|
Vector3 normalized() const noexcept;
|
||||||
|
#endif
|
||||||
|
constexpr Type length_sqr() const noexcept;
|
||||||
|
|
||||||
|
// Products
|
||||||
|
constexpr Type dot(const Vector3&) const noexcept;
|
||||||
|
constexpr Vector3 cross(const Vector3&) const noexcept; // right-handed
|
||||||
|
|
||||||
|
// Sums & tuples
|
||||||
|
constexpr Type sum() const noexcept; // x + y + z
|
||||||
|
constexpr Type sum_2d() const noexcept; // x + y
|
||||||
|
constexpr auto as_tuple() const noexcept -> std::tuple<Type,Type,Type>;
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
Vector3& abs() noexcept; // component-wise absolute
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Angles & orthogonality
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class Vector3Error { IMPOSSIBLE_BETWEEN_ANGLE };
|
||||||
|
|
||||||
|
// Angle in degrees, clamped to [0,180]. Error if any vector has zero length.
|
||||||
|
std::expected<
|
||||||
|
omath::Angle<float, 0.f, 180.f, AngleFlags::Clamped>,
|
||||||
|
Vector3Error
|
||||||
|
> angle_between(const Vector3& other) const noexcept;
|
||||||
|
|
||||||
|
bool is_perpendicular(const Vector3& other) const noexcept; // true if angle == 90°
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hashing & formatting
|
||||||
|
|
||||||
|
* **Hash (for `Vector3<float>`)**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<> struct std::hash<omath::Vector3<float>> {
|
||||||
|
std::size_t operator()(const omath::Vector3<float>&) const noexcept;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::unordered_map<omath::Vector3<float>, int> counts;
|
||||||
|
counts[{1.f, 2.f, 3.f}] = 7;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **`std::formatter`** (all character types)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class Type>
|
||||||
|
struct std::formatter<omath::Vector3<Type>>; // prints "[x, y, z]"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error handling
|
||||||
|
|
||||||
|
* `angle_between()` returns `std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE)` if either vector length is zero.
|
||||||
|
* Other operations are total for arithmetic `Type` (no throwing behavior in this class).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Cross product & perpendicular check
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
omath::Vector3<float> x{1,0,0}, y{0,1,0};
|
||||||
|
auto z = x.cross(y); // (0,0,1)
|
||||||
|
bool perp = x.is_perpendicular(y); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Safe normalization and angle
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
omath::Vector3<float> u{0,0,0}, v{1,1,0};
|
||||||
|
auto nu = u.normalized(); // returns {0,0,0}
|
||||||
|
if (auto ang = u.angle_between(v)) {
|
||||||
|
// won't happen: u has zero length → error
|
||||||
|
} else {
|
||||||
|
// handle degenerate case
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hadamard vs scalar multiply
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
omath::Vector3<float> a{2,3,4}, b{5,6,7};
|
||||||
|
auto h = a * b; // (10, 18, 28) component-wise
|
||||||
|
auto s = a * 2.f; // (4, 6, 8) scalar
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API summary (signatures)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Ctors
|
||||||
|
constexpr Vector3() noexcept;
|
||||||
|
constexpr Vector3(const Type& x, const Type& y, const Type& z) noexcept;
|
||||||
|
|
||||||
|
// Equality & ordering
|
||||||
|
constexpr bool operator==(const Vector3&) const noexcept;
|
||||||
|
constexpr bool operator!=(const Vector3&) const noexcept;
|
||||||
|
bool operator< (const Vector3&) const noexcept;
|
||||||
|
bool operator> (const Vector3&) const noexcept;
|
||||||
|
bool operator<=(const Vector3&) const noexcept;
|
||||||
|
bool operator>=(const Vector3&) const noexcept;
|
||||||
|
|
||||||
|
// Mutating arithmetic
|
||||||
|
Vector3& operator+=(const Vector3&) noexcept;
|
||||||
|
Vector3& operator-=(const Vector3&) noexcept;
|
||||||
|
Vector3& operator*=(const Vector3&) noexcept;
|
||||||
|
Vector3& operator/=(const Vector3&) noexcept;
|
||||||
|
Vector3& operator*=(const Type&) noexcept;
|
||||||
|
Vector3& operator/=(const Type&) noexcept;
|
||||||
|
Vector3& operator+=(const Type&) noexcept;
|
||||||
|
Vector3& operator-=(const Type&) noexcept;
|
||||||
|
|
||||||
|
// Non-mutating arithmetic
|
||||||
|
constexpr Vector3 operator-() const noexcept;
|
||||||
|
constexpr Vector3 operator+(const Vector3&) const noexcept;
|
||||||
|
constexpr Vector3 operator-(const Vector3&) const noexcept;
|
||||||
|
constexpr Vector3 operator*(const Vector3&) const noexcept;
|
||||||
|
constexpr Vector3 operator/(const Vector3&) const noexcept;
|
||||||
|
constexpr Vector3 operator*(const Type&) const noexcept;
|
||||||
|
constexpr Vector3 operator/(const Type&) const noexcept;
|
||||||
|
|
||||||
|
// Geometry
|
||||||
|
Type distance_to(const Vector3&) const;
|
||||||
|
constexpr Type distance_to_sqr(const Vector3&) const noexcept;
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
constexpr Type length() const;
|
||||||
|
constexpr Type length_2d() const;
|
||||||
|
constexpr Vector3 normalized() const;
|
||||||
|
#else
|
||||||
|
Type length() const noexcept;
|
||||||
|
Type length_2d() const noexcept;
|
||||||
|
Vector3 normalized() const noexcept;
|
||||||
|
#endif
|
||||||
|
constexpr Type length_sqr() const noexcept;
|
||||||
|
constexpr Type dot(const Vector3&) const noexcept;
|
||||||
|
constexpr Vector3 cross(const Vector3&) const noexcept;
|
||||||
|
|
||||||
|
Vector3& abs() noexcept;
|
||||||
|
constexpr Type sum() const noexcept;
|
||||||
|
constexpr Type sum_2d() const noexcept;
|
||||||
|
constexpr auto as_tuple() const noexcept -> std::tuple<Type,Type,Type>;
|
||||||
|
|
||||||
|
// Angles
|
||||||
|
std::expected<omath::Angle<float,0.f,180.f,AngleFlags::Clamped>, omath::Vector3Error>
|
||||||
|
angle_between(const Vector3&) const noexcept;
|
||||||
|
bool is_perpendicular(const Vector3&) const noexcept;
|
||||||
|
|
||||||
|
// Hash (float) and formatter specializations provided below the class
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
* Inherits all public API of `Vector2<Type>` (including `x`, `y`, many operators, and helpers used internally).
|
||||||
|
* `normalized()` returns the original vector if its length is zero (no NaNs).
|
||||||
|
* `cross()` uses the standard right-handed definition.
|
||||||
|
* `length()`/`normalized()` are `constexpr` on non-MSVC; MSVC builds provide `noexcept` runtime versions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Vector2 Documentation](vector2.md) - 2D vector operations
|
||||||
|
- [Vector4 Documentation](vector4.md) - 4D vector operations
|
||||||
|
- [Angle Documentation](../trigonometry/angle.md) - Working with angles
|
||||||
|
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
|
||||||
|
- [Tutorials](../tutorials.md) - Practical examples including vector math
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
253
docs/linear_algebra/vector4.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# `omath::Vector4` — 4D vector (C++20/23)
|
||||||
|
|
||||||
|
> Header: your project’s `vector4.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
|
||||||
|
> Inherits: `omath::Vector3<Type>` (brings `x`, `y`, `z` and most scalar ops)
|
||||||
|
|
||||||
|
`Vector4<Type>` extends `Vector3<Type>` with a `w` component and 4D operations: component-wise arithmetic, scalar ops, dot/length helpers, clamping, hashing (for `float`) and `std::formatter` support. Optional ImGui interop is available behind a macro.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "vector4.hpp"
|
||||||
|
using omath::Vector4;
|
||||||
|
|
||||||
|
using Vec4f = Vector4<float>;
|
||||||
|
|
||||||
|
Vec4f a{1, 2, 3, 1};
|
||||||
|
Vec4f b{4, 5, 6, 2};
|
||||||
|
|
||||||
|
// Component-wise & scalar ops
|
||||||
|
auto c = a + b; // (5, 7, 9, 3)
|
||||||
|
c *= 0.5f; // (2.5, 3.5, 4.5, 1.5)
|
||||||
|
auto h = a * b; // Hadamard: (4, 10, 18, 2)
|
||||||
|
|
||||||
|
// Dot / length
|
||||||
|
float d = a.dot(b); // 1*4 + 2*5 + 3*6 + 1*2 = 32
|
||||||
|
float L = a.length(); // sqrt(x²+y²+z²+w²)
|
||||||
|
|
||||||
|
// Clamp (x,y,z only; see notes)
|
||||||
|
Vec4f col{1.4f, -0.2f, 0.7f, 42.f};
|
||||||
|
col.clamp(0.f, 1.f); // -> (1, 0, 0.7, w unchanged)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data members
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Inherited from Vector3<Type>:
|
||||||
|
Type x{0};
|
||||||
|
Type y{0};
|
||||||
|
Type z{0};
|
||||||
|
|
||||||
|
// Added in Vector4:
|
||||||
|
Type w{0};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector4() noexcept; // (0,0,0,0)
|
||||||
|
constexpr Vector4(const Type& x, const Type& y,
|
||||||
|
const Type& z, const Type& w); // value-init
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Equality & ordering
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr bool operator==(const Vector4&) const noexcept; // component-wise
|
||||||
|
constexpr bool operator!=(const Vector4&) const noexcept;
|
||||||
|
|
||||||
|
bool operator< (const Vector4&) const noexcept; // compare by length()
|
||||||
|
bool operator> (const Vector4&) const noexcept;
|
||||||
|
bool operator<=(const Vector4&) const noexcept;
|
||||||
|
bool operator>=(const Vector4&) const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** Ordering uses **magnitude** (Euclidean norm), not lexicographic order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arithmetic (mutating)
|
||||||
|
|
||||||
|
With another vector (component-wise):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector4& operator+=(const Vector4&) noexcept;
|
||||||
|
Vector4& operator-=(const Vector4&) noexcept;
|
||||||
|
Vector4& operator*=(const Vector4&) noexcept; // Hadamard
|
||||||
|
Vector4& operator/=(const Vector4&) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
With a scalar:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector4& operator*=(const Type& v) noexcept;
|
||||||
|
Vector4& operator/=(const Type& v) noexcept;
|
||||||
|
|
||||||
|
// From base class (inherited):
|
||||||
|
Vector4& operator+=(const Type& v) noexcept; // adds v to x,y,z (and w via base? see notes)
|
||||||
|
Vector4& operator-=(const Type& v) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arithmetic (non-mutating)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Vector4 operator-() const noexcept;
|
||||||
|
constexpr Vector4 operator+(const Vector4&) const noexcept;
|
||||||
|
constexpr Vector4 operator-(const Vector4&) const noexcept;
|
||||||
|
constexpr Vector4 operator*(const Vector4&) const noexcept; // Hadamard
|
||||||
|
constexpr Vector4 operator/(const Vector4&) const noexcept; // Hadamard
|
||||||
|
constexpr Vector4 operator*(const Type& scalar) const noexcept;
|
||||||
|
constexpr Vector4 operator/(const Type& scalar) const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Geometry & helpers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr Type length_sqr() const noexcept; // x² + y² + z² + w²
|
||||||
|
Type length() const noexcept; // std::sqrt(length_sqr())
|
||||||
|
constexpr Type dot(const Vector4& rhs) const noexcept;
|
||||||
|
|
||||||
|
Vector4& abs() noexcept; // component-wise absolute
|
||||||
|
Vector4& clamp(const Type& min, const Type& max) noexcept;
|
||||||
|
// clamps x,y,z; leaves w unchanged (see notes)
|
||||||
|
constexpr Type sum() const noexcept; // x + y + z + w
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ImGui integration (optional)
|
||||||
|
|
||||||
|
Guarded by `OMATH_IMGUI_INTEGRATION`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#ifdef OMATH_IMGUI_INTEGRATION
|
||||||
|
constexpr ImVec4 to_im_vec4() const noexcept;
|
||||||
|
// NOTE: Provided signature returns Vector4<float> and (in current code) sets only x,y,z.
|
||||||
|
// See "Notes & caveats" for a corrected version you may prefer.
|
||||||
|
static Vector4<float> from_im_vec4(const ImVec4& other) noexcept;
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hashing & formatting
|
||||||
|
|
||||||
|
* **Hash specialization** (only for `Vector4<float>`):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<> struct std::hash<omath::Vector4<float>> {
|
||||||
|
std::size_t operator()(const omath::Vector4<float>&) const noexcept;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::unordered_map<omath::Vector4<float>, int> counts;
|
||||||
|
counts[{1.f, 2.f, 3.f, 1.f}] = 7;
|
||||||
|
```
|
||||||
|
|
||||||
|
* **`std::formatter`** (for any `Type`, all character kinds):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class Type>
|
||||||
|
struct std::formatter<omath::Vector4<Type>>; // -> "[x, y, z, w]"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & caveats (as implemented)
|
||||||
|
|
||||||
|
* `clamp(min,max)` **clamps only `x`, `y`, `z`** and **does not clamp `w`**. This may be intentional (e.g., when `w` is a homogeneous coordinate) — document your intent in your codebase.
|
||||||
|
If you want to clamp `w` too:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
w = std::clamp(w, min, max);
|
||||||
|
```
|
||||||
|
|
||||||
|
* **ImGui interop**:
|
||||||
|
|
||||||
|
* The header references `ImVec4` but does not include `<imgui.h>` itself. Ensure it’s included **before** this header whenever `OMATH_IMGUI_INTEGRATION` is defined.
|
||||||
|
* `from_im_vec4` currently returns `Vector4<float>` and (in the snippet shown) initializes **only x,y,z**. A more consistent version would be:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#ifdef OMATH_IMGUI_INTEGRATION
|
||||||
|
static Vector4<Type> from_im_vec4(const ImVec4& v) noexcept {
|
||||||
|
return {static_cast<Type>(v.x), static_cast<Type>(v.y),
|
||||||
|
static_cast<Type>(v.z), static_cast<Type>(v.w)};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
* Many scalar compound operators (`+= Type`, `-= Type`) are inherited from `Vector3<Type>`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API summary (signatures)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Ctors
|
||||||
|
constexpr Vector4() noexcept;
|
||||||
|
constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w);
|
||||||
|
|
||||||
|
// Equality & ordering
|
||||||
|
constexpr bool operator==(const Vector4&) const noexcept;
|
||||||
|
constexpr bool operator!=(const Vector4&) const noexcept;
|
||||||
|
bool operator< (const Vector4&) const noexcept;
|
||||||
|
bool operator> (const Vector4&) const noexcept;
|
||||||
|
bool operator<=(const Vector4&) const noexcept;
|
||||||
|
bool operator>=(const Vector4&) const noexcept;
|
||||||
|
|
||||||
|
// Mutating arithmetic
|
||||||
|
Vector4& operator+=(const Vector4&) noexcept;
|
||||||
|
Vector4& operator-=(const Vector4&) noexcept;
|
||||||
|
Vector4& operator*=(const Vector4&) noexcept;
|
||||||
|
Vector4& operator/=(const Vector4&) noexcept;
|
||||||
|
Vector4& operator*=(const Type&) noexcept;
|
||||||
|
Vector4& operator/=(const Type&) noexcept;
|
||||||
|
// (inherited) Vector4& operator+=(const Type&) noexcept;
|
||||||
|
// (inherited) Vector4& operator-=(const Type&) noexcept;
|
||||||
|
|
||||||
|
// Non-mutating arithmetic
|
||||||
|
constexpr Vector4 operator-() const noexcept;
|
||||||
|
constexpr Vector4 operator+(const Vector4&) const noexcept;
|
||||||
|
constexpr Vector4 operator-(const Vector4&) const noexcept;
|
||||||
|
constexpr Vector4 operator*(const Vector4&) const noexcept;
|
||||||
|
constexpr Vector4 operator/(const Vector4&) const noexcept;
|
||||||
|
constexpr Vector4 operator*(const Type&) const noexcept;
|
||||||
|
constexpr Vector4 operator/(const Type&) const noexcept;
|
||||||
|
|
||||||
|
// Geometry & helpers
|
||||||
|
constexpr Type length_sqr() const noexcept;
|
||||||
|
Type length() const noexcept;
|
||||||
|
constexpr Type dot(const Vector4&) const noexcept;
|
||||||
|
Vector4& abs() noexcept;
|
||||||
|
Vector4& clamp(const Type& min, const Type& max) noexcept;
|
||||||
|
constexpr Type sum() const noexcept;
|
||||||
|
|
||||||
|
// ImGui (optional)
|
||||||
|
#ifdef OMATH_IMGUI_INTEGRATION
|
||||||
|
constexpr ImVec4 to_im_vec4() const noexcept;
|
||||||
|
static Vector4<float> from_im_vec4(const ImVec4&) noexcept; // see note for preferred template version
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Hash (float) and formatter specializations provided below the class
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 31 Oct 2025*
|
||||||
188
docs/pathfinding/a_star.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# `omath::pathfinding::Astar` — Pathfinding over a navigation mesh
|
||||||
|
|
||||||
|
> Header: your project’s `pathfinding/astar.hpp`
|
||||||
|
> Namespace: `omath::pathfinding`
|
||||||
|
> Inputs: start/end as `Vector3<float>`, a `NavigationMesh`
|
||||||
|
> Output: ordered list of waypoints `std::vector<Vector3<float>>`
|
||||||
|
|
||||||
|
`Astar` exposes a single public function, `find_path`, that computes a path of 3D waypoints on a navigation mesh. Internally it reconstructs the result with `reconstruct_final_path` from a closed set keyed by `Vector3<float>`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::pathfinding {
|
||||||
|
|
||||||
|
struct PathNode; // holds per-node search data (see "Expected PathNode fields")
|
||||||
|
|
||||||
|
class Astar final {
|
||||||
|
public:
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::vector<Vector3<float>>
|
||||||
|
find_path(const Vector3<float>& start,
|
||||||
|
const Vector3<float>& end,
|
||||||
|
const NavigationMesh& nav_mesh) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::vector<Vector3<float>>
|
||||||
|
reconstruct_final_path(
|
||||||
|
const std::unordered_map<Vector3<float>, PathNode>& closed_list,
|
||||||
|
const Vector3<float>& current) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::pathfinding
|
||||||
|
```
|
||||||
|
|
||||||
|
### Semantics
|
||||||
|
|
||||||
|
* Returns a **polyline** of 3D points from `start` to `end`.
|
||||||
|
* If no path exists, the function typically returns an **empty vector** (behavior depends on implementation details; keep this contract in unit tests).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What `NavigationMesh` is expected to provide
|
||||||
|
|
||||||
|
The header doesn’t constrain `NavigationMesh`, but for A* it commonly needs:
|
||||||
|
|
||||||
|
* **Neighborhood queries**: given a position or node key → iterable neighbors.
|
||||||
|
* **Traversal cost**: `g(u,v)` (often Euclidean distance or edge weight).
|
||||||
|
* **Heuristic**: `h(x,end)` (commonly straight-line distance on the mesh).
|
||||||
|
* **Projection / snap**: the ability to map `start`/`end` to valid nodes/points on the mesh (if they are off-mesh).
|
||||||
|
|
||||||
|
> If your `NavigationMesh` doesn’t directly expose these, `Astar::find_path` likely does the adapter work (snapping to the nearest convex polygon/portal nodes and expanding across adjacency).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected `PathNode` fields
|
||||||
|
|
||||||
|
Although not visible here, `PathNode` typically carries:
|
||||||
|
|
||||||
|
* `Vector3<float> parent;` — predecessor position or key for backtracking
|
||||||
|
* `float g;` — cost from `start`
|
||||||
|
* `float h;` — heuristic to `end`
|
||||||
|
* `float f;` — `g + h`
|
||||||
|
|
||||||
|
`reconstruct_final_path(closed_list, current)` walks `parent` links from `current` back to the start, **reverses** the chain, and returns the path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Heuristic & optimality
|
||||||
|
|
||||||
|
* Use an **admissible** heuristic (never overestimates true cost) to keep A* optimal.
|
||||||
|
The usual choice is **Euclidean distance** in 3D:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
h(x, goal) = (goal - x).length();
|
||||||
|
```
|
||||||
|
* For best performance, make it **consistent** (triangle inequality holds). Euclidean distance is consistent on standard navmeshes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
|
||||||
|
Let `V` be explored vertices (or portal nodes) and `E` the traversed edges.
|
||||||
|
|
||||||
|
* With a binary heap open list: **O(E log V)** time, **O(V)** memory.
|
||||||
|
* With a d-ary heap or pairing heap you may reduce practical constants.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typical usage
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "omath/pathfinding/astar.hpp"
|
||||||
|
#include "omath/pathfinding/navigation_mesh.hpp"
|
||||||
|
|
||||||
|
using omath::Vector3;
|
||||||
|
using omath::pathfinding::Astar;
|
||||||
|
|
||||||
|
NavigationMesh nav = /* ... load/build mesh ... */;
|
||||||
|
|
||||||
|
Vector3<float> start{2.5f, 0.0f, -1.0f};
|
||||||
|
Vector3<float> goal {40.0f, 0.0f, 12.0f};
|
||||||
|
|
||||||
|
auto path = Astar::find_path(start, goal, nav);
|
||||||
|
|
||||||
|
if (!path.empty()) {
|
||||||
|
// feed to your agent/renderer
|
||||||
|
for (const auto& p : path) {
|
||||||
|
// draw waypoint p or push to steering
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// handle "no path" (e.g., unreachable or disconnected mesh)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & recommendations
|
||||||
|
|
||||||
|
* **Start/end snapping**: If `start` or `end` are outside the mesh, decide whether to snap to the nearest polygon/portal or fail early. Keep this behavior consistent and document it where `NavigationMesh` is defined.
|
||||||
|
* **Numerical stability**: Prefer squared distances when only comparing (`dist2`) to avoid unnecessary `sqrt`.
|
||||||
|
* **Tie-breaking**: When `f` ties are common (grid-like graphs), bias toward larger `g` or smaller `h` to reduce zig-zagging.
|
||||||
|
* **Smoothing**: A* returns a polyline that may hug polygon edges. Consider:
|
||||||
|
|
||||||
|
* **String pulling / Funnel algorithm** over the corridor of polygons to get a straightened path.
|
||||||
|
* **Raycast smoothing** (visibility checks) to remove redundant interior points.
|
||||||
|
* **Hashing `Vector3<float>`**: Your repo defines `std::hash<omath::Vector3<float>>`. Ensure equality/precision rules for using float keys are acceptable (or use discrete node IDs instead).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing checklist
|
||||||
|
|
||||||
|
* Start/end on the **same polygon** → direct path of 2 points.
|
||||||
|
* **Disconnected components** → empty result.
|
||||||
|
* **Narrow corridors** → path stays inside.
|
||||||
|
* **Obstacles blocking** → no path.
|
||||||
|
* **Floating-point noise** → still reconstructs a valid chain from parents.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal pseudo-implementation outline (for reference)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Pseudocode only — matches the header’s intent
|
||||||
|
std::vector<Vec3> find_path(start, goal, mesh) {
|
||||||
|
auto [snode, gnode] = mesh.snap_to_nodes(start, goal);
|
||||||
|
OpenSet open; // min-heap by f
|
||||||
|
std::unordered_map<Vec3, PathNode> closed;
|
||||||
|
|
||||||
|
open.push({snode, g=0, h=heuristic(snode, gnode)});
|
||||||
|
parents.clear();
|
||||||
|
|
||||||
|
while (!open.empty()) {
|
||||||
|
auto current = open.pop_min(); // node with lowest f
|
||||||
|
|
||||||
|
if (current.pos == gnode.pos)
|
||||||
|
return reconstruct_final_path(closed, current.pos);
|
||||||
|
|
||||||
|
for (auto [nbr, cost] : mesh.neighbors(current.pos)) {
|
||||||
|
float tentative_g = current.g + cost;
|
||||||
|
if (auto it = closed.find(nbr); it == closed.end() || tentative_g < it->second.g) {
|
||||||
|
closed[nbr] = { .parent = current.pos,
|
||||||
|
.g = tentative_g,
|
||||||
|
.h = heuristic(nbr, gnode.pos),
|
||||||
|
.f = tentative_g + heuristic(nbr, gnode.pos) };
|
||||||
|
open.push(closed[nbr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}; // no path
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
* **Why return `std::vector<Vector3<float>>` and not polygon IDs?**
|
||||||
|
Waypoints are directly usable by agents/steering and for rendering. If you also need the corridor (polygon chain), extend the API or `PathNode` to store it.
|
||||||
|
|
||||||
|
* **Does `find_path` modify the mesh?**
|
||||||
|
No; it should be a read-only search over `NavigationMesh`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 31 Oct 2025*
|
||||||
113
docs/pathfinding/navigation_mesh.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# `omath::pathfinding::NavigationMesh` — Lightweight vertex graph for A*
|
||||||
|
|
||||||
|
> Header: your project’s `pathfinding/navigation_mesh.hpp`
|
||||||
|
> Namespace: `omath::pathfinding`
|
||||||
|
> Nodes: `Vector3<float>` (3D points)
|
||||||
|
> Storage: adjacency map `unordered_map<Vector3<float>, std::vector<Vector3<float>>>`
|
||||||
|
|
||||||
|
A minimal navigation mesh represented as a **vertex/edge graph**. Each vertex is a `Vector3<float>` and neighbors are stored in an adjacency list. Designed to pair with `Astar::find_path`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class NavigationMesh final {
|
||||||
|
public:
|
||||||
|
// Nearest graph vertex to an arbitrary 3D point.
|
||||||
|
// On success -> closest vertex; on failure -> std::string error (e.g., empty mesh).
|
||||||
|
[[nodiscard]]
|
||||||
|
std::expected<Vector3<float>, std::string>
|
||||||
|
get_closest_vertex(const Vector3<float>& point) const noexcept;
|
||||||
|
|
||||||
|
// Read-only neighbor list for a vertex key.
|
||||||
|
// If vertex is absent, implementation should return an empty list (see notes).
|
||||||
|
[[nodiscard]]
|
||||||
|
const std::vector<Vector3<float>>&
|
||||||
|
get_neighbors(const Vector3<float>& vertex) const noexcept;
|
||||||
|
|
||||||
|
// True if the graph has no vertices/edges.
|
||||||
|
[[nodiscard]]
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
|
// Serialize/deserialize the graph (opaque binary).
|
||||||
|
[[nodiscard]] std::vector<uint8_t> serialize() const noexcept;
|
||||||
|
void deserialize(const std::vector<uint8_t>& raw) noexcept;
|
||||||
|
|
||||||
|
// Public adjacency (vertex -> neighbors)
|
||||||
|
std::unordered_map<Vector3<float>, std::vector<Vector3<float>>> m_vertex_map;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using omath::Vector3;
|
||||||
|
using omath::pathfinding::NavigationMesh;
|
||||||
|
|
||||||
|
// Build a tiny mesh (triangle)
|
||||||
|
NavigationMesh nav;
|
||||||
|
nav.m_vertex_map[ {0,0,0} ] = { {1,0,0}, {0,0,1} };
|
||||||
|
nav.m_vertex_map[ {1,0,0} ] = { {0,0,0}, {0,0,1} };
|
||||||
|
nav.m_vertex_map[ {0,0,1} ] = { {0,0,0}, {1,0,0} };
|
||||||
|
|
||||||
|
// Query the closest node to an arbitrary point
|
||||||
|
auto q = nav.get_closest_vertex({0.3f, 0.0f, 0.2f});
|
||||||
|
if (q) {
|
||||||
|
const auto& v = *q;
|
||||||
|
const auto& nbrs = nav.get_neighbors(v);
|
||||||
|
(void)nbrs;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Semantics & expectations
|
||||||
|
|
||||||
|
* **Nearest vertex**
|
||||||
|
`get_closest_vertex(p)` should scan known vertices and return the one with minimal Euclidean distance to `p`. If the mesh is empty, expect an error (`unexpected` with a message).
|
||||||
|
|
||||||
|
* **Neighbors**
|
||||||
|
`get_neighbors(v)` returns the adjacency list for `v`. If `v` is not present, a conventional behavior is to return a **reference to a static empty vector** (since the API is `noexcept` and returns by reference). Verify in your implementation.
|
||||||
|
|
||||||
|
* **Graph invariants** (recommended)
|
||||||
|
|
||||||
|
* Neighbor links are **symmetric** for undirected navigation (if `u` has `v`, then `v` has `u`).
|
||||||
|
* No self-loops unless explicitly desired.
|
||||||
|
* Vertices are unique keys; hashing uses `std::hash<Vector3<float>>` (be mindful of floating-point equality).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Serialization
|
||||||
|
|
||||||
|
* `serialize()` → opaque, implementation-defined binary of the current `m_vertex_map`.
|
||||||
|
* `deserialize(raw)` → restores the internal map from `raw`.
|
||||||
|
Keep versioning in mind if you evolve the format (e.g., add a header/magic/version).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
Let `V = m_vertex_map.size()` and `E = Σ|neighbors(v)|`.
|
||||||
|
|
||||||
|
* `get_closest_vertex`: **O(V)** (linear scan) unless you back it with a spatial index (KD-tree, grid, etc.).
|
||||||
|
* `get_neighbors`: **O(1)** average (hash lookup).
|
||||||
|
* Memory: **O(V + E)**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage notes
|
||||||
|
|
||||||
|
* **Floating-point keys**: Using `Vector3<float>` as an unordered_map key relies on your `std::hash<omath::Vector3<float>>` and exact `operator==`. Avoid building meshes with numerically “close but not equal” duplicates; consider canonicalizing or using integer IDs if needed.
|
||||||
|
* **Pathfinding**: Pair with `Astar::find_path(start, end, nav)`; the A* heuristic can use straight-line distance between vertex positions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal test ideas
|
||||||
|
|
||||||
|
* Empty mesh → `get_closest_vertex` returns error; `empty() == true`.
|
||||||
|
* Single vertex → nearest always that vertex; neighbors empty.
|
||||||
|
* Symmetric edges → `get_neighbors(a)` contains `b` and vice versa.
|
||||||
|
* Serialization round-trip preserves vertex/edge counts and neighbor lists.
|
||||||
161
docs/projectile_prediction/proj_pred_engine_avx2.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# `omath::projectile_prediction::ProjPredEngineAvx2` — AVX2-accelerated ballistic aim solver
|
||||||
|
|
||||||
|
> Header: your project’s `projectile_prediction/proj_pred_engine_avx2.hpp`
|
||||||
|
> Namespace: `omath::projectile_prediction`
|
||||||
|
> Inherits: `ProjPredEngineInterface`
|
||||||
|
> Depends on: `Vector3<float>`, `Projectile`, `Target`
|
||||||
|
> CPU: Uses AVX2 when available; falls back to scalar elsewhere (fields are marked `[[maybe_unused]]` for non-x86/AVX2 builds).
|
||||||
|
|
||||||
|
This engine computes a **world-space aim point** (and implicitly the firing **yaw/pitch**) to intersect a moving target under a **constant gravity** model and **constant muzzle speed**. It typically scans candidate times of flight and solves for the elevation (`pitch`) that makes the vertical and horizontal kinematics meet at the same time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class ProjPredEngineAvx2 final : public ProjPredEngineInterface {
|
||||||
|
public:
|
||||||
|
[[nodiscard]]
|
||||||
|
std::optional<Vector3<float>>
|
||||||
|
maybe_calculate_aim_point(const Projectile& projectile,
|
||||||
|
const Target& target) const override;
|
||||||
|
|
||||||
|
ProjPredEngineAvx2(float gravity_constant,
|
||||||
|
float simulation_time_step,
|
||||||
|
float maximum_simulation_time);
|
||||||
|
~ProjPredEngineAvx2() override = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Solve for pitch at a fixed time-of-flight t.
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::optional<float>
|
||||||
|
calculate_pitch(const Vector3<float>& proj_origin,
|
||||||
|
const Vector3<float>& target_pos,
|
||||||
|
float bullet_gravity, float v0, float time);
|
||||||
|
|
||||||
|
// Tunables (may be unused on non-AVX2 builds)
|
||||||
|
[[maybe_unused]] const float m_gravity_constant; // |g| (e.g., 9.81)
|
||||||
|
[[maybe_unused]] const float m_simulation_time_step; // Δt (e.g., 1/240 s)
|
||||||
|
[[maybe_unused]] const float m_maximum_simulation_time; // Tmax (e.g., 3 s)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters (constructor)
|
||||||
|
|
||||||
|
* `gravity_constant` — magnitude of gravity (units consistent with your world, e.g., **m/s²**).
|
||||||
|
* `simulation_time_step` — Δt used to scan candidate intercept times.
|
||||||
|
* `maximum_simulation_time` — cap on time of flight; larger allows longer-range solutions but increases cost.
|
||||||
|
|
||||||
|
### Return (solver)
|
||||||
|
|
||||||
|
* `maybe_calculate_aim_point(...)`
|
||||||
|
|
||||||
|
* **`Vector3<float>`**: a world-space **aim point** yielding an intercept under the model.
|
||||||
|
* **`std::nullopt`**: no feasible solution (e.g., target receding too fast, out of range, or kinematics inconsistent).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How it solves (expected flow)
|
||||||
|
|
||||||
|
1. **Predict target at time `t`** (constant-velocity model unless your `Target` carries more):
|
||||||
|
|
||||||
|
```
|
||||||
|
T(t) = target.position + target.velocity * t
|
||||||
|
```
|
||||||
|
2. **Horizontal/vertical kinematics at fixed `t`** with muzzle speed `v0` and gravity `g`:
|
||||||
|
|
||||||
|
* Let `Δ = T(t) - proj_origin`, `d = length(Δ.xz)`, `h = Δ.y`.
|
||||||
|
* Required initial components:
|
||||||
|
|
||||||
|
```
|
||||||
|
cosθ = d / (v0 * t)
|
||||||
|
sinθ = (h + 0.5 * g * t^2) / (v0 * t)
|
||||||
|
```
|
||||||
|
* If `cosθ` ∈ [−1,1] and `sinθ` ∈ [−1,1] and `sin²θ + cos²θ ≈ 1`, then
|
||||||
|
|
||||||
|
```
|
||||||
|
θ = atan2(sinθ, cosθ)
|
||||||
|
```
|
||||||
|
|
||||||
|
That is what `calculate_pitch(...)` returns on success.
|
||||||
|
3. **Yaw** is the azimuth toward `Δ.xz`.
|
||||||
|
4. **Pick the earliest feasible `t`** in `[Δt, Tmax]` (scanned in steps of `Δt`; AVX2 batches several `t` at once).
|
||||||
|
5. **Return the aim point.** Common choices:
|
||||||
|
|
||||||
|
* The **impact point** `T(t*)` (useful as a HUD marker), or
|
||||||
|
* A point along the **initial firing ray** at some convenient range using `(yaw, pitch)`; both are consistent—pick the convention your caller expects.
|
||||||
|
|
||||||
|
> The private `calculate_pitch(...)` matches step **2** and returns `nullopt` if the trigonometric constraints are violated for that `t`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AVX2 notes
|
||||||
|
|
||||||
|
* On x86/x64 with AVX2, candidate times `t` can be evaluated **8 at a time** using FMA (great for dense scans).
|
||||||
|
* On ARM/ARM64 (no AVX2), code falls back to scalar math; the `[[maybe_unused]]` members acknowledge compilation without SIMD.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
ProjPredEngineAvx2 solver(
|
||||||
|
/*gravity*/ 9.81f,
|
||||||
|
/*dt*/ 1.0f/240.0f,
|
||||||
|
/*Tmax*/ 3.0f
|
||||||
|
);
|
||||||
|
|
||||||
|
Projectile proj; // fill: origin, muzzle_speed, etc.
|
||||||
|
Target tgt; // fill: position, velocity
|
||||||
|
|
||||||
|
if (auto aim = solver.maybe_calculate_aim_point(proj, tgt)) {
|
||||||
|
// Aim your weapon at *aim and fire with muzzle speed proj.v0
|
||||||
|
// If you need yaw/pitch explicitly, replicate the pitch solve and azimuth.
|
||||||
|
} else {
|
||||||
|
// No solution (out of envelope) — pick a fallback
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Edge cases & failure modes
|
||||||
|
|
||||||
|
* **Zero or tiny `v0`** → no solution.
|
||||||
|
* **Target collinear & receding faster than `v0`** → no solution.
|
||||||
|
* **`t` constraints**: if viable solutions exist only beyond `Tmax`, you’ll get `nullopt`.
|
||||||
|
* **Geometric infeasibility** at a given `t` (e.g., `d > v0*t`) causes `calculate_pitch` to fail that sample.
|
||||||
|
* **Numerical tolerance**: check `sin²θ + cos²θ` against 1 with a small epsilon (e.g., `1e-3`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance & tuning
|
||||||
|
|
||||||
|
* Work is roughly `O(Nt)` where `Nt ≈ Tmax / Δt`.
|
||||||
|
* Smaller `Δt` → better accuracy, higher cost. With AVX2 you can afford smaller steps.
|
||||||
|
* If you frequently miss solutions **between** steps, consider:
|
||||||
|
|
||||||
|
* **Coarse-to-fine**: coarse scan, then local refine around the best `t`.
|
||||||
|
* **Newton on time**: root-find `‖horizontal‖ − v0 t cosθ(t) = 0` shaped from the kinematics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing checklist
|
||||||
|
|
||||||
|
* **Stationary target** at same height → θ ≈ 0, aim point ≈ target.
|
||||||
|
* **Higher target** → positive pitch; **lower target** → negative pitch.
|
||||||
|
* **Perpendicular moving target** → feasible at moderate speeds.
|
||||||
|
* **Very fast receding target** → `nullopt`.
|
||||||
|
* **Boundary**: `d ≈ v0*Tmax` and `h` large → verify pass/fail around thresholds.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `ProjPredEngineInterface` — base interface and general contract
|
||||||
|
* `Projectile`, `Target` — data carriers for solver inputs (speed, origin, position, velocity, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
184
docs/projectile_prediction/proj_pred_engine_legacy.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# `omath::projectile_prediction::ProjPredEngineLegacy` — Legacy trait-based aim solver
|
||||||
|
|
||||||
|
> Header: `omath/projectile_prediction/proj_pred_engine_legacy.hpp`
|
||||||
|
> Namespace: `omath::projectile_prediction`
|
||||||
|
> Inherits: `ProjPredEngineInterface`
|
||||||
|
> Template param (default): `EngineTrait = source_engine::PredEngineTrait`
|
||||||
|
> Purpose: compute a world-space **aim point** to hit a (possibly moving) target using a **discrete time scan** and a **closed-form ballistic pitch** under constant gravity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`ProjPredEngineLegacy` is a portable, trait-driven projectile lead solver. At each simulation time step `t` it:
|
||||||
|
|
||||||
|
1. **Predicts target position** with `EngineTrait::predict_target_position(target, t, g)`.
|
||||||
|
2. **Computes launch pitch** via a gravity-aware closed form (or a direct angle if gravity is zero).
|
||||||
|
3. **Validates** that a projectile fired with that pitch (and direct yaw) actually reaches the predicted target within a **distance tolerance** at time `t`.
|
||||||
|
4. On success, **returns an aim point** computed by `EngineTrait::calc_viewpoint_from_angles(...)`.
|
||||||
|
|
||||||
|
If no time step yields a feasible solution up to `maximum_simulation_time`, returns `std::nullopt`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class EngineTrait = source_engine::PredEngineTrait>
|
||||||
|
requires PredEngineConcept<EngineTrait>
|
||||||
|
class ProjPredEngineLegacy final : public ProjPredEngineInterface {
|
||||||
|
public:
|
||||||
|
ProjPredEngineLegacy(float gravity_constant,
|
||||||
|
float simulation_time_step,
|
||||||
|
float maximum_simulation_time,
|
||||||
|
float distance_tolerance);
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::optional<Vector3<float>>
|
||||||
|
maybe_calculate_aim_point(const Projectile& projectile,
|
||||||
|
const Target& target) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Closed-form ballistic pitch solver (internal)
|
||||||
|
std::optional<float>
|
||||||
|
maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile,
|
||||||
|
const Vector3<float>& target_position) const noexcept;
|
||||||
|
|
||||||
|
bool is_projectile_reached_target(const Vector3<float>& target_position,
|
||||||
|
const Projectile& projectile,
|
||||||
|
float pitch, float time) const noexcept;
|
||||||
|
|
||||||
|
const float m_gravity_constant;
|
||||||
|
const float m_simulation_time_step;
|
||||||
|
const float m_maximum_simulation_time;
|
||||||
|
const float m_distance_tolerance;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Constructor parameters
|
||||||
|
|
||||||
|
* `gravity_constant` — magnitude of gravity (e.g., `9.81f`), world units/s².
|
||||||
|
* `simulation_time_step` — Δt for the scan (e.g., `1/240.f`).
|
||||||
|
* `maximum_simulation_time` — search horizon in seconds.
|
||||||
|
* `distance_tolerance` — max allowed miss distance at time `t` to accept a solution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trait requirements (`PredEngineConcept`)
|
||||||
|
|
||||||
|
Your `EngineTrait` must expose **noexcept** static methods with these signatures:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Vector3<float> predict_projectile_position(const Projectile&, float pitch_deg, float yaw_deg,
|
||||||
|
float time, float gravity) noexcept;
|
||||||
|
|
||||||
|
Vector3<float> predict_target_position(const Target&, float time, float gravity) noexcept;
|
||||||
|
|
||||||
|
float calc_vector_2d_distance(const Vector3<float>& v) noexcept; // typically length in XZ plane
|
||||||
|
float get_vector_height_coordinate(const Vector3<float>& v) noexcept; // typically Y
|
||||||
|
|
||||||
|
Vector3<float> calc_viewpoint_from_angles(const Projectile&, Vector3<float> target,
|
||||||
|
std::optional<float> maybe_pitch_deg) noexcept;
|
||||||
|
|
||||||
|
float calc_direct_pitch_angle(const Vector3<float>& from, const Vector3<float>& to) noexcept;
|
||||||
|
float calc_direct_yaw_angle (const Vector3<float>& from, const Vector3<float>& to) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
> This design lets you adapt different game/physics conventions (axes, units, handedness) without changing the solver.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Algorithm details
|
||||||
|
|
||||||
|
### Time scan
|
||||||
|
|
||||||
|
For `t = 0 .. maximum_simulation_time` in steps of `simulation_time_step`:
|
||||||
|
|
||||||
|
1. `T = EngineTrait::predict_target_position(target, t, g)`
|
||||||
|
2. `pitch = maybe_calculate_projectile_launch_pitch_angle(projectile, T)`
|
||||||
|
|
||||||
|
* If `std::nullopt`: continue
|
||||||
|
3. `yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin, T)`
|
||||||
|
4. `P = EngineTrait::predict_projectile_position(projectile, pitch, yaw, t, g)`
|
||||||
|
5. Accept if `|P - T| <= distance_tolerance`
|
||||||
|
6. Return `EngineTrait::calc_viewpoint_from_angles(projectile, T, pitch)`
|
||||||
|
|
||||||
|
### Closed-form pitch (gravity on)
|
||||||
|
|
||||||
|
Implements the classic ballistic formula (low-arc branch), where:
|
||||||
|
|
||||||
|
* `v` = muzzle speed,
|
||||||
|
* `g` = `gravity_constant * projectile.m_gravity_scale`,
|
||||||
|
* `x` = horizontal (2D) distance to target,
|
||||||
|
* `y` = vertical offset to target.
|
||||||
|
|
||||||
|
[
|
||||||
|
\theta ;=; \arctan!\left(\frac{v^{2} ;-; \sqrt{v^{4}-g!\left(gx^{2}+2yv^{2}\right)}}{gx}\right)
|
||||||
|
]
|
||||||
|
|
||||||
|
* If the **discriminant** ( v^{4}-g(gx^{2}+2yv^{2}) < 0 ) ⇒ **no real solution**.
|
||||||
|
* If `g == 0`, falls back to `EngineTrait::calc_direct_pitch_angle(...)`.
|
||||||
|
* Returns **degrees** (internally converts from radians).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
ProjPredEngineLegacy solver(
|
||||||
|
/*gravity*/ 9.81f,
|
||||||
|
/*dt*/ 1.f / 240.f,
|
||||||
|
/*Tmax*/ 3.0f,
|
||||||
|
/*tol*/ 0.05f
|
||||||
|
);
|
||||||
|
|
||||||
|
Projectile proj; // fill: m_origin, m_launch_speed, m_gravity_scale, etc.
|
||||||
|
Target tgt; // fill: position/velocity as required by your trait
|
||||||
|
|
||||||
|
if (auto aim = solver.maybe_calculate_aim_point(proj, tgt)) {
|
||||||
|
// Drive your turret/reticle toward *aim
|
||||||
|
} else {
|
||||||
|
// No feasible intercept in the given horizon
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Behavior & edge cases
|
||||||
|
|
||||||
|
* **Zero gravity or zero distance**: uses direct pitch toward the target.
|
||||||
|
* **Negative discriminant** in the pitch formula: returns `std::nullopt` for that time step.
|
||||||
|
* **Very small `x`** (horizontal distance): the formula’s denominator `gx` approaches zero; your trait’s direct pitch helper provides a stable fallback.
|
||||||
|
* **Tolerance**: `distance_tolerance` controls acceptance; tighten for accuracy, loosen for robustness.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complexity & tuning
|
||||||
|
|
||||||
|
* Time: **O(T)** where ( T \approx \frac{\text{maximum_simulation_time}}{\text{simulation_time_step}} )
|
||||||
|
plus trait costs for prediction and angle math per step.
|
||||||
|
* Smaller `simulation_time_step` improves precision but increases runtime.
|
||||||
|
* If needed, do a **coarse-to-fine** search: coarse Δt scan, then refine around the best hit time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing checklist
|
||||||
|
|
||||||
|
* Stationary, level target → pitch ≈ 0 for short ranges; accepted within tolerance.
|
||||||
|
* Elevated/depressed targets → pitch positive/negative as expected.
|
||||||
|
* Receding fast target → unsolved within horizon ⇒ `nullopt`.
|
||||||
|
* Gravity scale = 0 → identical to straight-line solution.
|
||||||
|
* Near-horizon shots (large range, small arc) → discriminant near zero; verify stability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
* All angles produced/consumed by the trait in this implementation are **degrees**.
|
||||||
|
* `calc_viewpoint_from_angles` defines what “aim point” means in your engine (e.g., a point along the initial ray or the predicted impact point). Keep this consistent with your HUD/reticle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
96
docs/projectile_prediction/projectile.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# `omath::projectile_prediction::Projectile` — Projectile parameters for aim solvers
|
||||||
|
|
||||||
|
> Header: `omath/projectile_prediction/projectile.hpp`
|
||||||
|
> Namespace: `omath::projectile_prediction`
|
||||||
|
> Used by: `ProjPredEngineInterface` implementations (e.g., `ProjPredEngineLegacy`, `ProjPredEngineAvx2`)
|
||||||
|
|
||||||
|
`Projectile` is a tiny data holder that describes how a projectile is launched: **origin** (world position), **launch speed**, and a **gravity scale** (multiplier applied to the engine’s gravity constant).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::projectile_prediction {
|
||||||
|
|
||||||
|
class Projectile final {
|
||||||
|
public:
|
||||||
|
Vector3<float> m_origin; // Launch position (world space)
|
||||||
|
float m_launch_speed{}; // Initial speed magnitude (units/sec)
|
||||||
|
float m_gravity_scale{}; // Multiplier for global gravity (dimensionless)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::projectile_prediction
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Field semantics
|
||||||
|
|
||||||
|
* **`m_origin`**
|
||||||
|
World-space position where the projectile is spawned (e.g., muzzle or emitter point).
|
||||||
|
|
||||||
|
* **`m_launch_speed`**
|
||||||
|
Initial speed **magnitude** in your world units per second. Direction is determined by the solver (from yaw/pitch).
|
||||||
|
|
||||||
|
* Must be **non-negative**. Zero disables meaningful ballistic solutions.
|
||||||
|
|
||||||
|
* **`m_gravity_scale`**
|
||||||
|
Multiplies the engine’s gravity constant provided to the solver (e.g., `g = gravity_constant * m_gravity_scale`).
|
||||||
|
|
||||||
|
* Use `1.0f` for normal gravity, `0.0f` for no-drop projectiles, other values to simulate heavier/lighter rounds.
|
||||||
|
|
||||||
|
> Units must be consistent across your project (e.g., meters & seconds). If `gravity_constant = 9.81f`, then `m_launch_speed` is in m/s and positions are in meters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typical usage
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
Projectile proj;
|
||||||
|
proj.m_origin = { 0.0f, 1.6f, 0.0f }; // player eye / muzzle height
|
||||||
|
proj.m_launch_speed = 850.0f; // e.g., 850 m/s
|
||||||
|
proj.m_gravity_scale = 1.0f; // normal gravity
|
||||||
|
|
||||||
|
// With an aim solver:
|
||||||
|
auto aim = engine->maybe_calculate_aim_point(proj, target);
|
||||||
|
if (aim) {
|
||||||
|
// rotate/aim toward *aim and fire
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## With gravity-aware solver (outline)
|
||||||
|
|
||||||
|
Engines typically compute the firing angles to reach a predicted target position:
|
||||||
|
|
||||||
|
* Horizontal distance `x` and vertical offset `y` are derived from `target - m_origin`.
|
||||||
|
* Gravity used is `g = gravity_constant * m_gravity_scale`.
|
||||||
|
* Launch direction has speed `m_launch_speed` and angles solved by the engine.
|
||||||
|
|
||||||
|
If `m_gravity_scale == 0`, engines usually fall back to straight-line (no-drop) solutions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation & tips
|
||||||
|
|
||||||
|
* Keep `m_launch_speed ≥ 0`. Negative values are nonsensical.
|
||||||
|
* If your weapon can vary muzzle speed (charge-up, attachments), update `m_launch_speed` per shot.
|
||||||
|
* For different ammo types (tracers, grenades), prefer tweaking **`m_gravity_scale`** (and possibly the engine’s gravity constant) to match observed arc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `ProjPredEngineInterface` — common interface for aim solvers
|
||||||
|
* `ProjPredEngineLegacy` — trait-based, time-stepped ballistic solver
|
||||||
|
* `ProjPredEngineAvx2` — AVX2-accelerated solver with fixed-time pitch solve
|
||||||
|
* `Target` — target state consumed by the solvers
|
||||||
|
* `Vector3<float>` — math type used for positions and directions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
162
docs/projectile_prediction/projectile_engine.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# `omath::projectile_prediction::ProjPredEngineInterface` — Aim-point solver interface
|
||||||
|
|
||||||
|
> Header: your project’s `projectile_prediction/proj_pred_engine_interface.hpp`
|
||||||
|
> Namespace: `omath::projectile_prediction`
|
||||||
|
> Depends on: `Vector3<float>`, `Projectile`, `Target`
|
||||||
|
> Purpose: **contract** for engines that compute a lead/aim point to hit a moving target.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`ProjPredEngineInterface` defines a single pure-virtual method that attempts to compute the **world-space aim point** where a projectile should be launched to intersect a target under the engine’s physical model (e.g., constant projectile speed, gravity, drag, max flight time, etc.).
|
||||||
|
|
||||||
|
If a valid solution exists, the engine returns the 3D aim point. Otherwise, it returns `std::nullopt` (no feasible intercept).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::projectile_prediction {
|
||||||
|
|
||||||
|
class ProjPredEngineInterface {
|
||||||
|
public:
|
||||||
|
[[nodiscard]]
|
||||||
|
virtual std::optional<Vector3<float>>
|
||||||
|
maybe_calculate_aim_point(const Projectile& projectile,
|
||||||
|
const Target& target) const = 0;
|
||||||
|
|
||||||
|
virtual ~ProjPredEngineInterface() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::projectile_prediction
|
||||||
|
```
|
||||||
|
|
||||||
|
### Semantics
|
||||||
|
|
||||||
|
* **Input**
|
||||||
|
|
||||||
|
* `Projectile` — engine-specific projectile properties (typical: muzzle speed, gravity vector, drag flag/coeff, max range / flight time).
|
||||||
|
* `Target` — target state (typical: position, velocity, possibly acceleration).
|
||||||
|
|
||||||
|
* **Output**
|
||||||
|
|
||||||
|
* `std::optional<Vector3<float>>`
|
||||||
|
|
||||||
|
* `value()` — world-space point to aim at **now** so that the projectile intersects the target under the model.
|
||||||
|
* `std::nullopt` — no solution (e.g., target outruns projectile, blocked by constraints, numerical failure).
|
||||||
|
|
||||||
|
* **No side effects**: method is `const` and should not modify inputs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typical usage
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
std::unique_ptr<ProjPredEngineInterface> engine = /* your implementation */;
|
||||||
|
Projectile proj = /* fill from weapon config */;
|
||||||
|
Target tgt = /* read from tracking system */;
|
||||||
|
|
||||||
|
if (auto aim = engine->maybe_calculate_aim_point(proj, tgt)) {
|
||||||
|
// Rotate/steer to (*aim)
|
||||||
|
} else {
|
||||||
|
// Fall back: no-lead, predictive UI, or do not fire
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation guidance (for engine authors)
|
||||||
|
|
||||||
|
**Common models:**
|
||||||
|
|
||||||
|
1. **No gravity, constant speed**
|
||||||
|
Closed form intersect time `t` solves `‖p_t + v_t t − p_0‖ = v_p t`.
|
||||||
|
Choose the smallest non-negative real root; aim point = `p_t + v_t t`.
|
||||||
|
|
||||||
|
2. **Gravity (constant g), constant speed**
|
||||||
|
Solve ballistics with vertical drop: either numerical (Newton–Raphson on time) or 2D elevation + azimuth decomposition. Ensure convergence caps and time bounds.
|
||||||
|
|
||||||
|
3. **Drag**
|
||||||
|
Typically requires numeric integration (e.g., RK4) wrapped in a root find on time-of-flight.
|
||||||
|
|
||||||
|
**Robustness tips:**
|
||||||
|
|
||||||
|
* **Feasibility checks:** return `nullopt` when:
|
||||||
|
|
||||||
|
* projectile speed ≤ 0; target too fast in receding direction; solution time outside `[0, t_max]`.
|
||||||
|
* **Bounds:** clamp search time to reasonable `[t_min, t_max]` (e.g., `[0, max_flight_time]` or by range).
|
||||||
|
* **Tolerances:** use epsilons for convergence (e.g., `|f(t)| < 1e-4`, `|Δt| < 1e-4 s`).
|
||||||
|
* **Determinism:** fix iteration counts or seeds if needed for replayability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: constant-speed, no-gravity intercept (closed form)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Solve ||p + v t|| = s t where p = target_pos - shooter_pos, v = target_vel, s = projectile_speed
|
||||||
|
// Quadratic: (v·v - s^2) t^2 + 2 (p·v) t + (p·p) = 0
|
||||||
|
inline std::optional<float> intercept_time_no_gravity(const Vector3<float>& p,
|
||||||
|
const Vector3<float>& v,
|
||||||
|
float s) {
|
||||||
|
const float a = v.dot(v) - s*s;
|
||||||
|
const float b = 2.f * p.dot(v);
|
||||||
|
const float c = p.dot(p);
|
||||||
|
if (std::abs(a) < 1e-6f) { // near linear
|
||||||
|
if (std::abs(b) < 1e-6f) return std::nullopt;
|
||||||
|
float t = -c / b;
|
||||||
|
return t >= 0.f ? std::optional{t} : std::nullopt;
|
||||||
|
}
|
||||||
|
const float disc = b*b - 4.f*a*c;
|
||||||
|
if (disc < 0.f) return std::nullopt;
|
||||||
|
const float sqrtD = std::sqrt(disc);
|
||||||
|
float t1 = (-b - sqrtD) / (2.f*a);
|
||||||
|
float t2 = (-b + sqrtD) / (2.f*a);
|
||||||
|
float t = (t1 >= 0.f ? t1 : t2);
|
||||||
|
return t >= 0.f ? std::optional{t} : std::nullopt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Aim point (given shooter origin `S`, target pos `T`, vel `V`):
|
||||||
|
|
||||||
|
```
|
||||||
|
p = T - S
|
||||||
|
t* = intercept_time_no_gravity(p, V, speed)
|
||||||
|
aim = T + V * t*
|
||||||
|
```
|
||||||
|
|
||||||
|
Return `nullopt` if `t*` is absent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing checklist
|
||||||
|
|
||||||
|
* **Stationary target**: aim point equals target position when `s > 0`.
|
||||||
|
* **Target perpendicular motion**: lead equals lateral displacement `V⊥ * t`.
|
||||||
|
* **Receding too fast**: expect `nullopt`.
|
||||||
|
* **Gravity model**: verify arc solutions exist for short & long trajectories (if implemented).
|
||||||
|
* **Numerics**: convergence within max iterations; monotonic improvement of residuals.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
* This is an **interface** only; concrete engines (e.g., `SimpleNoGravityEngine`, `BallisticGravityEngine`) should document their assumptions (gravity, drag, wind, bounds) and units (meters, seconds).
|
||||||
|
* The coordinate system and handedness should be consistent with `Vector3<float>` and the rest of your math stack.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Projectile Documentation](projectile.md) - Projectile properties
|
||||||
|
- [Target Documentation](target.md) - Target state representation
|
||||||
|
- [Legacy Implementation](proj_pred_engine_legacy.md) - Standard projectile prediction engine
|
||||||
|
- [AVX2 Implementation](proj_pred_engine_avx2.md) - Optimized AVX2 engine
|
||||||
|
- [Tutorials - Projectile Prediction](../tutorials.md#tutorial-3-projectile-prediction-aim-bot) - Complete aim-bot tutorial
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
70
docs/projectile_prediction/target.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# `omath::projectile_prediction::Target` — Target state for aim solvers
|
||||||
|
|
||||||
|
> Header: `omath/projectile_prediction/target.hpp`
|
||||||
|
> Namespace: `omath::projectile_prediction`
|
||||||
|
> Used by: `ProjPredEngineInterface` implementations (e.g., Legacy/AVX2 engines)
|
||||||
|
|
||||||
|
A small POD-style container describing a target’s **current pose** and **motion** for projectile lead/aim computations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath::projectile_prediction {
|
||||||
|
|
||||||
|
class Target final {
|
||||||
|
public:
|
||||||
|
Vector3<float> m_origin; // Current world-space position of the target
|
||||||
|
Vector3<float> m_velocity; // World-space linear velocity (units/sec)
|
||||||
|
bool m_is_airborne{}; // Domain hint (e.g., ignore ground snapping)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath::projectile_prediction
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Field semantics
|
||||||
|
|
||||||
|
* **`m_origin`** — target position in world coordinates (same units as your `Vector3<float>` grid).
|
||||||
|
* **`m_velocity`** — instantaneous linear velocity. Solvers commonly assume **constant velocity** between “now” and impact unless your trait injects gravity/accel.
|
||||||
|
* **`m_is_airborne`** — optional hint for engine/trait logic (e.g., apply gravity to the target, skip ground friction/snap). Exact meaning is engine-dependent.
|
||||||
|
|
||||||
|
> Keep units consistent with your projectile model (e.g., meters & seconds). If projectiles use `g = 9.81 m/s²`, velocity should be in m/s and positions in meters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typical usage
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
Target tgt;
|
||||||
|
tgt.m_origin = { 42.0f, 1.8f, -7.5f };
|
||||||
|
tgt.m_velocity = { 3.0f, 0.0f, 0.0f }; // moving +X at 3 units/s
|
||||||
|
tgt.m_is_airborne = false;
|
||||||
|
|
||||||
|
// Feed into an aim solver with a Projectile
|
||||||
|
auto aim = engine->maybe_calculate_aim_point(projectile, tgt);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & tips
|
||||||
|
|
||||||
|
* If you track acceleration (e.g., gravity on ragdolls), your **EngineTrait** may derive it from `m_is_airborne` and world gravity; otherwise most solvers treat the target’s motion as linear.
|
||||||
|
* For highly agile targets, refresh `m_origin`/`m_velocity` every tick and re-solve; don’t reuse stale aim points.
|
||||||
|
* Precision: `Vector3<float>` is typically enough; if you need sub-millimeter accuracy over long ranges, consider double-precision internally in your trait.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `Projectile` — shooter origin, muzzle speed, gravity scale
|
||||||
|
* `ProjPredEngineInterface` — common interface for aim solvers
|
||||||
|
* `ProjPredEngineLegacy`, `ProjPredEngineAvx2` — concrete solvers using this data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
270
docs/projection/camera.md
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
# `omath::projection::Camera` — Generic, trait-driven camera with screen/world conversion
|
||||||
|
|
||||||
|
> Header: `omath/projection/camera.hpp` (this header)
|
||||||
|
> Namespace: `omath::projection`
|
||||||
|
> Template: `Camera<Mat4X4Type, ViewAnglesType, TraitClass>`
|
||||||
|
> Requires: `CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType>`
|
||||||
|
> Key features: **lazy view-projection caching**, world↔screen helpers, pluggable math via a **Trait**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`Camera` is a small, zero-allocation camera wrapper. It delegates the math for **view**, **projection**, and **look-at** to a **Trait** (`TraitClass`), which lets you plug in different coordinate systems or conventions without changing the camera code. The class caches the **View×Projection** matrix and invalidates it when any parameter changes.
|
||||||
|
|
||||||
|
Alongside the camera, the header defines:
|
||||||
|
|
||||||
|
* `struct ViewPort { float m_width, m_height; float aspect_ratio() const; }`
|
||||||
|
* `using FieldOfView = Angle<float, 0.f, 180.f, AngleFlags::Clamped>;`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template & trait requirements
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class T, class MatType, class ViewAnglesType>
|
||||||
|
concept CameraEngineConcept = requires(
|
||||||
|
const omath::Vector3<float>& cam_origin,
|
||||||
|
const omath::Vector3<float>& look_at,
|
||||||
|
const ViewAnglesType& angles,
|
||||||
|
const omath::projection::FieldOfView& fov,
|
||||||
|
const omath::projection::ViewPort& viewport,
|
||||||
|
float znear, float zfar
|
||||||
|
) {
|
||||||
|
{ T::calc_look_at_angle(cam_origin, look_at) } noexcept -> std::same_as<ViewAnglesType>;
|
||||||
|
{ T::calc_view_matrix(angles, cam_origin) } noexcept -> std::same_as<MatType>;
|
||||||
|
{ T::calc_projection_matrix(fov, viewport, znear, zfar)}noexcept -> std::same_as<MatType>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Your `Mat4X4Type` must behave like the library’s `Mat<4,4,...>` (supports `*`, `/`, `inverted()`, `.at(r,c)`, `.raw_array()`, and `static constexpr get_store_ordering()`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
|
||||||
|
|
||||||
|
// Example trait (sketch): assumes Y-up, column-major, left-handed
|
||||||
|
struct MyCamTrait {
|
||||||
|
static ViewAnglesType calc_look_at_angle(const Vector3<float>& eye,
|
||||||
|
const Vector3<float>& at) noexcept;
|
||||||
|
static Mat4 calc_view_matrix(const ViewAnglesType& ang,
|
||||||
|
const Vector3<float>& eye) noexcept;
|
||||||
|
static Mat4 calc_projection_matrix(const FieldOfView& fov,
|
||||||
|
const ViewPort& vp,
|
||||||
|
float znear, float zfar) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Camera = omath::projection::Camera<Mat4, MyViewAngles, MyCamTrait>;
|
||||||
|
|
||||||
|
omath::projection::ViewPort vp{1920, 1080};
|
||||||
|
omath::projection::FieldOfView fov = omath::angles::degrees(70.f);
|
||||||
|
|
||||||
|
Camera cam(/*position*/ {0,1.7f, -3},
|
||||||
|
/*angles*/ MyViewAngles{/*...*/},
|
||||||
|
/*viewport*/ vp, fov,
|
||||||
|
/*near*/ 0.1f,
|
||||||
|
/*far*/ 1000.f);
|
||||||
|
|
||||||
|
// Project world → screen (origin top-left)
|
||||||
|
auto s = cam.world_to_screen<Camera::ScreenStart::TOP_LEFT_CORNER>({1, 1, 0});
|
||||||
|
if (s) {
|
||||||
|
// s->x, s->y in pixels; s->z in NDC depth
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class ScreenStart { TOP_LEFT_CORNER, BOTTOM_LEFT_CORNER };
|
||||||
|
|
||||||
|
class Camera final {
|
||||||
|
public:
|
||||||
|
~Camera() = default;
|
||||||
|
|
||||||
|
Camera(const Vector3<float>& position,
|
||||||
|
const ViewAnglesType& view_angles,
|
||||||
|
const ViewPort& view_port,
|
||||||
|
const FieldOfView& fov,
|
||||||
|
float near, float far) noexcept;
|
||||||
|
|
||||||
|
void look_at(const Vector3<float>& target); // recomputes view angles; invalidates cache
|
||||||
|
|
||||||
|
// Lazily computed and cached:
|
||||||
|
const Mat4X4Type& get_view_projection_matrix() const noexcept;
|
||||||
|
|
||||||
|
// Setters (all invalidate cached VP):
|
||||||
|
void set_field_of_view(const FieldOfView&) noexcept;
|
||||||
|
void set_near_plane(float) noexcept;
|
||||||
|
void set_far_plane(float) noexcept;
|
||||||
|
void set_view_angles(const ViewAnglesType&) noexcept;
|
||||||
|
void set_origin(const Vector3<float>&) noexcept;
|
||||||
|
void set_view_port(const ViewPort&) noexcept;
|
||||||
|
|
||||||
|
// Getters:
|
||||||
|
const FieldOfView& get_field_of_view() const noexcept;
|
||||||
|
const float& get_near_plane() const noexcept;
|
||||||
|
const float& get_far_plane() const noexcept;
|
||||||
|
const ViewAnglesType& get_view_angles() const noexcept;
|
||||||
|
const Vector3<float>& get_origin() const noexcept;
|
||||||
|
|
||||||
|
// World → Screen (pixels) via NDC; choose screen origin:
|
||||||
|
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||||
|
std::expected<Vector3<float>, Error>
|
||||||
|
world_to_screen(const Vector3<float>& world) const noexcept;
|
||||||
|
|
||||||
|
// World → NDC (aka “viewport” in this code) ∈ [-1,1]^3
|
||||||
|
std::expected<Vector3<float>, Error>
|
||||||
|
world_to_view_port(const Vector3<float>& world) const noexcept;
|
||||||
|
|
||||||
|
// NDC → World (uses inverse VP)
|
||||||
|
std::expected<Vector3<float>, Error>
|
||||||
|
view_port_to_screen(const Vector3<float>& ndc) const noexcept;
|
||||||
|
|
||||||
|
// Screen (pixels) → World
|
||||||
|
std::expected<Vector3<float>, Error>
|
||||||
|
screen_to_world(const Vector3<float>& screen) const noexcept;
|
||||||
|
|
||||||
|
// 2D overload (z defaults to 1, i.e., far plane ray-end in NDC)
|
||||||
|
std::expected<Vector3<float>, Error>
|
||||||
|
screen_to_world(const Vector2<float>& screen) const noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ViewPort m_view_port{};
|
||||||
|
FieldOfView m_field_of_view;
|
||||||
|
mutable std::optional<Mat4X4Type> m_view_projection_matrix;
|
||||||
|
float m_far_plane_distance{};
|
||||||
|
float m_near_plane_distance{};
|
||||||
|
ViewAnglesType m_view_angles;
|
||||||
|
Vector3<float> m_origin;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr bool is_ndc_out_of_bounds(const Mat4X4Type& ndc) noexcept;
|
||||||
|
Vector3<float> ndc_to_screen_position_from_top_left_corner(const Vector3<float>& ndc) const noexcept;
|
||||||
|
Vector3<float> ndc_to_screen_position_from_bottom_left_corner(const Vector3<float>& ndc) const noexcept;
|
||||||
|
Vector3<float> screen_to_ndc(const Vector3<float>& screen) const noexcept;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error handling
|
||||||
|
|
||||||
|
All conversions return `std::expected<..., Error>` with errors from `error_codes.hpp`, notably:
|
||||||
|
|
||||||
|
* `Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS` — clip space W=0 or NDC outside `[-1,1]`.
|
||||||
|
* `Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO` — non-invertible View×Projection matrix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coordinate spaces & conversions
|
||||||
|
|
||||||
|
### World → NDC (`world_to_view_port`)
|
||||||
|
|
||||||
|
1. Build (or reuse cached) `VP = P * V` (projection * view).
|
||||||
|
2. Multiply by homogeneous column from the world point.
|
||||||
|
3. Reject if `w == 0`.
|
||||||
|
4. Perspective divide → NDC in `[-1,1]^3`.
|
||||||
|
5. Reject if any component is out of range.
|
||||||
|
|
||||||
|
Returns `{x_ndc, y_ndc, z_ndc}`.
|
||||||
|
|
||||||
|
### NDC → Screen (pixels)
|
||||||
|
|
||||||
|
The class offers two origins:
|
||||||
|
|
||||||
|
* **Top-left (default)**
|
||||||
|
|
||||||
|
```
|
||||||
|
x_px = (x_ndc + 1)/2 * width
|
||||||
|
y_px = ( -y_ndc/2 + 0.5) * height // flips Y
|
||||||
|
```
|
||||||
|
* **Bottom-left**
|
||||||
|
|
||||||
|
```
|
||||||
|
x_px = (x_ndc + 1)/2 * width
|
||||||
|
y_px = ( y_ndc/2 + 0.5) * height
|
||||||
|
```
|
||||||
|
|
||||||
|
### Screen (pixels) → NDC
|
||||||
|
|
||||||
|
```
|
||||||
|
x_ndc = screen_x / width * 2 - 1
|
||||||
|
y_ndc = 1 - screen_y / height * 2 // Top-left screen origin assumed here
|
||||||
|
z_ndc = screen_z // Caller-provided (e.g., 0..1 depth)
|
||||||
|
```
|
||||||
|
|
||||||
|
### NDC → World (`view_port_to_screen`)
|
||||||
|
|
||||||
|
Despite the method name, this function **unprojects** an NDC point back to world space:
|
||||||
|
|
||||||
|
1. Compute `VP^{-1}`; if not invertible → error.
|
||||||
|
2. Multiply by NDC (homogeneous 4D) and divide by `w`.
|
||||||
|
3. Return world point.
|
||||||
|
|
||||||
|
> Tip: to build a **world-space ray** from a screen pixel, unproject at `z=0` (near) and `z=1` (far).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Caching & invalidation
|
||||||
|
|
||||||
|
* `get_view_projection_matrix()` computes `P*V` once and caches it.
|
||||||
|
* Any setter (`set_*`) or `look_at()` clears the cache (`m_view_projection_matrix = std::nullopt`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & gotchas
|
||||||
|
|
||||||
|
* **Matrix order**: The camera multiplies `P * V`. Make sure your **Trait** matches this convention.
|
||||||
|
* **Store ordering**: The `Mat4X4Type::get_store_ordering()` is used when building homogeneous columns; ensure it’s consistent with your matrix implementation.
|
||||||
|
* **Naming quirk**: `view_port_to_screen()` returns a **world** point from **NDC** (it’s an unproject). Consider renaming to `ndc_to_world()` in your codebase for clarity.
|
||||||
|
* **FOV units**: `FieldOfView` uses the project’s `Angle` type; pass degrees via `angles::degrees(...)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal trait sketch (column-major, left-handed)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct LHCTrait {
|
||||||
|
static MyAngles calc_look_at_angle(const Vector3<float>& eye,
|
||||||
|
const Vector3<float>& at) noexcept { /* ... */ }
|
||||||
|
|
||||||
|
static Mat4 calc_view_matrix(const MyAngles& ang,
|
||||||
|
const Vector3<float>& eye) noexcept {
|
||||||
|
// Build from forward/right/up and translation
|
||||||
|
}
|
||||||
|
|
||||||
|
static Mat4 calc_projection_matrix(const FieldOfView& fov,
|
||||||
|
const ViewPort& vp,
|
||||||
|
float zn, float zf) noexcept {
|
||||||
|
return omath::mat_perspective_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(
|
||||||
|
fov.as_degrees(), vp.aspect_ratio(), zn, zf
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing checklist
|
||||||
|
|
||||||
|
* World point centered in view projects to **screen center**.
|
||||||
|
* Points outside frustum → `WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS`.
|
||||||
|
* Inverting `VP` fails gracefully for singular matrices.
|
||||||
|
* `ScreenStart` switch flips Y as expected.
|
||||||
|
* Screen→World ray: unproject `(x,y,0)` and `(x,y,1)` and verify direction passes through the camera frustum.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Engine-Specific Camera Traits](../engines/) - Camera implementations for different game engines
|
||||||
|
- [View Angles Documentation](../trigonometry/view_angles.md) - Understanding pitch/yaw/roll
|
||||||
|
- [Getting Started Guide](../getting_started.md) - Quick start with projection
|
||||||
|
- [Tutorials - World-to-Screen](../tutorials.md#tutorial-2-world-to-screen-projection) - Complete projection tutorial
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
79
docs/projection/error_codes.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# `omath::projection::Error` — Error codes for world/screen projection
|
||||||
|
|
||||||
|
> Header: `omath/projection/error_codes.hpp`
|
||||||
|
> Namespace: `omath::projection`
|
||||||
|
> Type: `enum class Error : uint16_t`
|
||||||
|
|
||||||
|
These error codes are returned by camera/projection helpers (e.g., `Camera::world_to_screen`, `Camera::screen_to_world`) wrapped in `std::expected<..., Error>`. Use them to distinguish **clipping/visibility** problems from **matrix/math** failures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enum values
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class Error : uint16_t {
|
||||||
|
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
|
||||||
|
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
* **`WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS`**
|
||||||
|
The input point cannot produce a valid on-screen coordinate:
|
||||||
|
|
||||||
|
* Clip-space `w == 0` (point at/infinite or behind camera plane), or
|
||||||
|
* After projection, any NDC component is outside `[-1, 1]`.
|
||||||
|
|
||||||
|
* **`INV_VIEW_PROJ_MAT_DET_EQ_ZERO`**
|
||||||
|
The **View × Projection** matrix is not invertible (determinant ≈ 0).
|
||||||
|
Unprojection (`screen_to_world` / `view_port_to_screen`) requires an invertible matrix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typical usage
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using omath::projection::Error;
|
||||||
|
|
||||||
|
auto pix = cam.world_to_screen(point);
|
||||||
|
if (!pix) {
|
||||||
|
switch (pix.error()) {
|
||||||
|
case Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS:
|
||||||
|
// Cull label/marker; point is off-screen or behind camera.
|
||||||
|
break;
|
||||||
|
case Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
|
||||||
|
// Investigate camera/projection setup; near/far/FOV or trait bug.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unproject a screen pixel (top-left origin) at depth 1.0
|
||||||
|
if (auto world = cam.screen_to_world({sx, sy, 1.0f})) {
|
||||||
|
// use *world
|
||||||
|
} else if (world.error() == Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO) {
|
||||||
|
// handle singular VP matrix
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## When you might see these errors
|
||||||
|
|
||||||
|
* **Out-of-bounds**
|
||||||
|
|
||||||
|
* The world point lies outside the camera frustum.
|
||||||
|
* The point is behind the camera (clip `w <= 0`).
|
||||||
|
* Extremely large coordinates cause overflow and fail NDC bounds.
|
||||||
|
|
||||||
|
* **Non-invertible VP**
|
||||||
|
|
||||||
|
* Degenerate projection settings (e.g., `near == far`, zero FOV).
|
||||||
|
* Trait builds `P` or `V` incorrectly (wrong handedness/order).
|
||||||
|
* Numerical issues from nearly singular configurations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
* Validate camera setup: `near > 0`, `far > near`, sensible FOV (e.g., 30°–120°).
|
||||||
|
* For UI markers: treat `WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS` as a simple **cull** signal.
|
||||||
|
* Log `INV_VIEW_PROJ_MAT_DET_EQ_ZERO` — it usually indicates a configuration or math bug worth fixing rather than hiding.
|
||||||
164
docs/rev_eng/external_rev_object.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# `omath::rev_eng::ExternalReverseEngineeredObject` — typed offsets over external memory
|
||||||
|
|
||||||
|
> Header: `omath/rev_eng/external_reverse_engineered_object.hpp`
|
||||||
|
> Namespace: `omath::rev_eng`
|
||||||
|
> Pattern: **CRTP-style wrapper** around a user-provided *ExternalMemoryManagementTrait* that actually reads/writes another process or device’s memory.
|
||||||
|
|
||||||
|
A tiny base class for reverse-engineered objects that live **outside** your address space. You pass an absolute base address and a trait with `read_memory` / `write_memory`. Your derived types then expose strongly-typed getters/setters that delegate into the trait using **byte offsets**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick look
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<class ExternalMemoryManagementTrait>
|
||||||
|
class ExternalReverseEngineeredObject {
|
||||||
|
public:
|
||||||
|
explicit ExternalReverseEngineeredObject(std::uintptr_t addr)
|
||||||
|
: m_object_address(addr) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<class Type>
|
||||||
|
[[nodiscard]] Type get_by_offset(std::ptrdiff_t offset) const {
|
||||||
|
return ExternalMemoryManagementTrait::read_memory(m_object_address + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
void set_by_offset(std::ptrdiff_t offset, const Type& value) const {
|
||||||
|
ExternalMemoryManagementTrait::write_memory(m_object_address + offset, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::uintptr_t m_object_address{};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trait requirements
|
||||||
|
|
||||||
|
Your `ExternalMemoryManagementTrait` must provide:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Reads sizeof(T) bytes starting at absolute address and returns T.
|
||||||
|
template<class T>
|
||||||
|
static T read_memory(std::uintptr_t absolute_address);
|
||||||
|
|
||||||
|
// Writes sizeof(T) bytes to absolute address.
|
||||||
|
template<class T>
|
||||||
|
static void write_memory(std::uintptr_t absolute_address, const T& value);
|
||||||
|
```
|
||||||
|
|
||||||
|
> Tip: If your implementation prefers returning `bool`/`expected<>` for writes, either:
|
||||||
|
>
|
||||||
|
> * make `write_memory` `void` and throw/log internally, or
|
||||||
|
> * adjust `set_by_offset` in your fork to surface the status.
|
||||||
|
|
||||||
|
### Common implementations
|
||||||
|
|
||||||
|
* **Windows**: wrap `ReadProcessMemory` / `WriteProcessMemory` with a stored `HANDLE` (often captured via a singleton or embedded in the trait).
|
||||||
|
* **Linux**: `/proc/<pid>/mem`, `process_vm_readv/writev`, `ptrace`.
|
||||||
|
* **Device/FPGA**: custom MMIO/driver APIs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to use (derive and map fields)
|
||||||
|
|
||||||
|
Create a concrete type for your target structure and map known offsets:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct WinRPMTrait {
|
||||||
|
template<class T>
|
||||||
|
static T read_memory(std::uintptr_t addr) {
|
||||||
|
T out{};
|
||||||
|
SIZE_T n{};
|
||||||
|
if (!ReadProcessMemory(g_handle, reinterpret_cast<LPCVOID>(addr), &out, sizeof(T), &n) || n != sizeof(T))
|
||||||
|
throw std::runtime_error("RPM failed");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
template<class T>
|
||||||
|
static void write_memory(std::uintptr_t addr, const T& val) {
|
||||||
|
SIZE_T n{};
|
||||||
|
if (!WriteProcessMemory(g_handle, reinterpret_cast<LPVOID>(addr), &val, sizeof(T), &n) || n != sizeof(T))
|
||||||
|
throw std::runtime_error("WPM failed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Player final : public omath::rev_eng::ExternalReverseEngineeredObject<WinRPMTrait> {
|
||||||
|
using Base = omath::rev_eng::ExternalReverseEngineeredObject<WinRPMTrait>;
|
||||||
|
public:
|
||||||
|
using Base::Base; // inherit ctor (takes base address)
|
||||||
|
|
||||||
|
// Offsets taken from your RE notes (in bytes)
|
||||||
|
Vector3<float> position() const { return get_by_offset<Vector3<float>>(0x30); }
|
||||||
|
void set_position(const Vector3<float>& p) const { set_by_offset(0x30, p); }
|
||||||
|
|
||||||
|
float health() const { return get_by_offset<float>(0x100); }
|
||||||
|
void set_health(float h) const { set_by_offset(0x100, h); }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Player p{ /* base address you discovered */ 0x7FF6'1234'0000ull };
|
||||||
|
auto pos = p.position();
|
||||||
|
p.set_health(100.f);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design notes & constraints
|
||||||
|
|
||||||
|
* **Offsets are byte offsets** from the object’s **base address** passed to the constructor.
|
||||||
|
* **Type safety is on you**: `Type` must match the external layout at that offset (endian, packing, alignment).
|
||||||
|
* **No lifetime tracking**: if the target object relocates/frees, you must update/recreate the wrapper with the new base address.
|
||||||
|
* **Thread safety**: the class itself is stateless; thread safety depends on your trait implementation.
|
||||||
|
* **Endianness**: assumes the host and target endianness agree, or your trait handles conversion.
|
||||||
|
* **Error handling**: this header doesn’t prescribe it; adopt exceptions/expected/logging inside the trait.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best practices
|
||||||
|
|
||||||
|
* Centralize offsets in one place (constexprs or a small struct) and **comment source/version** (e.g., *game v1.2.3*).
|
||||||
|
* Wrap fragile multi-field writes in a trait-level **transaction** if your platform supports it.
|
||||||
|
* Validate pointers/guards (e.g., vtable signature, canary) before trusting offsets.
|
||||||
|
* Prefer **plain old data** (`struct` without virtuals) for `Type` to ensure trivial byte copies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal trait sketch (POSIX, `process_vm_readv`)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct LinuxPvmTrait {
|
||||||
|
static pid_t pid;
|
||||||
|
|
||||||
|
template<class T> static T read_memory(std::uintptr_t addr) {
|
||||||
|
T out{};
|
||||||
|
iovec local{ &out, sizeof(out) }, remote{ reinterpret_cast<void*>(addr), sizeof(out) };
|
||||||
|
if (process_vm_readv(pid, &local, 1, &remote, 1, 0) != ssize_t(sizeof(out)))
|
||||||
|
throw std::runtime_error("pvm_readv failed");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> static void write_memory(std::uintptr_t addr, const T& val) {
|
||||||
|
iovec local{ const_cast<T*>(&val), sizeof(val) }, remote{ reinterpret_cast<void*>(addr), sizeof(val) };
|
||||||
|
if (process_vm_writev(pid, &local, 1, &remote, 1, 0) != ssize_t(sizeof(val)))
|
||||||
|
throw std::runtime_error("pvm_writev failed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
* **Garbled values** → wrong offset/Type, or target’s structure changed between versions.
|
||||||
|
* **Access denied** → missing privileges (admin/root), wrong process handle, or page protections.
|
||||||
|
* **Crashes in trait** → add bounds/sanity checks; many APIs fail on unmapped pages.
|
||||||
|
* **Writes “stick” only briefly** → the target may constantly overwrite (server authority / anti-cheat / replication).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
142
docs/rev_eng/internal_rev_object.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# `omath::rev_eng::InternalReverseEngineeredObject` — raw in-process offset/VTABLE access
|
||||||
|
|
||||||
|
> Header: `omath/rev_eng/internal_reverse_engineered_object.hpp`
|
||||||
|
> Namespace: `omath::rev_eng`
|
||||||
|
> Purpose: Convenience base for **internal** (same-process) RE wrappers that:
|
||||||
|
>
|
||||||
|
> * read/write fields by **byte offset** from `this`
|
||||||
|
> * call **virtual methods** by **vtable index**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## At a glance
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class InternalReverseEngineeredObject {
|
||||||
|
protected:
|
||||||
|
template<class Type>
|
||||||
|
[[nodiscard]] Type& get_by_offset(std::ptrdiff_t offset);
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
[[nodiscard]] const Type& get_by_offset(std::ptrdiff_t offset) const;
|
||||||
|
|
||||||
|
template<std::size_t id, class ReturnType>
|
||||||
|
ReturnType call_virtual_method(auto... arg_list);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
* `get_by_offset<T>(off)` — returns a **reference** to `T` located at `reinterpret_cast<uintptr_t>(this) + off`.
|
||||||
|
* `call_virtual_method<id, Ret>(args...)` — fetches the function pointer from `(*reinterpret_cast<void***>(this))[id]` and invokes it as a free function with implicit `this` passed explicitly.
|
||||||
|
|
||||||
|
On MSVC builds the function pointer type uses `__thiscall`; on non-MSVC it uses a plain function pointer taking `void*` as the first parameter (the typical Itanium ABI shape).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: wrapping a reverse-engineered class
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Player : omath::rev_eng::InternalReverseEngineeredObject {
|
||||||
|
// Field offsets (document game/app version!)
|
||||||
|
static constexpr std::ptrdiff_t kHealth = 0x100;
|
||||||
|
static constexpr std::ptrdiff_t kPosition = 0x30;
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
float& health() { return get_by_offset<float>(kHealth); }
|
||||||
|
const float& health() const { return get_by_offset<float>(kHealth); }
|
||||||
|
|
||||||
|
Vector3<float>& position() { return get_by_offset<Vector3<float>>(kPosition); }
|
||||||
|
const Vector3<float>& position() const { return get_by_offset<Vector3<float>>(kPosition); }
|
||||||
|
|
||||||
|
// Virtuals (vtable indices discovered via RE)
|
||||||
|
int getTeam() { return call_virtual_method<27, int>(); }
|
||||||
|
void setArmor(float val) { call_virtual_method<42, void>(val); } // signature must match!
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto* p = /* pointer to live Player instance within the same process */;
|
||||||
|
p->health() = 100.f;
|
||||||
|
int team = p->getTeam();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How `call_virtual_method` resolves the signature
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<std::size_t id, class ReturnType>
|
||||||
|
ReturnType call_virtual_method(auto... arg_list) {
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
using Fn = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
||||||
|
#else
|
||||||
|
using Fn = ReturnType(*)(void*, decltype(arg_list)...);
|
||||||
|
#endif
|
||||||
|
return (*reinterpret_cast<Fn**>(this))[id](this, arg_list...);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* The **first parameter** is always `this` (`void*`).
|
||||||
|
* Remaining parameter types are deduced from the **actual arguments** (`decltype(arg_list)...`).
|
||||||
|
Ensure you pass arguments with the correct types (e.g., `int32_t` vs `int`, pointer/ref qualifiers), or define thin wrappers that cast to the exact signature you recovered.
|
||||||
|
|
||||||
|
> ⚠ On 32-bit MSVC the `__thiscall` distinction matters; on 64-bit MSVC it’s ignored (all member funcs use the common x64 calling convention).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Safety notes (read before using!)
|
||||||
|
|
||||||
|
Working at this level is inherently unsafe; be deliberate:
|
||||||
|
|
||||||
|
1. **Correct offsets & alignment**
|
||||||
|
|
||||||
|
* `get_by_offset<T>` assumes `this + offset` is **properly aligned** for `T` and points to an object of type `T`.
|
||||||
|
* Wrong offsets or misalignment ⇒ **undefined behavior** (UB), crashes, silent corruption.
|
||||||
|
|
||||||
|
2. **Object layout assumptions**
|
||||||
|
|
||||||
|
* The vtable pointer is assumed to be at the **start of the most-derived subobject at `this`**.
|
||||||
|
* With **multiple/virtual inheritance**, the desired subobject’s vptr may be at a non-zero offset. If so, adjust `this` to that subobject before calling, e.g.:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto* sub = reinterpret_cast<void*>(reinterpret_cast<std::uintptr_t>(this) + kSubobjectOffset);
|
||||||
|
// … then reinterpret sub instead of this inside a custom helper
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **ABI & calling convention**
|
||||||
|
|
||||||
|
* Indices and signatures are **compiler/ABI-specific**. Recheck after updates or different builds (MSVC vs Clang/LLVM-MSVC vs MinGW).
|
||||||
|
|
||||||
|
4. **Strict aliasing**
|
||||||
|
|
||||||
|
* Reinterpreting memory as unrelated `T` can violate aliasing rules. Prefer **trivially copyable** PODs and exact original types where possible.
|
||||||
|
|
||||||
|
5. **Const-correctness**
|
||||||
|
|
||||||
|
* The `const` overload returns `const T&` but still reinterprets memory; do not write through it. Use the non-const overload to mutate.
|
||||||
|
|
||||||
|
6. **Thread safety**
|
||||||
|
|
||||||
|
* No synchronization is provided. Ensure the underlying object isn’t concurrently mutated in incompatible ways.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips & patterns
|
||||||
|
|
||||||
|
* **Centralize offsets** in `constexpr` with comments (`// game v1.2.3, sig XYZ`).
|
||||||
|
* **Guard reads**: if you have a canary or RTTI/vtable hash, check it before relying on offsets.
|
||||||
|
* **Prefer accessors** returning references**:** lets you both read and write with natural syntax.
|
||||||
|
* **Wrap tricky virtuals**: if a method takes complex/reference params, wrap `call_virtual_method` in a strongly typed member that casts exactly as needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
* **Crash on virtual call** → wrong index or wrong `this` (subobject), or mismatched signature (args/ret or calling conv).
|
||||||
|
* **Weird field values** → wrong offset, wrong type size/packing, stale layout after an update.
|
||||||
|
* **Only in 32-bit** → double-check `__thiscall` and parameter passing (register vs stack).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
4
docs/styles/center.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/* docs/css/custom.css */
|
||||||
|
.center-text {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
11
docs/styles/custom-header.css
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* Widen the navbar container */
|
||||||
|
.navbar .container {
|
||||||
|
max-width: 100%; /* adjust to your target width */
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tighter spacing between navbar items */
|
||||||
|
.navbar-nav > li > a {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
165
docs/trigonometry/angle.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# `omath::Angle` — templated angle with normalize/clamper + trig
|
||||||
|
|
||||||
|
> Header: `omath/trigonometry/angle.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Template: `Angle<Type = float, min = 0, max = 360, flags = AngleFlags::Normalized>`
|
||||||
|
> Requires: `std::is_arithmetic_v<Type>`
|
||||||
|
> Formatters: `std::formatter` for `char`, `wchar_t`, `char8_t` → `"{}deg"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`Angle` is a tiny value-type that stores an angle in **degrees** and automatically **normalizes** or **clamps** it into a compile-time range. It exposes conversions to/from radians, common trig (`sin/cos/tan/cot`), arithmetic with wrap/clamp semantics, and lightweight formatting.
|
||||||
|
|
||||||
|
Two behaviors via `AngleFlags`:
|
||||||
|
|
||||||
|
* `AngleFlags::Normalized` (default): values are wrapped into `[min, max]` using `angles::wrap_angle`.
|
||||||
|
* `AngleFlags::Clamped`: values are clamped to `[min, max]` using `std::clamp`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath {
|
||||||
|
|
||||||
|
enum class AngleFlags { Normalized = 0, Clamped = 1 };
|
||||||
|
|
||||||
|
template<class Type = float, Type min = Type(0), Type max = Type(360),
|
||||||
|
AngleFlags flags = AngleFlags::Normalized>
|
||||||
|
requires std::is_arithmetic_v<Type>
|
||||||
|
class Angle {
|
||||||
|
public:
|
||||||
|
// Construction
|
||||||
|
static constexpr Angle from_degrees(const Type& deg) noexcept;
|
||||||
|
static constexpr Angle from_radians(const Type& rad) noexcept;
|
||||||
|
constexpr Angle() noexcept; // 0 deg, adjusted by flags/range
|
||||||
|
|
||||||
|
// Accessors / conversions (degrees stored internally)
|
||||||
|
constexpr const Type& operator*() const noexcept; // raw degrees reference
|
||||||
|
constexpr Type as_degrees() const noexcept;
|
||||||
|
constexpr Type as_radians() const noexcept;
|
||||||
|
|
||||||
|
// Trig (computed from radians)
|
||||||
|
Type sin() const noexcept;
|
||||||
|
Type cos() const noexcept;
|
||||||
|
Type tan() const noexcept;
|
||||||
|
Type atan() const noexcept; // atan(as_radians()) (rarely used)
|
||||||
|
Type cot() const noexcept; // cos()/sin() (watch sin≈0)
|
||||||
|
|
||||||
|
// Arithmetic (wraps or clamps per flags and [min,max])
|
||||||
|
constexpr Angle& operator+=(const Angle&) noexcept;
|
||||||
|
constexpr Angle& operator-=(const Angle&) noexcept;
|
||||||
|
constexpr Angle operator+(const Angle&) noexcept;
|
||||||
|
constexpr Angle operator-(const Angle&) noexcept;
|
||||||
|
constexpr Angle operator-() const noexcept;
|
||||||
|
|
||||||
|
// Comparison (partial ordering)
|
||||||
|
constexpr std::partial_ordering operator<=>(const Angle&) const noexcept = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::format("{}", Angle<float>::from_degrees(45)); // "45deg"
|
||||||
|
```
|
||||||
|
|
||||||
|
Formatters exist for `char`, `wchar_t`, and `char8_t`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage examples
|
||||||
|
|
||||||
|
### Defaults (0–360, normalized)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Deg = omath::Angle<>; // float, [0,360], Normalized
|
||||||
|
|
||||||
|
auto a = Deg::from_degrees(370); // -> 10deg
|
||||||
|
auto b = Deg::from_radians(omath::angles::pi); // -> 180deg
|
||||||
|
|
||||||
|
a += Deg::from_degrees(355); // 10 + 355 -> 365 -> wraps -> 5deg
|
||||||
|
|
||||||
|
float s = a.sin(); // sin(5°)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clamped range
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Fov = omath::Angle<float, 1.f, 179.f, omath::AngleFlags::Clamped>;
|
||||||
|
auto fov = Fov::from_degrees(200.f); // -> 179deg (clamped)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Signed, normalized range
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using SignedDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
|
||||||
|
|
||||||
|
auto x = SignedDeg::from_degrees(190.f); // -> -170deg
|
||||||
|
auto y = SignedDeg::from_degrees(-200.f); // -> 160deg
|
||||||
|
auto z = x + y; // -170 + 160 = -10deg (wrapped if needed)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get/set raw degrees
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto yaw = SignedDeg::from_degrees(-45.f);
|
||||||
|
float deg = *yaw; // same as yaw.as_degrees()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Semantics & notes
|
||||||
|
|
||||||
|
* **Storage & units:** Internally stores **degrees** (`Type m_angle`). `as_radians()`/`from_radians()` use the project helpers in `omath::angles`.
|
||||||
|
* **Arithmetic honors policy:** `operator+=`/`-=` and the binary `+`/`-` apply **wrap** or **clamp** in `[min,max]`, mirroring construction behavior.
|
||||||
|
* **`atan()`**: returns `std::atan(as_radians())` (the arctangent of the *radian value*). This is mathematically unusual for an angle type and is rarely useful; prefer `tan()`/`atan2` in client code when solving geometry problems.
|
||||||
|
* **`cot()` / `tan()` singularities:** Near multiples where `sin() ≈ 0` or `cos() ≈ 0`, results blow up. Guard in your usage if inputs can approach these points.
|
||||||
|
* **Comparison:** `operator<=>` is defaulted. With normalization, distinct representatives can compare as expected (e.g., `-180` vs `180` in signed ranges are distinct endpoints).
|
||||||
|
* **No implicit numeric conversion:** There’s **no `operator Type()`**. Use `as_degrees()`/`as_radians()` (or `*angle`) explicitly—this intentional friction avoids unit mistakes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customization patterns
|
||||||
|
|
||||||
|
* **Radians workflow:** Keep angles in degrees internally but wrap helper creators:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
inline Deg degf(float d) { return Deg::from_degrees(d); }
|
||||||
|
inline Deg radf(float r) { return Deg::from_radians(r); }
|
||||||
|
```
|
||||||
|
* **Compile-time policy:** Pick ranges/flags at the type level to enforce invariants (e.g., `YawDeg = Angle<float,-180,180,Normalized>`; `FovDeg = Angle<float,1,179,Clamped>`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pitfalls & gotchas
|
||||||
|
|
||||||
|
* Ensure `min < max` at compile time for meaningful wrap/clamp behavior.
|
||||||
|
* For normalized signed ranges, decide whether your `wrap_angle(min,max)` treats endpoints half-open (e.g., `[-180,180)`) to avoid duplicate representations; the formatter will print the stored value verbatim.
|
||||||
|
* If you need **sum of many angles**, accumulating in radians then converting back can improve numeric stability at extreme values.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal tests
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using A = omath::Angle<>;
|
||||||
|
REQUIRE(A::from_degrees(360).as_degrees() == 0.f);
|
||||||
|
REQUIRE(A::from_degrees(-1).as_degrees() == 359.f);
|
||||||
|
|
||||||
|
using S = omath::Angle<float,-180.f,180.f, omath::AngleFlags::Normalized>;
|
||||||
|
REQUIRE(S::from_degrees( 181).as_degrees() == -179.f);
|
||||||
|
REQUIRE(S::from_degrees(-181).as_degrees() == 179.f);
|
||||||
|
|
||||||
|
using C = omath::Angle<float, 10.f, 20.f, omath::AngleFlags::Clamped>;
|
||||||
|
REQUIRE(C::from_degrees(5).as_degrees() == 10.f);
|
||||||
|
REQUIRE(C::from_degrees(25).as_degrees() == 20.f);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
107
docs/trigonometry/angles.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# `omath::angles` — angle conversions, FOV helpers, and wrapping
|
||||||
|
|
||||||
|
> Header: `omath/trigonometry/angles.hpp`
|
||||||
|
> Namespace: `omath::angles`
|
||||||
|
> All functions are `[[nodiscard]]` and `noexcept` where applicable.
|
||||||
|
|
||||||
|
A small set of constexpr-friendly utilities for converting between degrees/radians, converting horizontal/vertical field of view, and wrapping angles into a closed interval.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Degrees ↔ Radians (Type must be floating-point)
|
||||||
|
template<class Type>
|
||||||
|
requires std::is_floating_point_v<Type>
|
||||||
|
constexpr Type radians_to_degrees(const Type& radians) noexcept;
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
requires std::is_floating_point_v<Type>
|
||||||
|
constexpr Type degrees_to_radians(const Type& degrees) noexcept;
|
||||||
|
|
||||||
|
// FOV conversion (inputs/outputs in degrees, aspect = width/height)
|
||||||
|
template<class Type>
|
||||||
|
requires std::is_floating_point_v<Type>
|
||||||
|
Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) noexcept;
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
requires std::is_floating_point_v<Type>
|
||||||
|
Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) noexcept;
|
||||||
|
|
||||||
|
// Wrap angle into [min, max] (any arithmetic type)
|
||||||
|
template<class Type>
|
||||||
|
requires std::is_arithmetic_v<Type>
|
||||||
|
Type wrap_angle(const Type& angle, const Type& min, const Type& max) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Degrees ↔ Radians
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
float rad = omath::angles::degrees_to_radians(180.0f); // π
|
||||||
|
double deg = omath::angles::radians_to_degrees(std::numbers::pi); // 180
|
||||||
|
```
|
||||||
|
|
||||||
|
### Horizontal ↔ Vertical FOV
|
||||||
|
|
||||||
|
* `aspect` = **width / height**.
|
||||||
|
* Inputs/outputs are **degrees**.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
float hdeg = 90.0f;
|
||||||
|
float aspect = 16.0f / 9.0f;
|
||||||
|
|
||||||
|
float vdeg = omath::angles::horizontal_fov_to_vertical(hdeg, aspect); // ~58.0°
|
||||||
|
float hdeg2 = omath::angles::vertical_fov_to_horizontal(vdeg, aspect); // ≈ 90.0°
|
||||||
|
```
|
||||||
|
|
||||||
|
Formulas (in radians):
|
||||||
|
|
||||||
|
* `v = 2 * atan( tan(h/2) / aspect )`
|
||||||
|
* `h = 2 * atan( tan(v/2) * aspect )`
|
||||||
|
|
||||||
|
### Wrapping angles (or any periodic value)
|
||||||
|
|
||||||
|
Wrap any numeric `angle` into `[min, max]`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Wrap degrees into [0, 360]
|
||||||
|
float a = omath::angles::wrap_angle( 370.0f, 0.0f, 360.0f); // 10
|
||||||
|
float b = omath::angles::wrap_angle( -15.0f, 0.0f, 360.0f); // 345
|
||||||
|
// Signed range [-180,180]
|
||||||
|
float c = omath::angles::wrap_angle( 200.0f, -180.0f, 180.0f); // -160
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & edge cases
|
||||||
|
|
||||||
|
* **Type requirements**
|
||||||
|
|
||||||
|
* Converters & FOV helpers require **floating-point** `Type`.
|
||||||
|
* `wrap_angle` accepts any arithmetic `Type` (floats or integers).
|
||||||
|
* **Aspect ratio** must be **positive** and finite. For `aspect == 0` the FOV helpers are undefined.
|
||||||
|
* **Units**: FOV functions accept/return **degrees** but compute internally in radians.
|
||||||
|
* **Wrapping interval**: Behavior assumes `max > min`. The result lies in the **closed interval** `[min, max]` with modulo arithmetic; if you need half-open behavior (e.g., `[min,max)`), adjust your range or post-process endpoint cases.
|
||||||
|
* **constexpr**: Converters are `constexpr`; FOV helpers are runtime constexpr-compatible except for `std::atan/std::tan` constraints on some standard libraries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick tests
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::angles;
|
||||||
|
|
||||||
|
static_assert(degrees_to_radians(180.0) == std::numbers::pi);
|
||||||
|
static_assert(radians_to_degrees(std::numbers::pi_v<float>) == 180.0f);
|
||||||
|
|
||||||
|
float v = horizontal_fov_to_vertical(90.0f, 16.0f/9.0f);
|
||||||
|
float h = vertical_fov_to_horizontal(v, 16.0f/9.0f);
|
||||||
|
assert(std::abs(h - 90.0f) < 1e-5f);
|
||||||
|
|
||||||
|
assert(wrap_angle(360.0f, 0.0f, 360.0f) == 0.0f || wrap_angle(360.0f, 0.0f, 360.0f) == 360.0f);
|
||||||
|
```
|
||||||
87
docs/trigonometry/view_angles.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# `omath::ViewAngles` — tiny POD for pitch/yaw/roll
|
||||||
|
|
||||||
|
> Header: your project’s `view_angles.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Kind: **aggregate struct** (POD), no methods, no allocation
|
||||||
|
|
||||||
|
A minimal container for Euler angles. You choose the types for each component (e.g., raw `float` or the strong `omath::Angle<>` type), and plug it into systems like `projection::Camera`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath {
|
||||||
|
template<class PitchType, class YawType, class RollType>
|
||||||
|
struct ViewAngles {
|
||||||
|
PitchType pitch;
|
||||||
|
YawType yaw;
|
||||||
|
RollType roll;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* Aggregate: supports brace-init, aggregate copying, and `constexpr` usage when the component types do.
|
||||||
|
* Semantics (units/handedness/ranges) are **entirely defined by your chosen types**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common aliases
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Simple, raw degrees as floats (be careful with wrapping!)
|
||||||
|
using ViewAnglesF = omath::ViewAngles<float, float, float>;
|
||||||
|
|
||||||
|
// Safer, policy-based angles (recommended)
|
||||||
|
using PitchDeg = omath::Angle<float, -89.f, 89.f, omath::AngleFlags::Clamped>;
|
||||||
|
using YawDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
|
||||||
|
using RollDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
|
||||||
|
using ViewAnglesDeg = omath::ViewAngles<PitchDeg, YawDeg, RollDeg>;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic construction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
omath::ViewAngles<float,float,float> a{ 10.f, 45.f, 0.f }; // pitch, yaw, roll in degrees
|
||||||
|
```
|
||||||
|
|
||||||
|
### With `omath::Angle<>` (automatic wrap/clamper)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
ViewAnglesDeg v{
|
||||||
|
PitchDeg::from_degrees( 95.f), // -> 89deg (clamped)
|
||||||
|
YawDeg::from_degrees (-190.f), // -> 170deg (wrapped)
|
||||||
|
RollDeg::from_degrees ( 30.f)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using with `projection::Camera`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using Mat4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
|
||||||
|
using Cam = omath::projection::Camera<Mat4, ViewAnglesDeg, MyCameraTrait>;
|
||||||
|
|
||||||
|
omath::projection::ViewPort vp{1920,1080};
|
||||||
|
auto fov = omath::angles::degrees_to_radians(70.f); // or your Angle type
|
||||||
|
|
||||||
|
Cam cam(/*position*/ {0,1.7f,-3},
|
||||||
|
/*angles*/ ViewAnglesDeg{ PitchDeg::from_degrees(0),
|
||||||
|
YawDeg::from_degrees(0),
|
||||||
|
RollDeg::from_degrees(0) },
|
||||||
|
/*viewport*/ vp,
|
||||||
|
/*fov*/ omath::Angle<float,0.f,180.f,omath::AngleFlags::Clamped>::from_degrees(70.f),
|
||||||
|
/*near*/ 0.1f,
|
||||||
|
/*far*/ 1000.f);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & tips
|
||||||
|
|
||||||
|
* **Ranges/units**: pick types that encode your policy (e.g., signed yaw in `[-180,180]`, pitch clamped to avoid gimbal flips).
|
||||||
|
* **Handedness & order**: this struct doesn’t impose rotation order. Your math/trait layer (e.g., `MyCameraTrait`) must define how `(pitch, yaw, roll)` map to a view matrix (common orders: ZYX or XYZ).
|
||||||
|
* **Zero-cost**: with plain `float`s this is as cheap as three scalars; with `Angle<>` you gain safety at the cost of tiny wrap/clamp logic on construction/arithmetic.
|
||||||
525
docs/troubleshooting.md
Normal file
@@ -0,0 +1,525 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
Solutions to common problems when using OMath.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build & Compilation Issues
|
||||||
|
|
||||||
|
### Error: C++20 features not available
|
||||||
|
|
||||||
|
**Problem:** Compiler doesn't support C++20.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
Upgrade your compiler:
|
||||||
|
- **GCC**: Version 10 or newer
|
||||||
|
- **Clang**: Version 11 or newer
|
||||||
|
- **MSVC**: Visual Studio 2019 16.10 or newer
|
||||||
|
|
||||||
|
Set C++20 in CMakeLists.txt:
|
||||||
|
```cmake
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error: `std::expected` not found
|
||||||
|
|
||||||
|
**Problem:** Using C++20 without C++23's `std::expected`.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. **Upgrade to C++23** (recommended):
|
||||||
|
```cmake
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Use a backport library**:
|
||||||
|
```cmake
|
||||||
|
find_package(tl-expected CONFIG REQUIRED)
|
||||||
|
target_link_libraries(your_target PRIVATE tl::expected)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error: `omath/omath.hpp` not found
|
||||||
|
|
||||||
|
**Problem:** OMath not installed or not in include path.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
Check installation:
|
||||||
|
```bash
|
||||||
|
# vcpkg
|
||||||
|
vcpkg list | grep omath
|
||||||
|
|
||||||
|
# Check if files exist
|
||||||
|
ls /path/to/vcpkg/installed/x64-linux/include/omath
|
||||||
|
```
|
||||||
|
|
||||||
|
In CMakeLists.txt:
|
||||||
|
```cmake
|
||||||
|
find_package(omath CONFIG REQUIRED)
|
||||||
|
target_link_libraries(your_target PRIVATE omath::omath)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linker errors with AVX2 engine
|
||||||
|
|
||||||
|
**Problem:** Undefined references to AVX2 functions.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
Enable AVX2 in your build:
|
||||||
|
```cmake
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(your_target PRIVATE /arch:AVX2)
|
||||||
|
else()
|
||||||
|
target_compile_options(your_target PRIVATE -mavx2)
|
||||||
|
endif()
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the legacy engine instead:
|
||||||
|
```cpp
|
||||||
|
// Use this instead of ProjPredEngineAVX2
|
||||||
|
ProjPredEngineLegacy engine;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Runtime Issues
|
||||||
|
|
||||||
|
### `world_to_screen()` always returns `nullopt`
|
||||||
|
|
||||||
|
**Common causes:**
|
||||||
|
|
||||||
|
1. **Point behind camera**
|
||||||
|
```cpp
|
||||||
|
// Point is behind the camera
|
||||||
|
Vector3<float> behind = camera_pos - Vector3<float>{0, 0, 100};
|
||||||
|
auto result = camera.world_to_screen(behind); // Returns nullopt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix:** Only project points in front of camera. Check Z-coordinate in view space.
|
||||||
|
|
||||||
|
2. **Invalid near/far planes**
|
||||||
|
```cpp
|
||||||
|
// Bad: near >= far
|
||||||
|
Camera camera(pos, angles, viewport, fov, 100.0f, 1.0f);
|
||||||
|
|
||||||
|
// Good: near < far
|
||||||
|
Camera camera(pos, angles, viewport, fov, 0.1f, 1000.0f);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Invalid FOV**
|
||||||
|
```cpp
|
||||||
|
// Bad: FOV out of range
|
||||||
|
auto fov = FieldOfView::from_degrees(0.0f); // Too small
|
||||||
|
auto fov = FieldOfView::from_degrees(180.0f); // Too large
|
||||||
|
|
||||||
|
// Good: FOV in valid range
|
||||||
|
auto fov = FieldOfView::from_degrees(90.0f);
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Uninitialized camera**
|
||||||
|
```cpp
|
||||||
|
// Make sure camera is properly initialized
|
||||||
|
camera.update(current_position, current_angles);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debugging:**
|
||||||
|
```cpp
|
||||||
|
Vector3<float> world_pos{100, 100, 100};
|
||||||
|
|
||||||
|
// Check projection step by step
|
||||||
|
std::cout << "World pos: " << world_pos.x << ", "
|
||||||
|
<< world_pos.y << ", " << world_pos.z << "\n";
|
||||||
|
|
||||||
|
auto view_matrix = camera.get_view_matrix();
|
||||||
|
// Transform to view space manually and check if Z > 0
|
||||||
|
|
||||||
|
if (auto screen = camera.world_to_screen(world_pos)) {
|
||||||
|
std::cout << "Success: " << screen->x << ", " << screen->y << "\n";
|
||||||
|
} else {
|
||||||
|
std::cout << "Failed - check if point is behind camera\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Angles wrapping incorrectly
|
||||||
|
|
||||||
|
**Problem:** Angles not normalizing to expected ranges.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
Use proper angle types:
|
||||||
|
```cpp
|
||||||
|
// Wrong: using raw floats
|
||||||
|
float pitch = 95.0f; // Out of valid range!
|
||||||
|
|
||||||
|
// Right: using typed angles
|
||||||
|
auto pitch = PitchAngle::from_degrees(89.0f); // Clamped to valid range
|
||||||
|
```
|
||||||
|
|
||||||
|
For custom ranges:
|
||||||
|
```cpp
|
||||||
|
// Define custom angle with wrapping
|
||||||
|
auto angle = Angle<float, -180.0f, 180.0f, AngleFlags::Normalized>::from_degrees(270.0f);
|
||||||
|
// Result: -90° (wrapped)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Projection appears mirrored or inverted
|
||||||
|
|
||||||
|
**Problem:** Using wrong engine trait for your game.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
Different engines have different coordinate systems:
|
||||||
|
|
||||||
|
| Symptom | Likely Issue | Fix |
|
||||||
|
|---------|-------------|-----|
|
||||||
|
| Upside down | Y-axis inverted | Try different engine or negate Y |
|
||||||
|
| Left-right flipped | Wrong handedness | Check engine documentation |
|
||||||
|
| Rotated 90° | Axis swap | Verify engine coordinate system |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Try different engine traits
|
||||||
|
using namespace omath::source_engine; // Z-up, left-handed
|
||||||
|
using namespace omath::unity_engine; // Y-up, left-handed
|
||||||
|
using namespace omath::unreal_engine; // Z-up, left-handed (different conventions)
|
||||||
|
using namespace omath::opengl_engine; // Y-up, right-handed
|
||||||
|
```
|
||||||
|
|
||||||
|
If still wrong, manually transform coordinates:
|
||||||
|
```cpp
|
||||||
|
// Example: swap Y and Z for Y-up to Z-up conversion
|
||||||
|
Vector3<float> convert_y_up_to_z_up(const Vector3<float>& pos) {
|
||||||
|
return Vector3<float>{pos.x, pos.z, pos.y};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projectile Prediction Issues
|
||||||
|
|
||||||
|
### `maybe_calculate_aim_point()` returns `nullopt`
|
||||||
|
|
||||||
|
**Common causes:**
|
||||||
|
|
||||||
|
1. **Target moving too fast**
|
||||||
|
```cpp
|
||||||
|
Target target;
|
||||||
|
target.velocity = Vector3<float>{1000, 0, 0}; // Very fast!
|
||||||
|
|
||||||
|
Projectile proj;
|
||||||
|
proj.speed = 500.0f; // Too slow to catch target
|
||||||
|
|
||||||
|
// Returns nullopt - projectile can't catch target
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix:** Check if projectile speed > target speed in the direction of motion.
|
||||||
|
|
||||||
|
2. **Zero projectile speed**
|
||||||
|
```cpp
|
||||||
|
Projectile proj;
|
||||||
|
proj.speed = 0.0f; // Invalid!
|
||||||
|
|
||||||
|
// Returns nullopt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix:** Ensure `proj.speed > 0`.
|
||||||
|
|
||||||
|
3. **Invalid positions**
|
||||||
|
```cpp
|
||||||
|
// NaN or infinite values
|
||||||
|
target.position = Vector3<float>{NAN, 0, 0};
|
||||||
|
|
||||||
|
// Returns nullopt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix:** Validate all input values are finite.
|
||||||
|
|
||||||
|
4. **Target out of range**
|
||||||
|
```cpp
|
||||||
|
// Target very far away
|
||||||
|
float distance = shooter_pos.distance_to(target.position);
|
||||||
|
float max_range = proj.speed * max_flight_time;
|
||||||
|
|
||||||
|
if (distance > max_range) {
|
||||||
|
// Will return nullopt
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debugging:**
|
||||||
|
```cpp
|
||||||
|
Projectile proj{/* ... */};
|
||||||
|
Target target{/* ... */};
|
||||||
|
|
||||||
|
// Check inputs
|
||||||
|
assert(proj.speed > 0);
|
||||||
|
assert(std::isfinite(target.position.length()));
|
||||||
|
assert(std::isfinite(target.velocity.length()));
|
||||||
|
|
||||||
|
// Check if target is reachable
|
||||||
|
float distance = proj.origin.distance_to(target.position);
|
||||||
|
float target_speed = target.velocity.length();
|
||||||
|
|
||||||
|
std::cout << "Distance: " << distance << "\n";
|
||||||
|
std::cout << "Projectile speed: " << proj.speed << "\n";
|
||||||
|
std::cout << "Target speed: " << target_speed << "\n";
|
||||||
|
|
||||||
|
if (target_speed >= proj.speed) {
|
||||||
|
std::cout << "Target may be too fast!\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aim point is inaccurate
|
||||||
|
|
||||||
|
**Problem:** Calculated aim point doesn't hit target.
|
||||||
|
|
||||||
|
**Possible causes:**
|
||||||
|
|
||||||
|
1. **Unit mismatch**
|
||||||
|
```cpp
|
||||||
|
// All units must match!
|
||||||
|
proj.speed = 800.0f; // meters per second
|
||||||
|
target.velocity = Vector3<float>{2, 1, 0}; // Must also be m/s!
|
||||||
|
|
||||||
|
// If using different units (e.g., game units vs meters), convert:
|
||||||
|
float game_units_to_meters = 0.01905f; // Example for Source
|
||||||
|
target.velocity = game_velocity * game_units_to_meters;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Wrong gravity vector**
|
||||||
|
```cpp
|
||||||
|
// Source Engine: Z-up
|
||||||
|
proj.gravity = Vector3<float>{0, 0, -9.81f};
|
||||||
|
|
||||||
|
// Unity: Y-up
|
||||||
|
proj.gravity = Vector3<float>{0, -9.81f, 0};
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Target velocity not updated**
|
||||||
|
```cpp
|
||||||
|
// Update target velocity each frame
|
||||||
|
target.velocity = current_velocity; // Not last frame's velocity!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern Scanning Issues
|
||||||
|
|
||||||
|
### Pattern not found when it should be
|
||||||
|
|
||||||
|
**Problem:** `pattern_scan()` returns `nullopt` but pattern exists.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. **Pattern syntax error**
|
||||||
|
```cpp
|
||||||
|
// Wrong: missing spaces
|
||||||
|
PatternView pattern{"488B05????????"};
|
||||||
|
|
||||||
|
// Right: spaces between bytes
|
||||||
|
PatternView pattern{"48 8B 05 ?? ?? ?? ??"};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Pattern too specific**
|
||||||
|
```cpp
|
||||||
|
// May fail if any byte is different
|
||||||
|
PatternView pattern{"48 8B 05 01 02 03 04 48 85 C0"};
|
||||||
|
|
||||||
|
// Better: use wildcards for variable bytes
|
||||||
|
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Searching wrong memory region**
|
||||||
|
```cpp
|
||||||
|
// Make sure you're scanning the right memory
|
||||||
|
std::vector<uint8_t> code_section = get_code_section();
|
||||||
|
auto result = pattern_scan(code_section, pattern);
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Pattern might have multiple matches**
|
||||||
|
```cpp
|
||||||
|
// Find all matches instead of just first
|
||||||
|
size_t offset = 0;
|
||||||
|
while (offset < memory.size()) {
|
||||||
|
auto result = pattern_scan(
|
||||||
|
std::span(memory.begin() + offset, memory.end()),
|
||||||
|
pattern
|
||||||
|
);
|
||||||
|
if (result) {
|
||||||
|
std::cout << "Match at: " << offset + result->offset << "\n";
|
||||||
|
offset += result->offset + 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern found at wrong location
|
||||||
|
|
||||||
|
**Problem:** Pattern matches unintended code.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. **Make pattern more specific**
|
||||||
|
```cpp
|
||||||
|
// Too generic
|
||||||
|
PatternView pattern{"48 8B"};
|
||||||
|
|
||||||
|
// More specific - include more context
|
||||||
|
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0 74 ??"};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Verify found address**
|
||||||
|
```cpp
|
||||||
|
if (auto result = pattern_scan(memory, pattern)) {
|
||||||
|
// Verify by checking nearby bytes
|
||||||
|
size_t offset = result->offset;
|
||||||
|
|
||||||
|
// Check if instruction makes sense
|
||||||
|
if (memory[offset] == 0x48 && memory[offset + 1] == 0x8B) {
|
||||||
|
// Looks good
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Use multiple patterns**
|
||||||
|
```cpp
|
||||||
|
// Find reference function first
|
||||||
|
auto ref_pattern = PatternView{"E8 ?? ?? ?? ?? 85 C0"};
|
||||||
|
auto ref_result = pattern_scan(memory, ref_pattern);
|
||||||
|
|
||||||
|
// Then search near that location
|
||||||
|
// This provides context validation
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vector & Math Issues
|
||||||
|
|
||||||
|
### `normalized()` returns zero vector
|
||||||
|
|
||||||
|
**Problem:** Normalizing a zero-length vector.
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
```cpp
|
||||||
|
Vector3<float> zero{0, 0, 0};
|
||||||
|
auto result = zero.normalized(); // Returns {0, 0, 0}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is **intentional** to avoid NaN. Check vector length first:
|
||||||
|
```cpp
|
||||||
|
if (v.length() > 0.001f) {
|
||||||
|
auto normalized = v.normalized();
|
||||||
|
// Use normalized vector
|
||||||
|
} else {
|
||||||
|
// Handle zero-length case
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `angle_between()` returns error
|
||||||
|
|
||||||
|
**Problem:** One or both vectors have zero length.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```cpp
|
||||||
|
auto angle_result = v1.angle_between(v2);
|
||||||
|
|
||||||
|
if (angle_result) {
|
||||||
|
float degrees = angle_result->as_degrees();
|
||||||
|
} else {
|
||||||
|
// Handle error - one or both vectors have zero length
|
||||||
|
std::cerr << "Cannot compute angle between zero-length vectors\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cross product seems wrong
|
||||||
|
|
||||||
|
**Problem:** Unexpected cross product result.
|
||||||
|
|
||||||
|
**Check:**
|
||||||
|
1. **Right-handed system**
|
||||||
|
```cpp
|
||||||
|
Vector3<float> x{1, 0, 0};
|
||||||
|
Vector3<float> y{0, 1, 0};
|
||||||
|
auto z = x.cross(y); // Should be {0, 0, 1} in right-handed system
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Order matters**
|
||||||
|
```cpp
|
||||||
|
auto cross1 = a.cross(b); // {x1, y1, z1}
|
||||||
|
auto cross2 = b.cross(a); // {-x1, -y1, -z1} (opposite direction!)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Issues
|
||||||
|
|
||||||
|
### Code is slower than expected
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. **Enable optimizations**
|
||||||
|
```cmake
|
||||||
|
# CMakeLists.txt
|
||||||
|
target_compile_options(your_target PRIVATE
|
||||||
|
$<$<CONFIG:Release>:-O3>
|
||||||
|
$<$<CONFIG:Release>:-march=native>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Use AVX2 engine**
|
||||||
|
```cpp
|
||||||
|
// Instead of
|
||||||
|
ProjPredEngineLegacy engine;
|
||||||
|
|
||||||
|
// Use
|
||||||
|
ProjPredEngineAVX2 engine;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Avoid unnecessary operations**
|
||||||
|
```cpp
|
||||||
|
// Bad: recompute every frame
|
||||||
|
for (auto& entity : entities) {
|
||||||
|
float dist = entity.pos.distance_to(player_pos); // Expensive sqrt!
|
||||||
|
if (dist < 100.0f) { /* ... */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good: use squared distance
|
||||||
|
constexpr float max_dist_sq = 100.0f * 100.0f;
|
||||||
|
for (auto& entity : entities) {
|
||||||
|
float dist_sq = entity.pos.distance_to_sqr(player_pos); // No sqrt!
|
||||||
|
if (dist_sq < max_dist_sq) { /* ... */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Cache matrices**
|
||||||
|
```cpp
|
||||||
|
// Bad: recompute matrix every call
|
||||||
|
for (auto& pos : positions) {
|
||||||
|
auto screen = camera.world_to_screen(pos); // Recomputes matrices!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good: matrices are cached in camera automatically
|
||||||
|
camera.update(pos, angles); // Updates matrices once
|
||||||
|
for (auto& pos : positions) {
|
||||||
|
auto screen = camera.world_to_screen(pos); // Uses cached matrices
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting More Help
|
||||||
|
|
||||||
|
If your issue isn't covered here:
|
||||||
|
|
||||||
|
1. **Check the docs**: [API Overview](api_overview.md), [Tutorials](tutorials.md)
|
||||||
|
2. **Search GitHub issues**: [Issues page](https://github.com/orange-cpp/omath/issues)
|
||||||
|
3. **Ask on Discord**: [Join community](https://discord.gg/eDgdaWbqwZ)
|
||||||
|
4. **Open a new issue**: Include:
|
||||||
|
- OMath version
|
||||||
|
- Compiler and version
|
||||||
|
- Minimal reproducible example
|
||||||
|
- What you expected vs what happened
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
616
docs/tutorials.md
Normal file
@@ -0,0 +1,616 @@
|
|||||||
|
# Tutorials
|
||||||
|
|
||||||
|
This page provides step-by-step tutorials for common OMath use cases.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tutorial 1: Basic Vector Math
|
||||||
|
|
||||||
|
Learn the fundamentals of vector operations in OMath.
|
||||||
|
|
||||||
|
### Step 1: Include OMath
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace omath;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Create Vectors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 2D vectors
|
||||||
|
Vector2<float> v2a{3.0f, 4.0f};
|
||||||
|
Vector2<float> v2b{1.0f, 2.0f};
|
||||||
|
|
||||||
|
// 3D vectors
|
||||||
|
Vector3<float> v3a{1.0f, 2.0f, 3.0f};
|
||||||
|
Vector3<float> v3b{4.0f, 5.0f, 6.0f};
|
||||||
|
|
||||||
|
// 4D vectors (often used for homogeneous coordinates)
|
||||||
|
Vector4<float> v4{1.0f, 2.0f, 3.0f, 1.0f};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Perform Operations
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Addition
|
||||||
|
auto sum = v3a + v3b; // {5, 7, 9}
|
||||||
|
|
||||||
|
// Subtraction
|
||||||
|
auto diff = v3a - v3b; // {-3, -3, -3}
|
||||||
|
|
||||||
|
// Scalar multiplication
|
||||||
|
auto scaled = v3a * 2.0f; // {2, 4, 6}
|
||||||
|
|
||||||
|
// Dot product
|
||||||
|
float dot = v3a.dot(v3b); // 32.0
|
||||||
|
|
||||||
|
// Cross product (3D only)
|
||||||
|
auto cross = v3a.cross(v3b); // {-3, 6, -3}
|
||||||
|
|
||||||
|
// Length
|
||||||
|
float len = v3a.length(); // ~3.74
|
||||||
|
|
||||||
|
// Normalization (safe - returns original if length is zero)
|
||||||
|
auto normalized = v3a.normalized();
|
||||||
|
|
||||||
|
// Distance between vectors
|
||||||
|
float dist = v3a.distance_to(v3b); // ~5.196
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Angle Calculations
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (auto angle = v3a.angle_between(v3b)) {
|
||||||
|
std::cout << "Angle in degrees: " << angle->as_degrees() << "\n";
|
||||||
|
std::cout << "Angle in radians: " << angle->as_radians() << "\n";
|
||||||
|
} else {
|
||||||
|
std::cout << "Cannot compute angle (zero-length vector)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if perpendicular
|
||||||
|
if (v3a.is_perpendicular(v3b)) {
|
||||||
|
std::cout << "Vectors are perpendicular\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key takeaways:**
|
||||||
|
- All vector operations are type-safe and constexpr-friendly
|
||||||
|
- Safe normalization never produces NaN
|
||||||
|
- Angle calculations use `std::expected` for error handling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tutorial 2: World-to-Screen Projection
|
||||||
|
|
||||||
|
Project 3D coordinates to 2D screen space for overlays and ESP.
|
||||||
|
|
||||||
|
### Step 1: Choose Your Game Engine
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
|
||||||
|
// For Source Engine games (CS:GO, TF2, etc.)
|
||||||
|
using namespace omath::source_engine;
|
||||||
|
|
||||||
|
// Or for other engines:
|
||||||
|
// using namespace omath::unity_engine;
|
||||||
|
// using namespace omath::unreal_engine;
|
||||||
|
// using namespace omath::frostbite_engine;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Set Up the Camera
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath;
|
||||||
|
using namespace omath::projection;
|
||||||
|
|
||||||
|
// Define viewport (screen dimensions)
|
||||||
|
ViewPort viewport{1920.0f, 1080.0f};
|
||||||
|
|
||||||
|
// Define field of view
|
||||||
|
auto fov = FieldOfView::from_degrees(90.0f);
|
||||||
|
|
||||||
|
// Camera position and angles
|
||||||
|
Vector3<float> camera_pos{0.0f, 0.0f, 100.0f};
|
||||||
|
ViewAngles camera_angles{
|
||||||
|
PitchAngle::from_degrees(0.0f),
|
||||||
|
YawAngle::from_degrees(0.0f),
|
||||||
|
RollAngle::from_degrees(0.0f)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create camera (using Source Engine in this example)
|
||||||
|
Camera camera(
|
||||||
|
camera_pos,
|
||||||
|
camera_angles,
|
||||||
|
viewport,
|
||||||
|
fov,
|
||||||
|
0.1f, // near plane
|
||||||
|
1000.0f // far plane
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Project 3D Points
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 3D world position (e.g., enemy player position)
|
||||||
|
Vector3<float> enemy_pos{150.0f, 200.0f, 75.0f};
|
||||||
|
|
||||||
|
// Project to screen
|
||||||
|
if (auto screen = camera.world_to_screen(enemy_pos)) {
|
||||||
|
std::cout << "Enemy on screen at: "
|
||||||
|
<< screen->x << ", " << screen->y << "\n";
|
||||||
|
|
||||||
|
// Draw ESP box or marker at screen->x, screen->y
|
||||||
|
// Note: screen coordinates are in viewport space (0-width, 0-height)
|
||||||
|
} else {
|
||||||
|
// Enemy is not visible (behind camera or outside frustum)
|
||||||
|
std::cout << "Enemy not visible\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Update Camera for Each Frame
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void render_frame() {
|
||||||
|
// Read current camera data from game
|
||||||
|
Vector3<float> new_pos = read_camera_position();
|
||||||
|
ViewAngles new_angles = read_camera_angles();
|
||||||
|
|
||||||
|
// Update camera
|
||||||
|
camera.update(new_pos, new_angles);
|
||||||
|
|
||||||
|
// Project all entities
|
||||||
|
for (const auto& entity : entities) {
|
||||||
|
if (auto screen = camera.world_to_screen(entity.position)) {
|
||||||
|
draw_esp_box(screen->x, screen->y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key takeaways:**
|
||||||
|
- Choose the engine trait that matches your target game
|
||||||
|
- `world_to_screen()` returns `std::optional` - always check the result
|
||||||
|
- Update camera each frame for accurate projections
|
||||||
|
- Screen coordinates are in the viewport space you defined
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tutorial 3: Projectile Prediction (Aim-Bot)
|
||||||
|
|
||||||
|
Calculate where to aim to hit a moving target.
|
||||||
|
|
||||||
|
### Step 1: Define Projectile Properties
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
#include <omath/projectile_prediction/proj_pred_engine_legacy.hpp>
|
||||||
|
|
||||||
|
using namespace omath;
|
||||||
|
using namespace omath::projectile_prediction;
|
||||||
|
|
||||||
|
// Define your weapon's projectile
|
||||||
|
Projectile bullet;
|
||||||
|
bullet.origin = Vector3<float>{0, 0, 0}; // Shooter position
|
||||||
|
bullet.speed = 800.0f; // Muzzle velocity (m/s or game units/s)
|
||||||
|
bullet.gravity = Vector3<float>{0, 0, -9.81f}; // Gravity vector
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Define Target State
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Target information (enemy player)
|
||||||
|
Target enemy;
|
||||||
|
enemy.position = Vector3<float>{100, 200, 50}; // Current position
|
||||||
|
enemy.velocity = Vector3<float>{10, 5, 0}; // Current velocity
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Calculate Aim Point
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Create prediction engine
|
||||||
|
// Use AVX2 version if available for better performance:
|
||||||
|
// ProjPredEngineAVX2 engine;
|
||||||
|
ProjPredEngineLegacy engine;
|
||||||
|
|
||||||
|
// Calculate where to aim
|
||||||
|
if (auto aim_point = engine.maybe_calculate_aim_point(bullet, enemy)) {
|
||||||
|
std::cout << "Aim at: "
|
||||||
|
<< aim_point->x << ", "
|
||||||
|
<< aim_point->y << ", "
|
||||||
|
<< aim_point->z << "\n";
|
||||||
|
|
||||||
|
// Calculate angles to aim_point
|
||||||
|
Vector3<float> aim_direction = (*aim_point - bullet.origin).normalized();
|
||||||
|
|
||||||
|
// Convert to view angles (engine-specific)
|
||||||
|
// ViewAngles angles = calculate_angles_to_direction(aim_direction);
|
||||||
|
// set_aim_angles(angles);
|
||||||
|
} else {
|
||||||
|
// Cannot hit target (too fast, out of range, etc.)
|
||||||
|
std::cout << "Target cannot be hit\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Handle Different Scenarios
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Stationary target
|
||||||
|
Target stationary;
|
||||||
|
stationary.position = Vector3<float>{100, 100, 100};
|
||||||
|
stationary.velocity = Vector3<float>{0, 0, 0};
|
||||||
|
// aim_point will equal position for stationary targets
|
||||||
|
|
||||||
|
// Fast-moving target
|
||||||
|
Target fast;
|
||||||
|
fast.position = Vector3<float>{100, 100, 100};
|
||||||
|
fast.velocity = Vector3<float>{50, 0, 0}; // Moving very fast
|
||||||
|
// May return nullopt if target is too fast
|
||||||
|
|
||||||
|
// Target at different heights
|
||||||
|
Target aerial;
|
||||||
|
aerial.position = Vector3<float>{100, 100, 200}; // High up
|
||||||
|
aerial.velocity = Vector3<float>{5, 5, -10}; // Falling
|
||||||
|
// Gravity will be factored into the calculation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Performance Optimization
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// For better performance on modern CPUs, use AVX2:
|
||||||
|
#include <omath/projectile_prediction/proj_pred_engine_avx2.hpp>
|
||||||
|
|
||||||
|
ProjPredEngineAVX2 fast_engine; // 2-4x faster than legacy
|
||||||
|
|
||||||
|
// Use the same way as legacy engine
|
||||||
|
if (auto aim = fast_engine.maybe_calculate_aim_point(bullet, enemy)) {
|
||||||
|
// Process aim point
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key takeaways:**
|
||||||
|
- Always check if aim point exists before using
|
||||||
|
- Velocity must be in same units as position/speed
|
||||||
|
- Gravity vector points down (typically negative Z or Y depending on engine)
|
||||||
|
- Use AVX2 engine when possible for better performance
|
||||||
|
- Returns `nullopt` when target is unreachable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tutorial 4: Collision Detection
|
||||||
|
|
||||||
|
Perform ray-casting and intersection tests.
|
||||||
|
|
||||||
|
### Step 1: Ray-Plane Intersection
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
// Define a ground plane (Z=0, normal pointing up)
|
||||||
|
Plane ground{
|
||||||
|
Vector3<float>{0, 0, 0}, // Point on plane
|
||||||
|
Vector3<float>{0, 0, 1} // Normal vector (Z-up)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define a ray (e.g., looking downward from above)
|
||||||
|
Vector3<float> ray_origin{10, 20, 100};
|
||||||
|
Vector3<float> ray_direction{0, 0, -1}; // Pointing down
|
||||||
|
|
||||||
|
// Test intersection
|
||||||
|
if (auto hit = ground.intersects_ray(ray_origin, ray_direction)) {
|
||||||
|
std::cout << "Hit ground at: "
|
||||||
|
<< hit->x << ", " << hit->y << ", " << hit->z << "\n";
|
||||||
|
// Expected: (10, 20, 0)
|
||||||
|
} else {
|
||||||
|
std::cout << "Ray does not intersect plane\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Distance to Plane
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Calculate signed distance from point to plane
|
||||||
|
Vector3<float> point{10, 20, 50};
|
||||||
|
float distance = ground.distance_to_point(point);
|
||||||
|
|
||||||
|
std::cout << "Distance to ground: " << distance << "\n";
|
||||||
|
// Expected: 50.0 (50 units above ground)
|
||||||
|
|
||||||
|
// Negative distance means point is below the plane
|
||||||
|
Vector3<float> below{10, 20, -5};
|
||||||
|
float dist_below = ground.distance_to_point(below);
|
||||||
|
// Expected: -5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Axis-Aligned Bounding Box
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/3d_primitives/box.hpp>
|
||||||
|
|
||||||
|
// Create a bounding box
|
||||||
|
Box bbox{
|
||||||
|
Vector3<float>{0, 0, 0}, // Min corner
|
||||||
|
Vector3<float>{100, 100, 100} // Max corner
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test if point is inside
|
||||||
|
Vector3<float> inside{50, 50, 50};
|
||||||
|
if (bbox.contains(inside)) {
|
||||||
|
std::cout << "Point is inside box\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3<float> outside{150, 50, 50};
|
||||||
|
if (!bbox.contains(outside)) {
|
||||||
|
std::cout << "Point is outside box\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Box-box intersection
|
||||||
|
Box other{
|
||||||
|
Vector3<float>{50, 50, 50},
|
||||||
|
Vector3<float>{150, 150, 150}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bbox.intersects(other)) {
|
||||||
|
std::cout << "Boxes overlap\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Line Tracing
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/collision/line_tracer.hpp>
|
||||||
|
|
||||||
|
using namespace omath::collision;
|
||||||
|
|
||||||
|
// Ray-triangle intersection
|
||||||
|
Vector3<float> v0{0, 0, 0};
|
||||||
|
Vector3<float> v1{100, 0, 0};
|
||||||
|
Vector3<float> v2{0, 100, 0};
|
||||||
|
|
||||||
|
Vector3<float> ray_start{25, 25, 100};
|
||||||
|
Vector3<float> ray_dir{0, 0, -1};
|
||||||
|
|
||||||
|
LineTracer tracer;
|
||||||
|
if (auto hit = tracer.ray_triangle_intersect(ray_start, ray_dir, v0, v1, v2)) {
|
||||||
|
std::cout << "Hit triangle at: "
|
||||||
|
<< hit->point.x << ", "
|
||||||
|
<< hit->point.y << ", "
|
||||||
|
<< hit->point.z << "\n";
|
||||||
|
std::cout << "Hit distance: " << hit->distance << "\n";
|
||||||
|
std::cout << "Surface normal: "
|
||||||
|
<< hit->normal.x << ", "
|
||||||
|
<< hit->normal.y << ", "
|
||||||
|
<< hit->normal.z << "\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key takeaways:**
|
||||||
|
- Plane normals should be unit vectors
|
||||||
|
- Ray direction should typically be normalized
|
||||||
|
- Signed distance indicates which side of plane a point is on
|
||||||
|
- AABB tests are very fast for broad-phase collision detection
|
||||||
|
- Line tracer provides hit point, distance, and surface normal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tutorial 5: Pattern Scanning
|
||||||
|
|
||||||
|
Search for byte patterns in memory.
|
||||||
|
|
||||||
|
### Step 1: Basic Pattern Scanning
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/utility/pattern_scan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
// Memory to search (e.g., from a loaded module)
|
||||||
|
std::vector<uint8_t> memory = {
|
||||||
|
0x48, 0x8B, 0x05, 0xAA, 0xBB, 0xCC, 0xDD,
|
||||||
|
0x48, 0x85, 0xC0, 0x74, 0x10,
|
||||||
|
// ... more bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pattern with wildcards (?? = match any byte)
|
||||||
|
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
|
||||||
|
|
||||||
|
// Scan for pattern
|
||||||
|
if (auto result = pattern_scan(memory, pattern)) {
|
||||||
|
std::cout << "Pattern found at offset: " << result->offset << "\n";
|
||||||
|
|
||||||
|
// Extract wildcard values if needed
|
||||||
|
// result->wildcards contains the matched bytes at ?? positions
|
||||||
|
} else {
|
||||||
|
std::cout << "Pattern not found\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: PE File Scanning
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/utility/pe_pattern_scan.hpp>
|
||||||
|
|
||||||
|
// Scan a PE file (EXE or DLL)
|
||||||
|
PEPatternScanner scanner("game.exe");
|
||||||
|
|
||||||
|
PatternView pattern{"E8 ?? ?? ?? ?? 85 C0 75 ??"};
|
||||||
|
|
||||||
|
if (auto rva = scanner.scan_pattern(pattern)) {
|
||||||
|
std::cout << "Pattern found at RVA: 0x"
|
||||||
|
<< std::hex << *rva << std::dec << "\n";
|
||||||
|
|
||||||
|
// Convert RVA to absolute address if needed
|
||||||
|
uintptr_t base_address = get_module_base("game.exe");
|
||||||
|
uintptr_t absolute = base_address + *rva;
|
||||||
|
} else {
|
||||||
|
std::cout << "Pattern not found in PE file\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Multiple Patterns
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Search for multiple patterns
|
||||||
|
std::vector<PatternView> patterns{
|
||||||
|
PatternView{"48 8B 05 ?? ?? ?? ??"},
|
||||||
|
PatternView{"E8 ?? ?? ?? ?? 85 C0"},
|
||||||
|
PatternView{"FF 15 ?? ?? ?? ?? 48 8B"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < patterns.size(); ++i) {
|
||||||
|
if (auto result = pattern_scan(memory, patterns[i])) {
|
||||||
|
std::cout << "Pattern " << i << " found at: "
|
||||||
|
<< result->offset << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Pattern with Masks
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Alternative: use mask-based patterns
|
||||||
|
// Pattern: bytes to match
|
||||||
|
std::vector<uint8_t> pattern_bytes{0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
|
// Mask: 'x' = must match, '?' = wildcard
|
||||||
|
std::string mask{"xxx????"};
|
||||||
|
|
||||||
|
// Custom scan function
|
||||||
|
auto scan_with_mask = [&](const std::vector<uint8_t>& data) {
|
||||||
|
for (size_t i = 0; i < data.size() - pattern_bytes.size(); ++i) {
|
||||||
|
bool match = true;
|
||||||
|
for (size_t j = 0; j < pattern_bytes.size(); ++j) {
|
||||||
|
if (mask[j] == 'x' && data[i + j] != pattern_bytes[j]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) return i;
|
||||||
|
}
|
||||||
|
return size_t(-1);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key takeaways:**
|
||||||
|
- Use `??` in pattern strings for wildcards
|
||||||
|
- PE scanner works with files and modules
|
||||||
|
- Pattern scanning is useful for finding functions, vtables, or data
|
||||||
|
- Always validate found addresses before use
|
||||||
|
- Patterns may have multiple matches - consider context
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tutorial 6: Angles and View Angles
|
||||||
|
|
||||||
|
Work with game camera angles properly.
|
||||||
|
|
||||||
|
### Step 1: Understanding Angle Types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
|
||||||
|
using namespace omath;
|
||||||
|
|
||||||
|
// Generic angle with custom range
|
||||||
|
auto angle1 = Angle<float, 0.0f, 360.0f>::from_degrees(45.0f);
|
||||||
|
auto angle2 = Angle<float, -180.0f, 180.0f>::from_degrees(270.0f);
|
||||||
|
|
||||||
|
// Specialized camera angles
|
||||||
|
auto pitch = PitchAngle::from_degrees(-10.0f); // Looking down
|
||||||
|
auto yaw = YawAngle::from_degrees(90.0f); // Looking right
|
||||||
|
auto roll = RollAngle::from_degrees(0.0f); // No tilt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Angle Conversions
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Create from degrees
|
||||||
|
auto deg_angle = PitchAngle::from_degrees(45.0f);
|
||||||
|
|
||||||
|
// Get as radians
|
||||||
|
float radians = deg_angle.as_radians();
|
||||||
|
std::cout << "45° = " << radians << " radians\n";
|
||||||
|
|
||||||
|
// Get as degrees
|
||||||
|
float degrees = deg_angle.as_degrees();
|
||||||
|
std::cout << "Value: " << degrees << "°\n";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: View Angles (Camera)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Pitch: vertical rotation (-89° to 89°)
|
||||||
|
// Yaw: horizontal rotation (-180° to 180°)
|
||||||
|
// Roll: camera tilt (-180° to 180°)
|
||||||
|
|
||||||
|
ViewAngles camera_angles{
|
||||||
|
PitchAngle::from_degrees(-15.0f), // Looking slightly down
|
||||||
|
YawAngle::from_degrees(45.0f), // Facing northeast
|
||||||
|
RollAngle::from_degrees(0.0f) // No tilt
|
||||||
|
};
|
||||||
|
|
||||||
|
// Access individual components
|
||||||
|
float pitch_val = camera_angles.pitch.as_degrees();
|
||||||
|
float yaw_val = camera_angles.yaw.as_degrees();
|
||||||
|
float roll_val = camera_angles.roll.as_degrees();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Calculating Look-At Angles
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace omath::source_engine; // Or your game's engine
|
||||||
|
|
||||||
|
Vector3<float> camera_pos{0, 0, 100};
|
||||||
|
Vector3<float> target_pos{100, 100, 100};
|
||||||
|
|
||||||
|
// Calculate angles to look at target
|
||||||
|
ViewAngles look_at = CameraTrait::calc_look_at_angle(camera_pos, target_pos);
|
||||||
|
|
||||||
|
std::cout << "Pitch: " << look_at.pitch.as_degrees() << "°\n";
|
||||||
|
std::cout << "Yaw: " << look_at.yaw.as_degrees() << "°\n";
|
||||||
|
std::cout << "Roll: " << look_at.roll.as_degrees() << "°\n";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Angle Arithmetic
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Angles support arithmetic with automatic normalization
|
||||||
|
auto angle1 = YawAngle::from_degrees(170.0f);
|
||||||
|
auto angle2 = YawAngle::from_degrees(20.0f);
|
||||||
|
|
||||||
|
// Addition (wraps around)
|
||||||
|
auto sum = angle1 + angle2; // 190° → normalized to -170°
|
||||||
|
|
||||||
|
// Subtraction
|
||||||
|
auto diff = angle2 - angle1; // -150°
|
||||||
|
|
||||||
|
// Scaling
|
||||||
|
auto scaled = angle1 * 2.0f;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key takeaways:**
|
||||||
|
- Use specialized angle types for camera angles (PitchAngle, YawAngle, RollAngle)
|
||||||
|
- Angles automatically normalize to their valid ranges
|
||||||
|
- Each game engine may have different angle conventions
|
||||||
|
- Use engine traits to calculate look-at angles correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that you've completed these tutorials, explore:
|
||||||
|
|
||||||
|
- **[API Overview](api_overview.md)** - Complete API reference
|
||||||
|
- **[Engine Documentation](engines/)** - Engine-specific features
|
||||||
|
- **[Examples](../examples/)** - More code examples
|
||||||
|
- **[Getting Started](getting_started.md)** - Quick start guide
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 1 Nov 2025*
|
||||||
190
docs/utility/color.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# `omath::Color` — RGBA color with HSV helpers (C++20/23)
|
||||||
|
|
||||||
|
> Header: your project’s `color.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Inherits: `Vector4<float>` (`x=r`, `y=g`, `z=b`, `w=a`)
|
||||||
|
> Depends on: `<cstdint>`, `Vector4`, optionally ImGui (`OMATH_IMGUI_INTEGRATION`)
|
||||||
|
> Formatting: provides `std::formatter<omath::Color>`
|
||||||
|
|
||||||
|
`Color` is a tiny RGBA utility on top of `Vector4<float>`. It offers sRGB-style channel construction, HSV↔RGB conversion, in-place HSV setters, linear blending, and string/formatter helpers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "color.hpp"
|
||||||
|
using omath::Color;
|
||||||
|
|
||||||
|
// RGBA in [0,1] (r,g,b clamped to [0,1] on construction)
|
||||||
|
Color c{0.2f, 0.4f, 0.8f, 0.5f};
|
||||||
|
|
||||||
|
// From 8-bit channels
|
||||||
|
auto red = Color::from_rgba(255, 0, 0, 255);
|
||||||
|
auto green = Color::from_rgba(0, 255, 0, 160);
|
||||||
|
|
||||||
|
// From HSV (h ∈ [0,1], s ∈ [0,1], v ∈ [0,1])
|
||||||
|
auto cyan = Color::from_hsv(0.5f, 1.0f, 1.0f); // a = 1
|
||||||
|
|
||||||
|
// Read/modify via HSV
|
||||||
|
auto hsv = cyan.to_hsv(); // hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1]
|
||||||
|
cyan.set_value(0.6f); // converts back to RGB (alpha becomes 1)
|
||||||
|
|
||||||
|
// Blend linearly (lerp)
|
||||||
|
auto mid = red.blend(green, 0.5f);
|
||||||
|
|
||||||
|
// Printable (0–255 per channel)
|
||||||
|
std::string s = std::format("{}", mid); // "[r:128, g:128, b:0, a:207]" for example
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data model
|
||||||
|
|
||||||
|
* Inherits `Vector4<float>`:
|
||||||
|
|
||||||
|
* `x` = **red**, `y` = **green**, `z` = **blue**, `w` = **alpha**.
|
||||||
|
* Construction clamps **RGB** to `[0,1]` (via `Vector4::clamp(0,1)`), **alpha is not clamped** by that call (see notes).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Construction & factories
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// RGBA in [0,1] (RGB clamped to [0,1]; alpha untouched by clamp)
|
||||||
|
constexpr Color(float r, float g, float b, float a) noexcept;
|
||||||
|
|
||||||
|
// Default
|
||||||
|
constexpr Color() noexcept;
|
||||||
|
|
||||||
|
// From 8-bit RGBA (0–255) → normalized to [0,1]
|
||||||
|
constexpr static Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
|
||||||
|
|
||||||
|
// From HSV where hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1]
|
||||||
|
struct Hsv { float hue{}, saturation{}, value{}; };
|
||||||
|
|
||||||
|
constexpr static Color from_hsv(float hue, float saturation, float value) noexcept;
|
||||||
|
constexpr static Color from_hsv(const Hsv& hsv) noexcept; // delegates to the above
|
||||||
|
|
||||||
|
// Construct from a Vector4 (RGB clamped, alpha not clamped)
|
||||||
|
constexpr explicit Color(const Vector4& vec) noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
**HSV details**
|
||||||
|
|
||||||
|
* `from_hsv(h, s, v)`: `h` is **normalized** (`[0,1]`); it is clamped, then mapped to the 6 hue sectors; **alpha = 1.0**.
|
||||||
|
* `to_hsv()`: returns `Hsv{h,s,v}` with **`h ∈ [0,1]`** (internally computes degrees and divides by 360), `s,v ∈ [0,1]`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mutators
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr void set_hue(float h) noexcept; // h ∈ [0,1] recommended
|
||||||
|
constexpr void set_saturation(float s) noexcept; // s ∈ [0,1]
|
||||||
|
constexpr void set_value(float v) noexcept; // v ∈ [0,1]
|
||||||
|
|
||||||
|
// Linear blend: (1-ratio)*this + ratio*other, ratio clamped to [0,1]
|
||||||
|
constexpr Color blend(const Color& other, float ratio) const noexcept;
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **Alpha reset on HSV setters:** each `set_*` converts HSV→RGB using `from_hsv(...)`, which **sets alpha to 1.0** (overwriting previous `w`). If you need to preserve alpha:
|
||||||
|
>
|
||||||
|
> ```cpp
|
||||||
|
> float a = col.w;
|
||||||
|
> col.set_value(0.5f);
|
||||||
|
> col.w = a;
|
||||||
|
> ```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
static constexpr Color red(); // (1,0,0,1)
|
||||||
|
static constexpr Color green(); // (0,1,0,1)
|
||||||
|
static constexpr Color blue(); // (0,0,1,1)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## String & formatting
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// "[r:R, g:G, b:B, a:A]" with each channel shown as 0–255 integer
|
||||||
|
std::string to_string() const noexcept;
|
||||||
|
std::wstring to_wstring() const noexcept;
|
||||||
|
std::u8string to_u8string() const noexcept;
|
||||||
|
|
||||||
|
// Formatter forwards to the above (char/wchar_t/char8_t)
|
||||||
|
template<> struct std::formatter<omath::Color>;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ImGui (optional)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#ifdef OMATH_IMGUI_INTEGRATION
|
||||||
|
ImColor to_im_color() const noexcept; // constructs from Vector4's to_im_vec4()
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure `<imgui.h>` is included somewhere before this header when the macro is enabled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & caveats
|
||||||
|
|
||||||
|
* **Alpha clamping:** `Vector4::clamp(min,max)` (called by `Color` ctors) clamps **x,y,z** only in the provided `Vector4` implementation; `w` is **left unchanged**. If you require strict `[0,1]` alpha, clamp it yourself:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
col.w = std::clamp(col.w, 0.0f, 1.0f);
|
||||||
|
```
|
||||||
|
* **HSV range:** The API consistently uses **normalized hue** (`[0,1]`). Convert degrees ↔ normalized as `h_norm = h_deg / 360.f`.
|
||||||
|
* **Blend space:** `blend` is a **linear** interpolation in RGBA; it is not perceptually uniform.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API summary
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Hsv { float hue{}, saturation{}, value{}; };
|
||||||
|
|
||||||
|
class Color final : public Vector4<float> {
|
||||||
|
public:
|
||||||
|
constexpr Color(float r, float g, float b, float a) noexcept;
|
||||||
|
constexpr Color() noexcept;
|
||||||
|
constexpr explicit Color(const Vector4& vec) noexcept;
|
||||||
|
|
||||||
|
static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
|
||||||
|
static constexpr Color from_hsv(float hue, float saturation, float value) noexcept;
|
||||||
|
static constexpr Color from_hsv(const Hsv& hsv) noexcept;
|
||||||
|
|
||||||
|
constexpr Hsv to_hsv() const noexcept;
|
||||||
|
|
||||||
|
constexpr void set_hue(float h) noexcept;
|
||||||
|
constexpr void set_saturation(float s) noexcept;
|
||||||
|
constexpr void set_value(float v) noexcept;
|
||||||
|
|
||||||
|
constexpr Color blend(const Color& other, float ratio) const noexcept;
|
||||||
|
|
||||||
|
static constexpr Color red();
|
||||||
|
static constexpr Color green();
|
||||||
|
static constexpr Color blue();
|
||||||
|
|
||||||
|
#ifdef OMATH_IMGUI_INTEGRATION
|
||||||
|
ImColor to_im_color() const noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string to_string() const noexcept;
|
||||||
|
std::wstring to_wstring() const noexcept;
|
||||||
|
std::u8string to_u8string() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
// formatter<omath::Color> provided
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 31 Oct 2025*
|
||||||
194
docs/utility/pattern_scan.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# `omath::PatternScanner` — Fast byte-pattern search with wildcards
|
||||||
|
|
||||||
|
> Header: your project’s `pattern_scanner.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Core API: `scan_for_pattern(...)` (span or iterators)
|
||||||
|
> Errors: `PatternScanError::INVALID_PATTERN_STRING` (from `parse_pattern`)
|
||||||
|
> C++: uses `std::span`, `std::expected`, `std::byte`
|
||||||
|
|
||||||
|
`PatternScanner` scans a contiguous byte range for a **hex pattern** that may include **wildcards**. It returns an iterator to the **first match** or the end iterator if not found (or if the pattern string is invalid).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "pattern_scanner.hpp"
|
||||||
|
using omath::PatternScanner;
|
||||||
|
|
||||||
|
std::vector<std::byte> buf = /* ... bytes ... */;
|
||||||
|
std::span<std::byte> s{buf.data(), buf.size()};
|
||||||
|
|
||||||
|
// Example pattern: "48 8B ?? ?? 89" (hex bytes with '?' as any byte)
|
||||||
|
auto it = PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89");
|
||||||
|
if (it != s.end()) {
|
||||||
|
// Found at offset:
|
||||||
|
auto offset = static_cast<std::size_t>(it - s.begin());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with iterators:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "DE AD BE EF");
|
||||||
|
if (it != buf.end()) { /* ... */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern string grammar
|
||||||
|
|
||||||
|
The pattern string is parsed into a sequence of byte **tokens**:
|
||||||
|
|
||||||
|
* **Hex byte**: two hexadecimal digits form one byte.
|
||||||
|
Examples: `90`, `4F`, `00`, `ff`
|
||||||
|
* **Wildcard byte**: `?` or `??` matches **any single byte**.
|
||||||
|
* **Separators**: any ASCII whitespace (space, tab, newline) is **ignored** and may be used to group tokens.
|
||||||
|
|
||||||
|
> ✔️ Valid: `"48 8B ?? 05 00"`, `"90 90 90"`, `"??"`
|
||||||
|
> ❌ Invalid: odd number of hex digits in a token, non-hex characters (besides `?` and whitespace)
|
||||||
|
|
||||||
|
If the string cannot be parsed into a clean sequence of tokens, `parse_pattern()` returns `std::unexpected(PatternScanError::INVALID_PATTERN_STRING)`, and the public scan function returns **end**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath {
|
||||||
|
|
||||||
|
enum class PatternScanError { INVALID_PATTERN_STRING };
|
||||||
|
|
||||||
|
class PatternScanner final {
|
||||||
|
public:
|
||||||
|
// Contiguous range (span) overload
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::span<std::byte>::iterator
|
||||||
|
scan_for_pattern(const std::span<std::byte>& range,
|
||||||
|
const std::string_view& pattern);
|
||||||
|
|
||||||
|
// Deleted rvalue-span overload (prevents dangling)
|
||||||
|
static std::span<std::byte>::iterator
|
||||||
|
scan_for_pattern(std::span<std::byte>&&,
|
||||||
|
const std::string_view&) = delete;
|
||||||
|
|
||||||
|
// Iterator overload
|
||||||
|
template<class IteratorType>
|
||||||
|
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
|
||||||
|
static IteratorType
|
||||||
|
scan_for_pattern(const IteratorType& begin,
|
||||||
|
const IteratorType& end,
|
||||||
|
const std::string_view& pattern);
|
||||||
|
private:
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
|
||||||
|
parse_pattern(const std::string_view& pattern_string);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath
|
||||||
|
```
|
||||||
|
|
||||||
|
### Return value
|
||||||
|
|
||||||
|
* On success: iterator to the **first** matching position.
|
||||||
|
* On failure / not found / invalid pattern: **`end`**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
|
||||||
|
Let `N` be the number of bytes in the range and `M` the number of **pattern tokens**.
|
||||||
|
|
||||||
|
* Time: **O(N × M)** (simple sliding window with early break).
|
||||||
|
* Space: **O(M)** for the parsed pattern vector.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Find a function prologue
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// x86-64: push rbp; mov rbp, rsp
|
||||||
|
auto it = PatternScanner::scan_for_pattern(s, "55 48 89 E5");
|
||||||
|
if (it != s.end()) {
|
||||||
|
// ... process
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skip variable bytes with wildcards
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// mov rax, <imm32>; call <rel32>
|
||||||
|
auto it = PatternScanner::scan_for_pattern(s, "48 B8 ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ??");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Iterator-based scan (subrange)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto sub_begin = buf.begin() + 1024;
|
||||||
|
auto sub_end = buf.begin() + 4096;
|
||||||
|
auto it = PatternScanner::scan_for_pattern(sub_begin, sub_end, "DE AD ?? BE EF");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Behavior & edge cases
|
||||||
|
|
||||||
|
* **Empty or too-short**: If the effective pattern token count `M` is `0` or `M > N`, the function returns `end`.
|
||||||
|
* **Wildcards**: Each `?`/`??` token matches **exactly one** byte.
|
||||||
|
* **No exceptions**: Invalid pattern → parsed as `unexpected`, public API returns `end`.
|
||||||
|
* **No ownership**: The span overload takes `const std::span<std::byte>&` and returns a **mutable iterator** into that span. You must ensure the underlying memory stays alive. The rvalue-span overload is **deleted** to prevent dangling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & caveats (implementation-sensitive)
|
||||||
|
|
||||||
|
* Although the iterator overload is constrained with `std::input_or_output_iterator`, the implementation uses `*(begin + i + j)` and arithmetic on iterators. In practice this means you should pass **random-access / contiguous iterators** (e.g., from `std::vector<std::byte>` or `std::span<std::byte>`). Using non-random-access iterators would be ill-formed.
|
||||||
|
* The inner matching loop compares a parsed token (`std::optional<std::byte>`) to the candidate byte; `std::nullopt` is treated as a **wildcard** (always matches).
|
||||||
|
* **Implementation note:** the outer scan currently derives its scan bound from the **pattern string length**; conceptually it should use the **number of parsed tokens** (hex/wildcard bytes). If you plan to accept spaces (or other non-byte characters) in the pattern string, ensure the scan window uses the parsed token count to avoid false negatives near the end of the buffer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing hooks
|
||||||
|
|
||||||
|
The class befriends several unit tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
unit_test_pattern_scan_read_test_Test
|
||||||
|
unit_test_pattern_scan_corner_case_1_Test
|
||||||
|
unit_test_pattern_scan_corner_case_2_Test
|
||||||
|
unit_test_pattern_scan_corner_case_3_Test
|
||||||
|
unit_test_pattern_scan_corner_case_4_Test
|
||||||
|
```
|
||||||
|
|
||||||
|
Use these to validate parsing correctness, wildcard handling, and boundary conditions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
* **Always returns end**: verify the pattern string is valid hex/wildcards and that you’re scanning the intended subrange. Try a simpler pattern (e.g., a single known byte) to sanity-check.
|
||||||
|
* **Crashes or compile errors with iterator overload**: use iterators that support random access (e.g., from `std::vector`), or prefer the `std::span` overload.
|
||||||
|
* **Ambiguous wildcards**: this scanner treats `?` and `??` as **byte-wide** wildcards (not per-nibble). If you need nibble-level masks, extend `parse_pattern` to support patterns like `A?`/`?F` with bitmask matching.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal unit test sketch
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
TEST(pattern, basic) {
|
||||||
|
std::array<std::byte, 8> data{
|
||||||
|
std::byte{0x48}, std::byte{0x8B}, std::byte{0x01}, std::byte{0x02},
|
||||||
|
std::byte{0x89}, std::byte{0x50}, std::byte{0x90}, std::byte{0xCC}
|
||||||
|
};
|
||||||
|
std::span<std::byte> s{data.data(), data.size()};
|
||||||
|
auto it = omath::PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89");
|
||||||
|
ASSERT_NE(it, s.end());
|
||||||
|
EXPECT_EQ(static_cast<size_t>(it - s.begin()), 0U);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 31 Oct 2025*
|
||||||
155
docs/utility/pe_pattern_scan.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# `omath::PePatternScanner` — Scan PE images for byte patterns
|
||||||
|
|
||||||
|
> Header: your project’s `pe_pattern_scanner.hpp`
|
||||||
|
> Namespace: `omath`
|
||||||
|
> Platform: **Windows / PE (Portable Executable) images**
|
||||||
|
> Depends on: `<filesystem>`, `<optional>`, `<cstdint>`
|
||||||
|
> Companion: works well with `omath::PatternScanner` (same pattern grammar)
|
||||||
|
|
||||||
|
`PePatternScanner` searches **Portable Executable (PE)** binaries for a hex pattern (with wildcards). You can scan:
|
||||||
|
|
||||||
|
* a **loaded module** in the current process, or
|
||||||
|
* a **PE file on disk** (by section name; defaults to **`.text`**).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern string grammar (same as `PatternScanner`)
|
||||||
|
|
||||||
|
* **Hex byte**: two hex digits → one byte (`90`, `4F`, `00`, `ff`).
|
||||||
|
* **Wildcard byte**: `?` or `??` matches **any byte**.
|
||||||
|
* **Whitespace**: ignored (use to group tokens).
|
||||||
|
|
||||||
|
✔️ `"48 8B ?? ?? 89"`, `"55 8B EC"`, `"??"`
|
||||||
|
❌ odd digit counts, non-hex characters (besides `?` and whitespace)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace omath {
|
||||||
|
|
||||||
|
struct PeSectionScanResult {
|
||||||
|
std::uint64_t virtual_base_addr; // RVA base of the scanned section (ImageBase + SectionRVA)
|
||||||
|
std::uint64_t raw_base_addr; // file offset (start of section in the file)
|
||||||
|
std::ptrdiff_t target_offset; // offset from section base to the first matched byte
|
||||||
|
};
|
||||||
|
|
||||||
|
class PePatternScanner final {
|
||||||
|
public:
|
||||||
|
// Scan a module already loaded in *this* process.
|
||||||
|
// module_base_address: HMODULE / ImageBase (e.g., from GetModuleHandle)
|
||||||
|
// Returns absolute address (process VA) of the first match, or nullopt.
|
||||||
|
static std::optional<std::uintptr_t>
|
||||||
|
scan_for_pattern_in_loaded_module(const void* module_base_address,
|
||||||
|
const std::string_view& pattern);
|
||||||
|
|
||||||
|
// Scan a PE file on disk, by section name (default ".text").
|
||||||
|
// Returns section bases (virtual + raw) and match offset within the section, or nullopt.
|
||||||
|
static std::optional<PeSectionScanResult>
|
||||||
|
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
|
||||||
|
const std::string_view& pattern,
|
||||||
|
const std::string_view& target_section_name = ".text");
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace omath
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Return values
|
||||||
|
|
||||||
|
* **Loaded module**: `std::optional<std::uintptr_t>`
|
||||||
|
|
||||||
|
* `value()` = **process virtual address** (ImageBase + SectionRVA + match offset).
|
||||||
|
* `nullopt` = no match or parse/PE error.
|
||||||
|
|
||||||
|
* **File scan**: `std::optional<PeSectionScanResult>`
|
||||||
|
|
||||||
|
* `virtual_base_addr` = **ImageBase + SectionRVA** of the scanned section (as if mapped).
|
||||||
|
* `raw_base_addr` = **file offset** of section start.
|
||||||
|
* `target_offset` = offset from the section base to the **first matched byte**.
|
||||||
|
* To get addresses:
|
||||||
|
|
||||||
|
* **Virtual (RVA)** of hit = `virtual_base_addr + target_offset`
|
||||||
|
* **Raw file offset** of hit = `raw_base_addr + target_offset`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage examples
|
||||||
|
|
||||||
|
### Scan a loaded module (current process)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <windows.h>
|
||||||
|
#include "pe_pattern_scanner.hpp"
|
||||||
|
|
||||||
|
using omath::PePatternScanner;
|
||||||
|
|
||||||
|
auto hMod = ::GetModuleHandleW(L"kernel32.dll");
|
||||||
|
if (hMod) {
|
||||||
|
auto addr = PePatternScanner::scan_for_pattern_in_loaded_module(
|
||||||
|
hMod, "48 8B ?? ?? 89" // hex + wildcards
|
||||||
|
);
|
||||||
|
if (addr) {
|
||||||
|
// Use the absolute process VA:
|
||||||
|
std::uintptr_t hit_va = *addr;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scan a PE file on disk (default section “.text”)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "pe_pattern_scanner.hpp"
|
||||||
|
using omath::PePatternScanner;
|
||||||
|
|
||||||
|
auto res = PePatternScanner::scan_for_pattern_in_file(
|
||||||
|
R"(C:\Windows\System32\kernel32.dll)", "55 8B EC"
|
||||||
|
);
|
||||||
|
if (res) {
|
||||||
|
auto rva_hit = res->virtual_base_addr + res->target_offset;
|
||||||
|
auto raw_hit = res->raw_base_addr + res->target_offset;
|
||||||
|
// rva_hit: where it would be in memory; raw_hit: file offset
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scan another section (e.g., “.rdata”)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto res = PePatternScanner::scan_for_pattern_in_file(
|
||||||
|
"foo.dll", "48 8D 0D ?? ?? ?? ??", ".rdata"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & edge cases
|
||||||
|
|
||||||
|
* **PE only**: these functions assume a valid **PE/COFF** layout. Non-PE files or corrupted headers yield `nullopt`.
|
||||||
|
* **Section name**: `scan_for_pattern_in_file` defaults to **`.text`**; pass a different name to target other sections.
|
||||||
|
* **Alignment & RVAs**: `virtual_base_addr` is computed from section headers (RVA aligned per section alignment). The returned “virtual” base is suitable for RVA math; the **process VA** returned by the “loaded module” API already includes the image base.
|
||||||
|
* **Architecture**: works for 32-bit and 64-bit PEs; `std::uintptr_t` size matches the build architecture.
|
||||||
|
* **Performance**: Pattern matching is **O(N × M)** (sliding window with wildcards). For large images, prefer scanning only necessary sections.
|
||||||
|
* **Wildcards**: Each `?` matches **one byte** (no nibble masks). If you need `A?`-style nibble wildcards, extend the parser (see `PatternScanner`).
|
||||||
|
* **Safety**: For loaded modules, you must have access to the memory; scanning read-only sections is fine, but never write. For file scans, ensure the file path is accessible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
* **`nullopt`**: Verify the pattern (valid tokens), correct **section**, and that you’re scanning the intended module/file (check bitness and version).
|
||||||
|
* **“Loaded module” address math**: If you need an **offset from the module base**, compute `offset = hit_va - reinterpret_cast<std::uintptr_t>(module_base_address)`.
|
||||||
|
* **Multiple matches**: Only the **first** match is returned. If you need all matches, extend the implementation to continue scanning from `target_offset + 1`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* `omath::PatternScanner` — raw buffer/iterator scanning with the same pattern grammar.
|
||||||
|
* `omath::Triangle`, `omath::Vector3` — math types used elsewhere in the library.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 31 Oct 2025*
|
||||||
@@ -23,15 +23,27 @@ namespace omath::rev_eng
|
|||||||
return *reinterpret_cast<Type*>(reinterpret_cast<std::uintptr_t>(this) + offset);
|
return *reinterpret_cast<Type*>(reinterpret_cast<std::uintptr_t>(this) + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::size_t id, class ReturnType>
|
template<std::size_t id, class ReturnType, class... Args>
|
||||||
ReturnType call_virtual_method(auto... arg_list)
|
ReturnType call_virtual_method(Args&&... arg_list)
|
||||||
{
|
{
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
|
||||||
#else
|
#else
|
||||||
using VirtualMethodType = ReturnType(*)(void*, decltype(arg_list)...);
|
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
|
||||||
#endif
|
#endif
|
||||||
return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, arg_list...);
|
return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, std::forward<Args>(arg_list)...);
|
||||||
|
}
|
||||||
|
template<std::size_t id, class ReturnType, class... Args>
|
||||||
|
ReturnType call_virtual_method(Args&&... arg_list) const
|
||||||
|
{
|
||||||
|
using This = std::remove_cv_t<std::remove_pointer_t<decltype(this)>>;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
using VirtualMethodType = ReturnType(__thiscall*)(const void*, decltype(arg_list)...);
|
||||||
|
#else
|
||||||
|
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
|
||||||
|
#endif
|
||||||
|
return (*reinterpret_cast<VirtualMethodType**>(const_cast<This*>(this)))[id](
|
||||||
|
const_cast<void*>(static_cast<const void*>(this)), std::forward<Args>(arg_list)...);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace omath::rev_eng
|
} // namespace omath::rev_eng
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
site_name: My Docs
|
site_name: OMATH Docs
|
||||||
|
theme:
|
||||||
|
name: darkly
|
||||||
|
extra_css:
|
||||||
|
- styles/center.css
|
||||||
|
- styles/custom-header.css
|
||||||
@@ -34,6 +34,10 @@ public:
|
|||||||
{
|
{
|
||||||
return call_virtual_method<1, int>();
|
return call_virtual_method<1, int>();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] int rev_bar_const() const
|
||||||
|
{
|
||||||
|
return call_virtual_method<1, int>();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -47,4 +51,5 @@ TEST(unit_test_reverse_enineering, read_test)
|
|||||||
|
|
||||||
EXPECT_EQ(player_original.bar(), player_reversed->rev_bar());
|
EXPECT_EQ(player_original.bar(), player_reversed->rev_bar());
|
||||||
EXPECT_EQ(player_original.foo(), player_reversed->rev_foo());
|
EXPECT_EQ(player_original.foo(), player_reversed->rev_foo());
|
||||||
|
EXPECT_EQ(player_original.bar(), player_reversed->rev_bar_const());
|
||||||
}
|
}
|
||||||