Costmap.cpp
Go to the documentation of this file.
1#include "Costmap.h"
2
3#include <math.h>
4
5#include <algorithm>
6#include <cmath>
7#include <cstddef>
8#include <limits>
9#include <optional>
10#include <unordered_set>
11#include <utility>
12#include <vector>
13
14#include <Eigen/Core>
15#include <Eigen/Geometry>
16
17#include <range/v3/view/zip.hpp>
18
21#include <ArmarXCore/interface/core/UserException.h>
23
26
28{
29
30
32 const Parameters& parameters,
33 const SceneBounds& sceneBounds,
34 const std::optional<Mask>& mask,
35 const core::Pose2D& origin) :
36 grid(grid),
37 mask(mask),
38 sceneBounds(sceneBounds),
39 parameters(parameters),
40 global_T_costmap(origin)
41 {
43 }
44
45 void
47 {
48 if (mask.has_value())
49 {
50 ARMARX_VERBOSE << VAROUT(grid.rows());
51 ARMARX_VERBOSE << VAROUT(grid.cols());
52 ARMARX_VERBOSE << VAROUT(mask->rows());
53 ARMARX_VERBOSE << VAROUT(mask->cols());
54
55 ARMARX_CHECK_EQUAL(grid.rows(), mask->rows());
56 ARMARX_CHECK_EQUAL(grid.cols(), mask->cols());
57 }
58 }
59
62 {
64
65 Eigen::Vector2f posLocal;
66 posLocal.x() =
67 sceneBounds.min.x() + index.x() * parameters.cellSize + parameters.cellSize / 2;
68 posLocal.y() =
69 sceneBounds.min.y() + index.y() * parameters.cellSize + parameters.cellSize / 2;
70
71 return posLocal;
72 }
73
76 {
77 const Costmap::Position costmap_P_pos = toPositionLocal(index);
78 return global_T_costmap * costmap_P_pos;
79 }
80
81 bool
82 Costmap::isValid(const Costmap::Index& index) const noexcept
83 {
84
85 if (index.x() < 0 || index.y() < 0)
86 {
87 ARMARX_VERBOSE << "Index out of bounds (< 0)";
88 return false;
89 }
90
91 if (index.x() >= grid.rows() || index.y() >= grid.cols())
92 {
93 ARMARX_VERBOSE << "Index out of bounds (>= grid size)";
94 return false;
95 }
96
97 if (mask.has_value())
98 {
99 if (grid.rows() != mask->rows() or grid.cols() != mask->cols())
100 {
101 ARMARX_ERROR << "Grid and mask have different dimensions!";
102 return false;
103 }
104
105 if (not mask.value()(index.x(), index.y()))
106 {
107 ARMARX_DEBUG << "Index masked out (mask)";
108 }
109 return mask.value()(index.x(), index.y());
110 }
111
112 return true;
113 }
114
115 std::pair<int, int>
116 Costmap::toIndexUnsanitized(const Eigen::Vector2f& globalPosition) const
117 {
118 const auto localPosition = global_T_costmap.inverse() * globalPosition;
119
120 const float vX = (localPosition.x() - parameters.cellSize / 2 - sceneBounds.min.x()) /
121 parameters.cellSize;
122 const float vY = (localPosition.y() - parameters.cellSize / 2 - sceneBounds.min.y()) /
123 parameters.cellSize;
124
125 const int iX = std::round(vX - 0.01);
126 const int iY = std::round(vY - 0.01);
127
128 return {iX, iY};
129 }
130
131 std::optional<Costmap::Vertex>
132 Costmap::toVertexOrInvalid(const Eigen::Vector2f& globalPosition) const
133 {
134 const auto [iX, iY] = toIndexUnsanitized(globalPosition);
135
136 if (iX < 0 || iY < 0 || iX >= grid.rows() || iY >= grid.cols())
137 {
138 // calculated vertex outside costmap
139 return std::nullopt;
140 }
141
142 return Vertex{.index = Index{iX, iY}, .position = globalPosition};
143 }
144
146 Costmap::toVertex(const Eigen::Vector2f& globalPosition) const
147 {
148 const auto [iX, iY] = toIndexUnsanitized(globalPosition);
149
150 const int iXSan = std::clamp<int>(iX, 0, grid.rows() - 1);
151 const int iYSan = std::clamp<int>(iY, 0, grid.cols() - 1);
152
153 // FIXME accept one cell off
154
155 // ARMARX_CHECK_GREATER(iX, 0);
156 // ARMARX_CHECK_GREATER(iY, 0);
157
158 // ARMARX_CHECK_LESS_EQUAL(iX, grid.rows() - 1);
159 // ARMARX_CHECK_LESS_EQUAL(iY, grid.cols() - 1);
160
161 return Vertex{.index = Index{iXSan, iYSan}, .position = globalPosition};
162 }
163
164 const SceneBounds&
166 {
167 return sceneBounds;
168 }
169
172 {
174
175 if (mask.has_value())
176 {
177 ARMARX_CHECK_GREATER(mask->array().sum(), 0)
178 << "At least one element has to be valid. Here, all elements are masked out!";
179 }
180
181 // index of the min element
182 Grid::Index row = 0;
183 Grid::Index col = 0;
184
185 // value of the min element
186 float minVal = std::numeric_limits<float>::max();
187
188 for (int r = 0; r < grid.rows(); r++)
189 {
190 for (int c = 0; c < grid.cols(); c++)
191 {
192 if (mask.has_value())
193 {
194 if (not mask.value()(r, c)) // skip invalid cells
195 {
196 continue;
197 }
198
199 const float currentVal = grid(r, c);
200 if (currentVal < minVal)
201 {
202 minVal = currentVal;
203 row = r;
204 col = c;
205 }
206 }
207 }
208 }
209
210 if (minVal == std::numeric_limits<float>::max())
211 {
212 return {.value = -1, .index = {-1, -1}, .position = {-1, -1}};
213 };
214
215 return {.value = minVal, .index = {row, col}, .position = toPositionGlobal({row, col})};
216 }
217
218 void
219 Costmap::forEachCell(const std::function<void(const Index&)>& f) const
220 {
221 for (int r = 0; r < grid.rows(); r++)
222 {
223 for (int c = 0; c < grid.cols(); c++)
224 {
225 f({r, c});
226 }
227 }
228 }
229
230 void
231 Costmap::forEachCellParallel(const std::function<void(const Index&)>& f) const
232 {
233#pragma omp parallel for schedule(static)
234 for (int r = 0; r < grid.rows(); r++)
235 {
236 for (int c = 0; c < grid.cols(); c++)
237 {
238 f({r, c});
239 }
240 }
241 }
242
243 void
245 {
247
248 ARMARX_CHECK(filter.matrix.rows() % 2 == 1 and filter.matrix.cols() % 2 == 1);
249
250 if (mask.has_value())
251 {
252 ARMARX_CHECK_GREATER(mask->array().sum(), 0)
253 << "At least one element has to be valid. Here, all elements are masked out!";
254 }
255
256 Grid newGrid(grid.rows(), grid.cols());
257 const double max = grid.maxCoeff();
258 newGrid.setConstant(max);
259 for (int r = 0; r < grid.rows(); r++)
260 {
261 for (int c = 0; c < grid.cols(); c++)
262 {
263 if (mask.has_value())
264 {
265 if (not mask.value()(r, c)) // skip invalid cells
266 {
267 continue;
268 }
269
270 Eigen::MatrixXf valueMatrix(filter.matrix.rows(), filter.matrix.cols());
271 const int radius_r = (filter.matrix.rows() - 1) / 2;
272 const int radius_c = (filter.matrix.cols() - 1) / 2;
273 valueMatrix.setConstant(max);
274 for (int i = -radius_r; i <= radius_r; i++)
275 {
276 for (int j = -radius_c; j <= radius_c; j++)
277 {
278 const int ri = r + i;
279 const int cj = c + j;
280 if (ri > 0 and ri < grid.rows() and cj > 0 and cj < grid.cols() and
281 mask.value()(ri, cj))
282 {
283 valueMatrix(radius_r + i, radius_c + j) = grid(ri, cj);
284 }
285 }
286 }
287 const double sum = filter.matrix.sum();
288 if (sum > 0)
289 {
290 const float filteredValue =
291 (valueMatrix.cwiseProduct(filter.matrix)).sum() / sum;
292 newGrid(r, c) =
293 filter.useMinimum ? std::min(grid(r, c), filteredValue) : filteredValue;
294 }
295 }
296 }
297 }
298 grid = newGrid;
299 }
300
302 Costmap::params() const noexcept
303 {
304 return parameters;
305 }
306
307 const Costmap::Grid&
309 {
310 return grid;
311 }
312
313 bool
315 {
316 const auto v = toVertex(p);
317 return grid(v.index.x(), v.index.y()) == 0.F;
318 }
319
320 bool
322 {
325 other.validateSizes();
326 ARMARX_CHECK_EQUAL(grid.rows(), other.grid.rows());
327 ARMARX_CHECK_EQUAL(grid.cols(), other.grid.cols());
328
329 ARMARX_CHECK_EQUAL(global_T_costmap.matrix(), other.global_T_costmap.matrix());
330
331 // add the costs of the other grid to this one by either min or max operator
332 switch (mode)
333 {
334 case AddMode::MIN:
335 grid.array() = other.grid.array().cwiseMin(grid.array());
336 break;
337
338 case AddMode::MAX:
339 grid.array() = other.grid.array().cwiseMax(grid.array());
340 break;
341 }
342
343
344 if (not mask.has_value())
345 {
346 mask = Mask(grid.rows(), grid.cols());
347 mask->setOnes();
348 }
349
350 Mask otherMask(grid.rows(), grid.cols());
351 otherMask.setOnes();
352
353 if (other.mask.has_value())
354 {
356
357 // union of masks
358 ARMARX_DEBUG << "Union of masks";
359 otherMask.array() *= other.mask->array();
360 }
361
363 mask->array() *= otherMask.array();
364
365 ARMARX_VERBOSE << "Fraction of valid cells: "
366 << mask->array().cast<float>().sum() / mask->size();
367
368 // apply mask to grid => all invalid points will be 0
370 grid.array() *= mask->cast<float>().array();
371
372 return true;
373 }
374
375 bool
376 Costmap::add(const Costmap& other, const float weight)
377 {
378 // FIXME: This function is broken when global_T_costmap is not an identity matrix
379 // Explanation:
380 // - If both costmap have different rotation, just iterating the grids is not possible
381 // - If both have the same rotation, then we would need a more clever approach to determine
382 // the regions where we can combine the values.
383 // ensure that both grid and mask are the same size
384
386
388 other.validateSizes();
389
390 const auto startIdx = toVertex(other.sceneBounds.min);
391
392 // merge other grid into this one => use max costs
393
394 // It might happen that this costmap is not large enough to cover the other costmap
395 // In this case, only add the part to this costmap that is possible.
396 const int rows = std::min(other.grid.rows(), grid.rows() - startIdx.index.x());
397 const int cols = std::min(other.grid.cols(), grid.cols() - startIdx.index.y());
398
399 ARMARX_VERBOSE << "Adding other grid to region (" << startIdx.index.x() << ", "
400 << startIdx.index.y() << "), " << " (" << startIdx.index.x() + rows << ", "
401 << startIdx.index.y() + cols << ")";
402
404
405 // add the costs of the other mask to this one by a weighting factor
406 grid.block(startIdx.index.x(), startIdx.index.y(), rows, cols).array() +=
407 weight * other.grid.block(0, 0, rows, cols).array();
408
409 if (not mask.has_value())
410 {
411 mask = Mask(grid.rows(), grid.cols());
412 mask->setOnes();
413 }
414
415 ARMARX_VERBOSE << "Fraction of valid cells before merge: "
416 << mask->array().cast<float>().sum() / mask->size();
417
418 Mask otherMask(grid.rows(), grid.cols());
419 otherMask.setZero();
420 otherMask.block(startIdx.index.x(), startIdx.index.y(), rows, cols).setOnes();
421
422 if (other.mask.has_value())
423 {
425
426 // union of masks
427 ARMARX_DEBUG << "Union of masks";
428 otherMask.block(startIdx.index.x(), startIdx.index.y(), rows, cols).array() *=
429 other.mask->block(0, 0, rows, cols).array();
430 }
431
433 mask->array() *= otherMask.array();
434
435 ARMARX_VERBOSE << "Fraction of valid cells: "
436 << mask->array().cast<float>().sum() / mask->size();
437
438 // apply mask to grid => all invalid points will be 0
440 grid.array() *= mask->cast<float>().array();
441
442 return true;
443 }
444
445 bool
446 Costmap::add(const Costmap& other, const AddMode mode, const bool keepThis)
447 {
450 other.validateSizes();
451
452 // General implementation: The costmaps can be rotated differently and have different origins
453 // Thus, we need to find the closest corresponding entry for each entry of the other costmap
454
455 if (not mask.has_value())
456 {
457 mask = Mask(grid.rows(), grid.cols());
458 mask->setOnes();
459 }
460 Mask otherMask;
461 if (other.mask.has_value())
462 {
463 otherMask = other.mask.value();
464 }
465 else
466 {
467 otherMask = Mask(other.grid.rows(), other.grid.cols());
468 otherMask.setOnes();
469 }
470
471 this->forEachCell(
472 [&](const Index& thisIdx)
473 {
474 const Position pos = this->toPositionGlobal(thisIdx);
475 const auto otherVOpt = other.toVertexOrInvalid(pos);
476
477 // Caution: the behavior for masked out cells may be different to previous implementation
478 // Especially masked out cells were not merged in some implementations
479 if (not otherVOpt.has_value())
480 {
481 // position not in other costmap
482 if (not keepThis)
483 {
484 (*this->mask)(thisIdx.x(), thisIdx.y()) = false;
485 }
486 return;
487 }
488
489 const Index otherIdx = otherVOpt->index;
490
491 // merge masks
492 if (other.mask.has_value())
493 {
494 (*this->mask)(thisIdx.x(), thisIdx.y()) &=
495 other.mask->operator()(otherIdx.x(), otherIdx.y());
496 }
497
498 // add the costs of the other grid to this one by either min or max operator
499 switch (mode)
500 {
501 case AddMode::MIN:
502 this->grid(thisIdx.x(), thisIdx.y()) =
503 std::min(other.grid(otherIdx.x(), otherIdx.y()),
504 this->grid(thisIdx.x(), thisIdx.y()));
505 break;
506 case AddMode::MAX:
507 this->grid(thisIdx.x(), thisIdx.y()) =
508 std::max(other.grid(otherIdx.x(), otherIdx.y()),
509 this->grid(thisIdx.x(), thisIdx.y()));
510 break;
512 this->grid(thisIdx.x(), thisIdx.y()) *=
513 other.grid(otherIdx.x(), otherIdx.y());
514 break;
515 default:
516 throw armarx::InvalidArgumentException{
517 "AddMode is not handled in Costmap::add(...)"};
518 }
519 });
520
521 // apply mask to grid => all invalid points will be 0
523 grid.array() *= mask->cast<float>().array();
524
525 return true;
526 }
527
528 const std::optional<Costmap::Mask>&
529 Costmap::getMask() const noexcept
530 {
531 return mask;
532 }
533
534 std::optional<Costmap::Mask>&
536 {
537 return mask;
538 }
539
540 std::optional<float>
542 {
543 if (not isValid(index))
544 {
545 ARMARX_IMPORTANT << "Requested index " << index << " but it is masked out!";
546 return std::nullopt;
547 }
548
549 return grid(index.x(), index.y());
550 }
551
552 std::optional<float>
553 Costmap::value(const Position& position) const
554 {
555 ARMARX_DEBUG << "value ...";
556
557 const auto v = toVertex(position);
558 return value(v.index);
559 }
560
561 void
563 {
565
566 const auto [min, max] = getValidBoundingBox();
567
568 // calculate new scene bounds based on bounding box
569 const auto newSceneBounds = SceneBounds{
570 .min = sceneBounds.min + parameters.cellSize * Eigen::Vector2f{min.cast<float>()},
571 .max = sceneBounds.min + parameters.cellSize * Eigen::Vector2f{max.cast<float>()}};
572
573 ARMARX_VERBOSE << "Cutting grid to valid bounding box: " << min << " and " << max;
574
575 const auto newGrid =
576 grid.block(min.x(), min.y(), max.x() - min.x() + 1, max.y() - min.y() + 1);
577 grid = newGrid;
578
579 if (mask.has_value())
580 {
581 ARMARX_VERBOSE << "Cutting mask to valid bounding box: " << min << " and " << max;
582
583 const auto newMask =
584 mask->block(min.x(), min.y(), max.x() - min.x() + 1, max.y() - min.y() + 1);
585 mask = newMask;
586 }
587
588 // update scene bounds
589 sceneBounds.min = newSceneBounds.min;
590 sceneBounds.max = newSceneBounds.max;
591
592 ARMARX_VERBOSE << "New scene bounds: " << sceneBounds.min << " and " << sceneBounds.max;
593 }
594
595 Costmap
596 Costmap::mergeInto(const std::vector<Costmap>& costmaps,
597 const std::vector<float>& weights) const
598 {
599 ARMARX_CHECK_EQUAL(costmaps.size() + 1, weights.size());
600
601 const std::vector<float> costmapWeights(weights.begin(), weights.end() - 1);
602 ARMARX_CHECK_EQUAL(costmapWeights.size(), costmaps.size());
603
604 ARMARX_VERBOSE << "Merging into with weights " << weights;
605
606 // create copy of this costmap
607 Costmap mergedCostmap = *this;
608
609 ARMARX_CHECK(mergedCostmap.mask.has_value());
610
611 // we iterate over all cells of THIS costmap. For each position, we perform a lookup in all other
612 // costmaps and merge the values according to the weights.
613 for (int x = 0; x < mergedCostmap.getGrid().rows(); x++)
614 {
615 for (int y = 0; y < mergedCostmap.getGrid().cols(); y++)
616 {
618 const Costmap::Position position = mergedCostmap.toPositionGlobal(Index{x, y});
619
620 // merge masks
621 for (const auto& costmap : costmaps)
622 {
624 mergedCostmap.mask.value()(x, y) &=
625 costmap.isValid(costmap.toVertex(position).index);
626 }
627
628 if (mergedCostmap.isValid(Index{x, y}))
629 {
630 float newVal = 0;
631 for (const auto& [weight, costmap] :
632 ranges::views::zip(costmapWeights, costmaps))
633 {
634 const auto otherCostmapVal = costmap.value(position);
635 ARMARX_CHECK(otherCostmapVal.has_value());
636
637 newVal += weight * otherCostmapVal.value();
638 }
639
640 newVal += weights.back() * mergedCostmap.grid(x, y);
641 mergedCostmap.grid(x, y) = newVal;
642 }
643 else
644 {
645 mergedCostmap.grid(x, y) = 0;
646 }
647 }
648 }
649
650 return mergedCostmap;
651 }
652
654 Costmap::Filter::gaussian(int radius, float sigma)
655 {
656 ARMARX_CHECK(radius > 0 and sigma > 0.);
657 Filter gaussianFilter{.matrix = Eigen::MatrixXf::Zero(radius * 2 + 1, radius * 2 + 1)};
658 for (int i = -radius; i <= radius; i++)
659 {
660 for (int j = -radius; j <= radius; j++)
661 {
662 gaussianFilter.matrix(radius + i, radius + j) =
663 std::exp(-0.5 * (std::pow(i / sigma, 2.0) + std::pow(j / sigma, 2.0))) /
664 (2 * M_PI * sigma * sigma);
665 }
666 }
667 return gaussianFilter;
668 }
669
670 std::size_t
672 {
673 if (not mask.has_value())
674 {
675 return grid.cols() * grid.rows();
676 }
677
678 return mask->array().cast<int>().sum();
679 }
680
682 Costmap::findClosestValidIndex(const Costmap::Index& index, int radius)
683 {
685
686 if (isValid(index))
687 {
688 return index;
689 }
690
691 const auto x = index.x();
692 const auto y = index.y();
693
694 // Check all cells in a radius around the given index
695 for (int r = 1; r < radius; r++)
696 {
697 for (int i = -r; i <= r; i++)
698 {
699 for (int j = -r; j <= r; j++)
700 {
701 const auto idx = Costmap::Index{x + i, y + j};
702 if (isValid(idx))
703 {
704 return idx;
705 }
706 }
707 }
708 }
709
710 ARMARX_ERROR << "No valid index found in the vicinity of " << index;
711 return index;
712 }
713
714 std::pair<Costmap::Index, Costmap::Index>
716 {
717 if (not mask.has_value())
718 {
719 return {Index{0, 0}, Index{grid.rows() - 1, grid.cols() - 1}};
720 }
721
722 ARMARX_VERBOSE << "Fraction of valid elements: "
723 << mask->cast<float>().sum() / mask->size();
724
726
727 Index min = Index{grid.rows(), grid.cols()};
728 Index max = Index{0, 0};
729
730 for (int x = 0; x < grid.rows(); x++)
731 {
732 for (int y = 0; y < grid.cols(); y++)
733 {
734 if (mask.value()(x, y))
735 {
736 min.x() = std::min(min.x(), x);
737 min.y() = std::min(min.y(), y);
738 max.x() = std::max(max.x(), x);
739 max.y() = std::max(max.y(), y);
740 }
741 }
742 }
743
744 return {min, max};
745 }
746
747 void
749 {
751
752 // Exclude all cells that are not reachable from the given position
753 // That is, they are not reachable over cells with a value > 0
754 // This is done by a simple flood fill algorithm
755
756 const auto v = toVertex(position);
757 auto startIdx = v.index;
758
759 // Find a valid start index
760 const auto [aabbMin, aabbMax] = getValidBoundingBox();
761 startIdx.x() = std::max(startIdx.x(), aabbMin.x());
762 startIdx.y() = std::max(startIdx.y(), aabbMin.y());
763 startIdx.x() = std::min(startIdx.x(), aabbMax.x());
764 startIdx.y() = std::min(startIdx.y(), aabbMax.y());
765
766 startIdx = findClosestValidIndex(startIdx);
767
768 // TODO: Good start index if the position is not valid
769
770 // Check validity before resetting mask
771 ARMARX_INFO << "Start index: " << startIdx;
772 ARMARX_CHECK(isValid(startIdx));
773
774 // Reset mask
775 grid.array() *= mask->cast<float>().array();
776 auto newMask = mask.value();
777 newMask.setZero();
778
779 struct pair_hash
780 {
781 inline std::size_t
782 operator()(const std::pair<int, int>& v) const noexcept
783 {
784 return v.first * 31 + v.second;
785 }
786 };
787
788 std::unordered_set<std::pair<int, int>, pair_hash> stack;
789 auto visited = newMask;
790 stack.emplace(startIdx.x(), startIdx.y());
791 int i = 0;
792 while (not stack.empty())
793 {
794 if (i++ > 1000)
795 {
796 ARMARX_INFO << "After 1000 iterations, current element is " << stack.begin()->first
797 << ", " << stack.begin()->second;
798 i = 0;
799 }
800 const auto it = stack.begin();
801 const auto idx = Index{it->first, it->second};
802 stack.erase(it);
803
804 // print all elements
805 /*for (const auto& element : stack)
806 {
807 ARMARX_INFO << "Stack element: " << element.first << ", " << element.second;
808 }*/
809
810 visited(idx.x(), idx.y()) = 1;
811
812 //ARMARX_INFO << "Checking " << idx;
813
814 if (not isValid(idx))
815 {
816 continue;
817 }
818
819 if (grid(idx.x(), idx.y()) == 0.F)
820 {
821 continue;
822 }
823
824 //ARMARX_INFO << "Including " << idx;
825
826 newMask.array()(idx.x(), idx.y()) = 1;
827
828 if (const std::pair<int, int> element = {idx.x() + 1, idx.y()};
829 element.first < visited.rows() && not visited(element.first, element.second))
830 {
831 stack.emplace(element);
832 }
833 if (const std::pair<int, int> element = {idx.x() - 1, idx.y()};
834 element.first > 0 && not visited(element.first, element.second))
835 {
836 stack.emplace(element);
837 }
838 if (const std::pair<int, int> element = {idx.x(), idx.y() + 1};
839 element.second < visited.cols() && not visited(element.first, element.second))
840 {
841 stack.emplace(element);
842 }
843 if (const std::pair<int, int> element = {idx.x(), idx.y() - 1};
844 element.second > 0 && not visited(element.first, element.second))
845 {
846 stack.emplace(element);
847 }
848 }
849
850 mask.emplace(newMask);
851 }
852
853 Costmap
854 Costmap::WithSameDimsAs(const Costmap& other, const Grid& grid, const std::optional<Mask>& mask)
855 {
856 return {grid, other.params(), other.getLocalSceneBounds(), mask, other.origin()};
857 }
858
859 std::optional<Costmap::Vertex>
860 Costmap::findClosestCollisionFreeVertex(const Position& position, float maxDistance) const
861 {
863
864 // Already collision-free?
865 if (!isInCollision(position))
866 {
867 return toVertex(position);
868 }
869
870 const Vertex startVertex = toVertex(position);
871 const int maxRadiusCells = static_cast<int>(std::ceil(maxDistance / parameters.cellSize));
872
873 // Search in expanding circles
874 for (int radius = 1; radius <= maxRadiusCells; ++radius)
875 {
876 std::optional<Vertex> bestVertex;
877 float bestDistanceSq = std::numeric_limits<float>::max();
878
879 // Check perimeter of current radius (optimization)
880 for (int i = -radius; i <= radius; ++i)
881 {
882 for (int j = -radius; j <= radius; ++j)
883 {
884 // Only check perimeter cells
885 if (std::abs(i) != radius && std::abs(j) != radius)
886 {
887 continue;
888 }
889
890 const Index idx{startVertex.index.x() + i, startVertex.index.y() + j};
891
892 // Check bounds and validity
893 if (!isValid(idx))
894 {
895 continue;
896 }
897
898 // Check if collision-free (grid value > 0)
899 if (grid(idx.x(), idx.y()) > 0.F)
900 {
901 const Position candidatePos = toPositionGlobal(idx);
902 const float distSq = (candidatePos - position).squaredNorm();
903
904 if (distSq < bestDistanceSq)
905 {
906 bestDistanceSq = distSq;
907 bestVertex = Vertex{.index = idx, .position = candidatePos};
908 }
909 }
910 }
911 }
912
913 // Found at thisradius? Return it (closest will be at smallest radius)
914 if (bestVertex)
915 {
916 return bestVertex;
917 }
918 }
919
920 // No collision-free vertex found
921 return std::nullopt;
922 }
923
924} // namespace armarx::navigation::algorithms
uint8_t index
#define M_PI
Definition MathTools.h:17
#define VAROUT(x)
constexpr T c
std::optional< Vertex > findClosestCollisionFreeVertex(const Position &position, float maxDistance) const
Find the closest collision-free position to a given position.
Definition Costmap.cpp:860
Costmap mergeInto(const std::vector< Costmap > &costmaps, const std::vector< float > &weights) const
Merge a list of costmaps into this costmap.
Definition Costmap.cpp:596
bool addCostmapWithSameSizeAndPose(const Costmap &other, AddMode mode)
Definition Costmap.cpp:321
const core::Pose2D & origin() const
Definition Costmap.h:171
static Costmap WithSameDimsAs(const Costmap &other, const Grid &grid, const std::optional< Mask > &mask=std::nullopt)
Create a costmap with the same dimensions and parameters as the given one.
Definition Costmap.cpp:854
const SceneBounds & getLocalSceneBounds() const noexcept
Definition Costmap.cpp:165
bool isInCollision(const Position &p) const
Definition Costmap.cpp:314
Position toPositionLocal(const Index &index) const
Definition Costmap.cpp:61
std::pair< Index, Index > getValidBoundingBox() const
Definition Costmap.cpp:715
Vertex toVertex(const Position &globalPosition) const
Definition Costmap.cpp:146
void excludeUnreachable(const Position &position)
Definition Costmap.cpp:748
std::size_t numberOfValidElements() const
Definition Costmap.cpp:671
const Parameters & params() const noexcept
Definition Costmap.cpp:302
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic > Mask
Definition Costmap.h:60
std::optional< Costmap::Mask > & getMutableMask() noexcept
Definition Costmap.cpp:535
Position toPositionGlobal(const Index &index) const
Definition Costmap.cpp:75
const std::optional< Mask > & getMask() const noexcept
Definition Costmap.cpp:529
std::optional< Costmap::Vertex > toVertexOrInvalid(const Eigen::Vector2f &globalPosition) const
Definition Costmap.cpp:132
void filter(const Filter &filter)
Definition Costmap.cpp:244
void forEachCellParallel(const std::function< void(const Index &)> &f) const
Definition Costmap.cpp:231
bool isValid(const Index &index) const noexcept
checks whether the cell is masked out
Definition Costmap.cpp:82
bool add(const Costmap &other, float weight=1.0)
Definition Costmap.cpp:376
std::pair< int, int > toIndexUnsanitized(const Eigen::Vector2f &globalPosition) const
Definition Costmap.cpp:116
Costmap(const Grid &grid, const Parameters &parameters, const SceneBounds &sceneBounds, const std::optional< Mask > &mask=std::nullopt, const core::Pose2D &origin=core::Pose2D::Identity())
Definition Costmap.cpp:31
void forEachCell(const std::function< void(const Index &)> &f) const
Definition Costmap.cpp:219
std::optional< float > value(const Index &index) const
Definition Costmap.cpp:541
#define ARMARX_CHECK_GREATER(lhs, rhs)
This macro evaluates whether lhs is greater (>) than rhs and if it turns out to be false it will thro...
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#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_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
This file is part of ArmarX.
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
std::vector< T > min(const std::vector< T > &v1, const std::vector< T > &v2)
static Filter gaussian(int radius=2, float sigma=1.)
Definition Costmap.cpp:654
#define ARMARX_TRACE
Definition trace.h:77