moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
test_distance_field.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2009, Willow Garage, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of Willow Garage 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: Mrinal Kalakrishnan, Ken Anderson */
36
37#include <gtest/gtest.h>
38
42#include <geometric_shapes/body_operations.h>
43#include <tf2_eigen/tf2_eigen.hpp>
44#include <octomap/octomap.h>
45#include <memory>
46
47using namespace distance_field;
48
49static const double WIDTH = 1.0;
50static const double HEIGHT = 1.0;
51static const double DEPTH = 1.0;
52static const double RESOLUTION = 0.1;
53static const double ORIGIN_X = 0.0;
54static const double ORIGIN_Y = 0.0;
55static const double ORIGIN_Z = 0.0;
56static const double MAX_DIST = 0.3;
57
58static const Eigen::Vector3d POINT1(0.1, 0.0, 0.0);
59static const Eigen::Vector3d POINT2(0.0, 0.1, 0.2);
60static const Eigen::Vector3d POINT3(0.4, 0.0, 0.0);
61
62int distanceSequence(int x, int y, int z)
63{
64 return x * x + y * y + z * z;
65}
66
67void print(PropagationDistanceField& pdf, int numX, int numY, int numZ)
68{
69 for (int z = 0; z < numZ; ++z)
70 {
71 for (int y = 0; y < numY; ++y)
72 {
73 for (int x = 0; x < numX; ++x)
74 {
75 std::cout << pdf.getCell(x, y, z).distance_square_ << ' ';
76 }
77 std::cout << '\n';
78 }
79 std::cout << '\n';
80 }
81 for (int z = 0; z < numZ; ++z)
82 {
83 for (int y = 0; y < numY; ++y)
84 {
85 for (int x = 0; x < numX; ++x)
86 {
87 if (pdf.getCell(x, y, z).distance_square_ == 0)
88 {
89 // RCLCPP_INFO("distance_field", "Obstacle cell %d %d %d", x, y, z);
90 }
91 }
92 }
93 }
94}
95
96void printNeg(PropagationDistanceField& pdf, int numX, int numY, int numZ)
97{
98 for (int z = 0; z < numZ; ++z)
99 {
100 for (int y = 0; y < numY; ++y)
101 {
102 for (int x = 0; x < numX; ++x)
103 {
104 std::cout << pdf.getCell(x, y, z).negative_distance_square_ << ' ';
105 }
106 std::cout << '\n';
107 }
108 std::cout << '\n';
109 }
110}
111
112void printPointCoords(const Eigen::Vector3i& p)
113{
114 if (p.x() < 0)
115 {
116 std::cout << '-';
117 }
118 else
119 {
120 std::cout << p.x();
121 }
122
123 if (p.y() < 0)
124 {
125 std::cout << '-';
126 }
127 else
128 {
129 std::cout << p.y();
130 }
131
132 if (p.z() < 0)
133 {
134 std::cout << '-';
135 }
136 else
137 {
138 std::cout << p.z();
139 }
140
141 std::cout << ' ';
142}
143
144void printBoth(PropagationDistanceField& pdf, int numX, int numY, int numZ)
145{
146 std::cout << "Positive distance square ... negative distance square\n";
147 for (int z = 0; z < numZ; ++z)
148 {
149 std::cout << "Z=" << z << '\n';
150 for (int y = 0; y < numY; ++y)
151 {
152 for (int x = 0; x < numX; ++x)
153 {
154 std::cout << pdf.getCell(x, y, z).distance_square_ << ' ';
155 }
156 std::cout << " ";
157 for (int x = 0; x < numX; ++x)
158 {
159 std::cout << pdf.getCell(x, y, z).negative_distance_square_ << ' ';
160 }
161 std::cout << " ";
162 for (int x = 0; x < numX; ++x)
163 {
165 }
166 std::cout << " ";
167 for (int x = 0; x < numX; ++x)
168 {
170 }
171 std::cout << '\n';
172 }
173 std::cout << '\n';
174 }
175}
176
178{
179 if (df1.getXNumCells() != df2.getXNumCells())
180 return false;
181 if (df1.getYNumCells() != df2.getYNumCells())
182 return false;
183 if (df1.getZNumCells() != df2.getZNumCells())
184 return false;
185 for (int z = 0; z < df1.getZNumCells(); ++z)
186 {
187 for (int x = 0; x < df1.getXNumCells(); ++x)
188 {
189 for (int y = 0; y < df1.getYNumCells(); ++y)
190 {
191 if (df1.getCell(x, y, z).distance_square_ != df2.getCell(x, y, z).distance_square_)
192 {
193 printf("Cell %d %d %d distances not equal %d %d\n", x, y, z, df1.getCell(x, y, z).distance_square_,
194 df2.getCell(x, y, z).distance_square_);
195 return false;
196 }
198 {
199 printf("Cell %d %d %d neg distances not equal %d %d\n", x, y, z,
201 return false;
202 }
203 }
204 }
205 }
206 return true;
207}
208
209bool checkOctomapVersusDistanceField(const PropagationDistanceField& df, const octomap::OcTree& octree)
210{
211 // just one way for now
212 for (int z = 0; z < df.getZNumCells(); ++z)
213 {
214 for (int x = 0; x < df.getXNumCells(); ++x)
215 {
216 for (int y = 0; y < df.getYNumCells(); ++y)
217 {
218 if (df.getCell(x, y, z).distance_square_ == 0)
219 {
220 // making sure that octomap also shows it as occupied
221 double qx, qy, qz;
222 df.gridToWorld(x, y, z, qx, qy, qz);
223 octomap::point3d query(qx, qy, qz);
224 octomap::OcTreeNode* result = octree.search(query);
225 if (!result)
226 {
227 for (float boundary_x = query.x() - df.getResolution();
228 boundary_x <= query.x() + df.getResolution() && !result; boundary_x += df.getResolution())
229 {
230 for (float boundary_y = query.y() - df.getResolution();
231 boundary_y <= query.y() + df.getResolution() && !result; boundary_y += df.getResolution())
232 {
233 for (float boundary_z = query.z() - df.getResolution(); boundary_z <= query.z() + df.getResolution();
234 boundary_z += df.getResolution())
235 {
236 octomap::point3d query_boundary(boundary_x, boundary_y, boundary_z);
237 result = octree.search(query_boundary);
238 if (result)
239 {
240 break;
241 }
242 }
243 }
244 }
245 if (!result)
246 {
247 std::cout << "No point at potential boundary query " << query.x() << ' ' << query.y() << ' ' << query.z()
248 << '\n';
249 return false;
250 }
251 }
252 if (!octree.isNodeOccupied(result))
253 {
254 std::cout << "Disagreement at " << qx << ' ' << qy << ' ' << qz << '\n';
255 return false;
256 }
257 }
258 }
259 }
260 }
261 return true;
262}
263
265{
266 unsigned int count = 0;
267 for (int z = 0; z < df.getZNumCells(); ++z)
268 {
269 for (int x = 0; x < df.getXNumCells(); ++x)
270 {
271 for (int y = 0; y < df.getYNumCells(); ++y)
272 {
273 if (df.getCell(x, y, z).distance_square_ == 0)
274 {
275 count++;
276 }
277 }
278 }
279 }
280 return count;
281}
282
283unsigned int countLeafNodes(const octomap::OcTree& octree)
284{
285 unsigned int count = 0;
286 for (octomap::OcTree::leaf_iterator it = octree.begin_leafs(), end = octree.end_leafs(); it != end; ++it)
287 {
288 if (octree.isNodeOccupied(*it))
289 {
290 std::cout << "Leaf node " << it.getX() << ' ' << it.getY() << ' ' << it.getZ() << '\n';
291 count++;
292 }
293 }
294 return count;
295}
296
297// points should contain all occupied points
298void checkDistanceField(const PropagationDistanceField& df, const EigenSTL::vector_Vector3d& points, int numX, int numY,
299 int numZ, bool do_negs)
300{
301 EigenSTL::vector_Vector3i points_ind(points.size());
302 for (unsigned int i = 0; i < points.size(); ++i)
303 {
304 Eigen::Vector3i loc;
305 df.worldToGrid(points[i].x(), points[i].y(), points[i].z(), loc.x(), loc.y(), loc.z());
306 points_ind[i] = loc;
307 }
308
309 for (int x = 0; x < numX; ++x)
310 {
311 for (int y = 0; y < numY; ++y)
312 {
313 for (int z = 0; z < numZ; ++z)
314 {
315 double dsq = df.getCell(x, y, z).distance_square_;
316 double ndsq = df.getCell(x, y, z).negative_distance_square_;
317 if (dsq == 0)
318 {
319 bool found = false;
320 for (Eigen::Vector3i& point : points_ind)
321 {
322 if (point.x() == x && point.y() == y && point.z() == z)
323 {
324 found = true;
325 break;
326 }
327 }
328 if (do_negs)
329 {
330 ASSERT_GT(ndsq, 0) << "Obstacle point " << x << ' ' << y << ' ' << z << " has zero negative value";
331 }
332 ASSERT_TRUE(found) << "Obstacle point " << x << ' ' << y << ' ' << z << " not found";
333 }
334 }
335 }
336 }
337}
338
339TEST(TestPropagationDistanceField, TestAddRemovePoints)
340{
341 PropagationDistanceField df(WIDTH, HEIGHT, DEPTH, RESOLUTION, ORIGIN_X, ORIGIN_Y, ORIGIN_Z, MAX_DIST);
342
343 // Check size
344 int num_x = df.getXNumCells();
345 int num_y = df.getYNumCells();
346 int num_z = df.getZNumCells();
347
348 EXPECT_EQ(num_x, static_cast<int>(WIDTH / RESOLUTION + 0.5));
349 EXPECT_EQ(num_y, static_cast<int>(HEIGHT / RESOLUTION + 0.5));
350 EXPECT_EQ(num_z, static_cast<int>(DEPTH / RESOLUTION + 0.5));
351
352 // getting a bad point
353 double tgx, tgy, tgz;
354 bool in_bounds;
355 EXPECT_NEAR(df.getDistance(1000.0, 1000.0, 1000.0), MAX_DIST, .0001);
356 EXPECT_NEAR(df.getDistanceGradient(1000.0, 1000.0, 1000.0, tgx, tgy, tgz, in_bounds), MAX_DIST, .0001);
357 EXPECT_FALSE(in_bounds);
358
359 // Add points to the grid
360 EigenSTL::vector_Vector3d points;
361 points.push_back(POINT1);
362 points.push_back(POINT2);
363 // RCLCPP_INFO("distance_field", "Adding %zu points", points.size());
364 df.addPointsToField(points);
365 // print(df, numX, numY, numZ);
366
367 // Update - iterative
368 points.clear();
369 points.push_back(POINT2);
370 points.push_back(POINT3);
371 EigenSTL::vector_Vector3d old_points;
372 old_points.push_back(POINT1);
373 df.updatePointsInField(old_points, points);
374 // std::cout << "One removal, one addition" << '\n';
375 // print(df, numX, numY, numZ);
376 // printNeg(df, numX, numY, numZ);
377 checkDistanceField(df, points, num_x, num_y, num_z, false);
378
379 // Remove
380 points.clear();
381 points.push_back(POINT2);
382 df.removePointsFromField(points);
383 points.clear();
384 points.push_back(POINT3);
385 checkDistanceField(df, points, num_x, num_y, num_z, false);
386
387 // now testing gradient calls
388 df.reset();
389 points.clear();
390 points.push_back(POINT1);
391 df.addPointsToField(points);
392 bool first = true;
393 for (int z = 1; z < df.getZNumCells() - 1; ++z)
394 {
395 for (int x = 1; x < df.getXNumCells() - 1; ++x)
396 {
397 for (int y = 1; y < df.getYNumCells() - 1; ++y)
398 {
399 double dist = df.getDistance(x, y, z);
400 double wx, wy, wz;
401 df.gridToWorld(x, y, z, wx, wy, wz);
402 Eigen::Vector3d grad(0.0, 0.0, 0.0);
403 bool grad_in_bounds;
404 double dist_grad = df.getDistanceGradient(wx, wy, wz, grad.x(), grad.y(), grad.z(), grad_in_bounds);
405 ASSERT_TRUE(grad_in_bounds) << x << ' ' << y << ' ' << z;
406 ASSERT_NEAR(dist, dist_grad, .0001);
407 if (dist > 0 && dist < MAX_DIST)
408 {
409 double xscale = grad.x() / grad.norm();
410 double yscale = grad.y() / grad.norm();
411 double zscale = grad.z() / grad.norm();
412
413 double comp_x = wx - xscale * dist;
414 double comp_y = wy - yscale * dist;
415 double comp_z = wz - zscale * dist;
416 if (first)
417 {
418 first = false;
419 std::cout << "Dist " << dist << '\n';
420 std::cout << "Cell " << x << ' ' << y << ' ' << z << ' ' << wx << ' ' << wy << ' ' << wz << '\n';
421 std::cout << "Scale " << xscale << ' ' << yscale << ' ' << zscale << '\n';
422 std::cout << "Grad " << grad.x() << ' ' << grad.y() << ' ' << grad.z() << " comp " << comp_x << ' '
423 << comp_y << ' ' << comp_z << '\n';
424 }
425 ASSERT_NEAR(comp_x, POINT1.x(), RESOLUTION)
426 << dist << x << ' ' << y << ' ' << z << ' ' << grad.x() << ' ' << grad.y() << ' ' << grad.z() << ' '
427 << xscale << ' ' << yscale << ' ' << zscale << '\n';
428 ASSERT_NEAR(comp_y, POINT1.y(), RESOLUTION)
429 << x << ' ' << y << ' ' << z << ' ' << grad.x() << ' ' << grad.y() << ' ' << grad.z() << ' ' << xscale
430 << ' ' << yscale << ' ' << zscale << '\n';
431 ASSERT_NEAR(comp_z, POINT1.z(), RESOLUTION)
432 << x << ' ' << y << ' ' << z << ' ' << grad.x() << ' ' << grad.y() << ' ' << grad.z() << ' ' << xscale
433 << ' ' << yscale << ' ' << zscale << '\n';
434 }
435 }
436 }
437 }
438 ASSERT_FALSE(first);
439}
440
441TEST(TestSignedPropagationDistanceField, TestSignedAddRemovePoints)
442{
443 PropagationDistanceField df(WIDTH, HEIGHT, DEPTH, RESOLUTION, ORIGIN_X, ORIGIN_Y, ORIGIN_Z, MAX_DIST, true);
444
445 // Check size
446 int num_x = df.getXNumCells();
447 int num_y = df.getYNumCells();
448 int num_z = df.getZNumCells();
449
450 EXPECT_EQ(num_x, static_cast<int>(WIDTH / RESOLUTION + 0.5));
451 EXPECT_EQ(num_y, static_cast<int>(HEIGHT / RESOLUTION + 0.5));
452 EXPECT_EQ(num_z, static_cast<int>(DEPTH / RESOLUTION + 0.5));
453
454 // Error checking
455 // print(df, numX, numY, numZ);
456
457 // TODO - check initial values
458 // EXPECT_EQ( df.getCell(0,0,0).distance_square_, max_distanceSequence_in_voxels );
459
460 // Add points to the grid
461 double lwx, lwy, lwz;
462 double hwx, hwy, hwz;
463 df.gridToWorld(1, 1, 1, lwx, lwy, lwz);
464 df.gridToWorld(8, 8, 8, hwx, hwy, hwz);
465
466 EigenSTL::vector_Vector3d points;
467 for (double x = lwx; x <= hwx; x += .1)
468 {
469 for (double y = lwy; y <= hwy; y += .1)
470 {
471 for (double z = lwz; z <= hwz; z += .1)
472 {
473 points.push_back(Eigen::Vector3d(x, y, z));
474 }
475 }
476 }
477
478 df.reset();
479 // RCLCPP_INFO("distance_field", "Adding %zu points", points.size());
480 df.addPointsToField(points);
481 // print(df, numX, numY, numZ);
482 // printNeg(df, numX, numY, numZ);
483
484 double cx, cy, cz;
485 df.gridToWorld(5, 5, 5, cx, cy, cz);
486
487 Eigen::Vector3d center_point(cx, cy, cz);
488
489 EigenSTL::vector_Vector3d rem_points;
490 rem_points.push_back(center_point);
491 df.removePointsFromField(rem_points);
492 // std::cout << "Pos "<< '\n';
493 // print(df, numX, numY, numZ);
494 // std::cout << "Neg "<< '\n';
495 // printNeg(df, numX, numY, numZ);
496
497 // testing equality with initial add of points without the center point
498 PropagationDistanceField test_df(WIDTH, HEIGHT, DEPTH, RESOLUTION, ORIGIN_X, ORIGIN_Y, ORIGIN_Z, MAX_DIST, true);
499 EigenSTL::vector_Vector3d test_points;
500 for (const Eigen::Vector3d& point : points)
501 {
502 if (point.x() != center_point.x() || point.y() != center_point.y() || point.z() != center_point.z())
503 {
504 test_points.push_back(point);
505 }
506 }
507 test_df.addPointsToField(test_points);
508 ASSERT_TRUE(areDistanceFieldsDistancesEqual(df, test_df));
509
510 PropagationDistanceField gradient_df(WIDTH, HEIGHT, DEPTH, RESOLUTION, ORIGIN_X, ORIGIN_Y, ORIGIN_Z, MAX_DIST, true);
511
512 shapes::Sphere sphere(.25);
513
514 geometry_msgs::msg::Pose p;
515 p.orientation.w = 1.0;
516 p.position.x = .5;
517 p.position.y = .5;
518 p.position.z = .5;
519
520 Eigen::Isometry3d p_eigen;
521 tf2::fromMsg(p, p_eigen);
522
523 gradient_df.addShapeToField(&sphere, p_eigen);
524 // printBoth(gradient_df, numX, numY, numZ);
525 EXPECT_GT(gradient_df.getCell(5, 5, 5).negative_distance_square_, 1);
526 // all negative cells should have gradients that point towards cells with distance 1
527 for (int z = 1; z < df.getZNumCells() - 1; ++z)
528 {
529 for (int x = 1; x < df.getXNumCells() - 1; ++x)
530 {
531 for (int y = 1; y < df.getYNumCells() - 1; ++y)
532 {
533 double dist = gradient_df.getDistance(x, y, z);
534 double ncell_dist;
535 Eigen::Vector3i ncell_pos;
536 const PropDistanceFieldVoxel* ncell = gradient_df.getNearestCell(x, y, z, ncell_dist, ncell_pos);
537
538 EXPECT_EQ(ncell_dist, dist);
539
540 if (ncell == nullptr)
541 {
542 if (ncell_dist > 0)
543 {
544 EXPECT_GE(ncell_dist, gradient_df.getUninitializedDistance())
545 << "dist=" << dist << " xyz=" << x << ' ' << y << ' ' << z << " ncell=" << ncell_pos.x() << ' '
546 << ncell_pos.y() << ' ' << ncell_pos.z() << '\n';
547 }
548 else if (ncell_dist < 0)
549 {
550 EXPECT_LE(ncell_dist, -gradient_df.getUninitializedDistance())
551 << "dist=" << dist << " xyz=" << x << ' ' << y << ' ' << z << " ncell=" << ncell_pos.x() << ' '
552 << ncell_pos.y() << ' ' << ncell_pos.z() << '\n';
553 }
554 }
555
556 if (gradient_df.getCell(x, y, z).negative_distance_square_ > 0)
557 {
558 ASSERT_LT(dist, 0) << "Pos " << gradient_df.getCell(x, y, z).distance_square_ << ' '
559 << gradient_df.getCell(x, y, z).negative_distance_square_;
560 double wx, wy, wz;
561 df.gridToWorld(x, y, z, wx, wy, wz);
562 Eigen::Vector3d grad(0.0, 0.0, 0.0);
563 bool grad_in_bounds;
564 double dist_grad = gradient_df.getDistanceGradient(wx, wy, wz, grad.x(), grad.y(), grad.z(), grad_in_bounds);
565 ASSERT_TRUE(grad_in_bounds) << x << ' ' << y << ' ' << z;
566 ASSERT_NEAR(dist, dist_grad, .0001);
567
568 if (!ncell)
569 continue;
570
571 EXPECT_GE(gradient_df.getCell(ncell_pos.x(), ncell_pos.y(), ncell_pos.z()).distance_square_, 1)
572 << "dist=" << dist << " xyz=" << x << ' ' << y << ' ' << z << " grad=" << grad.x() << ' ' << grad.y()
573 << ' ' << grad.z() << " ncell=" << ncell_pos.x() << ' ' << ncell_pos.y() << ' ' << ncell_pos.z() << '\n';
574
575 double grad_size_sq = grad.squaredNorm();
576 if (grad_size_sq < std::numeric_limits<double>::epsilon())
577 continue;
578
579 double oo_grad_size = 1.0 / sqrt(grad_size_sq);
580 double xscale = grad.x() * oo_grad_size;
581 double yscale = grad.y() * oo_grad_size;
582 double zscale = grad.z() * oo_grad_size;
583
584 double comp_x = wx - xscale * dist;
585 double comp_y = wy - yscale * dist;
586 double comp_z = wz - zscale * dist;
587
588 int cell_x, cell_y, cell_z;
589 bool cell_in_bounds = gradient_df.worldToGrid(comp_x, comp_y, comp_z, cell_x, cell_y, cell_z);
590
591 ASSERT_EQ(cell_in_bounds, true);
592 const PropDistanceFieldVoxel* cell = &gradient_df.getCell(cell_x, cell_y, cell_z);
593
594#if 0
595 EXPECT_EQ(ncell_pos.x(), cell_x);
596 EXPECT_EQ(ncell_pos.y(), cell_y);
597 EXPECT_EQ(ncell_pos.z(), cell_z);
598 EXPECT_EQ(ncell, cell);
599#endif
600 EXPECT_GE(cell->distance_square_, 1)
601 << dist << ' ' << x << ' ' << y << ' ' << z << ' ' << grad.x() << ' ' << grad.y() << ' ' << grad.z()
602 << ' ' << xscale << ' ' << yscale << ' ' << zscale << " cell " << comp_x << ' ' << comp_y << ' ' << comp_z
603 << '\n';
604 }
605 }
606 }
607 }
608}
609
610TEST(TestSignedPropagationDistanceField, TestShape)
611{
612 PropagationDistanceField df(WIDTH, HEIGHT, DEPTH, RESOLUTION, ORIGIN_X, ORIGIN_Y, ORIGIN_Z, MAX_DIST, true);
613
614 int num_x = df.getXNumCells();
615 int num_y = df.getYNumCells();
616 int num_z = df.getZNumCells();
617
618 shapes::Sphere sphere(.25);
619
620 Eigen::Isometry3d p = Eigen::Translation3d(0.5, 0.5, 0.5) * Eigen::Quaterniond(0.0, 0.0, 0.0, 1.0);
621 Eigen::Isometry3d np = Eigen::Translation3d(0.7, 0.7, 0.7) * Eigen::Quaterniond(0.0, 0.0, 0.0, 1.0);
622
623 df.addShapeToField(&sphere, p);
624
625 bodies::Body* body = bodies::createBodyFromShape(&sphere);
626 body->setPose(p);
627 EigenSTL::vector_Vector3d point_vec;
628 findInternalPointsConvex(*body, RESOLUTION, point_vec);
629 delete body;
630 checkDistanceField(df, point_vec, num_x, num_y, num_z, true);
631
632 // std::cout << "Shape pos "<< '\n';
633 // print(df, numX, numY, numZ);
634 // std::cout << "Shape neg "<< '\n';
635 // printNeg(df, numX, numY, numZ);
636
637 df.addShapeToField(&sphere, np);
638
639 body = bodies::createBodyFromShape(&sphere);
640 body->setPose(np);
641
642 EigenSTL::vector_Vector3d point_vec_2;
643 findInternalPointsConvex(*body, RESOLUTION, point_vec_2);
644 delete body;
645 EigenSTL::vector_Vector3d point_vec_union = point_vec_2;
646 point_vec_union.insert(point_vec_union.end(), point_vec.begin(), point_vec.end());
647 checkDistanceField(df, point_vec_union, num_x, num_y, num_z, true);
648
649 // should get rid of old pose
650 df.moveShapeInField(&sphere, p, np);
651
652 checkDistanceField(df, point_vec_2, num_x, num_y, num_z, true);
653
654 // should be equivalent to just adding second shape
655 PropagationDistanceField test_df(WIDTH, HEIGHT, DEPTH, RESOLUTION, ORIGIN_X, ORIGIN_Y, ORIGIN_Z, MAX_DIST, true);
656 test_df.addShapeToField(&sphere, np);
657 ASSERT_TRUE(areDistanceFieldsDistancesEqual(df, test_df));
658}
659
660static const double PERF_WIDTH = 3.0;
661static const double PERF_HEIGHT = 3.0;
662static const double PERF_DEPTH = 4.0;
663static const double PERF_RESOLUTION = 0.02;
664static const double PERF_ORIGIN_X = 0.0;
665static const double PERF_ORIGIN_Y = 0.0;
666static const double PERF_ORIGIN_Z = 0.0;
667static const double PERF_MAX_DIST = .25;
668static const unsigned int UNIFORM_DISTANCE = 10;
669
670TEST(TestSignedPropagationDistanceField, TestPerformance)
671{
672 std::cout << "Creating distance field with "
673 << (PERF_WIDTH / PERF_RESOLUTION) * (PERF_HEIGHT / PERF_RESOLUTION) * (PERF_DEPTH / PERF_RESOLUTION)
674 << " entries" << '\n';
675
676 auto dt = std::chrono::system_clock::now();
677 PropagationDistanceField df(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X, PERF_ORIGIN_Y,
678 PERF_ORIGIN_Z, PERF_MAX_DIST, false);
679 std::cout
680 << "Creating unsigned took "
681 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
682 << " secs" << '\n';
683
684 dt = std::chrono::system_clock::now();
685 PropagationDistanceField sdf(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X, PERF_ORIGIN_Y,
686 PERF_ORIGIN_Z, PERF_MAX_DIST, true);
687
688 std::cout
689 << "Creating signed took "
690 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
691 << " secs" << '\n';
692
693 shapes::Box big_table(2.0, 2.0, .5);
694
695 Eigen::Isometry3d p = Eigen::Translation3d(PERF_WIDTH / 2.0, PERF_DEPTH / 2.0, PERF_HEIGHT / 2.0) *
696 Eigen::Quaterniond(0.0, 0.0, 0.0, 1.0);
697 Eigen::Isometry3d np = Eigen::Translation3d(PERF_WIDTH / 2.0 + .01, PERF_DEPTH / 2.0, PERF_HEIGHT / 2.0) *
698 Eigen::Quaterniond(0.0, 0.0, 0.0, 1.0);
699
700 unsigned int big_num_points = ceil(2.0 / PERF_RESOLUTION) * ceil(2.0 / PERF_RESOLUTION) * ceil(.5 / PERF_RESOLUTION);
701
702 std::cout << "Adding " << big_num_points << " points" << '\n';
703
704 dt = std::chrono::system_clock::now();
705 df.addShapeToField(&big_table, p);
706 std::cout
707 << "Adding to unsigned took "
708 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
709 << " secs"
710 << " avg "
711 << (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() /
712 1000.0) /
713 (big_num_points * 1.0)
714 << '\n';
715
716 dt = std::chrono::system_clock::now();
717 df.addShapeToField(&big_table, p);
718 std::cout
719 << "Re-adding to unsigned took "
720 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
721 << " secs" << '\n';
722
723 dt = std::chrono::system_clock::now();
724 sdf.addShapeToField(&big_table, p);
725 std::cout
726 << "Adding to signed took "
727 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
728 << " secs"
729 << " avg "
730 << (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() /
731 1000.0) /
732 (big_num_points * 1.0)
733 << '\n';
734
735 dt = std::chrono::system_clock::now();
736 df.moveShapeInField(&big_table, p, np);
737 std::cout
738 << "Moving in unsigned took "
739 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
740 << " secs" << '\n';
741
742 dt = std::chrono::system_clock::now();
743 sdf.moveShapeInField(&big_table, p, np);
744 std::cout
745 << "Moving in signed took "
746 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
747 << " secs" << '\n';
748
749 dt = std::chrono::system_clock::now();
750 df.removeShapeFromField(&big_table, np);
751 std::cout
752 << "Removing from unsigned took "
753 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
754 << " secs" << '\n';
755
756 dt = std::chrono::system_clock::now();
757 sdf.removeShapeFromField(&big_table, np);
758 std::cout
759 << "Removing from signed took "
760 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
761 << " secs" << '\n';
762
763 dt = std::chrono::system_clock::now();
764 df.reset();
765
766 shapes::Box small_table(.25, .25, .05);
767
768 unsigned int small_num_points = (13) * (13) * (3);
769
770 std::cout << "Adding " << small_num_points << " points" << '\n';
771
772 dt = std::chrono::system_clock::now();
773 df.addShapeToField(&small_table, p);
774 std::cout
775 << "Adding to unsigned took "
776 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
777 << " secs"
778 << " avg " << (std::chrono::system_clock::now() - dt).count() / (small_num_points * 1.0) << '\n';
779
780 dt = std::chrono::system_clock::now();
781 sdf.addShapeToField(&small_table, p);
782 std::cout
783 << "Adding to signed took "
784 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
785 << " secs"
786 << " avg " << (std::chrono::system_clock::now() - dt).count() / (small_num_points * 1.0) << '\n';
787
788 dt = std::chrono::system_clock::now();
789 df.moveShapeInField(&small_table, p, np);
790 std::cout
791 << "Moving in unsigned took "
792 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
793 << " secs" << '\n';
794
795 dt = std::chrono::system_clock::now();
796 sdf.moveShapeInField(&small_table, p, np);
797 std::cout
798 << "Moving in signed took "
799 << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - dt).count() / 1000.0
800 << " secs" << '\n';
801
802 // uniformly spaced points - a worst case scenario
803 PropagationDistanceField worstdfu(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X, PERF_ORIGIN_Y,
804 PERF_ORIGIN_Z, PERF_MAX_DIST, false);
805
806 PropagationDistanceField worstdfs(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X, PERF_ORIGIN_Y,
807 PERF_ORIGIN_Z, PERF_MAX_DIST, true);
808
809 EigenSTL::vector_Vector3d bad_vec;
810 for (unsigned int z = UNIFORM_DISTANCE; z < worstdfu.getZNumCells() - UNIFORM_DISTANCE; z += UNIFORM_DISTANCE)
811 {
812 for (unsigned int x = UNIFORM_DISTANCE; x < worstdfu.getXNumCells() - UNIFORM_DISTANCE; x += UNIFORM_DISTANCE)
813 {
814 for (unsigned int y = UNIFORM_DISTANCE; y < worstdfu.getYNumCells() - UNIFORM_DISTANCE; y += UNIFORM_DISTANCE)
815 {
816 Eigen::Vector3d loc;
817 bool valid = worstdfu.gridToWorld(x, y, z, loc.x(), loc.y(), loc.z());
818
819 if (!valid)
820 {
821 continue;
822 }
823 bad_vec.push_back(loc);
824 }
825 }
826 }
827
828 dt = std::chrono::system_clock::now();
829 worstdfu.addPointsToField(bad_vec);
830 std::chrono::duration<double> wd = std::chrono::system_clock::now() - dt;
831 printf("Time for unsigned adding %u uniform points is %g average %g\n", static_cast<unsigned int>(bad_vec.size()),
832 wd.count(), wd.count() / (bad_vec.size() * 1.0));
833 dt = std::chrono::system_clock::now();
834 worstdfs.addPointsToField(bad_vec);
835 wd = std::chrono::system_clock::now() - dt;
836 printf("Time for signed adding %u uniform points is %g average %g\n", static_cast<unsigned int>(bad_vec.size()),
837 wd.count(), wd.count() / (bad_vec.size() * 1.0));
838}
839
840TEST(TestSignedPropagationDistanceField, TestOcTree)
841{
842 PropagationDistanceField df(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X, PERF_ORIGIN_Y,
843 PERF_ORIGIN_Z, PERF_MAX_DIST, false);
844
845 octomap::OcTree tree(.02);
846
847 unsigned int count = 0;
848 for (float x = 1.01; x < 1.5; x += .02)
849 {
850 for (float y = 1.01; y < 1.5; y += .02)
851 {
852 for (float z = 1.01; z < 1.5; z += .02, ++count)
853 {
854 octomap::point3d point(x, y, z);
855 tree.updateNode(point, true);
856 }
857 }
858 }
859
860 // more points at the border of the distance field
861 for (float x = 2.51; x < 3.5; x += .02)
862 {
863 for (float y = 1.01; y < 3.5; y += .02)
864 {
865 for (float z = 1.01; z < 3.5; z += .02, ++count)
866 {
867 octomap::point3d point(x, y, z);
868 tree.updateNode(point, true);
869 }
870 }
871 }
872
873 std::cout << "OcTree nodes " << count << '\n';
874 df.addOcTreeToField(&tree);
875
876 EXPECT_TRUE(checkOctomapVersusDistanceField(df, tree));
877
878 // more cells
879 for (float x = .01; x < .50; x += .02)
880 {
881 for (float y = .01; y < .50; y += .02)
882 {
883 for (float z = .01; z < .50; z += .02, ++count)
884 {
885 octomap::point3d point(x, y, z);
886 tree.updateNode(point, true);
887 }
888 }
889 }
890 df.addOcTreeToField(&tree);
891 EXPECT_TRUE(checkOctomapVersusDistanceField(df, tree));
892
893 PropagationDistanceField df_oct(tree, octomap::point3d(0.5, 0.5, 0.5), octomap::point3d(5.0, 5.0, 5.0), PERF_MAX_DIST,
894 false);
895
896 EXPECT_TRUE(checkOctomapVersusDistanceField(df_oct, tree));
897
898 // now try different resolutions
899 octomap::OcTree tree_lowres(.05);
900 octomap::point3d point1(.5, .5, .5);
901 octomap::point3d point2(.7, .5, .5);
902 octomap::point3d point3(1.0, .5, .5);
903 tree_lowres.updateNode(point1, true);
904 tree_lowres.updateNode(point2, true);
905 tree_lowres.updateNode(point3, true);
906 ASSERT_EQ(countLeafNodes(tree_lowres), 3u);
907
908 PropagationDistanceField df_highres(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X,
909 PERF_ORIGIN_Y, PERF_ORIGIN_Z, PERF_MAX_DIST, false);
910
911 df_highres.addOcTreeToField(&tree_lowres);
912 EXPECT_EQ(countOccupiedCells(df_highres), 3u * (4u * 4u * 4u));
913 std::cout << "Occupied cells " << countOccupiedCells(df_highres) << '\n';
914
915 // testing adding shape that happens to be octree
916 auto tree_shape = std::make_shared<octomap::OcTree>(.05);
917 octomap::point3d tpoint1(1.0, .5, 1.0);
918 octomap::point3d tpoint2(1.7, .5, .5);
919 octomap::point3d tpoint3(1.8, .5, .5);
920 tree_shape->updateNode(tpoint1, true);
921 tree_shape->updateNode(tpoint2, true);
922 tree_shape->updateNode(tpoint3, true);
923
924 auto shape_oc = std::make_shared<shapes::OcTree>(tree_shape);
925
926 PropagationDistanceField df_test_shape_1(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X,
927 PERF_ORIGIN_Y, PERF_ORIGIN_Z, PERF_MAX_DIST, false);
928
929 PropagationDistanceField df_test_shape_2(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X,
930 PERF_ORIGIN_Y, PERF_ORIGIN_Z, PERF_MAX_DIST, false);
931
932 df_test_shape_1.addOcTreeToField(tree_shape.get());
933 df_test_shape_2.addShapeToField(shape_oc.get(), Eigen::Isometry3d());
934 EXPECT_TRUE(areDistanceFieldsDistancesEqual(df_test_shape_1, df_test_shape_2));
935}
936
937TEST(TestSignedPropagationDistanceField, TestReadWrite)
938{
939 PropagationDistanceField small_df(WIDTH, HEIGHT, DEPTH, RESOLUTION, ORIGIN_X, ORIGIN_Y, ORIGIN_Z, MAX_DIST);
940
941 EigenSTL::vector_Vector3d points;
942 points.push_back(POINT1);
943 points.push_back(POINT2);
944 points.push_back(POINT3);
945 small_df.addPointsToField(points);
946
947 std::ofstream sf("test_small.df", std::ios::out);
948
949 small_df.writeToStream(sf);
950 // must close to make sure that the buffer is written
951 sf.close();
952
953 std::ifstream si("test_small.df", std::ios::in | std::ios::binary);
954 PropagationDistanceField small_df2(si, PERF_MAX_DIST, false);
955 ASSERT_TRUE(areDistanceFieldsDistancesEqual(small_df, small_df2));
956
957 PropagationDistanceField df(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X, PERF_ORIGIN_Y,
958 PERF_ORIGIN_Z, PERF_MAX_DIST, false);
959
960 shapes::Sphere sphere(.5);
961
962 Eigen::Isometry3d p = Eigen::Translation3d(0.5, 0.5, 0.5) * Eigen::Quaterniond(0.0, 0.0, 0.0, 1.0);
963
964 df.addShapeToField(&sphere, p);
965
966 std::ofstream f("test_big.df", std::ios::out);
967
968 df.writeToStream(f);
969 f.close();
970
971 std::ifstream i("test_big.df", std::ios::in);
972 PropagationDistanceField df2(i, PERF_MAX_DIST, false);
973 EXPECT_TRUE(areDistanceFieldsDistancesEqual(df, df2));
974
975 // we shouldn't segfault if we start with an old ifstream
976 PropagationDistanceField dfx(i, PERF_MAX_DIST, false);
977
978 std::ifstream i2("test_big.df", std::ios::in);
979 auto wt = std::chrono::system_clock::now();
980 PropagationDistanceField df3(i2, PERF_MAX_DIST + .02, false);
981 std::cout << "Reconstruction for big file took " << (std::chrono::system_clock::now() - wt).count() << '\n';
982 EXPECT_FALSE(areDistanceFieldsDistancesEqual(df, df3));
983}
984
985int main(int argc, char** argv)
986{
987 testing::InitGoogleTest(&argc, argv);
988 return RUN_ALL_TESTS();
989}
void moveShapeInField(const shapes::Shape *shape, const Eigen::Isometry3d &old_pose, const Eigen::Isometry3d &new_pose)
Moves the shape in the distance field from the old pose to the new pose, removing points that are no ...
void removeShapeFromField(const shapes::Shape *shape, const Eigen::Isometry3d &pose)
All points corresponding to the shape are removed from the distance field.
double getDistanceGradient(double x, double y, double z, double &gradient_x, double &gradient_y, double &gradient_z, bool &in_bounds) const
Gets not only the distance to the nearest cell but the gradient direction. The gradient is computed a...
void addOcTreeToField(const octomap::OcTree *octree)
Adds an octree to the distance field. Cells that are occupied in the octree that lie within the voxel...
double getResolution() const
Gets the resolution of the distance field in meters.
void addShapeToField(const shapes::Shape *shape, const Eigen::Isometry3d &pose)
Adds the set of points corresponding to the shape at the given pose as obstacle points into the dista...
A DistanceField implementation that uses a vector propagation method. Distances propagate outward fro...
void addPointsToField(const EigenSTL::vector_Vector3d &points) override
Add a set of obstacle points to the distance field, updating distance values accordingly....
void reset() override
Resets the entire distance field to max_distance for positive values and zero for negative values.
int getXNumCells() const override
Gets the number of cells along the X axis.
const PropDistanceFieldVoxel & getCell(int x, int y, int z) const
Gets full cell data given an index.
int getZNumCells() const override
Gets the number of cells along the Z axis.
bool worldToGrid(double world_x, double world_y, double world_z, int &x, int &y, int &z) const override
Converts from a world location to a set of integer indices. Should return false if the world location...
void removePointsFromField(const EigenSTL::vector_Vector3d &points) override
Remove a set of obstacle points from the distance field, updating distance values accordingly.
double getUninitializedDistance() const override
Gets a distance value for an invalid cell.
double getDistance(double x, double y, double z) const override
Get the distance value associated with the cell indicated by the world coordinate....
int getYNumCells() const override
Gets the number of cells along the Y axis.
void updatePointsInField(const EigenSTL::vector_Vector3d &old_points, const EigenSTL::vector_Vector3d &new_points) override
This function will remove any obstacle points that are in the old point set but not the new point set...
bool writeToStream(std::ostream &stream) const override
Writes the contents of the distance field to the supplied stream.
bool gridToWorld(int x, int y, int z, double &world_x, double &world_y, double &world_z) const override
Converts from an set of integer indices to a world location given the origin and resolution parameter...
const PropDistanceFieldVoxel * getNearestCell(int x, int y, int z, double &dist, Eigen::Vector3i &pos) const
Gets nearest surface cell and returns distance to it.
std::vector< Eigen::Vector3i, Eigen::aligned_allocator< Eigen::Vector3i > > vector_Vector3i
Namespace for holding classes that generate distance fields.
void findInternalPointsConvex(const bodies::Body &body, double resolution, EigenSTL::vector_Vector3d &points)
Find all points on a regular grid that are internal to the body, assuming the body is a convex shape....
Structure that holds voxel information for the DistanceField. Will be used in VoxelGrid.
Eigen::Vector3i closest_point_
Closest occupied cell.
Eigen::Vector3i closest_negative_point_
Closest unoccupied cell.
int negative_distance_square_
Distance in cells to the nearest unoccupied cell, squared.
int distance_square_
Distance in cells to the closest obstacle, squared.
unsigned int countLeafNodes(const octomap::OcTree &octree)
void print(PropagationDistanceField &pdf, int numX, int numY, int numZ)
int main(int argc, char **argv)
void printPointCoords(const Eigen::Vector3i &p)
bool areDistanceFieldsDistancesEqual(const PropagationDistanceField &df1, const PropagationDistanceField &df2)
void printBoth(PropagationDistanceField &pdf, int numX, int numY, int numZ)
void checkDistanceField(const PropagationDistanceField &df, const EigenSTL::vector_Vector3d &points, int numX, int numY, int numZ, bool do_negs)
bool checkOctomapVersusDistanceField(const PropagationDistanceField &df, const octomap::OcTree &octree)
void printNeg(PropagationDistanceField &pdf, int numX, int numY, int numZ)
int distanceSequence(int x, int y, int z)
TEST(TestPropagationDistanceField, TestAddRemovePoints)
unsigned int countOccupiedCells(const PropagationDistanceField &df)