moveit2
The MoveIt Motion Planning Framework for ROS 2.
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 
47 using namespace distance_field;
48 
49 static const double WIDTH = 1.0;
50 static const double HEIGHT = 1.0;
51 static const double DEPTH = 1.0;
52 static const double RESOLUTION = 0.1;
53 static const double ORIGIN_X = 0.0;
54 static const double ORIGIN_Y = 0.0;
55 static const double ORIGIN_Z = 0.0;
56 static const double MAX_DIST = 0.3;
57 
58 static const Eigen::Vector3d POINT1(0.1, 0.0, 0.0);
59 static const Eigen::Vector3d POINT2(0.0, 0.1, 0.2);
60 static const Eigen::Vector3d POINT3(0.4, 0.0, 0.0);
61 
62 int distanceSequence(int x, int y, int z)
63 {
64  return x * x + y * y + z * z;
65 }
66 
67 void 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 
96 void 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 
112 void 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 
144 void 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  }
197  if (df1.getCell(x, y, z).negative_distance_square_ != df2.getCell(x, y, z).negative_distance_square_)
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 
209 bool 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 
283 unsigned 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
298 void 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 
339 TEST(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 
441 TEST(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 
610 TEST(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 
660 static const double PERF_WIDTH = 3.0;
661 static const double PERF_HEIGHT = 3.0;
662 static const double PERF_DEPTH = 4.0;
663 static const double PERF_RESOLUTION = 0.02;
664 static const double PERF_ORIGIN_X = 0.0;
665 static const double PERF_ORIGIN_Y = 0.0;
666 static const double PERF_ORIGIN_Z = 0.0;
667 static const double PERF_MAX_DIST = .25;
668 static const unsigned int UNIFORM_DISTANCE = 10;
669 
670 TEST(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  unsigned int count = 0;
811  for (unsigned int z = UNIFORM_DISTANCE; z < worstdfu.getZNumCells() - UNIFORM_DISTANCE; z += UNIFORM_DISTANCE)
812  {
813  for (unsigned int x = UNIFORM_DISTANCE; x < worstdfu.getXNumCells() - UNIFORM_DISTANCE; x += UNIFORM_DISTANCE)
814  {
815  for (unsigned int y = UNIFORM_DISTANCE; y < worstdfu.getYNumCells() - UNIFORM_DISTANCE; y += UNIFORM_DISTANCE)
816  {
817  count++;
818  Eigen::Vector3d loc;
819  bool valid = worstdfu.gridToWorld(x, y, z, loc.x(), loc.y(), loc.z());
820 
821  if (!valid)
822  {
823  // RCLCPP_WARN("distance_field", "Something wrong");
824  continue;
825  }
826  bad_vec.push_back(loc);
827  }
828  }
829  }
830 
831  dt = std::chrono::system_clock::now();
832  worstdfu.addPointsToField(bad_vec);
833  std::chrono::duration<double> wd = std::chrono::system_clock::now() - dt;
834  printf("Time for unsigned adding %u uniform points is %g average %g\n", static_cast<unsigned int>(bad_vec.size()),
835  wd.count(), wd.count() / (bad_vec.size() * 1.0));
836  dt = std::chrono::system_clock::now();
837  worstdfs.addPointsToField(bad_vec);
838  wd = std::chrono::system_clock::now() - dt;
839  printf("Time for signed adding %u uniform points is %g average %g\n", static_cast<unsigned int>(bad_vec.size()),
840  wd.count(), wd.count() / (bad_vec.size() * 1.0));
841 }
842 
843 TEST(TestSignedPropagationDistanceField, TestOcTree)
844 {
845  PropagationDistanceField df(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X, PERF_ORIGIN_Y,
846  PERF_ORIGIN_Z, PERF_MAX_DIST, false);
847 
848  octomap::OcTree tree(.02);
849 
850  unsigned int count = 0;
851  for (float x = 1.01; x < 1.5; x += .02)
852  {
853  for (float y = 1.01; y < 1.5; y += .02)
854  {
855  for (float z = 1.01; z < 1.5; z += .02, ++count)
856  {
857  octomap::point3d point(x, y, z);
858  tree.updateNode(point, true);
859  }
860  }
861  }
862 
863  // more points at the border of the distance field
864  for (float x = 2.51; x < 3.5; x += .02)
865  {
866  for (float y = 1.01; y < 3.5; y += .02)
867  {
868  for (float z = 1.01; z < 3.5; z += .02, ++count)
869  {
870  octomap::point3d point(x, y, z);
871  tree.updateNode(point, true);
872  }
873  }
874  }
875 
876  std::cout << "OcTree nodes " << count << '\n';
877  df.addOcTreeToField(&tree);
878 
879  EXPECT_TRUE(checkOctomapVersusDistanceField(df, tree));
880 
881  // more cells
882  for (float x = .01; x < .50; x += .02)
883  {
884  for (float y = .01; y < .50; y += .02)
885  {
886  for (float z = .01; z < .50; z += .02, ++count)
887  {
888  octomap::point3d point(x, y, z);
889  tree.updateNode(point, true);
890  }
891  }
892  }
893  df.addOcTreeToField(&tree);
894  EXPECT_TRUE(checkOctomapVersusDistanceField(df, tree));
895 
896  PropagationDistanceField df_oct(tree, octomap::point3d(0.5, 0.5, 0.5), octomap::point3d(5.0, 5.0, 5.0), PERF_MAX_DIST,
897  false);
898 
899  EXPECT_TRUE(checkOctomapVersusDistanceField(df_oct, tree));
900 
901  // now try different resolutions
902  octomap::OcTree tree_lowres(.05);
903  octomap::point3d point1(.5, .5, .5);
904  octomap::point3d point2(.7, .5, .5);
905  octomap::point3d point3(1.0, .5, .5);
906  tree_lowres.updateNode(point1, true);
907  tree_lowres.updateNode(point2, true);
908  tree_lowres.updateNode(point3, true);
909  ASSERT_EQ(countLeafNodes(tree_lowres), 3u);
910 
911  PropagationDistanceField df_highres(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X,
912  PERF_ORIGIN_Y, PERF_ORIGIN_Z, PERF_MAX_DIST, false);
913 
914  df_highres.addOcTreeToField(&tree_lowres);
915  EXPECT_EQ(countOccupiedCells(df_highres), 3u * (4u * 4u * 4u));
916  std::cout << "Occupied cells " << countOccupiedCells(df_highres) << '\n';
917 
918  // testing adding shape that happens to be octree
919  auto tree_shape = std::make_shared<octomap::OcTree>(.05);
920  octomap::point3d tpoint1(1.0, .5, 1.0);
921  octomap::point3d tpoint2(1.7, .5, .5);
922  octomap::point3d tpoint3(1.8, .5, .5);
923  tree_shape->updateNode(tpoint1, true);
924  tree_shape->updateNode(tpoint2, true);
925  tree_shape->updateNode(tpoint3, true);
926 
927  auto shape_oc = std::make_shared<shapes::OcTree>(tree_shape);
928 
929  PropagationDistanceField df_test_shape_1(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X,
930  PERF_ORIGIN_Y, PERF_ORIGIN_Z, PERF_MAX_DIST, false);
931 
932  PropagationDistanceField df_test_shape_2(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X,
933  PERF_ORIGIN_Y, PERF_ORIGIN_Z, PERF_MAX_DIST, false);
934 
935  df_test_shape_1.addOcTreeToField(tree_shape.get());
936  df_test_shape_2.addShapeToField(shape_oc.get(), Eigen::Isometry3d());
937  EXPECT_TRUE(areDistanceFieldsDistancesEqual(df_test_shape_1, df_test_shape_2));
938 }
939 
940 TEST(TestSignedPropagationDistanceField, TestReadWrite)
941 {
942  PropagationDistanceField small_df(WIDTH, HEIGHT, DEPTH, RESOLUTION, ORIGIN_X, ORIGIN_Y, ORIGIN_Z, MAX_DIST);
943 
944  EigenSTL::vector_Vector3d points;
945  points.push_back(POINT1);
946  points.push_back(POINT2);
947  points.push_back(POINT3);
948  small_df.addPointsToField(points);
949 
950  std::ofstream sf("test_small.df", std::ios::out);
951 
952  small_df.writeToStream(sf);
953  // must close to make sure that the buffer is written
954  sf.close();
955 
956  std::ifstream si("test_small.df", std::ios::in | std::ios::binary);
957  PropagationDistanceField small_df2(si, PERF_MAX_DIST, false);
958  ASSERT_TRUE(areDistanceFieldsDistancesEqual(small_df, small_df2));
959 
960  PropagationDistanceField df(PERF_WIDTH, PERF_HEIGHT, PERF_DEPTH, PERF_RESOLUTION, PERF_ORIGIN_X, PERF_ORIGIN_Y,
961  PERF_ORIGIN_Z, PERF_MAX_DIST, false);
962 
963  shapes::Sphere sphere(.5);
964 
965  Eigen::Isometry3d p = Eigen::Translation3d(0.5, 0.5, 0.5) * Eigen::Quaterniond(0.0, 0.0, 0.0, 1.0);
966 
967  df.addShapeToField(&sphere, p);
968 
969  std::ofstream f("test_big.df", std::ios::out);
970 
971  df.writeToStream(f);
972  f.close();
973 
974  std::ifstream i("test_big.df", std::ios::in);
975  PropagationDistanceField df2(i, PERF_MAX_DIST, false);
976  EXPECT_TRUE(areDistanceFieldsDistancesEqual(df, df2));
977 
978  // we shouldn't segfault if we start with an old ifstream
979  PropagationDistanceField dfx(i, PERF_MAX_DIST, false);
980 
981  std::ifstream i2("test_big.df", std::ios::in);
982  auto wt = std::chrono::system_clock::now();
983  PropagationDistanceField df3(i2, PERF_MAX_DIST + .02, false);
984  std::cout << "Reconstruction for big file took " << (std::chrono::system_clock::now() - wt).count() << '\n';
985  EXPECT_FALSE(areDistanceFieldsDistancesEqual(df, df3));
986 }
987 
988 int main(int argc, char** argv)
989 {
990  testing::InitGoogleTest(&argc, argv);
991  return RUN_ALL_TESTS();
992 }
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.
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.
const PropDistanceFieldVoxel & getCell(int x, int y, int z) const
Gets full cell data given an index.
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.
const PropDistanceFieldVoxel * getNearestCell(int x, int y, int z, double &dist, Eigen::Vector3i &pos) const
Gets nearest surface cell and returns distance to it.
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...
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....
Vec3fX< details::Vec3Data< double > > Vector3d
Definition: fcl_compat.h:89
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)