Component.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 * @package navigation::ArmarXObjects::human_simulator
17 * @author Fabian Reister ( fabian dot reister at kit dot edu )
18 * @date 2022
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23
24#include "Component.h"
25
26#include <algorithm>
27#include <cstddef>
28#include <cstdlib>
29#include <string>
30#include <vector>
31
32#include <SimoxUtility/color/Color.h>
33#include <VirtualRobot/BoundingBox.h>
34#include <VirtualRobot/CollisionDetection/CollisionModel.h> // IWYU pragma: keep
35#include <VirtualRobot/SceneObjectSet.h> // IWYU pragma: keep
36
47
53
59
60#include <RVOSimulator.h>
61#include <Vector2.h>
62
63// Include headers you only need in function definitions in the .cpp.
64
65// #include <Eigen/Core>
66
67// #include <SimoxUtility/color/Color.h>
68
69
71{
73 {
74 addPlugin(virtualRobotReaderPlugin);
75 addPlugin(costmapReaderPlugin);
76 addPlugin(humanWriterPlugin);
77 }
78
79 const std::string Component::defaultName = "human_simulator";
80
83 {
86
87 // Publish to a topic (passing the TopicListenerPrx).
88 // def->topic(myTopicListener);
89
90 // Subscribe to a topic (passing the topic name).
91 // def->topic<PlatformUnitListener>("MyTopic");
92
93 // Use (and depend on) another component (passing the ComponentInterfacePrx).
94 // def->component(myComponentProxy)
95
96
97 // Add a required property. (The component won't start without a value being set.)
98 // def->required(properties.boxLayerName, "p.box.LayerName", "Name of the box layer in ArViz.");
99
100 // Add an optionalproperty.
101 def->optional(properties.taskPeriodMs, "p.taskPeriodMs", "");
102 def->optional(properties.nHumans, "p.nHumans", "Number of humans to spawn.");
103
104 def->optional(properties.humanParams.goalDistanceThreshold,
105 "p.human.goalDistanceThreshold",
106 "");
107 def->optional(properties.humanParams.minLinearVelocity, "p.human.maxLinearVelocity", "");
108 def->optional(properties.humanParams.maxLinearVelocity, "p.human.maxLinearVelocity", "");
109
110 def->optional(properties.objectPoseProviderName, "p.objectPoseProviderName", "");
111
112 def->required(properties.robotName, "p.robotName", "");
113
114 return def;
115 }
116
117 void
119 {
120 // Topics and properties defined above are automagically registered.
121
122 // Keep debug observer data until calling `sendDebugObserverBatch()`.
123 // (Requies the armarx::DebugObserverComponentPluginUser.)
124 // setDebugObserverBatchModeEnabled(true);
125 }
126
127 void
128 Component::setPreferredVelocities(RVO::RVOSimulator* simulator,
129 const std::vector<RVO::Vector2>& goals)
130 {
131 /* Set the preferred velocity to be a vector of unit magnitude (speed) in the
132 * direction of the goal. */
133 // #ifdef _OPENMP
134 // #pragma omp parallel for
135 // #endif /* _OPENMP */
136
137 for (int i = 0; i < static_cast<int>(properties.nHumans); ++i)
138 {
139 // RVO::Vector2 goalVector = goals[i] - simulator->getAgentPosition(i);
140
141 // if (RVO::absSq(goalVector) > 1.0F)
142 // {
143 // goalVector = RVO::normalize(goalVector);
144 // }
145
146 const auto agentPosition = simulator->getAgentPosition(i);
147 auto& simulatedHuman = simulatedHumans.at(i);
148
149 core::Pose2D pose = core::Pose2D::Identity();
150 pose.translation().x() = agentPosition.x() * 1000; // [m] -> [mm]
151 pose.translation().y() = agentPosition.y() * 1000; // [m] -> [mm]
152
153 simulatedHuman.reinitialize(pose);
154
155 const std::vector<core::Position> trajectoryPoints =
156 simulatedHuman.trajectory().positions();
157
158 const std::size_t lookAheadGoalIdx = std::min(trajectoryPoints.size() - 1, 5ul);
159 const auto& intermediateGoal = trajectoryPoints.at(lookAheadGoalIdx);
160
161 const Eigen::Vector2f rvoGoalHelper = intermediateGoal.head<2>() / 1000; // [mm] -> [m]
162
163 const RVO::Vector2 rvoGoal(rvoGoalHelper.x(), rvoGoalHelper.y());
164
165 RVO::Vector2 goalVector = rvoGoal - agentPosition;
166
167 if (RVO::absSq(goalVector) > 1.0F)
168 {
169 goalVector = RVO::normalize(goalVector);
170 }
171
172 simulator->setAgentPrefVelocity(i, goalVector);
173 }
174 }
175
176 void
178 {
179 //
180 // Costmaps
181 //
182
183 ARMARX_INFO << "Querying costmap";
184
185 const algorithms::Costmap costmap = [&]() -> algorithms::Costmap
186 {
187 while (true)
188 {
189 const auto timestamp = Clock::Now();
190
192 .providerName = "distance_to_obstacle_costmap_provider", // TODO check
193 .name = "distance_to_obstacles",
194 .timestamp = timestamp};
195
196 const memory::client::costmap::Reader::Result costmapResult =
197 costmapReaderPlugin->get().query(costmapQuery);
198
199 // if costmap is not available yet, wait
201 {
203 continue;
204 }
205
206 ARMARX_CHECK_EQUAL(costmapResult.status,
208
210 ARMARX_CHECK(costmapResult.costmap.has_value());
211 return costmapResult.costmap.value();
212 };
213 }();
214
215 /// create simulation environment
216 {
217 ARMARX_INFO << "Creating sim";
218
219 simulator.setTimeStep(static_cast<float>(properties.taskPeriodMs) / 1000);
220 simulator.setAgentDefaults(15.0F, 10U, 1.5F, .1F, .4F, .5F);
221
222 const objpose::ObjectPoseSeq obstacles =
224 properties.objectPoseProviderName);
225
227 obstacles,
228 VirtualRobot::ObjectIO::ObjectDescription::eCollisionModel);
229
230
231 ARMARX_VERBOSE << obstacles.size() << " obstacles";
232
233 {
234 auto layer = arviz.layer("obstacles");
235
236
237 for (const auto& obst : objects->getCollisionModels())
238 {
239
240 ARMARX_INFO << "adding obstacle ...";
241 const VirtualRobot::BoundingBox bbox = obst->getGlobalBoundingBox();
242
243 Eigen::Vector2f bbMin = bbox.getMin().head<2>();
244 Eigen::Vector2f bbMax = bbox.getMax().head<2>();
245
246 // add some margin to the obstacles
247 const float margin = 100; // [mm]
248
249 bbMin.x() -= margin;
250 bbMin.y() -= margin;
251 bbMax.x() += margin;
252 bbMax.y() += margin;
253
254 ARMARX_VERBOSE << "Bounding box: (" << bbMin.transpose() << "), ("
255 << bbMax.transpose() << ")";
256
257 {
258 constexpr float zHeight = 3; // [mm]
259
260 viz::Polygon polygon(std::to_string(layer.size()));
261 polygon.addPoint(Eigen::Vector3f{bbMin.x(), bbMin.y(), zHeight});
262 polygon.addPoint(Eigen::Vector3f{bbMax.x(), bbMin.y(), zHeight});
263 polygon.addPoint(Eigen::Vector3f{bbMax.x(), bbMax.y(), zHeight});
264 polygon.addPoint(Eigen::Vector3f{bbMin.x(), bbMax.y(), zHeight});
265
266 polygon.color(simox::Color::black());
267 polygon.color(simox::Color::black());
268
269 layer.add(polygon);
270 }
271
272
273 const Eigen::Vector2d min = bbMin.cast<double>() / 1000; // [mm] -> [m]
274 const Eigen::Vector2d max = bbMax.cast<double>() / 1000; // [mm] -> [m]
275
276 std::vector<RVO::Vector2> obstacle;
277 obstacle.emplace_back(min.x(), min.y());
278 obstacle.emplace_back(max.x(), min.y());
279 obstacle.emplace_back(max.x(), max.y());
280 obstacle.emplace_back(min.x(), max.y());
281
282 simulator.addObstacle(obstacle);
283 }
284
285 arviz.commit(layer);
286 }
287
288 simulator.processObstacles();
289 }
290
291 /// create humans in simulation
292 {
293 simulatedHumans.clear();
294
295 for (std::size_t i = 0U; i < properties.nHumans; ++i)
296 {
298 simulatedHumans.emplace_back(costmap);
299
300 const RVO::Vector2 position =
301 RVO::Vector2(human.human().pose.translation().x() / 1000,
302 human.human().pose.translation().y() / 1000);
303 auto goal = human.trajectory().positions().back() / 1000; // [mm] -> [m]
304
305 ARMARX_VERBOSE << "Adding agent";
306 // simulator.addAgent(position, simulator.addGoal(hrvo::Vector2(goal.x(), goal.y())));
307 simulator.addAgent(
308 position); // , simulator.addGoal(hrvo::Vector2(goal.x(), goal.y())));
309
310 goals.emplace_back(goal.x(), goal.y());
311 }
312 }
313
314 /// load robot and add it to the simulator
315 {
316 robot = virtualRobotReaderPlugin->get().getRobotWaiting(properties.robotName);
318 virtualRobotReaderPlugin->get().synchronizeRobot(*robot, armarx::Clock::Now()));
319
320 const auto robotPosH = robot->getGlobalPosition();
321 const RVO::Vector2 robotPos(robotPosH.x() / 1000, robotPosH.y() / 1000);
322
323 robotAgentId = simulator.addAgent(robotPos);
324 simulator.setAgentMaxSpeed(robotAgentId,
325 0); // we do not allow the robot to be perturbed
326 simulator.setAgentPrefVelocity(robotAgentId, RVO::Vector2(0, 0));
327 simulator.setAgentRadius(robotAgentId, 0.6);
328 }
329
330 /// start task
331 task = new PeriodicTask<Component>(this,
332 &Component::runPeriodically,
333 properties.taskPeriodMs,
334 false,
335 "runningTask");
336 task->start();
337 }
338
339 void
341 {
342 task->stop();
343 }
344
345 void
349
350 std::string
352 {
353 return Component::defaultName;
354 }
355
356 std::string
358 {
359 return Component::defaultName;
360 }
361
362 void
363 Component::runPeriodically()
364 {
365 const DateTime timestamp = Clock::Now();
366
367 setPreferredVelocities(&simulator, goals);
368
369 // TODO(fabian.reister): check how much the agents interact => this can be an evaluation criterion
370
371 // update robot
372 {
374 virtualRobotReaderPlugin->get().synchronizeRobot(*robot, armarx::Clock::Now()));
375
376 const auto robotPosH = robot->getGlobalPosition();
377 const RVO::Vector2 robotPos(robotPosH.x() / 1000, robotPosH.y() / 1000);
378
379 simulator.setAgentPosition(robotAgentId, robotPos);
380 }
381
382 simulator.doStep();
383
384 // visualize paths
385 {
386 auto layer = arviz.layer("trajectories");
387 for (const auto& simulatedHuman : simulatedHumans)
388 {
389 viz::Path path(std::to_string(layer.size()));
390
391 for (const auto& pt : simulatedHuman.trajectory().points())
392 {
393 path.addPoint(pt.waypoint.pose.translation());
394 }
395
396 // unique and distinct color for this human
397 path.colorGlasbeyLUT(layer.size());
398
399 layer.add(path);
400 }
401
402 arviz.commit(layer);
403 }
404
405
406 human::Humans humans;
407 for (std::size_t i = 0; i < properties.nHumans; i++)
408 {
409 const auto pos = simulator.getAgentPosition(i);
410
411 human::Human human;
412 human.pose.setIdentity();
413 human.pose.translation().x() = pos.x() * 1000; // [m] -> [mm]
414 human.pose.translation().y() = pos.y() * 1000; // [m] -> [mm]
415
416 humans.push_back(human);
417 }
418
419 ARMARX_VERBOSE << "Updating humans.";
420 humanWriterPlugin->get().store(humans, getName(), timestamp);
421 }
422
423 /* (Requires the armarx::LightweightRemoteGuiComponentPluginUser.)
424 void
425 Component::createRemoteGuiTab()
426 {
427 using namespace armarx::RemoteGui::Client;
428
429 // Setup the widgets.
430
431 tab.boxLayerName.setValue(properties.boxLayerName);
432
433 tab.numBoxes.setValue(properties.numBoxes);
434 tab.numBoxes.setRange(0, 100);
435
436 tab.drawBoxes.setLabel("Draw Boxes");
437
438 // Setup the layout.
439
440 GridLayout grid;
441 int row = 0;
442 {
443 grid.add(Label("Box Layer"), {row, 0}).add(tab.boxLayerName, {row, 1});
444 ++row;
445
446 grid.add(Label("Num Boxes"), {row, 0}).add(tab.numBoxes, {row, 1});
447 ++row;
448
449 grid.add(tab.drawBoxes, {row, 0}, {2, 1});
450 ++row;
451 }
452
453 VBoxLayout root = {grid, VSpacer()};
454 RemoteGui_createTab(getName(), root, &tab);
455 }
456
457
458 void
459 Component::RemoteGui_update()
460 {
461 if (tab.boxLayerName.hasValueChanged() || tab.numBoxes.hasValueChanged())
462 {
463 std::scoped_lock lock(propertiesMutex);
464 properties.boxLayerName = tab.boxLayerName.getValue();
465 properties.numBoxes = tab.numBoxes.getValue();
466
467 {
468 setDebugObserverDatafield("numBoxes", properties.numBoxes);
469 setDebugObserverDatafield("boxLayerName", properties.boxLayerName);
470 sendDebugObserverBatch();
471 }
472 }
473 if (tab.drawBoxes.wasClicked())
474 {
475 // Lock shared variables in methods running in seperate threads
476 // and pass them to functions. This way, the called functions do
477 // not need to think about locking.
478 std::scoped_lock lock(propertiesMutex, arvizMutex);
479 drawBoxes(properties, arviz);
480 }
481 }
482 */
483
484
485 /* (Requires the armarx::ArVizComponentPluginUser.)
486 void
487 Component::drawBoxes(const Component::Properties& p, viz::Client& arviz)
488 {
489 // Draw something in ArViz (requires the armarx::ArVizComponentPluginUser.
490 // See the ArVizExample in RobotAPI for more examples.
491
492 viz::Layer layer = arviz.layer(p.boxLayerName);
493 for (int i = 0; i < p.numBoxes; ++i)
494 {
495 layer.add(viz::Box("box_" + std::to_string(i))
496 .position(Eigen::Vector3f(i * 100, 0, 0))
497 .size(20).color(simox::Color::blue()));
498 }
499 arviz.commit(layer);
500 }
501 */
502
503
505
506} // namespace armarx::navigation::components::human_simulator
std::string timestamp()
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static void WaitFor(const Duration &duration)
Wait for a certain duration on the virtual clock.
Definition Clock.cpp:99
Default component property definition container.
Definition Component.h:70
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
Definition Duration.cpp:48
PluginT * addPlugin(const std::string prefix="", ParamsT &&... params)
std::string getName() const
Retrieve name of object.
objpose::ObjectPoseSeq getObjectPosesByProvider(const std::string &providerName)
The periodic task executes one thread method repeatedly using the time period specified in the constr...
Represents a point in time.
Definition DateTime.h:25
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Definition Component.cpp:82
static std::string GetDefaultName()
Get the component's default name.
virtual Layer layer(std::string const &name) const
Definition Client.cpp:80
CommitResult commit(StagedCommit const &commit)
Definition Client.cpp:89
DerivedT & color(Color color)
Definition ElementOps.h:218
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#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:181
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
This file is part of ArmarX.
std::vector< Human > Humans
Definition types.h:48
VirtualRobot::SceneObjectSetPtr asSceneObjects(const objpose::ObjectPoseSeq &objectPoses, const VirtualRobot::ObjectIO::ObjectDescription loadMode)
Definition util.cpp:147
std::vector< ObjectPose > ObjectPoseSeq
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
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)
std::optional< algorithms::Costmap > costmap
Definition Reader.h:51
enum armarx::navigation::memory::client::costmap::Reader::Result::Status status
Polygon & addPoint(Eigen::Vector3f p)
Definition Elements.h:302
#define ARMARX_TRACE
Definition trace.h:77