diff --git a/README.md b/README.md index e8591b2d..0bf77770 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-![banner](.github/images/logos/omath_logo_macro.png) +![banner](docs/images/logos/omath_logo_macro.png) ![Static Badge](https://img.shields.io/badge/license-libomath-orange) ![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath) @@ -43,25 +43,52 @@ It provides the latest features, is highly customizable, has all for cheat devel
-# 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. +## 🚀 Quick Example + +```cpp +#include + +using namespace omath; + +// 3D vector operations +Vector3 a{1, 2, 3}; +Vector3 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 +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.
# Gallery
-[![Youtube Video](.github/images/yt_previews/img.png)](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ) +[![Youtube Video](docs/images/yt_previews/img.png)](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
@@ -84,17 +111,35 @@ It provides the latest features, is highly customizable, has all for cheat devel
+## 📚 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 - [All contributors](https://github.com/orange-cpp/omath/graphs/contributors) -[APEX Preview]: .github/images/showcase/apex.png -[BO2 Preview]: .github/images/showcase/cod_bo2.png -[CS2 Preview]: .github/images/showcase/cs2.jpeg -[TF2 Preview]: .github/images/showcase/tf2.jpg +[APEX Preview]: docs/images/showcase/apex.png +[BO2 Preview]: docs/images/showcase/cod_bo2.png +[CS2 Preview]: docs/images/showcase/cs2.jpeg +[TF2 Preview]: docs/images/showcase/tf2.jpg +[QUICKSTART]: docs/getting_started.md [INSTALL]: INSTALL.md [DOCUMENTATION]: http://libomath.org +[TUTORIALS]: docs/tutorials.md [CONTRIBUTING]: CONTRIBUTING.md [EXAMPLES]: examples [SPONSOR]: https://boosty.to/orangecpp/purchase/3568644?ssource=DIRECT&share=subscription_link diff --git a/docs/3d_primitives/box.md b/docs/3d_primitives/box.md new file mode 100644 index 00000000..84e392d6 --- /dev/null +++ b/docs/3d_primitives/box.md @@ -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` + +```cpp +[[nodiscard]] +std::array>, 12> +create_box(const Vector3& top, + const Vector3& bottom, + const Vector3& dir_forward, + const Vector3& 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>, 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 bottom{0, 0, 0}; +Vector3 top {0, 2, 0}; + +// Orientation around the axis +Vector3 forward{0, 0, 1}; +Vector3 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* diff --git a/docs/3d_primitives/plane.md b/docs/3d_primitives/plane.md new file mode 100644 index 00000000..c4619a3a --- /dev/null +++ b/docs/3d_primitives/plane.md @@ -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` + +```cpp +[[nodiscard]] +std::array>, 2> +create_plane(const Vector3& vertex_a, + const Vector3& vertex_b, + const Vector3& 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>, 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 a{ -1, 0, -1 }; // edge start +Vector3 b{ 1, 0, -1 }; // edge end +Vector3 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. diff --git a/docs/api_overview.md b/docs/api_overview.md new file mode 100644 index 00000000..6dc0ad3b --- /dev/null +++ b/docs/api_overview.md @@ -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` | 2D vector | `length()`, `normalized()`, `dot()`, `distance_to()` | +| `Vector3` | 3D vector | `length()`, `normalized()`, `dot()`, `cross()`, `angle_between()` | +| `Vector4` | 4D vector | Extends Vector3 with `w` component | + +**Common aliases:** +```cpp +using Vec2f = Vector2; +using Vec3f = Vector3; +using Vec4f = Vector4; +``` + +**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` | 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 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)` - 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> +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 origin; + float speed; + Vector3 gravity; + // ... additional properties +}; +``` + +**`Target`** - Defines target state: +```cpp +struct Target { + Vector3 position; + Vector3 velocity; + // ... additional properties +}; +``` + +--- + +## Pathfinding + +### A* Algorithm + +```cpp +namespace omath::pathfinding { + template + 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//` 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 position = process.read>(address); +process.write(address, new_position); +``` + +**Internal memory access:** +```cpp +InternalRevObject memory; +auto value = memory.read(address); +memory.write(address, new_value); +``` + +--- + +## Concepts and Constraints + +OMath uses C++20 concepts for type safety: + +```cpp +template +concept Arithmetic = std::is_arithmetic_v; + +template +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, 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> +world_to_screen(const Vector3& 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 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> positions; + +// Good: structure of arrays for SIMD +struct Particles { + std::vector 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 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; +using Mat4 = omath::Mat4X4; +``` + +### 2. Prefer constexpr When Possible + +```cpp +constexpr auto compute_at_compile_time() { + Vector3 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* diff --git a/docs/best_practices.md b/docs/best_practices.md new file mode 100644 index 00000000..d38584ff --- /dev/null +++ b/docs/best_practices.md @@ -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; +using Vec2f = omath::Vector2; +using Mat4 = omath::Mat4X4; + +Vec3f position{1.0f, 2.0f, 3.0f}; +``` + +```cpp +// Avoid: Verbose and repetitive +omath::Vector3 position{1.0f, 2.0f, 3.0f}; +omath::Vector3 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 +#include + +// Okay for development +#include + +// 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 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> 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> 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 position; +Vector3 high_precision_pos; + +// Okay: Use default float +Vector3<> position; // Defaults to float + +// Avoid: Mixing types unintentionally +Vector3 a; +Vector3 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 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 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 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>& positions) { + for (const auto& pos : positions) { + // Process position + } +} + +// Bad: Copies entire vector +void draw_entities(std::vector> positions) { + // Expensive copy! +} + +// Good: Move when transferring ownership +std::vector> 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> project(const Vector3& 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& 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* diff --git a/docs/collision/line_tracer.md b/docs/collision/line_tracer.md new file mode 100644 index 00000000..9e9e1f8a --- /dev/null +++ b/docs/collision/line_tracer.md @@ -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`, `omath::Triangle>` +> 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 start; // ray origin + omath::Vector3 end; // end point (for finite segment) or a point along the direction + bool infinite_length = false; + + [[nodiscard]] omath::Vector3 direction_vector() const noexcept; + [[nodiscard]] omath::Vector3 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>& triangle + ) noexcept; + + // Möller–Trumbore intersection + [[nodiscard]] + static omath::Vector3 get_ray_hit_point( + const Ray& ray, + const omath::Triangle>& 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> tri( + Vector3{0, 0, 0}, + Vector3{1, 0, 0}, + Vector3{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 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{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 start, end; + bool infinite_length = false; + + [[nodiscard]] Vector3 direction_vector() const noexcept; + [[nodiscard]] Vector3 direction_vector_normalized() const noexcept; +}; + +class LineTracer { +public: + LineTracer() = delete; + + [[nodiscard]] static bool can_trace_line( + const Ray&, + const omath::Triangle>& + ) noexcept; + + [[nodiscard]] static Vector3 get_ray_hit_point( + const Ray&, + const omath::Triangle>& + ) 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>` (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* diff --git a/docs/engines/frostbite/camera_trait.md b/docs/engines/frostbite/camera_trait.md new file mode 100644 index 00000000..bb03cd02 --- /dev/null +++ b/docs/engines/frostbite/camera_trait.md @@ -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& cam_origin, + const Vector3& look_at) noexcept; + + // Build view matrix for given angles and origin. + static Mat4X4 + calc_view_matrix(const ViewAngles& angles, + const Vector3& 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`, `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; + +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). diff --git a/docs/engines/frostbite/constants.md b/docs/engines/frostbite/constants.md new file mode 100644 index 00000000..61d1fbe4 --- /dev/null +++ b/docs/engines/frostbite/constants.md @@ -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` (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 +#include + +namespace omath::frostbite_engine +{ + // Inline to avoid ODR across translation units + inline constexpr Vector3 k_abs_up = {0.0f, 1.0f, 0.0f}; + inline constexpr Vector3 k_abs_right = {1.0f, 0.0f, 0.0f}; + inline constexpr Vector3 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; + using YawAngle = Angle; + using RollAngle = Angle; + + using ViewAngles = omath::ViewAngles; +} // 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. diff --git a/docs/engines/frostbite/formulas.md b/docs/engines/frostbite/formulas.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/engines/frostbite/pred_engine_trait.md b/docs/engines/frostbite/pred_engine_trait.md new file mode 100644 index 00000000..a5b77518 --- /dev/null +++ b/docs/engines/frostbite/pred_engine_trait.md @@ -0,0 +1,105 @@ +// Created by Vlad on 8/6/2025. +#pragma once + +#include // sqrt, hypot, tan, asin, atan2 +#include + +#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 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 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 predict_target_position( + const projectile_prediction::Target& target, + float time, float gravity) noexcept + { + Vector3 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& 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& 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 calc_viewpoint_from_angles( + const projectile_prediction::Projectile& projectile, + const Vector3& predicted_target_position, + const std::optional 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& origin, + const Vector3& 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& origin, + const Vector3& 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 diff --git a/docs/engines/iw_engine/camera_trait.md b/docs/engines/iw_engine/camera_trait.md new file mode 100644 index 00000000..d693abd4 --- /dev/null +++ b/docs/engines/iw_engine/camera_trait.md @@ -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& cam_origin, + const Vector3& look_at) noexcept; + + // Build view matrix for given angles and origin. + static Mat4X4 + calc_view_matrix(const ViewAngles& angles, + const Vector3& 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`, `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; + +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). diff --git a/docs/engines/iw_engine/constants.md b/docs/engines/iw_engine/constants.md new file mode 100644 index 00000000..cb9d9d98 --- /dev/null +++ b/docs/engines/iw_engine/constants.md @@ -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 k_abs_up = {0, 0, 1}; + constexpr Vector3 k_abs_right = {0, -1, 0}; + constexpr Vector3 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; +using YawAngle = Angle; +using RollAngle = Angle; + +using ViewAngles = omath::ViewAngles; +``` + +* **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 diff --git a/docs/engines/iw_engine/formulas.md b/docs/engines/iw_engine/formulas.md new file mode 100644 index 00000000..283b4baa --- /dev/null +++ b/docs/engines/iw_engine/formulas.md @@ -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 forward_vector(const ViewAngles& angles) noexcept; + + // Compute right direction from pitch/yaw/roll + [[nodiscard]] + Vector3 right_vector(const ViewAngles& angles) noexcept; + + // Compute up direction from pitch/yaw/roll + [[nodiscard]] + Vector3 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& 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 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 diff --git a/docs/engines/iw_engine/pred_engine_trait.md b/docs/engines/iw_engine/pred_engine_trait.md new file mode 100644 index 00000000..85af5c13 --- /dev/null +++ b/docs/engines/iw_engine/pred_engine_trait.md @@ -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 + 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 + 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& delta) noexcept; + + // Get vertical coordinate (Z in IW Engine) + static constexpr float + get_vector_height_coordinate(const Vector3& vec) noexcept; + + // Compute aim point from angles + static Vector3 + calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, + Vector3 predicted_target_position, + std::optional projectile_pitch) noexcept; + + // Compute pitch angle to look at target + static float + calc_direct_pitch_angle(const Vector3& origin, + const Vector3& view_to) noexcept; + + // Compute yaw angle to look at target + static float + calc_direct_yaw_angle(const Vector3& origin, + const Vector3& 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` diff --git a/docs/engines/opengl_engine/camera_trait.md b/docs/engines/opengl_engine/camera_trait.md new file mode 100644 index 00000000..a7e288a8 --- /dev/null +++ b/docs/engines/opengl_engine/camera_trait.md @@ -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& cam_origin, + const Vector3& look_at) noexcept; + + // Build view matrix for given angles and origin (column-major). + static Mat4X4 + calc_view_matrix(const ViewAngles& angles, + const Vector3& 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`, `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; + +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). diff --git a/docs/engines/opengl_engine/constants.md b/docs/engines/opengl_engine/constants.md new file mode 100644 index 00000000..8bcf9ce8 --- /dev/null +++ b/docs/engines/opengl_engine/constants.md @@ -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 k_abs_up = {0, 1, 0}; + constexpr Vector3 k_abs_right = {1, 0, 0}; + constexpr Vector3 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; +using YawAngle = Angle; +using RollAngle = Angle; + +using ViewAngles = omath::ViewAngles; +``` + +* **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 diff --git a/docs/engines/opengl_engine/formulas.md b/docs/engines/opengl_engine/formulas.md new file mode 100644 index 00000000..c2515d55 --- /dev/null +++ b/docs/engines/opengl_engine/formulas.md @@ -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 forward_vector(const ViewAngles& angles) noexcept; + + // Compute right direction from pitch/yaw/roll + [[nodiscard]] + Vector3 right_vector(const ViewAngles& angles) noexcept; + + // Compute up direction from pitch/yaw/roll + [[nodiscard]] + Vector3 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& 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 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 diff --git a/docs/engines/opengl_engine/pred_engine_trait.md b/docs/engines/opengl_engine/pred_engine_trait.md new file mode 100644 index 00000000..84cccc38 --- /dev/null +++ b/docs/engines/opengl_engine/pred_engine_trait.md @@ -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 + 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 + 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& delta) noexcept; + + // Get vertical coordinate (Y in OpenGL) + static constexpr float + get_vector_height_coordinate(const Vector3& vec) noexcept; + + // Compute aim point from angles + static Vector3 + calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, + Vector3 predicted_target_position, + std::optional projectile_pitch) noexcept; + + // Compute pitch angle to look at target + static float + calc_direct_pitch_angle(const Vector3& origin, + const Vector3& view_to) noexcept; + + // Compute yaw angle to look at target + static float + calc_direct_yaw_angle(const Vector3& origin, + const Vector3& 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` diff --git a/docs/engines/source_engine/camera_trait.md b/docs/engines/source_engine/camera_trait.md new file mode 100644 index 00000000..610fc610 --- /dev/null +++ b/docs/engines/source_engine/camera_trait.md @@ -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& cam_origin, + const Vector3& look_at) noexcept; + + // Build view matrix for given angles and origin. + static Mat4X4 + calc_view_matrix(const ViewAngles& angles, + const Vector3& 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`, `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; + +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 diff --git a/docs/engines/source_engine/constants.md b/docs/engines/source_engine/constants.md new file mode 100644 index 00000000..829a9341 --- /dev/null +++ b/docs/engines/source_engine/constants.md @@ -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 k_abs_up = {0, 0, 1}; + constexpr Vector3 k_abs_right = {0, -1, 0}; + constexpr Vector3 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; +using YawAngle = Angle; +using RollAngle = Angle; + +using ViewAngles = omath::ViewAngles; +``` + +* **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 diff --git a/docs/engines/source_engine/formulas.md b/docs/engines/source_engine/formulas.md new file mode 100644 index 00000000..eba0c907 --- /dev/null +++ b/docs/engines/source_engine/formulas.md @@ -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 forward_vector(const ViewAngles& angles) noexcept; + + // Compute right direction from pitch/yaw/roll + [[nodiscard]] + Vector3 right_vector(const ViewAngles& angles) noexcept; + + // Compute up direction from pitch/yaw/roll + [[nodiscard]] + Vector3 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& 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 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 diff --git a/docs/engines/source_engine/pred_engine_trait.md b/docs/engines/source_engine/pred_engine_trait.md new file mode 100644 index 00000000..63f50241 --- /dev/null +++ b/docs/engines/source_engine/pred_engine_trait.md @@ -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 + 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 + 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& delta) noexcept; + + // Get vertical coordinate (Z in Source Engine) + static constexpr float + get_vector_height_coordinate(const Vector3& vec) noexcept; + + // Compute aim point from angles + static Vector3 + calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, + Vector3 predicted_target_position, + std::optional projectile_pitch) noexcept; + + // Compute pitch angle to look at target + static float + calc_direct_pitch_angle(const Vector3& origin, + const Vector3& view_to) noexcept; + + // Compute yaw angle to look at target + static float + calc_direct_yaw_angle(const Vector3& origin, + const Vector3& 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` diff --git a/docs/engines/unity_engine/camera_trait.md b/docs/engines/unity_engine/camera_trait.md new file mode 100644 index 00000000..1611eef1 --- /dev/null +++ b/docs/engines/unity_engine/camera_trait.md @@ -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& cam_origin, + const Vector3& look_at) noexcept; + + // Build view matrix for given angles and origin. + static Mat4X4 + calc_view_matrix(const ViewAngles& angles, + const Vector3& 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`, `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; + +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). diff --git a/docs/engines/unity_engine/constants.md b/docs/engines/unity_engine/constants.md new file mode 100644 index 00000000..fc25d6a0 --- /dev/null +++ b/docs/engines/unity_engine/constants.md @@ -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 k_abs_up = {0, 1, 0}; + constexpr Vector3 k_abs_right = {1, 0, 0}; + constexpr Vector3 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; +using YawAngle = Angle; +using RollAngle = Angle; + +using ViewAngles = omath::ViewAngles; +``` + +* **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 diff --git a/docs/engines/unity_engine/formulas.md b/docs/engines/unity_engine/formulas.md new file mode 100644 index 00000000..7ed0b90a --- /dev/null +++ b/docs/engines/unity_engine/formulas.md @@ -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 forward_vector(const ViewAngles& angles) noexcept; + + // Compute right direction from pitch/yaw/roll + [[nodiscard]] + Vector3 right_vector(const ViewAngles& angles) noexcept; + + // Compute up direction from pitch/yaw/roll + [[nodiscard]] + Vector3 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& 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 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 diff --git a/docs/engines/unity_engine/pred_engine_trait.md b/docs/engines/unity_engine/pred_engine_trait.md new file mode 100644 index 00000000..13992def --- /dev/null +++ b/docs/engines/unity_engine/pred_engine_trait.md @@ -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 + 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 + 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& delta) noexcept; + + // Get vertical coordinate (Y in Unity Engine) + static constexpr float + get_vector_height_coordinate(const Vector3& vec) noexcept; + + // Compute aim point from angles + static Vector3 + calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, + Vector3 predicted_target_position, + std::optional projectile_pitch) noexcept; + + // Compute pitch angle to look at target + static float + calc_direct_pitch_angle(const Vector3& origin, + const Vector3& view_to) noexcept; + + // Compute yaw angle to look at target + static float + calc_direct_yaw_angle(const Vector3& origin, + const Vector3& 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` diff --git a/docs/engines/unreal_engine/camera_trait.md b/docs/engines/unreal_engine/camera_trait.md new file mode 100644 index 00000000..7a02fc69 --- /dev/null +++ b/docs/engines/unreal_engine/camera_trait.md @@ -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& cam_origin, + const Vector3& look_at) noexcept; + + // Build view matrix for given angles and origin. + static Mat4X4 + calc_view_matrix(const ViewAngles& angles, + const Vector3& 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`, `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; + +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). diff --git a/docs/engines/unreal_engine/constants.md b/docs/engines/unreal_engine/constants.md new file mode 100644 index 00000000..5b66bd7e --- /dev/null +++ b/docs/engines/unreal_engine/constants.md @@ -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 k_abs_up = {0, 0, 1}; + constexpr Vector3 k_abs_right = {0, 1, 0}; + constexpr Vector3 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; +using YawAngle = Angle; +using RollAngle = Angle; + +using ViewAngles = omath::ViewAngles; +``` + +* **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 diff --git a/docs/engines/unreal_engine/formulas.md b/docs/engines/unreal_engine/formulas.md new file mode 100644 index 00000000..3f4ef3cb --- /dev/null +++ b/docs/engines/unreal_engine/formulas.md @@ -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 forward_vector(const ViewAngles& angles) noexcept; + + // Compute right direction from pitch/yaw/roll + [[nodiscard]] + Vector3 right_vector(const ViewAngles& angles) noexcept; + + // Compute up direction from pitch/yaw/roll + [[nodiscard]] + Vector3 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& 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 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 diff --git a/docs/engines/unreal_engine/pred_engine_trait.md b/docs/engines/unreal_engine/pred_engine_trait.md new file mode 100644 index 00000000..dd337db2 --- /dev/null +++ b/docs/engines/unreal_engine/pred_engine_trait.md @@ -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 + 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 + 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& delta) noexcept; + + // Get vertical coordinate (implementation returns Y, but UE is Z-up) + static constexpr float + get_vector_height_coordinate(const Vector3& vec) noexcept; + + // Compute aim point from angles + static Vector3 + calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, + Vector3 predicted_target_position, + std::optional projectile_pitch) noexcept; + + // Compute pitch angle to look at target + static float + calc_direct_pitch_angle(const Vector3& origin, + const Vector3& view_to) noexcept; + + // Compute yaw angle to look at target + static float + calc_direct_yaw_angle(const Vector3& origin, + const Vector3& 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` diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000..60f4ef93 --- /dev/null +++ b/docs/faq.md @@ -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 +``` + +**Specific components:** +```cpp +#include +#include +``` + +### 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 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& origin, + const Vector3& target + ); + + static Mat4X4 calc_view_matrix( + const ViewAngles& angles, + const Vector3& origin + ); + + static Mat4X4 calc_projection_matrix( + const FieldOfView& fov, + const ViewPort& viewport, + float near, float far + ); +}; + +// Use with Camera +using MyCamera = Camera; +``` + +### 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 +#include + +Vector3 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* diff --git a/docs/getting_started.md b/docs/getting_started.md new file mode 100644 index 00000000..2d05f738 --- /dev/null +++ b/docs/getting_started.md @@ -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 +#include + +int main() { + using namespace omath; + + // Create 3D vectors + Vector3 a{1.0f, 2.0f, 3.0f}; + Vector3 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 vec2{1.0f, 2.0f}; +Vector3 vec3{1.0f, 2.0f, 3.0f}; +Vector4 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::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{0, 0, 100}, // Position + ViewAngles{}, // Angles + viewport, + fov, + 0.1f, // near plane + 1000.0f // far plane +); + +// Project 3D point to 2D screen +Vector3 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 +using SourceCamera = omath::source_engine::Camera; + +// Unity Engine +#include +using UnityCamera = omath::unity_engine::Camera; + +// Unreal Engine +#include +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 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{0, 0, 0}, // shooter position + 1000.0f, // muzzle velocity (m/s) + Vector3{0, 0, -9.81f} // gravity +}; + +Target enemy{ + Vector3{100, 200, 50}, // position + Vector3{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{0, 0, 0}, // point on plane + Vector3{0, 0, 1} // normal (pointing up) +}; + +Vector3 ray_origin{0, 0, 100}; +Vector3 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 + +using namespace omath; + +std::vector 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* diff --git a/.github/images/banner.png b/docs/images/banner.png similarity index 100% rename from .github/images/banner.png rename to docs/images/banner.png diff --git a/.github/images/logos/omath_logo_macro.png b/docs/images/logos/omath_logo_macro.png similarity index 100% rename from .github/images/logos/omath_logo_macro.png rename to docs/images/logos/omath_logo_macro.png diff --git a/.github/images/logos/omath_logo_mega.png b/docs/images/logos/omath_logo_mega.png similarity index 100% rename from .github/images/logos/omath_logo_mega.png rename to docs/images/logos/omath_logo_mega.png diff --git a/.github/images/logos/omath_logo_micro.png b/docs/images/logos/omath_logo_micro.png similarity index 100% rename from .github/images/logos/omath_logo_micro.png rename to docs/images/logos/omath_logo_micro.png diff --git a/.github/images/logos/omath_logo_nano.png b/docs/images/logos/omath_logo_nano.png similarity index 100% rename from .github/images/logos/omath_logo_nano.png rename to docs/images/logos/omath_logo_nano.png diff --git a/.github/images/showcase/apex.png b/docs/images/showcase/apex.png similarity index 100% rename from .github/images/showcase/apex.png rename to docs/images/showcase/apex.png diff --git a/.github/images/showcase/cod_bo2.png b/docs/images/showcase/cod_bo2.png similarity index 100% rename from .github/images/showcase/cod_bo2.png rename to docs/images/showcase/cod_bo2.png diff --git a/.github/images/showcase/cs2.jpeg b/docs/images/showcase/cs2.jpeg similarity index 100% rename from .github/images/showcase/cs2.jpeg rename to docs/images/showcase/cs2.jpeg diff --git a/.github/images/showcase/tf2.jpg b/docs/images/showcase/tf2.jpg similarity index 100% rename from .github/images/showcase/tf2.jpg rename to docs/images/showcase/tf2.jpg diff --git a/.github/images/yt_previews/img.png b/docs/images/yt_previews/img.png similarity index 100% rename from .github/images/yt_previews/img.png rename to docs/images/yt_previews/img.png diff --git a/docs/index.md b/docs/index.md index 000ea345..36921807 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,239 @@ -# Welcome to MkDocs +
+ +

+ omath banner +

-For full documentation visit [mkdocs.org](https://www.mkdocs.org). + +

+ license: libomath + GitHub contributors + Top language + + CodeFactor + + GitHub Actions Workflow Status + + Vcpkg package + + GitHub forks + + Join us on Discord + + + Telegram + +

+
-## 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. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs -h` - Print help message and exit. +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... -## Project layout +--- - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +## 🚀 Quick Start + +**New to OMath?** Start here: + +- **[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 + +using namespace omath; + +Vector3 a{1, 2, 3}; +Vector3 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 + +
+ +[![Youtube Video](images/yt_previews/img.png)](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ) + +
+ +![APEX Preview] + +
+ +![BO2 Preview] + +
+ +![CS2 Preview] + +
+ +![TF2 Preview] + +
+
+ +--- + +## 🤝 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* + + +[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 \ No newline at end of file diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 00000000..4a3cd311 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,68 @@ +# Installation + +## 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). + +## 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") +``` + +## 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 **\-\** 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 | diff --git a/docs/linear_algebra/mat.md b/docs/linear_algebra/mat.md new file mode 100644 index 00000000..8d1efd68 --- /dev/null +++ b/docs/linear_algebra/mat.md @@ -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` 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; 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> 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 + Mat + operator*(const Mat&) 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 transposed() const noexcept; + ``` + +* **Determinant (square only)** + + ```cpp + Type determinant() const; // 1x1, 2x2 fast path; larger uses Laplace expansion + ``` + +* **Inverse (square only)** + + ```cpp + std::optional inverted() const; // nullopt if det == 0 + ``` + +* **Minors & cofactors (square only)** + + ```cpp + Mat 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& + 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 +Mat<1,4,T,St> mat_row_from_vector(const Vector3& v); + +template +Mat<4,1,T,St> mat_column_from_vector(const Vector3& v); +```` + +### Translation + +```cpp +template +Mat<4,4,T,St> mat_translation(const Vector3& d) noexcept; +``` + +### Axis rotations + +```cpp +// Angle type must provide angle.cos() and angle.sin() +template +Mat<4,4,T,St> mat_rotation_axis_x(const Angle& a) noexcept; + +template +Mat<4,4,T,St> mat_rotation_axis_y(const Angle& a) noexcept; + +template +Mat<4,4,T,St> mat_rotation_axis_z(const Angle& a) noexcept; +``` + +### Camera/view + +```cpp +template +Mat<4,4,T,St> mat_camera_view(const Vector3& forward, + const Vector3& right, + const Vector3& up, + const Vector3& camera_origin) noexcept; +``` + +### Perspective projections + +```cpp +template +Mat<4,4,T,St> mat_perspective_left_handed (float fov_deg, float aspect, float near, float far) noexcept; + +template +Mat<4,4,T,St> mat_perspective_right_handed(float fov_deg, float aspect, float near, float far) noexcept; +``` + +### Orthographic projections + +```cpp +template +Mat<4,4,T,St> mat_ortho_left_handed (T left, T right, T bottom, T top, T near, T far) noexcept; + +template +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 +Mat<4,4,T,St> mat_look_at_left_handed (const Vector3& eye, + const Vector3& center, + const Vector3& up); + +template +Mat<4,4,T,St> mat_look_at_right_handed(const Vector3& eye, + const Vector3& center, + const Vector3& up); +``` + +--- + +## Screen-space helper + +```cpp +template +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; +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(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(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{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 +concept MatTemplateEqual = + (M1::rows == M2::rows) && + (M1::columns == M2::columns) && + std::is_same_v && + (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 (`` 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` +* 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>); +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 Mat operator*(const Mat&) const; +Mat& operator*=(const Type&); Mat operator*(const Type&) const; +Mat& operator/=(const Type&); Mat operator/(const Type&) const; + +Mat transposed() const noexcept; +Type determinant() const; // square only +std::optional inverted() const; // square only + +Mat 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* diff --git a/docs/linear_algebra/triangle.md b/docs/linear_algebra/triangle.md new file mode 100644 index 00000000..fc80f886 --- /dev/null +++ b/docs/linear_algebra/triangle.md @@ -0,0 +1,173 @@ +# `omath::Triangle` — Simple 3D triangle utility + +> Header: your project’s `triangle.hpp` +> Namespace: `omath` +> Depends on: `omath::Vector3` (from `vector3.hpp`) + +A tiny helper around three `Vector3` 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 Triangle`, but the stored vertices are concretely `Vector3`. In practice this type is currently **fixed to `Vector3`**. 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 tri( // template arg unused; any placeholder ok + Vector3{0,0,0}, // v1 + Vector3{0,0,1}, // v2 (right angle is tested at v2) + Vector3{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 m_vertex1; // v1 +Vector3 m_vertex2; // v2 (the corner tested by is_rectangular) +Vector3 m_vertex3; // v3 +``` + +--- + +## Constructors + +```cpp +constexpr Triangle() = default; +constexpr Triangle(const Vector3& v1, + const Vector3& v2, + const Vector3& v3); +``` + +--- + +## Methods + +```cpp +// Normal (unit) using right-handed cross product: +// n = (v3 - v2) × (v1 - v2), then normalized() +[[nodiscard]] constexpr Vector3 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 side_a_vector() const; // v1 - v2 +[[nodiscard]] constexpr Vector3 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 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 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& v1, + const Vector3& v2, + const Vector3& v3); + + Vector3 m_vertex1, m_vertex2, m_vertex3; + + [[nodiscard]] constexpr Vector3 calculate_normal() const; + [[nodiscard]] float side_a_length() const; + [[nodiscard]] float side_b_length() const; + [[nodiscard]] constexpr Vector3 side_a_vector() const; + [[nodiscard]] constexpr Vector3 side_b_vector() const; + [[nodiscard]] constexpr float hypot() const; + [[nodiscard]] constexpr bool is_rectangular() const; + [[nodiscard]] constexpr Vector3 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* diff --git a/docs/linear_algebra/vector2.md b/docs/linear_algebra/vector2.md new file mode 100644 index 00000000..7f044754 --- /dev/null +++ b/docs/linear_algebra/vector2.md @@ -0,0 +1,300 @@ +# `omath::Vector2` — 2D vector (C++20/23) + +> Header: your project’s `vector2.hpp` +> Namespace: `omath` +> Template: `template requires std::is_arithmetic_v` + +`Vector2` 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; + +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 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`)** + + ```cpp + template<> struct std::hash> { + std::size_t operator()(const omath::Vector2&) const noexcept; + }; + ``` + + Example: + + ```cpp + std::unordered_set> set; + set.insert({1.f, 2.f}); + ``` + +* **`std::formatter`** (for any `Type`) + + ```cpp + // prints "[x, y]" for char / wchar_t / char8_t + template + struct std::formatter>; + ``` + +--- + +## 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 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 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, int> counts; +counts[{1.f, 2.f}] = 42; +``` + +### ImGui interop + +```cpp +#define OMATH_IMGUI_INTEGRATION +#include "vector2.hpp" + +omath::Vector2 v{10, 20}; +ImVec2 iv = v.to_im_vec2(); +v = omath::Vector2::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 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* diff --git a/docs/linear_algebra/vector3.md b/docs/linear_algebra/vector3.md new file mode 100644 index 00000000..15dda160 --- /dev/null +++ b/docs/linear_algebra/vector3.md @@ -0,0 +1,307 @@ +# `omath::Vector3` — 3D vector (C++20/23) + +> Header: your project’s `vector3.hpp` +> Namespace: `omath` +> Template: `template requires std::is_arithmetic_v` +> Depends on: `omath::Vector2` (base class), `omath::Angle` (for `angle_between`) +> C++: uses `std::expected` ⇒ **C++23** recommended (or a backport) + +`Vector3` extends `Vector2` 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; + +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 y{0}; // inherited from Vector2 +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; + +// 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, + 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`)** + + ```cpp + template<> struct std::hash> { + std::size_t operator()(const omath::Vector3&) const noexcept; + }; + ``` + + Example: + + ```cpp + std::unordered_map, int> counts; + counts[{1.f, 2.f, 3.f}] = 7; + ``` + +* **`std::formatter`** (all character types) + + ```cpp + template + struct std::formatter>; // 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 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 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 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; + +// Angles +std::expected, 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` (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* diff --git a/docs/linear_algebra/vector4.md b/docs/linear_algebra/vector4.md new file mode 100644 index 00000000..833fa736 --- /dev/null +++ b/docs/linear_algebra/vector4.md @@ -0,0 +1,253 @@ +# `omath::Vector4` — 4D vector (C++20/23) + +> Header: your project’s `vector4.hpp` +> Namespace: `omath` +> Template: `template requires std::is_arithmetic_v` +> Inherits: `omath::Vector3` (brings `x`, `y`, `z` and most scalar ops) + +`Vector4` extends `Vector3` 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; + +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 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 and (in current code) sets only x,y,z. +// See "Notes & caveats" for a corrected version you may prefer. +static Vector4 from_im_vec4(const ImVec4& other) noexcept; +#endif +``` + +--- + +## Hashing & formatting + +* **Hash specialization** (only for `Vector4`): + + ```cpp + template<> struct std::hash> { + std::size_t operator()(const omath::Vector4&) const noexcept; + }; + ``` + + Example: + + ```cpp + std::unordered_map, int> counts; + counts[{1.f, 2.f, 3.f, 1.f}] = 7; + ``` + +* **`std::formatter`** (for any `Type`, all character kinds): + + ```cpp + template + struct std::formatter>; // -> "[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 `` itself. Ensure it’s included **before** this header whenever `OMATH_IMGUI_INTEGRATION` is defined. + * `from_im_vec4` currently returns `Vector4` and (in the snippet shown) initializes **only x,y,z**. A more consistent version would be: + + ```cpp + #ifdef OMATH_IMGUI_INTEGRATION + static Vector4 from_im_vec4(const ImVec4& v) noexcept { + return {static_cast(v.x), static_cast(v.y), + static_cast(v.z), static_cast(v.w)}; + } + #endif + ``` + +* Many scalar compound operators (`+= Type`, `-= Type`) are inherited from `Vector3`. + +--- + +## 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 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* diff --git a/docs/pathfinding/a_star.md b/docs/pathfinding/a_star.md new file mode 100644 index 00000000..df44820c --- /dev/null +++ b/docs/pathfinding/a_star.md @@ -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`, a `NavigationMesh` +> Output: ordered list of waypoints `std::vector>` + +`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`. + +--- + +## API + +```cpp +namespace omath::pathfinding { + +struct PathNode; // holds per-node search data (see "Expected PathNode fields") + +class Astar final { +public: + [[nodiscard]] + static std::vector> + find_path(const Vector3& start, + const Vector3& end, + const NavigationMesh& nav_mesh) noexcept; + +private: + [[nodiscard]] + static std::vector> + reconstruct_final_path( + const std::unordered_map, PathNode>& closed_list, + const Vector3& 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 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 start{2.5f, 0.0f, -1.0f}; +Vector3 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`**: Your repo defines `std::hash>`. 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 find_path(start, goal, mesh) { + auto [snode, gnode] = mesh.snap_to_nodes(start, goal); + OpenSet open; // min-heap by f + std::unordered_map 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>` 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* diff --git a/docs/pathfinding/navigation_mesh.md b/docs/pathfinding/navigation_mesh.md new file mode 100644 index 00000000..a03f6c59 --- /dev/null +++ b/docs/pathfinding/navigation_mesh.md @@ -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` (3D points) +> Storage: adjacency map `unordered_map, std::vector>>` + +A minimal navigation mesh represented as a **vertex/edge graph**. Each vertex is a `Vector3` 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, std::string> + get_closest_vertex(const Vector3& 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>& + get_neighbors(const Vector3& vertex) const noexcept; + + // True if the graph has no vertices/edges. + [[nodiscard]] + bool empty() const; + + // Serialize/deserialize the graph (opaque binary). + [[nodiscard]] std::vector serialize() const noexcept; + void deserialize(const std::vector& raw) noexcept; + + // Public adjacency (vertex -> neighbors) + std::unordered_map, std::vector>> 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>` (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` as an unordered_map key relies on your `std::hash>` 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. diff --git a/docs/projectile_prediction/proj_pred_engine_avx2.md b/docs/projectile_prediction/proj_pred_engine_avx2.md new file mode 100644 index 00000000..56969f71 --- /dev/null +++ b/docs/projectile_prediction/proj_pred_engine_avx2.md @@ -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`, `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> + 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 + calculate_pitch(const Vector3& proj_origin, + const Vector3& 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`**: 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* diff --git a/docs/projectile_prediction/proj_pred_engine_legacy.md b/docs/projectile_prediction/proj_pred_engine_legacy.md new file mode 100644 index 00000000..fca8b174 --- /dev/null +++ b/docs/projectile_prediction/proj_pred_engine_legacy.md @@ -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 +requires PredEngineConcept +class ProjPredEngineLegacy final : public ProjPredEngineInterface { +public: + ProjPredEngineLegacy(float gravity_constant, + float simulation_time_step, + float maximum_simulation_time, + float distance_tolerance); + + [[nodiscard]] + std::optional> + maybe_calculate_aim_point(const Projectile& projectile, + const Target& target) const override; + +private: + // Closed-form ballistic pitch solver (internal) + std::optional + maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, + const Vector3& target_position) const noexcept; + + bool is_projectile_reached_target(const Vector3& 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 predict_projectile_position(const Projectile&, float pitch_deg, float yaw_deg, + float time, float gravity) noexcept; + +Vector3 predict_target_position(const Target&, float time, float gravity) noexcept; + +float calc_vector_2d_distance(const Vector3& v) noexcept; // typically length in XZ plane +float get_vector_height_coordinate(const Vector3& v) noexcept; // typically Y + +Vector3 calc_viewpoint_from_angles(const Projectile&, Vector3 target, + std::optional maybe_pitch_deg) noexcept; + +float calc_direct_pitch_angle(const Vector3& from, const Vector3& to) noexcept; +float calc_direct_yaw_angle (const Vector3& from, const Vector3& 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* diff --git a/docs/projectile_prediction/projectile.md b/docs/projectile_prediction/projectile.md new file mode 100644 index 00000000..a741a88d --- /dev/null +++ b/docs/projectile_prediction/projectile.md @@ -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 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` — math type used for positions and directions + +--- + +*Last updated: 1 Nov 2025* diff --git a/docs/projectile_prediction/projectile_engine.md b/docs/projectile_prediction/projectile_engine.md new file mode 100644 index 00000000..a47ff637 --- /dev/null +++ b/docs/projectile_prediction/projectile_engine.md @@ -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`, `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> + 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>` + + * `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 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 intercept_time_no_gravity(const Vector3& p, + const Vector3& 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` 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* diff --git a/docs/projectile_prediction/target.md b/docs/projectile_prediction/target.md new file mode 100644 index 00000000..45944635 --- /dev/null +++ b/docs/projectile_prediction/target.md @@ -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 m_origin; // Current world-space position of the target + Vector3 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` 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` 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* diff --git a/docs/projection/camera.md b/docs/projection/camera.md new file mode 100644 index 00000000..42f7ebd1 --- /dev/null +++ b/docs/projection/camera.md @@ -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` +> Requires: `CameraEngineConcept` +> 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;` + +--- + +## Template & trait requirements + +```cpp +template +concept CameraEngineConcept = requires( + const omath::Vector3& cam_origin, + const omath::Vector3& 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; + { T::calc_view_matrix(angles, cam_origin) } noexcept -> std::same_as; + { T::calc_projection_matrix(fov, viewport, znear, zfar)}noexcept -> std::same_as; +}; +``` + +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& eye, + const Vector3& at) noexcept; + static Mat4 calc_view_matrix(const ViewAnglesType& ang, + const Vector3& eye) noexcept; + static Mat4 calc_projection_matrix(const FieldOfView& fov, + const ViewPort& vp, + float znear, float zfar) noexcept; +}; + +using Camera = omath::projection::Camera; + +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({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& position, + const ViewAnglesType& view_angles, + const ViewPort& view_port, + const FieldOfView& fov, + float near, float far) noexcept; + + void look_at(const Vector3& 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&) 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& get_origin() const noexcept; + + // World → Screen (pixels) via NDC; choose screen origin: + template + std::expected, Error> + world_to_screen(const Vector3& world) const noexcept; + + // World → NDC (aka “viewport” in this code) ∈ [-1,1]^3 + std::expected, Error> + world_to_view_port(const Vector3& world) const noexcept; + + // NDC → World (uses inverse VP) + std::expected, Error> + view_port_to_screen(const Vector3& ndc) const noexcept; + + // Screen (pixels) → World + std::expected, Error> + screen_to_world(const Vector3& screen) const noexcept; + + // 2D overload (z defaults to 1, i.e., far plane ray-end in NDC) + std::expected, Error> + screen_to_world(const Vector2& screen) const noexcept; + +protected: + ViewPort m_view_port{}; + FieldOfView m_field_of_view; + mutable std::optional m_view_projection_matrix; + float m_far_plane_distance{}; + float m_near_plane_distance{}; + ViewAnglesType m_view_angles; + Vector3 m_origin; + +private: + static constexpr bool is_ndc_out_of_bounds(const Mat4X4Type& ndc) noexcept; + Vector3 ndc_to_screen_position_from_top_left_corner(const Vector3& ndc) const noexcept; + Vector3 ndc_to_screen_position_from_bottom_left_corner(const Vector3& ndc) const noexcept; + Vector3 screen_to_ndc(const Vector3& 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& eye, + const Vector3& at) noexcept { /* ... */ } + + static Mat4 calc_view_matrix(const MyAngles& ang, + const Vector3& 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( + 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* diff --git a/docs/projection/error_codes.md b/docs/projection/error_codes.md new file mode 100644 index 00000000..435751bb --- /dev/null +++ b/docs/projection/error_codes.md @@ -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. diff --git a/docs/rev_eng/external_rev_object.md b/docs/rev_eng/external_rev_object.md new file mode 100644 index 00000000..3a983fe1 --- /dev/null +++ b/docs/rev_eng/external_rev_object.md @@ -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 ExternalReverseEngineeredObject { +public: + explicit ExternalReverseEngineeredObject(std::uintptr_t addr) + : m_object_address(addr) {} + +protected: + template + [[nodiscard]] Type get_by_offset(std::ptrdiff_t offset) const { + return ExternalMemoryManagementTrait::read_memory(m_object_address + offset); + } + + template + 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 +static T read_memory(std::uintptr_t absolute_address); + +// Writes sizeof(T) bytes to absolute address. +template +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//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 + static T read_memory(std::uintptr_t addr) { + T out{}; + SIZE_T n{}; + if (!ReadProcessMemory(g_handle, reinterpret_cast(addr), &out, sizeof(T), &n) || n != sizeof(T)) + throw std::runtime_error("RPM failed"); + return out; + } + template + static void write_memory(std::uintptr_t addr, const T& val) { + SIZE_T n{}; + if (!WriteProcessMemory(g_handle, reinterpret_cast(addr), &val, sizeof(T), &n) || n != sizeof(T)) + throw std::runtime_error("WPM failed"); + } +}; + +class Player final : public omath::rev_eng::ExternalReverseEngineeredObject { + using Base = omath::rev_eng::ExternalReverseEngineeredObject; +public: + using Base::Base; // inherit ctor (takes base address) + + // Offsets taken from your RE notes (in bytes) + Vector3 position() const { return get_by_offset>(0x30); } + void set_position(const Vector3& p) const { set_by_offset(0x30, p); } + + float health() const { return get_by_offset(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 static T read_memory(std::uintptr_t addr) { + T out{}; + iovec local{ &out, sizeof(out) }, remote{ reinterpret_cast(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 static void write_memory(std::uintptr_t addr, const T& val) { + iovec local{ const_cast(&val), sizeof(val) }, remote{ reinterpret_cast(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* diff --git a/docs/rev_eng/internal_rev_object.md b/docs/rev_eng/internal_rev_object.md new file mode 100644 index 00000000..3fce7be7 --- /dev/null +++ b/docs/rev_eng/internal_rev_object.md @@ -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 + [[nodiscard]] Type& get_by_offset(std::ptrdiff_t offset); + + template + [[nodiscard]] const Type& get_by_offset(std::ptrdiff_t offset) const; + + template + ReturnType call_virtual_method(auto... arg_list); +}; +``` + +* `get_by_offset(off)` — returns a **reference** to `T` located at `reinterpret_cast(this) + off`. +* `call_virtual_method(args...)` — fetches the function pointer from `(*reinterpret_cast(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(kHealth); } + const float& health() const { return get_by_offset(kHealth); } + + Vector3& position() { return get_by_offset>(kPosition); } + const Vector3& position() const { return get_by_offset>(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 +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(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` 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(reinterpret_cast(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* diff --git a/docs/styles/center.css b/docs/styles/center.css new file mode 100644 index 00000000..c1b0e2a3 --- /dev/null +++ b/docs/styles/center.css @@ -0,0 +1,4 @@ +/* docs/css/custom.css */ +.center-text { + text-align: center; +} \ No newline at end of file diff --git a/docs/styles/custom-header.css b/docs/styles/custom-header.css new file mode 100644 index 00000000..f3b69319 --- /dev/null +++ b/docs/styles/custom-header.css @@ -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; +} diff --git a/docs/trigonometry/angle.md b/docs/trigonometry/angle.md new file mode 100644 index 00000000..b4b46701 --- /dev/null +++ b/docs/trigonometry/angle.md @@ -0,0 +1,165 @@ +# `omath::Angle` — templated angle with normalize/clamper + trig + +> Header: `omath/trigonometry/angle.hpp` +> Namespace: `omath` +> Template: `Angle` +> Requires: `std::is_arithmetic_v` +> 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 +requires std::is_arithmetic_v +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::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; +auto fov = Fov::from_degrees(200.f); // -> 179deg (clamped) +``` + +### Signed, normalized range + +```cpp +using SignedDeg = omath::Angle; + +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`; `FovDeg = Angle`). + +--- + +## 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; +REQUIRE(S::from_degrees( 181).as_degrees() == -179.f); +REQUIRE(S::from_degrees(-181).as_degrees() == 179.f); + +using C = omath::Angle; +REQUIRE(C::from_degrees(5).as_degrees() == 10.f); +REQUIRE(C::from_degrees(25).as_degrees() == 20.f); +``` + +--- + +*Last updated: 1 Nov 2025* diff --git a/docs/trigonometry/angles.md b/docs/trigonometry/angles.md new file mode 100644 index 00000000..afef6cbe --- /dev/null +++ b/docs/trigonometry/angles.md @@ -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 +requires std::is_floating_point_v +constexpr Type radians_to_degrees(const Type& radians) noexcept; + +template +requires std::is_floating_point_v +constexpr Type degrees_to_radians(const Type& degrees) noexcept; + +// FOV conversion (inputs/outputs in degrees, aspect = width/height) +template +requires std::is_floating_point_v +Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) noexcept; + +template +requires std::is_floating_point_v +Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) noexcept; + +// Wrap angle into [min, max] (any arithmetic type) +template +requires std::is_arithmetic_v +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) == 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); +``` diff --git a/docs/trigonometry/view_angles.md b/docs/trigonometry/view_angles.md new file mode 100644 index 00000000..5f6b12b9 --- /dev/null +++ b/docs/trigonometry/view_angles.md @@ -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 + 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; + +// Safer, policy-based angles (recommended) +using PitchDeg = omath::Angle; +using YawDeg = omath::Angle; +using RollDeg = omath::Angle; +using ViewAnglesDeg = omath::ViewAngles; +``` + +--- + +## Examples + +### Basic construction + +```cpp +omath::ViewAngles 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; + +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::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. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 00000000..7fc8574d --- /dev/null +++ b/docs/troubleshooting.md @@ -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 behind = camera_pos - Vector3{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 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::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 convert_y_up_to_z_up(const Vector3& pos) { + return Vector3{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{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{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{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{0, 0, -9.81f}; + + // Unity: Y-up + proj.gravity = Vector3{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 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 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 x{1, 0, 0}; + Vector3 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 + $<$:-O3> + $<$:-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* diff --git a/docs/tutorials.md b/docs/tutorials.md new file mode 100644 index 00000000..d911973f --- /dev/null +++ b/docs/tutorials.md @@ -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 +#include + +using namespace omath; +``` + +### Step 2: Create Vectors + +```cpp +// 2D vectors +Vector2 v2a{3.0f, 4.0f}; +Vector2 v2b{1.0f, 2.0f}; + +// 3D vectors +Vector3 v3a{1.0f, 2.0f, 3.0f}; +Vector3 v3b{4.0f, 5.0f, 6.0f}; + +// 4D vectors (often used for homogeneous coordinates) +Vector4 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 + +// 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 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 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 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 +#include + +using namespace omath; +using namespace omath::projectile_prediction; + +// Define your weapon's projectile +Projectile bullet; +bullet.origin = Vector3{0, 0, 0}; // Shooter position +bullet.speed = 800.0f; // Muzzle velocity (m/s or game units/s) +bullet.gravity = Vector3{0, 0, -9.81f}; // Gravity vector +``` + +### Step 2: Define Target State + +```cpp +// Target information (enemy player) +Target enemy; +enemy.position = Vector3{100, 200, 50}; // Current position +enemy.velocity = Vector3{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 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{100, 100, 100}; +stationary.velocity = Vector3{0, 0, 0}; +// aim_point will equal position for stationary targets + +// Fast-moving target +Target fast; +fast.position = Vector3{100, 100, 100}; +fast.velocity = Vector3{50, 0, 0}; // Moving very fast +// May return nullopt if target is too fast + +// Target at different heights +Target aerial; +aerial.position = Vector3{100, 100, 200}; // High up +aerial.velocity = Vector3{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 + +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 + +using namespace omath; + +// Define a ground plane (Z=0, normal pointing up) +Plane ground{ + Vector3{0, 0, 0}, // Point on plane + Vector3{0, 0, 1} // Normal vector (Z-up) +}; + +// Define a ray (e.g., looking downward from above) +Vector3 ray_origin{10, 20, 100}; +Vector3 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 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 below{10, 20, -5}; +float dist_below = ground.distance_to_point(below); +// Expected: -5.0 +``` + +### Step 3: Axis-Aligned Bounding Box + +```cpp +#include + +// Create a bounding box +Box bbox{ + Vector3{0, 0, 0}, // Min corner + Vector3{100, 100, 100} // Max corner +}; + +// Test if point is inside +Vector3 inside{50, 50, 50}; +if (bbox.contains(inside)) { + std::cout << "Point is inside box\n"; +} + +Vector3 outside{150, 50, 50}; +if (!bbox.contains(outside)) { + std::cout << "Point is outside box\n"; +} + +// Box-box intersection +Box other{ + Vector3{50, 50, 50}, + Vector3{150, 150, 150} +}; + +if (bbox.intersects(other)) { + std::cout << "Boxes overlap\n"; +} +``` + +### Step 4: Line Tracing + +```cpp +#include + +using namespace omath::collision; + +// Ray-triangle intersection +Vector3 v0{0, 0, 0}; +Vector3 v1{100, 0, 0}; +Vector3 v2{0, 100, 0}; + +Vector3 ray_start{25, 25, 100}; +Vector3 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 +#include + +using namespace omath; + +// Memory to search (e.g., from a loaded module) +std::vector 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 + +// 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 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 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& 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 + +using namespace omath; + +// Generic angle with custom range +auto angle1 = Angle::from_degrees(45.0f); +auto angle2 = Angle::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 camera_pos{0, 0, 100}; +Vector3 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* diff --git a/docs/utility/color.md b/docs/utility/color.md new file mode 100644 index 00000000..e4b864b0 --- /dev/null +++ b/docs/utility/color.md @@ -0,0 +1,190 @@ +# `omath::Color` — RGBA color with HSV helpers (C++20/23) + +> Header: your project’s `color.hpp` +> Namespace: `omath` +> Inherits: `Vector4` (`x=r`, `y=g`, `z=b`, `w=a`) +> Depends on: ``, `Vector4`, optionally ImGui (`OMATH_IMGUI_INTEGRATION`) +> Formatting: provides `std::formatter` + +`Color` is a tiny RGBA utility on top of `Vector4`. 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`: + + * `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; +``` + +--- + +## ImGui (optional) + +```cpp +#ifdef OMATH_IMGUI_INTEGRATION +ImColor to_im_color() const noexcept; // constructs from Vector4's to_im_vec4() +#endif +``` + +Ensure `` 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 { +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 provided +``` + +--- + +*Last updated: 31 Oct 2025* diff --git a/docs/utility/pattern_scan.md b/docs/utility/pattern_scan.md new file mode 100644 index 00000000..182c5b52 --- /dev/null +++ b/docs/utility/pattern_scan.md @@ -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 buf = /* ... bytes ... */; +std::span 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(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::iterator + scan_for_pattern(const std::span& range, + const std::string_view& pattern); + + // Deleted rvalue-span overload (prevents dangling) + static std::span::iterator + scan_for_pattern(std::span&&, + const std::string_view&) = delete; + + // Iterator overload + template + requires std::input_or_output_iterator> + static IteratorType + scan_for_pattern(const IteratorType& begin, + const IteratorType& end, + const std::string_view& pattern); +private: + [[nodiscard]] + static std::expected>, 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, ; call +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&` 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` or `std::span`). Using non-random-access iterators would be ill-formed. +* The inner matching loop compares a parsed token (`std::optional`) 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 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 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(it - s.begin()), 0U); +} +``` + +--- + +*Last updated: 31 Oct 2025* diff --git a/docs/utility/pe_pattern_scan.md b/docs/utility/pe_pattern_scan.md new file mode 100644 index 00000000..026afa7a --- /dev/null +++ b/docs/utility/pe_pattern_scan.md @@ -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: ``, ``, `` +> 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 + 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 + 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` + + * `value()` = **process virtual address** (ImageBase + SectionRVA + match offset). + * `nullopt` = no match or parse/PE error. + +* **File scan**: `std::optional` + + * `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 +#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(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* diff --git a/mkdocs.yml b/mkdocs.yml index c97182f5..cabaeb5b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1 +1,6 @@ -site_name: My Docs +site_name: OMATH Docs +theme: + name: darkly +extra_css: + - styles/center.css + - styles/custom-header.css \ No newline at end of file