GCC Code Coverage Report


Directory: ./
File: include/coal/internal/traversal_node_octree.h
Date: 2025-05-09 13:08:51
Exec Total Coverage
Lines: 250 571 43.8%
Branches: 250 898 27.8%

Line Branch Exec Source
1 /*
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2011-2014, Willow Garage, Inc.
5 * Copyright (c) 2014-2015, Open Source Robotics Foundation
6 * Copyright (c) 2022-2023, INRIA
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * * Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
19 * * Neither the name of Open Source Robotics Foundation nor the names of its
20 * contributors may be used to endorse or promote products derived
21 * from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37 /** \author Jia Pan */
38
39 #ifndef COAL_TRAVERSAL_NODE_OCTREE_H
40 #define COAL_TRAVERSAL_NODE_OCTREE_H
41
42 /// @cond INTERNAL
43
44 #include "coal/collision_data.h"
45 #include "coal/internal/traversal_node_base.h"
46 #include "coal/internal/traversal_node_hfield_shape.h"
47 #include "coal/narrowphase/narrowphase.h"
48 #include "coal/hfield.h"
49 #include "coal/octree.h"
50 #include "coal/BVH/BVH_model.h"
51 #include "coal/shape/geometric_shapes_utility.h"
52 #include "coal/internal/shape_shape_func.h"
53
54 namespace coal {
55
56 /// @brief Algorithms for collision related with octree
57 class COAL_DLLAPI OcTreeSolver {
58 private:
59 const GJKSolver* solver;
60
61 mutable const CollisionRequest* crequest;
62 mutable const DistanceRequest* drequest;
63
64 mutable CollisionResult* cresult;
65 mutable DistanceResult* dresult;
66
67 public:
68 3100 OcTreeSolver(const GJKSolver* solver_)
69 3100 : solver(solver_),
70 3100 crequest(NULL),
71 3100 drequest(NULL),
72 3100 cresult(NULL),
73 3100 dresult(NULL) {}
74
75 /// @brief collision between two octrees
76 void OcTreeIntersect(const OcTree* tree1, const OcTree* tree2,
77 const Transform3s& tf1, const Transform3s& tf2,
78 const CollisionRequest& request_,
79 CollisionResult& result_) const {
80 crequest = &request_;
81 cresult = &result_;
82
83 OcTreeIntersectRecurse(tree1, tree1->getRoot(), tree1->getRootBV(), tree2,
84 tree2->getRoot(), tree2->getRootBV(), tf1, tf2);
85 }
86
87 /// @brief distance between two octrees
88 void OcTreeDistance(const OcTree* tree1, const OcTree* tree2,
89 const Transform3s& tf1, const Transform3s& tf2,
90 const DistanceRequest& request_,
91 DistanceResult& result_) const {
92 drequest = &request_;
93 dresult = &result_;
94
95 OcTreeDistanceRecurse(tree1, tree1->getRoot(), tree1->getRootBV(), tree2,
96 tree2->getRoot(), tree2->getRootBV(), tf1, tf2);
97 }
98
99 /// @brief collision between octree and mesh
100 template <typename BV>
101 200 void OcTreeMeshIntersect(const OcTree* tree1, const BVHModel<BV>* tree2,
102 const Transform3s& tf1, const Transform3s& tf2,
103 const CollisionRequest& request_,
104 CollisionResult& result_) const {
105 200 crequest = &request_;
106 200 cresult = &result_;
107
108
2/4
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 100 times.
✗ Branch 6 not taken.
200 OcTreeMeshIntersectRecurse(tree1, tree1->getRoot(), tree1->getRootBV(),
109 tree2, 0, tf1, tf2);
110 }
111
112 /// @brief distance between octree and mesh
113 template <typename BV>
114 void OcTreeMeshDistance(const OcTree* tree1, const BVHModel<BV>* tree2,
115 const Transform3s& tf1, const Transform3s& tf2,
116 const DistanceRequest& request_,
117 DistanceResult& result_) const {
118 drequest = &request_;
119 dresult = &result_;
120
121 OcTreeMeshDistanceRecurse(tree1, tree1->getRoot(), tree1->getRootBV(),
122 tree2, 0, tf1, tf2);
123 }
124
125 /// @brief collision between mesh and octree
126 template <typename BV>
127 void MeshOcTreeIntersect(const BVHModel<BV>* tree1, const OcTree* tree2,
128 const Transform3s& tf1, const Transform3s& tf2,
129 const CollisionRequest& request_,
130 CollisionResult& result_) const
131
132 {
133 crequest = &request_;
134 cresult = &result_;
135
136 OcTreeMeshIntersectRecurse(tree2, tree2->getRoot(), tree2->getRootBV(),
137 tree1, 0, tf2, tf1);
138 }
139
140 /// @brief distance between mesh and octree
141 template <typename BV>
142 void MeshOcTreeDistance(const BVHModel<BV>* tree1, const OcTree* tree2,
143 const Transform3s& tf1, const Transform3s& tf2,
144 const DistanceRequest& request_,
145 DistanceResult& result_) const {
146 drequest = &request_;
147 dresult = &result_;
148
149 OcTreeMeshDistanceRecurse(tree1, 0, tree2, tree2->getRoot(),
150 tree2->getRootBV(), tf1, tf2);
151 }
152
153 template <typename BV>
154 2000 void OcTreeHeightFieldIntersect(
155 const OcTree* tree1, const HeightField<BV>* tree2, const Transform3s& tf1,
156 const Transform3s& tf2, const CollisionRequest& request_,
157 CollisionResult& result_, CoalScalar& sqrDistLowerBound) const {
158 2000 crequest = &request_;
159 2000 cresult = &result_;
160
161
2/4
✓ Branch 2 taken 1000 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1000 times.
✗ Branch 6 not taken.
2000 OcTreeHeightFieldIntersectRecurse(tree1, tree1->getRoot(),
162 tree1->getRootBV(), tree2, 0, tf1, tf2,
163 sqrDistLowerBound);
164 }
165
166 template <typename BV>
167 2000 void HeightFieldOcTreeIntersect(const HeightField<BV>* tree1,
168 const OcTree* tree2, const Transform3s& tf1,
169 const Transform3s& tf2,
170 const CollisionRequest& request_,
171 CollisionResult& result_,
172 CoalScalar& sqrDistLowerBound) const {
173 2000 crequest = &request_;
174 2000 cresult = &result_;
175
176
2/4
✓ Branch 2 taken 1000 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1000 times.
✗ Branch 6 not taken.
2000 HeightFieldOcTreeIntersectRecurse(tree1, 0, tree2, tree2->getRoot(),
177 tree2->getRootBV(), tf1, tf2,
178 sqrDistLowerBound);
179 }
180
181 /// @brief collision between octree and shape
182 template <typename S>
183 2000 void OcTreeShapeIntersect(const OcTree* tree, const S& s,
184 const Transform3s& tf1, const Transform3s& tf2,
185 const CollisionRequest& request_,
186 CollisionResult& result_) const {
187 2000 crequest = &request_;
188 2000 cresult = &result_;
189
190
1/2
✓ Branch 1 taken 1000 times.
✗ Branch 2 not taken.
2000 AABB bv2;
191
2/4
✓ Branch 1 taken 1000 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1000 times.
✗ Branch 5 not taken.
2000 computeBV<AABB>(s, Transform3s(), bv2);
192
1/2
✓ Branch 1 taken 1000 times.
✗ Branch 2 not taken.
2000 OBB obb2;
193
1/2
✓ Branch 1 taken 1000 times.
✗ Branch 2 not taken.
2000 convertBV(bv2, tf2, obb2);
194
3/6
✓ Branch 1 taken 1000 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1000 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1000 times.
✗ Branch 8 not taken.
2000 OcTreeShapeIntersectRecurse(tree, tree->getRoot(), tree->getRootBV(), s,
195 obb2, tf1, tf2);
196 }
197
198 /// @brief collision between shape and octree
199 template <typename S>
200 void ShapeOcTreeIntersect(const S& s, const OcTree* tree,
201 const Transform3s& tf1, const Transform3s& tf2,
202 const CollisionRequest& request_,
203 CollisionResult& result_) const {
204 crequest = &request_;
205 cresult = &result_;
206
207 AABB bv1;
208 computeBV<AABB>(s, Transform3s(), bv1);
209 OBB obb1;
210 convertBV(bv1, tf1, obb1);
211 OcTreeShapeIntersectRecurse(tree, tree->getRoot(), tree->getRootBV(), s,
212 obb1, tf2, tf1);
213 }
214
215 /// @brief distance between octree and shape
216 template <typename S>
217 void OcTreeShapeDistance(const OcTree* tree, const S& s,
218 const Transform3s& tf1, const Transform3s& tf2,
219 const DistanceRequest& request_,
220 DistanceResult& result_) const {
221 drequest = &request_;
222 dresult = &result_;
223
224 AABB aabb2;
225 computeBV<AABB>(s, tf2, aabb2);
226 OcTreeShapeDistanceRecurse(tree, tree->getRoot(), tree->getRootBV(), s,
227 aabb2, tf1, tf2);
228 }
229
230 /// @brief distance between shape and octree
231 template <typename S>
232 void ShapeOcTreeDistance(const S& s, const OcTree* tree,
233 const Transform3s& tf1, const Transform3s& tf2,
234 const DistanceRequest& request_,
235 DistanceResult& result_) const {
236 drequest = &request_;
237 dresult = &result_;
238
239 AABB aabb1;
240 computeBV<AABB>(s, tf1, aabb1);
241 OcTreeShapeDistanceRecurse(tree, tree->getRoot(), tree->getRootBV(), s,
242 aabb1, tf2, tf1);
243 }
244
245 private:
246 template <typename S>
247 bool OcTreeShapeDistanceRecurse(const OcTree* tree1,
248 const OcTree::OcTreeNode* root1,
249 const AABB& bv1, const S& s,
250 const AABB& aabb2, const Transform3s& tf1,
251 const Transform3s& tf2) const {
252 if (!tree1->nodeHasChildren(root1)) {
253 if (tree1->isNodeOccupied(root1)) {
254 Box box;
255 Transform3s box_tf;
256 constructBox(bv1, tf1, box, box_tf);
257
258 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
259 box.computeLocalAABB();
260 }
261
262 Vec3s p1, p2, normal;
263 const CoalScalar distance = internal::ShapeShapeDistance<Box, S>(
264 &box, box_tf, &s, tf2, this->solver,
265 this->drequest->enable_signed_distance, p1, p2, normal);
266
267 this->dresult->update(distance, tree1, &s,
268 (int)(root1 - tree1->getRoot()),
269 DistanceResult::NONE, p1, p2, normal);
270
271 return drequest->isSatisfied(*dresult);
272 } else
273 return false;
274 }
275
276 if (!tree1->isNodeOccupied(root1)) return false;
277
278 for (unsigned int i = 0; i < 8; ++i) {
279 if (tree1->nodeChildExists(root1, i)) {
280 const OcTree::OcTreeNode* child = tree1->getNodeChild(root1, i);
281 AABB child_bv;
282 computeChildBV(bv1, i, child_bv);
283
284 AABB aabb1;
285 convertBV(child_bv, tf1, aabb1);
286 CoalScalar d = aabb1.distance(aabb2);
287 if (d < dresult->min_distance) {
288 if (OcTreeShapeDistanceRecurse(tree1, child, child_bv, s, aabb2, tf1,
289 tf2))
290 return true;
291 }
292 }
293 }
294
295 return false;
296 }
297
298 template <typename S>
299 56052 bool OcTreeShapeIntersectRecurse(const OcTree* tree1,
300 const OcTree::OcTreeNode* root1,
301 const AABB& bv1, const S& s, const OBB& obb2,
302 const Transform3s& tf1,
303 const Transform3s& tf2) const {
304 // Empty OcTree is considered free.
305
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28026 times.
56052 if (!root1) return false;
306
307 /// stop when 1) bounding boxes of two objects not overlap; OR
308 /// 2) at least of one the nodes is free; OR
309 /// 2) (two uncertain nodes or one node occupied and one node
310 /// uncertain) AND cost not required
311
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 28026 times.
56052 if (tree1->isNodeFree(root1))
312 return false;
313
3/6
✓ Branch 1 taken 28026 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 28026 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 28026 times.
56052 else if ((tree1->isNodeUncertain(root1) || s.isUncertain()))
314 return false;
315 else {
316
1/2
✓ Branch 1 taken 28026 times.
✗ Branch 2 not taken.
56052 OBB obb1;
317
1/2
✓ Branch 1 taken 28026 times.
✗ Branch 2 not taken.
56052 convertBV(bv1, tf1, obb1);
318 CoalScalar sqrDistLowerBound;
319
3/4
✓ Branch 1 taken 28026 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 17739 times.
✓ Branch 4 taken 10287 times.
56052 if (!obb1.overlap(obb2, *crequest, sqrDistLowerBound)) {
320 35478 internal::updateDistanceLowerBoundFromBV(*crequest, *cresult,
321 sqrDistLowerBound);
322 35478 return false;
323 }
324 }
325
326
2/2
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 10271 times.
20574 if (!tree1->nodeHasChildren(root1)) {
327
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
32 assert(tree1->isNodeOccupied(root1)); // it isn't free nor uncertain.
328
329
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 Box box;
330
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 Transform3s box_tf;
331
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 constructBox(bv1, tf1, box, box_tf);
332
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
32 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
333 box.computeLocalAABB();
334 }
335
336 32 bool contactNotAdded =
337 32 (cresult->numContacts() >= crequest->num_max_contacts);
338 64 std::size_t ncontact = ShapeShapeCollide<Box, S>(
339
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 &box, box_tf, &s, tf2, solver, *crequest, *cresult);
340
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
32 assert(ncontact == 0 || ncontact == 1);
341
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
32 if (!contactNotAdded && ncontact == 1) {
342 // Update contact information.
343
1/2
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
32 const Contact& c = cresult->getContact(cresult->numContacts() - 1);
344
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 cresult->setContact(
345 32 cresult->numContacts() - 1,
346
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
64 Contact(tree1, c.o2, static_cast<int>(root1 - tree1->getRoot()),
347
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 c.b2, c.pos, c.normal, c.penetration_depth));
348 }
349
350 // no need to call `internal::updateDistanceLowerBoundFromLeaf` here
351 // as it is already done internally in `ShapeShapeCollide` above.
352
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 return crequest->isSatisfied(*cresult);
353 }
354
355
2/2
✓ Branch 0 taken 81389 times.
✓ Branch 1 taken 10025 times.
182828 for (unsigned int i = 0; i < 8; ++i) {
356
2/2
✓ Branch 1 taken 27026 times.
✓ Branch 2 taken 54363 times.
162778 if (tree1->nodeChildExists(root1, i)) {
357
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 const OcTree::OcTreeNode* child = tree1->getNodeChild(root1, i);
358
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 AABB child_bv;
359
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 computeChildBV(bv1, i, child_bv);
360
361
3/4
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 246 times.
✓ Branch 4 taken 26780 times.
54052 if (OcTreeShapeIntersectRecurse(tree1, child, child_bv, s, obb2, tf1,
362 tf2))
363 492 return true;
364 }
365 }
366
367 20050 return false;
368 }
369
370 template <typename BV>
371 bool OcTreeMeshDistanceRecurse(const OcTree* tree1,
372 const OcTree::OcTreeNode* root1,
373 const AABB& bv1, const BVHModel<BV>* tree2,
374 unsigned int root2, const Transform3s& tf1,
375 const Transform3s& tf2) const {
376 if (!tree1->nodeHasChildren(root1) && tree2->getBV(root2).isLeaf()) {
377 if (tree1->isNodeOccupied(root1)) {
378 Box box;
379 Transform3s box_tf;
380 constructBox(bv1, tf1, box, box_tf);
381
382 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
383 box.computeLocalAABB();
384 }
385
386 size_t primitive_id =
387 static_cast<size_t>(tree2->getBV(root2).primitiveId());
388 const Triangle& tri_id = (*(tree2->tri_indices))[primitive_id];
389 const TriangleP tri((*(tree2->vertices))[tri_id[0]],
390 (*(tree2->vertices))[tri_id[1]],
391 (*(tree2->vertices))[tri_id[2]]);
392
393 Vec3s p1, p2, normal;
394 const CoalScalar distance =
395 internal::ShapeShapeDistance<Box, TriangleP>(
396 &box, box_tf, &tri, tf2, this->solver,
397 this->drequest->enable_signed_distance, p1, p2, normal);
398
399 this->dresult->update(distance, tree1, tree2,
400 (int)(root1 - tree1->getRoot()),
401 static_cast<int>(primitive_id), p1, p2, normal);
402
403 return this->drequest->isSatisfied(*dresult);
404 } else
405 return false;
406 }
407
408 if (!tree1->isNodeOccupied(root1)) return false;
409
410 if (tree2->getBV(root2).isLeaf() ||
411 (tree1->nodeHasChildren(root1) &&
412 (bv1.size() > tree2->getBV(root2).bv.size()))) {
413 for (unsigned int i = 0; i < 8; ++i) {
414 if (tree1->nodeChildExists(root1, i)) {
415 const OcTree::OcTreeNode* child = tree1->getNodeChild(root1, i);
416 AABB child_bv;
417 computeChildBV(bv1, i, child_bv);
418
419 CoalScalar d;
420 AABB aabb1, aabb2;
421 convertBV(child_bv, tf1, aabb1);
422 convertBV(tree2->getBV(root2).bv, tf2, aabb2);
423 d = aabb1.distance(aabb2);
424
425 if (d < dresult->min_distance) {
426 if (OcTreeMeshDistanceRecurse(tree1, child, child_bv, tree2, root2,
427 tf1, tf2))
428 return true;
429 }
430 }
431 }
432 } else {
433 CoalScalar d;
434 AABB aabb1, aabb2;
435 convertBV(bv1, tf1, aabb1);
436 unsigned int child = (unsigned int)tree2->getBV(root2).leftChild();
437 convertBV(tree2->getBV(child).bv, tf2, aabb2);
438 d = aabb1.distance(aabb2);
439
440 if (d < dresult->min_distance) {
441 if (OcTreeMeshDistanceRecurse(tree1, root1, bv1, tree2, child, tf1,
442 tf2))
443 return true;
444 }
445
446 child = (unsigned int)tree2->getBV(root2).rightChild();
447 convertBV(tree2->getBV(child).bv, tf2, aabb2);
448 d = aabb1.distance(aabb2);
449
450 if (d < dresult->min_distance) {
451 if (OcTreeMeshDistanceRecurse(tree1, root1, bv1, tree2, child, tf1,
452 tf2))
453 return true;
454 }
455 }
456
457 return false;
458 }
459
460 /// \return True if the request is satisfied.
461 template <typename BV>
462 57964 bool OcTreeMeshIntersectRecurse(const OcTree* tree1,
463 const OcTree::OcTreeNode* root1,
464 const AABB& bv1, const BVHModel<BV>* tree2,
465 unsigned int root2, const Transform3s& tf1,
466 const Transform3s& tf2) const {
467 // FIXME(jmirabel) I do not understand why the BVHModel was traversed. The
468 // code in this if(!root1) did not output anything so the empty OcTree is
469 // considered free. Should an empty OcTree be considered free ?
470
471 // Empty OcTree is considered free.
472
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28982 times.
57964 if (!root1) return false;
473 57964 BVNode<BV> const& bvn2 = tree2->getBV(root2);
474
475 /// stop when 1) bounding boxes of two objects not overlap; OR
476 /// 2) at least one of the nodes is free; OR
477 /// 2) (two uncertain nodes OR one node occupied and one node
478 /// uncertain) AND cost not required
479
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 28982 times.
57964 if (tree1->isNodeFree(root1))
480 return false;
481
3/6
✓ Branch 1 taken 28982 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 28982 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 28982 times.
57964 else if ((tree1->isNodeUncertain(root1) || tree2->isUncertain()))
482 return false;
483 else {
484
2/4
✓ Branch 1 taken 28982 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28982 times.
✗ Branch 5 not taken.
57964 OBB obb1, obb2;
485
1/2
✓ Branch 1 taken 28982 times.
✗ Branch 2 not taken.
57964 convertBV(bv1, tf1, obb1);
486
1/2
✓ Branch 1 taken 28982 times.
✗ Branch 2 not taken.
57964 convertBV(bvn2.bv, tf2, obb2);
487 CoalScalar sqrDistLowerBound;
488
3/4
✓ Branch 1 taken 28982 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 17430 times.
✓ Branch 4 taken 11552 times.
57964 if (!obb1.overlap(obb2, *crequest, sqrDistLowerBound)) {
489 34860 internal::updateDistanceLowerBoundFromBV(*crequest, *cresult,
490 sqrDistLowerBound);
491 34860 return false;
492 }
493 }
494
495 // Check if leaf collides.
496
5/6
✓ Branch 1 taken 539 times.
✓ Branch 2 taken 11013 times.
✓ Branch 4 taken 539 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 539 times.
✓ Branch 7 taken 11013 times.
23104 if (!tree1->nodeHasChildren(root1) && bvn2.isLeaf()) {
497
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 539 times.
1078 assert(tree1->isNodeOccupied(root1)); // it isn't free nor uncertain.
498
1/2
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
1078 Box box;
499
1/2
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
1078 Transform3s box_tf;
500
1/2
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
1078 constructBox(bv1, tf1, box, box_tf);
501
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 539 times.
1078 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
502 box.computeLocalAABB();
503 }
504
505 1078 size_t primitive_id = static_cast<size_t>(bvn2.primitiveId());
506 1078 const Triangle& tri_id = (*(tree2->tri_indices))[primitive_id];
507
1/2
✓ Branch 4 taken 539 times.
✗ Branch 5 not taken.
1078 const TriangleP tri((*(tree2->vertices))[tri_id[0]],
508 1078 (*(tree2->vertices))[tri_id[1]],
509 1078 (*(tree2->vertices))[tri_id[2]]);
510
511 // When reaching this point, `this->solver` has already been set up
512 // by the CollisionRequest `this->crequest`.
513 // The only thing we need to (and can) pass to `ShapeShapeDistance` is
514 // whether or not penetration information is should be computed in case of
515 // collision.
516
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 539 times.
1078 const bool compute_penetration = this->crequest->enable_contact ||
517 (this->crequest->security_margin < 0);
518
3/6
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 539 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 539 times.
✗ Branch 8 not taken.
1078 Vec3s c1, c2, normal;
519 2156 const CoalScalar distance = internal::ShapeShapeDistance<Box, TriangleP>(
520
1/2
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
1078 &box, box_tf, &tri, tf2, this->solver, compute_penetration, c1, c2,
521 normal);
522 1078 const CoalScalar distToCollision =
523 1078 distance - this->crequest->security_margin;
524
525 1078 internal::updateDistanceLowerBoundFromLeaf(
526
1/2
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
1078 *(this->crequest), *(this->cresult), distToCollision, c1, c2, normal);
527
528
1/2
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
1078 if (cresult->numContacts() < crequest->num_max_contacts) {
529
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 486 times.
1078 if (distToCollision <= crequest->collision_distance_threshold) {
530
1/2
✓ Branch 1 taken 53 times.
✗ Branch 2 not taken.
106 cresult->addContact(Contact(
531
2/4
✓ Branch 1 taken 53 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 53 times.
✗ Branch 5 not taken.
106 tree1, tree2, (int)(root1 - tree1->getRoot()),
532 static_cast<int>(primitive_id), c1, c2, normal, distance));
533 }
534 }
535
1/2
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
1078 return crequest->isSatisfied(*cresult);
536 }
537
538 // Determine which tree to traverse first.
539
4/4
✓ Branch 1 taken 8970 times.
✓ Branch 2 taken 2043 times.
✓ Branch 3 taken 5600 times.
✓ Branch 4 taken 5413 times.
39966 if (bvn2.isLeaf() ||
540
3/4
✓ Branch 1 taken 8970 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 3557 times.
✓ Branch 6 taken 5413 times.
17940 (tree1->nodeHasChildren(root1) && (bv1.size() > bvn2.bv.size()))) {
541
2/2
✓ Branch 0 taken 42996 times.
✓ Branch 1 taken 4790 times.
95572 for (unsigned int i = 0; i < 8; ++i) {
542
2/2
✓ Branch 1 taken 18305 times.
✓ Branch 2 taken 24691 times.
85992 if (tree1->nodeChildExists(root1, i)) {
543
1/2
✓ Branch 1 taken 18305 times.
✗ Branch 2 not taken.
36610 const OcTree::OcTreeNode* child = tree1->getNodeChild(root1, i);
544
1/2
✓ Branch 1 taken 18305 times.
✗ Branch 2 not taken.
36610 AABB child_bv;
545
1/2
✓ Branch 1 taken 18305 times.
✗ Branch 2 not taken.
36610 computeChildBV(bv1, i, child_bv);
546
547
3/4
✓ Branch 1 taken 18305 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 810 times.
✓ Branch 4 taken 17495 times.
36610 if (OcTreeMeshIntersectRecurse(tree1, child, child_bv, tree2, root2,
548 tf1, tf2))
549 1620 return true;
550 }
551 }
552 } else {
553
2/2
✓ Branch 1 taken 249 times.
✓ Branch 2 taken 5164 times.
10826 if (OcTreeMeshIntersectRecurse(tree1, root1, bv1, tree2,
554 10826 (unsigned int)bvn2.leftChild(), tf1, tf2))
555 498 return true;
556
557
2/2
✓ Branch 1 taken 147 times.
✓ Branch 2 taken 5017 times.
10328 if (OcTreeMeshIntersectRecurse(tree1, root1, bv1, tree2,
558 10328 (unsigned int)bvn2.rightChild(), tf1, tf2))
559 294 return true;
560 }
561
562 19614 return false;
563 }
564
565 /// \return True if the request is satisfied.
566 template <typename BV>
567 56584 bool OcTreeHeightFieldIntersectRecurse(
568 const OcTree* tree1, const OcTree::OcTreeNode* root1, const AABB& bv1,
569 const HeightField<BV>* tree2, unsigned int root2, const Transform3s& tf1,
570 const Transform3s& tf2, CoalScalar& sqrDistLowerBound) const {
571 // FIXME(jmirabel) I do not understand why the BVHModel was traversed. The
572 // code in this if(!root1) did not output anything so the empty OcTree is
573 // considered free. Should an empty OcTree be considered free ?
574
575 // Empty OcTree is considered free.
576
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28292 times.
56584 if (!root1) return false;
577 56584 HFNode<BV> const& bvn2 = tree2->getBV(root2);
578
579 /// stop when 1) bounding boxes of two objects not overlap; OR
580 /// 2) at least one of the nodes is free; OR
581 /// 2) (two uncertain nodes OR one node occupied and one node
582 /// uncertain) AND cost not required
583
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 28292 times.
56584 if (tree1->isNodeFree(root1))
584 return false;
585
3/6
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 28292 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 28292 times.
56584 else if ((tree1->isNodeUncertain(root1) || tree2->isUncertain()))
586 return false;
587 else {
588
2/4
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28292 times.
✗ Branch 5 not taken.
56584 OBB obb1, obb2;
589
1/2
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
56584 convertBV(bv1, tf1, obb1);
590
1/2
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
56584 convertBV(bvn2.bv, tf2, obb2);
591 CoalScalar sqrDistLowerBound_;
592
3/4
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 17800 times.
✓ Branch 4 taken 10492 times.
56584 if (!obb1.overlap(obb2, *crequest, sqrDistLowerBound_)) {
593
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17800 times.
35600 if (sqrDistLowerBound_ < sqrDistLowerBound)
594 sqrDistLowerBound = sqrDistLowerBound_;
595 35600 internal::updateDistanceLowerBoundFromBV(*crequest, *cresult,
596 sqrDistLowerBound);
597 35600 return false;
598 }
599 }
600
601 // Check if leaf collides.
602
6/6
✓ Branch 1 taken 221 times.
✓ Branch 2 taken 10271 times.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 205 times.
✓ Branch 6 taken 16 times.
✓ Branch 7 taken 10476 times.
20984 if (!tree1->nodeHasChildren(root1) && bvn2.isLeaf()) {
603
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
32 assert(tree1->isNodeOccupied(root1)); // it isn't free nor uncertain.
604
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 Box box;
605
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 Transform3s box_tf;
606
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 constructBox(bv1, tf1, box, box_tf);
607
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
32 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
608 box.computeLocalAABB();
609 }
610
611 typedef Convex<Triangle> ConvexTriangle;
612
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
32 ConvexTriangle convex1, convex2;
613 int convex1_active_faces, convex2_active_faces;
614
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 details::buildConvexTriangles(bvn2, *tree2, convex1, convex1_active_faces,
615 convex2, convex2_active_faces);
616
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
32 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
617 convex1.computeLocalAABB();
618 convex2.computeLocalAABB();
619 }
620
621
4/8
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 16 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 16 times.
✗ Branch 11 not taken.
32 Vec3s c1, c2, normal, normal_top;
622 CoalScalar distance;
623 bool hfield_witness_is_on_bin_side;
624
625 64 bool collision = details::shapeDistance<Triangle, Box, 0>(
626
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 solver, *crequest, convex1, convex1_active_faces, convex2,
627 convex2_active_faces, tf2, box, box_tf, distance, c2, c1, normal,
628 normal_top, hfield_witness_is_on_bin_side);
629
630 32 CoalScalar distToCollision =
631
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 distance - crequest->security_margin * (normal_top.dot(normal));
632
633
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
32 if (distToCollision <= crequest->collision_distance_threshold) {
634 32 sqrDistLowerBound = 0;
635
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 if (crequest->num_max_contacts > cresult->numContacts()) {
636
4/8
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 16 times.
✓ Branch 8 taken 16 times.
✗ Branch 9 not taken.
32 if (normal_top.isApprox(normal) &&
637 (collision || !hfield_witness_is_on_bin_side)) {
638
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 cresult->addContact(
639
4/8
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 16 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 16 times.
✗ Branch 11 not taken.
64 Contact(tree1, tree2, (int)(root1 - tree1->getRoot()),
640 (int)Contact::NONE, c1, c2, -normal, distance));
641 }
642 }
643 } else
644 sqrDistLowerBound = distToCollision * distToCollision;
645
646 // const Vec3s c1 = contact_point - distance * 0.5 * normal;
647 // const Vec3s c2 = contact_point + distance * 0.5 * normal;
648
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
32 internal::updateDistanceLowerBoundFromLeaf(
649
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 *crequest, *cresult, distToCollision, c1, c2, -normal);
650
651
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
32 assert(cresult->isCollision() || sqrDistLowerBound > 0);
652
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 return crequest->isSatisfied(*cresult);
653 }
654
655 // Determine which tree to traverse first.
656
3/4
✓ Branch 1 taken 10476 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10271 times.
✓ Branch 4 taken 205 times.
41904 if (bvn2.isLeaf() ||
657
3/4
✓ Branch 1 taken 10271 times.
✓ Branch 2 taken 205 times.
✓ Branch 5 taken 10271 times.
✗ Branch 6 not taken.
20952 (tree1->nodeHasChildren(root1) && (bv1.size() > bvn2.bv.size()))) {
658
2/2
✓ Branch 0 taken 81389 times.
✓ Branch 1 taken 10025 times.
182828 for (unsigned int i = 0; i < 8; ++i) {
659
2/2
✓ Branch 1 taken 27026 times.
✓ Branch 2 taken 54363 times.
162778 if (tree1->nodeChildExists(root1, i)) {
660
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 const OcTree::OcTreeNode* child = tree1->getNodeChild(root1, i);
661
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 AABB child_bv;
662
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 computeChildBV(bv1, i, child_bv);
663
664
3/4
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 246 times.
✓ Branch 4 taken 26780 times.
54052 if (OcTreeHeightFieldIntersectRecurse(tree1, child, child_bv, tree2,
665 root2, tf1, tf2,
666 sqrDistLowerBound))
667 492 return true;
668 }
669 }
670 } else {
671
2/2
✓ Branch 1 taken 144 times.
✓ Branch 2 taken 61 times.
410 if (OcTreeHeightFieldIntersectRecurse(tree1, root1, bv1, tree2,
672 410 (unsigned int)bvn2.leftChild(), tf1,
673 tf2, sqrDistLowerBound))
674 288 return true;
675
676
1/2
✓ Branch 1 taken 61 times.
✗ Branch 2 not taken.
122 if (OcTreeHeightFieldIntersectRecurse(tree1, root1, bv1, tree2,
677 122 (unsigned int)bvn2.rightChild(),
678 tf1, tf2, sqrDistLowerBound))
679 122 return true;
680 }
681
682 20050 return false;
683 }
684
685 /// \return True if the request is satisfied.
686 template <typename BV>
687 56584 bool HeightFieldOcTreeIntersectRecurse(
688 const HeightField<BV>* tree1, unsigned int root1, const OcTree* tree2,
689 const OcTree::OcTreeNode* root2, const AABB& bv2, const Transform3s& tf1,
690 const Transform3s& tf2, CoalScalar& sqrDistLowerBound) const {
691 // FIXME(jmirabel) I do not understand why the BVHModel was traversed. The
692 // code in this if(!root1) did not output anything so the empty OcTree is
693 // considered free. Should an empty OcTree be considered free ?
694
695 // Empty OcTree is considered free.
696
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28292 times.
56584 if (!root2) return false;
697 56584 HFNode<BV> const& bvn1 = tree1->getBV(root1);
698
699 /// stop when 1) bounding boxes of two objects not overlap; OR
700 /// 2) at least one of the nodes is free; OR
701 /// 2) (two uncertain nodes OR one node occupied and one node
702 /// uncertain) AND cost not required
703
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 28292 times.
56584 if (tree2->isNodeFree(root2))
704 return false;
705
3/6
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 28292 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 28292 times.
56584 else if ((tree2->isNodeUncertain(root2) || tree1->isUncertain()))
706 return false;
707 else {
708
2/4
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28292 times.
✗ Branch 5 not taken.
56584 OBB obb1, obb2;
709
1/2
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
56584 convertBV(bvn1.bv, tf1, obb1);
710
1/2
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
56584 convertBV(bv2, tf2, obb2);
711 CoalScalar sqrDistLowerBound_;
712
3/4
✓ Branch 1 taken 28292 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 17800 times.
✓ Branch 4 taken 10492 times.
56584 if (!obb2.overlap(obb1, *crequest, sqrDistLowerBound_)) {
713
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17800 times.
35600 if (sqrDistLowerBound_ < sqrDistLowerBound)
714 sqrDistLowerBound = sqrDistLowerBound_;
715 35600 internal::updateDistanceLowerBoundFromBV(*crequest, *cresult,
716 sqrDistLowerBound);
717 35600 return false;
718 }
719 }
720
721 // Check if leaf collides.
722
6/6
✓ Branch 1 taken 221 times.
✓ Branch 2 taken 10271 times.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 205 times.
✓ Branch 6 taken 16 times.
✓ Branch 7 taken 10476 times.
20984 if (!tree2->nodeHasChildren(root2) && bvn1.isLeaf()) {
723
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
32 assert(tree2->isNodeOccupied(root2)); // it isn't free nor uncertain.
724
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 Box box;
725
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 Transform3s box_tf;
726
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 constructBox(bv2, tf2, box, box_tf);
727
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
32 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
728 box.computeLocalAABB();
729 }
730
731 typedef Convex<Triangle> ConvexTriangle;
732
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
32 ConvexTriangle convex1, convex2;
733 int convex1_active_faces, convex2_active_faces;
734
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 details::buildConvexTriangles(bvn1, *tree1, convex1, convex1_active_faces,
735 convex2, convex2_active_faces);
736
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
32 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
737 convex1.computeLocalAABB();
738 convex2.computeLocalAABB();
739 }
740
741
4/8
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 16 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 16 times.
✗ Branch 11 not taken.
32 Vec3s c1, c2, normal, normal_top;
742 CoalScalar distance;
743 bool hfield_witness_is_on_bin_side;
744
745 64 bool collision = details::shapeDistance<Triangle, Box, 0>(
746
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 solver, *crequest, convex1, convex1_active_faces, convex2,
747 convex2_active_faces, tf1, box, box_tf, distance, c1, c2, normal,
748 normal_top, hfield_witness_is_on_bin_side);
749
750 32 CoalScalar distToCollision =
751
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 distance - crequest->security_margin * (normal_top.dot(normal));
752
753
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
32 if (distToCollision <= crequest->collision_distance_threshold) {
754 32 sqrDistLowerBound = 0;
755
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 if (crequest->num_max_contacts > cresult->numContacts()) {
756
4/8
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 16 times.
✓ Branch 8 taken 16 times.
✗ Branch 9 not taken.
32 if (normal_top.isApprox(normal) &&
757 (collision || !hfield_witness_is_on_bin_side)) {
758
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 cresult->addContact(Contact(tree1, tree2, (int)Contact::NONE,
759
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
32 (int)(root2 - tree2->getRoot()), c1, c2,
760 normal, distance));
761 }
762 }
763 } else
764 sqrDistLowerBound = distToCollision * distToCollision;
765
766 // const Vec3s c1 = contact_point - distance * 0.5 * normal;
767 // const Vec3s c2 = contact_point + distance * 0.5 * normal;
768 32 internal::updateDistanceLowerBoundFromLeaf(
769
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 *crequest, *cresult, distToCollision, c1, c2, normal);
770
771
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
32 assert(cresult->isCollision() || sqrDistLowerBound > 0);
772
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 return crequest->isSatisfied(*cresult);
773 }
774
775 // Determine which tree to traverse first.
776
3/4
✓ Branch 1 taken 10476 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10271 times.
✓ Branch 4 taken 205 times.
41904 if (bvn1.isLeaf() ||
777
3/4
✓ Branch 1 taken 10271 times.
✓ Branch 2 taken 205 times.
✓ Branch 5 taken 10271 times.
✗ Branch 6 not taken.
20952 (tree2->nodeHasChildren(root2) && (bv2.size() > bvn1.bv.size()))) {
778
2/2
✓ Branch 0 taken 81389 times.
✓ Branch 1 taken 10025 times.
182828 for (unsigned int i = 0; i < 8; ++i) {
779
2/2
✓ Branch 1 taken 27026 times.
✓ Branch 2 taken 54363 times.
162778 if (tree2->nodeChildExists(root2, i)) {
780
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 const OcTree::OcTreeNode* child = tree2->getNodeChild(root2, i);
781
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 AABB child_bv;
782
1/2
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
54052 computeChildBV(bv2, i, child_bv);
783
784
3/4
✓ Branch 1 taken 27026 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 246 times.
✓ Branch 4 taken 26780 times.
54052 if (HeightFieldOcTreeIntersectRecurse(tree1, root1, tree2, child,
785 child_bv, tf1, tf2,
786 sqrDistLowerBound))
787 492 return true;
788 }
789 }
790 } else {
791
2/2
✓ Branch 1 taken 144 times.
✓ Branch 2 taken 61 times.
410 if (HeightFieldOcTreeIntersectRecurse(
792 410 tree1, (unsigned int)bvn1.leftChild(), tree2, root2, bv2, tf1,
793 tf2, sqrDistLowerBound))
794 288 return true;
795
796
1/2
✓ Branch 1 taken 61 times.
✗ Branch 2 not taken.
122 if (HeightFieldOcTreeIntersectRecurse(
797 122 tree1, (unsigned int)bvn1.rightChild(), tree2, root2, bv2, tf1,
798 tf2, sqrDistLowerBound))
799 122 return true;
800 }
801
802 20050 return false;
803 }
804
805 bool OcTreeDistanceRecurse(const OcTree* tree1,
806 const OcTree::OcTreeNode* root1, const AABB& bv1,
807 const OcTree* tree2,
808 const OcTree::OcTreeNode* root2, const AABB& bv2,
809 const Transform3s& tf1,
810 const Transform3s& tf2) const {
811 if (!tree1->nodeHasChildren(root1) && !tree2->nodeHasChildren(root2)) {
812 if (tree1->isNodeOccupied(root1) && tree2->isNodeOccupied(root2)) {
813 Box box1, box2;
814 Transform3s box1_tf, box2_tf;
815 constructBox(bv1, tf1, box1, box1_tf);
816 constructBox(bv2, tf2, box2, box2_tf);
817
818 if (solver->gjk_initial_guess == GJKInitialGuess::BoundingVolumeGuess) {
819 box1.computeLocalAABB();
820 box2.computeLocalAABB();
821 }
822
823 Vec3s p1, p2, normal;
824 const CoalScalar distance = internal::ShapeShapeDistance<Box, Box>(
825 &box1, box1_tf, &box2, box2_tf, this->solver,
826 this->drequest->enable_signed_distance, p1, p2, normal);
827
828 this->dresult->update(distance, tree1, tree2,
829 (int)(root1 - tree1->getRoot()),
830 (int)(root2 - tree2->getRoot()), p1, p2, normal);
831
832 return drequest->isSatisfied(*dresult);
833 } else
834 return false;
835 }
836
837 if (!tree1->isNodeOccupied(root1) || !tree2->isNodeOccupied(root2))
838 return false;
839
840 if (!tree2->nodeHasChildren(root2) ||
841 (tree1->nodeHasChildren(root1) && (bv1.size() > bv2.size()))) {
842 for (unsigned int i = 0; i < 8; ++i) {
843 if (tree1->nodeChildExists(root1, i)) {
844 const OcTree::OcTreeNode* child = tree1->getNodeChild(root1, i);
845 AABB child_bv;
846 computeChildBV(bv1, i, child_bv);
847
848 CoalScalar d;
849 AABB aabb1, aabb2;
850 convertBV(bv1, tf1, aabb1);
851 convertBV(bv2, tf2, aabb2);
852 d = aabb1.distance(aabb2);
853
854 if (d < dresult->min_distance) {
855 if (OcTreeDistanceRecurse(tree1, child, child_bv, tree2, root2, bv2,
856 tf1, tf2))
857 return true;
858 }
859 }
860 }
861 } else {
862 for (unsigned int i = 0; i < 8; ++i) {
863 if (tree2->nodeChildExists(root2, i)) {
864 const OcTree::OcTreeNode* child = tree2->getNodeChild(root2, i);
865 AABB child_bv;
866 computeChildBV(bv2, i, child_bv);
867
868 CoalScalar d;
869 AABB aabb1, aabb2;
870 convertBV(bv1, tf1, aabb1);
871 convertBV(bv2, tf2, aabb2);
872 d = aabb1.distance(aabb2);
873
874 if (d < dresult->min_distance) {
875 if (OcTreeDistanceRecurse(tree1, root1, bv1, tree2, child, child_bv,
876 tf1, tf2))
877 return true;
878 }
879 }
880 }
881 }
882
883 return false;
884 }
885
886 bool OcTreeIntersectRecurse(const OcTree* tree1,
887 const OcTree::OcTreeNode* root1, const AABB& bv1,
888 const OcTree* tree2,
889 const OcTree::OcTreeNode* root2, const AABB& bv2,
890 const Transform3s& tf1,
891 const Transform3s& tf2) const {
892 // Empty OcTree is considered free.
893 if (!root1) return false;
894 if (!root2) return false;
895
896 /// stop when 1) bounding boxes of two objects not overlap; OR
897 /// 2) at least one of the nodes is free; OR
898 /// 2) (two uncertain nodes OR one node occupied and one node
899 /// uncertain) AND cost not required
900 if (tree1->isNodeFree(root1) || tree2->isNodeFree(root2))
901 return false;
902 else if ((tree1->isNodeUncertain(root1) || tree2->isNodeUncertain(root2)))
903 return false;
904
905 bool bothAreLeaves =
906 (!tree1->nodeHasChildren(root1) && !tree2->nodeHasChildren(root2));
907 if (!bothAreLeaves || !crequest->enable_contact) {
908 OBB obb1, obb2;
909 convertBV(bv1, tf1, obb1);
910 convertBV(bv2, tf2, obb2);
911 CoalScalar sqrDistLowerBound;
912 if (!obb1.overlap(obb2, *crequest, sqrDistLowerBound)) {
913 if (cresult->distance_lower_bound > 0 &&
914 sqrDistLowerBound <
915 cresult->distance_lower_bound * cresult->distance_lower_bound)
916 cresult->distance_lower_bound =
917 sqrt(sqrDistLowerBound) - crequest->security_margin;
918 return false;
919 }
920 if (crequest->enable_contact) { // Overlap
921 if (cresult->numContacts() < crequest->num_max_contacts)
922 cresult->addContact(
923 Contact(tree1, tree2, static_cast<int>(root1 - tree1->getRoot()),
924 static_cast<int>(root2 - tree2->getRoot())));
925 return crequest->isSatisfied(*cresult);
926 }
927 }
928
929 // Both node are leaves
930 if (bothAreLeaves) {
931 assert(tree1->isNodeOccupied(root1) && tree2->isNodeOccupied(root2));
932
933 Box box1, box2;
934 Transform3s box1_tf, box2_tf;
935 constructBox(bv1, tf1, box1, box1_tf);
936 constructBox(bv2, tf2, box2, box2_tf);
937
938 if (this->solver->gjk_initial_guess ==
939 GJKInitialGuess::BoundingVolumeGuess) {
940 box1.computeLocalAABB();
941 box2.computeLocalAABB();
942 }
943
944 // When reaching this point, `this->solver` has already been set up
945 // by the CollisionRequest `this->request`.
946 // The only thing we need to (and can) pass to `ShapeShapeDistance` is
947 // whether or not penetration information is should be computed in case of
948 // collision.
949 const bool compute_penetration = (this->crequest->enable_contact ||
950 (this->crequest->security_margin < 0));
951 Vec3s c1, c2, normal;
952 CoalScalar distance = internal::ShapeShapeDistance<Box, Box>(
953 &box1, box1_tf, &box2, box2_tf, this->solver, compute_penetration, c1,
954 c2, normal);
955
956 const CoalScalar distToCollision =
957 distance - this->crequest->security_margin;
958
959 internal::updateDistanceLowerBoundFromLeaf(
960 *(this->crequest), *(this->cresult), distToCollision, c1, c2, normal);
961
962 if (this->cresult->numContacts() < this->crequest->num_max_contacts) {
963 if (distToCollision <= this->crequest->collision_distance_threshold)
964 this->cresult->addContact(
965 Contact(tree1, tree2, static_cast<int>(root1 - tree1->getRoot()),
966 static_cast<int>(root2 - tree2->getRoot()), c1, c2,
967 normal, distance));
968 }
969
970 return crequest->isSatisfied(*cresult);
971 }
972
973 // Determine which tree to traverse first.
974 if (!tree2->nodeHasChildren(root2) ||
975 (tree1->nodeHasChildren(root1) && (bv1.size() > bv2.size()))) {
976 for (unsigned int i = 0; i < 8; ++i) {
977 if (tree1->nodeChildExists(root1, i)) {
978 const OcTree::OcTreeNode* child = tree1->getNodeChild(root1, i);
979 AABB child_bv;
980 computeChildBV(bv1, i, child_bv);
981
982 if (OcTreeIntersectRecurse(tree1, child, child_bv, tree2, root2, bv2,
983 tf1, tf2))
984 return true;
985 }
986 }
987 } else {
988 for (unsigned int i = 0; i < 8; ++i) {
989 if (tree2->nodeChildExists(root2, i)) {
990 const OcTree::OcTreeNode* child = tree2->getNodeChild(root2, i);
991 AABB child_bv;
992 computeChildBV(bv2, i, child_bv);
993
994 if (OcTreeIntersectRecurse(tree1, root1, bv1, tree2, child, child_bv,
995 tf1, tf2))
996 return true;
997 }
998 }
999 }
1000
1001 return false;
1002 }
1003 };
1004
1005 /// @addtogroup Traversal_For_Collision
1006 /// @{
1007
1008 /// @brief Traversal node for octree collision
1009 class COAL_DLLAPI OcTreeCollisionTraversalNode
1010 : public CollisionTraversalNodeBase {
1011 public:
1012 OcTreeCollisionTraversalNode(const CollisionRequest& request)
1013 : CollisionTraversalNodeBase(request) {
1014 model1 = NULL;
1015 model2 = NULL;
1016
1017 otsolver = NULL;
1018 }
1019
1020 bool BVDisjoints(unsigned, unsigned, CoalScalar&) const { return false; }
1021
1022 void leafCollides(unsigned, unsigned, CoalScalar& sqrDistLowerBound) const {
1023 otsolver->OcTreeIntersect(model1, model2, tf1, tf2, request, *result);
1024 sqrDistLowerBound = std::max((CoalScalar)0, result->distance_lower_bound);
1025 sqrDistLowerBound *= sqrDistLowerBound;
1026 }
1027
1028 const OcTree* model1;
1029 const OcTree* model2;
1030
1031 Transform3s tf1, tf2;
1032
1033 const OcTreeSolver* otsolver;
1034 };
1035
1036 /// @brief Traversal node for shape-octree collision
1037 template <typename S>
1038 class COAL_DLLAPI ShapeOcTreeCollisionTraversalNode
1039 : public CollisionTraversalNodeBase {
1040 public:
1041 ShapeOcTreeCollisionTraversalNode(const CollisionRequest& request)
1042 : CollisionTraversalNodeBase(request) {
1043 model1 = NULL;
1044 model2 = NULL;
1045
1046 otsolver = NULL;
1047 }
1048
1049 bool BVDisjoints(unsigned int, unsigned int, CoalScalar&) const {
1050 return false;
1051 }
1052
1053 void leafCollides(unsigned int, unsigned int,
1054 CoalScalar& sqrDistLowerBound) const {
1055 otsolver->OcTreeShapeIntersect(model2, *model1, tf2, tf1, request, *result);
1056 sqrDistLowerBound = std::max((CoalScalar)0, result->distance_lower_bound);
1057 sqrDistLowerBound *= sqrDistLowerBound;
1058 }
1059
1060 const S* model1;
1061 const OcTree* model2;
1062
1063 Transform3s tf1, tf2;
1064
1065 const OcTreeSolver* otsolver;
1066 };
1067
1068 /// @brief Traversal node for octree-shape collision
1069
1070 template <typename S>
1071 class COAL_DLLAPI OcTreeShapeCollisionTraversalNode
1072 : public CollisionTraversalNodeBase {
1073 public:
1074 2000 OcTreeShapeCollisionTraversalNode(const CollisionRequest& request)
1075
2/4
✓ Branch 2 taken 1000 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1000 times.
✗ Branch 6 not taken.
2000 : CollisionTraversalNodeBase(request) {
1076 2000 model1 = NULL;
1077 2000 model2 = NULL;
1078
1079 2000 otsolver = NULL;
1080 }
1081
1082 bool BVDisjoints(unsigned int, unsigned int, coal::CoalScalar&) const {
1083 return false;
1084 }
1085
1086 2000 void leafCollides(unsigned int, unsigned int,
1087 CoalScalar& sqrDistLowerBound) const {
1088 2000 otsolver->OcTreeShapeIntersect(model1, *model2, tf1, tf2, request, *result);
1089 2000 sqrDistLowerBound = std::max((CoalScalar)0, result->distance_lower_bound);
1090 2000 sqrDistLowerBound *= sqrDistLowerBound;
1091 }
1092
1093 const OcTree* model1;
1094 const S* model2;
1095
1096 Transform3s tf1, tf2;
1097
1098 const OcTreeSolver* otsolver;
1099 };
1100
1101 /// @brief Traversal node for mesh-octree collision
1102 template <typename BV>
1103 class COAL_DLLAPI MeshOcTreeCollisionTraversalNode
1104 : public CollisionTraversalNodeBase {
1105 public:
1106 200 MeshOcTreeCollisionTraversalNode(const CollisionRequest& request)
1107
2/4
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 100 times.
✗ Branch 6 not taken.
200 : CollisionTraversalNodeBase(request) {
1108 200 model1 = NULL;
1109 200 model2 = NULL;
1110
1111 200 otsolver = NULL;
1112 }
1113
1114 bool BVDisjoints(unsigned int, unsigned int, CoalScalar&) const {
1115 return false;
1116 }
1117
1118 200 void leafCollides(unsigned int, unsigned int,
1119 CoalScalar& sqrDistLowerBound) const {
1120 200 otsolver->OcTreeMeshIntersect(model2, model1, tf2, tf1, request, *result);
1121 200 sqrDistLowerBound = std::max((CoalScalar)0, result->distance_lower_bound);
1122 200 sqrDistLowerBound *= sqrDistLowerBound;
1123 }
1124
1125 const BVHModel<BV>* model1;
1126 const OcTree* model2;
1127
1128 Transform3s tf1, tf2;
1129
1130 const OcTreeSolver* otsolver;
1131 };
1132
1133 /// @brief Traversal node for octree-mesh collision
1134 template <typename BV>
1135 class COAL_DLLAPI OcTreeMeshCollisionTraversalNode
1136 : public CollisionTraversalNodeBase {
1137 public:
1138 OcTreeMeshCollisionTraversalNode(const CollisionRequest& request)
1139 : CollisionTraversalNodeBase(request) {
1140 model1 = NULL;
1141 model2 = NULL;
1142
1143 otsolver = NULL;
1144 }
1145
1146 bool BVDisjoints(unsigned int, unsigned int, CoalScalar&) const {
1147 return false;
1148 }
1149
1150 void leafCollides(unsigned int, unsigned int,
1151 CoalScalar& sqrDistLowerBound) const {
1152 otsolver->OcTreeMeshIntersect(model1, model2, tf1, tf2, request, *result);
1153 sqrDistLowerBound = std::max((CoalScalar)0, result->distance_lower_bound);
1154 sqrDistLowerBound *= sqrDistLowerBound;
1155 }
1156
1157 const OcTree* model1;
1158 const BVHModel<BV>* model2;
1159
1160 Transform3s tf1, tf2;
1161
1162 const OcTreeSolver* otsolver;
1163 };
1164
1165 /// @brief Traversal node for octree-height-field collision
1166 template <typename BV>
1167 class COAL_DLLAPI OcTreeHeightFieldCollisionTraversalNode
1168 : public CollisionTraversalNodeBase {
1169 public:
1170 2000 OcTreeHeightFieldCollisionTraversalNode(const CollisionRequest& request)
1171
2/4
✓ Branch 2 taken 1000 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1000 times.
✗ Branch 6 not taken.
2000 : CollisionTraversalNodeBase(request) {
1172 2000 model1 = NULL;
1173 2000 model2 = NULL;
1174
1175 2000 otsolver = NULL;
1176 }
1177
1178 bool BVDisjoints(unsigned int, unsigned int, CoalScalar&) const {
1179 return false;
1180 }
1181
1182 2000 void leafCollides(unsigned int, unsigned int,
1183 CoalScalar& sqrDistLowerBound) const {
1184 2000 otsolver->OcTreeHeightFieldIntersect(model1, model2, tf1, tf2, request,
1185 2000 *result, sqrDistLowerBound);
1186 }
1187
1188 const OcTree* model1;
1189 const HeightField<BV>* model2;
1190
1191 Transform3s tf1, tf2;
1192
1193 const OcTreeSolver* otsolver;
1194 };
1195
1196 /// @brief Traversal node for octree-height-field collision
1197 template <typename BV>
1198 class COAL_DLLAPI HeightFieldOcTreeCollisionTraversalNode
1199 : public CollisionTraversalNodeBase {
1200 public:
1201 2000 HeightFieldOcTreeCollisionTraversalNode(const CollisionRequest& request)
1202
2/4
✓ Branch 2 taken 1000 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1000 times.
✗ Branch 6 not taken.
2000 : CollisionTraversalNodeBase(request) {
1203 2000 model1 = NULL;
1204 2000 model2 = NULL;
1205
1206 2000 otsolver = NULL;
1207 }
1208
1209 bool BVDisjoints(unsigned int, unsigned int, CoalScalar&) const {
1210 return false;
1211 }
1212
1213 2000 void leafCollides(unsigned int, unsigned int,
1214 CoalScalar& sqrDistLowerBound) const {
1215 2000 otsolver->HeightFieldOcTreeIntersect(model1, model2, tf1, tf2, request,
1216 2000 *result, sqrDistLowerBound);
1217 }
1218
1219 const HeightField<BV>* model1;
1220 const OcTree* model2;
1221
1222 Transform3s tf1, tf2;
1223
1224 const OcTreeSolver* otsolver;
1225 };
1226
1227 /// @}
1228
1229 /// @addtogroup Traversal_For_Distance
1230 /// @{
1231
1232 /// @brief Traversal node for octree distance
1233 class COAL_DLLAPI OcTreeDistanceTraversalNode
1234 : public DistanceTraversalNodeBase {
1235 public:
1236 OcTreeDistanceTraversalNode() {
1237 model1 = NULL;
1238 model2 = NULL;
1239
1240 otsolver = NULL;
1241 }
1242
1243 CoalScalar BVDistanceLowerBound(unsigned, unsigned) const { return -1; }
1244
1245 bool BVDistanceLowerBound(unsigned, unsigned, CoalScalar&) const {
1246 return false;
1247 }
1248
1249 void leafComputeDistance(unsigned, unsigned int) const {
1250 otsolver->OcTreeDistance(model1, model2, tf1, tf2, request, *result);
1251 }
1252
1253 const OcTree* model1;
1254 const OcTree* model2;
1255
1256 const OcTreeSolver* otsolver;
1257 };
1258
1259 /// @brief Traversal node for shape-octree distance
1260 template <typename Shape>
1261 class COAL_DLLAPI ShapeOcTreeDistanceTraversalNode
1262 : public DistanceTraversalNodeBase {
1263 public:
1264 ShapeOcTreeDistanceTraversalNode() {
1265 model1 = NULL;
1266 model2 = NULL;
1267
1268 otsolver = NULL;
1269 }
1270
1271 CoalScalar BVDistanceLowerBound(unsigned int, unsigned int) const {
1272 return -1;
1273 }
1274
1275 void leafComputeDistance(unsigned int, unsigned int) const {
1276 otsolver->OcTreeShapeDistance(model2, *model1, tf2, tf1, request, *result);
1277 }
1278
1279 const Shape* model1;
1280 const OcTree* model2;
1281
1282 const OcTreeSolver* otsolver;
1283 };
1284
1285 /// @brief Traversal node for octree-shape distance
1286 template <typename Shape>
1287 class COAL_DLLAPI OcTreeShapeDistanceTraversalNode
1288 : public DistanceTraversalNodeBase {
1289 public:
1290 OcTreeShapeDistanceTraversalNode() {
1291 model1 = NULL;
1292 model2 = NULL;
1293
1294 otsolver = NULL;
1295 }
1296
1297 CoalScalar BVDistanceLowerBound(unsigned int, unsigned int) const {
1298 return -1;
1299 }
1300
1301 void leafComputeDistance(unsigned int, unsigned int) const {
1302 otsolver->OcTreeShapeDistance(model1, *model2, tf1, tf2, request, *result);
1303 }
1304
1305 const OcTree* model1;
1306 const Shape* model2;
1307
1308 const OcTreeSolver* otsolver;
1309 };
1310
1311 /// @brief Traversal node for mesh-octree distance
1312 template <typename BV>
1313 class COAL_DLLAPI MeshOcTreeDistanceTraversalNode
1314 : public DistanceTraversalNodeBase {
1315 public:
1316 MeshOcTreeDistanceTraversalNode() {
1317 model1 = NULL;
1318 model2 = NULL;
1319
1320 otsolver = NULL;
1321 }
1322
1323 CoalScalar BVDistanceLowerBound(unsigned int, unsigned int) const {
1324 return -1;
1325 }
1326
1327 void leafComputeDistance(unsigned int, unsigned int) const {
1328 otsolver->OcTreeMeshDistance(model2, model1, tf2, tf1, request, *result);
1329 }
1330
1331 const BVHModel<BV>* model1;
1332 const OcTree* model2;
1333
1334 const OcTreeSolver* otsolver;
1335 };
1336
1337 /// @brief Traversal node for octree-mesh distance
1338 template <typename BV>
1339 class COAL_DLLAPI OcTreeMeshDistanceTraversalNode
1340 : public DistanceTraversalNodeBase {
1341 public:
1342 OcTreeMeshDistanceTraversalNode() {
1343 model1 = NULL;
1344 model2 = NULL;
1345
1346 otsolver = NULL;
1347 }
1348
1349 CoalScalar BVDistanceLowerBound(unsigned int, unsigned int) const {
1350 return -1;
1351 }
1352
1353 void leafComputeDistance(unsigned int, unsigned int) const {
1354 otsolver->OcTreeMeshDistance(model1, model2, tf1, tf2, request, *result);
1355 }
1356
1357 const OcTree* model1;
1358 const BVHModel<BV>* model2;
1359
1360 const OcTreeSolver* otsolver;
1361 };
1362
1363 /// @}
1364
1365 } // namespace coal
1366
1367 /// @endcond
1368
1369 #endif
1370