Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Geometry/GJKClosestPoint.h
9913 views
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
7
#include <Jolt/Core/NonCopyable.h>
8
#include <Jolt/Geometry/ClosestPoint.h>
9
#include <Jolt/Geometry/ConvexSupport.h>
10
11
//#define JPH_GJK_DEBUG
12
#ifdef JPH_GJK_DEBUG
13
#include <Jolt/Core/StringTools.h>
14
#include <Jolt/Renderer/DebugRenderer.h>
15
#endif
16
17
JPH_NAMESPACE_BEGIN
18
19
/// Convex vs convex collision detection
20
/// Based on: A Fast and Robust GJK Implementation for Collision Detection of Convex Objects - Gino van den Bergen
21
class GJKClosestPoint : public NonCopyable
22
{
23
private:
24
/// Get new closest point to origin given simplex mY of mNumPoints points
25
///
26
/// @param inPrevVLenSq Length of |outV|^2 from the previous iteration, used as a maximum value when selecting a new closest point.
27
/// @param outV Closest point
28
/// @param outVLenSq |outV|^2
29
/// @param outSet Set of points that form the new simplex closest to the origin (bit 1 = mY[0], bit 2 = mY[1], ...)
30
///
31
/// If LastPointPartOfClosestFeature is true then the last point added will be assumed to be part of the closest feature and the function will do less work.
32
///
33
/// @return True if new closest point was found.
34
/// False if the function failed, in this case the output variables are not modified
35
template <bool LastPointPartOfClosestFeature>
36
bool GetClosest(float inPrevVLenSq, Vec3 &outV, float &outVLenSq, uint32 &outSet) const
37
{
38
#ifdef JPH_GJK_DEBUG
39
for (int i = 0; i < mNumPoints; ++i)
40
Trace("y[%d] = [%s], |y[%d]| = %g", i, ConvertToString(mY[i]).c_str(), i, (double)mY[i].Length());
41
#endif
42
43
uint32 set;
44
Vec3 v;
45
46
switch (mNumPoints)
47
{
48
case 1:
49
// Single point
50
set = 0b0001;
51
v = mY[0];
52
break;
53
54
case 2:
55
// Line segment
56
v = ClosestPoint::GetClosestPointOnLine(mY[0], mY[1], set);
57
break;
58
59
case 3:
60
// Triangle
61
v = ClosestPoint::GetClosestPointOnTriangle<LastPointPartOfClosestFeature>(mY[0], mY[1], mY[2], set);
62
break;
63
64
case 4:
65
// Tetrahedron
66
v = ClosestPoint::GetClosestPointOnTetrahedron<LastPointPartOfClosestFeature>(mY[0], mY[1], mY[2], mY[3], set);
67
break;
68
69
default:
70
JPH_ASSERT(false);
71
return false;
72
}
73
74
#ifdef JPH_GJK_DEBUG
75
Trace("GetClosest: set = 0b%s, v = [%s], |v| = %g", NibbleToBinary(set), ConvertToString(v).c_str(), (double)v.Length());
76
#endif
77
78
float v_len_sq = v.LengthSq();
79
if (v_len_sq < inPrevVLenSq) // Note, comparison order important: If v_len_sq is NaN then this expression will be false so we will return false
80
{
81
// Return closest point
82
outV = v;
83
outVLenSq = v_len_sq;
84
outSet = set;
85
return true;
86
}
87
88
// No better match found
89
#ifdef JPH_GJK_DEBUG
90
Trace("New closer point is further away, failed to converge");
91
#endif
92
return false;
93
}
94
95
// Get max(|Y_0|^2 .. |Y_n|^2)
96
float GetMaxYLengthSq() const
97
{
98
float y_len_sq = mY[0].LengthSq();
99
for (int i = 1; i < mNumPoints; ++i)
100
y_len_sq = max(y_len_sq, mY[i].LengthSq());
101
return y_len_sq;
102
}
103
104
// Remove points that are not in the set, only updates mY
105
void UpdatePointSetY(uint32 inSet)
106
{
107
int num_points = 0;
108
for (int i = 0; i < mNumPoints; ++i)
109
if ((inSet & (1 << i)) != 0)
110
{
111
mY[num_points] = mY[i];
112
++num_points;
113
}
114
mNumPoints = num_points;
115
}
116
117
// Remove points that are not in the set, only updates mP
118
void UpdatePointSetP(uint32 inSet)
119
{
120
int num_points = 0;
121
for (int i = 0; i < mNumPoints; ++i)
122
if ((inSet & (1 << i)) != 0)
123
{
124
mP[num_points] = mP[i];
125
++num_points;
126
}
127
mNumPoints = num_points;
128
}
129
130
// Remove points that are not in the set, only updates mP and mQ
131
void UpdatePointSetPQ(uint32 inSet)
132
{
133
int num_points = 0;
134
for (int i = 0; i < mNumPoints; ++i)
135
if ((inSet & (1 << i)) != 0)
136
{
137
mP[num_points] = mP[i];
138
mQ[num_points] = mQ[i];
139
++num_points;
140
}
141
mNumPoints = num_points;
142
}
143
144
// Remove points that are not in the set, updates mY, mP and mQ
145
void UpdatePointSetYPQ(uint32 inSet)
146
{
147
int num_points = 0;
148
for (int i = 0; i < mNumPoints; ++i)
149
if ((inSet & (1 << i)) != 0)
150
{
151
mY[num_points] = mY[i];
152
mP[num_points] = mP[i];
153
mQ[num_points] = mQ[i];
154
++num_points;
155
}
156
mNumPoints = num_points;
157
}
158
159
// Calculate closest points on A and B
160
void CalculatePointAAndB(Vec3 &outPointA, Vec3 &outPointB) const
161
{
162
switch (mNumPoints)
163
{
164
case 1:
165
outPointA = mP[0];
166
outPointB = mQ[0];
167
break;
168
169
case 2:
170
{
171
float u, v;
172
ClosestPoint::GetBaryCentricCoordinates(mY[0], mY[1], u, v);
173
outPointA = u * mP[0] + v * mP[1];
174
outPointB = u * mQ[0] + v * mQ[1];
175
}
176
break;
177
178
case 3:
179
{
180
float u, v, w;
181
ClosestPoint::GetBaryCentricCoordinates(mY[0], mY[1], mY[2], u, v, w);
182
outPointA = u * mP[0] + v * mP[1] + w * mP[2];
183
outPointB = u * mQ[0] + v * mQ[1] + w * mQ[2];
184
}
185
break;
186
187
case 4:
188
#ifdef JPH_DEBUG
189
memset(&outPointA, 0xcd, sizeof(outPointA));
190
memset(&outPointB, 0xcd, sizeof(outPointB));
191
#endif
192
break;
193
}
194
}
195
196
public:
197
/// Test if inA and inB intersect
198
///
199
/// @param inA The convex object A, must support the GetSupport(Vec3) function.
200
/// @param inB The convex object B, must support the GetSupport(Vec3) function.
201
/// @param inTolerance Minimal distance between objects when the objects are considered to be colliding
202
/// @param ioV is used as initial separating axis (provide a zero vector if you don't know yet)
203
///
204
/// @return True if they intersect (in which case ioV = (0, 0, 0)).
205
/// False if they don't intersect in which case ioV is a separating axis in the direction from A to B (magnitude is meaningless)
206
template <typename A, typename B>
207
bool Intersects(const A &inA, const B &inB, float inTolerance, Vec3 &ioV)
208
{
209
float tolerance_sq = Square(inTolerance);
210
211
// Reset state
212
mNumPoints = 0;
213
214
#ifdef JPH_GJK_DEBUG
215
for (int i = 0; i < 4; ++i)
216
mY[i] = Vec3::sZero();
217
#endif
218
219
// Previous length^2 of v
220
float prev_v_len_sq = FLT_MAX;
221
222
for (;;)
223
{
224
#ifdef JPH_GJK_DEBUG
225
Trace("v = [%s], num_points = %d", ConvertToString(ioV).c_str(), mNumPoints);
226
#endif
227
228
// Get support points for shape A and B in the direction of v
229
Vec3 p = inA.GetSupport(ioV);
230
Vec3 q = inB.GetSupport(-ioV);
231
232
// Get support point of the minkowski sum A - B of v
233
Vec3 w = p - q;
234
235
// If the support point sA-B(v) is in the opposite direction as v, then we have found a separating axis and there is no intersection
236
if (ioV.Dot(w) < 0.0f)
237
{
238
// Separating axis found
239
#ifdef JPH_GJK_DEBUG
240
Trace("Separating axis");
241
#endif
242
return false;
243
}
244
245
// Store the point for later use
246
mY[mNumPoints] = w;
247
++mNumPoints;
248
249
#ifdef JPH_GJK_DEBUG
250
Trace("w = [%s]", ConvertToString(w).c_str());
251
#endif
252
253
// Determine the new closest point
254
float v_len_sq; // Length^2 of v
255
uint32 set; // Set of points that form the new simplex
256
if (!GetClosest<true>(prev_v_len_sq, ioV, v_len_sq, set))
257
return false;
258
259
// If there are 4 points, the origin is inside the tetrahedron and we're done
260
if (set == 0xf)
261
{
262
#ifdef JPH_GJK_DEBUG
263
Trace("Full simplex");
264
#endif
265
ioV = Vec3::sZero();
266
return true;
267
}
268
269
// If v is very close to zero, we consider this a collision
270
if (v_len_sq <= tolerance_sq)
271
{
272
#ifdef JPH_GJK_DEBUG
273
Trace("Distance zero");
274
#endif
275
ioV = Vec3::sZero();
276
return true;
277
}
278
279
// If v is very small compared to the length of y, we also consider this a collision
280
if (v_len_sq <= FLT_EPSILON * GetMaxYLengthSq())
281
{
282
#ifdef JPH_GJK_DEBUG
283
Trace("Machine precision reached");
284
#endif
285
ioV = Vec3::sZero();
286
return true;
287
}
288
289
// The next separation axis to test is the negative of the closest point of the Minkowski sum to the origin
290
// Note: This must be done before terminating as converged since the separating axis is -v
291
ioV = -ioV;
292
293
// If the squared length of v is not changing enough, we've converged and there is no collision
294
JPH_ASSERT(prev_v_len_sq >= v_len_sq);
295
if (prev_v_len_sq - v_len_sq <= FLT_EPSILON * prev_v_len_sq)
296
{
297
// v is a separating axis
298
#ifdef JPH_GJK_DEBUG
299
Trace("Converged");
300
#endif
301
return false;
302
}
303
prev_v_len_sq = v_len_sq;
304
305
// Update the points of the simplex
306
UpdatePointSetY(set);
307
}
308
}
309
310
/// Get closest points between inA and inB
311
///
312
/// @param inA The convex object A, must support the GetSupport(Vec3) function.
313
/// @param inB The convex object B, must support the GetSupport(Vec3) function.
314
/// @param inTolerance The minimal distance between A and B before the objects are considered colliding and processing is terminated.
315
/// @param inMaxDistSq The maximum squared distance between A and B before the objects are considered infinitely far away and processing is terminated.
316
/// @param ioV Initial guess for the separating axis. Start with any non-zero vector if you don't know.
317
/// If return value is 0, ioV = (0, 0, 0).
318
/// If the return value is bigger than 0 but smaller than FLT_MAX, ioV will be the separating axis in the direction from A to B and its length the squared distance between A and B.
319
/// If the return value is FLT_MAX, ioV will be the separating axis in the direction from A to B and the magnitude of the vector is meaningless.
320
/// @param outPointA , outPointB
321
/// If the return value is 0 the points are invalid.
322
/// If the return value is bigger than 0 but smaller than FLT_MAX these will contain the closest point on A and B.
323
/// If the return value is FLT_MAX the points are invalid.
324
///
325
/// @return The squared distance between A and B or FLT_MAX when they are further away than inMaxDistSq.
326
template <typename A, typename B>
327
float GetClosestPoints(const A &inA, const B &inB, float inTolerance, float inMaxDistSq, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)
328
{
329
float tolerance_sq = Square(inTolerance);
330
331
// Reset state
332
mNumPoints = 0;
333
334
#ifdef JPH_GJK_DEBUG
335
// Generate the hull of the Minkowski difference for visualization
336
MinkowskiDifference diff(inA, inB);
337
mGeometry = DebugRenderer::sInstance->CreateTriangleGeometryForConvex([&diff](Vec3Arg inDirection) { return diff.GetSupport(inDirection); });
338
339
for (int i = 0; i < 4; ++i)
340
{
341
mY[i] = Vec3::sZero();
342
mP[i] = Vec3::sZero();
343
mQ[i] = Vec3::sZero();
344
}
345
#endif
346
347
// Length^2 of v
348
float v_len_sq = ioV.LengthSq();
349
350
// Previous length^2 of v
351
float prev_v_len_sq = FLT_MAX;
352
353
for (;;)
354
{
355
#ifdef JPH_GJK_DEBUG
356
Trace("v = [%s], num_points = %d", ConvertToString(ioV).c_str(), mNumPoints);
357
#endif
358
359
// Get support points for shape A and B in the direction of v
360
Vec3 p = inA.GetSupport(ioV);
361
Vec3 q = inB.GetSupport(-ioV);
362
363
// Get support point of the minkowski sum A - B of v
364
Vec3 w = p - q;
365
366
float dot = ioV.Dot(w);
367
368
#ifdef JPH_GJK_DEBUG
369
// Draw -ioV to show the closest point to the origin from the previous simplex
370
DebugRenderer::sInstance->DrawArrow(mOffset, mOffset - ioV, Color::sOrange, 0.05f);
371
372
// Draw ioV to show where we're probing next
373
DebugRenderer::sInstance->DrawArrow(mOffset, mOffset + ioV, Color::sCyan, 0.05f);
374
375
// Draw w, the support point
376
DebugRenderer::sInstance->DrawArrow(mOffset, mOffset + w, Color::sGreen, 0.05f);
377
DebugRenderer::sInstance->DrawMarker(mOffset + w, Color::sGreen, 1.0f);
378
379
// Draw the simplex and the Minkowski difference around it
380
DrawState();
381
#endif
382
383
// Test if we have a separation of more than inMaxDistSq, in which case we terminate early
384
if (dot < 0.0f && dot * dot > v_len_sq * inMaxDistSq)
385
{
386
#ifdef JPH_GJK_DEBUG
387
Trace("Distance bigger than max");
388
#endif
389
#ifdef JPH_DEBUG
390
memset(&outPointA, 0xcd, sizeof(outPointA));
391
memset(&outPointB, 0xcd, sizeof(outPointB));
392
#endif
393
return FLT_MAX;
394
}
395
396
// Store the point for later use
397
mY[mNumPoints] = w;
398
mP[mNumPoints] = p;
399
mQ[mNumPoints] = q;
400
++mNumPoints;
401
402
#ifdef JPH_GJK_DEBUG
403
Trace("w = [%s]", ConvertToString(w).c_str());
404
#endif
405
406
uint32 set;
407
if (!GetClosest<true>(prev_v_len_sq, ioV, v_len_sq, set))
408
{
409
--mNumPoints; // Undo add last point
410
break;
411
}
412
413
// If there are 4 points, the origin is inside the tetrahedron and we're done
414
if (set == 0xf)
415
{
416
#ifdef JPH_GJK_DEBUG
417
Trace("Full simplex");
418
#endif
419
ioV = Vec3::sZero();
420
v_len_sq = 0.0f;
421
break;
422
}
423
424
// Update the points of the simplex
425
UpdatePointSetYPQ(set);
426
427
// If v is very close to zero, we consider this a collision
428
if (v_len_sq <= tolerance_sq)
429
{
430
#ifdef JPH_GJK_DEBUG
431
Trace("Distance zero");
432
#endif
433
ioV = Vec3::sZero();
434
v_len_sq = 0.0f;
435
break;
436
}
437
438
// If v is very small compared to the length of y, we also consider this a collision
439
#ifdef JPH_GJK_DEBUG
440
Trace("Check v small compared to y: %g <= %g", (double)v_len_sq, (double)(FLT_EPSILON * GetMaxYLengthSq()));
441
#endif
442
if (v_len_sq <= FLT_EPSILON * GetMaxYLengthSq())
443
{
444
#ifdef JPH_GJK_DEBUG
445
Trace("Machine precision reached");
446
#endif
447
ioV = Vec3::sZero();
448
v_len_sq = 0.0f;
449
break;
450
}
451
452
// The next separation axis to test is the negative of the closest point of the Minkowski sum to the origin
453
// Note: This must be done before terminating as converged since the separating axis is -v
454
ioV = -ioV;
455
456
// If the squared length of v is not changing enough, we've converged and there is no collision
457
#ifdef JPH_GJK_DEBUG
458
Trace("Check v not changing enough: %g <= %g", (double)(prev_v_len_sq - v_len_sq), (double)(FLT_EPSILON * prev_v_len_sq));
459
#endif
460
JPH_ASSERT(prev_v_len_sq >= v_len_sq);
461
if (prev_v_len_sq - v_len_sq <= FLT_EPSILON * prev_v_len_sq)
462
{
463
// v is a separating axis
464
#ifdef JPH_GJK_DEBUG
465
Trace("Converged");
466
#endif
467
break;
468
}
469
prev_v_len_sq = v_len_sq;
470
}
471
472
// Get the closest points
473
CalculatePointAAndB(outPointA, outPointB);
474
475
#ifdef JPH_GJK_DEBUG
476
Trace("Return: v = [%s], |v| = %g", ConvertToString(ioV).c_str(), (double)ioV.Length());
477
478
// Draw -ioV to show the closest point to the origin from the previous simplex
479
DebugRenderer::sInstance->DrawArrow(mOffset, mOffset - ioV, Color::sOrange, 0.05f);
480
481
// Draw the closest points
482
DebugRenderer::sInstance->DrawMarker(mOffset + outPointA, Color::sGreen, 1.0f);
483
DebugRenderer::sInstance->DrawMarker(mOffset + outPointB, Color::sPurple, 1.0f);
484
485
// Draw the simplex and the Minkowski difference around it
486
DrawState();
487
#endif
488
489
JPH_ASSERT(ioV.LengthSq() == v_len_sq);
490
return v_len_sq;
491
}
492
493
/// Get the resulting simplex after the GetClosestPoints algorithm finishes.
494
/// If it returned a squared distance of 0, the origin will be contained in the simplex.
495
void GetClosestPointsSimplex(Vec3 *outY, Vec3 *outP, Vec3 *outQ, uint &outNumPoints) const
496
{
497
uint size = sizeof(Vec3) * mNumPoints;
498
memcpy(outY, mY, size);
499
memcpy(outP, mP, size);
500
memcpy(outQ, mQ, size);
501
outNumPoints = mNumPoints;
502
}
503
504
/// Test if a ray inRayOrigin + lambda * inRayDirection for lambda e [0, ioLambda> intersects inA
505
///
506
/// Code based upon: Ray Casting against General Convex Objects with Application to Continuous Collision Detection - Gino van den Bergen
507
///
508
/// @param inRayOrigin Origin of the ray
509
/// @param inRayDirection Direction of the ray (ioLambda * inDirection determines length)
510
/// @param inTolerance The minimal distance between the ray and A before it is considered colliding
511
/// @param inA A convex object that has the GetSupport(Vec3) function
512
/// @param ioLambda The max fraction along the ray, on output updated with the actual collision fraction.
513
///
514
/// @return true if a hit was found, ioLambda is the solution for lambda.
515
template <typename A>
516
bool CastRay(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, float inTolerance, const A &inA, float &ioLambda)
517
{
518
float tolerance_sq = Square(inTolerance);
519
520
// Reset state
521
mNumPoints = 0;
522
523
float lambda = 0.0f;
524
Vec3 x = inRayOrigin;
525
Vec3 v = x - inA.GetSupport(Vec3::sZero());
526
float v_len_sq = FLT_MAX;
527
bool allow_restart = false;
528
529
for (;;)
530
{
531
#ifdef JPH_GJK_DEBUG
532
Trace("v = [%s], num_points = %d", ConvertToString(v).c_str(), mNumPoints);
533
#endif
534
535
// Get new support point
536
Vec3 p = inA.GetSupport(v);
537
Vec3 w = x - p;
538
539
#ifdef JPH_GJK_DEBUG
540
Trace("w = [%s]", ConvertToString(w).c_str());
541
#endif
542
543
float v_dot_w = v.Dot(w);
544
#ifdef JPH_GJK_DEBUG
545
Trace("v . w = %g", (double)v_dot_w);
546
#endif
547
if (v_dot_w > 0.0f)
548
{
549
// If ray and normal are in the same direction, we've passed A and there's no collision
550
float v_dot_r = v.Dot(inRayDirection);
551
#ifdef JPH_GJK_DEBUG
552
Trace("v . r = %g", (double)v_dot_r);
553
#endif
554
if (v_dot_r >= 0.0f)
555
return false;
556
557
// Update the lower bound for lambda
558
float delta = v_dot_w / v_dot_r;
559
float old_lambda = lambda;
560
lambda -= delta;
561
#ifdef JPH_GJK_DEBUG
562
Trace("lambda = %g, delta = %g", (double)lambda, (double)delta);
563
#endif
564
565
// If lambda didn't change, we cannot converge any further and we assume a hit
566
if (old_lambda == lambda)
567
break;
568
569
// If lambda is bigger or equal than max, we don't have a hit
570
if (lambda >= ioLambda)
571
return false;
572
573
// Update x to new closest point on the ray
574
x = inRayOrigin + lambda * inRayDirection;
575
576
// We've shifted x, so reset v_len_sq so that it is not used as early out for GetClosest
577
v_len_sq = FLT_MAX;
578
579
// We allow rebuilding the simplex once after x changes because the simplex was built
580
// for another x and numerical round off builds up as you keep adding points to an
581
// existing simplex
582
allow_restart = true;
583
}
584
585
// Add p to set P: P = P U {p}
586
mP[mNumPoints] = p;
587
++mNumPoints;
588
589
// Calculate Y = {x} - P
590
for (int i = 0; i < mNumPoints; ++i)
591
mY[i] = x - mP[i];
592
593
// Determine the new closest point from Y to origin
594
uint32 set; // Set of points that form the new simplex
595
if (!GetClosest<false>(v_len_sq, v, v_len_sq, set))
596
{
597
#ifdef JPH_GJK_DEBUG
598
Trace("Failed to converge");
599
#endif
600
601
// Only allow 1 restart, if we still can't get a closest point
602
// we're so close that we return this as a hit
603
if (!allow_restart)
604
break;
605
606
// If we fail to converge, we start again with the last point as simplex
607
#ifdef JPH_GJK_DEBUG
608
Trace("Restarting");
609
#endif
610
allow_restart = false;
611
mP[0] = p;
612
mNumPoints = 1;
613
v = x - p;
614
v_len_sq = FLT_MAX;
615
continue;
616
}
617
else if (set == 0xf)
618
{
619
#ifdef JPH_GJK_DEBUG
620
Trace("Full simplex");
621
#endif
622
623
// We're inside the tetrahedron, we have a hit (verify that length of v is 0)
624
JPH_ASSERT(v_len_sq == 0.0f);
625
break;
626
}
627
628
// Update the points P to form the new simplex
629
// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration
630
UpdatePointSetP(set);
631
632
// Check if x is close enough to inA
633
if (v_len_sq <= tolerance_sq)
634
{
635
#ifdef JPH_GJK_DEBUG
636
Trace("Converged");
637
#endif
638
break;
639
}
640
}
641
642
// Store hit fraction
643
ioLambda = lambda;
644
return true;
645
}
646
647
/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB
648
///
649
/// @param inStart Start position and orientation of the convex object
650
/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)
651
/// @param inTolerance The minimal distance between A and B before they are considered colliding
652
/// @param inA The convex object A, must support the GetSupport(Vec3) function.
653
/// @param inB The convex object B, must support the GetSupport(Vec3) function.
654
/// @param ioLambda The max fraction along the sweep, on output updated with the actual collision fraction.
655
///
656
/// @return true if a hit was found, ioLambda is the solution for lambda.
657
template <typename A, typename B>
658
bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inTolerance, const A &inA, const B &inB, float &ioLambda)
659
{
660
// Transform the shape to be cast to the starting position
661
TransformedConvexObject transformed_a(inStart, inA);
662
663
// Calculate the minkowski difference inB - inA
664
// inA is moving, so we need to add the back side of inB to the front side of inA
665
MinkowskiDifference difference(inB, transformed_a);
666
667
// Do a raycast against the Minkowski difference
668
return CastRay(Vec3::sZero(), inDirection, inTolerance, difference, ioLambda);
669
}
670
671
/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB
672
///
673
/// @param inStart Start position and orientation of the convex object
674
/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)
675
/// @param inTolerance The minimal distance between A and B before they are considered colliding
676
/// @param inA The convex object A, must support the GetSupport(Vec3) function.
677
/// @param inB The convex object B, must support the GetSupport(Vec3) function.
678
/// @param inConvexRadiusA The convex radius of A, this will be added on all sides to pad A.
679
/// @param inConvexRadiusB The convex radius of B, this will be added on all sides to pad B.
680
/// @param ioLambda The max fraction along the sweep, on output updated with the actual collision fraction.
681
/// @param outPointA is the contact point on A (if outSeparatingAxis is near zero, this may not be not the deepest point)
682
/// @param outPointB is the contact point on B (if outSeparatingAxis is near zero, this may not be not the deepest point)
683
/// @param outSeparatingAxis On return this will contain a vector that points from A to B along the smallest distance of separation.
684
/// The length of this vector indicates the separation of A and B without their convex radius.
685
/// If it is near zero, the direction may not be accurate as the bodies may overlap when lambda = 0.
686
///
687
/// @return true if a hit was found, ioLambda is the solution for lambda and outPoint and outSeparatingAxis are valid.
688
template <typename A, typename B>
689
bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inTolerance, const A &inA, const B &inB, float inConvexRadiusA, float inConvexRadiusB, float &ioLambda, Vec3 &outPointA, Vec3 &outPointB, Vec3 &outSeparatingAxis)
690
{
691
float tolerance_sq = Square(inTolerance);
692
693
// Calculate how close A and B (without their convex radius) need to be to each other in order for us to consider this a collision
694
float sum_convex_radius = inConvexRadiusA + inConvexRadiusB;
695
696
// Transform the shape to be cast to the starting position
697
TransformedConvexObject transformed_a(inStart, inA);
698
699
// Reset state
700
mNumPoints = 0;
701
702
float lambda = 0.0f;
703
Vec3 x = Vec3::sZero(); // Since A is already transformed we can start the cast from zero
704
Vec3 v = -inB.GetSupport(Vec3::sZero()) + transformed_a.GetSupport(Vec3::sZero()); // See CastRay: v = x - inA.GetSupport(Vec3::sZero()) where inA is the Minkowski difference inB - transformed_a (see CastShape above) and x is zero
705
float v_len_sq = FLT_MAX;
706
bool allow_restart = false;
707
708
// Keeps track of separating axis of the previous iteration.
709
// Initialized at zero as we don't know if our first v is actually a separating axis.
710
Vec3 prev_v = Vec3::sZero();
711
712
for (;;)
713
{
714
#ifdef JPH_GJK_DEBUG
715
Trace("v = [%s], num_points = %d", ConvertToString(v).c_str(), mNumPoints);
716
#endif
717
718
// Calculate the minkowski difference inB - inA
719
// inA is moving, so we need to add the back side of inB to the front side of inA
720
// Keep the support points on A and B separate so that in the end we can calculate a contact point
721
Vec3 p = transformed_a.GetSupport(-v);
722
Vec3 q = inB.GetSupport(v);
723
Vec3 w = x - (q - p);
724
725
#ifdef JPH_GJK_DEBUG
726
Trace("w = [%s]", ConvertToString(w).c_str());
727
#endif
728
729
// Difference from article to this code:
730
// We did not include the convex radius in p and q in order to be able to calculate a good separating axis at the end of the algorithm.
731
// However when moving forward along inDirection we do need to take this into account so that we keep A and B separated by the sum of their convex radii.
732
// From p we have to subtract: inConvexRadiusA * v / |v|
733
// To q we have to add: inConvexRadiusB * v / |v|
734
// This means that to w we have to add: -(inConvexRadiusA + inConvexRadiusB) * v / |v|
735
// So to v . w we have to add: v . (-(inConvexRadiusA + inConvexRadiusB) * v / |v|) = -(inConvexRadiusA + inConvexRadiusB) * |v|
736
float v_dot_w = v.Dot(w) - sum_convex_radius * v.Length();
737
#ifdef JPH_GJK_DEBUG
738
Trace("v . w = %g", (double)v_dot_w);
739
#endif
740
if (v_dot_w > 0.0f)
741
{
742
// If ray and normal are in the same direction, we've passed A and there's no collision
743
float v_dot_r = v.Dot(inDirection);
744
#ifdef JPH_GJK_DEBUG
745
Trace("v . r = %g", (double)v_dot_r);
746
#endif
747
if (v_dot_r >= 0.0f)
748
return false;
749
750
// Update the lower bound for lambda
751
float delta = v_dot_w / v_dot_r;
752
float old_lambda = lambda;
753
lambda -= delta;
754
#ifdef JPH_GJK_DEBUG
755
Trace("lambda = %g, delta = %g", (double)lambda, (double)delta);
756
#endif
757
758
// If lambda didn't change, we cannot converge any further and we assume a hit
759
if (old_lambda == lambda)
760
break;
761
762
// If lambda is bigger or equal than max, we don't have a hit
763
if (lambda >= ioLambda)
764
return false;
765
766
// Update x to new closest point on the ray
767
x = lambda * inDirection;
768
769
// We've shifted x, so reset v_len_sq so that it is not used as early out when GetClosest returns false
770
v_len_sq = FLT_MAX;
771
772
// Now that we've moved, we know that A and B are not intersecting at lambda = 0, so we can update our tolerance to stop iterating
773
// as soon as A and B are inConvexRadiusA + inConvexRadiusB apart
774
tolerance_sq = Square(inTolerance + sum_convex_radius);
775
776
// We allow rebuilding the simplex once after x changes because the simplex was built
777
// for another x and numerical round off builds up as you keep adding points to an
778
// existing simplex
779
allow_restart = true;
780
}
781
782
// Add p to set P, q to set Q: P = P U {p}, Q = Q U {q}
783
mP[mNumPoints] = p;
784
mQ[mNumPoints] = q;
785
++mNumPoints;
786
787
// Calculate Y = {x} - (Q - P)
788
for (int i = 0; i < mNumPoints; ++i)
789
mY[i] = x - (mQ[i] - mP[i]);
790
791
// Determine the new closest point from Y to origin
792
uint32 set; // Set of points that form the new simplex
793
if (!GetClosest<false>(v_len_sq, v, v_len_sq, set))
794
{
795
#ifdef JPH_GJK_DEBUG
796
Trace("Failed to converge");
797
#endif
798
799
// Only allow 1 restart, if we still can't get a closest point
800
// we're so close that we return this as a hit
801
if (!allow_restart)
802
break;
803
804
// If we fail to converge, we start again with the last point as simplex
805
#ifdef JPH_GJK_DEBUG
806
Trace("Restarting");
807
#endif
808
allow_restart = false;
809
mP[0] = p;
810
mQ[0] = q;
811
mNumPoints = 1;
812
v = x - q;
813
v_len_sq = FLT_MAX;
814
continue;
815
}
816
else if (set == 0xf)
817
{
818
#ifdef JPH_GJK_DEBUG
819
Trace("Full simplex");
820
#endif
821
822
// We're inside the tetrahedron, we have a hit (verify that length of v is 0)
823
JPH_ASSERT(v_len_sq == 0.0f);
824
break;
825
}
826
827
// Update the points P and Q to form the new simplex
828
// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration
829
UpdatePointSetPQ(set);
830
831
// Check if A and B are touching according to our tolerance
832
if (v_len_sq <= tolerance_sq)
833
{
834
#ifdef JPH_GJK_DEBUG
835
Trace("Converged");
836
#endif
837
break;
838
}
839
840
// Store our v to return as separating axis
841
prev_v = v;
842
}
843
844
// Calculate Y = {x} - (Q - P) again so we can calculate the contact points
845
for (int i = 0; i < mNumPoints; ++i)
846
mY[i] = x - (mQ[i] - mP[i]);
847
848
// Calculate the offset we need to apply to A and B to correct for the convex radius
849
Vec3 normalized_v = v.NormalizedOr(Vec3::sZero());
850
Vec3 convex_radius_a = inConvexRadiusA * normalized_v;
851
Vec3 convex_radius_b = inConvexRadiusB * normalized_v;
852
853
// Get the contact point
854
// Note that A and B will coincide when lambda > 0. In this case we calculate only B as it is more accurate as it contains less terms.
855
switch (mNumPoints)
856
{
857
case 1:
858
outPointB = mQ[0] + convex_radius_b;
859
outPointA = lambda > 0.0f? outPointB : mP[0] - convex_radius_a;
860
break;
861
862
case 2:
863
{
864
float bu, bv;
865
ClosestPoint::GetBaryCentricCoordinates(mY[0], mY[1], bu, bv);
866
outPointB = bu * mQ[0] + bv * mQ[1] + convex_radius_b;
867
outPointA = lambda > 0.0f? outPointB : bu * mP[0] + bv * mP[1] - convex_radius_a;
868
}
869
break;
870
871
case 3:
872
case 4: // A full simplex, we can't properly determine a contact point! As contact point we take the closest point of the previous iteration.
873
{
874
float bu, bv, bw;
875
ClosestPoint::GetBaryCentricCoordinates(mY[0], mY[1], mY[2], bu, bv, bw);
876
outPointB = bu * mQ[0] + bv * mQ[1] + bw * mQ[2] + convex_radius_b;
877
outPointA = lambda > 0.0f? outPointB : bu * mP[0] + bv * mP[1] + bw * mP[2] - convex_radius_a;
878
}
879
break;
880
}
881
882
// Store separating axis, in case we have a convex radius we can just return v,
883
// otherwise v will be very small and we resort to returning previous v as an approximation.
884
outSeparatingAxis = sum_convex_radius > 0.0f? -v : -prev_v;
885
886
// Store hit fraction
887
ioLambda = lambda;
888
return true;
889
}
890
891
private:
892
#ifdef JPH_GJK_DEBUG
893
/// Draw state of algorithm
894
void DrawState()
895
{
896
RMat44 origin = RMat44::sTranslation(mOffset);
897
898
// Draw origin
899
DebugRenderer::sInstance->DrawCoordinateSystem(origin, 1.0f);
900
901
// Draw the hull
902
DebugRenderer::sInstance->DrawGeometry(origin, mGeometry->mBounds.Transformed(origin), mGeometry->mBounds.GetExtent().LengthSq(), Color::sYellow, mGeometry);
903
904
// Draw Y
905
for (int i = 0; i < mNumPoints; ++i)
906
{
907
// Draw support point
908
RVec3 y_i = origin * mY[i];
909
DebugRenderer::sInstance->DrawMarker(y_i, Color::sRed, 1.0f);
910
for (int j = i + 1; j < mNumPoints; ++j)
911
{
912
// Draw edge
913
RVec3 y_j = origin * mY[j];
914
DebugRenderer::sInstance->DrawLine(y_i, y_j, Color::sRed);
915
for (int k = j + 1; k < mNumPoints; ++k)
916
{
917
// Make sure triangle faces the origin
918
RVec3 y_k = origin * mY[k];
919
RVec3 center = (y_i + y_j + y_k) / Real(3);
920
RVec3 normal = (y_j - y_i).Cross(y_k - y_i);
921
if (normal.Dot(center) < Real(0))
922
DebugRenderer::sInstance->DrawTriangle(y_i, y_j, y_k, Color::sLightGrey);
923
else
924
DebugRenderer::sInstance->DrawTriangle(y_i, y_k, y_j, Color::sLightGrey);
925
}
926
}
927
}
928
929
// Offset to the right
930
mOffset += Vec3(mGeometry->mBounds.GetSize().GetX() + 2.0f, 0, 0);
931
}
932
#endif // JPH_GJK_DEBUG
933
934
Vec3 mY[4]; ///< Support points on A - B
935
Vec3 mP[4]; ///< Support point on A
936
Vec3 mQ[4]; ///< Support point on B
937
int mNumPoints = 0; ///< Number of points in mY, mP and mQ that are valid
938
939
#ifdef JPH_GJK_DEBUG
940
DebugRenderer::GeometryRef mGeometry; ///< A visualization of the minkowski difference for state drawing
941
RVec3 mOffset = RVec3::sZero(); ///< Offset to use for state drawing
942
#endif
943
};
944
945
JPH_NAMESPACE_END
946
947