collision_checking.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstddef>
5#include <limits>
6#include <string>
7#include <vector>
8
9#include <boost/geometry/algorithms/buffer.hpp>
10#include <boost/geometry/algorithms/convex_hull.hpp>
11#include <boost/geometry/algorithms/distance.hpp>
12#include <boost/geometry/algorithms/simplify.hpp>
13#include <boost/geometry/algorithms/transform.hpp>
14#include <boost/geometry/algorithms/union.hpp>
15#include <boost/geometry/core/cs.hpp>
16#include <boost/geometry/geometries/multi_linestring.hpp>
17#include <boost/geometry/geometries/multi_point.hpp>
18#include <boost/geometry/geometries/multi_polygon.hpp>
19#include <boost/geometry/io/wkt/write.hpp>
20#include <boost/geometry/strategies/strategies.hpp>
21#include <boost/geometry/strategies/transform/matrix_transformers.hpp>
22#include <boost/pending/disjoint_sets.hpp>
23
24#include <Eigen/Geometry>
25
26#include <VirtualRobot/CollisionDetection/CollisionChecker.h>
27#include <VirtualRobot/CollisionDetection/CollisionModel.h>
28#include <VirtualRobot/Nodes/RobotNode.h>
29#include <VirtualRobot/Robot.h>
30#include <VirtualRobot/RobotFactory.h>
31#include <VirtualRobot/SceneObjectSet.h>
32#include <VirtualRobot/VirtualRobot.h>
33#include <VirtualRobot/Visualization/TriMeshModel.h>
34
38#include <ArmarXCore/interface/serialization/Eigen/Eigen_fdi.h>
40
44
45#include <range/v3/range/conversion.hpp>
46#include <range/v3/view/all.hpp>
47#include <range/v3/view/filter.hpp>
48#include <range/v3/view/transform.hpp>
49
51{
52 bool
54 {
56 return robot != nullptr && not collisionModels.empty();
57 }
58
61 {
62 Robot3D distanceRobot;
63 {
64 ARMARX_DEBUG << "Copying robot";
66 distanceRobot.robot =
67 robot->clone("collision_robot_" + std::to_string(omp_get_thread_num()));
68
69
70 // replace the standard collision model by our custom one
71 if (not params.primitiveApproximationIDs.empty())
72 {
73 ARMARX_INFO << "Setting primitive approximation IDs: "
74 << std::vector(params.primitiveApproximationIDs.begin(),
75 params.primitiveApproximationIDs.end());
76 distanceRobot.robot->setPrimitiveApproximationModel(
77 std::vector(params.primitiveApproximationIDs.begin(),
78 params.primitiveApproximationIDs.end()),
79 false);
80 }
81
82 distanceRobot.robot->setUpdateVisualization(true); // FIXME false
83 ARMARX_DEBUG << "Copying done";
84 }
85
86 ARMARX_CHECK_NOT_NULL(distanceRobot.robot);
87
88 ARMARX_CHECK(distanceRobot.robot);
89
90 if (not params.collisionModelName.empty())
91 {
92 ARMARX_INFO << "Using collision model " << QUOTED(params.collisionModelName);
93 ARMARX_CHECK(distanceRobot.robot->hasRobotNode(params.collisionModelName));
94
95 const auto collisionRobotNode =
96 distanceRobot.robot->getRobotNode(params.collisionModelName);
97 ARMARX_CHECK_NOT_NULL(collisionRobotNode);
98
99 distanceRobot.collisionModels = {collisionRobotNode->getCollisionModel()};
100 ARMARX_CHECK(distanceRobot.collisionModels.front())
101 << params.collisionModelName << " does not provide a collision model!";
102 }
103 else
104 {
105 ARMARX_INFO << "Using reduced robot (merging all collision models) ...";
106
107 std::vector<std::string> otherNodeNames{distanceRobot.robot->getRootNode()->getName()};
108 if (not params.rootNode.empty())
109 {
110 otherNodeNames.push_back(params.rootNode);
111 }
112
113 // create reduced robot
114 // distanceRobot.robot = VirtualRobot::RobotFactory::createReducedModel(
115 // *distanceRobot.robot, {}, otherNodeNames);
116
117 // ARMARX_CHECK_EQUAL(distanceRobot.robot->getCollisionModels().size(), 1);
118 ARMARX_IMPORTANT << distanceRobot.robot->getCollisionModels().size();
119
120 // distanceRobot.collisionModel = distanceRobot.robot->getCollisionModels().front();
121 distanceRobot.collisionModels = distanceRobot.robot->getCollisionModels();
122 }
123
124 ARMARX_CHECK(distanceRobot.collisionModels.empty() == false)
125 << "Collision model not available. "
126 "Make sure that you load the robot correctly!";
127
128
129 // scale collision model
130 for (auto& colModel : distanceRobot.robot->getCollisionModels())
131 {
132 colModel->scale(params.scaleFactor);
133 }
134
135 return distanceRobot;
136 }
137
141
142 bool
144 {
146 return robot.robot != nullptr && not robot.collisionModels.empty();
147 }
148
149 void
151 {
153 this->robot = robot;
154 }
155
156 void
158 {
159 this->robot = Robot3D::fromSimoxRobot(robot, params);
160 }
161
162 float
164 {
166 ARMARX_CHECK_NOT_NULL(robot.robot);
167 ARMARX_CHECK_NOT_EMPTY(robot.collisionModels);
168 ARMARX_CHECK_NOT_NULL(VirtualRobot::CollisionChecker::getGlobalCollisionChecker());
169 ARMARX_CHECK_NOT_NULL(scene.staticObjects);
170
171 const core::Pose globalPose = conv::to3D(pose2d);
172 robot.robot->setGlobalPose(globalPose.matrix());
173
174 ARMARX_CHECK_EQUAL(robot.collisionModels.size(), 1);
175
176 // distance to non-articulated objects
177 float distanceNonArticulated = std::numeric_limits<float>::max();
178 if (scene.staticObjects->getSize() > 0)
179 {
180 distanceNonArticulated =
181 VirtualRobot::CollisionChecker::getGlobalCollisionChecker()->calculateDistance(
182 robot.collisionModels.front(),
183 scene.staticObjects);
184 }
185
186 // distance to articulated objects
187 float distanceArticulatedMin = std::numeric_limits<float>::max();
188 for (const auto& articulatedObject : scene.articulatedObjects)
189 {
190 for (const auto& colModel : articulatedObject->getCollisionModels())
191 {
192 const float distanceArticulated =
193 VirtualRobot::CollisionChecker::getGlobalCollisionChecker()->calculateDistance(
194 robot.collisionModels.front(),
195 colModel);
196 distanceArticulatedMin = std::min(distanceArticulated, distanceArticulatedMin);
197 }
198 }
199
200 return std::min(distanceNonArticulated, distanceArticulatedMin);
201 }
202
203 namespace
204 {
205
206 void
207 collPolyFromVertices(const std::vector<Eigen::Vector3f>& vertices,
209 {
210 const auto verticesConverted =
211 vertices |
212 ranges::views::transform([](const Eigen::Vector3f& vec)
213 { return util::geometry::point_type{vec.x(), vec.y()}; }) |
214 ranges::to_vector;
215 boost::geometry::model::multi_point<util::geometry::point_type> mp(
216 verticesConverted.begin(),
217 verticesConverted.end());
218 boost::geometry::convex_hull(mp, out);
219 }
220
221 template <typename Polygon>
222 boost::geometry::model::multi_polygon<Polygon>
223 buffer_polygon_simple(const Polygon& poly, double distance)
224 {
225 namespace bg = boost::geometry;
226 using Multi = bg::model::multi_polygon<Polygon>;
227
228 using namespace bg::strategy::buffer;
229 distance_symmetric<double> distance_strategy(distance);
230 side_straight side_strategy;
231 join_miter join_strategy;
232 end_flat end_strategy;
233 point_circle circle_strategy(8);
234
235 Multi result;
236 bg::buffer(poly,
237 result,
238 distance_strategy,
239 side_strategy,
240 join_strategy,
241 end_strategy,
242 circle_strategy);
243 return result;
244 }
245
246 std::vector<util::geometry::polygon_type>
247 collPolyFrom3D(const std::vector<Eigen::Vector3f>& vertices,
248 const std::vector<VirtualRobot::MathTools::TriangleFace>& faces)
249 {
250 /*for (const auto& f : faces)
251 {
252 ARMARX_DEBUG << "Face: " << f.id1 << ", " << f.id2 << ", " << f.id3;
253 ARMARX_DEBUG << " v1: " << vertices[f.id1].transpose();
254 ARMARX_DEBUG << " v2: " << vertices[f.id2].transpose();
255 ARMARX_DEBUG << " v3: " << vertices[f.id3].transpose();
256 }*/
257 using point2d = boost::geometry::model::d2::point_xy<double>;
258 using polygon2d = boost::geometry::model::polygon<point2d>;
259 using multipolygon2d = boost::geometry::model::multi_polygon<polygon2d>;
260
261 multipolygon2d projected;
262 for (const auto& f : faces)
263 {
264 polygon2d p;
265 boost::geometry::append(p, point2d(vertices[f.id1].x(), vertices[f.id1].y()));
266 boost::geometry::append(p, point2d(vertices[f.id2].x(), vertices[f.id2].y()));
267 boost::geometry::append(p, point2d(vertices[f.id3].x(), vertices[f.id3].y()));
268 boost::geometry::append(p, point2d(vertices[f.id1].x(), vertices[f.id1].y()));
269 projected.push_back(p);
270 }
271
272 // --- Merge them ---
273 multipolygon2d unified = {projected.front()}; // start with first
274
275 for (size_t i = 1; i < projected.size(); ++i)
276 {
277 multipolygon2d tmp;
278 polygon2d next = projected[i];
279 boost::geometry::correct(next);
280 //multipolygon2d next_buffered;
281 //boost::geometry::buffer(next, next_buffered, 0.01);
282 multipolygon2d next_buffered = buffer_polygon_simple(next, 0.01);
283 if (not boost::geometry::is_valid(next_buffered))
284 {
285 ARMARX_WARNING << "Buffered polygon is not valid: "
286 << boost::geometry::wkt(next);
287 continue;
288 }
289 boost::geometry::union_(unified, next_buffered, tmp);
290 unified = std::move(tmp);
291 }
292
293 /*multipolygon2d unified;
294 boost::geometry::union_(projected, projected, unified);*/
295 //boost::geometry::simplify(unified, unified, 10);
296
297 std::vector<util::geometry::polygon_type> out;
298 for (const auto& p : unified)
299 {
301 for (const auto& point : p.outer())
302 {
303 boost::geometry::append(
304 poly,
305 util::geometry::point_type{static_cast<float>(point.x()),
306 static_cast<float>(point.y())});
307 }
308 out.push_back(poly);
309 ARMARX_INFO << " Polygon: " << boost::geometry::wkt(poly);
310 }
311 return out;
312 }
313
314 } // namespace
315
317 const VirtualRobot::CollisionModelPtr& robotCollisionModel)
318 {
319 // Reset robot to standard pose -> so that we can simply rotate the polygon
320 const core::Pose globalPose = conv::to3D(core::Pose2D::Identity());
321 collisionRobot->setUpdateCollisionModel(true);
322 collisionRobot->setUpdateVisualization(true);
323
324 collisionRobot->setGlobalPose(globalPose.matrix());
325
326 // create robot polygon
327 // vertices are relative to the collision node
328 const std::vector<Eigen::Vector3f> vertices = robotCollisionModel->getModelVeticesGlobal();
329
330 collPolyFromVertices(vertices, robot_polygon);
331 ARMARX_INFO << "Robot: " << boost::geometry::wkt(robot_polygon);
332 }
333
335 const std::vector<VirtualRobot::CollisionModelPtr>& robotCollisionModels)
336 {
337 // Reset robot to standard pose -> so that we can simply rotate the polygon
338 const core::Pose globalPose = conv::to3D(core::Pose2D::Identity());
339 collisionRobot->setUpdateCollisionModel(true);
340 collisionRobot->setUpdateVisualization(true);
341
342 collisionRobot->setGlobalPose(globalPose.matrix());
343
344 // create robot polygon
345 // vertices are relative to the collision node
346 std::vector<Eigen::Vector3f> vertices;
347 for (const auto& robotCollisionModel : robotCollisionModels)
348 {
349 const auto v = robotCollisionModel->getModelVeticesGlobal();
350 vertices.insert(vertices.end(), v.begin(), v.end());
351 }
352
353 collPolyFromVertices(vertices, robot_polygon);
354 ARMARX_INFO << "Robot: " << boost::geometry::wkt(robot_polygon);
355 }
356
358 Robot2D::getOrientedRobot(float orientationRad) const
359 {
360 boost::geometry::strategy::transform::
361 rotate_transformer<boost::geometry::radian, float, 2, 2>
362 transform(orientationRad);
364 boost::geometry::transform(robot_polygon, out, transform);
365 return out;
366 }
367
370 {
371 boost::geometry::strategy::transform::translate_transformer<float, 2, 2> translate(
372 pose.translation().x(),
373 pose.translation().y());
374 const auto angle =
375 -Eigen::Rotation2Df{pose.linear()}
376 .angle(); // negative because boost::geometry uses counter-clockwise rotation
377 boost::geometry::strategy::transform::
378 rotate_transformer<boost::geometry::radian, float, 2, 2>
379 rotate(angle);
381 boost::geometry::transform(robot_polygon, out, rotate);
383 boost::geometry::transform(out, out2, translate);
384 return out2;
385 }
386
387 Scene2D::Scene2D(std::vector<VirtualRobot::CollisionModelPtr>& collisionModels,
388 float ignoreVerticesOver)
389 {
390 initializeFromCollisionModels(collisionModels, ignoreVerticesOver);
391 }
392
393 Scene2D::Scene2D(const SceneRepresentation& scene, float ignoreVerticesOver)
394 {
395 std::vector<VirtualRobot::CollisionModelPtr> collisionModels;
396 const auto& sceneObjectColModels = scene.staticObjects->getCollisionModels();
397 collisionModels.insert(collisionModels.end(),
398 sceneObjectColModels.begin(),
399 sceneObjectColModels.end());
400 for (const auto& articulatedObject : scene.articulatedObjects)
401 {
402 const auto colModels = articulatedObject->getCollisionModels();
403 collisionModels.insert(collisionModels.end(), colModels.begin(), colModels.end());
404 }
405
406 initializeFromCollisionModels(collisionModels, ignoreVerticesOver);
407 }
408
409 float
411 {
412 double minDistance = std::numeric_limits<float>::max();
413 for (const auto& obs : obstacles)
414 {
415 const auto distance = boost::geometry::distance(obs, robot);
416 minDistance = std::min(distance, minDistance);
417 }
418 return static_cast<float>(minDistance);
419 }
420
421 std::vector<CollisionPolygon>&
423 {
424 return this->obstacles;
425 }
426
427 namespace
428 {
429
430 std::vector<std::vector<Eigen::Vector2f>>
431 getCollModelClusters(VirtualRobot::CollisionModelPtr collModel, float ignoreVerticesOver)
432 {
433 std::vector<std::vector<Eigen::Vector2f>> clusters;
434
435 auto allVertices = collModel->getModelVeticesGlobal();
436 auto allFaces = collModel->getTriMeshModel()->faces;
437
438 // filter out all faces where at least one vertex is too high in z-direction
439 auto facesFiltered = allFaces |
440 ranges::views::filter(
441 [ignoreVerticesOver, &allVertices](const auto& f)
442 {
443 return allVertices[f.id1].z() < ignoreVerticesOver &&
444 allVertices[f.id2].z() < ignoreVerticesOver &&
445 allVertices[f.id3].z() < ignoreVerticesOver;
446 }) |
447 ranges::to_vector;
448
449 // create clusters of connected faces
450 // idea: use union-find data structure for vertices and then join vertices that share faces
451 std::vector<int> rank;
452 std::vector<int> parent;
453 rank.reserve(allVertices.size());
454 parent.reserve(allVertices.size());
455 boost::disjoint_sets<int*, int*> ds(rank.data(), parent.data());
456 // initialize
457 for (std::size_t i = 0; i < allVertices.size(); i++)
458 {
459 rank.push_back(0);
460 parent.push_back(static_cast<int>(i));
461 ds.make_set(static_cast<int>(i));
462 }
463 for (const auto& face : facesFiltered)
464 {
465 ds.union_set(face.id1, face.id2);
466 ds.union_set(face.id2, face.id3);
467 ds.union_set(face.id1, face.id3);
468 }
469 // also merge vertices that are very close to one another
470 for (std::size_t i = 0; i < allVertices.size(); i++)
471 {
472 for (std::size_t j = i + 1; j < allVertices.size(); j++)
473 {
474 if (allVertices[i].z() >= ignoreVerticesOver ||
475 allVertices[j].z() >= ignoreVerticesOver)
476 {
477 continue;
478 }
479 const float dist = (allVertices[i] - allVertices[j]).squaredNorm();
480 if (dist < 100.F) // threshold for merging vertices
481 {
482 ds.union_set(static_cast<int>(i), static_cast<int>(j));
483 }
484 }
485 }
486 // collect clusters
487 std::map<int, std::vector<Eigen::Vector2f>> clustersMap;
488 for (std::size_t i = 0; i < allVertices.size(); i++)
489 {
490 if (allVertices[i].z() < ignoreVerticesOver)
491 {
492 const int setId = ds.find_set(static_cast<int>(i));
493 clustersMap[setId].emplace_back(allVertices[i].x(), allVertices[i].y());
494 }
495 }
496 for (const auto& [setId, vertices] : clustersMap)
497 {
498 clusters.push_back(vertices);
499 }
500 return clusters;
501 }
502
503 std::vector<util::geometry::polygon_type>
504 mergeOverlappingPolygons(std::vector<util::geometry::polygon_type>& obstacles)
505 {
506 std::vector<util::geometry::polygon_type> mergedObstacles;
507 std::vector<bool> merged(obstacles.size(), false);
508 for (std::size_t i = 0; i < obstacles.size(); i++)
509 {
510 if (merged[i])
511 {
512 continue;
513 }
514 util::geometry::polygon_type currentPoly = obstacles[i];
515 merged[i] = true;
516 bool mergedSomething = true;
517 while (mergedSomething)
518 {
519 mergedSomething = false;
520 for (std::size_t j = 0; j < obstacles.size(); j++)
521 {
522 if (i == j || merged[j])
523 {
524 continue;
525 }
526 if (boost::geometry::intersects(currentPoly, obstacles[j]) ||
527 boost::geometry::touches(currentPoly, obstacles[j]) ||
528 boost::geometry::within(currentPoly, obstacles[j]) ||
529 boost::geometry::within(obstacles[j], currentPoly))
530 {
531 std::vector<util::geometry::polygon_type> unionPoly;
532 boost::geometry::union_(currentPoly, obstacles[j], unionPoly);
533 if (unionPoly.size() > 1)
534 {
536 << "Merging two polygons resulted in multiple polygons. "
537 "This should not happen. Keeping the first polygon.";
538 }
539 else if (unionPoly.empty())
540 {
541 ARMARX_WARNING << "Merging two polygons resulted in an empty "
542 "polygon. This should "
543 "not happen. Keeping the original polygon.";
544 continue;
545 }
546 currentPoly = unionPoly[0];
547 merged[j] = true;
548 mergedSomething = true;
549 }
550 }
551 }
552 mergedObstacles.push_back(currentPoly);
553 }
554 return mergedObstacles;
555 }
556 } // namespace
557
558 void
559 Scene2D::initializeFromCollisionModels(
560 std::vector<VirtualRobot::CollisionModelPtr>& collisionModels,
561 float ignoreVerticesOver)
562 {
563 obstacles.clear();
564 bool useFaces = true;
565 for (const auto& collModel : collisionModels)
566 {
567 if (ignoreVerticesOver >= 0)
568 {
569 const auto clusters = getCollModelClusters(collModel, ignoreVerticesOver);
570 for (const auto& cluster : clusters)
571 {
572 if (cluster.size() >= 3) // need at least 3 points to form a polygon
573 {
574 useFaces = false; // currenty not supported
575 if (useFaces)
576 {
577 ARMARX_INFO << "";
578 }
579 else
580 {
581 ARMARX_INFO << "Using only vertices for collision model";
582 auto& collPoly = obstacles.emplace_back();
583 collPolyFromVertices(
584 cluster |
585 ranges::views::transform(
586 [](const Eigen::Vector2f& v)
587 { return Eigen::Vector3f{v.x(), v.y(), 0.0f}; }) |
588 ranges::to_vector,
589 collPoly);
590 }
591 }
592 else
593 {
594 ARMARX_INFO << "Ignoring cluster with less than 3 vertices";
595 }
596 }
597 }
598 else
599 {
600 const auto vertices = collModel->getModelVeticesGlobal();
601 if (useFaces)
602 {
603 ARMARX_INFO << "Using faces for collision model";
604 const auto triMeshModel = collModel->getTriMeshModel();
605 const auto faces = triMeshModel->faces;
606 const auto polygons = collPolyFrom3D(vertices, faces);
607 ARMARX_INFO << faces.size() << " faces in collision model";
608 ARMARX_INFO << "Found " << polygons.size() << " polygons in collision model";
609 obstacles.insert(obstacles.end(), polygons.begin(), polygons.end());
610 }
611 else
612 {
613 ARMARX_INFO << "Using only vertices for collision model";
614 auto& collPoly = obstacles.emplace_back();
615 collPolyFromVertices(vertices, collPoly);
616 }
617 }
618
619
620 // merge overlapping polygons
621 auto mergedObstacles = mergeOverlappingPolygons(obstacles);
622 ARMARX_INFO << "Reduced " << obstacles.size() << " obstacles to "
623 << mergedObstacles.size() << " after merging.";
624 obstacles = std::move(mergedObstacles);
625 }
626
627 for (const auto& obs : obstacles)
628 {
629 ARMARX_INFO << "Obstacle: " << boost::geometry::wkt(obs);
630 }
631 }
632
633} // namespace armarx::navigation::algorithms::orientation_aware
#define ARMARX_CHECK_NOT_EMPTY(c)
#define QUOTED(x)
void initializeRobot(const VirtualRobot::RobotPtr &robot, Robot3D::Params params)
CollisionPolygon getRobotAtPose(const core::Pose2D &pose) const
CollisionPolygon getOrientedRobot(float orientationRad) const
Scene2D(std::vector< VirtualRobot::CollisionModelPtr > &collisionModels, float ignoreVerticesOver)
#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_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
double v(double t, double v0, double a0, double j)
Definition CtrlUtil.h:39
std::vector< Eigen::Vector3f > to3D(const std::vector< Eigen::Vector2f > &v)
Definition eigen.cpp:14
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
Eigen::Isometry3f Pose
Definition basic_types.h:31
boost::geometry::model::d2::point_xy< float > point_type
Definition geometry.h:35
boost::geometry::model::polygon< point_type > polygon_type
Definition geometry.h:36
auto transform(const Container< InputT, Alloc > &in, OutputT(*func)(InputT const &)) -> Container< OutputT, typename std::allocator_traits< Alloc >::template rebind_alloc< OutputT > >
Convenience function (with less typing) to transform a container of type InputT into the same contain...
Definition algorithm.h:351
double distance(const Point &a, const Point &b)
Definition point.hpp:95
double angle(const Point &a, const Point &b, const Point &c)
Definition point.hpp:109
static Robot3D fromSimoxRobot(const VirtualRobot::RobotPtr &robot, Robot3D::Params params)
std::vector< VirtualRobot::CollisionModelPtr > collisionModels
#define ARMARX_TRACE
Definition trace.h:77