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