GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: test/hfields.cpp Lines: 251 251 100.0 %
Date: 2024-02-09 12:57:42 Branches: 654 1306 50.1 %

Line Branch Exec Source
1
/*
2
 * Software License Agreement (BSD License)
3
 *
4
 *  Copyright (c) 2021, INRIA
5
 *  All rights reserved.
6
 *
7
 *  Redistribution and use in source and binary forms, with or without
8
 *  modification, are permitted provided that the following conditions
9
 *  are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *   * Redistributions in binary form must reproduce the above
14
 *     copyright notice, this list of conditions and the following
15
 *     disclaimer in the documentation and/or other materials provided
16
 *     with the distribution.
17
 *   * Neither the name of Open Source Robotics Foundation nor the names of its
18
 *     contributors may be used to endorse or promote products derived
19
 *     from this software without specific prior written permission.
20
 *
21
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
 *  POSSIBILITY OF SUCH DAMAGE.
33
 */
34
35
/** \author Justin Carpentier */
36
37
#define BOOST_TEST_MODULE FCL_HEIGHT_FIELDS
38
#include <boost/test/included/unit_test.hpp>
39
#include <boost/filesystem.hpp>
40
41
#include "fcl_resources/config.h"
42
43
#include <hpp/fcl/collision.h>
44
#include <hpp/fcl/hfield.h>
45
#include <hpp/fcl/math/transform.h>
46
#include <hpp/fcl/shape/geometric_shapes.h>
47
#include <hpp/fcl/mesh_loader/assimp.h>
48
#include <hpp/fcl/mesh_loader/loader.h>
49
50
#include <hpp/fcl/collision.h>
51
52
#include "utility.h"
53
#include <iostream>
54
55
using namespace hpp::fcl;
56
57
template <typename BV>
58
12
void test_constant_hfields(const Eigen::DenseIndex nx,
59
                           const Eigen::DenseIndex ny,
60
                           const FCL_REAL min_altitude,
61
                           const FCL_REAL max_altitude) {
62
12
  const FCL_REAL x_dim = 1., y_dim = 2.;
63

24
  const MatrixXf heights = MatrixXf::Constant(ny, nx, max_altitude);
64
65
24
  HeightField<BV> hfield(x_dim, y_dim, heights, min_altitude);
66
67



12
  BOOST_CHECK(hfield.getXDim() == x_dim);
68



12
  BOOST_CHECK(hfield.getYDim() == y_dim);
69
70
12
  const VecXf& x_grid = hfield.getXGrid();
71



12
  BOOST_CHECK(x_grid[0] == -x_dim / 2.);
72



12
  BOOST_CHECK(x_grid[nx - 1] == x_dim / 2.);
73
74
12
  const VecXf& y_grid = hfield.getYGrid();
75



12
  BOOST_CHECK(y_grid[0] == y_dim / 2.);
76



12
  BOOST_CHECK(y_grid[ny - 1] == -y_dim / 2.);
77
78
  // Test local AABB
79
12
  hfield.computeLocalAABB();
80
81
500
  for (Eigen::DenseIndex i = 0; i < nx; ++i) {
82
40664
    for (Eigen::DenseIndex j = 0; j < ny; ++j) {
83


40176
      Vec3f point(x_grid[i], y_grid[j], heights(j, i));
84



40176
      BOOST_CHECK(hfield.aabb_local.contain(point));
85
    }
86
  }
87
88
  // Test clone
89
  {
90
12
    HeightField<BV>* hfield_clone = hfield.clone();
91
12
    hfield_clone->computeLocalAABB();
92



12
    BOOST_CHECK(*hfield_clone == hfield);
93
94
12
    delete hfield_clone;
95
  }
96
97
  // Build equivalent object
98
24
  const Box equivalent_box(x_dim, y_dim, max_altitude - min_altitude);
99

12
  const Transform3f box_placement(
100
12
      Matrix3f::Identity(), Vec3f(0., 0., (max_altitude + min_altitude) / 2.));
101
102
  // Test collision
103
24
  const Sphere sphere(1.);
104


12
  static const Transform3f IdTransform;
105
106

24
  const Box box(Vec3f::Ones());
107
108

12
  Transform3f M_sphere, M_box;
109
110
  // No collision case
111
  {
112
12
    const FCL_REAL eps_no_collision = +0.1 * (max_altitude - min_altitude);
113
12
    M_sphere.setTranslation(
114
12
        Vec3f(0., 0., max_altitude + sphere.radius + eps_no_collision));
115
12
    M_box.setTranslation(
116

12
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_no_collision));
117
12
    CollisionRequest request;
118
119
24
    CollisionResult result;
120
12
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
121
122



12
    BOOST_CHECK(!result.isCollision());
123
124
24
    CollisionResult result_check_sphere;
125

12
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
126
            request, result_check_sphere);
