Jolt Physics
A multi core friendly Game Physics Engine
Loading...
Searching...
No Matches
EPAPenetrationDepth.h
Go to the documentation of this file.
1// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
11
13
35{
36private:
37 // Typedefs
38 static constexpr int cMaxPoints = EPAConvexHullBuilder::cMaxPoints;
39 static constexpr int cMaxPointsToIncludeOriginInHull = 32;
40 static_assert(cMaxPointsToIncludeOriginInHull < cMaxPoints);
41
44
46 GJKClosestPoint mGJK;
47
49 class SupportPoints
50 {
51 public:
53 Points mY;
54 Vec3 mP[cMaxPoints];
55 Vec3 mQ[cMaxPoints];
56
58 template <typename A, typename B>
59 Vec3 Add(const A &inA, const B &inB, Vec3Arg inDirection, int &outIndex)
60 {
61 // Get support point of the minkowski sum A - B
62 Vec3 p = inA.GetSupport(inDirection);
63 Vec3 q = inB.GetSupport(-inDirection);
64 Vec3 w = p - q;
65
66 // Store new point
67 outIndex = mY.size();
68 mY.push_back(w);
69 mP[outIndex] = p;
70 mQ[outIndex] = q;
71
72 return w;
73 }
74 };
75
76public:
78 enum class EStatus
79 {
81 Colliding,
83 };
84
96 template <typename AE, typename BE>
97 EStatus GetPenetrationDepthStepGJK(const AE &inAExcludingConvexRadius, float inConvexRadiusA, const BE &inBExcludingConvexRadius, float inConvexRadiusB, float inTolerance, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)
98 {
100
101 // Don't supply a zero ioV, we only want to get points on the hull of the Minkowsky sum and not internal points
102 JPH_ASSERT(!ioV.IsNearZero());
103
104 // Get closest points
105 float combined_radius = inConvexRadiusA + inConvexRadiusB;
106 float combined_radius_sq = combined_radius * combined_radius;
107 float closest_points_dist_sq = mGJK.GetClosestPoints(inAExcludingConvexRadius, inBExcludingConvexRadius, inTolerance, combined_radius_sq, ioV, outPointA, outPointB);
108 if (closest_points_dist_sq > combined_radius_sq)
109 {
110 // No collision
111 return EStatus::NotColliding;
112 }
113 if (closest_points_dist_sq > 0.0f)
114 {
115 // Collision within convex radius, adjust points for convex radius
116 float v_len = sqrt(closest_points_dist_sq); // GetClosestPoints function returns |ioV|^2 when return value < FLT_MAX
117 outPointA += ioV * (inConvexRadiusA / v_len);
118 outPointB -= ioV * (inConvexRadiusB / v_len);
119 return EStatus::Colliding;
120 }
121
123 }
124
137 template <typename AI, typename BI>
138 bool GetPenetrationDepthStepEPA(const AI &inAIncludingConvexRadius, const BI &inBIncludingConvexRadius, float inTolerance, Vec3 &outV, Vec3 &outPointA, Vec3 &outPointB)
139 {
141
142 // Check that the tolerance makes sense (smaller value than this will just result in needless iterations)
143 JPH_ASSERT(inTolerance >= FLT_EPSILON);
144
145 // Fetch the simplex from GJK algorithm
146 SupportPoints support_points;
147 mGJK.GetClosestPointsSimplex(support_points.mY.data(), support_points.mP, support_points.mQ, support_points.mY.GetSizeRef());
148
149 // Fill up the amount of support points to 4
150 switch (support_points.mY.size())
151 {
152 case 1:
153 {
154 // 1 vertex, which must be at the origin, which is useless for our purpose
155 JPH_ASSERT(support_points.mY[0].IsNearZero(1.0e-8f));
156 support_points.mY.pop_back();
157
158 // Add support points in 4 directions to form a tetrahedron around the origin
159 int p1, p2, p3, p4;
160 (void)support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, Vec3(0, 1, 0), p1);
161 (void)support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, Vec3(-1, -1, -1), p2);
162 (void)support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, Vec3(1, -1, -1), p3);
163 (void)support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, Vec3(0, -1, 1), p4);
164 JPH_ASSERT(p1 == 0);
165 JPH_ASSERT(p2 == 1);
166 JPH_ASSERT(p3 == 2);
167 JPH_ASSERT(p4 == 3);
168 break;
169 }
170
171 case 2:
172 {
173 // Two vertices, create 3 extra by taking perpendicular axis and rotating it around in 120 degree increments
174 Vec3 axis = (support_points.mY[1] - support_points.mY[0]).Normalized();
175 Mat44 rotation = Mat44::sRotation(axis, DegreesToRadians(120.0f));
176 Vec3 dir1 = axis.GetNormalizedPerpendicular();
177 Vec3 dir2 = rotation * dir1;
178 Vec3 dir3 = rotation * dir2;
179 int p1, p2, p3;
180 (void)support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, dir1, p1);
181 (void)support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, dir2, p2);
182 (void)support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, dir3, p3);
183 JPH_ASSERT(p1 == 2);
184 JPH_ASSERT(p2 == 3);
185 JPH_ASSERT(p3 == 4);
186 break;
187 }
188
189 case 3:
190 case 4:
191 // We already have enough points
192 break;
193 }
194
195 // Create hull out of the initial points
196 JPH_ASSERT(support_points.mY.size() >= 3);
197 EPAConvexHullBuilder hull(support_points.mY);
198 hull.Initialize(0, 1, 2);
199 for (typename Points::size_type i = 3; i < support_points.mY.size(); ++i)
200 {
201 float dist_sq;
202 Triangle *t = hull.FindFacingTriangle(support_points.mY[i], dist_sq);
203 if (t != nullptr)
204 {
206 if (!hull.AddPoint(t, i, FLT_MAX, new_triangles))
207 {
208 // We can't recover from a failure to add a point to the hull because the old triangles have been unlinked already.
209 // Assume no collision. This can happen if the shapes touch in 1 point (or plane) in which case the hull is degenerate.
210 return false;
211 }
212 }
213 }
214
215 // Loop until we are sure that the origin is inside the hull
216 for (;;)
217 {
218 // Get the next closest triangle
220
221 // Don't process removed triangles, just free them (because they're in a heap we don't remove them earlier since we would have to rebuild the sorted heap)
222 if (t->mRemoved)
223 {
225
226 // If we run out of triangles, we couldn't include the origin in the hull so there must be very little penetration and we report no collision.
227 if (!hull.HasNextTriangle())
228 return false;
229
230 hull.FreeTriangle(t);
231 continue;
232 }
233
234 // If the closest to the triangle is zero or positive, the origin is in the hull and we can proceed to the main algorithm
235 if (t->mClosestLenSq >= 0.0f)
236 break;
237
238 // Remove the triangle from the queue before we start adding new ones (which may result in a new closest triangle at the front of the queue)
240
241 // Add a support point to get the origin inside the hull
242 int new_index;
243 Vec3 w = support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, t->mNormal, new_index);
244
245#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
246 // Draw the point that we're adding
247 hull.DrawMarker(w, Color::sRed, 1.0f);
248 hull.DrawWireTriangle(*t, Color::sRed);
249 hull.DrawState();
250#endif
251
252 // Add the point to the hull, if we fail we terminate and report no collision
254 if (!t->IsFacing(w) || !hull.AddPoint(t, new_index, FLT_MAX, new_triangles))
255 return false;
256
257 // If the triangle was removed we can free it now
258 if (t->mRemoved)
259 hull.FreeTriangle(t);
260
261 // If we run out of triangles or points, we couldn't include the origin in the hull so there must be very little penetration and we report no collision.
262 if (!hull.HasNextTriangle() || support_points.mY.size() >= cMaxPointsToIncludeOriginInHull)
263 return false;
264 }
265
266 // Current closest distance to origin
267 float closest_dist_sq = FLT_MAX;
268
269 // Remember last good triangle
270 Triangle *last = nullptr;
271
272 // Loop until closest point found
273 do
274 {
275 // Get closest triangle to the origin
277
278 // Don't process removed triangles, just free them (because they're in a heap we don't remove them earlier since we would have to rebuild the sorted heap)
279 if (t->mRemoved)
280 {
281 hull.FreeTriangle(t);
282 continue;
283 }
284
285 // Check if next triangle is further away than closest point, we've found the closest point
286 if (t->mClosestLenSq >= closest_dist_sq)
287 break;
288
289 // Replace last good with this triangle
290 if (last != nullptr)
291 hull.FreeTriangle(last);
292 last = t;
293
294 // Add support point in direction of normal of the plane
295 // Note that the article uses the closest point between the origin and plane, but this always has the exact same direction as the normal (if the origin is behind the plane)
296 // and this way we do less calculations and lose less precision
297 int new_index;
298 Vec3 w = support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, t->mNormal, new_index);
299
300 // Project w onto the triangle normal
301 float dot = t->mNormal.Dot(w);
302
303 // Check if we just found a separating axis. This can happen if the shape shrunk by convex radius and then expanded by
304 // convex radius is bigger then the original shape due to inaccuracies in the shrinking process.
305 if (dot < 0.0f)
306 return false;
307
308 // Get the distance squared (along normal) to the support point
309 float dist_sq = dot * dot / t->mNormal.LengthSq();
310
311#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
312 // Draw the point that we're adding
313 hull.DrawMarker(w, Color::sPurple, 1.0f);
314 hull.DrawWireTriangle(*t, Color::sPurple);
315 hull.DrawState();
316#endif
317
318 // If the error became small enough, we've converged
319 if (dist_sq - t->mClosestLenSq < t->mClosestLenSq * inTolerance)
320 break;
321
322 // Keep track of the minimum distance
323 closest_dist_sq = min(closest_dist_sq, dist_sq);
324
325 // If the triangle thinks this point is not front facing, we've reached numerical precision and we're done
326 if (!t->IsFacing(w))
327 break;
328
329 // Add point to hull
331 if (!hull.AddPoint(t, new_index, closest_dist_sq, new_triangles))
332 break;
333
334 // If the hull is starting to form defects then we're reaching numerical precision and we have to stop
335 bool has_defect = false;
336 for (const Triangle *nt : new_triangles)
337 if (nt->IsFacingOrigin())
338 {
339 has_defect = true;
340 break;
341 }
342 if (has_defect)
343 break;
344 }
345 while (hull.HasNextTriangle() && support_points.mY.size() < cMaxPoints);
346
347 // Determine closest points, if last == null it means the hull was a plane so there's no penetration
348 if (last == nullptr)
349 return false;
350
351 // Should be an interior point
353
354 // Calculate penetration by getting the vector from the origin to the closest point on the triangle:
355 // distance = (centroid - origin) . normal / |normal|, closest = origin + distance * normal / |normal|
356 outV = (last->mCentroid.Dot(last->mNormal) / last->mNormal.LengthSq()) * last->mNormal;
357
358 // If penetration is near zero, treat this as a non collision since we cannot find a good normal
359 if (outV.IsNearZero())
360 return false;
361
362 // Use the barycentric coordinates for the closest point to the origin to find the contact points on A and B
363 Vec3 p0 = support_points.mP[last->mEdge[0].mStartIdx];
364 Vec3 p1 = support_points.mP[last->mEdge[1].mStartIdx];
365 Vec3 p2 = support_points.mP[last->mEdge[2].mStartIdx];
366
367 Vec3 q0 = support_points.mQ[last->mEdge[0].mStartIdx];
368 Vec3 q1 = support_points.mQ[last->mEdge[1].mStartIdx];
369 Vec3 q2 = support_points.mQ[last->mEdge[2].mStartIdx];
370
371 if (last->mLambdaRelativeTo0)
372 {
373 // y0 was the reference vertex
374 outPointA = p0 + last->mLambda[0] * (p1 - p0) + last->mLambda[1] * (p2 - p0);
375 outPointB = q0 + last->mLambda[0] * (q1 - q0) + last->mLambda[1] * (q2 - q0);
376 }
377 else
378 {
379 // y1 was the reference vertex
380 outPointA = p1 + last->mLambda[0] * (p0 - p1) + last->mLambda[1] * (p2 - p1);
381 outPointB = q1 + last->mLambda[0] * (q0 - q1) + last->mLambda[1] * (q2 - q1);
382 }
383
384 return true;
385 }
386
390 template <typename AE, typename AI, typename BE, typename BI>
391 bool GetPenetrationDepth(const AE &inAExcludingConvexRadius, const AI &inAIncludingConvexRadius, float inConvexRadiusA, const BE &inBExcludingConvexRadius, const BI &inBIncludingConvexRadius, float inConvexRadiusB, float inCollisionToleranceSq, float inPenetrationTolerance, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)
392 {
393 // Check result of collision detection
394 switch (GetPenetrationDepthStepGJK(inAExcludingConvexRadius, inConvexRadiusA, inBExcludingConvexRadius, inConvexRadiusB, inCollisionToleranceSq, ioV, outPointA, outPointB))
395 {
397 return true;
398
400 return false;
401
403 return GetPenetrationDepthStepEPA(inAIncludingConvexRadius, inBIncludingConvexRadius, inPenetrationTolerance, ioV, outPointA, outPointB);
404 }
405
406 JPH_ASSERT(false);
407 return false;
408 }
409
427 template <typename A, typename B>
428 bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inCollisionTolerance, float inPenetrationTolerance, const A &inA, const B &inB, float inConvexRadiusA, float inConvexRadiusB, bool inReturnDeepestPoint, float &ioLambda, Vec3 &outPointA, Vec3 &outPointB, Vec3 &outContactNormal)
429 {
430 // First determine if there's a collision at all
431 if (!mGJK.CastShape(inStart, inDirection, inCollisionTolerance, inA, inB, inConvexRadiusA, inConvexRadiusB, ioLambda, outPointA, outPointB, outContactNormal))
432 return false;
433
434 // When our contact normal is too small, we don't have an accurate result
435 bool contact_normal_invalid = outContactNormal.IsNearZero(Square(inCollisionTolerance));
436
437 if (inReturnDeepestPoint
438 && ioLambda == 0.0f // Only when lambda = 0 we can have the bodies overlap
439 && (inConvexRadiusA + inConvexRadiusB == 0.0f // When no convex radius was provided we can never trust contact points at lambda = 0
440 || contact_normal_invalid))
441 {
442 // If we're initially intersecting, we need to run the EPA algorithm in order to find the deepest contact point
443 AddConvexRadius<A> add_convex_a(inA, inConvexRadiusA);
444 AddConvexRadius<B> add_convex_b(inB, inConvexRadiusB);
445 TransformedConvexObject<AddConvexRadius<A>> transformed_a(inStart, add_convex_a);
446 if (!GetPenetrationDepthStepEPA(transformed_a, add_convex_b, inPenetrationTolerance, outContactNormal, outPointA, outPointB))
447 return false;
448 }
449 else if (contact_normal_invalid)
450 {
451 // If we weren't able to calculate a contact normal, use the cast direction instead
452 outContactNormal = inDirection;
453 }
454
455 return true;
456 }
457};
458
#define JPH_NAMESPACE_END
Definition: Core.h:240
#define JPH_NAMESPACE_BEGIN
Definition: Core.h:234
#define JPH_ASSERT(...)
Definition: IssueReporting.h:33
constexpr T Square(T inV)
Square a value.
Definition: Math.h:52
constexpr float DegreesToRadians(float inV)
Convert a value from degrees to radians.
Definition: Math.h:13
#define JPH_PROFILE_FUNCTION()
Scope profiling for function.
Definition: Profiler.h:236
static const Color sPurple
Definition: Color.h:54
static const Color sRed
Definition: Color.h:48
int mStartIdx
Vertex index in mPositions that indicates the start vertex of this edge.
Definition: EPAConvexHullBuilder.h:54
Specialized points list that allows direct access to the size.
Definition: EPAConvexHullBuilder.h:172
Class that holds the information of one triangle.
Definition: EPAConvexHullBuilder.h:62
Vec3 mCentroid
Center of the triangle.
Definition: EPAConvexHullBuilder.h:89
bool mClosestPointInterior
Flag that indicates that the closest point from this triangle to the origin is an interior point.
Definition: EPAConvexHullBuilder.h:93
bool IsFacing(Vec3Arg inPosition) const
Check if triangle is facing inPosition.
Definition: EPAConvexHullBuilder.h:68
Vec3 mNormal
Normal of this triangle, length is 2 times area of triangle.
Definition: EPAConvexHullBuilder.h:88
Edge mEdge[3]
3 edges of this triangle
Definition: EPAConvexHullBuilder.h:87
float mClosestLenSq
Closest distance^2 from origin to triangle.
Definition: EPAConvexHullBuilder.h:90
float mLambda[2]
Barycentric coordinates of closest point to origin on triangle.
Definition: EPAConvexHullBuilder.h:91
bool mLambdaRelativeTo0
How to calculate the closest point, true: y0 + l0 * (y1 - y0) + l1 * (y2 - y0), false: y1 + l0 * (y0 ...
Definition: EPAConvexHullBuilder.h:92
bool mRemoved
Flag that indicates that triangle has been removed.
Definition: EPAConvexHullBuilder.h:94
A convex hull builder specifically made for the EPA penetration depth calculation....
Definition: EPAConvexHullBuilder.h:24
Triangle * PeekClosestTriangleInQueue()
Access to the next closest triangle to the origin (won't remove it from the queue).
Definition: EPAConvexHullBuilder.h:267
bool HasNextTriangle() const
Check if there's another triangle to process from the queue.
Definition: EPAConvexHullBuilder.h:261
static constexpr int cMaxPoints
Max number of points in hull.
Definition: EPAConvexHullBuilder.h:36
Triangle * PopClosestTriangleFromQueue()
Access to the next closest triangle to the origin and remove it from the queue.
Definition: EPAConvexHullBuilder.h:273
void FreeTriangle(Triangle *inT)
Free a triangle.
Definition: EPAConvexHullBuilder.h:365
Triangle * FindFacingTriangle(Vec3Arg inPosition, float &outBestDistSq)
Definition: EPAConvexHullBuilder.h:280
void Initialize(int inIdx1, int inIdx2, int inIdx3)
Initialize the hull with 3 points.
Definition: EPAConvexHullBuilder.h:233
bool AddPoint(Triangle *inFacingTriangle, int inIdx, float inClosestDistSq, NewTriangles &outTriangles)
Add a new point to the convex hull.
Definition: EPAConvexHullBuilder.h:305
Definition: EPAPenetrationDepth.h:35
bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inCollisionTolerance, float inPenetrationTolerance, const A &inA, const B &inB, float inConvexRadiusA, float inConvexRadiusB, bool inReturnDeepestPoint, float &ioLambda, Vec3 &outPointA, Vec3 &outPointB, Vec3 &outContactNormal)
Definition: EPAPenetrationDepth.h:428
bool GetPenetrationDepth(const AE &inAExcludingConvexRadius, const AI &inAIncludingConvexRadius, float inConvexRadiusA, const BE &inBExcludingConvexRadius, const BI &inBIncludingConvexRadius, float inConvexRadiusB, float inCollisionToleranceSq, float inPenetrationTolerance, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)
Definition: EPAPenetrationDepth.h:391
EStatus GetPenetrationDepthStepGJK(const AE &inAExcludingConvexRadius, float inConvexRadiusA, const BE &inBExcludingConvexRadius, float inConvexRadiusB, float inTolerance, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)
Definition: EPAPenetrationDepth.h:97
EStatus
Return code for GetPenetrationDepthStepGJK.
Definition: EPAPenetrationDepth.h:79
@ NotColliding
Returned if the objects don't collide, in this case outPointA/outPointB are invalid.
@ Indeterminate
Returned if the objects penetrate further than the convex radius. In this case you need to call GetPe...
@ Colliding
Returned if the objects penetrate.
bool GetPenetrationDepthStepEPA(const AI &inAIncludingConvexRadius, const BI &inBIncludingConvexRadius, float inTolerance, Vec3 &outV, Vec3 &outPointA, Vec3 &outPointB)
Definition: EPAPenetrationDepth.h:138
Definition: GJKClosestPoint.h:23
bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inTolerance, const A &inA, const B &inB, float &ioLambda)
Definition: GJKClosestPoint.h:676
void GetClosestPointsSimplex(Vec3 *outY, Vec3 *outP, Vec3 *outQ, uint &outNumPoints) const
Definition: GJKClosestPoint.h:502
float GetClosestPoints(const A &inA, const B &inB, float inTolerance, float inMaxDistSq, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)
Definition: GJKClosestPoint.h:334
Holds a 4x4 matrix of floats, but supports also operations on the 3x3 upper left part of the matrix.
Definition: Mat44.h:13
static JPH_INLINE Mat44 sRotation(Vec3Arg inAxis, float inAngle)
Rotate around arbitrary axis.
Definition: Mat44.inl:139
Simple variable length array backed by a fixed size buffer.
Definition: StaticArray.h:12
void push_back(const T &inElement)
Add element to the back of the array.
Definition: StaticArray.h:59
uint size_type
Definition: StaticArray.h:16
size_type size() const
Returns amount of elements in the array.
Definition: StaticArray.h:87
Definition: Vec3.h:16
JPH_INLINE float Dot(Vec3Arg inV2) const
Dot product.
Definition: Vec3.inl:637
JPH_INLINE Vec3 GetNormalizedPerpendicular() const
Get normalized vector that is perpendicular to this vector.
Definition: Vec3.inl:812
JPH_INLINE float LengthSq() const
Squared length of vector.
Definition: Vec3.inl:653
JPH_INLINE bool IsNearZero(float inMaxDistSq=1.0e-12f) const
Test if vector is near zero.
Definition: Vec3.inl:347
Structure that adds a convex radius.
Definition: ConvexSupport.h:46
Definition: ConvexSupport.h:15