coal 3.0.2
Coal, The Collision Detection Library. Previously known as HPP-FCL, fork of FCL -- The Flexible Collision Library
Loading...
Searching...
No Matches
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
36
37#ifndef COAL_CONTACT_PATCH_SOLVER_HXX
38#define COAL_CONTACT_PATCH_SOLVER_HXX
39
40#include "coal/data_types.h"
42
43namespace coal {
44
45// ============================================================================
46inline 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// ============================================================================
75template <typename ShapeType1, typename ShapeType2>
76void 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 =
192 this->support_set_shape1.size() + this->support_set_shape2.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// ============================================================================
373template <typename ShapeType1, typename ShapeType2>
374inline 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 Matrix3s & rotation() const
get rotation
Definition transform.h:113
const Vec3s & translation() const
get translation
Definition transform.h:104
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
Polygon & points()
Getter for the 2D points in the set.
Definition collision_data.h:616
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
Contact information returned by collision.
Definition collision_data.h:58
Vec3s pos
contact position, in world space
Definition collision_data.h:102
@ IsStrictlyConvex
Definition geometric_shapes_traits.h:50