@@ -130,23 +130,25 @@ void ContactPatchSolver::computePatch(const ShapeType1& s1,
130
130
return ;
131
131
}
132
132
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
+
133
137
if ((this ->support_set_shape1 .size () == 2 ) &&
134
138
(this ->support_set_shape2 .size () == 2 )) {
135
139
// Segment-Segment case
136
140
// We compute the determinant; if it is non-zero, the intersection
137
141
// 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 ();
139
143
const Vec2f& a = pts1[0 ];
140
144
const Vec2f& b = pts1[1 ];
141
145
142
- const SupportSet:: Polygon& pts2 = this ->support_set_shape2 .points ();
146
+ const Polygon& pts2 = this ->support_set_shape2 .points ();
143
147
const Vec2f& c = pts2[0 ];
144
148
const Vec2f& d = pts2[1 ];
145
149
146
150
const FCL_REAL det =
147
151
(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 ();
150
152
if ((std::abs (det) > eps) || ((c - d).squaredNorm () < eps) ||
151
153
((b - a).squaredNorm () < eps)) {
152
154
contact_patch.addPoint (contact.pos );
@@ -155,7 +157,7 @@ void ContactPatchSolver::computePatch(const ShapeType1& s1,
155
157
156
158
const Vec2f cd = (d - c);
157
159
const FCL_REAL l = cd.squaredNorm ();
158
- ContactPatch:: Polygon& patch = contact_patch.points ();
160
+ Polygon& patch = contact_patch.points ();
159
161
160
162
// Project a onto [c, d]
161
163
FCL_REAL t1 = (a - c).dot (cd);
@@ -174,69 +176,188 @@ void ContactPatchSolver::computePatch(const ShapeType1& s1,
174
176
}
175
177
176
178
//
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:
182
186
// 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.
183
190
//
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 ()) {
196
206
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 ());
197
211
}
198
212
199
- const SupportSet:: Polygon& clipper = *(clipper_ptr);
213
+ const Polygon& clipper = *(clipper_ptr);
200
214
const size_t clipper_size = clipper.size ();
201
-
202
215
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;
208
220
previous_ptr = current_ptr;
209
221
current_ptr = tmp_ptr;
210
222
211
- const SupportSet:: Polygon& previous = *(previous_ptr);
212
- SupportSet:: Polygon& current = *(current_ptr);
223
+ const Polygon& previous = *(previous_ptr);
224
+ Polygon& current = *(current_ptr);
213
225
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);
222
275
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
+ }
223
344
}
224
- } else if (pointIsInsideClippingRegion (vnext, a, b)) {
225
- const Vec2f p = computeLineSegmentIntersection (a, b, vcurrent, vnext);
226
- current.emplace_back (p);
227
345
}
228
346
}
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.
231
351
break ;
232
352
}
233
353
}
234
354
355
+ // Transfer the result of the Sutherland-Hodgman algorithm to the contact
356
+ // patch.
235
357
if (current_ptr->size () <= 1 ) {
236
358
contact_patch.addPoint (contact.pos );
237
359
return ;
238
360
}
239
-
240
361
this ->getResult (current_ptr, contact_patch);
241
362
}
242
363
@@ -249,21 +370,20 @@ inline void ContactPatchSolver::getResult(
249
370
ContactPatch::Polygon& patch = contact_patch.points ();
250
371
251
372
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;
259
374
return ;
260
375
}
261
376
262
377
// Post-processing step to select `max_patch_size` points of the computed
263
378
// contact patch.
264
379
// We simply select `max_patch_size` points of the patch by sampling the
265
380
// 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
+
267
387
const FCL_REAL angle_increment =
268
388
2.0 * (FCL_REAL)(EIGEN_PI) / ((FCL_REAL)(this ->max_patch_size ));
269
389
for (size_t i = 0 ; i < this ->max_patch_size ; ++i) {
@@ -279,14 +399,7 @@ inline void ContactPatchSolver::getResult(
279
399
}
280
400
}
281
401
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]);
290
403
this ->added_to_patch [support_idx] = true ;
291
404
}
292
405
}
@@ -343,18 +456,6 @@ inline Vec2f ContactPatchSolver::computeLineSegmentIntersection(
343
456
return alpha * c + (1 - alpha) * d;
344
457
}
345
458
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
-
358
459
} // namespace fcl
359
460
} // namespace hpp
360
461
0 commit comments