Skip to content

Commit a5f9726

Browse files
committed
CPatchSolver: rewrite Sutherland-Hodgman algo to handle all the edge cases
The SH algo is super easy to understand; however we have to handle the edge cases carefully. The edge cases are mainly due to degenerate cases where we have segments and points instead of non-empty convex polygons. If not handled properly, these edge cases induce redundancies in the final polygon of SH. For example, if SH is ran on two triangles which have only one common point, SH will return an intersection polygon composed of 3 identical points (the common point of the triangles). We detect these cases by using a tolerance when computing determinants. This allows us to remove redundant points. Also, we deal with the special case of the segment-polygon.
1 parent d8f646c commit a5f9726

File tree

2 files changed

+174
-82
lines changed

2 files changed

+174
-82
lines changed

include/hpp/fcl/contact_patch/contact_patch_solver.h

-9
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,6 @@ struct HPP_FCL_DLLAPI ContactPatchSolver {
181181
void getResult(const ContactPatch::Polygon* result,
182182
ContactPatch& contact_patch) const;
183183

184-
/// @return true if p inside a clipping region defined by a and b, false
185-
/// otherwise.
186-
/// @param p point to check
187-
/// @param a, b the vertices forming the edge of the clipping region.
188-
/// @note the clipping ray points from a to b. Points on the right of the ray
189-
/// are outside the clipping region; points on the left are inside.
190-
static bool pointIsInsideClippingRegion(const Vec2f& p, const Vec2f& a,
191-
const Vec2f& b);
192-
193184
/// @return the intersecting point between line defined by ray (a, b) and
194185
/// the segment [c, d].
195186
/// @note we make the following hypothesis:

include/hpp/fcl/contact_patch/contact_patch_solver.hxx

+174-73
Original file line numberDiff line numberDiff line change
@@ -130,23 +130,25 @@ void ContactPatchSolver::computePatch(const ShapeType1& s1,
130130
return;
131131
}
132132

133+
// `eps` is be used to check strict positivity of determinants.
134+
static constexpr FCL_REAL eps = Eigen::NumTraits<FCL_REAL>::dummy_precision();
135+
using Polygon = SupportSet::Polygon;
136+
133137
if ((this->support_set_shape1.size() == 2) &&
134138
(this->support_set_shape2.size() == 2)) {
135139
// Segment-Segment case
136140
// We compute the determinant; if it is non-zero, the intersection
137141
// has already been computed: it's `Contact::pos`.
138-
const SupportSet::Polygon& pts1 = this->support_set_shape1.points();
142+
const Polygon& pts1 = this->support_set_shape1.points();
139143
const Vec2f& a = pts1[0];
140144
const Vec2f& b = pts1[1];
141145

142-
const SupportSet::Polygon& pts2 = this->support_set_shape2.points();
146+
const Polygon& pts2 = this->support_set_shape2.points();
143147
const Vec2f& c = pts2[0];
144148
const Vec2f& d = pts2[1];
145149

146150
const FCL_REAL det =
147151
(b(0) - a(0)) * (d(1) - c(1)) >= (b(1) - a(1)) * (d(0) - c(0));
148-
static constexpr FCL_REAL eps =
149-
Eigen::NumTraits<FCL_REAL>::dummy_precision();
150152
if ((std::abs(det) > eps) || ((c - d).squaredNorm() < eps) ||
151153
((b - a).squaredNorm() < eps)) {
152154
contact_patch.addPoint(contact.pos);
@@ -155,7 +157,7 @@ void ContactPatchSolver::computePatch(const ShapeType1& s1,
155157

156158
const Vec2f cd = (d - c);
157159
const FCL_REAL l = cd.squaredNorm();
158-
ContactPatch::Polygon& patch = contact_patch.points();
160+
Polygon& patch = contact_patch.points();
159161

160162
// Project a onto [c, d]
161163
FCL_REAL t1 = (a - c).dot(cd);
@@ -174,69 +176,188 @@ void ContactPatchSolver::computePatch(const ShapeType1& s1,
174176
}
175177

176178
//
177-
// Step 3 - Main loop of the algorithm: use the "clipper"
178-
// to clip the current contact patch. The resulting intersection is the
179-
// contact patch of the contact between s1 and s2.
180-
// Currently, to clip one patch with the other, we use the
181-
// Sutherland-Hodgman algorithm:
179+
// Step 3 - Main loop of the algorithm: use the "clipper" polygon to clip the
180+
// "current" polygon. The resulting intersection is the contact patch of the
181+
// contact between s1 and s2. "clipper" and "current" are the support sets of
182+
// shape1 and shape2 (they can be swapped, i.e. clipper can be assigned to
183+
// shape1 and current to shape2, depending on which case we are). Currently,
184+
// to clip one polygon with the other, we use the Sutherland-Hodgman
185+
// algorithm:
182186
// https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
187+
// In the general case, Sutherland-Hodgman clips one polygon of size >=3 using
188+
// another polygon of size >=3. However, it can be easily extended to handle
189+
// the segment-polygon case.
183190
//
184-
// The Sutherland-Hodgman algorithm needs the clipper to be at least a
185-
// triangle.
186-
SupportSet::Polygon* clipper_ptr = nullptr;
187-
SupportSet::Polygon* current_ptr = nullptr;
188-
SupportSet::Polygon* previous_ptr = &(this->support_set_buffer.points());
189-
if (support_set_shape2.size() == 2) {
190-
// Use the first shape as clipper, second as clipped.
191-
clipper_ptr = &(this->support_set_shape1.points());
192-
current_ptr = &(this->support_set_shape2.points());
193-
} else {
194-
// Use the second shape as clipper, first as clipped.
195-
clipper_ptr = &(this->support_set_shape2.points());
191+
// The maximum size of the output of the Sutherland-Hodgman algorithm is n1 +
192+
// n2 where n1 and n2 are the sizes of the first and second polygon.
193+
const size_t max_result_size =
194+
this->support_set_shape1.size() + this->support_set_shape2.size();
195+
if (this->added_to_patch.size() < max_result_size) {
196+
this->added_to_patch.assign(max_result_size, false);
197+
}
198+
199+
const Polygon* clipper_ptr = nullptr;
200+
Polygon* current_ptr = nullptr;
201+
Polygon* previous_ptr = &(this->support_set_buffer.points());
202+
203+
// Let the clipper set be the one with the most vertices, to make sure it is
204+
// at least a triangle.
205+
if (this->support_set_shape1.size() < this->support_set_shape2.size()) {
196206
current_ptr = &(this->support_set_shape1.points());
207+
clipper_ptr = &(this->support_set_shape2.points());
208+
} else {
209+
current_ptr = &(this->support_set_shape2.points());
210+
clipper_ptr = &(this->support_set_shape1.points());
197211
}
198212

199-
const SupportSet::Polygon& clipper = *(clipper_ptr);
213+
const Polygon& clipper = *(clipper_ptr);
200214
const size_t clipper_size = clipper.size();
201-
202215
for (size_t i = 0; i < clipper_size; ++i) {
203-
const Vec2f a = clipper[i];
204-
const Vec2f b = clipper[(i + 1) % clipper_size];
205-
206-
// Swap current/previous
207-
SupportSet::Polygon* tmp_ptr = previous_ptr;
216+
// Swap `current` and `previous`.
217+
// `previous` tracks the last iteration of the algorithm; `current` is
218+
// filled by clipping `current` using `clipper`.
219+
Polygon* tmp_ptr = previous_ptr;
208220
previous_ptr = current_ptr;
209221
current_ptr = tmp_ptr;
210222

211-
const SupportSet::Polygon& previous = *(previous_ptr);
212-
SupportSet::Polygon& current = *(current_ptr);
223+
const Polygon& previous = *(previous_ptr);
224+
Polygon& current = *(current_ptr);
213225
current.clear();
214-
const size_t previous_size = previous.size();
215-
for (size_t j = 0; j < previous_size; ++j) {
216-
const Vec2f vcurrent = previous[j];
217-
const Vec2f vnext = previous[(j + 1) % previous_size];
218-
if (pointIsInsideClippingRegion(vcurrent, a, b)) {
219-
current.emplace_back(vcurrent);
220-
if (!pointIsInsideClippingRegion(vnext, a, b)) {
221-
const Vec2f p = computeLineSegmentIntersection(a, b, vcurrent, vnext);
226+
227+
const Vec2f& a = clipper[i];
228+
const Vec2f& b = clipper[(i + 1) % clipper_size];
229+
const Vec2f ab = b - a;
230+
231+
if (previous.size() == 2) {
232+
//
233+
// Segment-Polygon case
234+
//
235+
const Vec2f& p1 = previous[0];
236+
const Vec2f& p2 = previous[1];
237+
238+
const Vec2f ap1 = p1 - a;
239+
const Vec2f ap2 = p2 - a;
240+
241+
const FCL_REAL det1 = ab(0) * ap1(1) - ab(1) * ap1(0);
242+
const FCL_REAL det2 = ab(0) * ap2(1) - ab(1) * ap2(0);
243+
244+
if (det1 < 0 && det2 < 0) {
245+
// Both p1 and p2 are outside the clipping polygon, i.e. there is no
246+
// intersection. The algorithm can stop.
247+
break;
248+
}
249+
250+
if (det1 >= 0 && det2 >= 0) {
251+
// Both p1 and p2 are inside the clipping polygon, there is nothing to
252+
// do; move to the next iteration.
253+
current = previous;
254+
continue;
255+
}
256+
257+
// Compute the intersection between the line (a, b) and the segment
258+
// [p1, p2].
259+
if (det1 >= 0) {
260+
if (det1 > eps) {
261+
const Vec2f p = computeLineSegmentIntersection(a, b, p1, p2);
262+
current.emplace_back(p1);
263+
current.emplace_back(p);
264+
continue;
265+
} else {
266+
// p1 is the only point of current which is also a point of the
267+
// clipper. We can exit.
268+
current.emplace_back(p1);
269+
break;
270+
}
271+
} else {
272+
if (det2 > eps) {
273+
const Vec2f p = computeLineSegmentIntersection(a, b, p1, p2);
274+
current.emplace_back(p2);
222275
current.emplace_back(p);
276+
continue;
277+
} else {
278+
// p2 is the only point of current which is also a point of the
279+
// clipper. We can exit.
280+
current.emplace_back(p2);
281+
break;
282+
}
283+
}
284+
} else {
285+
//
286+
// Polygon-Polygon case.
287+
//
288+
std::fill(this->added_to_patch.begin(), //
289+
this->added_to_patch.end(), //
290+
false);
291+
292+
const size_t previous_size = previous.size();
293+
for (size_t j = 0; j < previous_size; ++j) {
294+
const Vec2f& p1 = previous[j];
295+
const Vec2f& p2 = previous[(j + 1) % previous_size];
296+
297+
const Vec2f ap1 = p1 - a;
298+
const Vec2f ap2 = p2 - a;
299+
300+
const FCL_REAL det1 = ab(0) * ap1(1) - ab(1) * ap1(0);
301+
const FCL_REAL det2 = ab(0) * ap2(1) - ab(1) * ap2(0);
302+
303+
if (det1 < 0 && det2 < 0) {
304+
// No intersection. Continue to next segment of previous.
305+
continue;
306+
}
307+
308+
if (det1 >= 0 && det2 >= 0) {
309+
// Both p1 and p2 are inside the clipping polygon, add p1 to current
310+
// (only if it has not already been added).
311+
if (!this->added_to_patch[j]) {
312+
current.emplace_back(p1);
313+
this->added_to_patch[j] = true;
314+
}
315+
// Continue to next segment of previous.
316+
continue;
317+
}
318+
319+
if (det1 >= 0) {
320+
if (det1 > eps) {
321+
if (!this->added_to_patch[j]) {
322+
current.emplace_back(p1);
323+
this->added_to_patch[j] = true;
324+
}
325+
const Vec2f p = computeLineSegmentIntersection(a, b, p1, p2);
326+
current.emplace_back(p);
327+
} else {
328+
// a, b and p1 are colinear; we add only p1.
329+
if (!this->added_to_patch[j]) {
330+
current.emplace_back(p1);
331+
this->added_to_patch[j] = true;
332+
}
333+
}
334+
} else {
335+
if (det2 > eps) {
336+
const Vec2f p = computeLineSegmentIntersection(a, b, p1, p2);
337+
current.emplace_back(p);
338+
} else {
339+
if (!this->added_to_patch[(j + 1) % previous.size()]) {
340+
current.emplace_back(p2);
341+
this->added_to_patch[(j + 1) % previous.size()] = true;
342+
}
343+
}
223344
}
224-
} else if (pointIsInsideClippingRegion(vnext, a, b)) {
225-
const Vec2f p = computeLineSegmentIntersection(a, b, vcurrent, vnext);
226-
current.emplace_back(p);
227345
}
228346
}
229-
if (current.size() == 0) {
230-
// No intersection found, the algo can early stop.
347+
//
348+
// End of iteration i of Sutherland-Hodgman.
349+
if (current.size() <= 1) {
350+
// No intersection or one point found, the algo can early stop.
231351
break;
232352
}
233353
}
234354

355+
// Transfer the result of the Sutherland-Hodgman algorithm to the contact
356+
// patch.
235357
if (current_ptr->size() <= 1) {
236358
contact_patch.addPoint(contact.pos);
237359
return;
238360
}
239-
240361
this->getResult(current_ptr, contact_patch);
241362
}
242363

@@ -249,21 +370,20 @@ inline void ContactPatchSolver::getResult(
249370
ContactPatch::Polygon& patch = contact_patch.points();
250371

251372
if (result.size() <= this->max_patch_size) {
252-
patch.emplace_back(result[0]);
253-
for (size_t i = 1; i < result.size(); ++i) {
254-
if ((patch.back() - result[i]).squaredNorm() >
255-
Eigen::NumTraits<FCL_REAL>::dummy_precision()) {
256-
patch.emplace_back(result[i]);
257-
}
258-
}
373+
patch = result;
259374
return;
260375
}
261376

262377
// Post-processing step to select `max_patch_size` points of the computed
263378
// contact patch.
264379
// We simply select `max_patch_size` points of the patch by sampling the
265380
// 2d support function of the patch along the unit circle.
266-
this->added_to_patch.assign(result.size(), false);
381+
if (this->added_to_patch.size() < result.size()) {
382+
this->added_to_patch.assign(result.size(), false);
383+
} else {
384+
std::fill(this->added_to_patch.begin(), this->added_to_patch.end(), false);
385+
}
386+
267387
const FCL_REAL angle_increment =
268388
2.0 * (FCL_REAL)(EIGEN_PI) / ((FCL_REAL)(this->max_patch_size));
269389
for (size_t i = 0; i < this->max_patch_size; ++i) {
@@ -279,14 +399,7 @@ inline void ContactPatchSolver::getResult(
279399
}
280400
}
281401
if (!this->added_to_patch[support_idx]) {
282-
if (patch.empty()) {
283-
patch.emplace_back(result[support_idx]);
284-
} else {
285-
if ((patch.back() - result[support_idx]).squaredNorm() >
286-
Eigen::NumTraits<FCL_REAL>::dummy_precision()) {
287-
patch.emplace_back(result[support_idx]);
288-
}
289-
}
402+
patch.emplace_back(result[support_idx]);
290403
this->added_to_patch[support_idx] = true;
291404
}
292405
}
@@ -343,18 +456,6 @@ inline Vec2f ContactPatchSolver::computeLineSegmentIntersection(
343456
return alpha * c + (1 - alpha) * d;
344457
}
345458

346-
// ==========================================================================
347-
inline bool ContactPatchSolver::pointIsInsideClippingRegion(const Vec2f& p,
348-
const Vec2f& a,
349-
const Vec2f& b) {
350-
// Note: being inside/outside the clipping zone can easily be determined by
351-
// looking at the sign of det(b - a, p - a). If det > 0, then (b - a, p - a)
352-
// forms a right sided base, i.e. p is on the right of the ray.
353-
// Otherwise (b - a, p - a) forms a left sided base, i.e. p is on the left of
354-
// the ray.
355-
return (b(0) - a(0)) * (p(1) - a(1)) >= (b(1) - a(1)) * (p(0) - a(0));
356-
}
357-
358459
} // namespace fcl
359460
} // namespace hpp
360461

0 commit comments

Comments
 (0)