Costmap3DWrapper.h
Go to the documentation of this file.
1#pragma once
2#include <algorithm>
3#include <cmath>
4#include <functional>
5#include <optional>
6
8
10
11#include <ceres/ceres.h>
12
14{
15
16 //
17 // A simple differentiable wrapper around Costmap3D.
18 // Produces a *continuous* obstacle cost for Ceres AutoDiff.
19 //
20 // Design choices:
21 // * bilinear interpolation in X/Y
22 // * nearest-neighbor in orientation (your grid is discrete anyway)
23 // * returns 0 if value() returns nullopt
24 // * everything templated so Ceres autodiff works
25 //
26 // Generated by GPT-5 on 2025-12-04
27
29 {
30 public:
31 using Position = Eigen::Vector2f;
33 using Index = Eigen::Array2i;
34
35 private:
36 // self-written functions
37 template <typename IndexT, typename WeightT, int size>
38 struct LinearInterpolationResult
39 {
40 std::array<IndexT, size> indices;
41 std::array<WeightT, size> weights;
42
43 template <typename T>
44 T
45 calcWeightedAccess(std::function<T(const IndexT&)> accessor) const
46 {
47 T result = T(0);
48 for (int i = 0; i < size; ++i)
49 {
50 result += weights[i] * accessor(indices[i]);
51 }
52 return result;
53 }
54 };
55
56 template <typename IndexT1, typename IndexT2, typename CommonWeightT, int size1, int size2>
57 static LinearInterpolationResult<std::tuple<IndexT1, IndexT2>, CommonWeightT, size1 * size2>
58 combineLinearInterpolationResults(
59 LinearInterpolationResult<IndexT1, CommonWeightT, size1> r1,
60 LinearInterpolationResult<IndexT2, CommonWeightT, size2> r2)
61 {
62 LinearInterpolationResult<std::tuple<IndexT1, IndexT2>, CommonWeightT, size1 * size2>
63 result;
64 for (int i = 0; i < size1; ++i)
65 {
66 for (int j = 0; j < size2; ++j)
67 {
68 result.indices[i * size2 + j] = std::make_tuple(r1.indices[i], r2.indices[j]);
69 result.weights[i * size2 + j] = r1.weights[i] * r2.weights[j];
70 }
71 }
72 return result;
73 }
74
75 public:
77 map_(map)
78 {
79 cell_size_ = map_.params().cellSize; // [mm]
80 inv_cell_size_ = 1.0f / cell_size_;
81 grid_size_ = map_.getSize();
82 map_.convertToPseudoSDF();
83 }
84
85 /// Templated call operator – fully autodiff-friendly
86 template <typename T>
87 bool
88 operator()(const T* const x_ptr,
89 const T* const y_ptr,
90 const T* const theta_ptr,
91 T* out_cost) const
92 {
93 // Idea: First get indices for accessing using provided methods
94 // Then calculate interpolation using Jet types
95
96 const auto rotation_indices = closestRotationIndexFromDegreesBilinear(*theta_ptr);
97 const auto position_indices = toIndexBilinear(Eigen::Matrix<T, 2, 1>{*x_ptr, *y_ptr});
98 const auto combined_interpolation =
99 combineLinearInterpolationResults(rotation_indices, position_indices);
100 out_cost[0] = combined_interpolation.template calcWeightedAccess<T>(
101 [this](const std::tuple<int, Eigen::Array<int, 2, 1, 0, 2, 1>>& idx) -> T
102 {
103 auto& [rot, pos] = idx;
104 Costmap3D::Index posIdx = {pos.x(), pos.y()};
105 auto v = map_.value(posIdx, rot);
106
107 if (!v.has_value() /*|| v.value() < 2.0*/)
108 {
109 ARMARX_DEBUG << "Accessing masked-out costmap cell at index "
110 << VAROUT(posIdx) << " rot " << VAROUT(rot);
111 //return T(-100.0F);
112 }
113
114 T value = T(v.value());
115
116 // safety margin
117 value -= T(cell_size_ * 5.0F);
118
119 return value;
120 });
121 return true;
122 }
123
124 private:
126
127 float cell_size_;
128 float inv_cell_size_;
130
131 template <typename T>
132 static float
133 toFloat(const T& val)
134 {
135 if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float>)
136 return static_cast<float>(val);
137 else
138 return static_cast<float>(val.a); // Jet
139 }
140
141 // ----------- helper: wrap [0,360) --------------
142 template <typename T>
143 static T
144 wrapDegrees(const T& deg)
145 {
146 T d = deg;
147 d = d - T(360.0) * ceres::floor(d / T(360.0));
148 if (d > T(360.0))
149 d -= T(360.0);
150 if (d < T(0.0))
151 d += T(360.0);
152 return d;
153 }
154
155 int
156 wrapRotationIndex(const int index) const
157 {
158 int d = index;
159 if (d > int(map_.parameters.orientations - 1))
160 d -= int(map_.parameters.orientations);
161 if (d < int(0.0))
162 d += int(map_.parameters.orientations);
163 return d;
164 }
165
166 template <typename T>
167 LinearInterpolationResult<Costmap3D::RotationIndex, T, 2>
168 closestRotationIndexFromDegreesBilinear(const T& degrees_in) const
169 {
170 const T degrees = wrapDegrees(degrees_in);
171 const T deg_per_orientation = T(360.F) / T(map_.parameters.orientations);
172
173 // simply floor for lower one
174 const int lower_index = wrapRotationIndex(
175 static_cast<int>(toFloat(ceres::floor(degrees / deg_per_orientation))));
176 const int higher_index = wrapRotationIndex(lower_index + 1);
177
178 if (lower_index < 0 || lower_index > map_.parameters.orientations)
179 {
180 ARMARX_DEBUG << "lower_index out of range: " << lower_index;
181 ARMARX_DEBUG << "degrees: " << degrees;
182 ARMARX_DEBUG << VAROUT(deg_per_orientation);
183 }
184 if (lower_index < 0 || higher_index > map_.parameters.orientations)
185 {
186 ARMARX_DEBUG << "higher_index out of range: " << higher_index;
187 }
188
189 const T error_to_lower_normalized =
190 (degrees - T(lower_index) * deg_per_orientation) / deg_per_orientation;
191
192 return {.indices = {lower_index, higher_index},
193 .weights = {T(1) - error_to_lower_normalized, error_to_lower_normalized}};
194 }
195
196 template <typename T>
197 static Eigen::Transform<T, 2, 1>
198 convertTransform(const Eigen::Transform<float, 2, 1>& tf)
199 {
200 Eigen::Transform<T, 2, 1> out;
201 out.matrix() = tf.matrix().template cast<T>();
202 return out;
203 }
204
205 template <typename T>
206 LinearInterpolationResult<Costmap3D::Index, T, 4>
207 toIndexBilinear(const Eigen::Matrix<T, 2, 1>& globalPosition) const
208 {
209 // Transform world → local
210 auto tfT = convertTransform<T>(map_.global_T_Costmap3D);
211 Eigen::Matrix<T, 2, 1> localPosition = tfT.inverse() * globalPosition;
212
213 const T cell = T(map_.parameters.cellSize);
214
215 // Compute continuous cell coordinates
216 const T vX = (localPosition.x() - cell / T(2) - T(map_.sceneBounds.min.x())) / cell;
217
218 const T vY = (localPosition.y() - cell / T(2) - T(map_.sceneBounds.min.y())) / cell;
219
220 // Bilinear kernel: continuous cell coordinates must be bracketed
221 const T vX_shift = vX - T(0.01);
222 const T vY_shift = vY - T(0.01);
223
224 // Jet-safe floor/ceil
225 int iXlow = static_cast<int>(toFloat(ceres::floor(vX_shift)));
226 int iXhigh = static_cast<int>(toFloat(ceres::ceil(vX_shift)));
227 int iYlow = static_cast<int>(toFloat(ceres::floor(vY_shift)));
228 int iYhigh = static_cast<int>(toFloat(ceres::ceil(vY_shift)));
229
230 // Clamp to map bounds
231 const auto size = map_.getSize();
232 int iXlow_cl = std::clamp(iXlow, 0, size.x() - 1);
233 int iXhigh_cl = std::clamp(iXhigh, 0, size.x() - 1);
234 int iYlow_cl = std::clamp(iYlow, 0, size.y() - 1);
235 int iYhigh_cl = std::clamp(iYhigh, 0, size.y() - 1);
236
237 // Bilinear weights (fully Jet-differentiable)
238 // These must use T, not int
239 const T w00 = (T(iXhigh) - vX) * (T(iYhigh) - vY); // (low, low)
240 const T w10 = (vX - T(iXlow)) * (T(iYhigh) - vY); // (high, low)
241 const T w01 = (T(iXhigh) - vX) * (vY - T(iYlow)); // (low, high)
242 const T w11 = (vX - T(iXlow)) * (vY - T(iYlow)); // (high, high)
243
244 LinearInterpolationResult<Costmap3D::Index, T, 4> result;
245
246 result.indices[0] = {iXlow_cl, iYlow_cl};
247 result.indices[1] = {iXhigh_cl, iYlow_cl};
248 result.indices[2] = {iXlow_cl, iYhigh_cl};
249 result.indices[3] = {iXhigh_cl, iYhigh_cl};
250
251 result.weights[0] = w00;
252 result.weights[1] = w10;
253 result.weights[2] = w01;
254 result.weights[3] = w11;
255
256 return result;
257 }
258 };
259} // namespace armarx::navigation::algorithms::orientation_aware::smoothing
#define float
Definition 16_Level.h:22
uint8_t index
#define VAROUT(x)
bool operator()(const T *const x_ptr, const T *const y_ptr, const T *const theta_ptr, T *out_cost) const
Templated call operator – fully autodiff-friendly.
Costmap3DWrapper(const armarx::navigation::algorithms::orientation_aware::Costmap3D &map)
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
float toFloat(const std::string &input)
Converts a string to float and uses always dot as seperator.
int orientations
How many orientations of the robot each cell contains.
Definition Costmap3D.h:38