GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
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 |
* All rights reserved. |
||
7 |
* |
||
8 |
* Redistribution and use in source and binary forms, with or without |
||
9 |
* modification, are permitted provided that the following conditions |
||
10 |
* are met: |
||
11 |
* |
||
12 |
* * Redistributions of source code must retain the above copyright |
||
13 |
* notice, this list of conditions and the following disclaimer. |
||
14 |
* * Redistributions in binary form must reproduce the above |
||
15 |
* copyright notice, this list of conditions and the following |
||
16 |
* disclaimer in the documentation and/or other materials provided |
||
17 |
* with the distribution. |
||
18 |
* * Neither the name of Open Source Robotics Foundation nor the names of its |
||
19 |
* contributors may be used to endorse or promote products derived |
||
20 |
* from this software without specific prior written permission. |
||
21 |
* |
||
22 |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
23 |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
24 |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||
25 |
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||
26 |
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||
27 |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||
28 |
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
29 |
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||
30 |
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||
31 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||
32 |
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||
33 |
* POSSIBILITY OF SUCH DAMAGE. |
||
34 |
*/ |
||
35 |
|||
36 |
/** \author Jia Pan */ |
||
37 |
|||
38 |
#ifndef HPP_FCL_GEOMETRIC_SHAPES_H |
||
39 |
#define HPP_FCL_GEOMETRIC_SHAPES_H |
||
40 |
|||
41 |
#include <boost/math/constants/constants.hpp> |
||
42 |
|||
43 |
#include <hpp/fcl/collision_object.h> |
||
44 |
#include <hpp/fcl/data_types.h> |
||
45 |
#include <string.h> |
||
46 |
|||
47 |
namespace hpp { |
||
48 |
namespace fcl { |
||
49 |
|||
50 |
/// @brief Base class for all basic geometric shapes |
||
51 |
1 |
class HPP_FCL_DLLAPI ShapeBase : public CollisionGeometry { |
|
52 |
public: |
||
53 |
116887093 |
ShapeBase() {} |
|
54 |
|||
55 |
/// \brief Copy constructor |
||
56 |
36 |
ShapeBase(const ShapeBase& other) : CollisionGeometry(other) {} |
|
57 |
|||
58 |
ShapeBase& operator=(const ShapeBase& other) = default; |
||
59 |
|||
60 |
233774126 |
virtual ~ShapeBase(){}; |
|
61 |
|||
62 |
/// @brief Get object type: a geometric shape |
||
63 |
2480222 |
OBJECT_TYPE getObjectType() const { return OT_GEOM; } |
|
64 |
}; |
||
65 |
|||
66 |
/// @defgroup Geometric_Shapes Geometric shapes |
||
67 |
/// Classes of different types of geometric shapes. |
||
68 |
/// @{ |
||
69 |
|||
70 |
/// @brief Triangle stores the points instead of only indices of points |
||
71 |
class HPP_FCL_DLLAPI TriangleP : public ShapeBase { |
||
72 |
public: |
||
73 |
TriangleP(){}; |
||
74 |
|||
75 |
116806559 |
TriangleP(const Vec3f& a_, const Vec3f& b_, const Vec3f& c_) |
|
76 |
✓✗✓✗ ✓✗ |
116806559 |
: ShapeBase(), a(a_), b(b_), c(c_) {} |
77 |
|||
78 |
TriangleP(const TriangleP& other) |
||
79 |
: ShapeBase(other), a(other.a), b(other.b), c(other.c) {} |
||
80 |
|||
81 |
/// @brief Clone *this into a new TriangleP |
||
82 |
virtual TriangleP* clone() const { return new TriangleP(*this); }; |
||
83 |
|||
84 |
/// @brief virtual function of compute AABB in local coordinate |
||
85 |
void computeLocalAABB(); |
||
86 |
|||
87 |
87614083 |
NODE_TYPE getNodeType() const { return GEOM_TRIANGLE; } |
|
88 |
|||
89 |
// std::pair<ShapeBase*, Transform3f> inflated(const FCL_REAL value) const { |
||
90 |
// if (value == 0) return std::make_pair(new TriangleP(*this), |
||
91 |
// Transform3f()); Vec3f AB(b - a), BC(c - b), CA(a - c); AB.normalize(); |
||
92 |
// BC.normalize(); |
||
93 |
// CA.normalize(); |
||
94 |
// |
||
95 |
// Vec3f new_a(a + value * Vec3f(-AB + CA).normalized()); |
||
96 |
// Vec3f new_b(b + value * Vec3f(-BC + AB).normalized()); |
||
97 |
// Vec3f new_c(c + value * Vec3f(-CA + BC).normalized()); |
||
98 |
// |
||
99 |
// return std::make_pair(new TriangleP(new_a, new_b, new_c), |
||
100 |
// Transform3f()); |
||
101 |
// } |
||
102 |
// |
||
103 |
// FCL_REAL minInflationValue() const |
||
104 |
// { |
||
105 |
// return (std::numeric_limits<FCL_REAL>::max)(); // TODO(jcarpent): |
||
106 |
// implement |
||
107 |
// } |
||
108 |
|||
109 |
Vec3f a, b, c; |
||
110 |
|||
111 |
private: |
||
112 |
6 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
113 |
✓✗ | 6 |
const TriangleP* other_ptr = dynamic_cast<const TriangleP*>(&_other); |
114 |
✗✓ | 6 |
if (other_ptr == nullptr) return false; |
115 |
6 |
const TriangleP& other = *other_ptr; |
|
116 |
|||
117 |
✓✗✓✗ ✓✗ |
6 |
return a == other.a && b == other.b && c == other.c; |
118 |
} |
||
119 |
|||
120 |
public: |
||
121 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
||
122 |
}; |
||
123 |
|||
124 |
/// @brief Center at zero point, axis aligned box |
||
125 |
class HPP_FCL_DLLAPI Box : public ShapeBase { |
||
126 |
public: |
||
127 |
7753 |
Box(FCL_REAL x, FCL_REAL y, FCL_REAL z) |
|
128 |
✓✗ | 7753 |
: ShapeBase(), halfSide(x / 2, y / 2, z / 2) {} |
129 |
|||
130 |
✓✗✓✗ |
1821 |
Box(const Vec3f& side_) : ShapeBase(), halfSide(side_ / 2) {} |
131 |
|||
132 |
✓✗ | 5 |
Box(const Box& other) : ShapeBase(other), halfSide(other.halfSide) {} |
133 |
|||
134 |
1581 |
Box& operator=(const Box& other) { |
|
135 |
✗✓ | 1581 |
if (this == &other) return *this; |
136 |
|||
137 |
1581 |
this->halfSide = other.halfSide; |
|
138 |
1581 |
return *this; |
|
139 |
} |
||
140 |
|||
141 |
/// @brief Clone *this into a new Box |
||
142 |
virtual Box* clone() const { return new Box(*this); }; |
||
143 |
|||
144 |
/// @brief Default constructor |
||
145 |
✓✗ | 1582 |
Box() {} |
146 |
|||
147 |
/// @brief box side half-length |
||
148 |
Vec3f halfSide; |
||
149 |
|||
150 |
/// @brief Compute AABB |
||
151 |
void computeLocalAABB(); |
||
152 |
|||
153 |
/// @brief Get node type: a box |
||
154 |
2663103 |
NODE_TYPE getNodeType() const { return GEOM_BOX; } |
|
155 |
|||
156 |
4 |
FCL_REAL computeVolume() const { return 8 * halfSide.prod(); } |
|
157 |
|||
158 |
2 |
Matrix3f computeMomentofInertia() const { |
|
159 |
✓✗ | 2 |
FCL_REAL V = computeVolume(); |
160 |
✓✗✓✗ ✓✗ |
2 |
Vec3f s(halfSide.cwiseAbs2() * V); |
161 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ |
4 |
return (Vec3f(s[1] + s[2], s[0] + s[2], s[0] + s[1]) / 3).asDiagonal(); |
162 |
} |
||
163 |
|||
164 |
7 |
FCL_REAL minInflationValue() const { return -halfSide.minCoeff(); } |
|
165 |
|||
166 |
/// \brief Inflate the box by an amount given by value |
||
167 |
/// |
||
168 |
/// \param[in] value of the shape inflation. |
||
169 |
/// |
||
170 |
/// \returns a new inflated box and the related transform to account for the |
||
171 |
/// change of shape frame |
||
172 |
6 |
std::pair<Box, Transform3f> inflated(const FCL_REAL value) const { |
|
173 |
✓✓ | 6 |
if (value <= minInflationValue()) |
174 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ |
1 |
HPP_FCL_THROW_PRETTY("value (" << value << ") " |
175 |
<< "is two small. It should be at least: " |
||
176 |
<< minInflationValue(), |
||
177 |
std::invalid_argument); |
||
178 |
✓✗✓✗ ✓✗✓✗ ✓✗ |
10 |
return std::make_pair(Box(2 * (halfSide + Vec3f::Constant(value))), |
179 |
✓✗ | 15 |
Transform3f()); |
180 |
} |
||
181 |
|||
182 |
private: |
||
183 |
7 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
184 |
✓✗ | 7 |
const Box* other_ptr = dynamic_cast<const Box*>(&_other); |
185 |
✗✓ | 7 |
if (other_ptr == nullptr) return false; |
186 |
7 |
const Box& other = *other_ptr; |
|
187 |
|||
188 |
7 |
return halfSide == other.halfSide; |
|
189 |
} |
||
190 |
|||
191 |
public: |
||
192 |
23888 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
193 |
}; |
||
194 |
|||
195 |
/// @brief Center at zero point sphere |
||
196 |
class HPP_FCL_DLLAPI Sphere : public ShapeBase { |
||
197 |
public: |
||
198 |
/// @brief Default constructor |
||
199 |
1 |
Sphere() {} |
|
200 |
|||
201 |
5569 |
explicit Sphere(FCL_REAL radius_) : ShapeBase(), radius(radius_) {} |
|
202 |
|||
203 |
6 |
Sphere(const Sphere& other) : ShapeBase(other), radius(other.radius) {} |
|
204 |
|||
205 |
/// @brief Clone *this into a new Sphere |
||
206 |
virtual Sphere* clone() const { return new Sphere(*this); }; |
||
207 |
|||
208 |
/// @brief Radius of the sphere |
||
209 |
FCL_REAL radius; |
||
210 |
|||
211 |
/// @brief Compute AABB |
||
212 |
void computeLocalAABB(); |
||
213 |
|||
214 |
/// @brief Get node type: a sphere |
||
215 |
707071 |
NODE_TYPE getNodeType() const { return GEOM_SPHERE; } |
|
216 |
|||
217 |
2 |
Matrix3f computeMomentofInertia() const { |
|
218 |
✓✗ | 2 |
FCL_REAL I = 0.4 * radius * radius * computeVolume(); |
219 |
✓✗✓✗ ✓✗ |
4 |
return I * Matrix3f::Identity(); |
220 |
} |
||
221 |
|||
222 |
4 |
FCL_REAL computeVolume() const { |
|
223 |
4 |
return 4 * boost::math::constants::pi<FCL_REAL>() * radius * radius * |
|
224 |
4 |
radius / 3; |
|
225 |
} |
||
226 |
|||
227 |
8 |
FCL_REAL minInflationValue() const { return -radius; } |
|
228 |
|||
229 |
/// \brief Inflate the sphere by an amount given by value |
||
230 |
/// |
||
231 |
/// \param[in] value of the shape inflation. |
||
232 |
/// |
||
233 |
/// \returns a new inflated sphere and the related transform to account for |
||
234 |
/// the change of shape frame |
||
235 |
7 |
std::pair<Sphere, Transform3f> inflated(const FCL_REAL value) const { |
|
236 |
✓✓ | 7 |
if (value <= minInflationValue()) |
237 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ |
1 |
HPP_FCL_THROW_PRETTY( |
238 |
"value (" << value << ") is two small. It should be at least: " |
||
239 |
<< minInflationValue(), |
||
240 |
std::invalid_argument); |
||
241 |
✓✗✓✗ |
12 |
return std::make_pair(Sphere(radius + value), Transform3f()); |
242 |
} |
||
243 |
|||
244 |
private: |
||
245 |
7 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
246 |
✓✗ | 7 |
const Sphere* other_ptr = dynamic_cast<const Sphere*>(&_other); |
247 |
✗✓ | 7 |
if (other_ptr == nullptr) return false; |
248 |
7 |
const Sphere& other = *other_ptr; |
|
249 |
|||
250 |
7 |
return radius == other.radius; |
|
251 |
} |
||
252 |
|||
253 |
public: |
||
254 |
17808 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
255 |
}; |
||
256 |
|||
257 |
/// @brief Ellipsoid centered at point zero |
||
258 |
class HPP_FCL_DLLAPI Ellipsoid : public ShapeBase { |
||
259 |
public: |
||
260 |
/// @brief Default constructor |
||
261 |
✓✗ | 1 |
Ellipsoid() {} |
262 |
|||
263 |
19 |
Ellipsoid(FCL_REAL rx, FCL_REAL ry, FCL_REAL rz) |
|
264 |
✓✗ | 19 |
: ShapeBase(), radii(rx, ry, rz) {} |
265 |
|||
266 |
✓✗ | 5 |
explicit Ellipsoid(const Vec3f& radii) : radii(radii) {} |
267 |
|||
268 |
✓✗ | 5 |
Ellipsoid(const Ellipsoid& other) : ShapeBase(other), radii(other.radii) {} |
269 |
|||
270 |
/// @brief Clone *this into a new Ellipsoid |
||
271 |
virtual Ellipsoid* clone() const { return new Ellipsoid(*this); }; |
||
272 |
|||
273 |
/// @brief Radii of the Ellipsoid (such that on boundary: x^2/rx^2 + y^2/ry^2 |
||
274 |
/// + z^2/rz^2 = 1) |
||
275 |
Vec3f radii; |
||
276 |
|||
277 |
/// @brief Compute AABB |
||
278 |
void computeLocalAABB(); |
||
279 |
|||
280 |
/// @brief Get node type: an ellipsoid |
||
281 |
30005 |
NODE_TYPE getNodeType() const { return GEOM_ELLIPSOID; } |
|
282 |
|||
283 |
Matrix3f computeMomentofInertia() const { |
||
284 |
FCL_REAL V = computeVolume(); |
||
285 |
FCL_REAL a2 = V * radii[0] * radii[0]; |
||
286 |
FCL_REAL b2 = V * radii[1] * radii[1]; |
||
287 |
FCL_REAL c2 = V * radii[2] * radii[2]; |
||
288 |
return (Matrix3f() << 0.2 * (b2 + c2), 0, 0, 0, 0.2 * (a2 + c2), 0, 0, 0, |
||
289 |
0.2 * (a2 + b2)) |
||
290 |
.finished(); |
||
291 |
} |
||
292 |
|||
293 |
FCL_REAL computeVolume() const { |
||
294 |
return 4 * boost::math::constants::pi<FCL_REAL>() * radii[0] * radii[1] * |
||
295 |
radii[2] / 3; |
||
296 |
} |
||
297 |
|||
298 |
7 |
FCL_REAL minInflationValue() const { return -radii.minCoeff(); } |
|
299 |
|||
300 |
/// \brief Inflate the ellipsoid by an amount given by value |
||
301 |
/// |
||
302 |
/// \param[in] value of the shape inflation. |
||
303 |
/// |
||
304 |
/// \returns a new inflated ellipsoid and the related transform to account for |
||
305 |
/// the change of shape frame |
||
306 |
6 |
std::pair<Ellipsoid, Transform3f> inflated(const FCL_REAL value) const { |
|
307 |
✓✓ | 6 |
if (value <= minInflationValue()) |
308 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗ |
1 |
HPP_FCL_THROW_PRETTY( |
309 |
"value (" << value << ") is two small. It should be at least: " |
||
310 |
<< minInflationValue(), |
||
311 |
std::invalid_argument); |
||
312 |
✓✗✓✗ ✓✗✓✗ |
10 |
return std::make_pair(Ellipsoid(radii + Vec3f::Constant(value)), |
313 |
✓✗ | 15 |
Transform3f()); |
314 |
} |
||
315 |
|||
316 |
private: |
||
317 |
7 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
318 |
✓✗ | 7 |
const Ellipsoid* other_ptr = dynamic_cast<const Ellipsoid*>(&_other); |
319 |
✗✓ | 7 |
if (other_ptr == nullptr) return false; |
320 |
7 |
const Ellipsoid& other = *other_ptr; |
|
321 |
|||
322 |
7 |
return radii == other.radii; |
|
323 |
} |
||
324 |
|||
325 |
public: |
||
326 |
8 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
327 |
}; |
||
328 |
|||
329 |
/// @brief Capsule |
||
330 |
/// It is \f$ { x~\in~\mathbb{R}^3, d(x, AB) \leq radius } \f$ |
||
331 |
/// where \f$ d(x, AB) \f$ is the distance between the point x and the capsule |
||
332 |
/// segment AB, with \f$ A = (0,0,-halfLength), B = (0,0,halfLength) \f$. |
||
333 |
class HPP_FCL_DLLAPI Capsule : public ShapeBase { |
||
334 |
public: |
||
335 |
/// @brief Default constructor |
||
336 |
1 |
Capsule() {} |
|
337 |
|||
338 |
1046 |
Capsule(FCL_REAL radius_, FCL_REAL lz_) : ShapeBase(), radius(radius_) { |
|
339 |
1046 |
halfLength = lz_ / 2; |
|
340 |
1046 |
} |
|
341 |
|||
342 |
5 |
Capsule(const Capsule& other) |
|
343 |
5 |
: ShapeBase(other), radius(other.radius), halfLength(other.halfLength) {} |
|
344 |
|||
345 |
/// @brief Clone *this into a new Capsule |
||
346 |
virtual Capsule* clone() const { return new Capsule(*this); }; |
||
347 |
|||
348 |
/// @brief Radius of capsule |
||
349 |
FCL_REAL radius; |
||
350 |
|||
351 |
/// @brief Half Length along z axis |
||
352 |
FCL_REAL halfLength; |
||
353 |
|||
354 |
/// @brief Compute AABB |
||
355 |
void computeLocalAABB(); |
||
356 |
|||
357 |
/// @brief Get node type: a capsule |
||
358 |
32264 |
NODE_TYPE getNodeType() const { return GEOM_CAPSULE; } |
|
359 |
|||
360 |
2 |
FCL_REAL computeVolume() const { |
|
361 |
2 |
return boost::math::constants::pi<FCL_REAL>() * radius * radius * |
|
362 |
2 |
((halfLength * 2) + radius * 4 / 3.0); |
|
363 |
} |
||
364 |
|||
365 |
2 |
Matrix3f computeMomentofInertia() const { |
|
366 |
2 |
FCL_REAL v_cyl = radius * radius * (halfLength * 2) * |
|
367 |
2 |
boost::math::constants::pi<FCL_REAL>(); |
|
368 |
4 |
FCL_REAL v_sph = radius * radius * radius * |
|
369 |
2 |
boost::math::constants::pi<FCL_REAL>() * 4 / 3.0; |
|
370 |
|||
371 |
2 |
FCL_REAL h2 = halfLength * halfLength; |
|
372 |
2 |
FCL_REAL r2 = radius * radius; |
|
373 |
2 |
FCL_REAL ix = v_cyl * (h2 / 3. + r2 / 4.) + |
|
374 |
2 |
v_sph * (0.4 * r2 + h2 + 0.75 * radius * halfLength); |
|
375 |
2 |
FCL_REAL iz = (0.5 * v_cyl + 0.4 * v_sph) * radius * radius; |
|
376 |
|||
377 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ |
4 |
return (Matrix3f() << ix, 0, 0, 0, ix, 0, 0, 0, iz).finished(); |
378 |
} |
||
379 |
|||
380 |
7 |
FCL_REAL minInflationValue() const { return -radius; } |
|
381 |
|||
382 |
/// \brief Inflate the capsule by an amount given by value |
||
383 |
/// |
||
384 |
/// \param[in] value of the shape inflation. |
||
385 |
/// |
||
386 |
/// \returns a new inflated capsule and the related transform to account for |
||
387 |
/// the change of shape frame |
||
388 |
6 |
std::pair<Capsule, Transform3f> inflated(const FCL_REAL value) const { |
|
389 |
✓✓ | 6 |
if (value <= minInflationValue()) |
390 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ |
1 |
HPP_FCL_THROW_PRETTY( |
391 |
"value (" << value << ") is two small. It should be at least: " |
||
392 |
<< minInflationValue(), |
||
393 |
std::invalid_argument); |
||
394 |
✓✗ | 10 |
return std::make_pair(Capsule(radius + value, 2 * halfLength), |
395 |
✓✗ | 15 |
Transform3f()); |
396 |
} |
||
397 |
|||
398 |
private: |
||
399 |
7 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
400 |
✓✗ | 7 |
const Capsule* other_ptr = dynamic_cast<const Capsule*>(&_other); |
401 |
✗✓ | 7 |
if (other_ptr == nullptr) return false; |
402 |
7 |
const Capsule& other = *other_ptr; |
|
403 |
|||
404 |
✓✗✓✗ |
7 |
return radius == other.radius && halfLength == other.halfLength; |
405 |
} |
||
406 |
|||
407 |
public: |
||
408 |
88 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
409 |
}; |
||
410 |
|||
411 |
/// @brief Cone |
||
412 |
/// The base of the cone is at \f$ z = - halfLength \f$ and the top is at |
||
413 |
/// \f$ z = halfLength \f$. |
||
414 |
class HPP_FCL_DLLAPI Cone : public ShapeBase { |
||
415 |
public: |
||
416 |
/// @brief Default constructor |
||
417 |
Cone() {} |
||
418 |
|||
419 |
1022 |
Cone(FCL_REAL radius_, FCL_REAL lz_) : ShapeBase(), radius(radius_) { |
|
420 |
1022 |
halfLength = lz_ / 2; |
|
421 |
1022 |
} |
|
422 |
|||
423 |
5 |
Cone(const Cone& other) |
|
424 |
5 |
: ShapeBase(other), radius(other.radius), halfLength(other.halfLength) {} |
|
425 |
|||
426 |
/// @brief Clone *this into a new Cone |
||
427 |
virtual Cone* clone() const { return new Cone(*this); }; |
||
428 |
|||
429 |
/// @brief Radius of the cone |
||
430 |
FCL_REAL radius; |
||
431 |
|||
432 |
/// @brief Half Length along z axis |
||
433 |
FCL_REAL halfLength; |
||
434 |
|||
435 |
/// @brief Compute AABB |
||
436 |
void computeLocalAABB(); |
||
437 |
|||
438 |
/// @brief Get node type: a cone |
||
439 |
1355 |
NODE_TYPE getNodeType() const { return GEOM_CONE; } |
|
440 |
|||
441 |
4 |
FCL_REAL computeVolume() const { |
|
442 |
4 |
return boost::math::constants::pi<FCL_REAL>() * radius * radius * |
|
443 |
4 |
(halfLength * 2) / 3; |
|
444 |
} |
||
445 |
|||
446 |
2 |
Matrix3f computeMomentofInertia() const { |
|
447 |
✓✗ | 2 |
FCL_REAL V = computeVolume(); |
448 |
2 |
FCL_REAL ix = |
|
449 |
2 |
V * (0.4 * halfLength * halfLength + 3 * radius * radius / 20); |
|
450 |
2 |
FCL_REAL iz = 0.3 * V * radius * radius; |
|
451 |
|||
452 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ |
4 |
return (Matrix3f() << ix, 0, 0, 0, ix, 0, 0, 0, iz).finished(); |
453 |
} |
||
454 |
|||
455 |
✓✗ | 2 |
Vec3f computeCOM() const { return Vec3f(0, 0, -0.5 * halfLength); } |
456 |
|||
457 |
7 |
FCL_REAL minInflationValue() const { return -(std::min)(radius, halfLength); } |
|
458 |
|||
459 |
/// \brief Inflate the cone by an amount given by value |
||
460 |
/// |
||
461 |
/// \param[in] value of the shape inflation. |
||
462 |
/// |
||
463 |
/// \returns a new inflated cone and the related transform to account for the |
||
464 |
/// change of shape frame |
||
465 |
6 |
std::pair<Cone, Transform3f> inflated(const FCL_REAL value) const { |
|
466 |
✓✗✓✓ |
6 |
if (value <= minInflationValue()) |
467 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗ |
1 |
HPP_FCL_THROW_PRETTY( |
468 |
"value (" << value << ") is two small. It should be at least: " |
||
469 |
<< minInflationValue(), |
||
470 |
std::invalid_argument); |
||
471 |
|||
472 |
// tan(alpha) = 2*halfLength/radius; |
||
473 |
5 |
const FCL_REAL tan_alpha = 2 * halfLength / radius; |
|
474 |
5 |
const FCL_REAL sin_alpha = tan_alpha / std::sqrt(1 + tan_alpha * tan_alpha); |
|
475 |
5 |
const FCL_REAL top_inflation = value / sin_alpha; |
|
476 |
5 |
const FCL_REAL bottom_inflation = value; |
|
477 |
|||
478 |
5 |
const FCL_REAL new_lz = 2 * halfLength + top_inflation + bottom_inflation; |
|
479 |
5 |
const FCL_REAL new_cz = (top_inflation + bottom_inflation) / 2.; |
|
480 |
5 |
const FCL_REAL new_radius = new_lz / tan_alpha; |
|
481 |
|||
482 |
✓✗ | 10 |
return std::make_pair(Cone(new_radius, new_lz), |
483 |
✓✗✓✗ ✓✗ |
15 |
Transform3f(Vec3f(0., 0., new_cz))); |
484 |
} |
||
485 |
|||
486 |
private: |
||
487 |
6 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
488 |
✓✗ | 6 |
const Cone* other_ptr = dynamic_cast<const Cone*>(&_other); |
489 |
✗✓ | 6 |
if (other_ptr == nullptr) return false; |
490 |
6 |
const Cone& other = *other_ptr; |
|
491 |
|||
492 |
✓✗✓✗ |
6 |
return radius == other.radius && halfLength == other.halfLength; |
493 |
} |
||
494 |
|||
495 |
public: |
||
496 |
8 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
497 |
}; |
||
498 |
|||
499 |
/// @brief Cylinder along Z axis. |
||
500 |
/// The cylinder is defined at its centroid. |
||
501 |
class HPP_FCL_DLLAPI Cylinder : public ShapeBase { |
||
502 |
public: |
||
503 |
/// @brief Default constructor |
||
504 |
1 |
Cylinder() {} |
|
505 |
|||
506 |
5529 |
Cylinder(FCL_REAL radius_, FCL_REAL lz_) : ShapeBase(), radius(radius_) { |
|
507 |
5529 |
halfLength = lz_ / 2; |
|
508 |
5529 |
} |
|
509 |
|||
510 |
5 |
Cylinder(const Cylinder& other) |
|
511 |
5 |
: ShapeBase(other), radius(other.radius), halfLength(other.halfLength) {} |
|
512 |
|||
513 |
1 |
Cylinder& operator=(const Cylinder& other) { |
|
514 |
✗✓ | 1 |
if (this == &other) return *this; |
515 |
|||
516 |
1 |
this->radius = other.radius; |
|
517 |
1 |
this->halfLength = other.halfLength; |
|
518 |
1 |
return *this; |
|
519 |
} |
||
520 |
|||
521 |
/// @brief Clone *this into a new Cylinder |
||
522 |
virtual Cylinder* clone() const { return new Cylinder(*this); }; |
||
523 |
|||
524 |
/// @brief Radius of the cylinder |
||
525 |
FCL_REAL radius; |
||
526 |
|||
527 |
/// @brief Half Length along z axis |
||
528 |
FCL_REAL halfLength; |
||
529 |
|||
530 |
/// @brief Compute AABB |
||
531 |
void computeLocalAABB(); |
||
532 |
|||
533 |
/// @brief Get node type: a cylinder |
||
534 |
1406186 |
NODE_TYPE getNodeType() const { return GEOM_CYLINDER; } |
|
535 |
|||
536 |
4 |
FCL_REAL computeVolume() const { |
|
537 |
4 |
return boost::math::constants::pi<FCL_REAL>() * radius * radius * |
|
538 |
4 |
(halfLength * 2); |
|
539 |
} |
||
540 |
|||
541 |
2 |
Matrix3f computeMomentofInertia() const { |
|
542 |
✓✗ | 2 |
FCL_REAL V = computeVolume(); |
543 |
2 |
FCL_REAL ix = V * (radius * radius / 4 + halfLength * halfLength / 3); |
|
544 |
2 |
FCL_REAL iz = V * radius * radius / 2; |
|
545 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ |
4 |
return (Matrix3f() << ix, 0, 0, 0, ix, 0, 0, 0, iz).finished(); |
546 |
} |
||
547 |
|||
548 |
7 |
FCL_REAL minInflationValue() const { return -(std::min)(radius, halfLength); } |
|
549 |
|||
550 |
/// \brief Inflate the cylinder by an amount given by value |
||
551 |
/// |
||
552 |
/// \param[in] value of the shape inflation. |
||
553 |
/// |
||
554 |
/// \returns a new inflated cylinder and the related transform to account for |
||
555 |
/// the change of shape frame |
||
556 |
6 |
std::pair<Cylinder, Transform3f> inflated(const FCL_REAL value) const { |
|
557 |
✓✓ | 6 |
if (value <= minInflationValue()) |
558 |
✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗✓✗ ✓✗ |
1 |
HPP_FCL_THROW_PRETTY( |
559 |
"value (" << value << ") is two small. It should be at least: " |
||
560 |
<< minInflationValue(), |
||
561 |
std::invalid_argument); |
||
562 |
✓✗ | 10 |
return std::make_pair(Cylinder(radius + value, 2 * (halfLength + value)), |
563 |
✓✗ | 15 |
Transform3f()); |
564 |
} |
||
565 |
|||
566 |
private: |
||
567 |
7 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
568 |
✓✗ | 7 |
const Cylinder* other_ptr = dynamic_cast<const Cylinder*>(&_other); |
569 |
✗✓ | 7 |
if (other_ptr == nullptr) return false; |
570 |
7 |
const Cylinder& other = *other_ptr; |
|
571 |
|||
572 |
✓✗✓✗ |
7 |
return radius == other.radius && halfLength == other.halfLength; |
573 |
} |
||
574 |
|||
575 |
public: |
||
576 |
17780 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
577 |
}; |
||
578 |
|||
579 |
/// @brief Base for convex polytope. |
||
580 |
/// @note Inherited classes are responsible for filling ConvexBase::neighbors; |
||
581 |
class HPP_FCL_DLLAPI ConvexBase : public ShapeBase { |
||
582 |
public: |
||
583 |
/// @brief Build a convex hull based on Qhull library |
||
584 |
/// and store the vertices and optionally the triangles |
||
585 |
/// \param points, num_points the points whose convex hull should be computed. |
||
586 |
/// \param keepTriangles if \c true, returns a Convex<Triangle> object which |
||
587 |
/// contains the triangle of the shape. |
||
588 |
/// \param qhullCommand the command sent to qhull. |
||
589 |
/// - if \c keepTriangles is \c true, this parameter should include |
||
590 |
/// "Qt". If \c NULL, "Qt" is passed to Qhull. |
||
591 |
/// - if \c keepTriangles is \c false, an empty string is passed to |
||
592 |
/// Qhull. |
||
593 |
/// \note hpp-fcl must have been compiled with option \c HPP_FCL_HAS_QHULL set |
||
594 |
/// to \c ON. |
||
595 |
static ConvexBase* convexHull(const Vec3f* points, unsigned int num_points, |
||
596 |
bool keepTriangles, |
||
597 |
const char* qhullCommand = NULL); |
||
598 |
|||
599 |
virtual ~ConvexBase(); |
||
600 |
|||
601 |
/// @brief Clone (deep copy). |
||
602 |
virtual ConvexBase* clone() const { |
||
603 |
ConvexBase* copy_ptr = new ConvexBase(*this); |
||
604 |
ConvexBase& copy = *copy_ptr; |
||
605 |
|||
606 |
if (!copy.own_storage_) { |
||
607 |
copy.points = new Vec3f[copy.num_points]; |
||
608 |
std::copy(points, points + num_points, copy.points); |
||
609 |
} |
||
610 |
copy.own_storage_ = true; |
||
611 |
copy.ShapeBase::operator=(*this); |
||
612 |
|||
613 |
return copy_ptr; |
||
614 |
} |
||
615 |
|||
616 |
/// @brief Compute AABB |
||
617 |
void computeLocalAABB(); |
||
618 |
|||
619 |
/// @brief Get node type: a conex polytope |
||
620 |
147363 |
NODE_TYPE getNodeType() const { return GEOM_CONVEX; } |
|
621 |
|||
622 |
/// @brief An array of the points of the polygon. |
||
623 |
Vec3f* points; |
||
624 |
unsigned int num_points; |
||
625 |
|||
626 |
struct HPP_FCL_DLLAPI Neighbors { |
||
627 |
unsigned char count_; |
||
628 |
unsigned int* n_; |
||
629 |
|||
630 |
8 |
unsigned char const& count() const { return count_; } |
|
631 |
24 |
unsigned int& operator[](int i) { |
|
632 |
✗✓ | 24 |
assert(i < count_); |
633 |
24 |
return n_[i]; |
|
634 |
} |
||
635 |
unsigned int const& operator[](int i) const { |
||
636 |
assert(i < count_); |
||
637 |
return n_[i]; |
||
638 |
} |
||
639 |
|||
640 |
12 |
bool operator==(const Neighbors& other) const { |
|
641 |
✗✓ | 12 |
if (count_ != other.count_) return false; |
642 |
|||
643 |
✓✓ | 60 |
for (int i = 0; i < count_; ++i) { |
644 |
✗✓ | 48 |
if (n_[i] != other.n_[i]) return false; |
645 |
} |
||
646 |
|||
647 |
12 |
return true; |
|
648 |
} |
||
649 |
|||
650 |
12 |
bool operator!=(const Neighbors& other) const { return !(*this == other); } |
|
651 |
}; |
||
652 |
/// Neighbors of each vertex. |
||
653 |
/// It is an array of size num_points. For each vertex, it contains the number |
||
654 |
/// of neighbors and a list of indices to them. |
||
655 |
Neighbors* neighbors; |
||
656 |
|||
657 |
/// @brief center of the convex polytope, this is used for collision: center |
||
658 |
/// is guaranteed in the internal of the polytope (as it is convex) |
||
659 |
Vec3f center; |
||
660 |
|||
661 |
protected: |
||
662 |
/// @brief Construct an uninitialized convex object |
||
663 |
/// Initialization is done with ConvexBase::initialize. |
||
664 |
55674 |
ConvexBase() |
|
665 |
55674 |
: ShapeBase(), |
|
666 |
points(NULL), |
||
667 |
num_points(0), |
||
668 |
neighbors(NULL), |
||
669 |
nneighbors_(NULL), |
||
670 |
✓✗ | 55674 |
own_storage_(false) {} |
671 |
|||
672 |
/// @brief Initialize the points of the convex shape |
||
673 |
/// This also initializes the ConvexBase::center. |
||
674 |
/// |
||
675 |
/// \param ownStorage weither the ConvexBase owns the data. |
||
676 |
/// \param points_ list of 3D points /// |
||
677 |
/// \param num_points_ number of 3D points |
||
678 |
void initialize(bool ownStorage, Vec3f* points_, unsigned int num_points_); |
||
679 |
|||
680 |
/// @brief Set the points of the convex shape. |
||
681 |
/// |
||
682 |
/// \param ownStorage weither the ConvexBase owns the data. |
||
683 |
/// \param points_ list of 3D points /// |
||
684 |
/// \param num_points_ number of 3D points |
||
685 |
void set(bool ownStorage, Vec3f* points_, unsigned int num_points_); |
||
686 |
|||
687 |
/// @brief Copy constructor |
||
688 |
/// Only the list of neighbors is copied. |
||
689 |
ConvexBase(const ConvexBase& other); |
||
690 |
|||
691 |
unsigned int* nneighbors_; |
||
692 |
|||
693 |
bool own_storage_; |
||
694 |
|||
695 |
private: |
||
696 |
void computeCenter(); |
||
697 |
|||
698 |
private: |
||
699 |
2 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
700 |
✓✗ | 2 |
const ConvexBase* other_ptr = dynamic_cast<const ConvexBase*>(&_other); |
701 |
✗✓ | 2 |
if (other_ptr == nullptr) return false; |
702 |
2 |
const ConvexBase& other = *other_ptr; |
|
703 |
|||
704 |
✗✓ | 2 |
if (num_points != other.num_points) return false; |
705 |
|||
706 |
✓✓ | 14 |
for (unsigned int i = 0; i < num_points; ++i) { |
707 |
✗✓ | 12 |
if (points[i] != other.points[i]) return false; |
708 |
} |
||
709 |
|||
710 |
✓✓ | 14 |
for (unsigned int i = 0; i < num_points; ++i) { |
711 |
✗✓ | 12 |
if (neighbors[i] != other.neighbors[i]) return false; |
712 |
} |
||
713 |
|||
714 |
2 |
return center == other.center; |
|
715 |
} |
||
716 |
|||
717 |
public: |
||
718 |
28 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
719 |
}; |
||
720 |
|||
721 |
template <typename PolygonT> |
||
722 |
class Convex; |
||
723 |
|||
724 |
/// @brief Half Space: this is equivalent to the Plane in ODE. The separation |
||
725 |
/// plane is defined as n * x = d; Points in the negative side of the separation |
||
726 |
/// plane (i.e. {x | n * x < d}) are inside the half space and points in the |
||
727 |
/// positive side of the separation plane (i.e. {x | n * x > d}) are outside the |
||
728 |
/// half space |
||
729 |
class HPP_FCL_DLLAPI Halfspace : public ShapeBase { |
||
730 |
public: |
||
731 |
/// @brief Construct a half space with normal direction and offset |
||
732 |
✓✗ | 258 |
Halfspace(const Vec3f& n_, FCL_REAL d_) : ShapeBase(), n(n_), d(d_) { |
733 |
✓✗ | 258 |
unitNormalTest(); |
734 |
258 |
} |
|
735 |
|||
736 |
/// @brief Construct a plane with normal direction and offset |
||
737 |
Halfspace(FCL_REAL a, FCL_REAL b, FCL_REAL c, FCL_REAL d_) |
||
738 |
: ShapeBase(), n(a, b, c), d(d_) { |
||
739 |
unitNormalTest(); |
||
740 |
} |
||
741 |
|||
742 |
✓✗ | 1 |
Halfspace() : ShapeBase(), n(1, 0, 0), d(0) {} |
743 |
|||
744 |
5 |
Halfspace(const Halfspace& other) |
|
745 |
✓✗ | 5 |
: ShapeBase(other), n(other.n), d(other.d) {} |
746 |
|||
747 |
/// @brief operator = |
||
748 |
6 |
Halfspace& operator=(const Halfspace& other) { |
|
749 |
6 |
n = other.n; |
|
750 |
6 |
d = other.d; |
|
751 |
6 |
return *this; |
|
752 |
} |
||
753 |
|||
754 |
/// @brief Clone *this into a new Halfspace |
||
755 |
virtual Halfspace* clone() const { return new Halfspace(*this); }; |
||
756 |
|||
757 |
278 |
FCL_REAL signedDistance(const Vec3f& p) const { return n.dot(p) - d; } |
|
758 |
|||
759 |
FCL_REAL distance(const Vec3f& p) const { return std::abs(n.dot(p) - d); } |
||
760 |
|||
761 |
/// @brief Compute AABB |
||
762 |
void computeLocalAABB(); |
||
763 |
|||
764 |
/// @brief Get node type: a half space |
||
765 |
226 |
NODE_TYPE getNodeType() const { return GEOM_HALFSPACE; } |
|
766 |
|||
767 |
5 |
FCL_REAL minInflationValue() const { |
|
768 |
5 |
return std::numeric_limits<FCL_REAL>::lowest(); |
|
769 |
} |
||
770 |
|||
771 |
/// \brief Inflate the cylinder by an amount given by value |
||
772 |
/// |
||
773 |
/// \param[in] value of the shape inflation. |
||
774 |
/// |
||
775 |
/// \returns a new inflated cylinder and the related transform to account for |
||
776 |
/// the change of shape frame |
||
777 |
5 |
std::pair<Halfspace, Transform3f> inflated(const FCL_REAL value) const { |
|
778 |
✗✓ | 5 |
if (value <= minInflationValue()) |
779 |
HPP_FCL_THROW_PRETTY( |
||
780 |
"value (" << value << ") is two small. It should be at least: " |
||
781 |
<< minInflationValue(), |
||
782 |
std::invalid_argument); |
||
783 |
✓✗✓✗ |
10 |
return std::make_pair(Halfspace(n, d + value), Transform3f()); |
784 |
} |
||
785 |
|||
786 |
/// @brief Plane normal |
||
787 |
Vec3f n; |
||
788 |
|||
789 |
/// @brief Plane offset |
||
790 |
FCL_REAL d; |
||
791 |
|||
792 |
protected: |
||
793 |
/// @brief Turn non-unit normal into unit |
||
794 |
void unitNormalTest(); |
||
795 |
|||
796 |
private: |
||
797 |
7 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
798 |
✓✗ | 7 |
const Halfspace* other_ptr = dynamic_cast<const Halfspace*>(&_other); |
799 |
✗✓ | 7 |
if (other_ptr == nullptr) return false; |
800 |
7 |
const Halfspace& other = *other_ptr; |
|
801 |
|||
802 |
✓✗✓✗ |
7 |
return n == other.n && d == other.d; |
803 |
} |
||
804 |
|||
805 |
public: |
||
806 |
12 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
807 |
}; |
||
808 |
|||
809 |
/// @brief Infinite plane |
||
810 |
class HPP_FCL_DLLAPI Plane : public ShapeBase { |
||
811 |
public: |
||
812 |
/// @brief Construct a plane with normal direction and offset |
||
813 |
✓✗ | 250 |
Plane(const Vec3f& n_, FCL_REAL d_) : ShapeBase(), n(n_), d(d_) { |
814 |
✓✗ | 250 |
unitNormalTest(); |
815 |
250 |
} |
|
816 |
|||
817 |
/// @brief Construct a plane with normal direction and offset |
||
818 |
Plane(FCL_REAL a, FCL_REAL b, FCL_REAL c, FCL_REAL d_) |
||
819 |
: ShapeBase(), n(a, b, c), d(d_) { |
||
820 |
unitNormalTest(); |
||
821 |
} |
||
822 |
|||
823 |
✓✗ | 1 |
Plane() : ShapeBase(), n(1, 0, 0), d(0) {} |
824 |
|||
825 |
Plane(const Plane& other) : ShapeBase(other), n(other.n), d(other.d) {} |
||
826 |
|||
827 |
/// @brief operator = |
||
828 |
6 |
Plane& operator=(const Plane& other) { |
|
829 |
6 |
n = other.n; |
|
830 |
6 |
d = other.d; |
|
831 |
6 |
return *this; |
|
832 |
} |
||
833 |
|||
834 |
/// @brief Clone *this into a new Plane |
||
835 |
virtual Plane* clone() const { return new Plane(*this); }; |
||
836 |
|||
837 |
389 |
FCL_REAL signedDistance(const Vec3f& p) const { return n.dot(p) - d; } |
|
838 |
|||
839 |
8 |
FCL_REAL distance(const Vec3f& p) const { return std::abs(n.dot(p) - d); } |
|
840 |
|||
841 |
/// @brief Compute AABB |
||
842 |
void computeLocalAABB(); |
||
843 |
|||
844 |
/// @brief Get node type: a plane |
||
845 |
222 |
NODE_TYPE getNodeType() const { return GEOM_PLANE; } |
|
846 |
|||
847 |
/// @brief Plane normal |
||
848 |
Vec3f n; |
||
849 |
|||
850 |
/// @brief Plane offset |
||
851 |
FCL_REAL d; |
||
852 |
|||
853 |
protected: |
||
854 |
/// @brief Turn non-unit normal into unit |
||
855 |
void unitNormalTest(); |
||
856 |
|||
857 |
private: |
||
858 |
7 |
virtual bool isEqual(const CollisionGeometry& _other) const { |
|
859 |
✓✗ | 7 |
const Plane* other_ptr = dynamic_cast<const Plane*>(&_other); |
860 |
✗✓ | 7 |
if (other_ptr == nullptr) return false; |
861 |
7 |
const Plane& other = *other_ptr; |
|
862 |
|||
863 |
✓✗✓✗ |
7 |
return n == other.n && d == other.d; |
864 |
} |
||
865 |
|||
866 |
public: |
||
867 |
8 |
EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
|
868 |
}; |
||
869 |
|||
870 |
} // namespace fcl |
||
871 |
|||
872 |
} // namespace hpp |
||
873 |
|||
874 |
#endif |
Generated by: GCOVR (Version 4.2) |