ManifoldBetweenTwoFaces.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <Jolt/Jolt.h>
  5. #include <Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h>
  6. #include <Jolt/Physics/Constraints/ContactConstraintManager.h>
  7. #include <Jolt/Geometry/ClipPoly.h>
  8. #ifdef JPH_DEBUG_RENDERER
  9. #include <Jolt/Renderer/DebugRenderer.h>
  10. #endif // JPH_DEBUG_RENDERER
  11. JPH_NAMESPACE_BEGIN
  12. void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioContactPointsOn1, ContactPoints &ioContactPointsOn2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass))
  13. {
  14. // Makes no sense to call this with 4 or less points
  15. JPH_ASSERT(ioContactPointsOn1.size() > 4);
  16. // Both arrays should have the same size
  17. JPH_ASSERT(ioContactPointsOn1.size() == ioContactPointsOn2.size());
  18. // Penetration axis must be normalized
  19. JPH_ASSERT(inPenetrationAxis.IsNormalized());
  20. // We use a heuristic of (distance to center of mass) * (penetration depth) to find the contact point that we should keep
  21. // Neither of those two terms should ever become zero, so we clamp against this minimum value
  22. constexpr float cMinDistanceSq = 1.0e-6f; // 1 mm
  23. ContactPoints projected;
  24. StaticArray<float, 64> penetration_depth_sq;
  25. for (ContactPoints::size_type i = 0; i < ioContactPointsOn1.size(); ++i)
  26. {
  27. // Project contact points on the plane through inCenterOfMass with normal inPenetrationAxis and center around the center of mass of body 1
  28. // (note that since all points are relative to inCenterOfMass we can project onto the plane through the origin)
  29. Vec3 v1 = ioContactPointsOn1[i];
  30. projected.push_back(v1 - v1.Dot(inPenetrationAxis) * inPenetrationAxis);
  31. // Calculate penetration depth^2 of each point and clamp against the minimal distance
  32. Vec3 v2 = ioContactPointsOn2[i];
  33. penetration_depth_sq.push_back(max(cMinDistanceSq, (v2 - v1).LengthSq()));
  34. }
  35. // Find the point that is furthest away from the center of mass (its torque will have the biggest influence)
  36. // and the point that has the deepest penetration depth. Use the heuristic (distance to center of mass) * (penetration depth) for this.
  37. uint point1 = 0;
  38. float val = max(cMinDistanceSq, projected[0].LengthSq()) * penetration_depth_sq[0];
  39. for (uint i = 0; i < projected.size(); ++i)
  40. {
  41. float v = max(cMinDistanceSq, projected[i].LengthSq()) * penetration_depth_sq[i];
  42. if (v > val)
  43. {
  44. val = v;
  45. point1 = i;
  46. }
  47. }
  48. Vec3 point1v = projected[point1];
  49. // Find point furthest from the first point forming a line segment with point1. Again combine this with the heuristic
  50. // for deepest point as per above.
  51. uint point2 = uint(-1);
  52. val = -FLT_MAX;
  53. for (uint i = 0; i < projected.size(); ++i)
  54. if (i != point1)
  55. {
  56. float v = max(cMinDistanceSq, (projected[i] - point1v).LengthSq()) * penetration_depth_sq[i];
  57. if (v > val)
  58. {
  59. val = v;
  60. point2 = i;
  61. }
  62. }
  63. JPH_ASSERT(point2 != uint(-1));
  64. Vec3 point2v = projected[point2];
  65. // Find furthest points on both sides of the line segment in order to maximize the area
  66. uint point3 = uint(-1);
  67. uint point4 = uint(-1);
  68. float min_val = 0.0f;
  69. float max_val = 0.0f;
  70. Vec3 perp = (point2v - point1v).Cross(inPenetrationAxis);
  71. for (uint i = 0; i < projected.size(); ++i)
  72. if (i != point1 && i != point2)
  73. {
  74. float v = perp.Dot(projected[i] - point1v);
  75. if (v < min_val)
  76. {
  77. min_val = v;
  78. point3 = i;
  79. }
  80. else if (v > max_val)
  81. {
  82. max_val = v;
  83. point4 = i;
  84. }
  85. }
  86. // Add points to array (in order so they form a polygon)
  87. StaticArray<Vec3, 4> points_to_keep_on_1, points_to_keep_on_2;
  88. points_to_keep_on_1.push_back(ioContactPointsOn1[point1]);
  89. points_to_keep_on_2.push_back(ioContactPointsOn2[point1]);
  90. if (point3 != uint(-1))
  91. {
  92. points_to_keep_on_1.push_back(ioContactPointsOn1[point3]);
  93. points_to_keep_on_2.push_back(ioContactPointsOn2[point3]);
  94. }
  95. points_to_keep_on_1.push_back(ioContactPointsOn1[point2]);
  96. points_to_keep_on_2.push_back(ioContactPointsOn2[point2]);
  97. if (point4 != uint(-1))
  98. {
  99. JPH_ASSERT(point3 != point4);
  100. points_to_keep_on_1.push_back(ioContactPointsOn1[point4]);
  101. points_to_keep_on_2.push_back(ioContactPointsOn2[point4]);
  102. }
  103. #ifdef JPH_DEBUG_RENDERER
  104. if (ContactConstraintManager::sDrawContactPointReduction)
  105. {
  106. // Draw input polygon
  107. DebugRenderer::sInstance->DrawWirePolygon(RMat44::sTranslation(inCenterOfMass), ioContactPointsOn1, Color::sOrange, 0.05f);
  108. // Draw primary axis
  109. DebugRenderer::sInstance->DrawArrow(inCenterOfMass + ioContactPointsOn1[point1], inCenterOfMass + ioContactPointsOn1[point2], Color::sRed, 0.05f);
  110. // Draw contact points we kept
  111. for (Vec3 p : points_to_keep_on_1)
  112. DebugRenderer::sInstance->DrawMarker(inCenterOfMass + p, Color::sGreen, 0.1f);
  113. }
  114. #endif // JPH_DEBUG_RENDERER
  115. // Copy the points back to the input buffer
  116. ioContactPointsOn1 = points_to_keep_on_1;
  117. ioContactPointsOn2 = points_to_keep_on_2;
  118. }
  119. void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inMaxContactDistance, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass))
  120. {
  121. JPH_ASSERT(inMaxContactDistance > 0.0f);
  122. #ifdef JPH_DEBUG_RENDERER
  123. if (ContactConstraintManager::sDrawContactPoint)
  124. {
  125. RVec3 cp1 = inCenterOfMass + inContactPoint1;
  126. RVec3 cp2 = inCenterOfMass + inContactPoint2;
  127. // Draw contact points
  128. DebugRenderer::sInstance->DrawMarker(cp1, Color::sRed, 0.1f);
  129. DebugRenderer::sInstance->DrawMarker(cp2, Color::sGreen, 0.1f);
  130. // Draw contact normal
  131. DebugRenderer::sInstance->DrawArrow(cp1, cp1 + inPenetrationAxis.Normalized(), Color::sRed, 0.05f);
  132. }
  133. #endif // JPH_DEBUG_RENDERER
  134. // Remember size before adding new points, to check at the end if we added some
  135. ContactPoints::size_type old_size = outContactPoints1.size();
  136. // Both faces need to have at least 2 points or else there can never be more than 1 contact point
  137. // At least one face 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)
  138. if (min(inShape1Face.size(), inShape2Face.size()) >= 2
  139. && max(inShape1Face.size(), inShape2Face.size()) >= 3)
  140. {
  141. // Swap the shapes if the 2nd face doesn't have enough vertices
  142. const ConvexShape::SupportingFace *shape1_face, *shape2_face;
  143. ContactPoints *contact_points1, *contact_points2;
  144. Vec3 penetration_axis;
  145. if (inShape2Face.size() >= 3)
  146. {
  147. shape1_face = &inShape1Face;
  148. shape2_face = &inShape2Face;
  149. contact_points1 = &outContactPoints1;
  150. contact_points2 = &outContactPoints2;
  151. penetration_axis = inPenetrationAxis;
  152. }
  153. else
  154. {
  155. shape1_face = &inShape2Face;
  156. shape2_face = &inShape1Face;
  157. contact_points1 = &outContactPoints2;
  158. contact_points2 = &outContactPoints1;
  159. penetration_axis = -inPenetrationAxis;
  160. }
  161. // Determine plane origin and first edge direction
  162. Vec3 plane_origin = shape1_face->at(0);
  163. Vec3 first_edge = shape1_face->at(1) - plane_origin;
  164. Vec3 plane_normal;
  165. ConvexShape::SupportingFace clipped_face;
  166. if (shape1_face->size() >= 3)
  167. {
  168. // Clip the polygon of face 2 against that of 1
  169. ClipPolyVsPoly(*shape2_face, *shape1_face, penetration_axis, clipped_face);
  170. // Three vertices, can just calculate the normal
  171. plane_normal = first_edge.Cross(shape1_face->at(2) - plane_origin);
  172. }
  173. else
  174. {
  175. // Clip the polygon of face 2 against edge of 1
  176. ClipPolyVsEdge(*shape2_face, shape1_face->at(0), shape1_face->at(1), penetration_axis, clipped_face);
  177. // 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
  178. plane_normal = first_edge.Cross(penetration_axis).Cross(first_edge);
  179. }
  180. // If penetration axis and plane normal are perpendicular, fall back to the contact points
  181. float penetration_axis_dot_plane_normal = penetration_axis.Dot(plane_normal);
  182. if (penetration_axis_dot_plane_normal != 0.0f)
  183. {
  184. float penetration_axis_len = penetration_axis.Length();
  185. for (Vec3 p2 : clipped_face)
  186. {
  187. // Project clipped face back onto the plane of face 1, we do this by solving:
  188. // p1 = p2 + distance * penetration_axis / |penetration_axis|
  189. // (p1 - plane_origin) . plane_normal = 0
  190. // This gives us:
  191. // distance = -|penetration_axis| * (p2 - plane_origin) . plane_normal / penetration_axis . plane_normal
  192. float distance = (p2 - plane_origin).Dot(plane_normal) / penetration_axis_dot_plane_normal; // note left out -|penetration_axis| term
  193. // If the point is less than inMaxContactDistance in front of the plane of face 2, add it as a contact point
  194. if (distance * penetration_axis_len < inMaxContactDistance)
  195. {
  196. Vec3 p1 = p2 - distance * penetration_axis;
  197. contact_points1->push_back(p1);
  198. contact_points2->push_back(p2);
  199. }
  200. }
  201. }
  202. #ifdef JPH_DEBUG_RENDERER
  203. if (ContactConstraintManager::sDrawSupportingFaces)
  204. {
  205. RMat44 com = RMat44::sTranslation(inCenterOfMass);
  206. // Draw clipped poly
  207. DebugRenderer::sInstance->DrawWirePolygon(com, clipped_face, Color::sOrange);
  208. // Draw supporting faces
  209. DebugRenderer::sInstance->DrawWirePolygon(com, inShape1Face, Color::sRed, 0.05f);
  210. DebugRenderer::sInstance->DrawWirePolygon(com, inShape2Face, Color::sGreen, 0.05f);
  211. // Draw normal
  212. float plane_normal_len = plane_normal.Length();
  213. if (plane_normal_len > 0.0f)
  214. {
  215. RVec3 plane_origin_ws = inCenterOfMass + plane_origin;
  216. DebugRenderer::sInstance->DrawArrow(plane_origin_ws, plane_origin_ws + plane_normal / plane_normal_len, Color::sYellow, 0.05f);
  217. }
  218. // Draw contact points that remain after distance check
  219. for (ContactPoints::size_type p = old_size; p < outContactPoints1.size(); ++p)
  220. {
  221. DebugRenderer::sInstance->DrawMarker(inCenterOfMass + outContactPoints1[p], Color::sYellow, 0.1f);
  222. DebugRenderer::sInstance->DrawMarker(inCenterOfMass + outContactPoints2[p], Color::sOrange, 0.1f);
  223. }
  224. }
  225. #endif // JPH_DEBUG_RENDERER
  226. }
  227. // If the clipping result is empty, use the contact point itself
  228. if (outContactPoints1.size() == old_size)
  229. {
  230. outContactPoints1.push_back(inContactPoint1);
  231. outContactPoints2.push_back(inContactPoint2);
  232. }
  233. }
  234. JPH_NAMESPACE_END