Skip to content
2 changes: 1 addition & 1 deletion include/omath/3d_primitives/mesh.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace omath::primitives
Vao m_vertex_array_object;

Mesh(Vbo vbo, Vao vao, const Vector3<NumericType> scale = {1, 1, 1,})
: m_vertex_buffer(std::move(vbo)), m_vertex_array_object(std::move(vao)), m_scale(scale)
: m_vertex_buffer(std::move(vbo)), m_vertex_array_object(std::move(vao)), m_scale(std::move(scale))
{
}
void set_origin(const Vector3<NumericType>& new_origin)
Expand Down
261 changes: 261 additions & 0 deletions include/omath/collision/epa_algorithm.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
#pragma once
#include "simplex.hpp"
#include <algorithm> // find_if
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <queue>
#include <vector>

namespace omath::collision
{
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>;
};

template<class ColliderType>
class Epa final
{
public:
using Vertex = typename ColliderType::VertexType;
static_assert(EpaVector<Vertex>, "VertexType must satisfy EpaVector concept");

struct Result final
{
bool success{false};
Vertex normal{}; // outward normal (from B to A)
float depth{0.0f};
int iterations{0};
int num_vertices{0};
int num_faces{0};
};

struct Params final
{
int max_iterations{64};
float tolerance{1e-4f}; // absolute tolerance on distance growth
};

// Precondition: simplex.size()==4 and contains the origin.
[[nodiscard]]
static Result solve(const ColliderType& a, const ColliderType& b, const Simplex<Vertex>& simplex,
const Params params = {})
{
// --- Build initial polytope from simplex (4 points) ---
std::vector<Vertex> vertexes;
vertexes.reserve(64);
for (std::size_t i = 0; i < simplex.size(); ++i)
vertexes.push_back(simplex[i]);

// Initial tetra faces (windings corrected in make_face)
std::vector<Face> faces;
faces.reserve(128);
faces.emplace_back(make_face(vertexes, 0, 1, 2));
faces.emplace_back(make_face(vertexes, 0, 2, 3));
faces.emplace_back(make_face(vertexes, 0, 3, 1));
faces.emplace_back(make_face(vertexes, 1, 3, 2));

auto heap = rebuild_heap(faces);

Result out{};

for (int it = 0; it < params.max_iterations; ++it)
{
// If heap might be stale after face edits, rebuild lazily.
if (heap.empty())
break;
// Rebuild when the "closest" face changed (simple cheap guard)
// (We could keep face handles; this is fine for small Ns.)

if (const auto top = heap.top(); faces[top.idx].d != top.d)
heap = rebuild_heap(faces);

if (heap.empty())
break;

const int fidx = heap.top().idx;
const Face f = faces[fidx];

// Get farthest point in face normal direction
const Vertex p = support_point(a, b, f.n);
const float p_dist = f.n.dot(p);

// Converged if we can’t push the face closer than tolerance
if (p_dist - f.d <= params.tolerance)
{
out.success = true;
out.normal = f.n;
out.depth = f.d; // along unit normal
out.iterations = it + 1;
out.num_vertices = static_cast<int>(vertexes.size());
out.num_faces = static_cast<int>(faces.size());
return out;
}

// Add new vertex
const int new_idx = static_cast<int>(vertexes.size());
vertexes.push_back(p);

// Mark faces visible from p and collect their horizon
std::vector<char> to_delete(faces.size(), 0);
std::vector<Edge> boundary;
boundary.reserve(faces.size() * 2);

for (int i = 0; i < static_cast<int>(faces.size()); ++i)
{
if (to_delete[i])
continue;
if (visible_from(faces[i], p))
{
const auto& rf = faces[i];
to_delete[i] = 1;
add_edge_boundary(boundary, rf.i0, rf.i1);
add_edge_boundary(boundary, rf.i1, rf.i2);
add_edge_boundary(boundary, rf.i2, rf.i0);
}
}

// Remove visible faces
std::vector<Face> new_faces;
new_faces.reserve(faces.size() + boundary.size());
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
if (!to_delete[i])
new_faces.push_back(faces[i]);
faces.swap(new_faces);

// Stitch new faces around the horizon
for (const auto& e : boundary)
faces.push_back(make_face(vertexes, e.a, e.b, new_idx));

// Rebuild heap after topology change
heap = rebuild_heap(faces);

if (!std::isfinite(vertexes.back().dot(vertexes.back())))
break; // safety
out.iterations = it + 1;
}

// Fallback: pick closest face as best-effort answer
if (!faces.empty())
{
auto best = faces[0];
for (const auto& f : faces)
if (f.d < best.d)
best = f;
out.success = true;
out.normal = best.n;
out.depth = best.d;
out.num_vertices = static_cast<int>(vertexes.size());
out.num_faces = static_cast<int>(faces.size());
}
return out;
}

private:
struct Face final
{
int i0, i1, i2;
Vertex n; // unit outward normal
float d; // n · v0 (>=0 ideally because origin is inside)
};

struct Edge final
{
int a, b;
};

struct HeapItem final
{
float d;
int idx;
};
struct HeapCmp final
{
bool operator()(const HeapItem& lhs, const HeapItem& rhs) const noexcept
{
return lhs.d > rhs.d; // min-heap by distance
}
};
using Heap = std::priority_queue<HeapItem, std::vector<HeapItem>, HeapCmp>;

[[nodiscard]]
static Heap rebuild_heap(const std::vector<Face>& faces)
{
Heap h;
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
h.push({faces[i].d, i});
return h;
}

[[nodiscard]]
static bool visible_from(const Face& f, const Vertex& p)
{
// positive if p is in front of the face
return (f.n.dot(p) - f.d) > 1e-7f;
}

static void add_edge_boundary(std::vector<Edge>& boundary, int a, int b)
{
// Keep edges that appear only once; erase if opposite already present
auto itb =
std::find_if(boundary.begin(), boundary.end(), [&](const Edge& e) { return e.a == b && e.b == a; });
if (itb != boundary.end())
boundary.erase(itb); // internal edge cancels out
else
boundary.push_back({a, b}); // horizon edge (directed)
}

[[nodiscard]]
static Face make_face(const std::vector<Vertex>& vertexes, int i0, int i1, int i2)
{
const Vertex& a0 = vertexes[i0];
const Vertex& a1 = vertexes[i1];
const Vertex& a2 = vertexes[i2];
Vertex n = (a1 - a0).cross(a2 - a0);
if (n.dot(n) <= 1e-30f)
{
n = any_perp_vec(a1 - a0); // degenerate guard
}
// Ensure normal points outward (away from origin): require n·a0 >= 0
if (n.dot(a0) < 0.0f)
{
std::swap(i1, i2);
n = -n;
}
const float inv_len = 1.0f / std::sqrt(std::max(n.dot(n), 1e-30f));
n = n * inv_len;
const float d = n.dot(a0);
return {i0, i1, i2, n, d};
}

[[nodiscard]]
static Vertex support_point(const ColliderType& a, const ColliderType& b, const Vertex& dir)
{
return a.find_abs_furthest_vertex(dir) - b.find_abs_furthest_vertex(-dir);
}

template<class V>
[[nodiscard]]
static constexpr bool near_zero_vec(const V& v, const float eps = 1e-7f)
{
return v.dot(v) <= eps * eps;
}

template<class V>
[[nodiscard]]
static constexpr V any_perp_vec(const V& v)
{
for (const auto& dir : {V{1, 0, 0}, V{0, 1, 0}, V{0, 0, 1}})
if (const auto d = v.cross(dir); !near_zero_vec(d))
return d;
return V{1, 0, 0};
}
};
} // namespace omath::collision
19 changes: 16 additions & 3 deletions include/omath/collision/gjk_algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@

