ManifoldBetweenTwoFaces.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <Jolt.h>
  4. #include <Physics/Collision/ManifoldBetweenTwoFaces.h>
  5. #include <Physics/Constraints/ContactConstraintManager.h>
  6. #include <Geometry/ClipPoly.h>
  7. #ifdef JPH_DEBUG_RENDERER
  8. #include <Renderer/DebugRenderer.h>
  9. #endif // JPH_DEBUG_RENDERER
  10. namespace JPH {
  11. void PruneContactPoints(Vec3Arg inCenterOfMass, Vec3Arg inPenetrationAxis, ContactPoints &ioContactPointsOn1, ContactPoints &ioContactPointsOn2)
  12. {
  13. // Makes no sense to call this with 4 or less points
  14. JPH_ASSERT(ioContactPointsOn1.size() > 4);
  15. // Both arrays should have the same size
  16. JPH_ASSERT(ioContactPointsOn1.size() == ioContactPointsOn2.size());
  17. // Penetration axis must be normalized
  18. JPH_ASSERT(inPenetrationAxis.IsNormalized());
  19. // We use a heuristic of (distance to center of mass) * (penetration depth) to find the contact point that we should keep
  20. // Neither of those two terms should ever become zero, so we clamp against this minimum value
  21. constexpr float cMinDistanceSq = 1.0e-6f; // 1 mm
  22. ContactPoints projected;
  23. StaticArray<float, 64> penetration_depth_sq;
  24. for (ContactPoints::size_type i = 0; i < ioContactPointsOn1.size(); ++i)
  25. {
  26. // Project contact points on the plane through inCenterOfMass with normal inPenetrationAxis and center around the center of mass of body 1
  27. Vec3 v1 = ioContactPointsOn1[i];
  28. Vec3 v1_local = v1 - inCenterOfMass;
  29. projected.push_back(v1_local - v1_local.Dot(inPenetrationAxis) * inPenetrationAxis);
  30. // Calculate penetration depth^2 of each point and clamp against the minimal distance
  31. Vec3 v2 = ioContactPointsOn2[i];
  32. penetration_depth_sq.push_back(max(cMinDistanceSq, (v2 - v1).LengthSq()));
  33. }
  34. // Find the point that is furthest away from the center of mass (its torque will have the biggest influence)
  35. // and the point that has the deepest penetration depth. Use the heuristic (distance to center of mass) * (penetration depth) for this.
  36. uint point1 = 0;
  37. float val = max(cMinDistanceSq, projected[0].LengthSq()) * penetration_depth_sq[0];
  38. for (uint i = 0; i < projected.size(); ++i)
  39. {
  40. float v = max(cMinDistanceSq, projected[i].LengthSq()) * penetration_depth_sq[i];
  41. if (v > val)
  42. {
  43. val = v;
  44. point1 = i;
  45. }
  46. }
  47. Vec3 point1v = projected[point1];
  48. // Find point furthest from the first point forming a line segment with point1. Again combine this with the heuristic
  49. // for deepest point as per above.
  50. uint point2 = uint(-1);
  51. val = -FLT_MAX;
  52. for (uint i = 0; i < projected.size(); ++i)
  53. if (i != point1)
  54. {
  55. float v = max(cMinDistanceSq, (projected[i] - point1v).LengthSq()) * penetration_depth_sq[i];
  56. if (v > val)
  57. {
  58. val = v;
  59. point2 = i;
  60. }
  61. }
  62. JPH_ASSERT(point2 != uint(-1));
  63. Vec3 point2v = projected[point2];
  64. // Find furthest points on both sides of the line segment in order to maximize the area
  65. uint point3 = uint(-1);
  66. uint point4 = uint(-1);
  67. float min_val = 0.0f;
  68. float max_val = 0.0f;
  69. Vec3 perp = (point2v - point1v).Cross(inPenetrationAxis);
  70. for (uint i = 0; i < projected.size(); ++i)
  71. if (i != point1 && i != point2)
  72. {
  73. float v = perp.Dot(projected[i] - point1v);
  74. if (v < min_val)
  75. {
  76. min_val = v;
  77. point3 = i;
  78. }
  79. else if (v > max_val)
  80. {
  81. max_val = v;
  82. point4 = i;
  83. }
  84. }
  85. // Add points to array (in order so they form a polygon)
  86. StaticArray<Vec3, 4> points_to_keep_on_1, points_to_keep_on_2;
  87. points_to_keep_on_1.push_back(ioContactPointsOn1[point1]);
  88. points_to_keep_on_2.push_back(ioContactPointsOn2[point1]);
  89. if (point3 != uint(-1))
  90. {
  91. points_to_keep_on_1.push_back(ioContactPointsOn1[point3]);
  92. points_to_keep_on_2.push_back(ioContactPointsOn2[point3]);
  93. }
  94. points_to_keep_on_1.push_back(ioContactPointsOn1[point2]);
  95. points_to_keep_on_2.push_back(ioContactPointsOn2[point2]);
  96. if (point4 != uint(-1))
  97. {
  98. JPH_ASSERT(point3 != point4);
  99. points_to_keep_on_1.push_back(ioContactPointsOn1[point4]);
  100. points_to_keep_on_2.push_back(ioContactPointsOn2[point4]);
  101. }
  102. #ifdef JPH_DEBUG_RENDERER
  103. if (ContactConstraintManager::sDrawContactPointReduction)
  104. {
  105. // Draw input polygon
  106. DebugRenderer::sInstance->DrawWirePolygon(ioContactPointsOn1, Color::sOrange, 0.05f);
  107. // Draw primary axis
  108. DebugRenderer::sInstance->DrawArrow(ioContactPointsOn1[point1], ioContactPointsOn1[point2], Color::sRed, 0.05f);
  109. // Draw contact points we kept
  110. for (Vec3 p : points_to_keep_on_1)
  111. DebugRenderer::sInstance->DrawMarker(p, Color::sGreen, 0.1f);
  112. }
  113. #endif // JPH_DEBUG_RENDERER
  114. // Copy the points back to the input buffer
  115. ioContactPointsOn1 = points_to_keep_on_1;
  116. ioContactPointsOn2 = points_to_keep_on_2;
  117. }
  118. void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inSpeculativeContactDistanceSq, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2)
  119. {
  120. #ifdef JPH_DEBUG_RENDERER
  121. if (ContactConstraintManager::sDrawContactPoint)
  122. {
  123. // Draw contact points
  124. DebugRenderer::sInstance->DrawMarker(inContactPoint1, Color::sRed, 0.1f);
  125. DebugRenderer::sInstance->DrawMarker(inContactPoint2, Color::sGreen, 0.1f);
  126. // Draw contact normal
  127. DebugRenderer::sInstance->DrawArrow(inContactPoint1, inContactPoint1 + inPenetrationAxis.Normalized(), Color::sRed, 0.05f);
  128. }
  129. #endif // JPH_DEBUG_RENDERER
  130. // Remember size before adding new points, to check at the end if we added some
  131. ContactPoints::size_type old_size = outContactPoints1.size();
  132. // Check if both shapes have polygon faces
  133. if (inShape1Face.size() >= 2 // The dynamic shape needs to have at least 2 points or else there can never be more than 1 contact point
  134. && inShape2Face.size() >= 3) // The dynamic/static shape needs to have at least 3 points (in the case that it has 2 points only if the edges match exactly you can have 2 contact points, but this situation is unstable anyhow)
  135. {
  136. // Clip the polygon of face 2 against that of 1
  137. ConvexShape::SupportingFace clipped_face;
  138. if (inShape1Face.size() >= 3)
  139. ClipPolyVsPoly(inShape2Face, inShape1Face, inPenetrationAxis, clipped_face);
  140. else if (inShape1Face.size() == 2)
  141. ClipPolyVsEdge(inShape2Face, inShape1Face[0], inShape1Face[1], inPenetrationAxis, clipped_face);
  142. // Project the points back onto the plane of shape 1 face and only keep those that are behind the plane
  143. Vec3 plane_origin = inShape1Face[0];
  144. Vec3 plane_normal;
  145. Vec3 first_edge = inShape1Face[1] - plane_origin;
  146. if (inShape1Face.size() >= 3)
  147. {
  148. // Three vertices, can just calculate the normal
  149. plane_normal = first_edge.Cross(inShape1Face[2] - plane_origin);
  150. }
  151. else
  152. {
  153. // Two vertices, first find a perpendicular to the edge and penetration axis and then use the perpendicular together with the edge to form a normal
  154. plane_normal = first_edge.Cross(inPenetrationAxis).Cross(first_edge);
  155. }
  156. // Check if the plane normal has any length, if not the clipped shape is so small that we'll just use the contact points
  157. float plane_normal_len_sq = plane_normal.LengthSq();
  158. if (plane_normal_len_sq > 0.0f)
  159. {
  160. // Discard points of faces that are too far away to collide
  161. for (Vec3 p2 : clipped_face)
  162. {
  163. float distance = (p2 - plane_origin).Dot(plane_normal); // Note should divide by length of plane_normal (unnormalized here)
  164. if (distance <= 0.0f || Square(distance) < inSpeculativeContactDistanceSq * plane_normal_len_sq) // Must be close enough to plane, note we correct for not dividing by plane normal length here
  165. {
  166. // Project point back on shape 1 using the normal, note we correct for not dividing by plane normal length here:
  167. // p1 = p2 - (distance / sqrt(plane_normal_len_sq)) * (plane_normal / sqrt(plane_normal_len_sq));
  168. Vec3 p1 = p2 - (distance / plane_normal_len_sq) * plane_normal;
  169. outContactPoints1.push_back(p1);
  170. outContactPoints2.push_back(p2);
  171. }
  172. }
  173. }
  174. #ifdef JPH_DEBUG_RENDERER
  175. if (ContactConstraintManager::sDrawSupportingFaces)
  176. {
  177. // Draw clipped poly
  178. DebugRenderer::sInstance->DrawWirePolygon(clipped_face, Color::sOrange);
  179. // Draw supporting faces
  180. DebugRenderer::sInstance->DrawWirePolygon(inShape1Face, Color::sRed, 0.05f);
  181. DebugRenderer::sInstance->DrawWirePolygon(inShape2Face, Color::sGreen, 0.05f);
  182. // Draw normal
  183. if (plane_normal_len_sq > 0.0f)
  184. DebugRenderer::sInstance->DrawArrow(plane_origin, plane_origin + plane_normal / sqrt(plane_normal_len_sq), Color::sYellow, 0.05f);
  185. // Draw contact points that remain after distance check
  186. for (ContactPoints::size_type p = old_size; p < outContactPoints1.size(); ++p)
  187. DebugRenderer::sInstance->DrawMarker(outContactPoints1[p], Color::sYellow, 0.1f);
  188. }
  189. #endif // JPH_DEBUG_RENDERER
  190. }
  191. // If the clipping result is empty, use the contact point itself
  192. if (outContactPoints1.size() == old_size)
  193. {
  194. outContactPoints1.push_back(inContactPoint1);
  195. outContactPoints2.push_back(inContactPoint2);
  196. }
  197. }
  198. } // JPH