# `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*