CostmapBuilder.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 Fabian Reister ( fabian dot reister at kit dot edu )
17 * @date 2022
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "CostmapBuilder.h"
23
24#include <algorithm>
25#include <cstddef>
26#include <functional>
27#include <memory>
28#include <optional>
29#include <string>
30#include <variant>
31#include <vector>
32
33#include <boost/geometry/algorithms/convex_hull.hpp>
34#include <boost/geometry/algorithms/disjoint.hpp>
35#include <boost/geometry/geometries/box.hpp>
36
37#include <SimoxUtility/meta/enum/EnumNames.hpp>
38#include <VirtualRobot/CollisionDetection/CollisionChecker.h>
39#include <VirtualRobot/CollisionDetection/CollisionModel.h>
40#include <VirtualRobot/Nodes/RobotNode.h> // IWYU pragma: keep
41#include <VirtualRobot/Robot.h> // IWYU pragma: keep
42#include <VirtualRobot/RobotFactory.h>
43#include <VirtualRobot/SceneObjectSet.h>
44#include <VirtualRobot/VirtualRobot.h>
45
51
59
60#include <omp.h>
61
62#if COSTMAP_BUILDER_SIMOX_CONTROL
63#include <simox/control/environment/CollisionRobot.h>
64#include <simox/control/impl/simox/robot/Robot.h>
65#include <simox/control/impl/simox/utils/conversion.h>
66
67#include <hpp/fcl/broadphase/broadphase_dynamic_AABB_tree.h>
68#include <hpp/fcl/collision_object.h>
69#endif
70
72{
73
74#if COSTMAP_BUILDER_SIMOX_CONTROL
75 namespace sc = simox::control;
76#endif
77
78
79 // helper type for the visitor
80 template <class... Ts>
81 struct overloaded : Ts...
82 {
83 using Ts::operator()...;
84 };
85 // explicit deduction guide (not needed as of C++20)
86 template <class... Ts>
87 overloaded(Ts...) -> overloaded<Ts...>;
88
89 const simox::meta::EnumNames<CostmapBuilder::DistanceCalculator>
93
95 const VirtualRobot::SceneObjectSetPtr& obstacles,
96 const std::vector<VirtualRobot::RobotPtr>& articulatedObjects,
97 const std::vector<Room>& rooms,
98 const Costmap::Parameters& parameters,
99 const std::string& robotCollisonModelName,
100 const CostmapBuilderParams& builderParameters) :
101 robot(robot),
102 obstacles(obstacles),
103 articulatedObjects(articulatedObjects),
104 rooms(rooms),
105 parameters(parameters),
106 robotCollisionModelName(robotCollisonModelName),
107 builderParameters(builderParameters)
108 {
109 ARMARX_CHECK_NOT_NULL(robot) << "Robot must be set";
110 ARMARX_CHECK_NOT_NULL(obstacles);
111 ARMARX_CHECK(robot->hasRobotNode(robotCollisionModelName));
112
113 ARMARX_CHECK_NONNEGATIVE(this->builderParameters.numThreads);
114
115 const auto& enabledRooms = this->builderParameters.roomEnableList;
116 if (not enabledRooms.empty())
117 {
118 // if roomEnableList is specified, remove all other rooms from the provided list
119 this->rooms.erase(std::remove_if(this->rooms.begin(),
120 this->rooms.end(),
121 [&enabledRooms](const Room& room)
122 {
123 const bool roomIncluded =
124 std::find(enabledRooms.cbegin(),
125 enabledRooms.cend(),
126 room.name) != enabledRooms.cend();
127 if (not roomIncluded)
128 {
129 ARMARX_VERBOSE
130 << "Room " << room.name
131 << " was found but is not included";
132 }
133 return not roomIncluded;
134 }),
135 this->rooms.end());
136 }
137
138 if (builderParameters.restrictToRooms)
139 {
141 << "No rooms enabled, even though restrict to rooms is true";
142 }
143 }
144
145 Costmap
147 {
149 [](const armarx::core::time::Duration& duration)
150 { ARMARX_INFO << "Creating costmap took " << duration; });
151
152 ARMARX_INFO << articulatedObjects.size() << " articulated objects";
153 ARMARX_INFO << obstacles->getSize() << " objects";
154
155 const auto sceneBounds = computeSceneBounds(obstacles,
156 articulatedObjects,
157 init,
158 rooms,
159 parameters.sceneBoundsMargin,
160 builderParameters.restrictToRooms);
161 const auto grid = createUniformGrid(sceneBounds, parameters);
162
163 ARMARX_VERBOSE << "Created grid";
164 Costmap costmap(grid, parameters, sceneBounds);
165
166 if (builderParameters.restrictToRooms)
167 {
168 initializeEmptyMask(costmap);
169
171 ARMARX_VERBOSE << "Restricting to rooms:";
172 for (const auto& room : rooms)
173 {
174 ARMARX_VERBOSE << " - " << room.name;
175 }
176
177 invalidateOutsideRooms(rooms, costmap, builderParameters.robotFootprintRadius);
178
179 ARMARX_VERBOSE << "Restricted to rooms. Fraction of valid elements: "
180 << costmap.mask->cast<float>().sum() / costmap.mask->size();
181 }
182
183 {
185 [](const armarx::core::time::Duration& duration)
186 { ARMARX_INFO << "Filling costmap took " << duration; });
187
188 ARMARX_VERBOSE << "Filling grid with size (" << costmap.getGrid().rows() << "/"
189 << costmap.getGrid().cols() << ") and resolution "
190 << costmap.params().cellSize;
191 fillGridCosts(costmap);
192 ARMARX_VERBOSE << "Filled grid";
193 }
194
196 ARMARX_VERBOSE << "Initialized mask";
197
198 return costmap;
199 }
200
201 Costmap
203 {
205 [](const armarx::core::time::Duration& duration)
206 { ARMARX_INFO << "Extending costmap took " << duration; });
207
208 if (builderParameters.restrictToRooms)
209 {
210 // mask out any cell not within a room
211 ARMARX_VERBOSE << "Restricting to rooms";
212 invalidateOutsideRooms(rooms, costmap, builderParameters.robotFootprintRadius);
213 }
214
215 {
217 [](const armarx::core::time::Duration& duration)
218 { ARMARX_INFO << "Filling costmap took " << duration; });
219
220 ARMARX_VERBOSE << "Extending grid with size (" << costmap.getGrid().rows() << "/"
221 << costmap.getGrid().cols() << ") and resolution "
222 << costmap.params().cellSize;
223 extendGridCosts(costmap);
224 ARMARX_VERBOSE << "Filled grid";
225 }
226
228 ARMARX_VERBOSE << "Initialized mask";
229
230 return costmap;
231 }
232
233 void
235 {
236 costmap.mask = costmap.grid.array() > 0.F;
237
238 ARMARX_VERBOSE << "Update mask: Fraction of valid elements: "
239 << costmap.mask->cast<float>().sum() / costmap.mask->size();
240 }
241
242 void
243 CostmapBuilder::initializeEmptyMask(Costmap& costmap)
244 {
245 costmap.mask = costmap.grid.array() >= -42.F; // FIXME: magic number, just same size
246 costmap.mask->setOnes();
247
248 ARMARX_VERBOSE << "Initializing empty mask: Fraction of valid elements: "
249 << costmap.mask->cast<float>().sum() / costmap.mask->size();
250 }
251
252 Eigen::MatrixXf
254 const Costmap::Parameters& parameters)
255 {
257
258 ARMARX_VERBOSE << "Scene bounds are " << sceneBounds.min << " and " << sceneBounds.max;
259
260 //+1 for explicit rounding up
261 size_t c_x = (sceneBounds.max.x() - sceneBounds.min.x()) / parameters.cellSize + 1;
262 size_t c_y = (sceneBounds.max.y() - sceneBounds.min.y()) / parameters.cellSize + 1;
263
264 ARMARX_VERBOSE << "Grid size: " << c_x << ", " << c_y;
265
266 ARMARX_VERBOSE << "Resetting grid";
267 Eigen::MatrixXf grid(c_x, c_y);
268 grid.setZero();
269
270 return grid;
271 }
272
273 float
274 CostmapBuilder::computeCost(const Costmap::Position& position,
275 const CollisionSetup& collisionSetupVariant)
276 {
277 float cost = std::visit(
279 [&position](const CollisionSetupSC& collisionSetup) -> float
280 {
281 // SimoxControl implementation to calculate distance
282#if COSTMAP_BUILDER_SIMOX_CONTROL
283 ARMARX_CHECK_NOT_NULL(collisionSetup.robot);
284 ARMARX_CHECK_NOT_NULL(collisionSetup.collisionRobot);
285 ARMARX_CHECK_NOT_NULL(collisionSetup.obstacleCollisionManager);
286
287 // Remember: SimoxControl uses meters.
288 const core::Pose globalPose(Eigen::Translation3f(conv::to3D(position)));
289 collisionSetup.robot->setGlobalPose(
290 sc::simox::utils::from_simox(globalPose.matrix()), true);
291
292 collisionSetup.collisionRobot->update();
293 collisionSetup.collisionRobot->getCollisionManager()->update();
294
295 // The obstacles are static: their collision objects and the broadphase
296 // manager are built once in `initializeCollisionSetup` and never move, so
297 // there is nothing to update here.
298
299 hpp::fcl::DistanceCallBackDefault defaultCallback;
300 collisionSetup.obstacleCollisionManager->distance(
301 collisionSetup.collisionRobot->getCollisionManager(), &defaultCallback);
302
303 const double minDistance = defaultCallback.data.result.min_distance;
304
305 return static_cast<float>(std::max(minDistance, 0.) * 1000);
306#else
308 << "SimoxControl distance calculation requested but not supported. "
309 "Please compile with SimoxControl to use this calculation method.";
310 return 0.F;
311#endif
312 },
313 [&position, this](const CollisionSetupSx& collisionSetup) -> float
314 {
315 const auto& collisionRobot = collisionSetup.collisionRobot;
316 const auto& robotCollisionModel = collisionSetup.robotCollisionModel;
317
319 ARMARX_CHECK_NOT_NULL(collisionRobot);
320 ARMARX_CHECK_NOT_NULL(robotCollisionModel);
322 VirtualRobot::CollisionChecker::getGlobalCollisionChecker());
323
324 const VirtualRobot::SceneObjectSetPtr& actualObstacles = [&]()
325 {
326 if (collisionSetup.filteredObstacles)
327 {
328 return collisionSetup.filteredObstacles;
329 }
330 else
331 {
332 return this->obstacles;
333 }
334 }();
335 ARMARX_CHECK_NOT_NULL(actualObstacles);
336
337 const core::Pose globalPose(Eigen::Translation3f(conv::to3D(position)));
338 collisionRobot->setGlobalPose(globalPose.matrix());
339
340 // distance to non-articulated objects
341 float distanceNonArticulated = std::numeric_limits<float>::max();
342 if (actualObstacles->getSize() > 0)
343 {
344 distanceNonArticulated =
345 VirtualRobot::CollisionChecker::getGlobalCollisionChecker()
346 ->calculateDistance(robotCollisionModel, actualObstacles);
347 }
348
349 // distance to articulated objects
350 float distanceArticulatedMin = std::numeric_limits<float>::max();
351 for (const auto& articulatedObject : articulatedObjects)
352 {
353 for (const auto& colModel : articulatedObject->getCollisionModels())
354 {
355 const float distanceArticulated =
356 VirtualRobot::CollisionChecker::getGlobalCollisionChecker()
357 ->calculateDistance(robotCollisionModel, colModel);
358 distanceArticulatedMin =
359 std::min(distanceArticulated, distanceArticulatedMin);
360 }
361 }
362
363 return std::min(distanceNonArticulated, distanceArticulatedMin);
364
365
366 // Eigen::Vector3f P1;
367 // Eigen::Vector3f P2;
368 // int id1, id2;
369
370 // float minDistance = std::numeric_limits<float>::max();
371
372 // // TODO omp...
373 // for (size_t i = 0; i < obstacles->getSize(); i++)
374 // {
375 // // cheap collision check
376 // // VirtualRobot::BoundingBox obstacleBbox =
377 // // obstacles->getSceneObject(i)->getCollisionModel()->getBoundingBox(true);
378 // // if (not intersects(robotBbox, obstacleBbox))
379 // // {
380 // // continue;
381 // // }
382
383 // // precise collision check
384 // const float dist =
385 // VirtualRobot::CollisionChecker::getGlobalCollisionChecker()->calculateDistance(
386 // robotCollisionModel,
387 // obstacles->getSceneObject(i)->getCollisionModel(),
388 // P1,
389 // P2,
390 // &id1,
391 // &id2);
392
393 // // check if objects collide
394 // if ((dist <= parameters.cellSize / 2) or
395 // VirtualRobot::CollisionChecker::getGlobalCollisionChecker()->checkCollision(
396 // robotCollisionModel, obstacles->getSceneObject(i)->getCollisionModel()))
397 // {
398 // minDistance = 0;
399 // break;
400 // }
401
402 // minDistance = std::min(minDistance, dist);
403 // }
404 // // return n->position.x() >= sceneBounds.min.x() && n->position.x() <= sceneBounds.max.x() &&
405 // // n->position.y() >= sceneBounds.min.y() && n->position.y() <= sceneBounds.max.y();
406
407 // return minDistance;
408 },
409 [](const std::monostate& _) -> float
410 {
411 ARMARX_ERROR << "Invalid collision setup";
412 return 0.F;
413 }},
414 collisionSetupVariant);
415
416 // Cap distance at maxDistance
417 cost = std::min(cost, builderParameters.maxDistance);
418 return cost;
419 }
420
421 void
422 CostmapBuilder::fillGridCosts(Costmap& costmap)
423 {
424 applyFnToCostmap(costmap,
425 [&](const CollisionSetup& collisionSetup,
426 const std::optional<float> maskOpt,
427 const float /*cost*/,
428 const Costmap::Index& index) -> float
429 {
430 const auto position = costmap.toPositionGlobal(index);
431
432 // consider mask if available and skip if not valid
433 if (maskOpt.has_value())
434 {
435 if (not maskOpt.value())
436 {
437 // important to set a value <= 0 here, so updateMask wont override mask later
438 return 0.F;
439 }
440 }
441
442 if( builderParameters.fakeModeForTesting)
443 {
444 return builderParameters.maxDistance; // similar distance everywhere
445 }
446
447 return computeCost(position, collisionSetup);
448 });
449 }
450
451 void
452 CostmapBuilder::extendGridCosts(Costmap& costmap)
453 {
454 applyFnToCostmap(
455 costmap,
456 [&](const CollisionSetup& collisionSetup,
457 const std::optional<float> maskOpt,
458 const float cost,
459 const Costmap::Index& index) -> float
460 {
461 const auto position = costmap.toPositionGlobal(index);
462
463 // consider mask if available and skip if not valid
464 // as mask is updated to respect rooms above, this will also ignore areas outside any rooms
465 if (maskOpt.has_value())
466 {
467 if (not maskOpt.value())
468 {
469 return std::min(0.F, cost);
470 }
471 }
472
473 if (cost < 0.F)
474 {
475 return cost;
476 }
477
478 if (builderParameters.fakeModeForTesting)
479 {
480 return std::min(cost, builderParameters.maxDistance); // similar distance everywhere
481 }
482
483 return std::min(cost, computeCost(position, collisionSetup));
484 });
485 }
486
487#if COSTMAP_BUILDER_SIMOX_CONTROL
488 CostmapBuilder::SharedObstacleColObjects
489 CostmapBuilder::buildSharedObstacleCollisionObjects() const
490 {
491 armarx::core::time::ScopedStopWatch sw(
492 [](const armarx::core::time::Duration& duration)
493 { ARMARX_INFO << "Converting obstacles to collision objects took " << duration; });
494
495 using ScRobot = sc::simox::robot::Robot;
496 using ScCollisionRobot = sc::environment::CollisionRobot<hpp::fcl::OBBRSS>;
497
498 SharedObstacleColObjects colObjects;
499
500 // Note: this must not be moved into the OpenMP region. The mesh extraction below reads
501 // the *shared* VirtualRobot::CollisionModel of the source scene object (its global pose
502 // in particular), while the vertices are transformed back to local using the pose of the
503 // per-robot node. Building this concurrently lets those two poses disagree, which bakes
504 // the obstacle's BVH at the wrong place for whichever thread lost the race.
505 const auto convert = [&colObjects](const VirtualRobot::RobotPtr& r)
506 {
507 try
508 {
509 const auto obstacle = ScRobot::CREATE_SIMPLE_WRAPPER(r);
510 ARMARX_CHECK_NOT_NULL(obstacle);
511
512 const auto collisionRobot = std::make_unique<ScCollisionRobot>(*obstacle);
513 collisionRobot->update();
514
515 for (auto& node : collisionRobot->getNodes())
516 {
517 for (std::size_t i = 0; i < node.size(); i++)
518 {
519 // Copying the collision object keeps its BVH geometry alive through the
520 // geometry's shared_ptr, so the intermediate robots can be dropped here.
521 colObjects.emplace_back(
522 std::make_shared<hpp::fcl::CollisionObject>(node.getColObject(i)));
523 }
524 }
525 }
526 catch (const std::exception& ex)
527 {
528 // Do not fail the whole costmap over one bad object, but never let this pass
529 // unnoticed either: the obstacle is missing from the costmap entirely.
530 ARMARX_ERROR << "Failed to convert object `" << r->getName()
531 << "` to a collision robot. It will be MISSING from the costmap.\n"
532 << ex.what();
533 }
534 };
535
536 for (const auto& o : this->obstacles->getSceneObjects())
537 {
538 // Here, the scene objects are ManipulationObjects
539 const auto casted =
540 std::dynamic_pointer_cast<VirtualRobot::GraspableSensorizedObject>(o);
541 ARMARX_CHECK_NOT_NULL(casted) << "Scene object is not a graspable sensorized object";
542
543 // static obstacles first have to be converted to a Robot,
544 // then converted to simox control
545 convert(VirtualRobot::RobotFactory::createRobot(*casted));
546 }
547
548 // articulated objects already are Robots, thus they can be immediately converted
549 for (const auto& o : this->articulatedObjects)
550 {
551 convert(o);
552 }
553
554 ARMARX_INFO << "Converted " << (this->obstacles->getSize() + this->articulatedObjects.size())
555 << " obstacles into " << colObjects.size() << " collision objects.";
556
557 return colObjects;
558 }
559#endif
560
561 void
562 CostmapBuilder::applyFnToCostmap(Costmap& costmap,
563 const std::function<float(const CollisionSetup& collisionSetup,
564 const std::optional<float>,
565 const float,
566 const Costmap::Index&)>& fn)
567 {
568 if (builderParameters.fakeModeForTesting)
569 {
570 // Fake mode is for testing without a real robot/scene - skip building any collision
571 // geometry entirely instead of constructing it only to discard the result per cell.
572 const std::size_t c_x = costmap.grid.rows();
573 const std::size_t c_y = costmap.grid.cols();
574 for (unsigned int x = 0; x < c_x; x++)
575 {
576 for (unsigned int y = 0; y < c_y; y++)
577 {
578 const Costmap::Index index{x, y};
579 const auto maskVal = costmap.mask.has_value()
580 ? std::make_optional(costmap.mask.value()(x, y))
581 : std::nullopt;
582 costmap.grid(x, y) = fn(CollisionSetup{}, maskVal, costmap.grid(x, y), index);
583 }
584 }
585 return;
586 }
587
588 // filter scene objects once, not in every thread
589 const VirtualRobot::SceneObjectSetPtr filteredObjects = filterObjectsForCostmap(costmap);
590
591#if COSTMAP_BUILDER_SIMOX_CONTROL
592 // Convert the obstacles once, serially (see buildSharedObstacleCollisionObjects). The
593 // resulting geometry is immutable and is shared by all threads; each thread only gets its
594 // own lightweight transform wrappers.
595 const SharedObstacleColObjects sharedObstacleColObjects =
596 builderParameters.calculationMethod == DistanceCalculator::SimoxControl
597 ? buildSharedObstacleCollisionObjects()
598 : SharedObstacleColObjects{};
599#endif
600
601 // called from every thread to initialize the private collision data
602 const auto initializeCollisionSetup = [&]() -> CollisionSetup
603 {
604 armarx::core::time::ScopedStopWatch sw(
605 [](const armarx::core::time::Duration& duration)
606 {
607 ARMARX_INFO << "Initializing collision setup on thread " << omp_get_thread_num()
608 << " took: " << duration;
609 });
610
611 switch (builderParameters.calculationMethod)
612 {
613 case DistanceCalculator::SimoxControl:
614 {
615#if COSTMAP_BUILDER_SIMOX_CONTROL
616 ARMARX_VERBOSE << "Initializing collision setup";
617 CollisionSetupSC collisionSetup;
618
619 using ScRobot = sc::simox::robot::Robot;
620 using ScCollisionRobot = sc::environment::CollisionRobot<hpp::fcl::OBBRSS>;
621
622 // create copy of this robot instance
623 ARMARX_DEBUG << "Copying robot";
625 VirtualRobot::RobotPtr clonedRobot =
626 robot->clone("collision_robot_" + std::to_string(omp_get_thread_num()));
627 ARMARX_CHECK_NOT_NULL(clonedRobot);
628
629 // replace the standard collision model by our custom one
630 clonedRobot->setPrimitiveApproximationModel({"navigation"}, false);
631
632 clonedRobot->setUpdateVisualization(false);
633
634 // will copy RobotPtr and keep it alive after leaving the scope
635 collisionSetup.robot = ScRobot::CREATE_SIMPLE_WRAPPER(clonedRobot);
636 ARMARX_CHECK_NOT_NULL(collisionSetup.robot);
637 collisionSetup.collisionRobot =
638 std::make_unique<ScCollisionRobot>(*collisionSetup.robot,
639 std::string(),
640 false,
641 std::vector<std::string>{"navigation"});
642
643 collisionSetup.obstacleCollisionManager =
644 std::make_unique<hpp::fcl::DynamicAABBTreeCollisionManager>();
645
646 // Thread-local copies of the shared obstacle collision objects. This only
647 // duplicates transform and AABB - the BVH geometry is shared - so it is cheap
648 // compared to converting every obstacle in every thread.
649 collisionSetup.obstacleColObjects.reserve(sharedObstacleColObjects.size());
650 for (const auto& sharedColObject : sharedObstacleColObjects)
651 {
652 ARMARX_CHECK_NOT_NULL(sharedColObject);
653 collisionSetup.obstacleColObjects.emplace_back(
654 std::make_unique<hpp::fcl::CollisionObject>(*sharedColObject));
655 }
656
657 // register only after the vector is fully populated: the manager stores raw
658 // pointers to the collision objects
659 for (const auto& colObject : collisionSetup.obstacleColObjects)
660 {
661 collisionSetup.obstacleCollisionManager->registerObject(colObject.get());
662 }
663
664 collisionSetup.obstacleCollisionManager->setup();
665
666 return collisionSetup;
667#else
669 << "SimoxControl distance calculation requested but not supported. "
670 "Please compile with SimoxControl to use this calculation method.";
671 return {};
672#endif
673 }
674 case DistanceCalculator::Simox:
675 {
676 CollisionSetupSx collisionSetup;
677
678 ARMARX_DEBUG << "Copying robot";
680 collisionSetup.collisionRobot =
681 robot->clone("collision_robot_" + std::to_string(omp_get_thread_num()));
682
683 // replace the standard collision model by our custom one
684 collisionSetup.collisionRobot->setPrimitiveApproximationModel({"navigation"},
685 false);
686
687 collisionSetup.collisionRobot->setUpdateVisualization(false);
688 ARMARX_DEBUG << "Copying done";
689
690 ARMARX_CHECK_NOT_NULL(collisionSetup.collisionRobot);
692 collisionSetup.collisionRobot->hasRobotNode(robotCollisionModelName));
693
694 const auto collisionRobotNode =
695 collisionSetup.collisionRobot->getRobotNode(robotCollisionModelName);
696 ARMARX_CHECK_NOT_NULL(collisionRobotNode);
697
698 collisionSetup.robotCollisionModel = collisionRobotNode->getCollisionModel();
699 ARMARX_CHECK_NOT_NULL(collisionSetup.robotCollisionModel)
700 << "Collision model not available. "
701 "Make sure that you load the robot correctly!";
702
703 // scale collision model
704 collisionSetup.robotCollisionModel->scale(
705 builderParameters.collisionModelScaleFactor);
706
707 collisionSetup.filteredObstacles = filteredObjects;
708
709 ARMARX_CHECK_NOT_NULL(collisionSetup.collisionRobot);
710 ARMARX_CHECK_NOT_NULL(collisionSetup.robotCollisionModel);
711
712 return collisionSetup;
713 }
714 default:
715 ARMARX_ERROR << "Invalid distance calculator specified";
716 return {};
717 }
718 };
719
720 if (costmap.mask.has_value())
721 {
722 ARMARX_VERBOSE << "Costmap provides mask.";
723 }
724
725 const std::size_t c_x = costmap.grid.rows();
726 const std::size_t c_y = costmap.grid.cols();
727
728 robot->setUpdateVisualization(false);
729
730 // a per-thread local variable (through `private` directive below)
731 CollisionSetup collisionSetup;
732
733 const int threadNumDefault = omp_get_max_threads();
734 const int actualThreads = this->builderParameters.numThreads == 0
735 ? threadNumDefault
736 : this->builderParameters.numThreads;
737 ARMARX_INFO << "Using " << actualThreads << " threads.";
738
739
740// `schedule(dynamic)`: with a room mask, rows that lie entirely outside every room return
741// almost immediately, so a static split leaves threads idle.
742#pragma omp parallel for num_threads(actualThreads) \
743 schedule(dynamic) private(collisionSetup) default(shared)
744 for (unsigned int x = 0; x < c_x; x++)
745 {
746 // any exception (like those generated by ARMARX_CHECK) need to be caught in the executing thread
747 try
748 {
749
750 // we have to initialize the collision setup for each thread
751 if (std::holds_alternative<std::monostate>(collisionSetup))
752 {
753 collisionSetup = initializeCollisionSetup();
754 ARMARX_VERBOSE << "Collision setup created successfully";
755 }
756 ARMARX_CHECK(not std::holds_alternative<std::monostate>(collisionSetup));
757
758
759 for (unsigned int y = 0; y < c_y; y++)
760 {
761 const Costmap::Index index{x, y};
762
763 const auto maskVal = costmap.mask.has_value()
764 ? std::make_optional(costmap.mask.value()(x, y))
765 : std::nullopt;
766 costmap.grid(x, y) = fn(collisionSetup, maskVal, costmap.grid(x, y), index);
767 }
768 }
769 catch (const std::exception& e)
770 {
771 ARMARX_ERROR << "Error during costmap calculation: " << e.what();
772 }
773 }
774 }
775
776 VirtualRobot::SceneObjectSetPtr
777 CostmapBuilder::filterObjectsForCostmap(const Costmap& costmap)
778 {
779 armarx::core::time::ScopedStopWatch sw(
780 [](const armarx::core::time::Duration& duration)
781 { ARMARX_INFO << "Filtering objects took " << duration; });
782
783 VirtualRobot::SceneObjectSetPtr filtered(new VirtualRobot::SceneObjectSet);
784
786 using Box = boost::geometry::model::box<Point>;
788
789 const auto toPoint = [](const Eigen::Vector3f& vec) { return Point(vec.x(), vec.y()); };
790 const auto toBox = [&toPoint](const VirtualRobot::BoundingBox& bb)
791 { return Box(toPoint(bb.getMin()), toPoint(bb.getMax())); };
792
793 Polygon costmapBB;
794 {
795 Polygon cornerPoints = util::geometry::toPolygon(std::vector<Eigen::Vector2f>{
796 costmap.toPositionGlobal(Costmap::Index(0, 0)),
797 costmap.toPositionGlobal(Costmap::Index(0, costmap.grid.cols() - 1)),
798 costmap.toPositionGlobal(Costmap::Index(costmap.grid.rows() - 1, 0)),
799 costmap.toPositionGlobal(
800 Costmap::Index(costmap.grid.rows() - 1, costmap.grid.cols() - 1))});
801 boost::geometry::convex_hull(cornerPoints, costmapBB);
802 }
803
804
805 for (const auto& object : this->obstacles->getSceneObjects())
806 {
807 const auto& colModel = object->getCollisionModel();
808
809 if (colModel)
810 {
811 const auto globalBB = colModel->getGlobalBoundingBox();
812 if (!globalBB.getMin().allFinite() || !globalBB.getMax().allFinite())
813 {
814 ARMARX_WARNING << "Object `" << object->getName()
815 << "` has a non-finite bounding box (likely missing "
816 "collision mesh) - excluding it from costmap filtering.";
817 continue;
818 }
819 Box bb = toBox(globalBB);
820
821 // if bb of object is disjoint to bb of costmap we check the distance between them
822 // otherwise, we always include the object
823 if (boost::geometry::disjoint(costmapBB, bb))
824 {
825 float distance = boost::geometry::distance(costmapBB, bb);
826 if (distance > builderParameters.maxFilterDistance)
827 {
828 // if the distance is greater than configured, we skip this object
829 continue;
830 }
831 }
832
833 filtered->addSceneObject(object);
834 }
835 }
836 const std::size_t prevSize = this->obstacles->getSize();
837 const std::size_t filteredSize = filtered->getSize();
838
839 ARMARX_INFO << "Remaining objects for costmap calculation: " << filteredSize << "/"
840 << prevSize << " ("
841 << static_cast<float>(filteredSize) * 100.f / static_cast<float>(prevSize)
842 << "%)";
843
844 for (const auto& object : filtered->getSceneObjects())
845 {
846 ARMARX_VERBOSE << "- " << object->getName();
847 }
848
849
850 return filtered;
851 }
852
853} // namespace armarx::navigation::algorithms
uint8_t index
#define ARMARX_CHECK_NOT_EMPTY(c)
Represents a duration.
Definition Duration.h:17
Measures the time this stop watch was inside the current scope.
static const simox::meta::EnumNames< DistanceCalculator > DistanceCalculatorNames
static Eigen::MatrixXf createUniformGrid(const SceneBounds &sceneBounds, const Costmap::Parameters &parameters)
CostmapBuilder(const VirtualRobot::RobotPtr &robot, const VirtualRobot::SceneObjectSetPtr &obstacles, const std::vector< VirtualRobot::RobotPtr > &articulatedObjects, const std::vector< Room > &rooms, const Costmap::Parameters &parameters, const std::string &robotCollisonModelName, const CostmapBuilderParams &builderParameters)
Costmap create(const SceneBounds &init=SceneBounds())
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#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_CHECK_NONNEGATIVE(number)
Check whether number is nonnegative (>= 0).
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
This file is part of ArmarX.
overloaded(Ts...) -> overloaded< Ts... >
void invalidateOutsideRooms(const std::vector< Room > &rooms, Costmap &costmap, const float footprintRadius)
Definition util.cpp:501
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
std::vector< Eigen::Vector3f > to3D(const std::vector< Eigen::Vector2f > &v)
Definition eigen.cpp:14
Eigen::Isometry3f Pose
Definition basic_types.h:31
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
std::vector< core::Pose > convert(const std::vector< Eigen::Matrix4f > &wps)
Definition Component.cpp:93
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:75