LaserScannerSimulation.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 ArmarXSimulation::ArmarXObjects::LaserScannerSimulation
17 * @author Fabian Paus ( fabian dot paus at kit dot edu )
18 * @date 2017
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <chrono>
26#include <cmath>
27#include <cstddef>
28#include <memory>
29
30#include <Eigen/Geometry>
31
32#include <SimoxUtility/shapes/AxisAlignedBoundingBox.h>
33#include <SimoxUtility/shapes/OrientedBox.h>
34#include <VirtualRobot/BoundingBox.h>
35#include <VirtualRobot/CollisionDetection/CollisionChecker.h>
36#include <VirtualRobot/CollisionDetection/CollisionModel.h>
37#include <VirtualRobot/Robot.h>
38#include <VirtualRobot/SceneObjectSet.h>
39#include <VirtualRobot/VirtualRobot.h>
40#include <VirtualRobot/Visualization/CoinVisualization/CoinVisualizationFactory.h>
41#include <VirtualRobot/Visualization/VisualizationFactory.h>
42
48
50
51#include "ArVizDrawer.h"
52
54{
55
56 static std::size_t
57 nextMultipleOf(std::size_t value, std::size_t multiple)
58 {
59 if (multiple == 0)
60 {
61 return value;
62 }
63
64 std::size_t remainder = value % multiple;
65 if (remainder == 0)
66 {
67 return value;
68 }
69
70 return value + multiple - remainder;
71 }
72
73 static std::vector<std::string>
74 splitProperty(Property<std::string> prop)
75 {
76 std::string propString(prop.getValue());
77 std::vector<std::string> result = Split(propString, ",");
78 return result;
79 }
80
82
84 LaserScannerSimulation::calculateGridDimension(
85 const std::vector<VirtualRobot::BoundingBox>& boundingBoxes) const
86 {
87 simox::AxisAlignedBoundingBox box;
88 for (const auto& boundingBox : boundingBoxes)
89 {
90 box.expand_to(boundingBox.getMin());
91 box.expand_to(boundingBox.getMax());
92 }
93
94 const auto extents = box.extents();
95
96 std::size_t gridSizeX = static_cast<std::size_t>(std::ceil(extents.x() / gridCellSize));
97 gridSizeX = nextMultipleOf(gridSizeX, sizeof(std::size_t) * CHAR_BIT);
98
99 std::size_t gridSizeY = static_cast<std::size_t>(std::ceil(extents.y() / gridCellSize));
100 gridSizeY = nextMultipleOf(gridSizeY, sizeof(std::size_t) * CHAR_BIT);
101
102 return {.originX = box.min_x(),
103 .originY = box.min_y(),
104 .sizeX = std::max<std::size_t>(gridSizeX, 1),
105 .sizeY = std::max<std::size_t>(gridSizeY, 1)};
106 }
107
108 VirtualRobot::SceneObjectSetPtr
109 LaserScannerSimulation::getCollisionObjects(
110 const std::vector<VirtualRobot::SceneObjectPtr>& sceneObjects) const
111 {
112 VirtualRobot::SceneObjectSetPtr objects(new VirtualRobot::SceneObjectSet());
113
114 for (auto& object : sceneObjects)
115 {
116 VirtualRobot::Robot* robot = dynamic_cast<VirtualRobot::Robot*>(object.get());
117 if (robot == nullptr) // standard scene object
118 {
119 objects->addSceneObject(object);
120 }
121 else // passive robot
122 {
123 for (const auto& rns : robot->getRobotNodeSets())
124 {
125 objects->addSceneObjects(rns);
126 }
127 }
128 }
129
130 return objects;
131 }
132
134 LaserScannerSimulation::calculateGridDimension(
135 const std::vector<VirtualRobot::SceneObjectPtr>& sceneObjects) const
136 {
137 VirtualRobot::SceneObjectSetPtr objectSet(new VirtualRobot::SceneObjectSet());
138
139 for (auto& object : sceneObjects)
140 {
141 VirtualRobot::Robot* robot = dynamic_cast<VirtualRobot::Robot*>(object.get());
142 if (robot == nullptr) // standard scene object
143 {
144 objectSet->addSceneObject(object);
145 }
146 else // passive robot
147 {
148 // for (const auto rns : robot->getRobotNodeSets())
149 // {
150 // objects->addSceneObjects(rns);
151 // }
152 objectSet->addSceneObject(object); // This is not working properly
153 }
154 }
155
156 // Create a occupancy grid
157 return calculateGridDimension(objectSet);
158 }
159
160 std::vector<VirtualRobot::BoundingBox>
161 LaserScannerSimulation::boundingBoxes(VirtualRobot::SceneObjectSetPtr const& objects) const
162 {
163 std::vector<VirtualRobot::BoundingBox> objectBoudingBoxes;
164
165 for (unsigned int objIndex = 0; objIndex < objects->getSize(); ++objIndex)
166 {
167 VirtualRobot::SceneObjectPtr object = objects->getSceneObject(objIndex);
168
169 VirtualRobot::Robot* robot = dynamic_cast<VirtualRobot::Robot*>(object.get());
170 if (robot == nullptr) // standard scene object
171 {
172 objectBoudingBoxes.push_back(object->getCollisionModel()->getBoundingBox());
173 }
174 else // passive robot
175 {
176 for (const auto& collisionModel : robot->getCollisionModels())
177 {
178 if (collisionModel->getCollisionModelImplementation()->getPQPModel())
179 {
180 objectBoudingBoxes.push_back(collisionModel->getBoundingBox());
181 }
182 }
183 }
184 }
185
186 return objectBoudingBoxes;
187 }
188
190 LaserScannerSimulation::calculateGridDimension(
191 VirtualRobot::SceneObjectSetPtr const& objects) const
192 {
193 if (objects->getSize() == 0)
194 {
195 return {.originX = 0.0F, .originY = 0.0F, .sizeX = 1, .sizeY = 1};
196 }
197
198 const auto objectBoundingBoxes = boundingBoxes(objects);
199 ARMARX_INFO << objectBoundingBoxes.size() << " bounding boxes";
200
201 // arvizDrawer->drawBoundingBoxes(objectBoundingBoxes);
202
203 return calculateGridDimension(objectBoundingBoxes);
204 }
205
206 void
207 LaserScannerSimulation::fillOccupancyGrid(
208 std::vector<VirtualRobot::SceneObjectPtr> const& sceneObjects)
209 {
210 // initialize grid
211 const auto gridDim = calculateGridDimension(sceneObjects);
212 grid.init(gridDim.sizeX, gridDim.sizeY, gridDim.originX, gridDim.originY, gridCellSize);
213
214 ARMARX_INFO_S << "Creating grid with size (" << gridDim.sizeX << ", " << gridDim.sizeY
215 << ")";
216
217 VirtualRobot::CollisionCheckerPtr collisionChecker =
218 VirtualRobot::CollisionChecker::getGlobalCollisionChecker();
219 VirtualRobot::VisualizationFactoryPtr factory(new VirtualRobot::CoinVisualizationFactory());
220
221 // collision checking by sliding an object with the grid step size over the grid map
222 float boxSize = 1.1f * gridCellSize;
223 VirtualRobot::VisualizationNodePtr boxVisu = factory->createBox(
224 boxSize, boxSize, boxSize, VirtualRobot::VisualizationFactory::Color::Green());
225 VirtualRobot::CollisionModelPtr boxCollisionModel(
226 new VirtualRobot::CollisionModel(boxVisu, "", collisionChecker));
227 VirtualRobot::SceneObjectPtr box(
228 new VirtualRobot::SceneObject("my_box", boxVisu, boxCollisionModel));
229
230 if (not collisionChecker)
231 {
232 ARMARX_WARNING_S << "No global collision checker found";
233 return;
234 }
235
236 ARMARX_INFO_S << "Filling occupancy grid";
237 const auto collisionObjects = getCollisionObjects(sceneObjects);
238
239 for (std::size_t indexY = 0; indexY < grid.sizeY; ++indexY)
240 {
241 for (std::size_t indexX = 0; indexX < grid.sizeX; ++indexX)
242 {
243 const Eigen::Vector2f pos = grid.getCentralPosition(indexX, indexY);
244 const Eigen::Vector3f boxPos(pos.x(), pos.y(), boxPosZ);
245 const Eigen::Matrix4f boxPose =
246 Eigen::Affine3f(Eigen::Translation3f(boxPos)).matrix();
247 box->setGlobalPose(boxPose);
248 // - Check collisions with the other objects
249 const bool collision = collisionChecker->checkCollision(box, collisionObjects);
250 // - Mark each field as occupied or free
251 grid.setOccupied(indexX, indexY, collision);
252 }
253 }
254 }
255
256 void
258 {
259 scanners.clear();
260
261 // Necessary to initialize SoDB and Qt, SoQt::init (otherwise the application crashes at startup)
262 VirtualRobot::init("SimulatorViewerApp");
264 getIceProperties(), getName() + "_PhysicsWorldVisualization");
265 getArmarXManager()->addObject(worldVisu);
266
267 enableVisualization = getProperty<bool>("visualization.enable").getValue();
268 ARMARX_INFO << "Visualization will be " << (enableVisualization ? "enabled" : "disabled");
269
270 topicName = getProperty<std::string>("LaserScannerTopicName").getValue();
271 ARMARX_INFO << "Reporting on topic \"" << topicName << "\"";
272 offeringTopic(topicName);
273
274 robotStateName = getProperty<std::string>("RobotStateComponentName").getValue();
275 ARMARX_INFO << "Using RobotStateComponent \"" << robotStateName << "\"";
276 usingProxy(robotStateName);
277
278 debugDrawerName = getProperty<std::string>("DebugDrawerTopicName").getValue();
279 ARMARX_INFO << "Using DebugDrawerTopic \"" << debugDrawerName << "\"";
280 // No usingProxy for the DebugDrawerTopic? or is it a topic?
281
282 topicReplayerDummy = getProperty<bool>("TopicReplayerDummy").getValue();
283 updatePeriod = getProperty<int>("UpdatePeriod").getValue();
284 visuUpdateFrequency = getProperty<int>("VisuUpdateFrequency").getValue();
285 gridCellSize = getProperty<float>("GridCellSize").getValue();
286
287 auto framesStrings = splitProperty(getProperty<std::string>("Frames"));
288 auto deviceStrings = splitProperty(getProperty<std::string>("Devices"));
289 auto minAnglesStrings = splitProperty(getProperty<std::string>("MinAngles"));
290 auto maxAnglesStrings = splitProperty(getProperty<std::string>("MaxAngles"));
291 auto stepsStrings = splitProperty(getProperty<std::string>("Steps"));
292 auto noiseStrings = splitProperty(getProperty<std::string>("NoiseStdDev"));
293 std::size_t scannerSize = framesStrings.size();
294 if (deviceStrings.size() == 1)
295 {
296 deviceStrings.resize(scannerSize, deviceStrings.front());
297 }
298 else if (deviceStrings.size() != scannerSize)
299 {
300 ARMARX_WARNING << "Unexpected size of property Devices (expected " << scannerSize
301 << " but got " << deviceStrings.size() << ")";
302 return;
303 }
304 if (minAnglesStrings.size() == 1)
305 {
306 minAnglesStrings.resize(scannerSize, minAnglesStrings.front());
307 }
308 else if (minAnglesStrings.size() != scannerSize)
309 {
310 ARMARX_WARNING << "Unexpected size of property MinAngles (expected " << scannerSize
311 << " but got " << minAnglesStrings.size() << ")";
312 return;
313 }
314 if (maxAnglesStrings.size() == 1)
315 {
316 maxAnglesStrings.resize(scannerSize, maxAnglesStrings.front());
317 }
318 else if (maxAnglesStrings.size() != scannerSize)
319 {
320 ARMARX_WARNING << "Unexpected size of property MaxAngles (expected " << scannerSize
321 << " but got " << maxAnglesStrings.size() << ")";
322 return;
323 }
324 if (stepsStrings.size() == 1)
325 {
326 stepsStrings.resize(scannerSize, stepsStrings.front());
327 }
328 else if (stepsStrings.size() != scannerSize)
329 {
330 ARMARX_WARNING << "Unexpected size of property Steps (expected " << scannerSize
331 << " but got " << stepsStrings.size() << ")";
332 return;
333 }
334 if (noiseStrings.size() == 1)
335 {
336 noiseStrings.resize(scannerSize, noiseStrings.front());
337 }
338 else if (noiseStrings.size() != scannerSize)
339 {
340 ARMARX_WARNING << "Unexpected size of property NoiseStdDev (expected " << scannerSize
341 << " but got " << noiseStrings.size() << ")";
342 return;
343 }
344
345 scanners.reserve(scannerSize);
346 connectedDevices.clear();
347 for (std::size_t i = 0; i < scannerSize; ++i)
348 {
349 LaserScannerSimUnit scanner;
350 scanner.frame = framesStrings[i];
351 try
352 {
353 scanner.minAngle = std::stof(minAnglesStrings[i]);
354 scanner.maxAngle = std::stof(maxAnglesStrings[i]);
355 scanner.noiseStdDev = std::stof(noiseStrings[i]);
356 scanner.steps = std::stoi(stepsStrings[i]);
357 }
358 catch (std::exception const& ex)
359 {
360 ARMARX_INFO << "Scanner[" << i << "] Config error: " << ex.what();
361 continue;
362 }
363
364 scanners.push_back(scanner);
365
366 LaserScannerInfo info;
367 info.device = topicReplayerDummy ? deviceStrings[i] : scanner.frame;
368 info.frame = scanner.frame;
369 info.minAngle = scanner.minAngle;
370 info.maxAngle = scanner.maxAngle;
371 info.stepSize = (info.maxAngle - info.minAngle) / scanner.steps;
372 connectedDevices.push_back(info);
373
374 ARMARX_INFO << "Scanner[" << i << "]: " << scanner.frame << ", " << scanner.minAngle
375 << ", " << scanner.maxAngle << ", " << scanner.steps;
376 }
377 }
378
379 void
381 {
382 if (topicReplayerDummy)
383 {
384 ARMARX_INFO << "Fake connect (component is used for topic replay)";
385 return;
386 }
387 topic = getTopic<LaserScannerUnitListenerPrx>(topicName);
388 robotState = getProxy<RobotStateComponentInterfacePrx>(robotStateName);
389 sharedRobot = robotState->getSynchronizedRobot();
390 debugDrawer = getTopic<DebugDrawerInterfacePrx>(debugDrawerName);
391
392 for (LaserScannerSimUnit& scanner : scanners)
393 {
394 try
395 {
396 scanner.frameNode = sharedRobot->getRobotNode(scanner.frame);
397 }
398 catch (std::exception const& ex)
399 {
400 ARMARX_WARNING << "Error while querying robot frame: " << scanner.frame << " "
401 << ex.what();
402 scanner.frameNode = nullptr;
403 }
404
405 if (!scanner.frameNode)
406 {
407 ARMARX_WARNING << "Tried to use a non-existing robot node as frame for the "
408 "laser scanner simulation: "
409 << scanner.frame;
410 }
411 }
412
413 if (task)
414 {
415 task->stop();
416 }
417
418 if (enableVisualization)
419 {
420 arvizDrawer = std::make_unique<ArVizDrawer>(getArvizClient());
421 }
422
423 // Don't forget to sync the data otherwise there may be no objects
424 std::vector<VirtualRobot::SceneObjectPtr> objects;
425 while (objects.empty())
426 {
427 worldVisu->synchronizeVisualizationData();
428 objects = worldVisu->getObjects();
429 ARMARX_INFO << deactivateSpam(5) << "Got " << objects.size()
430 << " objects from the simulator";
431 if (objects.empty())
432 {
434 << "Could not get any objects from the simulator after syncing";
436 }
437 }
438
439 std::vector<VirtualRobot::RobotPtr> robots;
440 while (robots.empty())
441 {
442 worldVisu->synchronizeVisualizationData();
443 robots = worldVisu->getRobots();
444
445 if (robots.empty())
446 {
448 << "Could not get any robots from the simulator after syncing";
450 }
451 }
452
453 ARMARX_INFO << deactivateSpam(5) << "Got " << robots.size() << " robots from the simulator";
454
455 // remove active robots as they might move
456 robots.erase(std::remove_if(robots.begin(),
457 robots.end(),
458 [](const auto& r) -> bool { return not r->isPassive(); }),
459 robots.end());
460
461 ARMARX_INFO << "Got " << objects.size() << " scene objects from the simulator";
462
463 std::vector<VirtualRobot::SceneObjectPtr> validObjects;
464 for (VirtualRobot::SceneObjectPtr const& o : objects)
465 {
466 VirtualRobot::CollisionModelPtr cm = o->getCollisionModel();
467 if (!cm)
468 {
469 ARMARX_WARNING << "Scene object with no collision model: " << o->getName();
470 continue;
471 }
472
473 const auto pqpModel = cm->getCollisionModelImplementation()->getPQPModel();
474 if (pqpModel)
475 {
476 validObjects.push_back(o);
477 }
478 else
479 {
480 ARMARX_WARNING << "PQP model is not filled: " << o->getName();
481 }
482 }
483
484 // robots consist of multiple collision models
485 validObjects.insert(validObjects.end(), robots.begin(), robots.end());
486
487 fillOccupancyGrid(validObjects);
488
489 if (arvizDrawer)
490 {
491 arvizDrawer->drawOccupancyGrid(grid, boxPosZ);
492 }
493
494 visuThrottler = std::make_unique<Throttler>(visuUpdateFrequency);
495
497 &LaserScannerSimulation::updateScanData,
498 updatePeriod,
499 false,
500 "LaserScannerSimUpdate");
501 task->start();
502 }
503
504 void
506 {
507 if (task)
508 {
509 task->stop();
510 task = nullptr;
511 }
512 }
513
514 void
516 {
517 if (worldVisu)
518 {
519 getArmarXManager()->removeObjectBlocking(worldVisu);
520 worldVisu = nullptr;
521 }
522 }
523
530
531 std::string
533 {
534 return topicName;
535 }
536
537 LaserScannerInfoSeq
539 {
540 return connectedDevices;
541 }
542
543 void
544 LaserScannerSimulation::updateScanData()
545 {
546 auto startTime = armarx::Clock::Now();
548
549 const bool updateVisu =
550 enableVisualization and visuThrottler->check(TimeUtil::GetTime().toMicroSeconds());
551
552 for (LaserScannerSimUnit const& scanner : scanners)
553 {
554 if (!scanner.frameNode)
555 {
556 continue;
557 }
558 PosePtr scannerPoseP = PosePtr::dynamicCast(scanner.frameNode->getGlobalPose());
559 Eigen::Matrix4f scannerPose = scannerPoseP->toEigen();
560 Eigen::Vector2f position = scannerPose.col(3).head<2>();
561
562 Eigen::Matrix3f scannerRot = scannerPose.block<3, 3>(0, 0);
563 Eigen::Vector2f yWorld(0.0f, 1.0f);
564 Eigen::Vector2f yScanner = scannerRot.col(1).head<2>();
565 float theta = acos(yWorld.dot(yScanner));
566 if (yScanner.x() >= 0.0f)
567 {
568 theta = -theta;
569 }
570
571 float minAngle = scanner.minAngle;
572 float maxAngle = scanner.maxAngle;
573 int scanSteps = scanner.steps;
574
575 LaserScan scan;
576 scan.reserve(scanSteps);
577
578 // those scan lines would go through the robot
579 // const Interval skipInterval(M_PI_2f32, M_PIf32);
580 const Interval skipInterval(0.0, M_PI_2);
581
582 std::normal_distribution<float> dist(0.0f, scanner.noiseStdDev);
583 for (int i = 0; i < scanSteps; ++i)
584 {
585 LaserScanStep step;
586 step.angle = minAngle + i * (maxAngle - minAngle) / (scanSteps - 1);
587
588 if (skipInterval.contains(step.angle))
589 {
590 continue;
591 }
592
593 const Eigen::Vector2f scanDirGlobal =
594 Eigen::Rotation2Df(step.angle + theta) * Eigen::Vector2f(0.0f, 1.0f);
595 const float distance = grid.computeDistance(position, scanDirGlobal);
596 if (distance > 0.0f)
597 {
598 step.distance = distance + dist(engine);
599 scan.push_back(step);
600 }
601 }
602
603 topic->reportSensorValues(scanner.frame, scanner.frame, scan, now);
604
605 if (updateVisu)
606 {
607 arvizDrawer->prepareScan(scan, scanner.frame, Eigen::Affine3f(scannerPose));
608 }
609 }
610
611 if (updateVisu)
612 {
613 arvizDrawer->drawScans();
614 }
615
616 auto endTime = armarx::Clock::Now();
617 auto timeElapsed = endTime - startTime;
618
620 << "Time to simulate laser scanners: " << timeElapsed.toMilliSecondsDouble()
621 << " ms";
622 }
623
624 std::string
626 {
627 return "LaserScannerSimulation";
628 }
629
631
632} // namespace armarx::laser_scanner_simulation
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
armarx::viz::Client & getArvizClient()
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static TPtr create(Ice::PropertiesPtr properties=Ice::createProperties(), const std::string &configName="", const std::string &configDomain="ArmarX")
Factory method for a component.
Definition Component.h:116
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
Property< PropertyType > getProperty(const std::string &name)
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
Definition Logging.cpp:99
void offeringTopic(const std::string &name)
Registers a topic for retrival after initialization.
TopicProxyType getTopic(const std::string &name)
Returns a proxy of the specified topic.
bool usingProxy(const std::string &name, const std::string &endpoints="")
Registers a proxy for retrieval after initialization and adds it to the dependency list.
std::string getName() const
Retrieve name of object.
Ice::ObjectPrx getProxy(long timeoutMs=0, bool waitForScheduler=true) const
Returns the proxy of this object (optionally it waits for the proxy)
ArmarXManagerPtr getArmarXManager() const
Returns the ArmarX manager used to add and remove components.
The periodic task executes one thread method repeatedly using the time period specified in the constr...
Ice::PropertiesPtr getIceProperties() const
Returns the set of Ice properties.
static IceUtil::Time GetTime(TimeMode timeMode=TimeMode::VirtualTime)
Get the current time.
Definition TimeUtil.cpp:42
static void MSSleep(int durationMS)
lock the calling thread for a given duration (like usleep(...) but using Timeserver time)
Definition TimeUtil.cpp:100
Implements a Variant type for timestamps.
Brief description of class LaserScannerSimulation.
std::string getReportTopicName(const Ice::Current &) const override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
LaserScannerInfoSeq getConnectedDevices(const Ice::Current &) const override
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_INFO_S
Definition Logging.h:202
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
#define ARMARX_WARNING_S
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:213
std::vector< std::string > Split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)
IceInternal::Handle< Pose > PosePtr
Definition Pose.h:306
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
IceInternal::Handle< TimestampVariant > TimestampVariantPtr
std::shared_ptr< Value > value()
Definition cxxopts.hpp:855
double distance(const Point &a, const Point &b)
Definition point.hpp:95