LCOV - code coverage report
Current view: top level - rbc/gjk - ContactManifoldGenerator.hpp (source / functions) Coverage Total Hit
Test: coverage_clean.info Lines: 0.0 % 192 0
Test Date: 2026-03-30 03:13:12 Functions: 0.0 % 6 0

            Line data    Source code
       1              : #pragma once
       2              : // ============================================================================
       3              : //  ContactManifoldGenerator.hpp
       4              : //
       5              : //  Given an EPA result (one point, one normal, one depth) this generates a
       6              : //  proper 1-4 point manifold by:
       7              : //
       8              : //   1. Finding the REFERENCE face (most aligned with normal) on shape A.
       9              : //   2. Finding the INCIDENT face (most anti-aligned) on shape B.
      10              : //   3. Clipping the incident face polygon against the reference face's side
      11              : //      planes (Sutherland-Hodgman), then depth-testing against the reference
      12              : //      face plane.
      13              : //   4. Reducing to ≤4 contact points.
      14              : //
      15              : //  For primitive pairs that have an analytic fast-path (SphereSphere,
      16              : //  SphereBox, etc.) the analytic algorithm should fill the manifold directly
      17              : //  and not call this generator.
      18              : //
      19              : //  This is used by the GJK/EPA *fallback* path in CollisionAlgorithm<A,B>.
      20              : // ============================================================================
      21              : 
      22              : #include "rbc/Contact.hpp"
      23              : #include "rbc/gjk/GJK.hpp"
      24              : #include "rbc/gjk/EPA.hpp"
      25              : #include "rbc/gjk/MinkowskiDiff.hpp"
      26              : #include "rbc/shapes/ShapeTypes.hpp"
      27              : #include <cstring>
      28              : #include <algorithm>
      29              : 
      30              : namespace rbc
      31              : {
      32              : 
      33              :     // ---------------------------------------------------------------------------
      34              :     //  Sutherland-Hodgman clip (shared with BoxBox.hpp — defined here as inline
      35              :     //  so there is no ODR violation; BoxBox.hpp has its own copy in detail::)
      36              :     // ---------------------------------------------------------------------------
      37              : 
      38              :     namespace manifold_detail
      39              :     {
      40              : 
      41            0 :         inline int clip_polygon_by_plane(const m3d::vec3 *poly, int n,
      42              :                                          const m3d::vec3 &plane_n,
      43              :                                          const m3d::vec3 &plane_p,
      44              :                                          m3d::vec3 *out)
      45              :         {
      46            0 :             int out_n = 0;
      47            0 :             for (int i = 0; i < n; ++i)
      48              :             {
      49            0 :                 const m3d::vec3 &A = poly[i];
      50            0 :                 const m3d::vec3 &B = poly[(i + 1) % n];
      51              : 
      52            0 :                 const m3d::scalar dA = m3d::dot(A - plane_p, plane_n);
      53            0 :                 const m3d::scalar dB = m3d::dot(B - plane_p, plane_n);
      54              : 
      55            0 :                 if (dA >= 0.0)
      56            0 :                     out[out_n++] = A;
      57              : 
      58            0 :                 if ((dA > 0.0) != (dB > 0.0))
      59              :                 {
      60            0 :                     const m3d::scalar t = dA / (dA - dB);
      61            0 :                     out[out_n++] = A + (B - A) * t;
      62              :                 }
      63              :             }
      64            0 :             return out_n;
      65              :         }
      66              : 
      67              :         // Reduce n contact candidates to ≤4 by maximising the support area.
      68            0 :         inline int reduce_to_4(const m3d::vec3 *pts,
      69              :                                const m3d::scalar *depths,
      70              :                                int n,
      71              :                                m3d::vec3 *out_pts,
      72              :                                m3d::scalar *out_depths)
      73              :         {
      74            0 :             if (n <= 4)
      75              :             {
      76            0 :                 for (int i = 0; i < n; ++i)
      77              :                 {
      78            0 :                     out_pts[i] = pts[i];
      79            0 :                     out_depths[i] = depths[i];
      80              :                 }
      81            0 :                 return n;
      82              :             }
      83              : 
      84              :             // 1. Deepest
      85            0 :             int i0 = 0;
      86            0 :             for (int i = 1; i < n; ++i)
      87            0 :                 if (depths[i] > depths[i0])
      88            0 :                     i0 = i;
      89              : 
      90              :             // 2. Farthest from i0
      91            0 :             int i1 = (i0 == 0) ? 1 : 0;
      92            0 :             m3d::scalar best = m3d::length_sq(pts[i1] - pts[i0]);
      93            0 :             for (int i = 0; i < n; ++i)
      94              :             {
      95            0 :                 if (i == i0)
      96            0 :                     continue;
      97            0 :                 m3d::scalar d = m3d::length_sq(pts[i] - pts[i0]);
      98            0 :                 if (d > best)
      99              :                 {
     100            0 :                     best = d;
     101            0 :                     i1 = i;
     102              :                 }
     103              :             }
     104              : 
     105              :             // 3. Farthest from segment
     106            0 :             int i2 = -1;
     107            0 :             best = 0.0;
     108            0 :             const m3d::vec3 seg = pts[i1] - pts[i0];
     109            0 :             for (int i = 0; i < n; ++i)
     110              :             {
     111            0 :                 if (i == i0 || i == i1)
     112            0 :                     continue;
     113            0 :                 m3d::scalar d = m3d::length_sq(m3d::cross(pts[i] - pts[i0], seg));
     114            0 :                 if (d > best)
     115              :                 {
     116            0 :                     best = d;
     117            0 :                     i2 = i;
     118              :                 }
     119              :             }
     120            0 :             if (i2 < 0)
     121              :             {
     122            0 :                 for (int i = 0; i < n; ++i)
     123            0 :                     if (i != i0 && i != i1)
     124              :                     {
     125            0 :                         i2 = i;
     126            0 :                         break;
     127              :                     }
     128              :             }
     129              : 
     130              :             // 4. Farthest from triangle
     131            0 :             int i3 = -1;
     132            0 :             best = 0.0;
     133            0 :             if (i2 >= 0)
     134              :             {
     135            0 :                 const m3d::vec3 tri_n = m3d::cross(pts[i1] - pts[i0], pts[i2] - pts[i0]);
     136            0 :                 for (int i = 0; i < n; ++i)
     137              :                 {
     138            0 :                     if (i == i0 || i == i1 || i == i2)
     139            0 :                         continue;
     140            0 :                     m3d::scalar d = m3d::abs(m3d::dot(pts[i] - pts[i0], tri_n));
     141            0 :                     if (d > best)
     142              :                     {
     143            0 :                         best = d;
     144            0 :                         i3 = i;
     145              :                     }
     146              :                 }
     147            0 :                 if (i3 < 0)
     148            0 :                     for (int i = 0; i < n; ++i)
     149            0 :                         if (i != i0 && i != i1 && i != i2)
     150              :                         {
     151            0 :                             i3 = i;
     152            0 :                             break;
     153              :                         }
     154              :             }
     155              : 
     156            0 :             out_pts[0] = pts[i0];
     157            0 :             out_depths[0] = depths[i0];
     158            0 :             out_pts[1] = pts[i1];
     159            0 :             out_depths[1] = depths[i1];
     160            0 :             if (i2 >= 0)
     161              :             {
     162            0 :                 out_pts[2] = pts[i2];
     163            0 :                 out_depths[2] = depths[i2];
     164              :             }
     165            0 :             if (i3 >= 0)
     166              :             {
     167            0 :                 out_pts[3] = pts[i3];
     168            0 :                 out_depths[3] = depths[i3];
     169            0 :                 return 4;
     170              :             }
     171            0 :             return (i2 >= 0) ? 3 : 2;
     172              :         }
     173              : 
     174              :         // ---------------------------------------------------------------------------
     175              :         //  Support-based polygon extraction for a generic shape.
     176              :         //
     177              :         //  For convex shapes (Box, Capsule endpoints, Ellipsoid…) we approximate the
     178              :         //  reference / incident "face" by sampling support points in a small disc
     179              :         //  perpendicular to the contact normal and centered on the EPA support point.
     180              :         //
     181              :         //  For BOX shapes we use exact face corners (identical to the BoxBox path).
     182              :         //  Other shapes get a 4-point disc approximation — good enough for sphere
     183              :         //  caps, cylinder ends, and ellipsoid poles.
     184              :         // ---------------------------------------------------------------------------
     185              : 
     186              :         // Return the 4 corners of the best face of a BOX.
     187            0 :         inline int get_box_face_corners(const Box &box,
     188              :                                         const m3d::tf &tf,
     189              :                                         const m3d::vec3 &dir,
     190              :                                         m3d::vec3 corners[4])
     191              :         {
     192              :             const m3d::vec3 axes[3] = {
     193            0 :                 tf.rotate_vector(m3d::vec3(1, 0, 0)),
     194            0 :                 tf.rotate_vector(m3d::vec3(0, 1, 0)),
     195            0 :                 tf.rotate_vector(m3d::vec3(0, 0, 1)),
     196            0 :             };
     197            0 :             const m3d::scalar half[3] = {box.half_extents.x, box.half_extents.y, box.half_extents.z};
     198              : 
     199            0 :             int best = 0;
     200            0 :             m3d::scalar best_d = m3d::abs(m3d::dot(axes[0], dir));
     201            0 :             for (int i = 1; i < 3; ++i)
     202              :             {
     203            0 :                 m3d::scalar d = m3d::abs(m3d::dot(axes[i], dir));
     204            0 :                 if (d > best_d)
     205              :                 {
     206            0 :                     best_d = d;
     207            0 :                     best = i;
     208              :                 }
     209              :             }
     210              : 
     211            0 :             const m3d::scalar sign = (m3d::dot(axes[best], dir) >= 0.0) ? 1.0 : -1.0;
     212            0 :             const m3d::vec3 centre = tf.pos + axes[best] * (sign * half[best]);
     213            0 :             const int u = (best + 1) % 3, v = (best + 2) % 3;
     214              : 
     215            0 :             corners[0] = centre + axes[u] * half[u] + axes[v] * half[v];
     216            0 :             corners[1] = centre - axes[u] * half[u] + axes[v] * half[v];
     217            0 :             corners[2] = centre - axes[u] * half[u] - axes[v] * half[v];
     218            0 :             corners[3] = centre + axes[u] * half[u] - axes[v] * half[v];
     219            0 :             return 4;
     220              :         }
     221              : 
     222              :         // Generic fallback: a small disc of 4 points around the support point.
     223              :         // `support_pt` is the EPA support point on the shape surface.
     224              :         // `radius`     is a representative "face radius" (half-extent, capsule radius…).
     225            0 :         inline int get_generic_face_corners(const m3d::vec3 &support_pt,
     226              :                                             const m3d::vec3 &normal,
     227              :                                             m3d::scalar radius,
     228              :                                             m3d::vec3 corners[4])
     229              :         {
     230              :             // Build a tangent frame
     231            0 :             m3d::vec3 t1, t2;
     232            0 :             if (m3d::abs(normal.x) < 0.9)
     233            0 :                 t1 = m3d::normalize(m3d::cross(normal, m3d::vec3(1, 0, 0)));
     234              :             else
     235            0 :                 t1 = m3d::normalize(m3d::cross(normal, m3d::vec3(0, 1, 0)));
     236            0 :             t2 = m3d::cross(normal, t1);
     237              : 
     238            0 :             const m3d::scalar r = radius * 0.5; // conservative
     239            0 :             corners[0] = support_pt + (t1 + t2) * r;
     240            0 :             corners[1] = support_pt + (-t1 + t2) * r;
     241            0 :             corners[2] = support_pt + (-t1 - t2) * r;
     242            0 :             corners[3] = support_pt + (t1 - t2) * r;
     243            0 :             return 4;
     244              :         }
     245              : 
     246              :     } // namespace manifold_detail
     247              : 
     248              :     // ---------------------------------------------------------------------------
     249              :     //  Main entry point
     250              :     // ---------------------------------------------------------------------------
     251              : 
     252              :     // Expand a single-point EPA result into a full manifold.
     253              :     // `epa_normal`   — penetration normal (A→B convention from EPA)
     254              :     // `epa_depth`    — penetration depth
     255              :     // `epa_contact`  — EPA contact point (barycentric on the Minkowski diff face)
     256              :     // `shape_a/b`    — the two shapes
     257              :     // `tf_a/tf_b`    — their world transforms
     258              :     //
     259              :     // The function fills `manifold` with 1-4 contact points.
     260            0 :     inline void generate_manifold(const m3d::vec3 &epa_normal,
     261              :                                   m3d::scalar epa_depth,
     262              :                                   const m3d::vec3 &epa_contact,
     263              :                                   const Shape &shape_a,
     264              :                                   const m3d::tf &tf_a,
     265              :                                   const Shape &shape_b,
     266              :                                   const m3d::tf &tf_b,
     267              :                                   ContactManifold &manifold)
     268              :     {
     269            0 :         manifold.normal = epa_normal;
     270            0 :         manifold.num_points = 0;
     271              : 
     272              :         // ── Determine reference and incident "face polygons" ──────────────────
     273              :         // We branch on shape type to get the best polygon representation.
     274              : 
     275            0 :         m3d::vec3 ref_corners[4], inc_corners[4];
     276            0 :         int ref_n = 0, inc_n = 0;
     277              : 
     278              :         // --- Reference face (shape A, in direction of normal) ---
     279            0 :         if (shape_a.type == ShapeType::Box)
     280              :         {
     281            0 :             ref_n = manifold_detail::get_box_face_corners(
     282            0 :                 shape_a.box, tf_a, epa_normal, ref_corners);
     283              :         }
     284              :         else
     285              :         {
     286              :             // Generic: find the support point of A in the normal direction,
     287              :             // then build a disc around it.
     288            0 :             const m3d::vec3 local_dir = tf_a.inverse_rotate_vector(epa_normal);
     289            0 :             const m3d::vec3 sup_local = shape_support(shape_a, local_dir);
     290            0 :             const m3d::vec3 sup_world = tf_a.transform_point(sup_local);
     291              : 
     292              :             // Representative radius: largest half-extent or capsule radius
     293            0 :             m3d::scalar r = 0.1; // fallback
     294            0 :             if (shape_a.type == ShapeType::Sphere)
     295            0 :                 r = shape_a.sphere.radius;
     296            0 :             if (shape_a.type == ShapeType::Capsule)
     297            0 :                 r = shape_a.capsule.radius;
     298            0 :             if (shape_a.type == ShapeType::Ellipsoid)
     299            0 :                 r = m3d::max(shape_a.ellipsoid.half_extents.x,
     300            0 :                              m3d::max(shape_a.ellipsoid.half_extents.y,
     301            0 :                                       shape_a.ellipsoid.half_extents.z));
     302              : 
     303            0 :             ref_n = manifold_detail::get_generic_face_corners(
     304              :                 sup_world, epa_normal, r, ref_corners);
     305              :         }
     306              : 
     307              :         // --- Incident face (shape B, in direction of -normal) ---
     308            0 :         if (shape_b.type == ShapeType::Box)
     309              :         {
     310            0 :             inc_n = manifold_detail::get_box_face_corners(
     311            0 :                 shape_b.box, tf_b, -epa_normal, inc_corners);
     312              :         }
     313              :         else
     314              :         {
     315            0 :             const m3d::vec3 local_dir = tf_b.inverse_rotate_vector(-epa_normal);
     316            0 :             const m3d::vec3 sup_local = shape_support(shape_b, local_dir);
     317            0 :             const m3d::vec3 sup_world = tf_b.transform_point(sup_local);
     318              : 
     319            0 :             m3d::scalar r = 0.1;
     320            0 :             if (shape_b.type == ShapeType::Sphere)
     321            0 :                 r = shape_b.sphere.radius;
     322            0 :             if (shape_b.type == ShapeType::Capsule)
     323            0 :                 r = shape_b.capsule.radius;
     324            0 :             if (shape_b.type == ShapeType::Ellipsoid)
     325            0 :                 r = m3d::max(shape_b.ellipsoid.half_extents.x,
     326            0 :                              m3d::max(shape_b.ellipsoid.half_extents.y,
     327            0 :                                       shape_b.ellipsoid.half_extents.z));
     328              : 
     329            0 :             inc_n = manifold_detail::get_generic_face_corners(
     330            0 :                 sup_world, -epa_normal, r, inc_corners);
     331              :         }
     332              : 
     333              :         // ── Sutherland-Hodgman: clip incident polygon against reference face ──
     334              :         // Build the reference face normal (outward = epa_normal direction)
     335              :         // The reference face plane passes through ref_corners[0].
     336            0 :         const m3d::vec3 ref_face_n = epa_normal; // outward normal of reference face
     337            0 :         const m3d::scalar ref_d = m3d::dot(ref_corners[0], ref_face_n);
     338              : 
     339              :         // For the side planes we need to know the face "tangent frame".
     340              :         // We approximate with the two axes perpendicular to ref_face_n.
     341              :         // Use the winding of ref_corners to build edge normals.
     342            0 :         m3d::vec3 buf0[16], buf1[16];
     343            0 :         int cnt = inc_n;
     344            0 :         for (int i = 0; i < inc_n; ++i)
     345            0 :             buf0[i] = inc_corners[i];
     346              : 
     347            0 :         for (int i = 0; i < ref_n && cnt > 0; ++i)
     348              :         {
     349            0 :             const m3d::vec3 edge = ref_corners[(i + 1) % ref_n] - ref_corners[i];
     350            0 :             const m3d::vec3 side_n = m3d::normalize(m3d::cross(ref_face_n, edge));
     351            0 :             cnt = manifold_detail::clip_polygon_by_plane(
     352            0 :                 buf0, cnt, side_n, ref_corners[i], buf1);
     353            0 :             for (int k = 0; k < cnt; ++k)
     354            0 :                 buf0[k] = buf1[k];
     355              :         }
     356              : 
     357              :         // Keep only points that are on or behind the reference face plane
     358            0 :         m3d::vec3 keep_pts[16];
     359              :         m3d::scalar keep_dep[16];
     360            0 :         int keep_n = 0;
     361              : 
     362            0 :         for (int i = 0; i < cnt; ++i)
     363              :         {
     364            0 :             const m3d::scalar sd = m3d::dot(buf0[i], ref_face_n) - ref_d;
     365            0 :             if (sd <= m3d::EPSILON) // on or below reference face
     366              :             {
     367            0 :                 keep_pts[keep_n] = buf0[i];
     368              :                 // Penetration depth for this point along the normal.
     369              :                 // A rough estimate: use the global EPA depth (conservative).
     370              :                 // More precise: distance from the point to shape A's reference face.
     371            0 :                 keep_dep[keep_n] = (sd < 0.0) ? (-sd) : epa_depth;
     372            0 :                 ++keep_n;
     373              :             }
     374              :         }
     375              : 
     376              :         // Fall back to the single EPA contact point if clipping gave nothing
     377            0 :         if (keep_n == 0)
     378              :         {
     379            0 :             manifold.num_points = 1;
     380            0 :             manifold.points[0].position = epa_contact;
     381            0 :             manifold.points[0].penetration_depth = epa_depth;
     382            0 :             return;
     383              :         }
     384              : 
     385              :         // Reduce to ≤4
     386            0 :         m3d::vec3 red_pts[4];
     387              :         m3d::scalar red_dep[4];
     388            0 :         int red_n = manifold_detail::reduce_to_4(
     389              :             keep_pts, keep_dep, keep_n, red_pts, red_dep);
     390              : 
     391            0 :         manifold.num_points = red_n;
     392            0 :         for (int i = 0; i < red_n; ++i)
     393              :         {
     394            0 :             manifold.points[i].position = red_pts[i];
     395            0 :             manifold.points[i].penetration_depth = red_dep[i];
     396              :         }
     397              :     }
     398              : 
     399              :     // ---------------------------------------------------------------------------
     400              :     //  Convenience: run GJK → EPA → generate_manifold in one call.
     401              :     //  This is what the primary CollisionAlgorithm<A,B> template should call
     402              :     //  instead of filling manifold manually.
     403              :     // ---------------------------------------------------------------------------
     404            0 :     inline bool gjk_epa_manifold(const Shape &sa,
     405              :                                  const m3d::tf &tf_a,
     406              :                                  const Shape &sb,
     407              :                                  const m3d::tf &tf_b,
     408              :                                  ContactManifold &manifold)
     409              :     {
     410            0 :         MinkowskiDiff md(&sa, &sb, tf_a, tf_b);
     411              : 
     412            0 :         m3d::vec3 guess = tf_b.pos - tf_a.pos;
     413            0 :         if (m3d::length_sq(guess) < m3d::EPSILON)
     414            0 :             guess = m3d::vec3(1.0, 0.0, 0.0);
     415              : 
     416            0 :         GJK gjk;
     417            0 :         if (gjk.evaluate(md, guess) != GJK::Inside)
     418            0 :             return false;
     419              : 
     420            0 :         EPA epa;
     421            0 :         if (epa.evaluate(gjk, md) != EPA::Valid)
     422            0 :             return false;
     423              : 
     424            0 :         generate_manifold(epa.normal, epa.depth, epa.contact_point,
     425              :                           sa, tf_a, sb, tf_b, manifold);
     426            0 :         return true;
     427            0 :     }
     428              : 
     429              : } // namespace rbc
        

Generated by: LCOV version 2.0-1