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 // final cost is product of both factors
162 const double f = (fDistance + 1) * (fAngle + 1);
163
164 return distanceToObstacle * f;
165 };
166
167
168 // create costmap
169 auto grid = CostmapBuilder::createUniformGrid(sceneBounds, costmapParameters_);
170
171 Costmap costmap(grid, costmapParameters_, sceneBounds);
172
173 costmap.forEachCell(
174 [&costmap, &sceneInfo, global_T_human = global_T_human_opt.value(), &costFn](
175 const Costmap::Index& idx)
176 {
177 // obtain global position of the cell
178 const auto global_P_cell = costmap.toPositionGlobal(idx);
179
180 // initialize based on distance map
181
182 const auto vertex = sceneInfo.distanceMap.toVertexOrInvalid(global_P_cell);
183
184 // initialize as invalid
185 {
186 costmap.getMutableGrid()(idx.x(), idx.y()) = 0.0f;
187 costmap.getMutableMask()->operator()(idx.x(), idx.y()) = false;
188 }
189
190 if (!vertex.has_value())
191 {
192 // outside distance map or invalid
193 return;
194 }
195
196 const auto d = sceneInfo.distanceMap.value(vertex->index);
197
198 if (!d.has_value())
199 {
200 // invalid distance
201 return;
202 }
203
204 // Now we know that the cell is valid. Hence, we can compute its cost.
205 {
206
207 const float distanceToObstacle = d.value();
208 const Eigen::Vector2f human_P_cell = global_T_human.inverse() * global_P_cell;
209
210 const float distanceToHuman = human_P_cell.norm();
211
212 const float angleToHuman = std::atan2(human_P_cell.y(), human_P_cell.x());
213
214
215 const auto costOpt = costFn(distanceToObstacle, distanceToHuman, angleToHuman);
216
217 if (not costOpt.has_value())
218 {
219 // invalid cost
220 return;
221 }
222
223 costmap.getMutableGrid()(idx.x(), idx.y()) = costOpt.value();
224 costmap.getMutableMask()->operator()(idx.x(), idx.y()) = true;
225 }
226 });
227
228 return costmap;
229 }
230
231 std::optional<core::Pose2D>
233 {
234 const auto costmapOpt = create(sceneInfo);
235
236 if (not costmapOpt.has_value())
237 {
238 return std::nullopt;
239 }
240
241 const auto& costmap = costmapOpt.value();
242 const auto optimum = costmap.optimum();
243
244 if (optimum.index.isConstant(-1))
245 {
246 // no valid optimum found
247 return std::nullopt;
248 }
249
250 // compute orientation towards human
251 const auto global_P_human = armem::human::computeMeanPosition(sceneInfo.humanPose);
252
253 if (not global_P_human.has_value())
254 {
255 // cannot compute human pose
256 return std::nullopt;
257 }
258
259 const auto global_P_handover = optimum.position;
260
261 const Eigen::Vector2f direction =
262 (global_P_human->head<2>() - global_P_handover).normalized();
263
264 const float yaw = std::atan2(direction.y(), direction.x());
265
266 return core::Pose2D{Eigen::Translation2f{global_P_handover.x(), global_P_handover.y()} *
267 Eigen::Rotation2Df{yaw}};
268 }
269} // namespace armarx::navigation::algorithms::costmap
if(!yyvaluep)
Definition Grammar.cpp:645
static Eigen::MatrixXf createUniformGrid(const SceneBounds &sceneBounds, const Costmap::Parameters &parameters)
std::optional< float > value(const Index &index) const
Definition Costmap.cpp:541
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