hpp-fcl  3.0.0
HPP fork of FCL -- The Flexible Collision Library
contact_patch_solver.hxx
Go to the documentation of this file.
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2024, INRIA
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of INRIA nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34 
37 #ifndef HPP_FCL_CONTACT_PATCH_SOLVER_HXX
38 #define HPP_FCL_CONTACT_PATCH_SOLVER_HXX
39 
40 #include "hpp/fcl/data_types.h"
42 
43 namespace hpp {
44 namespace fcl {
45 
46 // ============================================================================
47 inline void ContactPatchSolver::set(const ContactPatchRequest& request) {
48  // Note: it's important for the number of pre-allocated Vec3f in
49  // `m_clipping_sets` to be larger than `request.max_size_patch`
50  // because we don't know in advance how many supports will be discarded to
51  // form the convex-hulls of the shapes supports which will serve as the
52  // input of the Sutherland-Hodgman algorithm.
53  size_t num_preallocated_supports = default_num_preallocated_supports;
54  if (num_preallocated_supports < 2 * request.getNumSamplesCurvedShapes()) {
55  num_preallocated_supports = 2 * request.getNumSamplesCurvedShapes();
56  }
57 
58  // Used for support set computation of shape1 and for the first iterate of the
59  // Sutherland-Hodgman algo.
60  this->support_set_shape1.points().reserve(num_preallocated_supports);
61  this->support_set_shape1.direction = SupportSetDirection::DEFAULT;
62 
63  // Used for computing the next iterate of the Sutherland-Hodgman algo.
64  this->support_set_buffer.points().reserve(num_preallocated_supports);
65 
66  // Used for support set computation of shape2 and acts as the "clipper" set in
67  // the Sutherland-Hodgman algo.
68  this->support_set_shape2.points().reserve(num_preallocated_supports);
69  this->support_set_shape2.direction = SupportSetDirection::INVERTED;
70 
72  this->patch_tolerance = request.getPatchTolerance();
73 }
74 
75 // ============================================================================
76 template <typename ShapeType1, typename ShapeType2>
77 void ContactPatchSolver::computePatch(const ShapeType1& s1,
78  const Transform3f& tf1,
79  const ShapeType2& s2,
80  const Transform3f& tf2,
81  const Contact& contact,
82  ContactPatch& contact_patch) const {
83  // Note: `ContactPatch` is an alias for `SupportSet`.
84  // Step 1
85  constructContactPatchFrameFromContact(contact, contact_patch);
86  contact_patch.points().clear();
89  // If a shape is strictly convex, the support set in any direction is
90  // reduced to a single point. Thus, the contact point `contact.pos` is the
91  // only point belonging to the contact patch, and it has already been
92  // computed.
93  // TODO(louis): even for strictly convex shapes, we can sample the support
94  // function around the normal and return a pseudo support set. This would
95  // allow spheres and ellipsoids to have a contact surface, which does make
96  // sense in certain physics simulation cases.
97  // Do the same for strictly convex regions of non-strictly convex shapes
98  // like the ends of capsules.
99  contact_patch.addPoint(contact.pos);
100  return;
101  }
102 
103  // Step 2 - Compute support set of each shape, in the direction of
104  // the contact's normal.
105  // The first shape's support set is called "current"; it will be the first
106  // iterate of the Sutherland-Hodgman algorithm. The second shape's support set
107  // is called "clipper"; it will be used to clip "current". The support set
108  // computation step computes a convex polygon; its vertices are ordered
109  // counter-clockwise. This is important as the Sutherland-Hodgman algorithm
110  // expects points to be ranked counter-clockwise.
111  this->reset(s1, tf1, s2, tf2, contact_patch);
112  assert(this->num_samples_curved_shapes > 3);
113 
114  this->supportFuncShape1(&s1, this->support_set_shape1, this->support_guess[0],
115  this->supports_data[0],
117  this->patch_tolerance);
118 
119  this->supportFuncShape2(&s2, this->support_set_shape2, this->support_guess[1],
120  this->supports_data[1],
122  this->patch_tolerance);
123 
124  // We can immediatly return if one of the support set has only
125  // one point.
126  if (this->support_set_shape1.size() <= 1 ||
127  this->support_set_shape2.size() <= 1) {
128  contact_patch.addPoint(contact.pos);
129  return;
130  }
131 
132  // `eps` is be used to check strict positivity of determinants.
133  const FCL_REAL eps = Eigen::NumTraits<FCL_REAL>::dummy_precision();
134  using Polygon = SupportSet::Polygon;
135 
136  if ((this->support_set_shape1.size() == 2) &&
137  (this->support_set_shape2.size() == 2)) {
138  // Segment-Segment case
139  // We compute the determinant; if it is non-zero, the intersection
140  // has already been computed: it's `Contact::pos`.
141  const Polygon& pts1 = this->support_set_shape1.points();
142  const Vec2f& a = pts1[0];
143  const Vec2f& b = pts1[1];
144 
145  const Polygon& pts2 = this->support_set_shape2.points();
146  const Vec2f& c = pts2[0];
147  const Vec2f& d = pts2[1];
148 
149  const FCL_REAL det =
150  (b(0) - a(0)) * (d(1) - c(1)) >= (b(1) - a(1)) * (d(0) - c(0));
151  if ((std::abs(det) > eps) || ((c - d).squaredNorm() < eps) ||
152  ((b - a).squaredNorm() < eps)) {
153  contact_patch.addPoint(contact.pos);
154  return;
155  }
156 
157  const Vec2f cd = (d - c);
158  const FCL_REAL l = cd.squaredNorm();
159  Polygon& patch = contact_patch.points();
160 
161  // Project a onto [c, d]
162  FCL_REAL t1 = (a - c).dot(cd);
163  t1 = (t1 >= l) ? 1.0 : ((t1 <= 0) ? 0.0 : (t1 / l));
164  const Vec2f p1 = c + t1 * cd;
165  patch.emplace_back(p1);
166 
167  // Project b onto [c, d]
168  FCL_REAL t2 = (b - c).dot(cd);
169  t2 = (t2 >= l) ? 1.0 : ((t2 <= 0) ? 0.0 : (t2 / l));
170  const Vec2f p2 = c + t2 * cd;
171  if ((p1 - p2).squaredNorm() >= eps) {
172  patch.emplace_back(p2);
173  }
174  return;
175  }
176 
177  //
178  // Step 3 - Main loop of the algorithm: use the "clipper" polygon to clip the
179  // "current" polygon. The resulting intersection is the contact patch of the
180  // contact between s1 and s2. "clipper" and "current" are the support sets of
181  // shape1 and shape2 (they can be swapped, i.e. clipper can be assigned to
182  // shape1 and current to shape2, depending on which case we are). Currently,
183  // to clip one polygon with the other, we use the Sutherland-Hodgman
184  // algorithm:
185  // https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
186  // In the general case, Sutherland-Hodgman clips one polygon of size >=3 using
187  // another polygon of size >=3. However, it can be easily extended to handle
188  // the segment-polygon case.
189  //
190  // The maximum size of the output of the Sutherland-Hodgman algorithm is n1 +
191  // n2 where n1 and n2 are the sizes of the first and second polygon.
192  const size_t max_result_size =
194  if (this->added_to_patch.size() < max_result_size) {
195  this->added_to_patch.assign(max_result_size, false);
196  }
197 
198  const Polygon* clipper_ptr = nullptr;
199  Polygon* current_ptr = nullptr;
200  Polygon* previous_ptr = &(this->support_set_buffer.points());
201 
202  // Let the clipper set be the one with the most vertices, to make sure it is
203  // at least a triangle.
204  if (this->support_set_shape1.size() < this->support_set_shape2.size()) {
205  current_ptr = &(this->support_set_shape1.points());
206  clipper_ptr = &(this->support_set_shape2.points());
207  } else {
208  current_ptr = &(this->support_set_shape2.points());
209  clipper_ptr = &(this->support_set_shape1.points());
210  }
211 
212  const Polygon& clipper = *(clipper_ptr);
213  const size_t clipper_size = clipper.size();
214  for (size_t i = 0; i < clipper_size; ++i) {
215  // Swap `current` and `previous`.
216  // `previous` tracks the last iteration of the algorithm; `current` is
217  // filled by clipping `current` using `clipper`.
218  Polygon* tmp_ptr = previous_ptr;
219  previous_ptr = current_ptr;
220  current_ptr = tmp_ptr;
221 
222  const Polygon& previous = *(previous_ptr);
223  Polygon& current = *(current_ptr);
224  current.clear();
225 
226  const Vec2f& a = clipper[i];
227  const Vec2f& b = clipper[(i + 1) % clipper_size];
228  const Vec2f ab = b - a;
229 
230  if (previous.size() == 2) {
231  //
232  // Segment-Polygon case
233  //
234  const Vec2f& p1 = previous[0];
235  const Vec2f& p2 = previous[1];
236 
237  const Vec2f ap1 = p1 - a;
238  const Vec2f ap2 = p2 - a;
239 
240  const FCL_REAL det1 = ab(0) * ap1(1) - ab(1) * ap1(0);
241  const FCL_REAL det2 = ab(0) * ap2(1) - ab(1) * ap2(0);
242 
243  if (det1 < 0 && det2 < 0) {
244  // Both p1 and p2 are outside the clipping polygon, i.e. there is no
245  // intersection. The algorithm can stop.
246  break;
247  }
248 
249  if (det1 >= 0 && det2 >= 0) {
250  // Both p1 and p2 are inside the clipping polygon, there is nothing to
251  // do; move to the next iteration.
252  current = previous;
253  continue;
254  }
255 
256  // Compute the intersection between the line (a, b) and the segment
257  // [p1, p2].
258  if (det1 >= 0) {
259  if (det1 > eps) {
260  const Vec2f p = computeLineSegmentIntersection(a, b, p1, p2);
261  current.emplace_back(p1);
262  current.emplace_back(p);
263  continue;
264  } else {
265  // p1 is the only point of current which is also a point of the
266  // clipper. We can exit.
267  current.emplace_back(p1);
268  break;
269  }
270  } else {
271  if (det2 > eps) {
272  const Vec2f p = computeLineSegmentIntersection(a, b, p1, p2);
273  current.emplace_back(p2);
274  current.emplace_back(p);
275  continue;
276  } else {
277  // p2 is the only point of current which is also a point of the
278  // clipper. We can exit.
279  current.emplace_back(p2);
280  break;
281  }
282  }
283  } else {
284  //
285  // Polygon-Polygon case.
286  //
287  std::fill(this->added_to_patch.begin(), //
288  this->added_to_patch.end(), //
289  false);
290 
291  const size_t previous_size = previous.size();
292  for (size_t j = 0; j < previous_size; ++j) {
293  const Vec2f& p1 = previous[j];
294  const Vec2f& p2 = previous[(j + 1) % previous_size];
295 
296  const Vec2f ap1 = p1 - a;
297  const Vec2f ap2 = p2 - a;
298 
299  const FCL_REAL det1 = ab(0) * ap1(1) - ab(1) * ap1(0);
300  const FCL_REAL det2 = ab(0) * ap2(1) - ab(1) * ap2(0);
301 
302  if (det1 < 0 && det2 < 0) {
303  // No intersection. Continue to next segment of previous.
304  continue;
305  }
306 
307  if (det1 >= 0 && det2 >= 0) {
308  // Both p1 and p2 are inside the clipping polygon, add p1 to current
309  // (only if it has not already been added).
310  if (!this->added_to_patch[j]) {
311  current.emplace_back(p1);
312  this->added_to_patch[j] = true;
313  }
314  // Continue to next segment of previous.
315  continue;
316  }
317 
318  if (det1 >= 0) {
319  if (det1 > eps) {
320  if (!this->added_to_patch[j]) {
321  current.emplace_back(p1);
322  this->added_to_patch[j] = true;
323  }
324  const Vec2f p = computeLineSegmentIntersection(a, b, p1, p2);
325  current.emplace_back(p);
326  } else {
327  // a, b and p1 are colinear; we add only p1.
328  if (!this->added_to_patch[j]) {
329  current.emplace_back(p1);
330  this->added_to_patch[j] = true;
331  }
332  }
333  } else {
334  if (det2 > eps) {
335  const Vec2f p = computeLineSegmentIntersection(a, b, p1, p2);
336  current.emplace_back(p);
337  } else {
338  if (!this->added_to_patch[(j + 1) % previous.size()]) {
339  current.emplace_back(p2);
340  this->added_to_patch[(j + 1) % previous.size()] = true;
341  }
342  }
343  }
344  }
345  }
346  //
347  // End of iteration i of Sutherland-Hodgman.
348  if (current.size() <= 1) {
349  // No intersection or one point found, the algo can early stop.
350  break;
351  }
352  }
353 
354  // Transfer the result of the Sutherland-Hodgman algorithm to the contact
355  // patch.
356  this->getResult(contact, current_ptr, contact_patch);
357 }
358 
359 // ============================================================================
361  const Contact& contact, const ContactPatch::Polygon* result_ptr,
362  ContactPatch& contact_patch) const {
363  if (result_ptr->size() <= 1) {
364  contact_patch.addPoint(contact.pos);
365  return;
366  }
367 
368  const ContactPatch::Polygon& result = *(result_ptr);
369  ContactPatch::Polygon& patch = contact_patch.points();
370  patch = result;
371 }
372 
373 // ============================================================================
374 template <typename ShapeType1, typename ShapeType2>
375 inline void ContactPatchSolver::reset(const ShapeType1& shape1,
376  const Transform3f& tf1,
377  const ShapeType2& shape2,
378  const Transform3f& tf2,
379  const ContactPatch& contact_patch) const {
380  // Reset internal quantities
381  this->support_set_shape1.clear();
382  this->support_set_shape2.clear();
383  this->support_set_buffer.clear();
384 
385  // Get the support function of each shape
386  const Transform3f& tfc = contact_patch.tf;
387 
388  this->support_set_shape1.direction = SupportSetDirection::DEFAULT;
389  // Set the reference frame of the support set of the first shape to be the
390  // local frame of shape 1.
391  Transform3f& tf1c = this->support_set_shape1.tf;
392  tf1c.rotation().noalias() = tf1.rotation().transpose() * tfc.rotation();
393  tf1c.translation().noalias() =
394  tf1.rotation().transpose() * (tfc.translation() - tf1.translation());
395  this->supportFuncShape1 =
396  this->makeSupportSetFunction(&shape1, this->supports_data[0]);
397 
398  this->support_set_shape2.direction = SupportSetDirection::INVERTED;
399  // Set the reference frame of the support set of the second shape to be the
400  // local frame of shape 2.
401  Transform3f& tf2c = this->support_set_shape2.tf;
402  tf2c.rotation().noalias() = tf2.rotation().transpose() * tfc.rotation();
403  tf2c.translation().noalias() =
404  tf2.rotation().transpose() * (tfc.translation() - tf2.translation());
405  this->supportFuncShape2 =
406  this->makeSupportSetFunction(&shape2, this->supports_data[1]);
407 }
408 
409 // ==========================================================================
411  const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d) {
412  const Vec2f ab = b - a;
413  const Vec2f n(-ab(1), ab(0));
414  const FCL_REAL denominator = n.dot(c - d);
415  if (std::abs(denominator) < std::numeric_limits<double>::epsilon()) {
416  return d;
417  }
418  const FCL_REAL nominator = n.dot(a - d);
419  FCL_REAL alpha = nominator / denominator;
420  alpha = std::min<double>(1.0, std::max<FCL_REAL>(0.0, alpha));
421  return alpha * c + (1 - alpha) * d;
422 }
423 
424 } // namespace fcl
425 } // namespace hpp
426 
427 #endif // HPP_FCL_CONTACT_PATCH_SOLVER_HXX
Simple transform class used locally by InterpMotion.
Definition: transform.h:56
const Matrix3f & rotation() const
get rotation
Definition: transform.h:114
const Vec3f & translation() const
get translation
Definition: transform.h:105
Eigen::Matrix< FCL_REAL, 2, 1 > Vec2f
Definition: data_types.h:68
void constructContactPatchFrameFromContact(const Contact &contact, ContactPatch &contact_patch)
Construct a frame from a Contact's position and normal. Because both Contact's position and normal ar...
Definition: collision_data.h:706
double FCL_REAL
Definition: data_types.h:66
Main namespace.
Definition: broadphase_bruteforce.h:44
Request for a contact patch computation.
Definition: collision_data.h:726
size_t getNumSamplesCurvedShapes() const
Maximum samples to compute the support sets of curved shapes, i.e. when the normal is perpendicular t...
Definition: collision_data.h:795
FCL_REAL getPatchTolerance() const
Tolerance below which points are added to a contact patch. In details, given two shapes S1 and S2,...
Definition: collision_data.h:812
SupportSet support_set_buffer
Temporary support set used for the Sutherland-Hodgman algorithm.
Definition: contact_patch_solver.h:124
static Vec2f computeLineSegmentIntersection(const Vec2f &a, const Vec2f &b, const Vec2f &c, const Vec2f &d)
Definition: contact_patch_solver.hxx:410
void getResult(const Contact &contact, const ContactPatch::Polygon *result, ContactPatch &contact_patch) const
Retrieve result, adds a post-processing step if result has bigger size than this->max_patch_size.
Definition: contact_patch_solver.hxx:360
SupportSetFunction supportFuncShape2
Support set function for shape s2.
Definition: contact_patch_solver.h:107
static constexpr size_t default_num_preallocated_supports
Number of vectors to pre-allocate in the m_clipping_sets vectors.
Definition: contact_patch_solver.h:91
SupportSetFunction supportFuncShape1
Support set function for shape s1.
Definition: contact_patch_solver.h:104
void set(const ContactPatchRequest &request)
Set up the solver using a ContactPatchRequest.
Definition: contact_patch_solver.hxx:47
FCL_REAL patch_tolerance
Tolerance below which points are added to the shapes support sets. See ContactPatchRequest::m_patch_t...
Definition: contact_patch_solver.h:101
SupportSet support_set_shape2
Holder for support set of shape 2, used for internal computation. After computePatch has been called,...
Definition: contact_patch_solver.h:121
static SupportSetFunction makeSupportSetFunction(const ShapeBase *shape, ShapeSupportData &support_data)
Construct support set function for shape.
void computePatch(const ShapeType1 &s1, const Transform3f &tf1, const ShapeType2 &s2, const Transform3f &tf2, const Contact &contact, ContactPatch &contact_patch) const
Main API of the solver: compute a contact patch from a contact between shapes s1 and s2....
Definition: contact_patch_solver.hxx:77
support_func_guess_t support_guess
Guess for the support sets computation.
Definition: contact_patch_solver.h:113
SupportSet support_set_shape1
Holder for support set of shape 1, used for internal computation. After computePatch has been called,...
Definition: contact_patch_solver.h:117
std::vector< bool > added_to_patch
Tracks which point of the Sutherland-Hodgman result have been added to the contact patch....
Definition: contact_patch_solver.h:130
size_t num_samples_curved_shapes
Number of points sampled for Cone and Cylinder when the normal is orthogonal to the shapes' basis....
Definition: contact_patch_solver.h:97
std::array< ShapeSupportData, 2 > supports_data
Temporary data to compute the support sets on each shape.
Definition: contact_patch_solver.h:110
void reset(const ShapeType1 &shape1, const Transform3f &tf1, const ShapeType2 &shape2, const Transform3f &tf2, const ContactPatch &contact_patch) const
Reset the internal quantities of the solver.
Definition: contact_patch_solver.hxx:375
This structure allows to encode contact patches. A contact patch is defined by a set of points belong...
Definition: collision_data.h:512
void addPoint(const Vec3f &point_3d)
Add a 3D point to the set, expressed in the world frame.
Definition: collision_data.h:584
Transform3f tf
Frame of the set, expressed in the world coordinates. The z-axis of the frame's rotation is the conta...
Definition: collision_data.h:518
PatchDirection direction
Direction of this contact patch.
Definition: collision_data.h:532
size_t size() const
Returns the number of points in the contact patch.
Definition: collision_data.h:577
void clear()
Clear the set.
Definition: collision_data.h:642
Polygon & points()
Getter for the 2D points in the set.
Definition: collision_data.h:616
std::vector< Vec2f > Polygon
Definition: collision_data.h:514
Contact information returned by collision.
Definition: collision_data.h:59
Vec3f pos
contact position, in world space
Definition: collision_data.h:103
Definition: geometric_shapes_traits.h:56