#pragma once #include "omath/linear_algebra/vector3.hpp" #include #include #include #include namespace omath::collision { // Minimal structural contract for the vector type used by GJK. 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; }; template> class Simplex final { std::array m_points{}; std::size_t m_size{0}; public: static constexpr std::size_t capacity = 4; constexpr Simplex() = default; constexpr Simplex& operator=(std::initializer_list list) noexcept { assert(list.size() <= capacity && "Simplex can have at most 4 points"); m_size = 0; for (const auto& p : list) m_points[m_size++] = p; return *this; } constexpr void push_front(const VectorType& p) noexcept { const std::size_t limit = (m_size < capacity) ? m_size : capacity - 1; for (std::size_t i = limit; i > 0; --i) m_points[i] = m_points[i - 1]; m_points[0] = p; if (m_size < capacity) ++m_size; } [[nodiscard]] constexpr const VectorType& operator[](std::size_t i) const noexcept { return m_points[i]; } [[nodiscard]] constexpr VectorType& operator[](std::size_t i) noexcept { return m_points[i]; } [[nodiscard]] constexpr std::size_t size() const noexcept { return m_size; } [[nodiscard]] constexpr bool empty() const noexcept { return m_size == 0; } [[nodiscard]] constexpr const VectorType& front() const noexcept { return m_points[0]; } [[nodiscard]] constexpr const VectorType& back() const noexcept { return m_points[m_size - 1]; } [[nodiscard]] constexpr const VectorType* data() const noexcept { return m_points.data(); } [[nodiscard]] constexpr auto begin() const noexcept { return m_points.begin(); } [[nodiscard]] constexpr auto end() const noexcept { return m_points.begin() + m_size; } constexpr void clear() noexcept { m_size = 0; } // GJK step: updates simplex + next search direction. // Returns true iff the origin lies inside the tetrahedron. [[nodiscard]] constexpr bool handle(VectorType& direction) noexcept { switch (m_size) { case 0: return false; case 1: return handle_point(direction); case 2: return handle_line(direction); case 3: return handle_triangle(direction); case 4: return handle_tetrahedron(direction); default: std::unreachable(); } } private: [[nodiscard]] constexpr bool handle_point(VectorType& direction) noexcept { const auto& a = m_points[0]; direction = -a; return false; } template [[nodiscard]] static constexpr bool near_zero(const V& v, const float eps = 1e-7f) noexcept { return v.dot(v) <= eps * eps; } template [[nodiscard]] static constexpr V any_perp(const V& v) { for (const auto& dir : {V{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}) if (const auto d = v.cross(dir); !near_zero(d)) return d; std::unreachable(); } [[nodiscard]] constexpr bool handle_line(VectorType& direction) noexcept { const auto& a = m_points[0]; const auto& b = m_points[1]; const auto ab = b - a; const auto ao = -a; if (ab.point_to_same_direction(ao)) { // ReSharper disable once CppTooWideScopeInitStatement auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos direction = near_zero(n) ? any_perp(ab) : n.cross(ab); return false; } *this = {a}; direction = ao; return false; } [[nodiscard]] constexpr bool handle_triangle(VectorType& direction) noexcept { const auto& a = m_points[0]; const auto& b = m_points[1]; const auto& c = m_points[2]; const auto ab = b - a; const auto ac = c - a; const auto ao = -a; const auto abc = ab.cross(ac); // Region AC if (abc.cross(ac).point_to_same_direction(ao)) { if (ac.point_to_same_direction(ao)) { *this = {a, c}; direction = ac.cross(ao).cross(ac); return false; } *this = {a, b}; return handle_line(direction); } // Region AB if (ab.cross(abc).point_to_same_direction(ao)) { *this = {a, b}; return handle_line(direction); } // Above or below triangle if (abc.point_to_same_direction(ao)) { direction = abc; } else { *this = {a, c, b}; // flip winding direction = -abc; } return false; } [[nodiscard]] constexpr bool handle_tetrahedron(VectorType& direction) noexcept { const auto& a = m_points[0]; const auto& b = m_points[1]; const auto& c = m_points[2]; const auto& d = m_points[3]; const auto ab = b - a; const auto ac = c - a; const auto ad = d - a; const auto ao = -a; const auto abc = ab.cross(ac); const auto acd = ac.cross(ad); const auto adb = ad.cross(ab); if (abc.point_to_same_direction(ao)) { *this = {a, b, c}; return handle_triangle(direction); } if (acd.point_to_same_direction(ao)) { *this = {a, c, d}; return handle_triangle(direction); } if (adb.point_to_same_direction(ao)) { *this = {a, d, b}; return handle_triangle(direction); } // Origin inside tetrahedron return true; } }; } // namespace omath::collision