127
128



12
    BOOST_CHECK(!result_check_sphere.isCollision());
129
130
24
    CollisionResult result_check_box;
131

12
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
132
            result_check_box);
133
134



12
    BOOST_CHECK(!result_check_box.isCollision());
135
  }
136
137
  // Collision case
138
  {
139
12
    const FCL_REAL eps_collision = -0.1 * (max_altitude - min_altitude);
140
12
    M_sphere.setTranslation(
141
12
        Vec3f(0., 0., max_altitude + sphere.radius + eps_collision));
142
12
    M_box.setTranslation(
143

12
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_collision));
144
    CollisionRequest
145
12
        request;  //(CONTACT | DISTANCE_LOWER_BOUND, (size_t)((nx-1)*(ny-1)));
146
147
24
    CollisionResult result;
148
12
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
149
150



12
    BOOST_CHECK(result.isCollision());
151
152
24
    CollisionResult result_check;
153

12
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
154
            request, result_check);
155
156



12
    BOOST_CHECK(result_check.isCollision());
157
158
24
    CollisionResult result_check_box;
159

12
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
160
            result_check_box);
161
162



12
    BOOST_CHECK(result_check_box.isCollision());
163
  }
164
165
  // Update height
166

12
  hfield.updateHeights(
167
12
      MatrixXf::Constant(ny, nx, max_altitude / 2.));  // We change nothing
168
169
  // No collision case
170
  {
171
12
    const FCL_REAL eps_no_collision = +0.1 * (max_altitude - min_altitude);
172
12
    M_sphere.setTranslation(
173
12
        Vec3f(0., 0., max_altitude + sphere.radius + eps_no_collision));
174
12
    M_box.setTranslation(
175

12
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_no_collision));
176
12
    CollisionRequest request;
177
178
24
    CollisionResult result;
179
12
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
180
181



12
    BOOST_CHECK(!result.isCollision());
182
183
24
    CollisionResult result_check_sphere;
184

12
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
185
            request, result_check_sphere);
186
187



12
    BOOST_CHECK(!result_check_sphere.isCollision());
188
189
24
    CollisionResult result_check_box;
190

12
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
191
            result_check_box);
192
193



12
    BOOST_CHECK(!result_check_box.isCollision());
194
  }
195
196
  // Collision case
197
  {
198
12
    const FCL_REAL eps_collision = -0.1 * (max_altitude - min_altitude);
199
12
    M_sphere.setTranslation(
200
12
        Vec3f(0., 0., max_altitude + sphere.radius + eps_collision));
201
12
    M_box.setTranslation(
202

12
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_collision));
203
    CollisionRequest
204
12
        request;  //(CONTACT | DISTANCE_LOWER_BOUND, (size_t)((nx-1)*(ny-1)));
205
206
24
    CollisionResult result;
207
12
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
208
209



12
    BOOST_CHECK(!result.isCollision());
210
211
24
    CollisionResult result_check;
212

12
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
213
            request, result_check);
214
215



12
    BOOST_CHECK(result_check.isCollision());
216
217
24
    CollisionResult result_check_box;
218

12
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
219
            result_check_box);
220
221



12
    BOOST_CHECK(result_check_box.isCollision());
222
  }
