swept_sphere_radius.cpp
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 Willow Garage, Inc. 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 #define BOOST_TEST_MODULE COAL_SWEPT_SPHERE_RADIUS
38 #include <boost/test/included/unit_test.hpp>
39 
41 #include "coal/collision_utility.h"
42 
47 
48 #include "utility.h"
49 
50 using namespace coal;
51 
54 int line;
55 
56 #define SET_LINE \
57  node1_type = shape1.getNodeType(); \
58  node2_type = shape2.getNodeType(); \
59  line = __LINE__
60 
61 #define COAL_CHECK(cond) \
62  BOOST_CHECK_MESSAGE( \
63  cond, "from line " << line << ", for collision pair: " \
64  << get_node_type_name(node1_type) << " - " \
65  << get_node_type_name(node2_type) \
66  << " with ssr1 = " << shape1.getSweptSphereRadius() \
67  << ", ssr2 = " << shape2.getSweptSphereRadius() \
68  << ": " #cond)
69 
70 #define COAL_CHECK_VECTOR_CLOSE(v1, v2, tol) \
71  EIGEN_VECTOR_IS_APPROX(v1, v2, tol); \
72  COAL_CHECK(((v1) - (v2)).isZero(tol))
73 
74 #define COAL_CHECK_REAL_CLOSE(v1, v2, tol) \
75  CoalScalar_IS_APPROX(v1, v2, tol); \
76  COAL_CHECK(std::abs((v1) - (v2)) < tol)
77 
78 #define COAL_CHECK_CONDITION(cond) \
79  BOOST_CHECK(cond); \
80  COAL_CHECK(cond)
81 
82 // Preambule: swept sphere radius allows to virually convolve geometric shapes
83 // by a sphere with positive radius (Minkowski sum).
84 // Sweeping a shape by a sphere corresponds to doing a Minkowski addition of the
85 // shape with a sphere of radius r. Essentially, this rounds the shape's corners
86 // and edges, which can be useful to smooth collision detection algorithms.
87 //
88 // A nice mathematical property of GJK and EPA is that it is not
89 // necessary to take into account the swept sphere radius in the iterations of
90 // the algorithms. This is because the GJK and EPA algorithms are based on the
91 // Minkowski difference of the two objects.
92 // With spheres of radii r1 and r2 swept around the shapes s1 and s2 of a
93 // collision pair, the Minkowski difference is simply the Minkowski difference
94 // of s1 and s2, summed with a sphere of radius r1 + r2.
95 // This means that running GJK and EPA on the swept-sphere shapes is equivalent
96 // to running GJK and EPA on the original shapes, and then augmenting the
97 // distance by r1 + r2.
98 // This does not modify the normal returned by GJK and EPA.
99 // So we can also easily recover the witness points of the swept sphere shapes.
100 //
101 // This suite of test is designed to verify that property and generally test for
102 // swept-sphere radius support in Coal.
103 // Notes:
104 // - not all collision pairs use GJK/EPA, so this test makes sure that
105 // swept-sphere radius is taken into account even for specialized collision
106 // algorithms.
107 // - when manually taking swept-sphere radius into account in GJK/EPA, we
108 // strongly deteriorate the convergence properties of the algorithms. This
109 // is because certain parts of the shapes become locally strictly convex,
110 // which GJK/EPA are not designed to handle. This becomes particularly the
111 // bigger the swept-sphere radius. So don't be surprised if the tests fail
112 // for large radii.
113 
115  template <typename S1, typename S2>
117  const S1& s1, const Transform3s& tf1, const S2& s2,
118  const Transform3s& tf2, bool compute_penetration, Vec3s& p1, Vec3s& p2,
119  Vec3s& normal, bool use_swept_sphere_radius_in_gjk_epa_iterations) const {
120  if (use_swept_sphere_radius_in_gjk_epa_iterations) {
122  this->runGJKAndEPA<S1, S2, details::SupportOptions::WithSweptSphere>(
123  s1, tf1, s2, tf2, compute_penetration, distance, p1, p2, normal);
124  return distance;
125  }
126 
127  // Default behavior of coal's GJKSolver
129  this->runGJKAndEPA<S1, S2, details::SupportOptions::NoSweptSphere>(
130  s1, tf1, s2, tf2, compute_penetration, distance, p1, p2, normal);
131  return distance;
132  }
133 };
134 
135 template <typename S1, typename S2>
136 void test_gjksolver_swept_sphere_radius(S1& shape1, S2& shape2) {
137  SweptSphereGJKSolver solver;
138  // The swept sphere radius is detrimental to the convergence of GJK
139  // and EPA. This gets worse as the radius of the swept sphere increases.
140  // So we need to increase the number of iterations to get a good result.
141  const CoalScalar tol = 1e-6;
142  solver.gjk_tolerance = tol;
143  solver.epa_tolerance = tol;
144  solver.epa_max_iterations = 1000;
145  const bool compute_penetration = true;
146 
147  CoalScalar extents[] = {-2, -2, -2, 2, 2, 2};
148  std::size_t n = 10;
149  std::vector<Transform3s> tf1s;
150  std::vector<Transform3s> tf2s;
153  const std::array<CoalScalar, 4> swept_sphere_radius = {0, 0.1, 1., 10.};
154 
155  for (const CoalScalar& ssr1 : swept_sphere_radius) {
156  shape1.setSweptSphereRadius(ssr1);
157  for (const CoalScalar& ssr2 : swept_sphere_radius) {
158  shape2.setSweptSphereRadius(ssr2);
159  for (std::size_t i = 0; i < n; ++i) {
160  Transform3s tf1 = tf1s[i];
161  Transform3s tf2 = tf2s[i];
162 
163  SET_LINE;
164 
165  std::array<CoalScalar, 2> distance;
166  std::array<Vec3s, 2> p1;
167  std::array<Vec3s, 2> p2;
168  std::array<Vec3s, 2> normal;
169 
170  // Default coal behavior - Don't take swept sphere radius into account
171  // during GJK/EPA iterations. Correct the solution afterwards.
172  distance[0] =
173  solver.shapeDistance(shape1, tf1, shape2, tf2, compute_penetration,
174  p1[0], p2[0], normal[0], false);
175 
176  // Take swept sphere radius into account during GJK/EPA iterations
177  distance[1] =
178  solver.shapeDistance(shape1, tf1, shape2, tf2, compute_penetration,
179  p1[1], p2[1], normal[1], true);
180 
181  // Precision is dependent on the swept-sphere radius.
182  // The issue of precision does not come from the default behavior of
183  // coal, but from the result in which we manually take the swept
184  // sphere radius into account in GJK/EPA iterations.
185  const CoalScalar precision =
186  3 * sqrt(tol) +
187  (1 / 100.0) * std::max(shape1.getSweptSphereRadius(),
188  shape2.getSweptSphereRadius());
189 
190  // Check that the distance is the same
191  COAL_CHECK_REAL_CLOSE(distance[0], distance[1], precision);
192 
193  // Check that the normal is the same
194  COAL_CHECK_CONDITION(normal[0].dot(normal[1]) > 0);
195  COAL_CHECK_CONDITION(std::abs(1 - normal[0].dot(normal[1])) <
196  precision);
197 
198  // Check that the witness points are the same
199  COAL_CHECK_VECTOR_CLOSE(p1[0], p1[1], precision);
200  COAL_CHECK_VECTOR_CLOSE(p2[0], p2[1], precision);
201  }
202  }
203  }
204 }
205 
206 static const CoalScalar min_shape_size = 0.1;
207 static const CoalScalar max_shape_size = 0.5;
208 
209 BOOST_AUTO_TEST_CASE(ssr_mesh_mesh) {
212  test_gjksolver_swept_sphere_radius(shape1, shape2);
213 }
214 
215 BOOST_AUTO_TEST_CASE(ssr_mesh_ellipsoid) {
218  test_gjksolver_swept_sphere_radius(shape1, shape2);
219 }
220 
221 BOOST_AUTO_TEST_CASE(ssr_box_box) {
224  test_gjksolver_swept_sphere_radius(shape1, shape2);
225 }
226 
227 BOOST_AUTO_TEST_CASE(ssr_ellipsoid_ellipsoid) {
230  test_gjksolver_swept_sphere_radius(shape1, shape2);
231 }
232 
233 BOOST_AUTO_TEST_CASE(ssr_ellipsoid_box) {
236  test_gjksolver_swept_sphere_radius(shape1, shape2);
237 }
238 
239 BOOST_AUTO_TEST_CASE(ssr_cone_cone) {
244  test_gjksolver_swept_sphere_radius(shape1, shape2);
245 }
246 
247 BOOST_AUTO_TEST_CASE(ssr_cone_ellipsoid) {
251  test_gjksolver_swept_sphere_radius(shape1, shape2);
252 }
253 
254 BOOST_AUTO_TEST_CASE(ssr_capsule_capsule) {
259  test_gjksolver_swept_sphere_radius(shape1, shape2);
260 }
261 
262 BOOST_AUTO_TEST_CASE(ssr_capsule_cone) {
267  test_gjksolver_swept_sphere_radius(shape1, shape2);
268 }
269 
270 BOOST_AUTO_TEST_CASE(ssr_cylinder_cylinder) {
275  test_gjksolver_swept_sphere_radius(shape1, shape2);
276 }
277 
278 template <typename S1, typename S2>
279 void test_collide_swept_sphere_radius(S1& shape1, S2& shape2) {
280  std::cout << "Testing collision between "
281  << std::string(get_node_type_name(shape1.getNodeType())) << " and "
282  << std::string(get_node_type_name(shape2.getNodeType())) << '\n';
283 
284  CoalScalar extents[] = {-2, -2, -2, 2, 2, 2};
285  std::size_t n = 1;
286  std::vector<Transform3s> tf1s;
287  std::vector<Transform3s> tf2s;
290 
291  const std::array<CoalScalar, 4> swept_sphere_radius = {0, 0.1, 1., 10.};
292  for (const CoalScalar& ssr1 : swept_sphere_radius) {
293  shape1.setSweptSphereRadius(ssr1);
294  for (const CoalScalar& ssr2 : swept_sphere_radius) {
295  shape2.setSweptSphereRadius(ssr2);
296  for (std::size_t i = 0; i < n; ++i) {
297  Transform3s tf1 = tf1s[i];
298  Transform3s tf2 = tf2s[i];
299 
300  SET_LINE;
301  CollisionRequest request;
302  request.enable_contact = true;
303  // We make sure we get witness points by setting the security margin to
304  // infinity. That way shape1 and shape2 will always be considered in
305  // collision.
306  request.security_margin = std::numeric_limits<CoalScalar>::max();
307  const CoalScalar tol = 1e-6;
308  request.gjk_tolerance = tol;
309  request.epa_tolerance = tol;
310 
311  std::array<CollisionResult, 2> result;
312 
313  // Without swept sphere radius
314  const CoalScalar ssr1 = shape1.getSweptSphereRadius();
315  const CoalScalar ssr2 = shape2.getSweptSphereRadius();
316  shape1.setSweptSphereRadius(0.);
317  shape2.setSweptSphereRadius(0.);
318  coal::collide(&shape1, tf1, &shape2, tf2, request, result[0]);
319 
320  // With swept sphere radius
321  shape1.setSweptSphereRadius(ssr1);
322  shape2.setSweptSphereRadius(ssr2);
323  coal::collide(&shape1, tf1, &shape2, tf2, request, result[1]);
324 
325  BOOST_CHECK(result[0].isCollision());
326  BOOST_CHECK(result[1].isCollision());
327  if (result[0].isCollision() && result[1].isCollision()) {
328  std::array<Contact, 2> contact;
329  contact[0] = result[0].getContact(0);
330  contact[1] = result[1].getContact(0);
331 
332  // Precision is dependent on the swept sphere radii.
333  // The issue of precision does not come from the default behavior of
334  // coal, but from the result in which we manually take the swept
335  // sphere radius into account in GJK/EPA iterations.
336  const CoalScalar precision =
337  3 * sqrt(tol) + (1 / 100.0) * std::max(ssr1, ssr2);
338  const CoalScalar ssr = ssr1 + ssr2;
339 
340  // Check that the distance is the same
341  COAL_CHECK_REAL_CLOSE(contact[0].penetration_depth - ssr,
342  contact[1].penetration_depth, precision);
343 
344  // Check that the normal is the same
345  COAL_CHECK_CONDITION((contact[0].normal).dot(contact[1].normal) > 0);
347  std::abs(1 - (contact[0].normal).dot(contact[1].normal)) <
348  precision);
349 
350  // Check that the witness points are the same
352  contact[0].nearest_points[0] + ssr1 * contact[0].normal,
353  contact[1].nearest_points[0], precision);
355  contact[0].nearest_points[1] - ssr2 * contact[0].normal,
356  contact[1].nearest_points[1], precision);
357  }
358  }
359  }
360  }
361 }
362 
363 const std::vector<NODE_TYPE> tested_geometries = {
366 
367 BOOST_AUTO_TEST_CASE(ssr_geom_geom) {
368  // Each possible geom pair is tested twice
369  for (const NODE_TYPE& shape_type1 : tested_geometries) {
370  for (const NODE_TYPE& shape_type2 : tested_geometries) {
371  if (shape_type1 == GEOM_PLANE || shape_type1 == GEOM_HALFSPACE) {
372  if (shape_type2 == GEOM_PLANE || shape_type2 == GEOM_HALFSPACE) {
373  // TODO(louis): check plane-plane plane-halfspace etc. collisions
374  continue;
375  }
376  }
377  std::shared_ptr<ShapeBase> shape1 = makeRandomGeometry(shape_type1);
378  std::shared_ptr<ShapeBase> shape2 = makeRandomGeometry(shape_type2);
379  test_collide_swept_sphere_radius(*shape1, *shape2);
380  }
381  }
382 }
coal::makeRandomGeometry
std::shared_ptr< ShapeBase > makeRandomGeometry(NODE_TYPE node_type)
Definition: utility.cpp:607
coal::QueryRequest::gjk_tolerance
CoalScalar gjk_tolerance
tolerance for the GJK algorithm. Note: This tolerance determines the precision on the estimated dista...
Definition: coal/collision_data.h:192
coal::Vec3s
Eigen::Matrix< CoalScalar, 3, 1 > Vec3s
Definition: coal/data_types.h:77
coal::QueryRequest::epa_tolerance
CoalScalar epa_tolerance
tolerance for EPA. Note: This tolerance determines the precision on the estimated distance between tw...
Definition: coal/collision_data.h:211
coal::CollisionRequest::enable_contact
bool enable_contact
whether the contact information (normal, penetration depth and contact position) will return.
Definition: coal/collision_data.h:317
coal::makeRandomCone
Cone makeRandomCone(std::array< CoalScalar, 2 > min_size, std::array< CoalScalar, 2 > max_size)
Definition: utility.cpp:581
coal::GEOM_CONE
@ GEOM_CONE
Definition: coal/collision_object.h:77
coal::GEOM_CONVEX
@ GEOM_CONVEX
Definition: coal/collision_object.h:79
max_shape_size
static const CoalScalar max_shape_size
Definition: swept_sphere_radius.cpp:207
gjk.tf1
tuple tf1
Definition: test/scripts/gjk.py:27
coal::makeRandomConvex
Convex< Triangle > makeRandomConvex(CoalScalar min_size, CoalScalar max_size)
Definition: utility.cpp:593
coal::CollisionRequest::security_margin
CoalScalar security_margin
Distance below which objects are considered in collision. See Collision.
Definition: coal/collision_data.h:328
coal::NODE_TYPE
NODE_TYPE
traversal node type: bounding volume (AABB, OBB, RSS, kIOS, OBBRSS, KDOP16, KDOP18,...
Definition: coal/collision_object.h:64
coal::Capsule
Capsule It is where is the distance between the point x and the capsule segment AB,...
Definition: coal/shape/geometric_shapes.h:383
coal
Main namespace.
Definition: coal/broadphase/broadphase_bruteforce.h:44
narrowphase.h
utility.h
coal::GEOM_CAPSULE
@ GEOM_CAPSULE
Definition: coal/collision_object.h:76
coal::makeRandomBox
Box makeRandomBox(CoalScalar min_size, CoalScalar max_size)
Definition: utility.cpp:559
coal::GJKSolver
collision and distance solver based on the GJK and EPA algorithms. Originally, GJK and EPA were imple...
Definition: coal/narrowphase/narrowphase.h:57
coal::Ellipsoid
Ellipsoid centered at point zero.
Definition: coal/shape/geometric_shapes.h:305
coal::distance
COAL_DLLAPI CoalScalar distance(const Matrix3s &R0, const Vec3s &T0, const kIOS &b1, const kIOS &b2, Vec3s *P=NULL, Vec3s *Q=NULL)
Approximate distance between two kIOS bounding volumes.
Definition: kIOS.cpp:180
test_collide_swept_sphere_radius
void test_collide_swept_sphere_radius(S1 &shape1, S2 &shape2)
Definition: swept_sphere_radius.cpp:279
coal::GEOM_SPHERE
@ GEOM_SPHERE
Definition: coal/collision_object.h:75
coal::Box
Center at zero point, axis aligned box.
Definition: coal/shape/geometric_shapes.h:166
min_shape_size
static const CoalScalar min_shape_size
Definition: swept_sphere_radius.cpp:206
coal::Transform3s
Simple transform class used locally by InterpMotion.
Definition: coal/math/transform.h:55
node2_type
NODE_TYPE node2_type
Definition: swept_sphere_radius.cpp:53
SET_LINE
#define SET_LINE
Definition: swept_sphere_radius.cpp:56
coal::GEOM_PLANE
@ GEOM_PLANE
Definition: coal/collision_object.h:80
SweptSphereGJKSolver::shapeDistance
CoalScalar shapeDistance(const S1 &s1, const Transform3s &tf1, const S2 &s2, const Transform3s &tf2, bool compute_penetration, Vec3s &p1, Vec3s &p2, Vec3s &normal, bool use_swept_sphere_radius_in_gjk_epa_iterations) const
Definition: swept_sphere_radius.cpp:116
coal::GEOM_ELLIPSOID
@ GEOM_ELLIPSOID
Definition: coal/collision_object.h:84
coal::GEOM_HALFSPACE
@ GEOM_HALFSPACE
Definition: coal/collision_object.h:81
coal::GJKSolver::epa_tolerance
CoalScalar epa_tolerance
tolerance of EPA
Definition: coal/narrowphase/narrowphase.h:104
COAL_CHECK_CONDITION
#define COAL_CHECK_CONDITION(cond)
Definition: swept_sphere_radius.cpp:78
coal::CollisionRequest
request to the collision algorithm
Definition: coal/collision_data.h:311
archive.h
gjk.tf2
tuple tf2
Definition: test/scripts/gjk.py:36
coal::Convex
Convex polytope.
Definition: coal/serialization/collision_object.h:51
coal::Cylinder
Cylinder along Z axis. The cylinder is defined at its centroid.
Definition: coal/shape/geometric_shapes.h:560
BOOST_AUTO_TEST_CASE
BOOST_AUTO_TEST_CASE(ssr_mesh_mesh)
Definition: swept_sphere_radius.cpp:209
coal::generateRandomTransforms
void generateRandomTransforms(CoalScalar extents[6], std::vector< Transform3s > &transforms, std::size_t n)
Generate n random transforms whose translations are constrained by extents.
Definition: utility.cpp:226
extents
CoalScalar extents[6]
Definition: test/geometric_shapes.cpp:53
line
int line
Definition: swept_sphere_radius.cpp:54
octree.p1
tuple p1
Definition: octree.py:54
coal::GEOM_CYLINDER
@ GEOM_CYLINDER
Definition: coal/collision_object.h:78
coal::get_node_type_name
const char * get_node_type_name(NODE_TYPE node_type)
Returns the name associated to a NODE_TYPE.
Definition: coal/collision_utility.h:32
coal::Cone
Cone The base of the cone is at and the top is at .
Definition: coal/shape/geometric_shapes.h:467
test_gjksolver_swept_sphere_radius
void test_gjksolver_swept_sphere_radius(S1 &shape1, S2 &shape2)
Definition: swept_sphere_radius.cpp:136
convex.h
COAL_CHECK_VECTOR_CLOSE
#define COAL_CHECK_VECTOR_CLOSE(v1, v2, tol)
Definition: swept_sphere_radius.cpp:70
COAL_CHECK_REAL_CLOSE
#define COAL_CHECK_REAL_CLOSE(v1, v2, tol)
Definition: swept_sphere_radius.cpp:74
collision_utility.h
node1_type
NODE_TYPE node1_type
Definition: swept_sphere_radius.cpp:52
coal::makeRandomEllipsoid
Ellipsoid makeRandomEllipsoid(CoalScalar min_size, CoalScalar max_size)
Definition: utility.cpp:569
coal::collide
COAL_DLLAPI std::size_t collide(const CollisionObject *o1, const CollisionObject *o2, const CollisionRequest &request, CollisionResult &result)
Main collision interface: given two collision objects, and the requirements for contacts,...
Definition: src/collision.cpp:61
coal::GEOM_BOX
@ GEOM_BOX
Definition: coal/collision_object.h:74
coal::GJKSolver::epa_max_iterations
size_t epa_max_iterations
maximum number of iterations of EPA
Definition: coal/narrowphase/narrowphase.h:101
geometric_shapes.h
transform.h
coal::makeRandomCylinder
Cylinder makeRandomCylinder(std::array< CoalScalar, 2 > min_size, std::array< CoalScalar, 2 > max_size)
Definition: utility.cpp:587
coal::CoalScalar
double CoalScalar
Definition: coal/data_types.h:76
coal::makeRandomCapsule
Capsule makeRandomCapsule(std::array< CoalScalar, 2 > min_size, std::array< CoalScalar, 2 > max_size)
Definition: utility.cpp:575
tested_geometries
const std::vector< NODE_TYPE > tested_geometries
Definition: swept_sphere_radius.cpp:363
coal::GJKSolver::gjk_tolerance
CoalScalar gjk_tolerance
tolerance of GJK
Definition: coal/narrowphase/narrowphase.h:68
SweptSphereGJKSolver
Definition: swept_sphere_radius.cpp:114


hpp-fcl
Author(s):
autogenerated on Sat Nov 23 2024 03:44:59