Skip to content

Commit 3c2b993

Browse files
Fix EPA, use a more robust ccdVec3PointTriDist2. (#634)
Do not use libccd's ccdVec3PointTriDist2 directly. Instead, wrap it so we always ask libccd to use a witness point which produces better values. Co-authored-by: Sean Curtis <[email protected]>
1 parent b03987c commit 3c2b993

File tree

2 files changed

+619
-53
lines changed

2 files changed

+619
-53
lines changed

include/fcl/narrowphase/detail/convexity_based_algorithm/gjk_libccd-inl.h

Lines changed: 81 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,17 @@
3838
#ifndef FCL_NARROWPHASE_DETAIL_GJKLIBCCD_INL_H
3939
#define FCL_NARROWPHASE_DETAIL_GJKLIBCCD_INL_H
4040

41-
#include "fcl/narrowphase/detail/convexity_based_algorithm/gjk_libccd.h"
42-
#include "fcl/narrowphase/detail/failed_at_this_configuration.h"
43-
4441
#include <array>
42+
#include <iostream>
43+
#include <sstream>
44+
#include <string>
4545
#include <unordered_map>
4646
#include <unordered_set>
4747

4848
#include "fcl/common/unused.h"
4949
#include "fcl/common/warning.h"
50+
#include "fcl/narrowphase/detail/convexity_based_algorithm/gjk_libccd.h"
51+
#include "fcl/narrowphase/detail/failed_at_this_configuration.h"
5052

5153
namespace fcl
5254
{
@@ -186,6 +188,46 @@ struct ccd_triangle_t : public ccd_obj_t
186188
namespace libccd_extension
187189
{
188190

191+
// These two functions contain the only "valid" invocations of
192+
// ccdVec3PointTriDist2(). Any other invocations should be considered a defect.
193+
// We can remove these safety layers if/when the issue noted below gets
194+
// resolved upstream.
195+
196+
// The code in this file (and elsewhere if it comes up), should *not* ever call
197+
// `ccdVec3PointTriDist2()` directly. It has some precision quirks. For those
198+
// invocations that want the squared distance without a witness point, call
199+
// *this* function.
200+
static ccd_real_t ccdVec3PointTriDist2NoWitness(const ccd_vec3_t* P,
201+
const ccd_vec3_t* a,
202+
const ccd_vec3_t* b,
203+
const ccd_vec3_t* c) {
204+
// When called, ccdVec3PointTriDist2 can optionally compute the value of the
205+
// "witness" point. When we don't need the witness point, we skip it. However,
206+
// the libccd implementation takes two different code paths based on whether
207+
// we request the witness point producing different answers due to numerical
208+
// precision issues. So, when FCL doesn't want/need a witness point, we use
209+
// this wrapper to force the witness point to get a more consistent and
210+
// reliable answer. See
211+
// https://github.com/danfis/libccd/issues/55 for an explanation.
212+
//
213+
// Any actual invocation of ccdVec3PointTriDist2() in the code should request
214+
// a witness point *or* call this invocation.
215+
ccd_vec3_t unused;
216+
return ccdVec3PointTriDist2(P, a, b, c, &unused);
217+
}
218+
219+
// The code in this file (and elsewhere if it comes up), should *not* ever call
220+
// `ccdVec3PointTriDist2()` directly. It has some precision quirks. For those
221+
// invocations that want the squared distance *with* a witness point, call
222+
// *this* function.
223+
static ccd_real_t ccdVec3PointTriDist2WithWitness(const ccd_vec3_t* P,
224+
const ccd_vec3_t* a,
225+
const ccd_vec3_t* b,
226+
const ccd_vec3_t* c,
227+
ccd_vec3_t* w) {
228+
return ccdVec3PointTriDist2(P, a, b, c, w);
229+
}
230+
189231
static ccd_real_t simplexReduceToTriangle(ccd_simplex_t *simplex,
190232
ccd_real_t dist,
191233
ccd_vec3_t *best_witness)
@@ -197,11 +239,10 @@ static ccd_real_t simplexReduceToTriangle(ccd_simplex_t *simplex,
197239

198240
// try the fourth point in all three positions
199241
for (i = 0; i < 3; i++){
200-
newdist = ccdVec3PointTriDist2(ccd_vec3_origin,
201-
&ccdSimplexPoint(simplex, (i == 0 ? 3 : 0))->v,
202-
&ccdSimplexPoint(simplex, (i == 1 ? 3 : 1))->v,
203-
&ccdSimplexPoint(simplex, (i == 2 ? 3 : 2))->v,
204-
&witness);
242+
newdist = ccdVec3PointTriDist2WithWitness(
243+
ccd_vec3_origin, &ccdSimplexPoint(simplex, (i == 0 ? 3 : 0))->v,
244+
&ccdSimplexPoint(simplex, (i == 1 ? 3 : 1))->v,
245+
&ccdSimplexPoint(simplex, (i == 2 ? 3 : 2))->v, &witness);
205246
newdist = CCD_SQRT(newdist);
206247

207248
// record the best triangle
@@ -398,13 +439,8 @@ static int doSimplex3(ccd_simplex_t *simplex, ccd_vec3_t *dir)
398439
C = ccdSimplexPoint(simplex, 0);
399440

400441
// check touching contact
401-
// Compute origin_projection as well. Without computing the origin projection,
402-
// libccd could give inaccurate result. See
403-
// https://github.com/danfis/libccd/issues/55.
404-
ccd_vec3_t origin_projection_unused;
405-
406-
const ccd_real_t dist_squared = ccdVec3PointTriDist2(
407-
ccd_vec3_origin, &A->v, &B->v, &C->v, &origin_projection_unused);
442+
const ccd_real_t dist_squared = ccdVec3PointTriDist2NoWitness(
443+
ccd_vec3_origin, &A->v, &B->v, &C->v);
408444
if (isAbsValueLessThanEpsSquared(dist_squared)) {
409445
return 1;
410446
}
@@ -493,30 +529,25 @@ static int doSimplex4(ccd_simplex_t *simplex, ccd_vec3_t *dir)
493529
// check if tetrahedron is really tetrahedron (has volume > 0)
494530
// if it is not simplex can't be expanded and thus no intersection is
495531
// found.
496-
// point_projection_on_triangle_unused is not used. We ask
497-
// ccdVec3PointTriDist2 to compute this witness point, so as to get a
498-
// numerical robust dist_squared. See
499-
// https://github.com/danfis/libccd/issues/55 for an explanation.
500-
ccd_vec3_t point_projection_on_triangle_unused;
501-
ccd_real_t dist_squared = ccdVec3PointTriDist2(
502-
&A->v, &B->v, &C->v, &D->v, &point_projection_on_triangle_unused);
532+
ccd_real_t dist_squared = ccdVec3PointTriDist2NoWitness(
533+
&A->v, &B->v, &C->v, &D->v);
503534
if (isAbsValueLessThanEpsSquared(dist_squared)) {
504535
return -1;
505536
}
506537

507538
// check if origin lies on some of tetrahedron's face - if so objects
508539
// intersect
509-
dist_squared = ccdVec3PointTriDist2(ccd_vec3_origin, &A->v, &B->v, &C->v,
510-
&point_projection_on_triangle_unused);
540+
dist_squared =
541+
ccdVec3PointTriDist2NoWitness(ccd_vec3_origin, &A->v, &B->v, &C->v);
511542
if (isAbsValueLessThanEpsSquared((dist_squared))) return 1;
512-
dist_squared = ccdVec3PointTriDist2(ccd_vec3_origin, &A->v, &C->v, &D->v,
513-
&point_projection_on_triangle_unused);
543+
dist_squared =
544+
ccdVec3PointTriDist2NoWitness(ccd_vec3_origin, &A->v, &C->v, &D->v);
514545
if (isAbsValueLessThanEpsSquared((dist_squared))) return 1;
515-
dist_squared = ccdVec3PointTriDist2(ccd_vec3_origin, &A->v, &B->v, &D->v,
516-
&point_projection_on_triangle_unused);
546+
dist_squared =
547+
ccdVec3PointTriDist2NoWitness(ccd_vec3_origin, &A->v, &B->v, &D->v);
517548
if (isAbsValueLessThanEpsSquared(dist_squared)) return 1;
518-
dist_squared = ccdVec3PointTriDist2(ccd_vec3_origin, &B->v, &C->v, &D->v,
519-
&point_projection_on_triangle_unused);
549+
dist_squared =
550+
ccdVec3PointTriDist2NoWitness(ccd_vec3_origin, &B->v, &C->v, &D->v);
520551
if (isAbsValueLessThanEpsSquared(dist_squared)) return 1;
521552

522553
// compute AO, AB, AC, AD segments and ABC, ACD, ADB normal vectors
@@ -757,15 +788,14 @@ static int convert2SimplexToTetrahedron(const void* obj1, const void* obj2,
757788
ccdVec3Sub2(&ac, &c->v, &a->v);
758789
ccdVec3Cross(&dir, &ab, &ac);
759790
__ccdSupport(obj1, obj2, &dir, ccd, &d);
760-
ccd_vec3_t point_projection_on_triangle_unused;
761-
const ccd_real_t dist_squared = ccdVec3PointTriDist2(
762-
&d.v, &a->v, &b->v, &c->v, &point_projection_on_triangle_unused);
791+
const ccd_real_t dist_squared =
792+
ccdVec3PointTriDist2NoWitness(&d.v, &a->v, &b->v, &c->v);
763793

764794
// and second one take in opposite direction
765795
ccdVec3Scale(&dir, -CCD_ONE);
766796
__ccdSupport(obj1, obj2, &dir, ccd, &d2);
767-
const ccd_real_t dist_squared_opposite = ccdVec3PointTriDist2(
768-
&d2.v, &a->v, &b->v, &c->v, &point_projection_on_triangle_unused);
797+
const ccd_real_t dist_squared_opposite =
798+
ccdVec3PointTriDist2NoWitness(&d2.v, &a->v, &b->v, &c->v);
769799

770800
// check if face isn't already on edge of minkowski sum and thus we
771801
// have touching contact
@@ -844,14 +874,15 @@ static int simplexToPolytope4(const void* obj1, const void* obj2,
844874
// of the simplex to that face and then attempt to construct the polytope from
845875
// the resulting face. We simply take the first face which exhibited the
846876
// trait.
877+
847878
ccd_real_t dist_squared =
848-
ccdVec3PointTriDist2(ccd_vec3_origin, &a->v, &b->v, &c->v, NULL);
879+
ccdVec3PointTriDist2NoWitness(ccd_vec3_origin, &a->v, &b->v, &c->v);
849880
if (isAbsValueLessThanEpsSquared(dist_squared)) {
850881
use_polytope3 = true;
851882
}
852883
if (!use_polytope3) {
853884
dist_squared =
854-
ccdVec3PointTriDist2(ccd_vec3_origin, &a->v, &c->v, &d->v, NULL);
885+
ccdVec3PointTriDist2NoWitness(ccd_vec3_origin, &a->v, &c->v, &d->v);
855886
if (isAbsValueLessThanEpsSquared(dist_squared)) {
856887
use_polytope3 = true;
857888
ccdSimplexSet(simplex, 1, c);
@@ -860,15 +891,15 @@ static int simplexToPolytope4(const void* obj1, const void* obj2,
860891
}
861892
if (!use_polytope3) {
862893
dist_squared =
863-
ccdVec3PointTriDist2(ccd_vec3_origin, &a->v, &b->v, &d->v, NULL);
894+
ccdVec3PointTriDist2NoWitness(ccd_vec3_origin, &a->v, &b->v, &d->v);
864895
if (isAbsValueLessThanEpsSquared(dist_squared)) {
865896
use_polytope3 = true;
866897
ccdSimplexSet(simplex, 2, d);
867898
}
868899
}
869900
if (!use_polytope3) {
870901
dist_squared =
871-
ccdVec3PointTriDist2(ccd_vec3_origin, &b->v, &c->v, &d->v, NULL);
902+
ccdVec3PointTriDist2NoWitness(ccd_vec3_origin, &b->v, &c->v, &d->v);
872903
if (isAbsValueLessThanEpsSquared(dist_squared)) {
873904
use_polytope3 = true;
874905
ccdSimplexSet(simplex, 0, b);
@@ -1583,9 +1614,7 @@ static int nextSupport(const ccd_pt_t* polytope, const void* obj1,
15831614
ccdPtFaceVec3(reinterpret_cast<const ccd_pt_face_t*>(el), &a, &b, &c);
15841615

15851616
// check if new point can significantly expand polytope
1586-
ccd_vec3_t point_projection_on_triangle_unused;
1587-
dist_squared = ccdVec3PointTriDist2(&out->v, a, b, c,
1588-
&point_projection_on_triangle_unused);
1617+
dist_squared = ccdVec3PointTriDist2NoWitness(&out->v, a, b, c);
15891618
}
15901619

15911620
if (std::sqrt(dist_squared) < ccd->epa_tolerance) return -1;
@@ -1716,9 +1745,12 @@ static void validateNearestFeatureOfPolytopeBeingEdge(ccd_pt_t* polytope) {
17161745
// distance to be considered zero. We assume that the GJK/EPA code
17171746
// ultimately classifies inside/outside around *zero* and *not* epsilon.
17181747
if (origin_to_face_distance[i] > plane_threshold) {
1719-
FCL_THROW_FAILED_AT_THIS_CONFIGURATION(
1720-
"The origin is outside of the polytope. This should already have "
1721-
"been identified as separating.");
1748+
std::ostringstream oss;
1749+
oss << "The origin is outside of the polytope by "
1750+
<< origin_to_face_distance[i] << ", exceeding the threshold "
1751+
<< plane_threshold
1752+
<< ". This should already have been identified as separating.";
1753+
FCL_THROW_FAILED_AT_THIS_CONFIGURATION(oss.str());
17221754
}
17231755
}
17241756

@@ -2065,11 +2097,10 @@ static inline ccd_real_t _ccdDist(const void *obj1, const void *obj2,
20652097
}
20662098
else if (ccdSimplexSize(simplex) == 3)
20672099
{
2068-
dist = ccdVec3PointTriDist2(ccd_vec3_origin,
2069-
&ccdSimplexPoint(simplex, 0)->v,
2070-
&ccdSimplexPoint(simplex, 1)->v,
2071-
&ccdSimplexPoint(simplex, 2)->v,
2072-
&closest_p);
2100+
dist = ccdVec3PointTriDist2WithWitness(
2101+
ccd_vec3_origin, &ccdSimplexPoint(simplex, 0)->v,
2102+
&ccdSimplexPoint(simplex, 1)->v, &ccdSimplexPoint(simplex, 2)->v,
2103+
&closest_p);
20732104
dist = CCD_SQRT(dist);
20742105
}
20752106
else

0 commit comments

Comments
 (0)