namespace omath::collision
{
template<class VertexType>
struct GjkHitInfo final
{
bool hit{false};
Simplex<VertexType> simplex; // valid only if hit == true and size==4
};

template<class ColliderType>
class GjkAlgorithm final
{
Expand All @@ -23,7 +30,13 @@ namespace omath::collision
[[nodiscard]]
static bool is_collide(const ColliderType& collider_a, const ColliderType& collider_b)
{
// Get initial support point in any direction
return is_collide_with_simplex_info(collider_a, collider_b).hit;
}

[[nodiscard]]
static GjkHitInfo<VertexType> is_collide_with_simplex_info(const ColliderType& collider_a,
const ColliderType& collider_b)
{
auto support = find_support_vertex(collider_a, collider_b, {1, 0, 0});

Simplex<VertexType> simplex;
Expand All @@ -36,12 +49,12 @@ namespace omath::collision
support = find_support_vertex(collider_a, collider_b, direction);

if (support.dot(direction) <= 0.f)
return false;
return {false, simplex};

simplex.push_front(support);

if (simplex.handle(direction))
return true;
return {true, simplex};
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions include/omath/collision/mesh_collider.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ namespace omath::collision
}

[[nodiscard]]
const Vector3<float>& find_furthest_vertex(const Vector3<float>& direction) const
const VertexType& find_furthest_vertex(const VertexType& direction) const
{
return *std::ranges::max_element(m_mesh.m_vertex_buffer, [&direction](const auto& first, const auto& second)
{ return first.dot(direction) < second.dot(direction); });
}

[[nodiscard]]
Vector3<float> find_abs_furthest_vertex(const Vector3<float>& direction) const
VertexType find_abs_furthest_vertex(const VertexType& direction) const
{
return m_mesh.vertex_to_world_space(find_furthest_vertex(direction));
}
Expand Down
20 changes: 10 additions & 10 deletions include/omath/linear_algebra/triangle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ namespace omath
{
}

Vector3<float> m_vertex1;
Vector3<float> m_vertex2;
Vector3<float> m_vertex3;
Vector m_vertex1;
Vector m_vertex2;
Vector m_vertex3;

[[nodiscard]]
constexpr Vector3<float> calculate_normal() const
constexpr Vector calculate_normal() const
{
const auto b = side_b_vector();
const auto a = side_a_vector();
Expand All @@ -40,25 +40,25 @@ namespace omath
}

[[nodiscard]]
float side_a_length() const
Vector::ContainedType side_a_length() const
{
return m_vertex1.distance_to(m_vertex2);
}

[[nodiscard]]
float side_b_length() const
Vector::ContainedType side_b_length() const
{
return m_vertex3.distance_to(m_vertex2);
}

[[nodiscard]]
constexpr Vector3<float> side_a_vector() const
constexpr Vector side_a_vector() const
{
return m_vertex1 - m_vertex2;
}

[[nodiscard]]
constexpr float hypot() const
constexpr Vector::ContainedType hypot() const
{
return m_vertex1.distance_to(m_vertex3);
}
Expand All @@ -72,12 +72,12 @@ namespace omath
return std::abs(side_a * side_a + side_b * side_b - hypot_value * hypot_value) <= 0.0001f;
}
[[nodiscard]]
constexpr Vector3<float> side_b_vector() const
constexpr Vector side_b_vector() const
{
return m_vertex3 - m_vertex2;
}
[[nodiscard]]
constexpr Vector3<float> mid_point() const
constexpr Vector mid_point() const
{
return (m_vertex1 + m_vertex2 + m_vertex3) / 3;
}
Expand Down
Loading
Loading