Scene.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 Fabian Reister ( fabian dot reister at kit dot edu )
17 * @date 2026
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "Scene.h"
23
24#include "reparametrization.h"
25
26#include <algorithm>
27#include <fstream>
28#include <limits>
29#include <stdexcept>
30#include <string>
31
32#include <SimoxUtility/json/json.hpp>
33
37
39
41{
42
43 namespace
44 {
45
46 Eigen::Vector2f
47 toVector2f(const nlohmann::json& j)
48 {
49 ARMARX_CHECK_EQUAL(j.size(), 2) << "Expected a two-element array, got " << j.dump();
50 return Eigen::Vector2f{j.at(0).get<float>(), j.at(1).get<float>()};
51 }
52
54 toPose(const nlohmann::json& j)
55 {
56 const float yaw = j.value("yaw", 0.F);
57
58 core::Pose pose = core::Pose::Identity();
59 pose.translation() << j.at("x").get<float>(), j.at("y").get<float>(), 0.F;
60 pose.linear() = Eigen::AngleAxisf(yaw, Eigen::Vector3f::UnitZ()).toRotationMatrix();
61
62 return pose;
63 }
64
65 } // namespace
66
67 float
68 Box::distanceTo(const Eigen::Vector2f& p) const
69 {
70 // Component-wise overshoot beyond the box on either side; zero inside.
71 const Eigen::Vector2f d = (min - p).cwiseMax(p - max).cwiseMax(Eigen::Vector2f::Zero());
72 return d.norm();
73 }
74
75 Config
76 readConfig(const std::filesystem::path& filename)
77 {
78 ARMARX_CHECK(std::filesystem::exists(filename))
79 << "Scene description " << QUOTED(filename.string()) << " does not exist.";
80
81 std::ifstream ifs{filename};
82 const nlohmann::json j = nlohmann::json::parse(ifs);
83
84 Config config;
85
86 // Scene.
87 {
88 const nlohmann::json& jScene = j.at("scene");
89
90 config.scene.bounds.min = toVector2f(jScene.at("bounds").at("min"));
91 config.scene.bounds.max = toVector2f(jScene.at("bounds").at("max"));
92 config.scene.cellSize = jScene.at("cell_size").get<float>();
93
95 ARMARX_CHECK_GREATER(config.scene.bounds.max.x(), config.scene.bounds.min.x());
96 ARMARX_CHECK_GREATER(config.scene.bounds.max.y(), config.scene.bounds.min.y());
97
98 for (const nlohmann::json& jObstacle : jScene.at("obstacles"))
99 {
100 const std::string type = jObstacle.value("type", std::string{"box"});
101 if (type != "box")
102 {
103 throw std::invalid_argument("Unsupported obstacle type `" + type +
104 "`. Only `box` is supported.");
105 }
106
107 const Box box{.min = toVector2f(jObstacle.at("min")),
108 .max = toVector2f(jObstacle.at("max"))};
109
110 ARMARX_CHECK_GREATER_EQUAL(box.max.x(), box.min.x());
111 ARMARX_CHECK_GREATER_EQUAL(box.max.y(), box.min.y());
112
113 config.scene.obstacles.push_back(box);
114 }
115 }
116
117 config.start = toPose(j.at("start"));
118 config.goal = toPose(j.at("goal"));
119
120 // Costmap.
121 {
122 const nlohmann::json jCostmap = j.value("costmap", nlohmann::json::object());
123
124 config.costmapParams.cellSize = config.scene.cellSize;
125 config.costmapParams.robotRadius = jCostmap.value("robot_radius", 0.F);
126 config.costmapParams.binaryGrid = false;
127
128 config.costmapMaxDistance = jCostmap.value("max_distance", config.costmapMaxDistance);
129
131 }
132
133 // Planner.
134 {
135 const nlohmann::json jSpfa = j.value("spfa", nlohmann::json::object());
136 auto& algo = config.plannerParams.algo;
137
139 jSpfa.value("obstacle_distance_costs", algo.obstacleDistanceCosts);
140 algo.obstacleCostExponent =
141 jSpfa.value("obstacle_cost_exponent", algo.obstacleCostExponent);
142 algo.obstacleMaxDistance =
143 jSpfa.value("obstacle_max_distance", algo.obstacleMaxDistance);
144 algo.obstacleDistanceWeight =
145 jSpfa.value("obstacle_distance_weight", algo.obstacleDistanceWeight);
146
148 jSpfa.value("enable_position_smoothing",
151 jSpfa.value("enable_final_velocity_clamp",
153 }
154
155 // General navigation stack config.
156 {
157 const nlohmann::json jGeneral = j.value("general", nlohmann::json::object());
158 core::GeneralConfig& general = config.generalConfig;
159
160 general.maxVel.linear = jGeneral.value("max_linear_velocity", general.maxVel.linear);
161 general.maxVel.angular = jGeneral.value("max_angular_velocity", general.maxVel.angular);
162 general.enableRampingStart =
163 jGeneral.value("enable_ramping_start", general.enableRampingStart);
164 general.enableRampingEnd =
165 jGeneral.value("enable_ramping_end", general.enableRampingEnd);
166 general.enableRampingCorners =
167 jGeneral.value("enable_ramping_corners", general.enableRampingCorners);
168 general.rampLength = jGeneral.value("ramp_length", general.rampLength);
169 general.cornerVelocity = jGeneral.value("corner_velocity", general.cornerVelocity);
170 general.boundaryVelocity =
171 jGeneral.value("boundary_velocity", general.boundaryVelocity);
172 general.cornerLimit = jGeneral.value("corner_limit", general.cornerLimit);
173 }
174
175 // Low-level controller simulation.
176 {
177 const nlohmann::json jSim = j.value("simulation", nlohmann::json::object());
178 auto& sim = config.simulationParams;
179 auto& ctrl = sim.controller;
180
181 config.simulate = jSim.value("enabled", config.simulate);
182
183 sim.dt = jSim.value("dt", sim.dt);
184 sim.maxDuration = jSim.value("max_duration", sim.maxDuration);
185 sim.goalDistanceThreshold =
186 jSim.value("goal_distance_threshold", sim.goalDistanceThreshold);
187
188 ctrl.alpha = jSim.value("alpha", ctrl.alpha);
189 ctrl.velocityFactor = jSim.value("velocity_factor", ctrl.velocityFactor);
190 ctrl.maxSegmentsAhead = jSim.value("max_segments_ahead", ctrl.maxSegmentsAhead);
191 ctrl.orientationWeight = jSim.value("orientation_weight", ctrl.orientationWeight);
192 ctrl.coupleLinearAndAngularLimits =
193 jSim.value("couple_linear_and_angular_limits",
194 ctrl.coupleLinearAndAngularLimits);
195 ctrl.enableAngularFeedforward =
196 jSim.value("enable_angular_feedforward", ctrl.enableAngularFeedforward);
197
198 ctrl.pidPos.Kp = jSim.value("pid_pos_kp", ctrl.pidPos.Kp);
199 ctrl.pidPos.Ki = jSim.value("pid_pos_ki", ctrl.pidPos.Ki);
200 ctrl.pidPos.Kd = jSim.value("pid_pos_kd", ctrl.pidPos.Kd);
201 ctrl.pidOri.Kp = jSim.value("pid_ori_kp", ctrl.pidOri.Kp);
202 ctrl.pidOri.Ki = jSim.value("pid_ori_ki", ctrl.pidOri.Ki);
203 ctrl.pidOri.Kd = jSim.value("pid_ori_kd", ctrl.pidOri.Kd);
204
205 // The controller's own limits default to the navigation stack's.
206 ctrl.limits.linear = jSim.value("limit_linear", config.generalConfig.maxVel.linear);
207 ctrl.limits.angular = jSim.value("limit_angular", config.generalConfig.maxVel.angular);
208
209 // What the simulated mass can deliver comes from the package data directory; the
210 // scene may override it to compare against an unconstrained base.
211 // Selects config/platform/PlatformDynamics<Robot>.json, spelled as the rest of
212 // the codebase does: `Armar7`, `Armar6`, `ArmarDE`.
213 config.robot = jSim.value("robot", std::string{"Armar7"});
215 sim.dynamics.maxLinearAcceleration =
216 jSim.value("max_linear_acceleration", sim.dynamics.maxLinearAcceleration);
217 sim.dynamics.maxAngularAcceleration =
218 jSim.value("max_angular_acceleration", sim.dynamics.maxAngularAcceleration);
219
220 // Device-side command ramp. Defaults mirror Armar7a/parts/Platform.xml.
221 {
222 const nlohmann::json jRamp =
223 jSim.value("rate_limit", nlohmann::json::object());
224 auto& ramp = sim.rateLimit;
225
226 ramp.enabled = jRamp.value("enabled", ramp.enabled);
227 ramp.dt = jRamp.value("dt", ramp.dt);
228 ramp.maxVelocity = jRamp.value("max_velocity", ramp.maxVelocity);
229 ramp.maxAcceleration = jRamp.value("max_acceleration", ramp.maxAcceleration);
230 ramp.maxDeceleration = jRamp.value("max_deceleration", ramp.maxDeceleration);
231 ramp.maxAngularVelocity =
232 jRamp.value("max_angular_velocity", ramp.maxAngularVelocity);
233 ramp.maxAngularAcceleration =
234 jRamp.value("max_angular_acceleration", ramp.maxAngularAcceleration);
235 ramp.maxAngularDeceleration =
236 jRamp.value("max_angular_deceleration", ramp.maxAngularDeceleration);
237 ramp.directionPreserving =
238 jRamp.value("direction_preserving", ramp.directionPreserving);
239 }
240
241 if (jSim.contains("model"))
242 {
243 const std::string model = jSim.at("model");
244 if (model == "omni_wheel_torque")
245 {
247 }
248 else if (model == "omni_wheel_velocity")
249 {
251 }
252 else if (model == "mecanum_torque")
253 {
254 sim.dynamics.model = simulation::PlatformModel::MecanumTorque;
255 }
256 else if (model == "cartesian")
257 {
258 sim.dynamics.model = simulation::PlatformModel::Cartesian;
259 }
260 else
261 {
262 // Previously an unrecognised name silently became Cartesian, which looks
263 // like a working run against the wrong model -- exactly how `mecanum_torque`
264 // went unnoticed here after being added to the config parser.
265 throw std::invalid_argument(
266 "Unknown platform model `" + model +
267 "`. Expected `cartesian`, `omni_wheel_velocity`, `omni_wheel_torque` "
268 "or `mecanum_torque`.");
269 }
270 }
271 }
272
273 // Velocity parametrization.
274 {
275 const nlohmann::json jParam =
276 j.value("reparametrization", nlohmann::json::object());
277
278 config.parametrization =
279 parametrizationModeFromString(jParam.value("mode", std::string{"none"}));
281 jParam.value("num_samples", config.parametrizationSamples);
283 jParam.value("torque_fraction", config.parametrizationTorqueFraction);
284 }
285
286 return config;
287 }
288
290 buildCostmap(const Config& config)
291 {
292 // Reuse the builder's sizing so the grid dimensions cannot drift from what the
293 // costmap's index <-> position math expects.
295 config.scene.bounds, config.costmapParams);
296
297 ARMARX_INFO << "Costmap grid: " << grid.rows() << " rows (x) x " << grid.cols()
298 << " cols (y), cell size " << config.costmapParams.cellSize << " mm.";
299
300 const float cellSize = config.costmapParams.cellSize;
301 const Eigen::Vector2f& boundsMin = config.scene.bounds.min;
302
303 for (int ix = 0; ix < grid.rows(); ix++)
304 {
305 for (int iy = 0; iy < grid.cols(); iy++)
306 {
307 // Rows correspond to x, columns to y (see Costmap::toPositionLocal).
308 const Eigen::Vector2f cellCenter{
309 boundsMin.x() + static_cast<float>(ix) * cellSize + cellSize / 2,
310 boundsMin.y() + static_cast<float>(iy) * cellSize + cellSize / 2};
311
312 float distance = std::numeric_limits<float>::max();
313 for (const Box& obstacle : config.scene.obstacles)
314 {
315 distance = std::min(distance, obstacle.distanceTo(cellCenter));
316 }
317
318 const float clearance = distance - config.costmapParams.robotRadius;
319 grid(ix, iy) = std::clamp(clearance, 0.F, config.costmapMaxDistance);
320 }
321 }
322
323 return algorithms::Costmap(grid, config.costmapParams, config.scene.bounds);
324 }
325
326} // namespace armarx::navigation::analysis
#define QUOTED(x)
static Eigen::MatrixXf createUniformGrid(const SceneBounds &sceneBounds, const Costmap::Parameters &parameters)
#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_GREATER_EQUAL(lhs, rhs)
This macro evaluates whether lhs is greater or equal (>=) rhs and if it turns out to be false it will...
#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:179
This file is part of ArmarX.
Definition io.cpp:36
core::TrajectoryParametrization parametrizationModeFromString(const std::string &name)
Parse the --parametrization choice. Throws on an unknown name.
Config readConfig(const std::filesystem::path &filename)
Read the scene description. Throws on malformed input.
Definition Scene.cpp:76
algorithms::Costmap buildCostmap(const Config &config)
Build a costmap holding, per cell, the distance to the closest obstacle reduced by the robot radius (...
Definition Scene.cpp:290
Eigen::Isometry3f Pose
Definition basic_types.h:31
@ MecanumTorque
As OmniWheelTorque, but for a four-wheel mecanum drive.
@ OmniWheelVelocity
Bound each wheel's angular acceleration separately, so one saturating wheel also distorts the directi...
@ Cartesian
Bound the magnitude of the twist change. Direction of the change is preserved.
@ OmniWheelTorque
Bound each motor's torque and speed, using the robot's real inertia.
double distance(const Point &a, const Point &b)
Definition point.hpp:95
float cellSize
How big each cell is in the uniform grid.
Definition Costmap.h:31
An axis-aligned box obstacle in the ground plane.
Definition Scene.h:41
float distanceTo(const Eigen::Vector2f &p) const
Distance from p to the box surface. Zero if p is inside the box.
Definition Scene.cpp:68
Everything the application needs, as read from the scene description file.
Definition Scene.h:63
core::GeneralConfig generalConfig
Definition Scene.h:77
global_planning::SPFAParams plannerParams
Definition Scene.h:75
int parametrizationSamples
Waypoints the reparametrization should return.
Definition Scene.h:103
bool simulate
Simulate the low-level controller following the planned trajectory.
Definition Scene.h:80
std::string robot
Platform name selecting PlatformDynamics<robot>.json, spelled as the rest of the codebase does: Armar...
Definition Scene.h:87
algorithms::Costmap::Parameters costmapParams
Definition Scene.h:70
core::TrajectoryParametrization parametrization
How the velocities along the planned path are (re-)assigned before simulating.
Definition Scene.h:93
float parametrizationTorqueFraction
Fraction of the motor torque the reparametrization may use.
Definition Scene.h:106
float costmapMaxDistance
Obstacle distances are clipped to this value [mm].
Definition Scene.h:73
simulation::TrajectoryFollowingSimulation::Parameters simulationParams
Definition Scene.h:82
algorithms::SceneBounds bounds
Definition Scene.h:53
float cellSize
Edge length of a costmap cell [mm].
Definition Scene.h:56
std::vector< Box > obstacles
Definition Scene.h:58
algorithms::spfa::ShortestPathFasterAlgorithm::Parameters algo
Definition SPFA.h:53
bool enableFinalVelocityClamp
Re-apply the obstacle-aware velocity limit after orientation optimization.
Definition SPFA.h:71
bool enablePositionSmoothing
Diagnostic switches for the post-processing stages.
Definition SPFA.h:63
static PlatformDynamics FromPackagePath(const std::string &robot="Armar7")
Read the dynamics from config/platform/PlatformDynamics<Robot>.json.
traj_ctrl::global::TrajectoryFollowingControllerParams controller
Parameters of the trajectory following controller, including alpha and limits.