223
224
  // Restore height
225

12
  hfield.updateHeights(
226
      MatrixXf::Constant(ny, nx, max_altitude));  // We change nothing
227
228
  // Collision case
229
  {
230
12
    const FCL_REAL eps_collision = -0.1 * (max_altitude - min_altitude);
231
12
    M_sphere.setTranslation(
232
12
        Vec3f(0., 0., max_altitude + sphere.radius + eps_collision));
233
12
    M_box.setTranslation(
234

12
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_collision));
235
    CollisionRequest
236
12
        request;  //(CONTACT | DISTANCE_LOWER_BOUND, (size_t)((nx-1)*(ny-1)));
237
238
24
    CollisionResult result;
239
12
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
240
241



12
    BOOST_CHECK(result.isCollision());
242
243
24
    CollisionResult result_check;
244

12
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
245
            request, result_check);
246
247



12
    BOOST_CHECK(result_check.isCollision());
248
249
24
    CollisionResult result_check_box;
250

12
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
251
            result_check_box);
252
253



12
    BOOST_CHECK(result_check_box.isCollision());
254
  }
255
12
}
256
257
















4
BOOST_AUTO_TEST_CASE(building_constant_hfields) {
258
2
  const FCL_REAL max_altitude = 1., min_altitude = 0.;
259
260
2
  test_constant_hfields<OBBRSS>(2, 2, min_altitude,
261
                                max_altitude);  // Simple case
262
2
  test_constant_hfields<OBBRSS>(20, 2, min_altitude, max_altitude);
263
2
  test_constant_hfields<OBBRSS>(100, 100, min_altitude, max_altitude);
264
  //  test_constant_hfields<OBBRSS>(1000,1000,min_altitude,max_altitude);
265
266
2
  test_constant_hfields<AABB>(2, 2, min_altitude, max_altitude);  // Simple case
267
2
  test_constant_hfields<AABB>(20, 2, min_altitude, max_altitude);
268
2
  test_constant_hfields<AABB>(100, 100, min_altitude, max_altitude);
269
2
}
270
271
template <typename BV>
272
1
void test_negative_security_margin(const Eigen::DenseIndex nx,
273
                                   const Eigen::DenseIndex ny,
274
                                   const FCL_REAL min_altitude,
275
                                   const FCL_REAL max_altitude) {
276
1
  const FCL_REAL x_dim = 1., y_dim = 2.;
277

2
  const MatrixXf heights = MatrixXf::Constant(ny, nx, max_altitude);
278
279
2
  HeightField<BV> hfield(x_dim, y_dim, heights, min_altitude);
280
281
  // Build equivalent object
282
2
  const Box equivalent_box(x_dim, y_dim, max_altitude - min_altitude);
283

1
  const Transform3f box_placement(
284
1
      Matrix3f::Identity(), Vec3f(0., 0., (max_altitude + min_altitude) / 2.));
285
286
  // Test collision
287
2
  const Sphere sphere(1.);
288


1
  static const Transform3f IdTransform;
289
290

2
  const Box box(Vec3f::Ones());
291
292

1
  Transform3f M_sphere, M_box;
293
294
  // No collision case
295
  {
296
1
    const FCL_REAL eps_no_collision = +0.1 * (max_altitude - min_altitude);
297
1
    M_sphere.setTranslation(
298
1
        Vec3f(0., 0., max_altitude + sphere.radius + eps_no_collision));
299
1
    M_box.setTranslation(
300

1
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_no_collision));
301
1
    CollisionRequest request;
302
303
2
    CollisionResult result;
304
1
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
305
306



1
    BOOST_CHECK(!result.isCollision());
307
308
2
    CollisionResult result_check_sphere;
309

1
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
310
            request, result_check_sphere);
311
312



1
    BOOST_CHECK(!result_check_sphere.isCollision());
313
314
2
    CollisionResult result_check_box;
315

1
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
316
            result_check_box);
317
318



1
    BOOST_CHECK(!result_check_box.isCollision());
319
  }
320
321
  // Collision case - positive security_margin
