Simulable.cpp
Go to the documentation of this file.
1 /*+-------------------------------------------------------------------------+
2  | MultiVehicle simulator (libmvsim) |
3  | |
4  | Copyright (C) 2014-2023 Jose Luis Blanco Claraco |
5  | Copyright (C) 2017 Borys Tymchenko (Odessa Polytechnic University) |
6  | Distributed under 3-clause BSD License |
7  | See COPYING |
8  +-------------------------------------------------------------------------+ */
9 
10 #include <box2d/b2_contact.h>
11 #include <mvsim/Comms/Client.h>
12 #include <mvsim/Simulable.h>
14 #include <mvsim/VisualObject.h>
15 #include <mvsim/World.h>
16 
17 #include <cmath> // fmod()
18 
19 #include "JointXMLnode.h"
20 #include "parse_utils.h"
21 #include "xml_utils.h"
22 
23 #if defined(MVSIM_HAS_ZMQ) && defined(MVSIM_HAS_PROTOBUF)
24 #include <mvsim/mvsim-msgs/TimeStampedPose.pb.h>
25 
26 #endif
27 
28 using namespace mvsim;
29 
31  [[maybe_unused]] const TSimulContext& context)
32 {
33  // Follow animation, if enabled:
34  if (anim_keyframes_path_ && !anim_keyframes_path_->empty())
35  {
36  auto& poseSeq = anim_keyframes_path_.value();
37 
38  mrpt::math::TPose3D q;
39  bool interOk = false;
40  const double tMax = mrpt::Clock::toDouble(poseSeq.rbegin()->first);
41 
42  poseSeq.interpolate(
43  mrpt::Clock::fromDouble(std::fmod(context.simul_time, tMax)), q,
44  interOk);
45 
46  if (interOk) this->setPose(initial_q_ + q);
47  }
48 
49  if (!b2dBody_) return;
50 
51  // Pos:
52  b2dBody_->SetTransform(b2Vec2(q_.x, q_.y), q_.yaw);
53 
54  // Vel:
55  b2dBody_->SetLinearVelocity(b2Vec2(dq_.vx, dq_.vy));
56  b2dBody_->SetAngularVelocity(dq_.omega);
57 }
58 
60 {
61  if (b2dBody_)
62  {
63  q_mtx_.lock();
64  // simulable_parent_->physical_objects_mtx(): already locked by caller
65 
66  // Pos:
67  const b2Vec2& pos = b2dBody_->GetPosition();
68  const float angle = b2dBody_->GetAngle();
69  q_.x = pos(0);
70  q_.y = pos(1);
71  q_.yaw = angle;
72  // The rest (z,pitch,roll) will be always 0, unless other
73  // world-element modifies them! (e.g. elevation map)
74 
75  // Update the GUI element **poses** only:
76  if (auto* vo = meAsVisualObject(); vo)
77  vo->guiUpdate(std::nullopt, std::nullopt);
78 
79  // Vel:
80  const b2Vec2& vel = b2dBody_->GetLinearVelocity();
81  const float w = b2dBody_->GetAngularVelocity();
82  dq_.vx = vel(0);
83  dq_.vy = vel(1);
84  dq_.omega = w;
85 
86  // Estimate acceleration from finite differences:
87  ddq_lin_ =
88  (q_.translation() - former_q_.translation()) * (1.0 / context.dt);
89  former_q_ = q_;
90 
91  // Instantaneous collision flag:
92  isInCollision_ = false;
93  if (b2ContactEdge* cl = b2dBody_->GetContactList();
94  cl != nullptr && cl->contact != nullptr &&
95  cl->contact->IsTouching())
96  {
97  // We may store with which other bodies it's in collision...
98  isInCollision_ = true;
99  }
100  // Reseteable collision flag:
101  hadCollisionFlag_ = hadCollisionFlag_ || isInCollision_;
102 
103  q_mtx_.unlock();
104  }
105 
106  // Optional publish to topics:
107  internalHandlePublish(context);
108 }
109 
111  [[maybe_unused]] const mrpt::math::TVector2D& force,
112  [[maybe_unused]] const mrpt::math::TPoint2D& applyPoint)
113 { /* default: do nothing*/
114 }
115 
116 mrpt::math::TTwist2D Simulable::getVelocityLocal() const
117 {
118  std::shared_lock lck(q_mtx_);
119 
120  mrpt::math::TTwist2D local_vel = dq_;
121  local_vel.rotate(-q_.yaw); // "-" means inverse pose
122  return local_vel;
123 }
124 
125 mrpt::poses::CPose2D Simulable::getCPose2D() const
126 {
127  std::shared_lock lck(q_mtx_);
128 
129  return {q_.x, q_.y, q_.yaw};
130 }
131 
132 mrpt::poses::CPose3D Simulable::getCPose3D() const
133 {
134  std::shared_lock lck(q_mtx_);
135  return mrpt::poses::CPose3D(q_);
136 }
137 
138 bool Simulable::parseSimulable(
139  const JointXMLnode<>& rootNode, const ParseSimulableParams& psp)
140 {
141  MRPT_START
142 
143  using namespace rapidxml;
144  using namespace std::string_literals;
145 
146  // -------------------------------------
147  // (Mandatory) initial pose:
148  // -------------------------------------
149  if (const xml_node<>* nPose = rootNode.first_node("init_pose"); nPose)
150  {
151  mrpt::math::TPose3D p;
152  if (3 != ::sscanf(
153  mvsim::parse(
154  nPose->value(),
156  .c_str(),
157  "%lf %lf %lf", &p.x, &p.y, &p.yaw))
158  THROW_EXCEPTION_FMT(
159  "Error parsing <init_pose>%s</init_pose>", nPose->value());
160  p.yaw *= M_PI / 180.0; // deg->rad
161 
162  this->setPose(p);
163  initial_q_ = p; // save it for later usage in some animations, etc.
164  }
165  else if (const xml_node<>* nPose3 = rootNode.first_node("init_pose3d");
166  nPose3)
167  {
168  mrpt::math::TPose3D p;
169  if (6 != ::sscanf(
170  mvsim::parse(
171  nPose3->value(),
173  .c_str(),
174  "%lf %lf %lf %lf %lf %lf ", &p.x, &p.y, &p.z, &p.yaw,
175  &p.pitch, &p.roll))
176  THROW_EXCEPTION_FMT(
177  "Error parsing <init_pose3d>%s</init_pose3d>", nPose3->value());
178  p.yaw *= M_PI / 180.0; // deg->rad
179  p.pitch *= M_PI / 180.0; // deg->rad
180  p.roll *= M_PI / 180.0; // deg->rad
181 
182  this->setPose(p);
183  initial_q_ = p; // save it for later usage in some animations, etc.
184  }
185  else if (psp.init_pose_mandatory)
186  {
187  THROW_EXCEPTION(
188  "Missing required XML node <init_pose>x y phi</init_pose>");
189  }
190 
191  // -------------------------------------
192  // (Optional) initial vel:
193  // -------------------------------------
194  if (const xml_node<>* nInitVel = rootNode.first_node("init_vel"); nInitVel)
195  {
196  mrpt::math::TTwist2D dq;
197  if (3 != ::sscanf(
198  mvsim::parse(
199  nInitVel->value(),
201  .c_str(),
202  "%lf %lf %lf", &dq.vx, &dq.vy, &dq.omega))
203  THROW_EXCEPTION_FMT(
204  "Error parsing <init_vel>%s</init_vel>", nInitVel->value());
205  dq.omega *= M_PI / 180.0; // deg->rad
206 
207  // Convert twist (velocity) from local -> global coords:
208  dq.rotate(this->getPose().yaw);
209  this->setTwist(dq);
210  }
211 
212  // -------------------------------------
213  // Parse <publish> XML tag
214  // -------------------------------------
215  if (auto node = rootNode.first_node("publish"); node)
216  {
217  TParameterDefinitions params;
218  params["publish_pose_topic"] = TParamEntry("%s", &publishPoseTopic_);
219  params["publish_pose_period"] = TParamEntry("%lf", &publishPosePeriod_);
220 
221  params["publish_relative_pose_topic"] =
222  TParamEntry("%s", &publishRelativePoseTopic_);
223  std::string listObjects;
224  params["publish_relative_pose_objects"] =
225  TParamEntry("%s", &listObjects);
226 
227  auto varValues = simulable_parent_->user_defined_variables();
228  varValues["NAME"] = name_;
229 
230  parse_xmlnode_children_as_param(*node, params, varValues);
231 
232  // Parse the "enabled" attribute:
233  {
234  bool publishEnabled = true;
235  TParameterDefinitions auxPar;
236  auxPar["enabled"] = TParamEntry("%bool", &publishEnabled);
237  parse_xmlnode_attribs(*node, auxPar, varValues);
238 
239  // Reset publish topic if enabled==false
240  if (!publishEnabled) publishPoseTopic_.clear();
241  }
242 
243  if (!listObjects.empty())
244  {
245  mrpt::system::tokenize(
246  mrpt::system::trim(listObjects), " ,",
247  publishRelativePoseOfOtherObjects_);
248 
249 #if 0
250  std::cout << "[DEBUG] "
251  "Publishing relative poses of "
252  << publishRelativePoseOfOtherObjects_.size() << " objects ("
253  << listObjects << ") to topic " << publishRelativePoseTopic_
254  << std::endl;
255 #endif
256  }
257  ASSERT_(
258  (publishRelativePoseOfOtherObjects_.empty() &&
259  publishRelativePoseTopic_.empty()) ||
260  (!publishRelativePoseOfOtherObjects_.empty() &&
261  !publishRelativePoseTopic_.empty()));
262 
263  } // end <publish>
264 
265  // Parse animation effects:
266  // ----------------------------
267  if (auto nAnim = rootNode.first_node("animation"); nAnim)
268  {
269  auto aType = nAnim->first_attribute("type");
270  ASSERTMSG_(aType, "<animation> tag requires a type=\"...\" attribute");
271  const std::string sType = aType->value();
272 
273  if (sType == "keyframes")
274  {
275  auto& poseSeq = anim_keyframes_path_.emplace();
276  poseSeq.setInterpolationMethod(mrpt::poses::imLinearSlerp);
277 
278  for (auto n = nAnim->first_node(); n; n = n->next_sibling())
279  {
280  if (strcmp(n->name(), "time_pose") == 0)
281  {
282  mrpt::math::TPose3D p;
283  double t = 0;
284  if (4 != ::sscanf(
285  mvsim::parse(
286  n->value(), getSimulableWorldObject()
288  .c_str(),
289  "%lf %lf %lf %lf", &t, &p.x, &p.y, &p.yaw))
290  THROW_EXCEPTION_FMT(
291  "Error parsing <time_pose>:\n%s", n->value());
292  p.yaw *= M_PI / 180.0; // deg->rad
293 
294  poseSeq.insert(mrpt::Clock::fromDouble(t), p);
295  }
296  else if (strcmp(n->name(), "time_pose3d") == 0)
297  {
298  mrpt::math::TPose3D p;
299  double t = 0;
300  if (7 != ::sscanf(
301  mvsim::parse(
302  n->value(), getSimulableWorldObject()
304  .c_str(),
305  "%lf %lf %lf %lf %lf %lf %lf", &t, &p.x, &p.y,
306  &p.z, &p.yaw, &p.pitch, &p.roll))
307  THROW_EXCEPTION_FMT(
308  "Error parsing <time_pose3d>:\n%s", n->value());
309  p.yaw *= M_PI / 180.0; // deg->rad
310  p.pitch *= M_PI / 180.0; // deg->rad
311  p.roll *= M_PI / 180.0; // deg->rad
312 
313  poseSeq.insert(mrpt::Clock::fromDouble(t), p);
314  }
315  }
316 
317  // anim_keyframes_path_->saveToTextFile(name_ + "_path.txt"s);
318  }
319  else
320  {
321  THROW_EXCEPTION_FMT("Invalid animation type='%s'", sType.c_str());
322  }
323  }
324 
325  return true;
326  MRPT_END
327 }
328 
329 void Simulable::internalHandlePublish(const TSimulContext& context)
330 {
331  std::shared_lock lck(q_mtx_);
332 
333  MRPT_START
334 
335  if (publishPoseTopic_.empty() && publishRelativePoseTopic_.empty()) return;
336 
337  auto& client = context.world->commsClient();
338 
339  const double tNow = mrpt::Clock::toDouble(mrpt::Clock::now());
340  if (tNow < publishPoseLastTime_ + publishPosePeriod_) return;
341 
342  publishPoseLastTime_ = tNow;
343 
344  if (!publishPoseTopic_.empty())
345  {
346  mvsim_msgs::TimeStampedPose msg;
347  msg.set_unixtimestamp(tNow);
348  msg.set_objectid(name_);
349 
350  auto pose = msg.mutable_pose();
351  pose->set_x(q_.x);
352  pose->set_y(q_.y);
353  pose->set_z(.0);
354  pose->set_yaw(q_.yaw);
355  pose->set_pitch(q_.pitch);
356  pose->set_roll(q_.roll);
357 
358  client.publishTopic(publishPoseTopic_, msg);
359  }
360 
361  if (!publishRelativePoseTopic_.empty())
362  {
363  mvsim_msgs::TimeStampedPose msg;
364  msg.set_unixtimestamp(tNow);
365  msg.set_relativetoobjectid(name_);
366 
367  // Note: getSimulableWorldObjectMtx() is already hold by my caller.
368  const auto& allObjects =
370 
371  // detect other objects and publish their relative poses wrt me:
372  for (const auto& otherId : publishRelativePoseOfOtherObjects_)
373  {
374  if (auto itObj = allObjects.find(otherId);
375  itObj != allObjects.end())
376  {
377  msg.set_objectid(otherId);
378 
379  const auto relPose = itObj->second->q_ - q_;
380 
381  auto pose = msg.mutable_pose();
382  pose->set_x(relPose.x);
383  pose->set_y(relPose.y);
384  pose->set_z(relPose.z);
385  pose->set_yaw(relPose.yaw);
386  pose->set_pitch(relPose.pitch);
387  pose->set_roll(relPose.roll);
388 
389  client.publishTopic(publishRelativePoseTopic_, msg);
390  }
391  else
392  {
393  std::cerr
394  << "[WARNING] Trying to publish relative pose of '"
395  << otherId << "' wrt '" << name_
396  << "' but could not find any object in the world with "
397  "the former name."
398  << std::endl;
399  }
400  }
401  }
402 
403  MRPT_END
404 }
405 
407 {
408  MRPT_START
409 
410 #if defined(MVSIM_HAS_ZMQ) && defined(MVSIM_HAS_PROTOBUF)
411  // Topic:
412  if (!publishPoseTopic_.empty())
413  c.advertiseTopic<mvsim_msgs::TimeStampedPose>(publishPoseTopic_);
414 
415  if (!publishRelativePoseTopic_.empty())
416  c.advertiseTopic<mvsim_msgs::TimeStampedPose>(
417  publishRelativePoseTopic_);
418 #endif
419 
420  MRPT_END
421 }
422 
423 void Simulable::setPose(const mrpt::math::TPose3D& p) const
424 {
425  q_mtx_.lock();
426 
427  Simulable& me = const_cast<Simulable&>(*this);
428 
429  me.q_ = p;
430 
431  // Update the GUI element poses only:
432  if (auto* vo = me.meAsVisualObject(); vo)
433  vo->guiUpdate(std::nullopt, std::nullopt);
434 
435  q_mtx_.unlock();
436 }
437 
438 mrpt::math::TPose3D Simulable::getPose() const
439 {
440  q_mtx_.lock_shared();
441  mrpt::math::TPose3D ret = q_;
442  q_mtx_.unlock_shared();
443  return ret;
444 }
445 
446 mrpt::math::TPose3D Simulable::getPoseNoLock() const { return q_; }
447 
448 mrpt::math::TTwist2D Simulable::getTwist() const
449 {
450  q_mtx_.lock_shared();
451  mrpt::math::TTwist2D ret = dq_;
452  q_mtx_.unlock_shared();
453  return ret;
454 }
455 
456 mrpt::math::TVector3D Simulable::getLinearAcceleration() const
457 {
458  q_mtx_.lock_shared();
459  auto ret = ddq_lin_;
460  q_mtx_.unlock_shared();
461  return ret;
462 }
463 
464 void Simulable::setTwist(const mrpt::math::TTwist2D& dq) const
465 {
466  q_mtx_.lock();
467  const_cast<mrpt::math::TTwist2D&>(dq_) = dq;
468 
469  if (b2dBody_)
470  {
471  mrpt::math::TTwist2D local_dq = dq.rotated(q_.yaw);
472 
473  b2dBody_->SetLinearVelocity(b2Vec2(local_dq.vx, local_dq.vy));
474  b2dBody_->SetAngularVelocity(dq.omega);
475  }
476 
477  q_mtx_.unlock();
478 }
std::map< std::string, TParamEntry > TParameterDefinitions
void parse_xmlnode_children_as_param(const rapidxml::xml_node< char > &xml_node, const TParameterDefinitions &params, const std::map< std::string, std::string > &variableNamesValues={}, const char *functionNameContext="", mrpt::system::COutputLogger *logger=nullptr)
Definition: xml_utils.cpp:224
TF2SIMD_FORCE_INLINE tf2Scalar angle(const Quaternion &q1, const Quaternion &q2)
mrpt::math::TPose3D getPose() const
Definition: Simulable.cpp:438
mrpt::math::TPose3D getPoseNoLock() const
No thread-safe version. Used internally only.
Definition: Simulable.cpp:446
virtual void apply_force(const mrpt::math::TVector2D &force, const mrpt::math::TPoint2D &applyPoint=mrpt::math::TPoint2D(0, 0))
Definition: Simulable.cpp:110
mrpt::math::TTwist2D getTwist() const
Definition: Simulable.cpp:448
const rapidxml::xml_node< Ch > * first_node(const char *name) const
Definition: JointXMLnode.h:30
void parse_xmlnode_attribs(const rapidxml::xml_node< char > &xml_node, const TParameterDefinitions &params, const std::map< std::string, std::string > &variableNamesValues={}, const char *functionNameContext="")
Definition: xml_utils.cpp:184
#define M_PI
geometry_msgs::TransformStamped t
A 2D column vector.
Definition: b2_math.h:41
b2Contact * contact
the contact
Definition: b2_contact.h:80
mrpt::poses::CPose3D getCPose3D() const
Alternative to getPose()
Definition: Simulable.cpp:132
double dt
timestep
Definition: basic_types.h:60
mrpt::poses::CPose2D getCPose2D() const
Alternative to getPose()
Definition: Simulable.cpp:125
std::string parse(const std::string &input, const std::map< std::string, std::string > &variableNamesValues={})
std::string name_
Definition: Simulable.h:120
const std::map< std::string, std::string > & user_defined_variables() const
Definition: World.h:391
virtual void registerOnServer(mvsim::Client &c)
Definition: Simulable.cpp:406
virtual void simul_post_timestep(const TSimulContext &context)
Definition: Simulable.cpp:59
void setPose(const mrpt::math::TPose3D &p) const
Definition: Simulable.cpp:423
virtual void simul_pre_timestep(const TSimulContext &context)
Definition: Simulable.cpp:30
void advertiseTopic(const std::string &topicName)
Definition: Client.h:187
mrpt::math::TTwist2D getVelocityLocal() const
Definition: Simulable.cpp:116
void setTwist(const mrpt::math::TTwist2D &dq) const
Definition: Simulable.cpp:464
mvsim::Client & commsClient()
Definition: World.h:379
bool IsTouching() const
Is this contact touching?
Definition: b2_contact.h:281
mrpt::math::TVector3D getLinearAcceleration() const
Definition: Simulable.cpp:456
World * getSimulableWorldObject()
Definition: Simulable.h:113
SimulableList & getListOfSimulableObjects()
Always lock/unlock getListOfSimulableObjectsMtx() before using this:
Definition: World.h:313
virtual VisualObject * meAsVisualObject()
Definition: Simulable.h:59


mvsim
Author(s):
autogenerated on Tue Jul 4 2023 03:08:21