Skip to content

Commit f7dcddf

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 97cf77d commit f7dcddf

File tree

3 files changed

+95
-46
lines changed

3 files changed

+95
-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

+35-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 =
@@ -1701,6 +1681,14 @@ static void validateNearestFeatureOfPolytopeBeingEdge(ccd_pt_t* polytope) {
17011681
// normalized.
17021682
std::array<ccd_vec3_t, 2> face_normals;
17031683
std::array<double, 2> origin_to_face_distance;
1684+
1685+
// We define the plane equation using vertex[0]. If vertex[0] is far away
1686+
// from the origin, it can magnify rounding error. We scale epsilon to account
1687+
// for this possibility.
1688+
const ccd_real_t v0_dist =
1689+
std::sqrt(ccdVec3Len2(&nearest_edge->vertex[0]->v.v));
1690+
const ccd_real_t plane_threshold = kEps * std::max(1.0, v0_dist);
1691+
17041692
for (int i = 0; i < 2; ++i) {
17051693
face_normals[i] =
17061694
faceNormalPointingOutward(polytope, nearest_edge->faces[i]);
@@ -1709,27 +1697,29 @@ static void validateNearestFeatureOfPolytopeBeingEdge(ccd_pt_t* polytope) {
17091697
// n̂ ⋅ (o - vₑ) ≤ 0 or, with simplification, -n̂ ⋅ vₑ ≤ 0 (since n̂ ⋅ o = 0).
17101698
origin_to_face_distance[i] =
17111699
-ccdVec3Dot(&face_normals[i], &nearest_edge->vertex[0]->v.v);
1712-
if (origin_to_face_distance[i] > 0) {
1700+
// If the origin lies *on* the edge, then it also lies on the two adjacent
1701+
// faces. Rather than failing on strictly *positive* signed distance, we
1702+
// introduce an epsilon threshold. This usage of epsilon is to account for a
1703+
// discrepancy in the signed distance computation. How GJK (and partially
1704+
// EPA) compute the signed distance from origin to face may *not* be exactly
1705+
// the same as done in this test (i.e. for a given set of vertices, the
1706+
// plane equation can be defined various ways. Those ways are
1707+
// *mathematically* equivalent but numerically will differ due to rounding).
1708+
// We account for those differences by allowing a very small positive signed
1709+
// distance to be considered zero. We assume that the GJK/EPA code
1710+
// ultimately classifies inside/outside around *zero* and *not* epsilon.
1711+
if (origin_to_face_distance[i] > plane_threshold) {
17131712
FCL_THROW_FAILED_AT_THIS_CONFIGURATION(
17141713
"The origin is outside of the polytope. This should already have "
17151714
"been identified as separating.");
17161715
}
17171716
}
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-
}
1717+
1718+
// We know the reported squared distance to the edge. If that distance is
1719+
// functionally zero, then the edge must *truly* be the nearest feature.
1720+
// If it isn't, then it must be one of the adjacent faces.
1721+
const bool is_edge_closest_feature = nearest_edge->dist < kEps * kEps;
1722+
17331723
if (!is_edge_closest_feature) {
17341724
// We assume that libccd is not crazily wrong. Although the closest
17351725
// feature is not the edge, it is near that edge. Hence we select the
@@ -1779,7 +1769,7 @@ static int __ccdEPA(const void *obj1, const void *obj2,
17791769
return -2;
17801770
}
17811771

1782-
while (1){
1772+
while (1) {
17831773
// get triangle nearest to origin
17841774
*nearest = ccdPtNearest(polytope);
17851775
if (polytope->nearest_type == CCD_PT_EDGE) {
@@ -1791,14 +1781,13 @@ static int __ccdEPA(const void *obj1, const void *obj2,
17911781
*nearest = ccdPtNearest(polytope);
17921782
}
17931783

1794-
// get next support point
1795-
if (nextSupport(polytope, obj1, obj2, ccd, *nearest, &supp) != 0) {
1796-
break;
1797-
}
1784+
// get next support point
1785+
if (nextSupport(polytope, obj1, obj2, ccd, *nearest, &supp) != 0) {
1786+
break;
1787+
}
17981788

1799-
// expand nearest triangle using new point - supp
1800-
if (expandPolytope(polytope, *nearest, &supp) != 0)
1801-
return -2;
1789+
// expand nearest triangle using new point - supp
1790+
if (expandPolytope(polytope, *nearest, &supp) != 0) return -2;
18021791
}
18031792

18041793
return 0;

test/test_fcl_signed_distance.cpp

+58
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,62 @@ 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,
488+
&expected_distance);
489+
}
490+
491+
// This is a *specific* case that has cropped up in the wild. This error was
492+
// reported in https://github.com/flexible-collision-library/fcl/issues/493.
493+
// This includes the *second* scenario.
494+
template <typename S>
495+
void test_distance_box_box_regression7b() {
496+
const Vector3<S> box_size(0.25, 0.25, 0.25);
497+
// Both transforms have the same orientation.
498+
Matrix3<S> R_WB;
499+
// clang-format off
500+
R_WB <<
501+
0.95233240645725869555, 0.27969320707964251405, 0.12179777307008109177,
502+
-0.28888687719060901493, 0.95512127579560024415, 0.065480689593520297054,
503+
-0.098017140329560589751, -0.097545161008064124042, 0.99039264020161521529;
504+
// clang-format on
505+
Transform3<S> X_WB1 = Transform3<S>::Identity();
506+
X_WB1.linear() = R_WB;
507+
// clang-format off
508+
X_WB1.translation() <<
509+
0.069923301769910642389, 0.23878031894890006104, 1.2256137097479840037;
510+
// clang-format on
511+
Transform3<S> X_WB2 = Transform3<S>::Identity();
512+
X_WB2.linear() = R_WB;
513+
X_WB2.translation() << 0, 0, 1.25;
514+
const double expected_distance{0};
515+
test_distance_box_box_helper(box_size, X_WB1, box_size, X_WB2,
516+
&expected_distance);
517+
}
518+
463519
// This is a *specific* case that has cropped up in the wild that reaches the
464520
// unexpected `validateNearestFeatureOfPolytopeBeingEdge` error. This error was
465521
// reported in https://github.com/flexible-collision-library/fcl/issues/408
@@ -533,6 +589,8 @@ GTEST_TEST(FCL_SIGNED_DISTANCE, RealWorldRegression) {
533589
test_distance_box_box_regression4<double>();
534590
test_distance_box_box_regression5<double>();
535591
test_distance_box_box_regression6<double>();
592+
test_distance_box_box_regression7a<double>();
593+
test_distance_box_box_regression7b<double>();
536594
test_distance_sphere_box_regression1<double>();
537595
}
538596

0 commit comments

Comments
 (0)