322
  {
323
1
    const FCL_REAL eps_no_collision = +0.1 * (max_altitude - min_altitude);
324
1
    M_sphere.setTranslation(
325
1
        Vec3f(0., 0., max_altitude + sphere.radius + eps_no_collision));
326
1
    M_box.setTranslation(
327

1
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_no_collision));
328
1
    CollisionRequest request;
329
1
    request.security_margin = eps_no_collision + 1e-6;
330
331
2
    CollisionResult result;
332
1
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
333
334



1
    BOOST_CHECK(result.isCollision());
335
336
2
    CollisionResult result_check_sphere;
337

1
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
338
            request, result_check_sphere);
339
340



1
    BOOST_CHECK(result_check_sphere.isCollision());
341
342
2
    CollisionResult result_check_box;
343

1
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
344
            result_check_box);
345
346



1
    BOOST_CHECK(result_check_box.isCollision());
347
  }
348
349
  // Collision case
350
  {
351
1
    const FCL_REAL eps_no_collision = -0.1 * (max_altitude - min_altitude);
352
1
    M_sphere.setTranslation(
353
1
        Vec3f(0., 0., max_altitude + sphere.radius + eps_no_collision));
354
1
    M_box.setTranslation(
355

1
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_no_collision));
356
1
    CollisionRequest request;
357
358
2
    CollisionResult result;
359
1
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
360
361



1
    BOOST_CHECK(result.isCollision());
362
363
2
    CollisionResult result_check_sphere;
364

1
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
365
            request, result_check_sphere);
366
367



1
    BOOST_CHECK(result_check_sphere.isCollision());
368
369
2
    CollisionResult result_check_box;
370

1
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
371
            result_check_box);
372
373



1
    BOOST_CHECK(result_check_box.isCollision());
374
  }
375
376
  // No collision case - negative security_margin
377
  {
378
1
    const FCL_REAL eps_no_collision = -0.1 * (max_altitude - min_altitude);
379
1
    M_sphere.setTranslation(
380
1
        Vec3f(0., 0., max_altitude + sphere.radius + eps_no_collision));
381
1
    M_box.setTranslation(
382

1
        Vec3f(0., 0., max_altitude + box.halfSide[2] + eps_no_collision));
383
1
    CollisionRequest request;
384
1
    request.security_margin = eps_no_collision - 1e-4;
385
386
2
    CollisionResult result;
387
1
    collide(&hfield, IdTransform, &sphere, M_sphere, request, result);
388
389



1
    BOOST_CHECK(!result.isCollision());
390
391
2
    CollisionResult result_check_sphere;
392

1
    collide(&equivalent_box, IdTransform * box_placement, &sphere, M_sphere,
393
            request, result_check_sphere);
394
395



1
    BOOST_CHECK(!result_check_sphere.isCollision());
396
397
2
    CollisionResult result_check_box;
398

1
    collide(&equivalent_box, IdTransform * box_placement, &box, M_box, request,
399
            result_check_box);
400
401



1
    BOOST_CHECK(!result_check_box.isCollision());
402
  }
403
1
}
404
405
















4
BOOST_AUTO_TEST_CASE(negative_security_margin) {
406
2
  const FCL_REAL max_altitude = 1., min_altitude = 0.;
407
408
  //  test_negative_security_margin<OBBRSS>(100, 100, min_altitude,
409
  //  max_altitude);
410
2
  test_negative_security_margin<AABB>(100, 100, min_altitude, max_altitude);
411
2
}
412
413
















