diff --git a/docs/3d_primitives/mesh.md b/docs/3d_primitives/mesh.md new file mode 100644 index 0000000..803aaef --- /dev/null +++ b/docs/3d_primitives/mesh.md @@ -0,0 +1,465 @@ +# `omath::primitives::Mesh` — 3D mesh with transformation support + +> Header: `omath/3d_primitives/mesh.hpp` +> Namespace: `omath::primitives` +> Depends on: `omath::Vector3`, `omath::Mat4X4`, `omath::Triangle>` +> Purpose: represent and transform 3D meshes in different engine coordinate systems + +--- + +## Overview + +`Mesh` represents a 3D polygonal mesh with vertex data and transformation capabilities. It stores: +* **Vertex buffer (VBO)** — array of 3D vertex positions +* **Index buffer (VAO)** — array of triangular faces (indices into VBO) +* **Transformation** — position, rotation, and scale with caching + +The mesh supports transformation from local space to world space using engine-specific coordinate systems through the `MeshTrait` template parameter. + +--- + +## Template Declaration + +```cpp +template +class Mesh final; +``` + +### Template Parameters + +* `Mat4X4` — Matrix type for transformations (typically `omath::Mat4X4`) +* `RotationAngles` — Rotation representation (e.g., `ViewAngles` with pitch/yaw/roll) +* `MeshTypeTrait` — Engine-specific transformation trait (see [Engine Traits](#engine-traits)) +* `Type` — Scalar type for vertex coordinates (default `float`) + +--- + +## Type Aliases + +```cpp +using NumericType = Type; +``` + +Common engine-specific aliases: + +```cpp +// Source Engine +using Mesh = omath::primitives::Mesh; + +// Unity Engine +using Mesh = omath::primitives::Mesh; + +// Unreal Engine +using Mesh = omath::primitives::Mesh; + +// Frostbite, IW Engine, OpenGL similar... +``` + +Use the pre-defined type aliases in engine namespaces: +```cpp +using namespace omath::source_engine; +Mesh my_mesh = /* ... */; // Uses SourceEngine::Mesh +``` + +--- + +## Data Members + +### Vertex Data + +```cpp +std::vector> m_vertex_buffer; // VBO: vertex positions +std::vector> m_vertex_array_object; // VAO: face indices +``` + +* `m_vertex_buffer` — array of vertex positions in **local space** +* `m_vertex_array_object` — array of triangular faces, each containing 3 indices into `m_vertex_buffer` + +**Public access**: These members are public for direct manipulation when needed. + +--- + +## Constructor + +```cpp +Mesh(std::vector> vbo, + std::vector> vao, + Vector3 scale = {1, 1, 1}); +``` + +Creates a mesh from vertex and index data. + +**Parameters**: +* `vbo` — vertex buffer (moved into mesh) +* `vao` — index buffer / vertex array object (moved into mesh) +* `scale` — initial scale (default `{1, 1, 1}`) + +**Example**: +```cpp +std::vector> vertices = { + {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1} +}; + +std::vector> faces = { + {0, 1, 2}, // Triangle 1 + {0, 1, 3}, // Triangle 2 + {0, 2, 3}, // Triangle 3 + {1, 2, 3} // Triangle 4 +}; + +using namespace omath::source_engine; +Mesh tetrahedron(std::move(vertices), std::move(faces)); +``` + +--- + +## Transformation Methods + +### Setting Transform Components + +```cpp +void set_origin(const Vector3& new_origin); +void set_scale(const Vector3& new_scale); +void set_rotation(const RotationAngles& new_rotation_angles); +``` + +Update the mesh's transformation. **Side effect**: invalidates the cached transformation matrix, which will be recomputed on the next `get_to_world_matrix()` call. + +**Example**: +```cpp +mesh.set_origin({10, 0, 5}); +mesh.set_scale({2, 2, 2}); + +ViewAngles angles; +angles.pitch = PitchAngle::from_degrees(45.0f); +angles.yaw = YawAngle::from_degrees(30.0f); +mesh.set_rotation(angles); +``` + +### Getting Transform Components + +```cpp +[[nodiscard]] const Vector3& get_origin() const; +[[nodiscard]] const Vector3& get_scale() const; +[[nodiscard]] const RotationAngles& get_rotation_angles() const; +``` + +Retrieve current transformation components. + +### Transformation Matrix + +```cpp +[[nodiscard]] const Mat4X4& get_to_world_matrix() const; +``` + +Returns the cached local-to-world transformation matrix. The matrix is computed lazily on first access after any transformation change: + +``` +M = Translation(origin) × Scale(scale) × Rotation(angles) +``` + +The rotation matrix is computed using the engine-specific `MeshTrait::rotation_matrix()` method. + +**Caching**: The matrix is stored in a `mutable std::optional` and recomputed only when invalidated by `set_*` methods. + +--- + +## Vertex Transformation + +### `vertex_to_world_space` + +```cpp +[[nodiscard]] +Vector3 vertex_to_world_space(const Vector3& vertex) const; +``` + +Transforms a vertex from local space to world space by multiplying with the transformation matrix. + +**Algorithm**: +1. Convert vertex to column matrix: `[x, y, z, 1]ᵀ` +2. Multiply by transformation matrix: `M × vertex` +3. Extract the resulting 3D position + +**Usage**: +```cpp +Vector3 local_vertex{1, 0, 0}; +Vector3 world_vertex = mesh.vertex_to_world_space(local_vertex); +``` + +**Note**: This is used internally by `MeshCollider` to provide world-space support functions for GJK/EPA. + +--- + +## Face Transformation + +### `make_face_in_world_space` + +```cpp +[[nodiscard]] +Triangle> make_face_in_world_space( + const std::vector>::const_iterator vao_iterator +) const; +``` + +Creates a triangle in world space from a face index iterator. + +**Parameters**: +* `vao_iterator` — iterator to an element in `m_vertex_array_object` + +**Returns**: `Triangle` with all three vertices transformed to world space. + +**Example**: +```cpp +for (auto it = mesh.m_vertex_array_object.begin(); + it != mesh.m_vertex_array_object.end(); + ++it) { + Triangle> world_triangle = mesh.make_face_in_world_space(it); + // Render or process the triangle +} +``` + +--- + +## Usage Examples + +### Creating a Box Mesh + +```cpp +using namespace omath::source_engine; + +std::vector> box_vbo = { + // Bottom face + {-0.5f, -0.5f, 0.0f}, { 0.5f, -0.5f, 0.0f}, + { 0.5f, 0.5f, 0.0f}, {-0.5f, 0.5f, 0.0f}, + // Top face + {-0.5f, -0.5f, 1.0f}, { 0.5f, -0.5f, 1.0f}, + { 0.5f, 0.5f, 1.0f}, {-0.5f, 0.5f, 1.0f} +}; + +std::vector> box_vao = { + // Bottom + {0, 1, 2}, {0, 2, 3}, + // Top + {4, 6, 5}, {4, 7, 6}, + // Sides + {0, 4, 5}, {0, 5, 1}, + {1, 5, 6}, {1, 6, 2}, + {2, 6, 7}, {2, 7, 3}, + {3, 7, 4}, {3, 4, 0} +}; + +Mesh box(std::move(box_vbo), std::move(box_vao)); +box.set_origin({0, 0, 50}); +box.set_scale({10, 10, 10}); +``` + +### Transforming Mesh Over Time + +```cpp +void update_mesh(Mesh& mesh, float delta_time) { + // Rotate mesh + auto rotation = mesh.get_rotation_angles(); + rotation.yaw = YawAngle::from_degrees( + rotation.yaw.as_degrees() + 45.0f * delta_time + ); + mesh.set_rotation(rotation); + + // Oscillate position + auto origin = mesh.get_origin(); + origin.z = 50.0f + 10.0f * std::sin(current_time * 2.0f); + mesh.set_origin(origin); +} +``` + +### Collision Detection + +```cpp +using namespace omath::collision; +using namespace omath::source_engine; + +Mesh mesh_a(vbo_a, vao_a); +mesh_a.set_origin({0, 0, 0}); + +Mesh mesh_b(vbo_b, vao_b); +mesh_b.set_origin({5, 0, 0}); + +MeshCollider collider_a(std::move(mesh_a)); +MeshCollider collider_b(std::move(mesh_b)); + +auto result = GjkAlgorithm>::check_collision( + collider_a, collider_b +); +``` + +### Rendering Transformed Triangles + +```cpp +void render_mesh(const Mesh& mesh) { + for (auto it = mesh.m_vertex_array_object.begin(); + it != mesh.m_vertex_array_object.end(); + ++it) { + + Triangle> tri = mesh.make_face_in_world_space(it); + + // Draw triangle with your renderer + draw_triangle(tri.m_vertex1, tri.m_vertex2, tri.m_vertex3); + } +} +``` + +--- + +## Engine Traits + +Each game engine has a corresponding `MeshTrait` that provides the `rotation_matrix` function: + +```cpp +class MeshTrait final { +public: + [[nodiscard]] + static Mat4X4 rotation_matrix(const ViewAngles& rotation); +}; +``` + +### Available Engines + +| Engine | Namespace | Header | +|--------|-----------|--------| +| Source Engine | `omath::source_engine` | `engines/source_engine/mesh.hpp` | +| Unity | `omath::unity_engine` | `engines/unity_engine/mesh.hpp` | +| Unreal | `omath::unreal_engine` | `engines/unreal_engine/mesh.hpp` | +| Frostbite | `omath::frostbite_engine` | `engines/frostbite_engine/mesh.hpp` | +| IW Engine | `omath::iw_engine` | `engines/iw_engine/mesh.hpp` | +| OpenGL | `omath::opengl_engine` | `engines/opengl_engine/mesh.hpp` | + +**Example** (Source Engine): +```cpp +using namespace omath::source_engine; + +// Uses source_engine::MeshTrait automatically +Mesh my_mesh(vertices, indices); +``` + +See [MeshTrait Documentation](#mesh-trait-documentation) for engine-specific details. + +--- + +## Performance Considerations + +### Matrix Caching + +The transformation matrix is computed lazily and cached: +* **First access**: O(matrix multiply) ≈ 64 float operations +* **Subsequent access**: O(1) — returns cached matrix +* **Cache invalidation**: Any `set_*` call invalidates the cache + +**Best practice**: Batch transformation updates before accessing the matrix: +```cpp +// Good: single matrix recomputation +mesh.set_origin(new_origin); +mesh.set_rotation(new_rotation); +mesh.set_scale(new_scale); +auto matrix = mesh.get_to_world_matrix(); // Computes once + +// Bad: three matrix recomputations +mesh.set_origin(new_origin); +auto m1 = mesh.get_to_world_matrix(); // Compute +mesh.set_rotation(new_rotation); +auto m2 = mesh.get_to_world_matrix(); // Compute again +mesh.set_scale(new_scale); +auto m3 = mesh.get_to_world_matrix(); // Compute again +``` + +### Memory Layout + +* **VBO**: Contiguous `std::vector` for cache-friendly access +* **VAO**: Contiguous indices for cache-friendly face iteration +* **Matrix**: Cached in `std::optional` (no allocation) + +### Transformation Cost + +* `vertex_to_world_space`: ~15-20 FLOPs per vertex (4×4 matrix multiply) +* `make_face_in_world_space`: ~60 FLOPs (3 vertices) + +For high-frequency transformations, consider: +* Caching transformed vertices if the mesh doesn't change +* Using simpler proxy geometry for collision +* Batching transformations + +--- + +## Coordinate System Details + +Different engines use different coordinate systems: + +| Engine | Up Axis | Forward Axis | Handedness | +|--------|---------|--------------|------------| +| Source | +Z | +Y | Right | +| Unity | +Y | +Z | Left | +| Unreal | +Z | +X | Left | +| Frostbite | +Y | +Z | Right | +| IW Engine | +Z | +Y | Right | +| OpenGL | +Y | +Z | Right | + +The `MeshTrait::rotation_matrix` function accounts for these differences, ensuring correct transformations in each engine's space. + +--- + +## Limitations & Edge Cases + +### Empty Mesh + +A mesh with no vertices or faces is valid but not useful: +```cpp +Mesh empty_mesh({}, {}); // Valid but meaningless +``` + +For collision detection, ensure `m_vertex_buffer` is non-empty. + +### Index Validity + +No bounds checking is performed on indices in `m_vertex_array_object`. Ensure all indices are valid: +```cpp +assert(face.x < mesh.m_vertex_buffer.size()); +assert(face.y < mesh.m_vertex_buffer.size()); +assert(face.z < mesh.m_vertex_buffer.size()); +``` + +### Degenerate Triangles + +Faces with duplicate indices or collinear vertices will produce degenerate triangles. The mesh doesn't validate this; users must ensure clean geometry. + +### Thread Safety + +* **Read-only**: Safe to read from multiple threads (including const methods) +* **Modification**: Not thread-safe; synchronize `set_*` calls externally +* **Matrix cache**: Uses `mutable` member; not thread-safe even for const methods + +--- + +## See Also + +- [MeshCollider Documentation](../collision/mesh_collider.md) - Collision wrapper for meshes +- [GJK Algorithm Documentation](../collision/gjk_algorithm.md) - Uses mesh for collision detection +- [EPA Algorithm Documentation](../collision/epa_algorithm.md) - Penetration depth with meshes +- [Triangle Documentation](../linear_algebra/triangle.md) - Triangle primitive +- [Mat4X4 Documentation](../linear_algebra/mat.md) - Transformation matrices +- [Box Documentation](box.md) - Box primitive +- [Plane Documentation](plane.md) - Plane primitive + +--- + +## Mesh Trait Documentation + +For engine-specific `MeshTrait` details, see: + +- [Source Engine MeshTrait](../engines/source_engine/mesh_trait.md) +- [Unity Engine MeshTrait](../engines/unity_engine/mesh_trait.md) +- [Unreal Engine MeshTrait](../engines/unreal_engine/mesh_trait.md) +- [Frostbite Engine MeshTrait](../engines/frostbite/mesh_trait.md) +- [IW Engine MeshTrait](../engines/iw_engine/mesh_trait.md) +- [OpenGL Engine MeshTrait](../engines/opengl_engine/mesh_trait.md) + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/collision/epa_algorithm.md b/docs/collision/epa_algorithm.md new file mode 100644 index 0000000..68c23f6 --- /dev/null +++ b/docs/collision/epa_algorithm.md @@ -0,0 +1,322 @@ +# `omath::collision::Epa` — Expanding Polytope Algorithm for penetration depth + +> Header: `omath/collision/epa_algorithm.hpp` +> Namespace: `omath::collision` +> Depends on: `Simplex`, collider types with `find_abs_furthest_vertex` method +> Algorithm: **EPA** (Expanding Polytope Algorithm) for penetration depth and contact normal + +--- + +## Overview + +The **EPA (Expanding Polytope Algorithm)** calculates the **penetration depth** and **separation normal** between two intersecting convex shapes. It is typically used as a follow-up to the GJK algorithm after a collision has been detected. + +EPA takes a 4-point simplex containing the origin (from GJK) and iteratively expands it to find the point on the Minkowski difference closest to the origin. This point gives both: +* **Depth**: minimum translation distance to separate the shapes +* **Normal**: direction of separation (pointing from shape B to shape A) + +`Epa` is a template class working with any collider type that implements the support function interface. + +--- + +## `Epa::Result` + +```cpp +struct Result final { + bool success{false}; // true if EPA converged + Vertex normal{}; // outward normal (from B to A) + float depth{0.0f}; // penetration depth + int iterations{0}; // number of iterations performed + int num_vertices{0}; // final polytope vertex count + int num_faces{0}; // final polytope face count +}; +``` + +### Fields + +* `success` — `true` if EPA successfully computed depth and normal; `false` if it failed to converge +* `normal` — unit vector pointing from shape B toward shape A (separation direction) +* `depth` — minimum distance to move shape A along `normal` to separate the shapes +* `iterations` — actual iteration count (useful for performance tuning) +* `num_vertices`, `num_faces` — final polytope size (for diagnostics) + +--- + +## `Epa::Params` + +```cpp +struct Params final { + int max_iterations{64}; // maximum iterations before giving up + float tolerance{1e-4f}; // absolute tolerance on distance growth +}; +``` + +### Fields + +* `max_iterations` — safety limit to prevent infinite loops (default 64) +* `tolerance` — convergence threshold: stop when distance grows less than this (default 1e-4) + +--- + +## `Epa` Template Class + +```cpp +template +class Epa final { +public: + using Vertex = typename ColliderType::VertexType; + static_assert(EpaVector, "VertexType must satisfy EpaVector concept"); + + // Solve for penetration depth and normal + [[nodiscard]] + static Result solve( + const ColliderType& a, + const ColliderType& b, + const Simplex& simplex, + const Params params = {} + ); +}; +``` + +### Precondition + +The `simplex` parameter must: +* Have exactly 4 points (`simplex.size() == 4`) +* Contain the origin (i.e., be a valid GJK result with `hit == true`) + +Violating this precondition leads to undefined behavior. + +--- + +## Collider Requirements + +Any type used as `ColliderType` must provide: + +```cpp +// Type alias for vertex type (typically Vector3) +using VertexType = /* ... */; + +// Find the farthest point in world space along the given direction +[[nodiscard]] +VertexType find_abs_furthest_vertex(const VertexType& direction) const; +``` + +--- + +## Algorithm Details + +### Expanding Polytope + +EPA maintains a convex polytope (polyhedron) in Minkowski difference space `A - B`. Starting from the 4-point tetrahedron (simplex from GJK), it repeatedly: + +1. **Find closest face** to the origin +2. **Support query** in the direction of the face normal +3. **Expand polytope** by adding the new support point +4. **Update faces** to maintain convexity + +The algorithm terminates when: +* **Convergence**: the distance from origin to polytope stops growing (within tolerance) +* **Max iterations**: safety limit reached +* **Failure cases**: degenerate polytope or numerical issues + +### Minkowski Difference + +Like GJK, EPA operates in Minkowski difference space where `point = a - b` for points in shapes A and B. The closest point on this polytope to the origin gives the minimum separation. + +### Face Winding + +Faces are stored with outward-pointing normals. The algorithm uses a priority queue to efficiently find the face closest to the origin. + +--- + +## Vertex Type Requirements + +The `VertexType` must satisfy the `EpaVector` concept: + +```cpp +template +concept EpaVector = requires(const V& a, const V& b, float s) { + { a - b } -> std::same_as; + { a.cross(b) } -> std::same_as; + { a.dot(b) } -> std::same_as; + { -a } -> std::same_as; + { a * s } -> std::same_as; + { a / s } -> std::same_as; +}; +``` + +`omath::Vector3` satisfies this concept. + +--- + +## Usage Examples + +### Basic EPA Usage + +```cpp +using namespace omath::collision; +using namespace omath::source_engine; + +// First, run GJK to detect collision +MeshCollider collider_a(mesh_a); +MeshCollider collider_b(mesh_b); + +auto gjk_result = GjkAlgorithm>::check_collision( + collider_a, + collider_b +); + +if (gjk_result.hit) { + // Collision detected, use EPA to get penetration info + auto epa_result = Epa>::solve( + collider_a, + collider_b, + gjk_result.simplex + ); + + if (epa_result.success) { + std::cout << "Penetration depth: " << epa_result.depth << "\n"; + std::cout << "Separation normal: " + << "(" << epa_result.normal.x << ", " + << epa_result.normal.y << ", " + << epa_result.normal.z << ")\n"; + + // Apply separation: move A away from B + Vector3 correction = epa_result.normal * epa_result.depth; + mesh_a.set_origin(mesh_a.get_origin() + correction); + } +} +``` + +### Custom Parameters + +```cpp +// Use custom convergence settings +Epa::Params params; +params.max_iterations = 128; // Allow more iterations for complex shapes +params.tolerance = 1e-5f; // Tighter tolerance for more accuracy + +auto result = Epa::solve(a, b, simplex, params); +``` + +### Physics Integration + +```cpp +void resolve_collision(PhysicsBody& body_a, PhysicsBody& body_b) { + auto gjk_result = GjkAlgorithm::check_collision( + body_a.collider, body_b.collider + ); + + if (!gjk_result.hit) + return; // No collision + + auto epa_result = Epa::solve( + body_a.collider, + body_b.collider, + gjk_result.simplex + ); + + if (epa_result.success) { + // Separate bodies + float mass_sum = body_a.mass + body_b.mass; + float ratio_a = body_b.mass / mass_sum; + float ratio_b = body_a.mass / mass_sum; + + body_a.position += epa_result.normal * (epa_result.depth * ratio_a); + body_b.position -= epa_result.normal * (epa_result.depth * ratio_b); + + // Apply collision response + apply_impulse(body_a, body_b, epa_result.normal); + } +} +``` + +--- + +## Performance Characteristics + +* **Time complexity**: O(k × f) where k is iterations and f is faces per iteration (typically f grows slowly) +* **Space complexity**: O(n) where n is the number of polytope vertices (typically < 100) +* **Typical iterations**: 4-20 for most collisions +* **Worst case**: 64 iterations (configurable limit) + +### Performance Tips + +1. **Adjust max_iterations**: Balance accuracy vs. performance for your use case +2. **Tolerance tuning**: Larger tolerance = faster convergence but less accurate +3. **Shape complexity**: Simpler shapes (fewer faces) converge faster +4. **Deep penetrations**: Require more iterations; consider broad-phase separation + +--- + +## Limitations & Edge Cases + +* **Requires valid simplex**: Must be called with a 4-point simplex containing the origin (from successful GJK) +* **Convex shapes only**: Like GJK, EPA only works with convex colliders +* **Convergence failure**: Can fail to converge for degenerate or very thin shapes (check `result.success`) +* **Numerical precision**: Extreme scale differences or very small shapes may cause issues +* **Deep penetration**: Very deep intersections may require many iterations or fail to converge + +### Error Handling + +```cpp +auto result = Epa::solve(a, b, simplex); + +if (!result.success) { + // EPA failed to converge + // Fallback options: + // 1. Use a default separation (e.g., axis between centers) + // 2. Increase max_iterations and retry + // 3. Log a warning and skip this collision + std::cerr << "EPA failed after " << result.iterations << " iterations\n"; +} +``` + +--- + +## Theory & Background + +### Why EPA after GJK? + +GJK determines **if** shapes intersect but doesn't compute penetration depth. EPA extends GJK's final simplex to find the exact depth and normal needed for: +* **Collision response** — separating objects realistically +* **Contact manifolds** — generating contact points for physics +* **Constraint solving** — iterative physics solvers + +### Comparison with SAT + +| Feature | EPA | SAT (Separating Axis Theorem) | +|---------|-----|-------------------------------| +| Works with | Any convex shape | Polytopes (faces/edges) | +| Penetration depth | Yes | Yes | +| Complexity | Iterative | Per-axis projection | +| Best for | General convex | Boxes, prisms | +| Typical speed | Moderate | Fast (few axes) | + +EPA is more general; SAT is faster for axis-aligned shapes. + +--- + +## Implementation Details + +The EPA implementation in OMath: +* Uses a **priority queue** to efficiently find the closest face +* Maintains face winding for consistent normals +* Handles **edge cases**: degenerate faces, numerical instability +* Prevents infinite loops with iteration limits +* Returns detailed diagnostics (iteration count, polytope size) + +--- + +## See Also + +- [GJK Algorithm Documentation](gjk_algorithm.md) - Collision detection (required before EPA) +- [Simplex Documentation](simplex.md) - Input simplex structure +- [MeshCollider Documentation](mesh_collider.md) - Mesh-based collider +- [Mesh Documentation](../3d_primitives/mesh.md) - Mesh primitive +- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial +- [API Overview](../api_overview.md) - High-level API reference + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/collision/gjk_algorithm.md b/docs/collision/gjk_algorithm.md new file mode 100644 index 0000000..4fa1392 --- /dev/null +++ b/docs/collision/gjk_algorithm.md @@ -0,0 +1,216 @@ +# `omath::collision::GjkAlgorithm` — Gilbert-Johnson-Keerthi collision detection + +> Header: `omath/collision/gjk_algorithm.hpp` +> Namespace: `omath::collision` +> Depends on: `Simplex`, collider types with `find_abs_furthest_vertex` method +> Algorithm: **GJK** (Gilbert-Johnson-Keerthi) for convex shape collision detection + +--- + +## Overview + +The **GJK algorithm** determines whether two convex shapes intersect by iteratively constructing a simplex in Minkowski difference space. The algorithm is widely used in physics engines and collision detection systems due to its efficiency and robustness. + +`GjkAlgorithm` is a template class that works with any collider type implementing the required support function interface: + +* `find_abs_furthest_vertex(direction)` — returns the farthest point in the collider along the given direction. + +The algorithm returns a `GjkHitInfo` containing: +* `hit` — boolean indicating whether the shapes intersect +* `simplex` — a 4-point simplex containing the origin (valid only when `hit == true`) + +--- + +## `GjkHitInfo` + +```cpp +template +struct GjkHitInfo final { + bool hit{false}; // true if collision detected + Simplex simplex; // 4-point simplex (valid only if hit == true) +}; +``` + +The `simplex` field is only meaningful when `hit == true` and contains 4 points. This simplex can be passed to the EPA algorithm for penetration depth calculation. + +--- + +## `GjkAlgorithm` + +```cpp +template +class GjkAlgorithm final { + using VertexType = typename ColliderType::VertexType; + +public: + // Find support vertex in Minkowski difference + [[nodiscard]] + static VertexType find_support_vertex( + const ColliderType& collider_a, + const ColliderType& collider_b, + const VertexType& direction + ); + + // Check if two convex shapes intersect + [[nodiscard]] + static GjkHitInfo check_collision( + const ColliderType& collider_a, + const ColliderType& collider_b + ); +}; +``` + +--- + +## Collider Requirements + +Any type used as `ColliderType` must provide: + +```cpp +// Type alias for vertex type (typically Vector3) +using VertexType = /* ... */; + +// Find the farthest point in world space along the given direction +[[nodiscard]] +VertexType find_abs_furthest_vertex(const VertexType& direction) const; +``` + +Common collider types: +* `MeshCollider` — for arbitrary triangle meshes +* Custom colliders for spheres, boxes, capsules, etc. + +--- + +## Algorithm Details + +### Minkowski Difference + +GJK operates in the **Minkowski difference** space `A - B`, where a point in this space represents the difference between points in shapes A and B. The shapes intersect if and only if the origin lies within this Minkowski difference. + +### Support Function + +The support function finds the point in the Minkowski difference farthest along a given direction: + +```cpp +support(A, B, dir) = A.furthest(dir) - B.furthest(-dir) +``` + +This is computed by `find_support_vertex`. + +### Simplex Iteration + +The algorithm builds a simplex incrementally: +1. Start with an initial direction (typically vector between shape centers) +2. Add support vertices in directions that move the simplex toward the origin +3. Simplify the simplex to keep only points closest to the origin +4. Repeat until either: + * Origin is contained (collision detected, returns 4-point simplex) + * No progress can be made (no collision) + +Maximum 64 iterations are performed to prevent infinite loops in edge cases. + +--- + +## Usage Examples + +### Basic Collision Check + +```cpp +using namespace omath::collision; +using namespace omath::source_engine; + +// Create mesh colliders +Mesh mesh_a = /* ... */; +Mesh mesh_b = /* ... */; + +MeshCollider collider_a(mesh_a); +MeshCollider collider_b(mesh_b); + +// Check for collision +auto result = GjkAlgorithm>::check_collision( + collider_a, + collider_b +); + +if (result.hit) { + std::cout << "Collision detected!\n"; + // Can pass result.simplex to EPA for penetration depth +} +``` + +### Combined with EPA + +```cpp +auto gjk_result = GjkAlgorithm::check_collision(a, b); + +if (gjk_result.hit) { + // Get penetration depth and normal using EPA + auto epa_result = Epa::solve( + a, b, gjk_result.simplex + ); + + if (epa_result.success) { + std::cout << "Penetration depth: " << epa_result.depth << "\n"; + std::cout << "Separation normal: " << epa_result.normal << "\n"; + } +} +``` + +--- + +## Performance Characteristics + +* **Time complexity**: O(k) where k is the number of iterations (typically < 20 for most cases) +* **Space complexity**: O(1) — only stores a 4-point simplex +* **Best case**: 4-8 iterations for well-separated objects +* **Worst case**: 64 iterations (hard limit) +* **Cache efficient**: operates on small fixed-size data structures + +### Optimization Tips + +1. **Initial direction**: Use vector between shape centers for faster convergence +2. **Early exit**: GJK quickly rejects non-intersecting shapes +3. **Warm starting**: Reuse previous simplex for continuous collision detection +4. **Broad phase**: Use spatial partitioning before GJK (AABB trees, grids) + +--- + +## Limitations & Edge Cases + +* **Convex shapes only**: GJK only works with convex colliders. For concave shapes, decompose into convex parts or use a mesh collider wrapper. +* **Degenerate simplices**: The algorithm handles degenerate cases, but numerical precision can cause issues with very thin or flat shapes. +* **Iteration limit**: Hard limit of 64 iterations prevents infinite loops but may miss collisions in extreme cases. +* **Zero-length directions**: The simplex update logic guards against zero-length vectors, returning safe fallbacks. + +--- + +## Vertex Type Requirements + +The `VertexType` must satisfy the `GjkVector` concept (defined in `simplex.hpp`): + +```cpp +template +concept GjkVector = requires(const V& a, const V& b) { + { -a } -> std::same_as; + { a - b } -> std::same_as; + { a.cross(b) } -> std::same_as; + { a.point_to_same_direction(b) } -> std::same_as; +}; +``` + +`omath::Vector3` satisfies this concept. + +--- + +## See Also + +- [EPA Algorithm Documentation](epa_algorithm.md) - Penetration depth calculation +- [Simplex Documentation](simplex.md) - Simplex data structure +- [MeshCollider Documentation](mesh_collider.md) - Mesh-based collider +- [Mesh Documentation](../3d_primitives/mesh.md) - Mesh primitive +- [LineTracer Documentation](line_tracer.md) - Ray-triangle intersection +- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/collision/mesh_collider.md b/docs/collision/mesh_collider.md new file mode 100644 index 0000000..2e1b6f1 --- /dev/null +++ b/docs/collision/mesh_collider.md @@ -0,0 +1,371 @@ +# `omath::collision::MeshCollider` — Convex hull collider for meshes + +> Header: `omath/collision/mesh_collider.hpp` +> Namespace: `omath::collision` +> Depends on: `omath::primitives::Mesh`, `omath::Vector3` +> Purpose: wrap a mesh to provide collision detection support for GJK/EPA + +--- + +## Overview + +`MeshCollider` wraps a `Mesh` object to provide the **support function** interface required by the GJK and EPA collision detection algorithms. The support function finds the vertex of the mesh farthest along a given direction, which is essential for constructing Minkowski difference simplices. + +**Important**: `MeshCollider` assumes the mesh represents a **convex hull**. For non-convex shapes, you must either: +* Decompose into convex parts +* Use the convex hull of the mesh +* Use a different collision detection algorithm + +--- + +## Template Declaration + +```cpp +template +class MeshCollider; +``` + +### MeshType Requirements + +The `MeshType` must be an instantiation of `omath::primitives::Mesh` or provide: + +```cpp +struct MeshType { + using NumericType = /* float, double, etc. */; + + std::vector> m_vertex_buffer; + + // Transform vertex from local to world space + Vector3 vertex_to_world_space(const Vector3&) const; +}; +``` + +Common types: +* `omath::source_engine::Mesh` +* `omath::unity_engine::Mesh` +* `omath::unreal_engine::Mesh` +* `omath::frostbite_engine::Mesh` +* `omath::iw_engine::Mesh` +* `omath::opengl_engine::Mesh` + +--- + +## Type Aliases + +```cpp +using NumericType = typename MeshType::NumericType; +using VertexType = Vector3; +``` + +* `NumericType` — scalar type (typically `float`) +* `VertexType` — 3D vector type for vertices + +--- + +## Constructor + +```cpp +explicit MeshCollider(MeshType mesh); +``` + +Creates a collider from a mesh. The mesh is **moved** into the collider, so pass by value: + +```cpp +omath::source_engine::Mesh my_mesh = /* ... */; +MeshCollider collider(std::move(my_mesh)); +``` + +--- + +## Methods + +### `find_furthest_vertex` + +```cpp +[[nodiscard]] +const VertexType& find_furthest_vertex(const VertexType& direction) const; +``` + +Finds the vertex in the mesh's **local space** that has the maximum dot product with `direction`. + +**Algorithm**: Linear search through all vertices (O(n) where n is vertex count). + +**Returns**: Const reference to the vertex in `m_vertex_buffer`. + +--- + +### `find_abs_furthest_vertex` + +```cpp +[[nodiscard]] +VertexType find_abs_furthest_vertex(const VertexType& direction) const; +``` + +Finds the vertex farthest along `direction` and transforms it to **world space**. This is the primary method used by GJK/EPA. + +**Steps**: +1. Find furthest vertex in local space using `find_furthest_vertex` +2. Transform to world space using `mesh.vertex_to_world_space()` + +**Returns**: Vertex position in world coordinates. + +**Usage in GJK**: +```cpp +// GJK support function for Minkowski difference +VertexType support = collider_a.find_abs_furthest_vertex(direction) + - collider_b.find_abs_furthest_vertex(-direction); +``` + +--- + +## Usage Examples + +### Basic Collision Detection + +```cpp +using namespace omath::collision; +using namespace omath::source_engine; + +// Create meshes with vertex data +std::vector> vbo_a = { + {-1, -1, -1}, {1, -1, -1}, {1, 1, -1}, {-1, 1, -1}, + {-1, -1, 1}, {1, -1, 1}, {1, 1, 1}, {-1, 1, 1} +}; +std::vector> vao_a = /* face indices */; + +Mesh mesh_a(vbo_a, vao_a); +mesh_a.set_origin({0, 0, 0}); + +Mesh mesh_b(vbo_b, vao_b); +mesh_b.set_origin({5, 0, 0}); // Positioned away from mesh_a + +// Wrap in colliders +MeshCollider collider_a(std::move(mesh_a)); +MeshCollider collider_b(std::move(mesh_b)); + +// Run GJK +auto result = GjkAlgorithm>::check_collision( + collider_a, collider_b +); + +if (result.hit) { + std::cout << "Collision detected!\n"; +} +``` + +### With EPA for Penetration Depth + +```cpp +auto gjk_result = GjkAlgorithm>::check_collision( + collider_a, collider_b +); + +if (gjk_result.hit) { + auto epa_result = Epa>::solve( + collider_a, collider_b, gjk_result.simplex + ); + + if (epa_result.success) { + std::cout << "Penetration: " << epa_result.depth << " units\n"; + std::cout << "Normal: " << epa_result.normal << "\n"; + } +} +``` + +### Custom Mesh Creation + +```cpp +// Create a simple box mesh +std::vector> box_vertices = { + {-0.5f, -0.5f, -0.5f}, { 0.5f, -0.5f, -0.5f}, + { 0.5f, 0.5f, -0.5f}, {-0.5f, 0.5f, -0.5f}, + {-0.5f, -0.5f, 0.5f}, { 0.5f, -0.5f, 0.5f}, + { 0.5f, 0.5f, 0.5f}, {-0.5f, 0.5f, 0.5f} +}; + +std::vector> box_indices = { + {0, 1, 2}, {0, 2, 3}, // Front face + {4, 6, 5}, {4, 7, 6}, // Back face + {0, 4, 5}, {0, 5, 1}, // Bottom face + {2, 6, 7}, {2, 7, 3}, // Top face + {0, 3, 7}, {0, 7, 4}, // Left face + {1, 5, 6}, {1, 6, 2} // Right face +}; + +using namespace omath::source_engine; +Mesh box_mesh(box_vertices, box_indices); +box_mesh.set_origin({10, 0, 0}); +box_mesh.set_scale({2, 2, 2}); + +MeshCollider box_collider(std::move(box_mesh)); +``` + +### Oriented Collision + +```cpp +// Create rotated mesh +Mesh mesh(vertices, indices); +mesh.set_origin({5, 5, 5}); +mesh.set_scale({1, 1, 1}); + +// Set rotation (engine-specific angles) +ViewAngles rotation; +rotation.pitch = PitchAngle::from_degrees(45.0f); +rotation.yaw = YawAngle::from_degrees(30.0f); +mesh.set_rotation(rotation); + +// Collider automatically handles transformation +MeshCollider collider(std::move(mesh)); + +// Support function returns world-space vertices +auto support = collider.find_abs_furthest_vertex({0, 1, 0}); +``` + +--- + +## Performance Considerations + +### Linear Search + +`find_furthest_vertex` performs a **linear search** through all vertices: +* **Time complexity**: O(n) per support query +* **GJK iterations**: ~10-20 support queries per collision test +* **Total cost**: O(k × n) where k is GJK iterations + +For meshes with many vertices (>1000), consider: +* Using simpler proxy geometry (bounding box, convex hull with fewer vertices) +* Pre-computing hierarchical structures +* Using specialized collision shapes when possible + +### Caching Opportunities + +The implementation uses `std::ranges::max_element`, which is cache-friendly for contiguous vertex buffers. For optimal performance: +* Store vertices contiguously in memory +* Avoid pointer-based or scattered vertex storage +* Consider SoA (Structure of Arrays) layout for SIMD optimization + +### World Space Transformation + +The `vertex_to_world_space` call involves matrix multiplication: +* **Cost**: ~15-20 floating-point operations per vertex +* **Optimization**: The mesh caches its transformation matrix +* **Update cost**: Only recomputed when origin/rotation/scale changes + +--- + +## Limitations & Edge Cases + +### Convex Hull Requirement + +**Critical**: GJK/EPA only work with **convex shapes**. If your mesh is concave: + +#### Option 1: Convex Decomposition +```cpp +// Decompose concave mesh into convex parts +std::vector convex_parts = decompose_mesh(concave_mesh); + +for (const auto& part : convex_parts) { + MeshCollider collider(part); + // Test each part separately +} +``` + +#### Option 2: Use Convex Hull +```cpp +// Compute convex hull of vertices +auto hull_vertices = compute_convex_hull(mesh.m_vertex_buffer); +Mesh hull_mesh(hull_vertices, hull_indices); +MeshCollider collider(std::move(hull_mesh)); +``` + +#### Option 3: Different Algorithm +Use triangle-based collision (e.g., LineTracer) for true concave support. + +### Empty Mesh + +Behavior is undefined if `m_vertex_buffer` is empty. Always ensure: +```cpp +assert(!mesh.m_vertex_buffer.empty()); +MeshCollider collider(std::move(mesh)); +``` + +### Degenerate Meshes + +* **Single vertex**: Treated as a point (degenerates to sphere collision) +* **Two vertices**: Line segment (may cause GJK issues) +* **Coplanar vertices**: Flat mesh; EPA may have convergence issues + +**Recommendation**: Use at least 4 non-coplanar vertices for robustness. + +--- + +## Coordinate Systems + +`MeshCollider` supports different engine coordinate systems through the `MeshTrait`: + +| Engine | Up Axis | Handedness | Rotation Order | +|--------|---------|------------|----------------| +| Source Engine | Z | Right-handed | Pitch/Yaw/Roll | +| Unity | Y | Left-handed | Pitch/Yaw/Roll | +| Unreal | Z | Left-handed | Roll/Pitch/Yaw | +| Frostbite | Y | Right-handed | Pitch/Yaw/Roll | +| IW Engine | Z | Right-handed | Pitch/Yaw/Roll | +| OpenGL | Y | Right-handed | Pitch/Yaw/Roll | + +The `vertex_to_world_space` method handles these differences transparently. + +--- + +## Advanced Usage + +### Custom Support Function + +For specialized collision shapes, implement a custom collider: + +```cpp +class SphereCollider { +public: + using VertexType = Vector3; + + Vector3 center; + float radius; + + VertexType find_abs_furthest_vertex(const VertexType& direction) const { + auto normalized = direction.normalized(); + return center + normalized * radius; + } +}; + +// Use with GJK/EPA +auto result = GjkAlgorithm::check_collision(sphere_a, sphere_b); +``` + +### Debugging Support Queries + +```cpp +class DebugMeshCollider : public MeshCollider { +public: + using MeshCollider::MeshCollider; + + VertexType find_abs_furthest_vertex(const VertexType& direction) const { + auto result = MeshCollider::find_abs_furthest_vertex(direction); + std::cout << "Support query: direction=" << direction + << " -> vertex=" << result << "\n"; + return result; + } +}; +``` + +--- + +## See Also + +- [GJK Algorithm Documentation](gjk_algorithm.md) - Uses `MeshCollider` for collision detection +- [EPA Algorithm Documentation](epa_algorithm.md) - Uses `MeshCollider` for penetration depth +- [Simplex Documentation](simplex.md) - Data structure used by GJK +- [Mesh Documentation](../3d_primitives/mesh.md) - Underlying mesh primitive +- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/collision/simplex.md b/docs/collision/simplex.md new file mode 100644 index 0000000..c786e78 --- /dev/null +++ b/docs/collision/simplex.md @@ -0,0 +1,327 @@ +# `omath::collision::Simplex` — Fixed-capacity simplex for GJK/EPA + +> Header: `omath/collision/simplex.hpp` +> Namespace: `omath::collision` +> Depends on: `Vector3` (or any type satisfying `GjkVector` concept) +> Purpose: store and manipulate simplices in GJK and EPA algorithms + +--- + +## Overview + +`Simplex` is a lightweight container for up to 4 points, used internally by the GJK and EPA collision detection algorithms. A simplex in this context is a geometric shape defined by 1 to 4 vertices: + +* **1 point** — a single vertex +* **2 points** — a line segment +* **3 points** — a triangle +* **4 points** — a tetrahedron + +The GJK algorithm builds simplices incrementally to detect collisions, and EPA extends a 4-point simplex to compute penetration depth. + +--- + +## Template & Concepts + +```cpp +template> +class Simplex final; +``` + +### `GjkVector` Concept + +The vertex type must satisfy: + +```cpp +template +concept GjkVector = requires(const V& a, const V& b) { + { -a } -> std::same_as; + { a - b } -> std::same_as; + { a.cross(b) } -> std::same_as; + { a.point_to_same_direction(b) } -> std::same_as; +}; +``` + +`omath::Vector3` satisfies this concept and is the default. + +--- + +## Constructors & Assignment + +```cpp +constexpr Simplex() = default; + +constexpr Simplex& operator=(std::initializer_list list) noexcept; +``` + +### Initialization + +```cpp +// Empty simplex +Simplex> s; + +// Initialize with points +Simplex> s2; +s2 = {v1, v2, v3}; // 3-point simplex (triangle) +``` + +**Constraint**: Maximum 4 points. Passing more triggers an assertion in debug builds. + +--- + +## Core Methods + +### Adding Points + +```cpp +constexpr void push_front(const VectorType& p) noexcept; +``` + +Inserts a point at the **front** (index 0), shifting existing points back. If the simplex is already at capacity (4 points), the last point is discarded. + +**Usage pattern in GJK**: +```cpp +simplex.push_front(new_support_point); +// Now simplex[0] is the newest point +``` + +### Size & Capacity + +```cpp +[[nodiscard]] constexpr std::size_t size() const noexcept; +[[nodiscard]] static constexpr std::size_t capacity = 4; +``` + +* `size()` — current number of points (0-4) +* `capacity` — maximum points (always 4) + +### Element Access + +```cpp +[[nodiscard]] constexpr VectorType& operator[](std::size_t index) noexcept; +[[nodiscard]] constexpr const VectorType& operator[](std::size_t index) const noexcept; +``` + +Access points by index. **No bounds checking** — index must be `< size()`. + +```cpp +if (simplex.size() >= 2) { + auto edge = simplex[1] - simplex[0]; +} +``` + +### Iterators + +```cpp +[[nodiscard]] constexpr auto begin() noexcept; +[[nodiscard]] constexpr auto end() noexcept; +[[nodiscard]] constexpr auto begin() const noexcept; +[[nodiscard]] constexpr auto end() const noexcept; +``` + +Standard iterator support for range-based loops: + +```cpp +for (const auto& vertex : simplex) { + std::cout << vertex << "\n"; +} +``` + +--- + +## GJK-Specific Methods + +These methods implement the core logic for simplifying simplices in the GJK algorithm. + +### `contains_origin` + +```cpp +[[nodiscard]] constexpr bool contains_origin() noexcept; +``` + +Determines if the origin lies within the current simplex. This is the **core GJK test**: if true, the shapes intersect. + +* For a **1-point** simplex, always returns `false` (can't contain origin) +* For a **2-point** simplex (line), checks if origin projects onto the segment +* For a **3-point** simplex (triangle), checks if origin projects onto the triangle +* For a **4-point** simplex (tetrahedron), checks if origin is inside + +**Side effect**: Simplifies the simplex by removing points not needed to maintain proximity to the origin. After calling, `size()` may have decreased. + +**Return value**: +* `true` — origin is contained (collision detected) +* `false` — origin not contained; simplex has been simplified toward origin + +### `next_direction` + +```cpp +[[nodiscard]] constexpr VectorType next_direction() const noexcept; +``` + +Computes the next search direction for GJK. This is the direction from the simplex toward the origin, used to query the next support point. + +* Must be called **after** `contains_origin()` returns `false` +* Behavior is **undefined** if called when `size() == 0` or when origin is already contained + +--- + +## Usage Examples + +### GJK Iteration (Simplified) + +```cpp +Simplex> simplex; +Vector3 direction{1, 0, 0}; // Initial search direction + +for (int i = 0; i < 64; ++i) { + // Get support point in current direction + auto support = find_support_vertex(collider_a, collider_b, direction); + + // Check if we made progress + if (support.dot(direction) <= 0) + break; // No collision possible + + simplex.push_front(support); + + // Check if simplex contains origin + if (simplex.contains_origin()) { + // Collision detected! + assert(simplex.size() == 4); + return GjkHitInfo{true, simplex}; + } + + // Get next search direction + direction = simplex.next_direction(); +} + +// No collision +return GjkHitInfo{false, {}}; +``` + +### Manual Simplex Construction + +```cpp +using Vec3 = Vector3; + +Simplex simplex; +simplex = { + Vec3{0.0f, 0.0f, 0.0f}, + Vec3{1.0f, 0.0f, 0.0f}, + Vec3{0.0f, 1.0f, 0.0f}, + Vec3{0.0f, 0.0f, 1.0f} +}; + +assert(simplex.size() == 4); + +// Check if origin is inside this tetrahedron +bool has_collision = simplex.contains_origin(); +``` + +### Iterating Over Points + +```cpp +void print_simplex(const Simplex>& s) { + std::cout << "Simplex with " << s.size() << " points:\n"; + for (std::size_t i = 0; i < s.size(); ++i) { + const auto& p = s[i]; + std::cout << " [" << i << "] = (" + << p.x << ", " << p.y << ", " << p.z << ")\n"; + } +} +``` + +--- + +## Implementation Details + +### Simplex Simplification + +The `contains_origin()` method implements different tests based on simplex size: + +#### Line Segment (2 points) + +Checks if origin projects onto segment `[A, B]`: +* If yes, keeps both points +* If no, keeps only the closer point + +#### Triangle (3 points) + +Tests the origin against the triangle plane and edges using cross products. Simplifies to: +* The full triangle if origin projects onto its surface +* An edge if origin is closest to that edge +* A single vertex otherwise + +#### Tetrahedron (4 points) + +Tests origin against all four faces: +* If origin is inside, returns `true` (collision) +* If outside, reduces to the face/edge/vertex closest to origin + +### Direction Calculation + +The `next_direction()` method computes: +* For **line**: perpendicular from line toward origin +* For **triangle**: perpendicular from triangle toward origin +* Implementation uses cross products and projections to avoid sqrt when possible + +--- + +## Performance Characteristics + +* **Storage**: Fixed 4 × `sizeof(VectorType)` + size counter +* **Push front**: O(n) where n is current size (max 4, so effectively O(1)) +* **Contains origin**: O(1) for each case (line, triangle, tetrahedron) +* **Next direction**: O(1) — simple cross products and subtractions +* **No heap allocations**: All storage is inline + +**constexpr**: All methods are `constexpr`, enabling compile-time usage where feasible. + +--- + +## Edge Cases & Constraints + +### Degenerate Simplices + +* **Zero-length edges**: Can occur if support points coincide. The algorithm handles this by checking `point_to_same_direction` before divisions. +* **Collinear points**: Triangle simplification detects and handles collinear cases by reducing to a line. +* **Flat tetrahedron**: If the 4th point is coplanar with the first 3, the origin containment test may have reduced precision. + +### Assertions + +* **Capacity**: `operator=` asserts `list.size() <= 4` in debug builds +* **Index bounds**: No bounds checking in release builds — ensure `index < size()` + +### Thread Safety + +* **Read-only**: Safe to read from multiple threads +* **Modification**: Not thread-safe; synchronize writes externally + +--- + +## Relationship to GJK & EPA + +### In GJK + +* Starts empty or with an initial point +* Grows via `push_front` as support points are added +* Shrinks via `contains_origin` as it's simplified +* Once it reaches 4 points and contains origin, GJK succeeds + +### In EPA + +* Takes a 4-point simplex from GJK as input +* Uses the tetrahedron as the initial polytope +* Does not directly use the `Simplex` class for expansion (EPA maintains a more complex polytope structure) + +--- + +## See Also + +- [GJK Algorithm Documentation](gjk_algorithm.md) - Uses `Simplex` for collision detection +- [EPA Algorithm Documentation](epa_algorithm.md) - Takes 4-point `Simplex` as input +- [MeshCollider Documentation](mesh_collider.md) - Provides support function for GJK/EPA +- [Vector3 Documentation](../linear_algebra/vector3.md) - Default vertex type +- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Collision tutorial + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/engines/frostbite/mesh_trait.md b/docs/engines/frostbite/mesh_trait.md new file mode 100644 index 0000000..f6b235f --- /dev/null +++ b/docs/engines/frostbite/mesh_trait.md @@ -0,0 +1,119 @@ +# `omath::frostbite_engine::MeshTrait` — mesh transformation trait for Frostbite Engine + +> Header: `omath/engines/frostbite_engine/traits/mesh_trait.hpp` +> Namespace: `omath::frostbite_engine` +> Purpose: provide Frostbite Engine-specific rotation matrix computation for `omath::primitives::Mesh` + +--- + +## Summary + +`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Frostbite's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior. + +--- + +## Coordinate System + +**Frostbite Engine** uses: +* **Up axis**: +Y +* **Forward axis**: +Z +* **Right axis**: +X +* **Handedness**: Right-handed +* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z) + +--- + +## API + +```cpp +namespace omath::frostbite_engine { + +class MeshTrait final { +public: + [[nodiscard]] + static Mat4X4 rotation_matrix(const ViewAngles& rotation); +}; + +} // namespace omath::frostbite_engine +``` + +--- + +## Method: `rotation_matrix` + +```cpp +static Mat4X4 rotation_matrix(const ViewAngles& rotation); +``` + +Computes a 4×4 rotation matrix from Frostbite-style Euler angles. + +**Parameters**: +* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles + +**Returns**: 4×4 rotation matrix suitable for mesh transformation + +**Implementation**: Delegates to `frostbite_engine::rotation_matrix(rotation)` defined in `formulas.hpp`. + +--- + +## Usage + +### With Mesh + +```cpp +using namespace omath::frostbite_engine; + +// Create mesh (MeshTrait is used automatically) +Mesh my_mesh(vertices, indices); + +// Set rotation using ViewAngles +ViewAngles angles; +angles.pitch = PitchAngle::from_degrees(30.0f); +angles.yaw = YawAngle::from_degrees(45.0f); +angles.roll = RollAngle::from_degrees(0.0f); + +my_mesh.set_rotation(angles); + +// The rotation matrix is computed using MeshTrait::rotation_matrix +auto matrix = my_mesh.get_to_world_matrix(); +``` + +--- + +## Rotation Conventions + +Frostbite uses a right-handed Y-up coordinate system: + +1. **Pitch** (rotation around X-axis / right axis) + * Positive pitch looks upward (+Y direction) + * Range: typically [-89°, 89°] + +2. **Yaw** (rotation around Y-axis / up axis) + * Positive yaw rotates counterclockwise when viewed from above (right-handed) + * Range: [-180°, 180°] + +3. **Roll** (rotation around Z-axis / forward axis) + * Positive roll tilts right + * Range: [-180°, 180°] + +--- + +## Type Alias + +```cpp +namespace omath::frostbite_engine { + using Mesh = primitives::Mesh; +} +``` + +--- + +## See Also + +- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive +- [Formulas Documentation](formulas.md) - Frostbite rotation formula +- [CameraTrait Documentation](camera_trait.md) - Camera trait + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/engines/iw_engine/mesh_trait.md b/docs/engines/iw_engine/mesh_trait.md new file mode 100644 index 0000000..7f8b86a --- /dev/null +++ b/docs/engines/iw_engine/mesh_trait.md @@ -0,0 +1,119 @@ +# `omath::iw_engine::MeshTrait` — mesh transformation trait for IW Engine + +> Header: `omath/engines/iw_engine/traits/mesh_trait.hpp` +> Namespace: `omath::iw_engine` +> Purpose: provide IW Engine-specific rotation matrix computation for `omath::primitives::Mesh` + +--- + +## Summary + +`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in IW Engine's (Infinity Ward) coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior. + +--- + +## Coordinate System + +**IW Engine** (Call of Duty) uses: +* **Up axis**: +Z +* **Forward axis**: +Y +* **Right axis**: +X +* **Handedness**: Right-handed +* **Rotation order**: Pitch (X) → Yaw (Z) → Roll (Y) + +--- + +## API + +```cpp +namespace omath::iw_engine { + +class MeshTrait final { +public: + [[nodiscard]] + static Mat4X4 rotation_matrix(const ViewAngles& rotation); +}; + +} // namespace omath::iw_engine +``` + +--- + +## Method: `rotation_matrix` + +```cpp +static Mat4X4 rotation_matrix(const ViewAngles& rotation); +``` + +Computes a 4×4 rotation matrix from IW Engine-style Euler angles. + +**Parameters**: +* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles + +**Returns**: 4×4 rotation matrix suitable for mesh transformation + +**Implementation**: Delegates to `iw_engine::rotation_matrix(rotation)` defined in `formulas.hpp`. + +--- + +## Usage + +### With Mesh + +```cpp +using namespace omath::iw_engine; + +// Create mesh (MeshTrait is used automatically) +Mesh my_mesh(vertices, indices); + +// Set rotation using ViewAngles +ViewAngles angles; +angles.pitch = PitchAngle::from_degrees(30.0f); +angles.yaw = YawAngle::from_degrees(45.0f); +angles.roll = RollAngle::from_degrees(0.0f); + +my_mesh.set_rotation(angles); + +// The rotation matrix is computed using MeshTrait::rotation_matrix +auto matrix = my_mesh.get_to_world_matrix(); +``` + +--- + +## Rotation Conventions + +IW Engine uses a right-handed Z-up coordinate system (similar to Source Engine): + +1. **Pitch** (rotation around X-axis / right axis) + * Positive pitch looks upward (+Z direction) + * Range: typically [-89°, 89°] + +2. **Yaw** (rotation around Z-axis / up axis) + * Positive yaw rotates counterclockwise when viewed from above + * Range: [-180°, 180°] + +3. **Roll** (rotation around Y-axis / forward axis) + * Positive roll tilts right + * Range: [-180°, 180°] + +--- + +## Type Alias + +```cpp +namespace omath::iw_engine { + using Mesh = primitives::Mesh; +} +``` + +--- + +## See Also + +- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive +- [Formulas Documentation](formulas.md) - IW Engine rotation formula +- [CameraTrait Documentation](camera_trait.md) - Camera trait + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/engines/opengl_engine/mesh_trait.md b/docs/engines/opengl_engine/mesh_trait.md new file mode 100644 index 0000000..009e207 --- /dev/null +++ b/docs/engines/opengl_engine/mesh_trait.md @@ -0,0 +1,121 @@ +# `omath::opengl_engine::MeshTrait` — mesh transformation trait for OpenGL + +> Header: `omath/engines/opengl_engine/traits/mesh_trait.hpp` +> Namespace: `omath::opengl_engine` +> Purpose: provide OpenGL-specific rotation matrix computation for `omath::primitives::Mesh` + +--- + +## Summary + +`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in OpenGL's canonical coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior. + +--- + +## Coordinate System + +**OpenGL** (canonical) uses: +* **Up axis**: +Y +* **Forward axis**: +Z (toward viewer) +* **Right axis**: +X +* **Handedness**: Right-handed +* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z) + +--- + +## API + +```cpp +namespace omath::opengl_engine { + +class MeshTrait final { +public: + [[nodiscard]] + static Mat4X4 rotation_matrix(const ViewAngles& rotation); +}; + +} // namespace omath::opengl_engine +``` + +--- + +## Method: `rotation_matrix` + +```cpp +static Mat4X4 rotation_matrix(const ViewAngles& rotation); +``` + +Computes a 4×4 rotation matrix from OpenGL-style Euler angles. + +**Parameters**: +* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles + +**Returns**: 4×4 rotation matrix suitable for mesh transformation + +**Implementation**: Delegates to `opengl_engine::rotation_matrix(rotation)` defined in `formulas.hpp`. + +--- + +## Usage + +### With Mesh + +```cpp +using namespace omath::opengl_engine; + +// Create mesh (MeshTrait is used automatically) +Mesh my_mesh(vertices, indices); + +// Set rotation using ViewAngles +ViewAngles angles; +angles.pitch = PitchAngle::from_degrees(30.0f); +angles.yaw = YawAngle::from_degrees(45.0f); +angles.roll = RollAngle::from_degrees(0.0f); + +my_mesh.set_rotation(angles); + +// The rotation matrix is computed using MeshTrait::rotation_matrix +auto matrix = my_mesh.get_to_world_matrix(); +``` + +--- + +## Rotation Conventions + +OpenGL uses a right-handed Y-up coordinate system: + +1. **Pitch** (rotation around X-axis / right axis) + * Positive pitch looks upward (+Y direction) + * Range: typically [-89°, 89°] + +2. **Yaw** (rotation around Y-axis / up axis) + * Positive yaw rotates counterclockwise when viewed from above (right-handed) + * Range: [-180°, 180°] + +3. **Roll** (rotation around Z-axis / depth axis) + * Positive roll tilts right + * Range: [-180°, 180°] + +**Note**: In OpenGL, +Z points toward the viewer in view space, but away from the viewer in world space. + +--- + +## Type Alias + +```cpp +namespace omath::opengl_engine { + using Mesh = primitives::Mesh; +} +``` + +--- + +## See Also + +- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive +- [Formulas Documentation](formulas.md) - OpenGL rotation formula +- [CameraTrait Documentation](camera_trait.md) - Camera trait + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/engines/source_engine/mesh_trait.md b/docs/engines/source_engine/mesh_trait.md new file mode 100644 index 0000000..e866dbc --- /dev/null +++ b/docs/engines/source_engine/mesh_trait.md @@ -0,0 +1,182 @@ +# `omath::source_engine::MeshTrait` — mesh transformation trait for Source Engine + +> Header: `omath/engines/source_engine/traits/mesh_trait.hpp` +> Namespace: `omath::source_engine` +> Purpose: provide Source Engine-specific rotation matrix computation for `omath::primitives::Mesh` + +--- + +## Summary + +`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Source Engine's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior. + +--- + +## Coordinate System + +**Source Engine** uses: +* **Up axis**: +Z +* **Forward axis**: +Y +* **Right axis**: +X +* **Handedness**: Right-handed +* **Rotation order**: Pitch (X) → Yaw (Z) → Roll (Y) + +--- + +## API + +```cpp +namespace omath::source_engine { + +class MeshTrait final { +public: + [[nodiscard]] + static Mat4X4 rotation_matrix(const ViewAngles& rotation); +}; + +} // namespace omath::source_engine +``` + +--- + +## Method: `rotation_matrix` + +```cpp +static Mat4X4 rotation_matrix(const ViewAngles& rotation); +``` + +Computes a 4×4 rotation matrix from Source Engine-style Euler angles. + +**Parameters**: +* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles + +**Returns**: 4×4 rotation matrix suitable for mesh transformation + +**Implementation**: Delegates to `source_engine::rotation_matrix(rotation)` defined in `formulas.hpp`. + +--- + +## Usage + +### With Mesh + +```cpp +using namespace omath::source_engine; + +// Create mesh (MeshTrait is used automatically) +Mesh my_mesh(vertices, indices); + +// Set rotation using ViewAngles +ViewAngles angles; +angles.pitch = PitchAngle::from_degrees(30.0f); +angles.yaw = YawAngle::from_degrees(45.0f); +angles.roll = RollAngle::from_degrees(0.0f); + +my_mesh.set_rotation(angles); + +// The rotation matrix is computed using MeshTrait::rotation_matrix +auto matrix = my_mesh.get_to_world_matrix(); +``` + +### Direct Usage + +```cpp +using namespace omath::source_engine; + +ViewAngles angles; +angles.pitch = PitchAngle::from_degrees(45.0f); +angles.yaw = YawAngle::from_degrees(90.0f); +angles.roll = RollAngle::from_degrees(0.0f); + +Mat4X4 rot_matrix = MeshTrait::rotation_matrix(angles); + +// Use the matrix directly +Vector3 local_point{1, 0, 0}; +auto rotated = rot_matrix * mat_column_from_vector(local_point); +``` + +--- + +## Rotation Conventions + +The rotation matrix is built following Source Engine's conventions: + +1. **Pitch** (rotation around X-axis / right axis) + * Positive pitch looks upward (+Z direction) + * Range: typically [-89°, 89°] + +2. **Yaw** (rotation around Z-axis / up axis) + * Positive yaw rotates counterclockwise when viewed from above + * Range: [-180°, 180°] + +3. **Roll** (rotation around Y-axis / forward axis) + * Positive roll tilts right + * Range: [-180°, 180°] + +**Composition**: The matrices are combined in the order Pitch × Yaw × Roll, producing a rotation that: +* First applies roll around the forward axis +* Then applies yaw around the up axis +* Finally applies pitch around the right axis + +This matches Source Engine's internal rotation order. + +--- + +## Related Functions + +The trait delegates to the formula defined in `formulas.hpp`: + +```cpp +[[nodiscard]] +Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; +``` + +See [Formulas Documentation](formulas.md) for details on the rotation matrix computation. + +--- + +## Type Alias + +The Source Engine mesh type is pre-defined: + +```cpp +namespace omath::source_engine { + using Mesh = primitives::Mesh; +} +``` + +Use this alias to ensure correct trait usage: + +```cpp +using namespace omath::source_engine; + +// Correct: uses Source Engine trait +Mesh my_mesh(vbo, vao); + +// Avoid: manually specifying template parameters +primitives::Mesh verbose_mesh(vbo, vao); +``` + +--- + +## Notes + +* **Angle ranges**: Ensure angles are within valid ranges (pitch: [-89°, 89°], yaw/roll: [-180°, 180°]) +* **Performance**: Matrix computation is O(1) with ~64 floating-point operations +* **Caching**: The mesh caches the transformation matrix; recomputed only when rotation changes +* **Compatibility**: Works with all Source Engine games (CS:GO, TF2, Portal, Half-Life 2, etc.) + +--- + +## See Also + +- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive using this trait +- [MeshCollider Documentation](../../collision/mesh_collider.md) - Collision wrapper for meshes +- [Formulas Documentation](formulas.md) - Source Engine rotation formula +- [CameraTrait Documentation](camera_trait.md) - Camera transformation trait +- [Constants Documentation](constants.md) - Source Engine constants +- [API Overview](../../api_overview.md) - High-level API reference + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/engines/unity_engine/mesh_trait.md b/docs/engines/unity_engine/mesh_trait.md new file mode 100644 index 0000000..3886ac7 --- /dev/null +++ b/docs/engines/unity_engine/mesh_trait.md @@ -0,0 +1,119 @@ +# `omath::unity_engine::MeshTrait` — mesh transformation trait for Unity Engine + +> Header: `omath/engines/unity_engine/traits/mesh_trait.hpp` +> Namespace: `omath::unity_engine` +> Purpose: provide Unity Engine-specific rotation matrix computation for `omath::primitives::Mesh` + +--- + +## Summary + +`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Unity's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior. + +--- + +## Coordinate System + +**Unity Engine** uses: +* **Up axis**: +Y +* **Forward axis**: +Z +* **Right axis**: +X +* **Handedness**: Left-handed +* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z) + +--- + +## API + +```cpp +namespace omath::unity_engine { + +class MeshTrait final { +public: + [[nodiscard]] + static Mat4X4 rotation_matrix(const ViewAngles& rotation); +}; + +} // namespace omath::unity_engine +``` + +--- + +## Method: `rotation_matrix` + +```cpp +static Mat4X4 rotation_matrix(const ViewAngles& rotation); +``` + +Computes a 4×4 rotation matrix from Unity-style Euler angles. + +**Parameters**: +* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles + +**Returns**: 4×4 rotation matrix suitable for mesh transformation + +**Implementation**: Delegates to `unity_engine::rotation_matrix(rotation)` defined in `formulas.hpp`. + +--- + +## Usage + +### With Mesh + +```cpp +using namespace omath::unity_engine; + +// Create mesh (MeshTrait is used automatically) +Mesh my_mesh(vertices, indices); + +// Set rotation using ViewAngles +ViewAngles angles; +angles.pitch = PitchAngle::from_degrees(30.0f); +angles.yaw = YawAngle::from_degrees(45.0f); +angles.roll = RollAngle::from_degrees(0.0f); + +my_mesh.set_rotation(angles); + +// The rotation matrix is computed using MeshTrait::rotation_matrix +auto matrix = my_mesh.get_to_world_matrix(); +``` + +--- + +## Rotation Conventions + +Unity uses a left-handed coordinate system with Y-up: + +1. **Pitch** (rotation around X-axis / right axis) + * Positive pitch looks upward (+Y direction) + * Range: typically [-89°, 89°] + +2. **Yaw** (rotation around Y-axis / up axis) + * Positive yaw rotates clockwise when viewed from above (left-handed) + * Range: [-180°, 180°] + +3. **Roll** (rotation around Z-axis / forward axis) + * Positive roll tilts right + * Range: [-180°, 180°] + +--- + +## Type Alias + +```cpp +namespace omath::unity_engine { + using Mesh = primitives::Mesh; +} +``` + +--- + +## See Also + +- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive +- [Formulas Documentation](formulas.md) - Unity rotation formula +- [CameraTrait Documentation](camera_trait.md) - Camera trait + +--- + +*Last updated: 13 Nov 2025* diff --git a/docs/engines/unreal_engine/mesh_trait.md b/docs/engines/unreal_engine/mesh_trait.md new file mode 100644 index 0000000..7bbbbec --- /dev/null +++ b/docs/engines/unreal_engine/mesh_trait.md @@ -0,0 +1,121 @@ +# `omath::unreal_engine::MeshTrait` — mesh transformation trait for Unreal Engine + +> Header: `omath/engines/unreal_engine/traits/mesh_trait.hpp` +> Namespace: `omath::unreal_engine` +> Purpose: provide Unreal Engine-specific rotation matrix computation for `omath::primitives::Mesh` + +--- + +## Summary + +`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Unreal Engine's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior. + +--- + +## Coordinate System + +**Unreal Engine** uses: +* **Up axis**: +Z +* **Forward axis**: +X +* **Right axis**: +Y +* **Handedness**: Left-handed +* **Rotation order**: Roll (Y) → Pitch (X) → Yaw (Z) + +--- + +## API + +```cpp +namespace omath::unreal_engine { + +class MeshTrait final { +public: + [[nodiscard]] + static Mat4X4 rotation_matrix(const ViewAngles& rotation); +}; + +} // namespace omath::unreal_engine +``` + +--- + +## Method: `rotation_matrix` + +```cpp +static Mat4X4 rotation_matrix(const ViewAngles& rotation); +``` + +Computes a 4×4 rotation matrix from Unreal-style Euler angles. + +**Parameters**: +* `rotation` — `ViewAngles` containing pitch, yaw, and roll angles + +**Returns**: 4×4 rotation matrix suitable for mesh transformation + +**Implementation**: Delegates to `unreal_engine::rotation_matrix(rotation)` defined in `formulas.hpp`. + +--- + +## Usage + +### With Mesh + +```cpp +using namespace omath::unreal_engine; + +// Create mesh (MeshTrait is used automatically) +Mesh my_mesh(vertices, indices); + +// Set rotation using ViewAngles +ViewAngles angles; +angles.pitch = PitchAngle::from_degrees(30.0f); +angles.yaw = YawAngle::from_degrees(45.0f); +angles.roll = RollAngle::from_degrees(0.0f); + +my_mesh.set_rotation(angles); + +// The rotation matrix is computed using MeshTrait::rotation_matrix +auto matrix = my_mesh.get_to_world_matrix(); +``` + +--- + +## Rotation Conventions + +Unreal uses a left-handed Z-up coordinate system: + +1. **Roll** (rotation around Y-axis / right axis) + * Positive roll rotates forward axis upward + * Range: [-180°, 180°] + +2. **Pitch** (rotation around X-axis / forward axis) + * Positive pitch looks upward + * Range: typically [-89°, 89°] + +3. **Yaw** (rotation around Z-axis / up axis) + * Positive yaw rotates clockwise when viewed from above (left-handed) + * Range: [-180°, 180°] + +**Note**: Unreal applies rotations in Roll-Pitch-Yaw order, different from most other engines. + +--- + +## Type Alias + +```cpp +namespace omath::unreal_engine { + using Mesh = primitives::Mesh; +} +``` + +--- + +## See Also + +- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive +- [Formulas Documentation](formulas.md) - Unreal rotation formula +- [CameraTrait Documentation](camera_trait.md) - Camera trait + +--- + +*Last updated: 13 Nov 2025*