HandoverCostmapBuilder.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 ( )
17 * @date 2026
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
23
24#include <cmath>
25#include <optional>
26
27#include <Eigen/Geometry>
28
30
36
38
40{
41
42
44 const Parameters& handoverParameters) :
45 costmapParameters_(costmapParameters), handoverParameters_(handoverParameters)
46 {
47 ARMARX_CHECK_POSITIVE(handoverParameters_.handoverDistanceMin);
48 ARMARX_CHECK_POSITIVE(handoverParameters_.handoverDistanceMax);
49 ARMARX_CHECK_GREATER(handoverParameters_.handoverDistanceMax,
50 handoverParameters_.handoverDistanceMin);
51
52 ARMARX_CHECK_LESS_EQUAL(handoverParameters_.handoverAngleMin,
53 handoverParameters_.handoverAngleMax);
54
55 ARMARX_CHECK_LESS_EQUAL(handoverParameters_.handoverAngleMin,
56 handoverParameters_.handoverAnglePreference);
57 ARMARX_CHECK_LESS_EQUAL(handoverParameters_.handoverAnglePreference,
58 handoverParameters_.handoverAngleMax);
59 }
60
61 std::optional<Costmap>
63 {
64 const std::optional<core::Pose2D> global_T_human_opt =
66
67 if (not global_T_human_opt.has_value())
68 {
69 // cannot compute human pose
70 return std::nullopt;
71 }
72
73 // obtain scene information from human position
74 const auto sceneBounds = [this, global_T_human = global_T_human_opt.value()]
75 {
76 // scene bounds
77 const Eigen::Vector2f human_T_left{-handoverParameters_.humanSceneBoundOffsetSideLeft,
78 0};
79 const Eigen::Vector2f human_T_right{handoverParameters_.humanSceneBoundOffsetSideRight,
80 0};
81 const Eigen::Vector2f human_T_front{0, handoverParameters_.humanSceneBoundOffsetFront};
82
83 const Eigen::Vector2f human_T_back{0, -handoverParameters_.humanSceneBoundOffsetBehind};
84
85 SceneBounds initialSceneBounds;
86
87 const auto sceneBounds = computeSceneBounds({global_T_human * human_T_left,
88 global_T_human * human_T_right,
89 global_T_human * human_T_front,
90 global_T_human * human_T_back},
91 initialSceneBounds,
92 200);
93
94 return sceneBounds;
95 }();
96
97 // helper function to project a value to [-1, 1].
98 // Values outside the min/max range are clamped.
99 // min maps to -1.0, max maps to 1.0 while preference maps to 0.0
100 const auto projectToInterval =
101 [](const float value, const float min, const float max, const float preference) -> float
102 {
104 if (value <= min)
105 {
106 return -1.0f;
107 }
108 if (value >= max)
109 {
110 return 1.0f;
111 }
112 if (value == preference)
113 {
114 return 0.0f;
115 }
116 if (value < preference)
117 {
118 return -((preference - value) / (preference - min));
119 }
120 // value > preference
121 return (value - preference) / (max - preference);
122 };
123
124 const auto costFn = [this, &projectToInterval](float distanceToObstacle,
125 float distanceToHuman,
126 float angleToHuman) -> std::optional<float>
127 {
128 // check bounds
129 if (distanceToHuman < handoverParameters_.handoverDistanceMin ||
130 distanceToHuman > handoverParameters_.handoverDistanceMax)
131 {
132 return std::nullopt;
133 }
134
135 if (angleToHuman < handoverParameters_.handoverAngleMin ||
136 angleToHuman > handoverParameters_.handoverAngleMax)
137 {
138 return std::nullopt;
139 }
140
141
142 // distance cost (scaled to [-1, 1])
143 const float xDistance =
144 projectToInterval(distanceToHuman,
145 handoverParameters_.handoverDistanceMin,
146 handoverParameters_.handoverDistanceMax,
147 handoverParameters_.handoverDistancePreference);
148 const float xAngle = projectToInterval(angleToHuman,
149 handoverParameters_.handoverAngleMin,
150 handoverParameters_.handoverAngleMax,
151 handoverParameters_.handoverAnglePreference);
152
153 // compute factors that scale cost based on distance and angle
154 // both factors are in [0, +inf); 0 means optimal, +inf means worst
155 const double fDistance = std::abs(std::tan(xDistance * M_PI_2));
156 const double fAngle = std::abs(std::tan(xAngle * M_PI_2));
157
158 // in order to avoid multiplication by zero, we add an offset of 1. This avoids
159 // zero-cost areas that cannot be distinguished.
160
161 // base cost is the product of both factors
162 const double f = (fDistance + 1) * (fAngle + 1);
163
164 // slight additive penalty for candidates too close to an obstacle, instead
165 // of scaling the whole cost by the (raw) distance to the nearest obstacle
166 const double obstaclePenalty =
167 (distanceToObstacle < handoverParameters_.obstacleProximityThreshold)
168 ? handoverParameters_.obstacleProximityPenalty
169 : 0.0;
170
171 return static_cast<float>(f + obstaclePenalty);
172 };
173
174
175 // create costmap
176 auto grid = CostmapBuilder::createUniformGrid(sceneBounds, costmapParameters_);
177
178 Costmap costmap(grid, costmapParameters_, sceneBounds);
179 // the loop below writes through `getMutableMask()`, which requires the mask
180 // to already hold a value (otherwise it is a disengaged std::optional). Every
181 // cell visited by the loop overwrites its mask entry unconditionally, so the
182 // mask's initial content here is irrelevant, only its presence and size matter.
184
185 costmap.forEachCell(
186 [&costmap, &sceneInfo, global_T_human = global_T_human_opt.value(), &costFn](
187 const Costmap::Index& idx)
188 {
189 // obtain global position of the cell
190 const auto global_P_cell = costmap.toPositionGlobal(idx);
191
192 // initialize based on distance map
193
194 const auto vertex = sceneInfo.distanceMap.toVertexOrInvalid(global_P_cell);
195
196 // initialize as invalid
197 {
198 costmap.getMutableGrid()(idx.x(), idx.y()) = 0.0f;
199 costmap.getMutableMask()->operator()(idx.x(), idx.y()) = false;
200 }
201
202 if (!vertex.has_value())
203 {
204 // outside distance map or invalid
205 return;
206 }
207
208 // Use isValid()+direct grid access instead of value(), which logs an
209 // ARMARX_IMPORTANT message on every masked-out cell. Since the human-
210 // centered local scene bounds routinely extend beyond the valid region
211 // of the (larger, static) distance map, that path is hit very frequently
212 // here and would otherwise spam the log.
213 if (!sceneInfo.distanceMap.isValid(vertex->index))
214 {
215 // masked out / invalid distance
216 return;
217 }
218
219 // Now we know that the cell is valid. Hence, we can compute its cost.
220 {
221
222 const float distanceToObstacle =
223 sceneInfo.distanceMap.getGrid()(vertex->index.x(), vertex->index.y());
224 const Eigen::Vector2f human_P_cell = global_T_human.inverse() * global_P_cell;
225
226 const float distanceToHuman = human_P_cell.norm();
227
228 const float angleToHuman = std::atan2(human_P_cell.y(), human_P_cell.x());
229
230
231 const auto costOpt = costFn(distanceToObstacle, distanceToHuman, angleToHuman);
232
233 if (not costOpt.has_value())
234 {
235 // invalid cost
236 return;
237 }
238
239 costmap.getMutableGrid()(idx.x(), idx.y()) = costOpt.value();
240 costmap.getMutableMask()->operator()(idx.x(), idx.y()) = true;
241 }
242 });
243
244 return costmap;
245 }
246
247 std::optional<core::Pose2D>
249 {
250 const auto costmapOpt = create(sceneInfo);
251
252 if (not costmapOpt.has_value())
253 {
254 return std::nullopt;
255 }
256
257 const auto& costmap = costmapOpt.value();
258 const auto optimum = costmap.optimum();
259
260 if (optimum.index.isConstant(-1))
261 {
262 // no valid optimum found
263 return std::nullopt;
264 }
265
266 // compute orientation towards human
267 const auto global_P_human = armem::human::computeMeanPosition(sceneInfo.humanPose);
268
269 if (not global_P_human.has_value())
270 {
271 // cannot compute human pose
272 return std::nullopt;
273 }
274
275 const auto global_P_handover = optimum.position;
276
277 const Eigen::Vector2f direction =
278 (global_P_human->head<2>() - global_P_handover).normalized();
279
280 const float yaw = std::atan2(direction.y(), direction.x());
281
282 return core::Pose2D{Eigen::Translation2f{global_P_handover.x(), global_P_handover.y()} *
283 Eigen::Rotation2Df{yaw}};
284 }
285} // namespace armarx::navigation::algorithms::costmap
if(!yyvaluep)
Definition Grammar.cpp:645
static Eigen::MatrixXf createUniformGrid(const SceneBounds &sceneBounds, const Costmap::Parameters &parameters)
bool isValid(const Index &index) const noexcept
checks whether the cell is masked out
Definition Costmap.cpp:82
std::optional< core::Pose2D > getOptimalHandoverPose(const SceneInformation &sceneInfo)
std::optional< Costmap > create(const SceneInformation &sceneInfo)
HandoverCostmapBuilder(const Costmap::Parameters &costmapParameters, const Parameters &handoverParameters)
#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_POSITIVE(number)
This macro evaluates whether number is positive (> 0) and if it turns out to be false it will throw a...
#define ARMARX_CHECK_LESS_EQUAL(lhs, rhs)
This macro evaluates whether lhs is less or equal (<=) rhs and if it turns out to be false it will th...
std::optional< Eigen::Vector3f > computeMeanPosition(const HumanPose &humanPose, KeyPointCoordinateSystem coordSystem)
Definition util.cpp:31
std::optional< Eigen::Isometry2f > calculatePose(const HumanPose &humanPose)
Definition util.cpp:273
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
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
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)
armarx::armem::human::HumanPose humanPose
information about the human for handover