4
BOOST_AUTO_TEST_CASE(hfield_with_square_hole) {
414
2
  const Eigen::DenseIndex nx = 100, ny = 100;
415
416
  typedef AABB BV;
417
  const MatrixXf X =
418

4
      Eigen::RowVectorXd::LinSpaced(nx, -1., 1.).replicate(ny, 1);
419

4
  const MatrixXf Y = Eigen::VectorXd::LinSpaced(ny, 1., -1.).replicate(1, nx);
420
421
2
  const FCL_REAL dim_square = 0.5;
422
423
  const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> hole =
424




4
      (X.array().abs() < dim_square) && (Y.array().abs() < dim_square);
425
426
  const MatrixXf heights =
427


4
      MatrixXf::Ones(ny, nx) - hole.cast<double>().matrix();
428
429
4
  const HeightField<BV> hfield(2., 2., heights, -10.);
430
431
4
  Sphere sphere(0.48);
432

2
  const Transform3f sphere_pos(Vec3f(0., 0., 0.5));
433
2
  const Transform3f hfield_pos;
434
435
2
  const CollisionRequest request;
436
437
4
  CollisionResult result;
438
  {
439
2
    collide(&hfield, hfield_pos, &sphere, sphere_pos, request, result);
440
441



2
    BOOST_CHECK(!result.isCollision());
442
  }
443
444
2
  sphere.radius = 0.51;
445
446
  {
447
4
    CollisionResult result;
448
4
    const Sphere sphere2(0.51);
449
2
    collide(&hfield, hfield_pos, &sphere2, sphere_pos, request, result);
450
451



2
    BOOST_CHECK(result.isCollision());
452
  }
453
2
}
454
455
















4
BOOST_AUTO_TEST_CASE(hfield_with_circular_hole) {
456
2
  const Eigen::DenseIndex nx = 100, ny = 100;
457
458
  //  typedef OBBRSS BV; TODO(jcarpent): OBBRSS does not work (compile in Debug
459
  //  mode), as the overlap of OBBRSS is not satisfactory yet.
460
  typedef AABB BV;
461
  const MatrixXf X =
462

4
      Eigen::RowVectorXd::LinSpaced(nx, -1., 1.).replicate(ny, 1);
463

4
  const MatrixXf Y = Eigen::VectorXd::LinSpaced(ny, 1., -1.).replicate(1, nx);
464
465
2
  const FCL_REAL dim_hole = 1;
466
467
  const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> hole =
468



4
      (X.array().square() + Y.array().square() <= dim_hole);
469
470
  const MatrixXf heights =
471


4
      MatrixXf::Ones(ny, nx) - hole.cast<double>().matrix();
472
473
4
  const HeightField<BV> hfield(2., 2., heights, -10.);
474
475



2
  BOOST_CHECK(hfield.getXGrid()[0] == -1.);
476



2
  BOOST_CHECK(hfield.getXGrid()[nx - 1] == +1.);
477
478



2
  BOOST_CHECK(hfield.getYGrid()[0] == +1.);
479



2
  BOOST_CHECK(hfield.getYGrid()[ny - 1] == -1.);
480
481
4
  Sphere sphere(0.975);
482

2
  const Transform3f sphere_pos(Vec3f(0., 0., 1.));
483
2
  const Transform3f hfield_pos;
484
485
  {
486
4
    CollisionResult result;
487
2
    CollisionRequest request;
488
2
    request.security_margin = 0.;
489
2
    collide(&hfield, hfield_pos, &sphere, sphere_pos, request, result);
490
491



2
    BOOST_CHECK(!result.isCollision());
492
  }
493
494
  {
495
4
    CollisionResult result;
496
2
    CollisionRequest request;
497
2
    request.security_margin = 0.01;
498
2
    collide(&hfield, hfield_pos, &sphere, sphere_pos, request, result);
499
500



2
    BOOST_CHECK(!result.isCollision());
501
  }
502
503
  {
504
4
    CollisionResult result;
505
2
    CollisionRequest request;
506
2
    request.security_margin = 1. - sphere.radius;
507
2
    collide(&hfield, hfield_pos, &sphere, sphere_pos, request, result);
508
509



2
    BOOST_CHECK(result.isCollision());
510
  }
511
512
  {
513
4
    CollisionResult result;
514
2
    CollisionRequest request;
515
2
    request.security_margin = -0.005;
516
2
    collide(&hfield, hfield_pos, &sphere, sphere_pos, request, result);
517
518



2
    BOOST_CHECK(!result.isCollision());
519
  }
520
2
}