coal  3.0.1
Coal, The Collision Detection Library. Previously known as HPP-FCL, 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 COAL_CONTACT_PATCH_SOLVER_HXX
38 #define COAL_CONTACT_PATCH_SOLVER_HXX
39 
40 #include "coal/data_types.h"
42 
43 namespace coal {
44 
45 // ============================================================================
46 inline void ContactPatchSolver::set(const ContactPatchRequest& request) {
47  // Note: it's important for the number of pre-allocated Vec3s in
48  // `m_clipping_sets` to be larger than `request.max_size_patch`
49  // because we don't know in advance how many supports will be discarded to
50  // form the convex-hulls of the shapes supports which will serve as the
51  // input of the Sutherland-Hodgman algorithm.
52  size_t num_preallocated_supports = default_num_preallocated_supports;
53  if (num_preallocated_supports < 2 * request.getNumSamplesCurvedShapes()) {
54  num_preallocated_supports = 2 * request.getNumSamplesCurvedShapes();
55  }
56 
57  // Used for support set computation of shape1 and for the first iterate of the
58  // Sutherland-Hodgman algo.
59  this->support_set_shape1.points().reserve(num_preallocated_supports);
60  this->support_set_shape1.direction = SupportSetDirection::DEFAULT;
61 
62  // Used for computing the next iterate of the Sutherland-Hodgman algo.
63  this->support_set_buffer.points().reserve(num_preallocated_supports);
64 
65  // Used for support set computation of shape2 and acts as the "clipper" set in
66  // the Sutherland-Hodgman algo.
67  this->support_set_shape2.points().reserve(num_preallocated_supports);
68  this->support_set_shape2.direction = SupportSetDirection::INVERTED;
69 
71  this->patch_tolerance = request.getPatchTolerance();
72 }
73 
74 // ============================================================================
75 template <typename ShapeType1, typename ShapeType2>
76 void ContactPatchSolver::computePatch(const ShapeType1& s1,
77  const Transform3s& tf1,
78  const ShapeType2& s2,
79  const Transform3s& tf2,
80  const Contact& contact,
81  ContactPatch& contact_patch) const {
82  // Note: `ContactPatch` is an alias for `SupportSet`.
83  // Step 1
84  constructContactPatchFrameFromContact(contact, contact_patch);
85  contact_patch.points().clear();
88  // If a shape is strictly convex, the support set in any direction is
89  // reduced to a single point. Thus, the contact point `contact.pos` is the
90  // only point belonging to the contact patch, and it has already been
91  // computed.
92  // TODO(louis): even for strictly convex shapes, we can sample the support
93  // function around the normal and return a pseudo support set. This would
94  // allow spheres and ellipsoids to have a contact surface, which does make
95  // sense in certain physics simulation cases.
96  // Do the same for strictly convex regions of non-strictly convex shapes
97  // like the ends of capsules.
98  contact_patch.addPoint(contact.pos);
99  return;
100  }
101 
102  // Step 2 - Compute support set of each shape, in the direction of
103  // the contact's normal.
104  // The first shape's support set is called "current"; it will be the first
105  // iterate of the Sutherland-Hodgman algorithm. The second shape's support set
106  // is called "clipper"; it will be used to clip "current". The support set
107  // computation step computes a convex polygon; its vertices are ordered
108  // counter-clockwise. This is important as the Sutherland-Hodgman algorithm
109  // expects points to be ranked counter-clockwise.
110  this->reset(s1, tf1, s2, tf2, contact_patch);
111  assert(this->num_samples_curved_shapes > 3);
112 
113  this->supportFuncShape1(&s1, this->support_set_shape1, this->support_guess[0],
114  this->supports_data[0],
116  this->patch_tolerance);
117 
118  this->supportFuncShape2(&s2, this->support_set_shape2, this->support_guess[1],
119  this->supports_data[1],
121  this->patch_tolerance);
122 
123  // We can immediatly return if one of the support set has only
124  // one point.
125  if (this->support_set_shape1.size() <= 1 ||
126  this->support_set_shape2.size() <= 1) {
127  contact_patch.addPoint(contact.pos);
128  return;
129  }
130 
131  // `eps` is be used to check strict positivity of determinants.
132  const CoalScalar eps = Eigen::NumTraits<CoalScalar>::dummy_precision();
133  using Polygon = SupportSet::Polygon;
134 
135  if ((this->support_set_shape1.size() == 2) &&
136  (this->support_set_shape2.size() == 2)) {
137  // Segment-Segment case
138  // We compute the determinant; if it is non-zero, the intersection
139  // has already been computed: it's `Contact::pos`.
140  const Polygon& pts1 = this->support_set_shape1.points();
141  const Vec2s& a = pts1[0];
142  const Vec2s& b = pts1[1];
143 
144  const Polygon& pts2 = this->support_set_shape2.points();
145  const Vec2s& c = pts2[0];
146  const Vec2s& d = pts2[1];
147 
148  const CoalScalar det =
149  (b(0) - a(0)) * (d(1) - c(1)) >= (b(1) - a(1)) * (d(0) - c(0));
150  if ((std::abs(det) > eps) || ((c - d).squaredNorm() < eps) ||
151  ((b - a).squaredNorm() < eps)) {
152  contact_patch.addPoint(contact.pos);
153  return;
154  }
155 
156  const Vec2s cd = (d - c);
157  const CoalScalar l = cd.squaredNorm();
158  Polygon& patch = contact_patch.points();
159 
160  // Project a onto [c, d]
161  CoalScalar t1 = (a - c).dot(cd);
162  t1 = (t1 >= l) ? 1.0 : ((t1 <= 0) ? 0.0 : (t1 / l));
163  const Vec2s p1 = c + t1 * cd;
164  patch.emplace_back(p1);
165 
166  // Project b onto [c, d]
167  CoalScalar t2 = (b - c).dot(cd);
168  t2 = (t2 >= l) ? 1.0 : ((t2 <= 0) ? 0.0 : (t2 / l));
169  const Vec2s p2 = c + t2 * cd;
170  if ((p1 - p2).squaredNorm() >= eps) {
171  patch.emplace_back(p2);
172  }
173  return;
174  }
175 
176  //
177  // Step 3 - Main loop of the algorithm: use the "clipper" polygon to clip the
178  // "current" polygon. The resulting intersection is the contact patch of the
179  // contact between s1 and s2. "clipper" and "current" are the support sets of
180  // shape1 and shape2 (they can be swapped, i.e. clipper can be assigned to
181  // shape1 and current to shape2, depending on which case we are). Currently,
182  // to clip one polygon with the other, we use the Sutherland-Hodgman
183  // algorithm:
184  // https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
185  // In the general case, Sutherland-Hodgman clips one polygon of size >=3 using
186  // another polygon of size >=3. However, it can be easily extended to handle
187  // the segment-polygon case.
188  //
189  // The maximum size of the output of the Sutherland-Hodgman algorithm is n1 +
190  // n2 where n1 and n2 are the sizes of the first and second polygon.
191  const size_t max_result_size =
193  if (this->added_to_patch.size() < max_result_size) {
194  this->added_to_patch.assign(max_result_size, false);
195  }
196 
197  const Polygon* clipper_ptr = nullptr;
198  Polygon* current_ptr = nullptr;
199  Polygon* previous_ptr = &(this->support_set_buffer.points());
200 
201  // Let the clipper set be the one with the most vertices, to make sure it is
202  // at least a triangle.
203  if (this->support_set_shape1.size() < this->support_set_shape2.size()) {
204  current_ptr = &(this->support_set_shape1.points());
205  clipper_ptr = &(this->support_set_shape2.points());
206  } else {
207  current_ptr = &(this->support_set_shape2.points());
208  clipper_ptr = &(this->support_set_shape1.points());
209  }
210 
211  const Polygon& clipper = *(clipper_ptr);
212  const size_t clipper_size = clipper.size();
213  for (size_t i = 0; i < clipper_size; ++i) {
214  // Swap `current` and `previous`.
215  // `previous` tracks the last iteration of the algorithm; `current` is
216  // filled by clipping `current` using `clipper`.
217  Polygon* tmp_ptr = previous_ptr;
218  previous_ptr = current_ptr;
219  current_ptr = tmp_ptr;
220 
221  const Polygon& previous = *(previous_ptr);
222  Polygon& current = *(current_ptr);
223  current.clear();
224 
225  const Vec2s& a = clipper[i];
226  const Vec2s& b = clipper[(i + 1) % clipper_size];
227  const Vec2s ab = b - a;
228 
229  if (previous.size() == 2) {
230  //
231  // Segment-Polygon case
232  //
233  const Vec2s& p1 = previous[0];
234  const Vec2s& p2 = previous[1];
235 
236  const Vec2s ap1 = p1 - a;
237  const Vec2s ap2 = p2 - a;
238 
239  const CoalScalar det1 = ab(0) * ap1(1) - ab(1) * ap1(0);
240  const CoalScalar det2 = ab(0) * ap2(1) - ab(1) * ap2(0);
241 
242  if (det1 < 0 && det2 < 0) {
243  // Both p1 and p2 are outside the clipping polygon, i.e. there is no
244  // intersection. The algorithm can stop.
245  break;
246  }
247 
248  if (det1 >= 0 && det2 >= 0) {
249  // Both p1 and p2 are inside the clipping polygon, there is nothing to
250  // do; move to the next iteration.
251  current = previous;
252  continue;
253  }
254 
255  // Compute the intersection between the line (a, b) and the segment
256  // [p1, p2].
257  if (det1 >= 0) {
258  if (det1 > eps) {
259  const Vec2s p = computeLineSegmentIntersection(a, b, p1, p2);
260  current.emplace_back(p1);
261  current.emplace_back(p);
262  continue;
263  } else {
264  // p1 is the only point of current which is also a point of the
265  // clipper. We can exit.
266  current.emplace_back(p1);
267  break;
268  }
269  } else {
270  if (det2 > eps) {
271  const Vec2s p = computeLineSegmentIntersection(a, b, p1, p2);
272  current.emplace_back(p2);
273  current.emplace_back(p);
274  continue;
275  } else {
276  // p2 is the only point of current which is also a point of the
277  // clipper. We can exit.
278  current.emplace_back(p2);
279  break;
280  }
281  }
282  } else {
283  //
284  // Polygon-Polygon case.
285  //
286  std::fill(this->added_to_patch.begin(), //
287  this->added_to_patch.end(), //
288  false);
289 
290  const size_t previous_size = previous.size();
291  for (size_t j = 0; j < previous_size; ++j) {
292  const Vec2s& p1 = previous[j];
293  const Vec2s& p2 = previous[(j + 1) % previous_size];
294 
295  const Vec2s ap1 = p1 - a;
296  const Vec2s ap2 = p2 - a;
297 
298  const CoalScalar det1 = ab(0) * ap1(1) - ab(1) * ap1(0);
299  const CoalScalar det2 = ab(0) * ap2(1) - ab(1) * ap2(0);
300 
301  if (det1 < 0 && det2 < 0) {
302  // No intersection. Continue to next segment of previous.
303  continue;
304  }
305 
306  if (det1 >= 0 && det2 >= 0) {
307  // Both p1 and p2 are inside the clipping polygon, add p1 to current
308  // (only if it has not already been added).
309  if (!this->added_to_patch[j]) {
310  current.emplace_back(p1);
311  this->added_to_patch[j] = true;
312  }
313  // Continue to next segment of previous.
314  continue;
315  }
316 
317  if (det1 >= 0) {
318  if (det1 > eps) {
319  if (!this->added_to_patch[j]) {
320  current.emplace_back(p1);
321  this->added_to_patch[j] = true;
322  }
323  const Vec2s p = computeLineSegmentIntersection(a, b, p1, p2);
324  current.emplace_back(p);
325  } else {
326  // a, b and p1 are colinear; we add only p1.
327  if (!this->added_to_patch[j]) {
328  current.emplace_back(p1);
329  this->added_to_patch[j] = true;
330  }
331  }
332  } else {
333  if (det2 > eps) {
334  const Vec2s p = computeLineSegmentIntersection(a, b, p1, p2);
335  current.emplace_back(p);
336  } else {
337  if (!this->added_to_patch[(j + 1) % previous.size()]) {
338  current.emplace_back(p2);
339  this->added_to_patch[(j + 1) % previous.size()] = true;
340  }
341  }
342  }
343  }
344  }
345  //
346  // End of iteration i of Sutherland-Hodgman.
347  if (current.size() <= 1) {
348  // No intersection or one point found, the algo can early stop.
349  break;
350  }
351  }
352 
353  // Transfer the result of the Sutherland-Hodgman algorithm to the contact
354  // patch.
355  this->getResult(contact, current_ptr, contact_patch);
356 }
357 
358 // ============================================================================
360  const Contact& contact, const ContactPatch::Polygon* result_ptr,
361  ContactPatch& contact_patch) const {
362  if (result_ptr->size() <= 1) {
363  contact_patch.addPoint(contact.pos);
364  return;
365  }
366 
367  const ContactPatch::Polygon& result = *(result_ptr);
368  ContactPatch::Polygon& patch = contact_patch.points();
369  patch = result;
370 }
371 
372 // ============================================================================
373 template <typename ShapeType1, typename ShapeType2>
374 inline void ContactPatchSolver::reset(const ShapeType1& shape1,
375  const Transform3s& tf1,
376  const ShapeType2& shape2,
377  const Transform3s& tf2,
378  const ContactPatch& contact_patch) const {
379  // Reset internal quantities
380  this->support_set_shape1.clear();
381  this->support_set_shape2.clear();
382  this->support_set_buffer.clear();
383 
384  // Get the support function of each shape
385  const Transform3s& tfc = contact_patch.tf;
386 
387  this->support_set_shape1.direction = SupportSetDirection::DEFAULT;
388  // Set the reference frame of the support set of the first shape to be the
389  // local frame of shape 1.
390  Transform3s& tf1c = this->support_set_shape1.tf;
391  tf1c.rotation().noalias() = tf1.rotation().transpose() * tfc.rotation();
392  tf1c.translation().noalias() =
393  tf1.rotation().transpose() * (tfc.translation() - tf1.translation());
394  this->supportFuncShape1 =
395  this->makeSupportSetFunction(&shape1, this->supports_data[0]);
396 
397  this->support_set_shape2.direction = SupportSetDirection::INVERTED;
398  // Set the reference frame of the support set of the second shape to be the
399  // local frame of shape 2.
400  Transform3s& tf2c = this->support_set_shape2.tf;
401  tf2c.rotation().noalias() = tf2.rotation().transpose() * tfc.rotation();
402  tf2c.translation().noalias() =
403  tf2.rotation().transpose() * (tfc.translation() - tf2.translation());
404  this->supportFuncShape2 =
405  this->makeSupportSetFunction(&shape2, this->supports_data[1]);
406 }
407 
408 // ==========================================================================
410  const Vec2s& a, const Vec2s& b, const Vec2s& c, const Vec2s& d) {
411  const Vec2s ab = b - a;
412  const Vec2s n(-ab(1), ab(0));
413  const CoalScalar denominator = n.dot(c - d);
414  if (std::abs(denominator) < std::numeric_limits<double>::epsilon()) {
415  return d;
416  }
417  const CoalScalar nominator = n.dot(a - d);
418  CoalScalar alpha = nominator / denominator;
419  alpha = std::min<double>(1.0, std::max<CoalScalar>(0.0, alpha));
420  return alpha * c + (1 - alpha) * d;
421 }
422 
423 } // namespace coal
424 
425 #endif // COAL_CONTACT_PATCH_SOLVER_HXX
Simple transform class used locally by InterpMotion.
Definition: transform.h:55
const Vec3s & translation() const
get translation
Definition: transform.h:104
const Matrix3s & rotation() const
get rotation
Definition: transform.h:113
Main namespace.
Definition: broadphase_bruteforce.h:44
Eigen::Matrix< CoalScalar, 2, 1 > Vec2s
Definition: data_types.h:78
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:704
double CoalScalar
Definition: data_types.h:76
Request for a contact patch computation.
Definition: collision_data.h:724
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:793
CoalScalar getPatchTolerance() const
Tolerance below which points are added to a contact patch. In details, given two shapes S1 and S2,...
Definition: collision_data.h:810
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
SupportSetFunction supportFuncShape2
Support set function for shape s2.
Definition: contact_patch_solver.h:107
std::array< ShapeSupportData, 2 > supports_data
Temporary data to compute the support sets on each shape.
Definition: contact_patch_solver.h:110
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
static Vec2s computeLineSegmentIntersection(const Vec2s &a, const Vec2s &b, const Vec2s &c, const Vec2s &d)
Definition: contact_patch_solver.hxx:409
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:359
static SupportSetFunction makeSupportSetFunction(const ShapeBase *shape, ShapeSupportData &support_data)
Construct support set function for shape.
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
void reset(const ShapeType1 &shape1, const Transform3s &tf1, const ShapeType2 &shape2, const Transform3s &tf2, const ContactPatch &contact_patch) const
Reset the internal quantities of the solver.
Definition: contact_patch_solver.hxx:374
support_func_guess_t support_guess
Guess for the support sets computation.
Definition: contact_patch_solver.h:113
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
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:46
SupportSet support_set_buffer
Temporary support set used for the Sutherland-Hodgman algorithm.
Definition: contact_patch_solver.h:124
CoalScalar patch_tolerance
Tolerance below which points are added to the shapes support sets. See ContactPatchRequest::m_patch_t...
Definition: contact_patch_solver.h:101
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
void computePatch(const ShapeType1 &s1, const Transform3s &tf1, const ShapeType2 &s2, const Transform3s &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:76
This structure allows to encode contact patches. A contact patch is defined by a set of points belong...
Definition: collision_data.h:512
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 addPoint(const Vec3s &point_3d)
Add a 3D point to the set, expressed in the world frame.
Definition: collision_data.h:584
Transform3s 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
std::vector< Vec2s > Polygon
Definition: collision_data.h:514
void clear()
Clear the set.
Definition: collision_data.h:640
Polygon & points()
Getter for the 2D points in the set.
Definition: collision_data.h:616
Contact information returned by collision.
Definition: collision_data.h:58
Vec3s pos
contact position, in world space
Definition: collision_data.h:102
Definition: geometric_shapes_traits.h:55