Add documentation for collision detection and mesh classes

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-11-13 15:19:36 +00:00
parent d118e88f6b
commit 190a8bf91e
11 changed files with 2482 additions and 0 deletions

465
docs/3d_primitives/mesh.md Normal file
View File

@@ -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<T>`, `omath::Mat4X4`, `omath::Triangle<Vector3<T>>`
> 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 Mat4X4, class RotationAngles, class MeshTypeTrait, class Type = float>
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<Mat4X4, ViewAngles, MeshTrait, float>;
// Unity Engine
using Mesh = omath::primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
// Unreal Engine
using Mesh = omath::primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
// 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<Vector3<NumericType>> m_vertex_buffer; // VBO: vertex positions
std::vector<Vector3<std::size_t>> 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<Vector3<NumericType>> vbo,
std::vector<Vector3<std::size_t>> vao,
Vector3<NumericType> 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<Vector3<float>> vertices = {
{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}
};
std::vector<Vector3<std::size_t>> 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<NumericType>& new_origin);
void set_scale(const Vector3<NumericType>& 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<NumericType>& get_origin() const;
[[nodiscard]] const Vector3<NumericType>& 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<float> vertex_to_world_space(const Vector3<float>& 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<float> local_vertex{1, 0, 0};
Vector3<float> 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<Vector3<float>> make_face_in_world_space(
const std::vector<Vector3<std::size_t>>::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<Vector3<float>> 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<Vector3<float>> 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<Vector3<std::size_t>> 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<MeshCollider<Mesh>>::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<Vector3<float>> 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*

View File

@@ -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<VertexType>`, 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 ColliderType>
class Epa final {
public:
using Vertex = typename ColliderType::VertexType;
static_assert(EpaVector<Vertex>, "VertexType must satisfy EpaVector concept");
// Solve for penetration depth and normal
[[nodiscard]]
static Result solve(
const ColliderType& a,
const ColliderType& b,
const Simplex<Vertex>& 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<float>)
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<class V>
concept EpaVector = requires(const V& a, const V& b, float s) {
{ a - b } -> std::same_as<V>;
{ a.cross(b) } -> std::same_as<V>;
{ a.dot(b) } -> std::same_as<float>;
{ -a } -> std::same_as<V>;
{ a * s } -> std::same_as<V>;
{ a / s } -> std::same_as<V>;
};
```
`omath::Vector3<float>` 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<Mesh> collider_a(mesh_a);
MeshCollider<Mesh> collider_b(mesh_b);
auto gjk_result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
collider_a,
collider_b
);
if (gjk_result.hit) {
// Collision detected, use EPA to get penetration info
auto epa_result = Epa<MeshCollider<Mesh>>::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<float> correction = epa_result.normal * epa_result.depth;
mesh_a.set_origin(mesh_a.get_origin() + correction);
}
}
```
### Custom Parameters
```cpp
// Use custom convergence settings
Epa<Collider>::Params params;
params.max_iterations = 128; // Allow more iterations for complex shapes
params.tolerance = 1e-5f; // Tighter tolerance for more accuracy
auto result = Epa<Collider>::solve(a, b, simplex, params);
```
### Physics Integration
```cpp
void resolve_collision(PhysicsBody& body_a, PhysicsBody& body_b) {
auto gjk_result = GjkAlgorithm<Collider>::check_collision(
body_a.collider, body_b.collider
);
if (!gjk_result.hit)
return; // No collision
auto epa_result = Epa<Collider>::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<Collider>::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*

View File

@@ -0,0 +1,216 @@
# `omath::collision::GjkAlgorithm` — Gilbert-Johnson-Keerthi collision detection
> Header: `omath/collision/gjk_algorithm.hpp`
> Namespace: `omath::collision`
> Depends on: `Simplex<VertexType>`, 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<class VertexType>
struct GjkHitInfo final {
bool hit{false}; // true if collision detected
Simplex<VertexType> 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 ColliderType>
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<VertexType> 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<float>)
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<MeshType>` — 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<MeshCollider<Mesh>>::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<Collider>::check_collision(a, b);
if (gjk_result.hit) {
// Get penetration depth and normal using EPA
auto epa_result = Epa<Collider>::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<class V>
concept GjkVector = requires(const V& a, const V& b) {
{ -a } -> std::same_as<V>;
{ a - b } -> std::same_as<V>;
{ a.cross(b) } -> std::same_as<V>;
{ a.point_to_same_direction(b) } -> std::same_as<bool>;
};
```
`omath::Vector3<float>` 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*

View File

@@ -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<T>`
> 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 MeshType>
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<Vector3<NumericType>> m_vertex_buffer;
// Transform vertex from local to world space
Vector3<NumericType> vertex_to_world_space(const Vector3<NumericType>&) 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>;
```
* `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<Vector3<float>> 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<Vector3<std::size_t>> 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<Mesh> collider_a(std::move(mesh_a));
MeshCollider<Mesh> collider_b(std::move(mesh_b));
// Run GJK
auto result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
collider_a, collider_b
);
if (result.hit) {
std::cout << "Collision detected!\n";
}
```
### With EPA for Penetration Depth
```cpp
auto gjk_result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
collider_a, collider_b
);
if (gjk_result.hit) {
auto epa_result = Epa<MeshCollider<Mesh>>::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<Vector3<float>> 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<Vector3<std::size_t>> 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<Mesh> 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<Mesh> 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<Mesh> 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<float>;
Vector3<float> 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<SphereCollider>::check_collision(sphere_a, sphere_b);
```
### Debugging Support Queries
```cpp
class DebugMeshCollider : public MeshCollider<Mesh> {
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*

327
docs/collision/simplex.md Normal file
View File

@@ -0,0 +1,327 @@
# `omath::collision::Simplex` — Fixed-capacity simplex for GJK/EPA
> Header: `omath/collision/simplex.hpp`
> Namespace: `omath::collision`
> Depends on: `Vector3<float>` (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<GjkVector VectorType = Vector3<float>>
class Simplex final;
```
### `GjkVector` Concept
The vertex type must satisfy:
```cpp
template<class V>
concept GjkVector = requires(const V& a, const V& b) {
{ -a } -> std::same_as<V>;
{ a - b } -> std::same_as<V>;
{ a.cross(b) } -> std::same_as<V>;
{ a.point_to_same_direction(b) } -> std::same_as<bool>;
};
```
`omath::Vector3<float>` satisfies this concept and is the default.
---
## Constructors & Assignment
```cpp
constexpr Simplex() = default;
constexpr Simplex& operator=(std::initializer_list<VectorType> list) noexcept;
```
### Initialization
```cpp
// Empty simplex
Simplex<Vector3<float>> s;
// Initialize with points
Simplex<Vector3<float>> 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<Vector3<float>> simplex;
Vector3<float> 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<float>;
Simplex<Vec3> 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<Vector3<float>>& 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*

View File

@@ -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<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## 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*

View File

@@ -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<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## 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*

View File

@@ -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<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## 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*

View File

@@ -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<float> 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<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
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<Mat4X4, ViewAngles, MeshTrait, float> 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*

View File

@@ -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<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## 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*

View File

@@ -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<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## 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*