Costmap3DBuilder.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @author Niklas Arlt ( niklas dot arlt at kit dot edu )
17 * @date 2025
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22// Most of the code is taken from algorithms/CostmapBuilder.cpp
23
24#include "Costmap3DBuilder.h"
25
26#include <algorithm>
27#include <cstddef>
28#include <functional>
29#include <optional>
30#include <string>
31#include <vector>
32
33#include <VirtualRobot/CollisionDetection/CollisionModel.h>
34#include <VirtualRobot/Nodes/RobotNode.h> // IWYU pragma: keep
35#include <VirtualRobot/Robot.h> // IWYU pragma: keep
36#include <VirtualRobot/SceneObjectSet.h>
37#include <VirtualRobot/VirtualRobot.h>
38
45
52
54{
55
56
58 const VirtualRobot::RobotPtr& robot,
59 const VirtualRobot::SceneObjectSetPtr& obstacles,
60 const std::vector<VirtualRobot::RobotPtr>& articulatedObjects,
61 const std::vector<Room>& rooms,
62 const Costmap3D::Parameters& parameters,
63 const std::string& robotCollisonModelName,
64 const Costmap3DBuilderParams& builderParameters) :
65 robot(robot),
66 obstacles(obstacles),
67 articulatedObjects(articulatedObjects),
68 rooms(rooms),
69 parameters(parameters),
70 robotCollisionModelName(robotCollisonModelName),
71 builderParameters(builderParameters)
72 {
73 ARMARX_CHECK_NOT_NULL(robot) << "Robot must be set";
74 ARMARX_CHECK_NOT_NULL(obstacles);
75
76 if (not robotCollisonModelName.empty())
77 {
78 ARMARX_CHECK(robot->hasRobotNode(robotCollisionModelName)) << robotCollisionModelName;
79 }
80
81 if (not parameters.robotModel.rootNode.empty())
82 {
83 ARMARX_INFO << "Using alternative root node " << QUOTED(parameters.robotModel.rootNode);
84 auto node = robot->getRobotNode(parameters.robotModel.rootNode);
85 ARMARX_CHECK_NOT_NULL(node) << parameters.robotModel.rootNode;
86
87 const Eigen::Isometry3f root_T_used_root(node->getPoseInRootFrame());
88
89 // Obtain 2D transform. We make some strong assumptions that need to hold here.
90 ARMARX_CHECK_LESS(std::abs(root_T_used_root.translation().z()), 1)
91 << "Nodes must be at the same hight.";
92
93 const Eigen::Matrix3f R = root_T_used_root.linear();
94
95 // both the third row and column should be [0,0,1]
97 const float a = Eigen::Vector3f{R.row(2)}.dot(Eigen::Vector3f::UnitZ());
99 const float b = Eigen::Vector3f{R.col(2)}.dot(Eigen::Vector3f::UnitZ());
100
101 ARMARX_CHECK_CLOSE(a, 1, 0.01);
102 ARMARX_CHECK_CLOSE(b, 1, 0.01);
103
104 // Now we know that the nodes are in a plane
105
106 ARMARX_INFO << "Alternative root node is valid.";
107
108 root_T_used_root_2d = Eigen::Isometry2f::Identity();
110 root_T_used_root_2d.translation() = root_T_used_root.translation().head<2>();
112 root_T_used_root_2d.linear() = R.topLeftCorner<2, 2>();
113 // TODO ensure valid rotation matrix (cross product.)
114
116
117 // FIXME maybe we have to invert root_T_used_root_2d based on its usage
118 root_T_used_root_2d = root_T_used_root_2d.inverse();
119
120 ARMARX_INFO << "Root transform: " << root_T_used_root_2d.matrix();
121 }
122
123 // Room selection
124 const auto& enabledRooms = this->builderParameters.roomEnableList;
125 ARMARX_INFO << "Rooms:";
126 for (const auto& room : this->rooms)
127 {
128 ARMARX_INFO << " - " << room.name;
129 }
130 ARMARX_INFO << "";
131
132 if (not enabledRooms.empty())
133 {
134 // if roomEnableList is specified, remove all other rooms from the provided list
135 this->rooms.erase(std::remove_if(this->rooms.begin(),
136 this->rooms.end(),
137 [&enabledRooms](const Room& room)
138 {
139 const bool roomIncluded =
140 std::find(enabledRooms.cbegin(),
141 enabledRooms.cend(),
142 room.name) != enabledRooms.cend();
143 if (not roomIncluded)
144 {
145 ARMARX_VERBOSE
146 << "Room " << room.name
147 << " was found but is not included";
148 }
149 return not roomIncluded;
150 }),
151 this->rooms.end());
152 }
153
154 if (builderParameters.restrictToRooms)
155 {
157 << "No rooms enabled, even though restrict to rooms is true";
158 }
159 }
160
161 Costmap3D
163 {
165 [](const armarx::core::time::Duration& duration)
166 { ARMARX_INFO << "Creating Costmap3D took " << duration; });
167
168 // We do not restrict the costmap to rooms here. Instead, we only update the costmap
169 // inside the rooms below (if this is requested).
170
171 std::optional<Costmap3D> inflatedCostmap;
172 {
174 [](const armarx::core::time::Duration& duration)
175 { ARMARX_INFO << "Filling Costmap3D took " << duration; });
176
177 ARMARX_VERBOSE << "Filling grid with size (" << costmap.getGrid().rows() << "/"
178 << costmap.getGrid().cols() << ") and resolution "
179 << costmap.params().cellSize;
180
181 inflatedCostmap.emplace(inflateRotationDimension(costmap));
182
183 if (builderParameters.restrictToRooms)
184 {
185 ARMARX_INFO << "Restricting to rooms";
186 extendGridCosts(*inflatedCostmap, rooms);
187 }
188 else
189 {
190 extendGridCosts(*inflatedCostmap, {});
191 }
192
193 ARMARX_VERBOSE << "Filled grid";
194 }
195
196 initializeMask(inflatedCostmap.value());
197 ARMARX_VERBOSE << "Initialized mask";
198
199 return inflatedCostmap.value();
200 }
201
204 {
206 [](const armarx::core::time::Duration& duration)
207 { ARMARX_INFO << "Creating Costmap3D took " << duration; });
208
209 ARMARX_INFO << articulatedObjects.size() << " articulated objects";
210 ARMARX_INFO << obstacles->getSize() << " objects";
211
212 const auto sceneBounds = computeSceneBounds(obstacles,
213 articulatedObjects,
214 init,
215 rooms,
216 parameters.sceneBoundsMargin,
217 builderParameters.restrictToRooms);
218 const auto grid = createUniformGrid(sceneBounds, parameters);
219
220 ARMARX_VERBOSE << "Creating grid";
221 Costmap3D costmap(grid, parameters, sceneBounds);
222
223 if (builderParameters.restrictToRooms)
224 {
225 initializeEmptyMask(costmap);
226
228 ARMARX_VERBOSE << "Restricting to rooms:";
229 for (const auto& room : rooms)
230 {
231 ARMARX_VERBOSE << " - " << room.name;
232 }
233
235
236 ARMARX_VERBOSE << "Restricted to rooms. Fraction of valid elements: "
237 << costmap.mask->cast<float>().sum() / costmap.mask->size();
238 }
239
240 {
242 [](const armarx::core::time::Duration& duration)
243 { ARMARX_INFO << "Filling Costmap3D took " << duration; });
244
245 ARMARX_VERBOSE << "Filling grid with size (" << costmap.getGrid()[0].rows() << "/"
246 << costmap.getGrid()[0].cols() << "/rot" << costmap.params().orientations
247 << ") and resolution " << costmap.params().cellSize;
248 fillGridCosts(costmap);
249 ARMARX_VERBOSE << "Filled grid";
250 }
251
252 // Mask is broken for now
253 //initializeMask(costmap);
254 ARMARX_VERBOSE << "Initialized mask";
255
256 return costmap;
257 }
258
259 void
261 {
262 ARMARX_CHECK(costmap.grid.size() > 0);
263
264 costmap.mask = costmap.grid[0].array() > 0.F;
265
266 ARMARX_VERBOSE << "Initializing mask: Fraction of valid elements: "
267 << costmap.mask->cast<float>().sum() / costmap.mask->size();
268 }
269
270 std::optional<Costmap3DBuilder::DebugOutput2D>
272 {
273 return debugOutput2D;
274 }
275
276 void
277 Costmap3DBuilder::initializeEmptyMask(Costmap3D& costmap)
278 {
279 ARMARX_CHECK(costmap.grid.size() > 0);
280
281 costmap.mask = costmap.grid[0].array() >= -42.F; // FIXME: magic number, just same size
282 costmap.mask->setOnes();
283
284 ARMARX_VERBOSE << "Initializing empty mask: Fraction of valid elements: "
285 << costmap.mask->cast<float>().sum() / costmap.mask->size();
286 }
287
288 Costmap3D::Grid
290 const Costmap3D::Parameters& parameters)
291 {
292 Costmap3D::Grid grid;
293 grid.reserve(parameters.orientations);
294 for (int i = 0; i < parameters.orientations; ++i)
295 {
296 grid.push_back(createUniformGridSingleOrientation(sceneBounds, parameters));
297 }
298
299 return grid;
300 }
301
302 Eigen::MatrixXf
304 const Costmap3D::Parameters& parameters)
305 {
307
308 ARMARX_VERBOSE << "Scene bounds are " << sceneBounds.min << " and " << sceneBounds.max;
309
310 //+1 for explicit rounding up
311 size_t c_x = (sceneBounds.max.x() - sceneBounds.min.x()) / parameters.cellSize + 1;
312 size_t c_y = (sceneBounds.max.y() - sceneBounds.min.y()) / parameters.cellSize + 1;
313
314 ARMARX_VERBOSE << "Grid size: " << c_x << ", " << c_y;
315
316 ARMARX_VERBOSE << "Resetting grid";
317 Eigen::MatrixXf grid(c_x, c_y);
318 grid.setZero();
319
320 return grid;
321 }
322
323 float
324 Costmap3DBuilder::computeCost(const core::Pose2D& global_T_root,
325 const Robot2D& robot2d,
326 const Scene2D& scene)
327 {
328 const core::Pose2D global_T_used_root = global_T_root * root_T_used_root_2d;
329
330 const auto polygon = robot2d.getRobotAtPose(global_T_used_root);
331 return scene.distance(polygon);
332 }
333
334 Costmap3D
335 Costmap3DBuilder::inflateRotationDimension(Costmap& costmap)
336 {
337 // TODO(niklas): For efficient implementation: Use a wrapper with reference to old costmap and don't allocate anything
338
339 std::vector<Costmap::Grid> new_grid;
340 new_grid.reserve(parameters.orientations);
341 for (int i = 0; i < parameters.orientations; ++i)
342 {
343 new_grid.push_back(costmap.getGrid());
344 }
345
346 Costmap3D costmap3d{new_grid,
347 parameters,
348 costmap.getLocalSceneBounds(),
349 costmap.getMask(),
350 costmap.origin()};
351 return costmap3d;
352 }
353
354 void
355 Costmap3DBuilder::fillGridCosts2D(Costmap3D& costmap)
356 {
357 filteredObstacles = filterObjectsForCostmap(costmap);
358 const auto fn = [&](const MapStructRobot2D& m) -> float
359 {
360 const auto degrees = costmap.rotationFromIndex(m.c.rotIdx).degrees;
361 const auto pose = costmap.globalPose(m.c.posIdx, degrees);
362 ARMARX_CHECK(costmap.rotationDegrees(pose) - degrees < 0.001)
363 << "Rotation degrees do not match: " << costmap.rotationDegrees(pose)
364 << " != " << degrees;
365 if (m.c.isMaskedOut())
366 {
367 return 0.F;
368 }
369 float distance = computeCost(pose, m.robot, m.scene);
370 if (distance < builderParameters.obstacleSafetyMargin)
371 {
372 distance = 0.F;
373 }
374 return distance;
375 };
376
377 applyFnToCostmap(costmap, fn);
378 }
379
380 void
381 Costmap3DBuilder::fillGridCosts(Costmap3D& costmap)
382 {
383 filteredObstacles = filterObjectsForCostmap(costmap);
384 ARMARX_INFO << VAROUT(builderParameters.obstacleSafetyMargin);
385 if (builderParameters.approximation2D)
386 {
387 fillGridCosts2D(costmap);
388 return;
389 }
390
391 const auto fn = [&](const MapStruct& m) -> float
392 {
393 const auto pose =
394 costmap.globalPose(m.c.posIdx, costmap.rotationFromIndex(m.c.rotIdx).degrees);
395 if (m.c.isMaskedOut())
396 {
397 return 0.F;
398 }
399 float distance = m.distanceCalculator.distanceToObstacles(pose);
400 if (distance < builderParameters.obstacleSafetyMargin)
401 {
402 distance = 0.F;
403 }
404 return distance;
405 };
406
407 applyFnToCostmap(costmap, fn);
408 }
409
410 void
411 Costmap3DBuilder::extendGridCosts(Costmap3D& costmap, const std::vector<Room>& rooms)
412 {
413 // Note: Currently not implemented
414 // How can we create a 2D collision model footprint to check collisions in the costmap?
415 }
416
417 void
418 Costmap3DBuilder::applyFnToCostmap(Costmap3D& costmap, const MapFn& fn)
419 {
420 SceneRepresentation sceneRepresentation{.staticObjects = filteredObstacles,
421 .articulatedObjects = articulatedObjects};
422 DistanceCalculator distanceCalculator{sceneRepresentation};
423
424 if (costmap.mask.has_value())
425 {
426 ARMARX_VERBOSE << "Costmap3D provides mask.";
427 }
428
429 ARMARX_CHECK(costmap.grid.size() > 0);
430 const std::size_t c_x = costmap.grid[0].rows();
431 const std::size_t c_y = costmap.grid[0].cols();
432
433 robot->setUpdateVisualization(false);
434
435#pragma omp parallel for schedule(static) firstprivate(distanceCalculator) default(shared)
436 for (unsigned int x = 0; x < c_x; x++)
437 {
438 // initialize if needed
439 if (not distanceCalculator.isRobotInitialized())
440 {
441#pragma omp critical
442 distanceCalculator.initializeRobot(
443 robot,
444 Robot3D::Params{.collisionModelName = robotCollisionModelName,
445 .scaleFactor = builderParameters.collisionModelScaleFactor,
446 .primitiveApproximationIDs =
447 builderParameters.primitiveApproximationIDs,
448 .rootNode = parameters.robotModel.rootNode});
449 }
450 ARMARX_CHECK(distanceCalculator.isRobotInitialized());
451
452 for (unsigned int y = 0; y < c_y; y++)
453 {
454 for (int rot_idx = 0; rot_idx < parameters.orientations; rot_idx++)
455 {
456 const Costmap3D::Index index{x, y};
457
458 const auto maskVal = costmap.mask.has_value()
459 ? std::make_optional(costmap.mask.value()(x, y))
460 : std::nullopt;
461
462 const MapStruct m{
463 .c{
464 .maskOpt = maskVal,
465 .cost = costmap.grid[rot_idx](x, y),
466 .posIdx = index,
467 .rotIdx = rot_idx,
468 },
469 .distanceCalculator = distanceCalculator,
470 };
471 costmap.grid[rot_idx](x, y) = fn(m);
472 }
473 }
474 }
475 }
476
477 void
478 Costmap3DBuilder::applyFnToCostmap(Costmap3D& costmap, const MapFnRobot2D& fn)
479 {
480 Robot3D distanceRobot = Robot3D::fromSimoxRobot(
481 robot,
482 Robot3D::Params{.collisionModelName = robotCollisionModelName,
483 .scaleFactor = builderParameters.collisionModelScaleFactor,
484 .primitiveApproximationIDs =
485 builderParameters.primitiveApproximationIDs,
486 .rootNode = costmap.parameters.robotModel.rootNode});
487 ARMARX_CHECK(distanceRobot.isInitialized())
488 << "Robot3D is not initialized. Make sure to call initializeRobot() before using it!";
489 Robot2D robot2d{distanceRobot.robot, distanceRobot.collisionModels};
490
491 SceneRepresentation scene3d{
492 .staticObjects = filteredObstacles,
493 .articulatedObjects = articulatedObjects,
494 };
495 Scene2D scene2d{scene3d, builderParameters.approx2DignoreVerticesOver};
496
497 this->debugOutput2D = {
498 .robot = robot2d, .scene = scene2d, .root_T_used_root_2d = root_T_used_root_2d};
499
500 if (costmap.mask.has_value())
501 {
502 ARMARX_VERBOSE << "Costmap3D provides mask.";
503 }
504
505 ARMARX_CHECK(costmap.grid.size() > 0);
506 const std::size_t c_x = costmap.grid[0].rows();
507 const std::size_t c_y = costmap.grid[0].cols();
508
509 robot->setUpdateVisualization(false);
510
511#pragma omp parallel for schedule(static) shared(robot2d) default(shared)
512 for (unsigned int x = 0; x < c_x; x++)
513 {
514 for (unsigned int y = 0; y < c_y; y++)
515 {
516 for (int rot_idx = 0; rot_idx < parameters.orientations; rot_idx++)
517 {
518 const Costmap3D::Index index{x, y};
519
520 const auto maskVal = costmap.mask.has_value()
521 ? std::make_optional(costmap.mask.value()(x, y))
522 : std::nullopt;
523
524 const MapStructRobot2D m{
525 .c{
526 .maskOpt = maskVal,
527 .cost = costmap.grid[rot_idx](x, y),
528 .posIdx = index,
529 .rotIdx = rot_idx,
530 },
531 .robot = robot2d,
532 .scene = scene2d,
533 };
534 costmap.grid[rot_idx](x, y) = fn(m);
535 }
536 }
537 }
538 }
539
540 VirtualRobot::SceneObjectSetPtr
541 Costmap3DBuilder::filterObjectsForCostmap(const Costmap3D& costmap)
542 {
543 armarx::core::time::ScopedStopWatch sw(
544 [](const armarx::core::time::Duration& duration)
545 { ARMARX_INFO << "Filtering objects took " << duration; });
546
547 VirtualRobot::SceneObjectSetPtr filtered(new VirtualRobot::SceneObjectSet);
548
550 using Box = boost::geometry::model::box<Point>;
552
553 const auto toPoint = [](const Eigen::Vector3f& vec) { return Point(vec.x(), vec.y()); };
554 const auto toBox = [&toPoint](const VirtualRobot::BoundingBox& bb)
555 { return Box(toPoint(bb.getMin()), toPoint(bb.getMax())); };
556
557 Polygon costmapBB;
558 {
559 ARMARX_CHECK(costmap.grid.size() > 0);
560 Polygon cornerPoints = util::geometry::toPolygon(std::vector<Eigen::Vector2f>{
561 costmap.toPositionGlobal(Costmap::Index(0, 0)),
562 costmap.toPositionGlobal(Costmap::Index(0, costmap.grid[0].cols() - 1)),
563 costmap.toPositionGlobal(Costmap::Index(costmap.grid[0].rows() - 1, 0)),
564 costmap.toPositionGlobal(
565 Costmap::Index(costmap.grid[0].rows() - 1, costmap.grid[0].cols() - 1))});
566 boost::geometry::convex_hull(cornerPoints, costmapBB);
567 }
568
569
570 for (const auto& object : this->obstacles->getSceneObjects())
571 {
572 const auto& colModel = object->getCollisionModel();
573
574 if (colModel)
575 {
576 Box bb = toBox(colModel->getGlobalBoundingBox());
577
578 // if bb of object is disjoint to bb of costmap we check the distance between them
579 // otherwise, we always include the object
580 if (boost::geometry::disjoint(costmapBB, bb))
581 {
582 float distance = boost::geometry::distance(costmapBB, bb);
583 if (distance > builderParameters.maxFilterDistance)
584 {
585 // if the distance is greater than configured, we skip this object
586 continue;
587 }
588 }
589
590 filtered->addSceneObject(object);
591 }
592 }
593 const auto prevSize = this->obstacles->getSize();
594 const auto filteredSize = filtered->getSize();
595
596 ARMARX_INFO << "Remaining objects for costmap calculation: " << filteredSize << "/"
597 << prevSize << " (" << filteredSize * 100 / static_cast<float>(prevSize) << "%)";
598
599 for (const auto& object : filtered->getSceneObjects())
600 {
601 ARMARX_VERBOSE << "- " << object->getName();
602 }
603
604
605 return filtered;
606 }
607
608} // namespace armarx::navigation::algorithms::orientation_aware
uint8_t index
#define ARMARX_CHECK_NOT_EMPTY(c)
#define VAROUT(x)
#define QUOTED(x)
Represents a duration.
Definition Duration.h:17
Measures the time this stop watch was inside the current scope.
const core::Pose2D & origin() const
Definition Costmap.h:171
const SceneBounds & getLocalSceneBounds() const noexcept
Definition Costmap.cpp:165
const std::optional< Mask > & getMask() const noexcept
Definition Costmap.cpp:529
static Eigen::MatrixXf createUniformGridSingleOrientation(const SceneBounds &sceneBounds, const Costmap3D::Parameters &parameters)
Costmap3D create(const SceneBounds &init=SceneBounds())
Costmap3DBuilder(const VirtualRobot::RobotPtr &robot, const VirtualRobot::SceneObjectSetPtr &obstacles, const std::vector< VirtualRobot::RobotPtr > &articulatedObjects, const std::vector< Room > &rooms, const Costmap3D::Parameters &parameters, const std::string &robotCollisonModelName, const Costmap3DBuilderParams &builderParameters)
static Costmap3D::Grid createUniformGrid(const SceneBounds &sceneBounds, const Costmap3D::Parameters &parameters)
CollisionPolygon getRobotAtPose(const core::Pose2D &pose) const
#define ARMARX_CHECK_CLOSE(lhs, rhs, prec)
Check whether lhs is close to rhs, i.e.
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_LESS(lhs, rhs)
This macro evaluates whether lhs is less (<) than rhs and if it turns out to be false it will throw a...
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
SceneBounds computeSceneBounds(const VirtualRobot::SceneObjectSetPtr &obstacles, const std::vector< VirtualRobot::RobotPtr > &articulatedObjects, const SceneBounds &init, const std::vector< Room > &rooms, const float margin, const bool restrictToRooms)
Definition util.cpp:62
void invalidateOutsideRooms(const std::vector< Room > &rooms, Costmap &costmap)
Definition util.cpp:493
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
boost::geometry::model::d2::point_xy< float > point_type
Definition geometry.h:35
point_type toPoint(const Eigen::Vector2f &pt)
Definition geometry.cpp:43
polygon_type toPolygon(const std::vector< Eigen::Vector2f > &hull)
Definition geometry.cpp:31
boost::geometry::model::polygon< point_type > polygon_type
Definition geometry.h:36
objpose::ObjectPoseSeq articulatedObjects(objpose::ObjectPoseSeq objects)
Definition util.cpp:94
::wykobi::polygon< float, 2 > Polygon
Eigen::Vector3f Point
double distance(const Point &a, const Point &b)
Definition point.hpp:95
#define ARMARX_TRACE
Definition trace.h:77