Costmap2DWrapper.h
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
17#pragma once
18
19#include <algorithm>
20#include <array>
21#include <cmath>
22#include <limits>
23
24#include <Eigen/Core>
25#include <Eigen/Geometry>
26
28
30
31#include <ceres/ceres.h>
32
34{
35 /**
36 * Differentiable wrapper around the standard 2-D distance-to-obstacle costmap.
37 *
38 * Provides bilinear interpolation in (x, y) for Ceres AutoDiff, with a
39 * resolution-aware conservative correction:
40 * - the interpolated distance is reduced by cellSize/2 to account for the
41 * fact that the true obstacle boundary can be up to half a cell away
42 * from the stored cell-center distance;
43 * - if any of the four contributing cells is a collision cell (value <= 0),
44 * the result is capped at -cellSize/2 so the optimizer never sees a
45 * falsely positive clearance.
46 */
48 {
49 public:
51 {
52 cell_size_ = map_.params().cellSize;
53 }
54
55 template <typename T>
56 bool operator()(const T* const x_ptr, const T* const y_ptr, T* out_cost) const
57 {
58 // World -> local
59 const Eigen::Transform<T, 2, 1> tfT = convertTransform<T>(map_.origin());
60 const Eigen::Matrix<T, 2, 1> localPosition = tfT.inverse() * Eigen::Matrix<T, 2, 1>{*x_ptr, *y_ptr};
61
62 const T cell = T(cell_size_);
63 const T half_cell = cell / T(2.0);
64
65 // Continuous cell coordinates. Cell centers are at integer values.
66 const T vX = (localPosition.x() - half_cell - T(map_.getLocalSceneBounds().min.x())) / cell;
67 const T vY = (localPosition.y() - half_cell - T(map_.getLocalSceneBounds().min.y())) / cell;
68
69 // Degeneracy guard: shift slightly so floor/ceil behave predictably
70 // when vX/vY are exactly integers (i.e. on cell centers).
71 const T vX_shift = vX - T(0.01);
72 const T vY_shift = vY - T(0.01);
73
74 int iXlow = static_cast<int>(toFloat(ceres::floor(vX_shift)));
75 int iXhigh = static_cast<int>(toFloat(ceres::ceil(vX_shift)));
76 int iYlow = static_cast<int>(toFloat(ceres::floor(vY_shift)));
77 int iYhigh = static_cast<int>(toFloat(ceres::ceil(vY_shift)));
78
79 // Clamp to map bounds
80 const int maxX = static_cast<int>(map_.getGrid().rows()) - 1;
81 const int maxY = static_cast<int>(map_.getGrid().cols()) - 1;
82 iXlow = std::clamp(iXlow, 0, maxX);
83 iXhigh = std::clamp(iXhigh, 0, maxX);
84 iYlow = std::clamp(iYlow, 0, maxY);
85 iYhigh = std::clamp(iYhigh, 0, maxY);
86
87 // Retrieve four neighbour values. Treat invalid/masked cells as collision.
88 const double d_ll = queryCell(iXlow, iYlow);
89 const double d_hl = queryCell(iXhigh, iYlow);
90 const double d_lh = queryCell(iXlow, iYhigh);
91 const double d_hh = queryCell(iXhigh, iYhigh);
92
93 // Bilinear weights using the unshifted coordinates.
94 // Note: we intentionally use the unclamped indices for the weights
95 // so that w00+w10+w01+w11 == 1 even at boundaries.
96 const T w00 = (T(iXhigh) - vX) * (T(iYhigh) - vY); // (low, low)
97 const T w10 = (vX - T(iXlow)) * (T(iYhigh) - vY); // (high, low)
98 const T w01 = (T(iXhigh) - vX) * (vY - T(iYlow)); // (low, high)
99 const T w11 = (vX - T(iXlow)) * (vY - T(iYlow)); // (high, high)
100
101 T d_bilinear = w00 * T(d_ll) + w10 * T(d_hl) + w01 * T(d_lh) + w11 * T(d_hh);
102
103 // Degeneracy guard: if the query point lies exactly on a grid line,
104 // the weights sum to 0. Fall back to nearest-neighbour.
105 const T sumW = w00 + w10 + w01 + w11;
106 if (ceres::abs(sumW) < T(1e-6))
107 {
108 const std::array<std::pair<int, int>, 4> idxs = {{{iXlow, iYlow},
109 {iXhigh, iYlow},
110 {iXlow, iYhigh},
111 {iXhigh, iYhigh}}};
112 const std::array<double, 4> vals = {d_ll, d_hl, d_lh, d_hh};
113 double bestVal = vals[0];
114 double bestDist = std::numeric_limits<double>::max();
115 for (int k = 0; k < 4; ++k)
116 {
117 const double dx = static_cast<double>(idxs[k].first) - toFloat(vX);
118 const double dy = static_cast<double>(idxs[k].second) - toFloat(vY);
119 const double dist = dx * dx + dy * dy;
120 if (dist < bestDist)
121 {
122 bestDist = dist;
123 bestVal = vals[k];
124 }
125 }
126 d_bilinear = T(bestVal);
127 }
128
129 // Conservative correction based on grid resolution.
130 T d_conservative = d_bilinear - half_cell;
131
132 // If any contributing cell is in collision, never report a positive clearance.
133 const double d_min = std::min({d_ll, d_hl, d_lh, d_hh});
134 if (d_min <= 0.0 && d_conservative > -half_cell)
135 {
136 d_conservative = -half_cell;
137 }
138
139 out_cost[0] = d_conservative;
140 return true;
141 }
142
143 float cellSize() const noexcept
144 {
145 return cell_size_;
146 }
147
148 private:
150 float cell_size_;
151
152 double queryCell(int ix, int iy) const
153 {
155 if (!map_.isValid(idx))
156 {
157 // Invalid/masked/out-of-bounds cell -> treat as deep obstacle.
158 return -100.0;
159 }
160 const auto val = map_.value(idx);
161 if (!val.has_value())
162 {
163 return -100.0;
164 }
165 return static_cast<double>(val.value());
166 }
167
168 template <typename T>
169 static Eigen::Transform<T, 2, 1> convertTransform(const Eigen::Transform<float, 2, 1>& tf)
170 {
171 Eigen::Transform<T, 2, 1> out;
172 out.matrix() = tf.matrix().template cast<T>();
173 return out;
174 }
175
176 template <typename T>
177 static float toFloat(const T& val)
178 {
179 if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float>)
180 {
181 return static_cast<float>(val);
182 }
183 else
184 {
185 return static_cast<float>(val.a); // Jet
186 }
187 }
188 };
189} // namespace armarx::navigation::algorithms::spfa::smoothing
bool isValid(const Index &index) const noexcept
checks whether the cell is masked out
Definition Costmap.cpp:82
std::optional< float > value(const Index &index) const
Definition Costmap.cpp:541
bool operator()(const T *const x_ptr, const T *const y_ptr, T *out_cost) const
Costmap2DWrapper(const armarx::navigation::algorithms::Costmap &map)