Skip to content

Commit 8271741

Browse files
Address EPA failure from issue 493
- Introduce two regression tests (the two posted in the issue). - Correct a few confusing whitepsace formatting issues. - Modify the validateNearestFeatureOfPolytopeBeingEdge() function in two ways - Distance from origin to face must now be greater than epsilon (instead of zero). Justificaiton for this change provided. - Test to determine if the edge *truly* is the nearest feature has been simplified and documentation updated. This led to the removal of the is_point_on_line_segment() function.
1 parent 1a043ea commit 8271741

File tree

3 files changed

+85
-46
lines changed

3 files changed

+85
-46
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
[#472](https://github.com/flexible-collision-library/fcl/pull/472)
3434
* Documentation for OcTree no longer mistakenly excluded from doxygen:
3535
[#472](https://github.com/flexible-collision-library/fcl/pull/472)
36+
* Another failure mode in the GJK/EPA signed distance query patched:
37+
[#494](https://github.com/flexible-collision-library/fcl/pull/494)
3638

3739
* Build/Test/Misc
3840

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

+27-46
Original file line numberDiff line numberDiff line change
@@ -966,28 +966,6 @@ static bool triangle_area_is_zero(const ccd_vec3_t& a, const ccd_vec3_t& b,
966966
return false;
967967
}
968968

969-
/**
970-
* Determines if the point P is on the line segment AB.
971-
* If A, B, P are coincident, report true.
972-
*/
973-
static bool is_point_on_line_segment(const ccd_vec3_t& p, const ccd_vec3_t& a,
974-
const ccd_vec3_t& b) {
975-
if (are_coincident(a, b)) {
976-
return are_coincident(a, p);
977-
}
978-
// A and B are not coincident, if the triangle ABP has non-zero area, then P
979-
// is not on the line adjoining AB, and hence not on the line segment AB.
980-
if (!triangle_area_is_zero(a, b, p)) {
981-
return false;
982-
}
983-
// P is on the line adjoinging AB. If P is on the line segment AB, then
984-
// PA.dot(PB) <= 0.
985-
ccd_vec3_t PA, PB;
986-
ccdVec3Sub2(&PA, &p, &a);
987-
ccdVec3Sub2(&PB, &p, &b);
988-
return ccdVec3Dot(&PA, &PB) <= 0;
989-
}
990-
991969
/**
992970
* Computes the normal vector of a triangular face on a polytope, and the normal
993971
* vector points outward from the polytope. Notice we assume that the origin
@@ -1691,6 +1669,8 @@ static int __ccdGJK(const void *obj1, const void *obj2,
16911669
*/
16921670
static void validateNearestFeatureOfPolytopeBeingEdge(ccd_pt_t* polytope) {
16931671
assert(polytope->nearest_type == CCD_PT_EDGE);
1672+
1673+
constexpr ccd_real_t kEps = constants<ccd_real_t>::eps();
16941674
// Only verify the feature if the nearest feature is an edge.
16951675

16961676
const ccd_pt_edge_t* const nearest_edge =
@@ -1709,27 +1689,29 @@ static void validateNearestFeatureOfPolytopeBeingEdge(ccd_pt_t* polytope) {
17091689
// n̂ ⋅ (o - vₑ) ≤ 0 or, with simplification, -n̂ ⋅ vₑ ≤ 0 (since n̂ ⋅ o = 0).
17101690
origin_to_face_distance[i] =
17111691
-ccdVec3Dot(&face_normals[i], &nearest_edge->vertex[0]->v.v);
1712-
if (origin_to_face_distance[i] > 0) {
1692+
// If the origin lies *on* the edge, then it also lies on the two adjacent
1693+
// faces. Rather than failing on strictly *positive* signed distance, we
1694+
// introduce an epsilon threshold. This usage of epsilon is to account for a
1695+
// discrepancy in the signed distance computation. How GJK (and partially
1696+
// EPA) compute the signed distance from origin to face may *not* be exactly
1697+
// the same as done in this test (i.e. for a given set of vertices, the
1698+
// plane equation can be defined various ways. Those ways are
1699+
// *mathematically* equivalent but numerically will differ due to rounding).
1700+
// We account for those differences by allowing a very small positive signed
1701+
// distance to be considered zero. We assume that the GJK/EPA code
1702+
// ultimately clasifies inside/outside around *zero* and *not* epsilon.
1703+
if (origin_to_face_distance[i] > kEps) {
17131704
FCL_THROW_FAILED_AT_THIS_CONFIGURATION(
17141705
"The origin is outside of the polytope. This should already have "
17151706
"been identified as separating.");
17161707
}
17171708
}
1718-
// We compute the projection of the origin onto the plane as
1719-
// -face_normals[i] * origin_to_face_distance[i]
1720-
// If the projection to both faces are on the edge, the the edge is the
1721-
// closest feature.
1722-
bool is_edge_closest_feature = true;
1723-
for (int i = 0; i < 2; ++i) {
1724-
ccd_vec3_t origin_projection_to_plane = face_normals[i];
1725-
ccdVec3Scale(&(origin_projection_to_plane), -origin_to_face_distance[i]);
1726-
if (!is_point_on_line_segment(origin_projection_to_plane,
1727-
nearest_edge->vertex[0]->v.v,
1728-
nearest_edge->vertex[1]->v.v)) {
1729-
is_edge_closest_feature = false;
1730-
break;
1731-
}
1732-
}
1709+
1710+
// We know the reported squared distance to the edge. If that distance is
1711+
// functionally zero, then the edge must *truly* be the nearest feature.
1712+
// If it isn't, then it must be one of the adjacent faces.
1713+
const bool is_edge_closest_feature = nearest_edge->dist < kEps * kEps;
1714+
17331715
if (!is_edge_closest_feature) {
17341716
// We assume that libccd is not crazily wrong. Although the closest
17351717
// feature is not the edge, it is near that edge. Hence we select the
@@ -1779,7 +1761,7 @@ static int __ccdEPA(const void *obj1, const void *obj2,
17791761
return -2;
17801762
}
17811763

1782-
while (1){
1764+
while (1) {
17831765
// get triangle nearest to origin
17841766
*nearest = ccdPtNearest(polytope);
17851767
if (polytope->nearest_type == CCD_PT_EDGE) {
@@ -1791,14 +1773,13 @@ static int __ccdEPA(const void *obj1, const void *obj2,
17911773
*nearest = ccdPtNearest(polytope);
17921774
}
17931775

1794-
// get next support point
1795-
if (nextSupport(polytope, obj1, obj2, ccd, *nearest, &supp) != 0) {
1796-
break;
1797-
}
1776+
// get next support point
1777+
if (nextSupport(polytope, obj1, obj2, ccd, *nearest, &supp) != 0) {
1778+
break;
1779+
}
17981780

1799-
// expand nearest triangle using new point - supp
1800-
if (expandPolytope(polytope, *nearest, &supp) != 0)
1801-
return -2;
1781+
// expand nearest triangle using new point - supp
1782+
if (expandPolytope(polytope, *nearest, &supp) != 0) return -2;
18021783
}
18031784

18041785
return 0;

test/test_fcl_signed_distance.cpp

+56
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,60 @@ void test_distance_box_box_regression6() {
460460
test_distance_box_box_helper(box1_size, X_WB1, box2_size, X_WB2, &expected_distance);
461461
}
462462

463+
// This is a *specific* case that has cropped up in the wild. This error was
464+
// reported in https://github.com/flexible-collision-library/fcl/issues/493.
465+
// This includes the *first* scenario.
466+
template <typename S>
467+
void test_distance_box_box_regression7a() {
468+
const Vector3<S> box_size(0.5, 0.5, 0.5);
469+
// Both transforms have the same orientation.
470+
Matrix3<S> R_WB;
471+
// clang-format off
472+
R_WB <<
473+
0.96193976625564348026, 0.15401279915737359216, 0.22572537250328919556,
474+
-0.19134171618254486313, 0.96936494951283913579, 0.15401279915737359216,
475+
-0.19509032201612822033, -0.19134171618254486313, 0.96193976625564348026;
476+
// clang-format on
477+
Transform3<S> X_WB1 = Transform3<S>::Identity();
478+
X_WB1.linear() = R_WB;
479+
// clang-format off
480+
X_WB1.translation() <<
481+
2.0770063995786869349, 0.48468247475641951239, 1.1543291419087275962;
482+
// clang-format on
483+
Transform3<S> X_WB2 = Transform3<S>::Identity();
484+
X_WB2.linear() = R_WB;
485+
X_WB2.translation() << 2, 0, 1.25;
486+
const double expected_distance{0};
487+
test_distance_box_box_helper(box_size, X_WB1, box_size, X_WB2, &expected_distance);
488+
}
489+
490+
// This is a *specific* case that has cropped up in the wild. This error was
491+
// reported in https://github.com/flexible-collision-library/fcl/issues/493.
492+
// This includes the *second* scenario.
493+
template <typename S>
494+
void test_distance_box_box_regression7b() {
495+
const Vector3<S> box_size(0.25, 0.25, 0.25);
496+
// Both transforms have the same orientation.
497+
Matrix3<S> R_WB;
498+
// clang-format off
499+
R_WB <<
500+
0.95233240645725869555, 0.27969320707964251405, 0.12179777307008109177,
501+
-0.28888687719060901493, 0.95512127579560024415, 0.065480689593520297054,
502+
-0.098017140329560589751, -0.097545161008064124042, 0.99039264020161521529;
503+
// clang-format on
504+
Transform3<S> X_WB1 = Transform3<S>::Identity();
505+
X_WB1.linear() = R_WB;
506+
// clang-format off
507+
X_WB1.translation() <<
508+
0.069923301769910642389, 0.23878031894890006104, 1.2256137097479840037;
509+
// clang-format on
510+
Transform3<S> X_WB2 = Transform3<S>::Identity();
511+
X_WB2.linear() = R_WB;
512+
X_WB2.translation() << 0, 0, 1.25;
513+
const double expected_distance{0};
514+
test_distance_box_box_helper(box_size, X_WB1, box_size, X_WB2, &expected_distance);
515+
}
516+
463517
// This is a *specific* case that has cropped up in the wild that reaches the
464518
// unexpected `validateNearestFeatureOfPolytopeBeingEdge` error. This error was
465519
// reported in https://github.com/flexible-collision-library/fcl/issues/408
@@ -533,6 +587,8 @@ GTEST_TEST(FCL_SIGNED_DISTANCE, RealWorldRegression) {
533587
test_distance_box_box_regression4<double>();
534588
test_distance_box_box_regression5<double>();
535589
test_distance_box_box_regression6<double>();
590+
test_distance_box_box_regression7a<double>();
591+
test_distance_box_box_regression7b<double>();
536592
test_distance_sphere_box_regression1<double>();
537593
}
538594

0 commit comments

Comments
 (0)