ElevationMap.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 <mrpt/opengl/COpenGLScene.h>
11 #include <mrpt/opengl/CPointCloud.h>
12 #include <mrpt/tfest.h> // least-squares methods
13 #include <mrpt/version.h>
14 #include <mvsim/VehicleBase.h>
15 #include <mvsim/World.h>
17 
18 #include <limits>
19 #include <rapidxml.hpp>
20 
21 #include "xml_utils.h"
22 
23 using namespace rapidxml;
24 using namespace mvsim;
25 using namespace std;
26 
27 ElevationMap::ElevationMap(World* parent, const rapidxml::xml_node<char>* root)
28  : WorldElementBase(parent)
29 {
31 }
32 
35 {
36  // Other general params:
37  TParameterDefinitions params;
38 
39  std::string sElevationImgFile;
40  params["elevation_image"] = TParamEntry("%s", &sElevationImgFile);
41  std::string sTextureImgFile;
42  params["texture_image"] = TParamEntry("%s", &sTextureImgFile);
43 
44  double img_min_z = 0.0, img_max_z = 5.0;
45  params["elevation_image_min_z"] = TParamEntry("%lf", &img_min_z);
46  params["elevation_image_max_z"] = TParamEntry("%lf", &img_max_z);
47 
48  double corner_min_x = std::numeric_limits<double>::max();
49  double corner_min_y = std::numeric_limits<double>::max();
50 
51  params["corner_min_x"] = TParamEntry("%lf", &corner_min_x);
52  params["corner_min_y"] = TParamEntry("%lf", &corner_min_y);
53 
54  mrpt::img::TColor mesh_color(0xa0, 0xe0, 0xa0);
55  params["mesh_color"] = TParamEntry("%color", &mesh_color);
56 
57  params["resolution"] = TParamEntry("%f", &resolution_);
58  params["texture_extension_x"] = TParamEntry("%f", &textureExtensionX_);
59  params["texture_extension_y"] = TParamEntry("%f", &textureExtensionY_);
60 
61  params["debug_show_contact_points"] =
63 
65  *root, params, world_->user_defined_variables());
66 
67  // Load elevation data:
68  mrpt::math::CMatrixFloat elevation_data;
69  if (!sElevationImgFile.empty())
70  {
71  sElevationImgFile = world_->local_to_abs_path(sElevationImgFile);
72 
73  mrpt::img::CImage imgElev;
74  if (!imgElev.loadFromFile(
75  sElevationImgFile, 0 /*force load grayscale*/))
76  throw std::runtime_error(mrpt::format(
77  "[ElevationMap] ERROR: Cannot read elevation image '%s'",
78  sElevationImgFile.c_str()));
79 
80  // Scale: [0,1] => [min_z,max_z]
81  // Get image normalized in range [0,1]
82  imgElev.getAsMatrix(elevation_data);
83  ASSERT_(img_min_z != img_max_z);
84 
85  const double vmin = elevation_data.minCoeff();
86  const double vmax = elevation_data.maxCoeff();
87  mrpt::math::CMatrixFloat f = elevation_data;
88  f -= vmin;
89  f *= (img_max_z - img_min_z) / (vmax - vmin);
90  mrpt::math::CMatrixFloat m(
91  elevation_data.rows(), elevation_data.cols());
92  m.setConstant(img_min_z);
93  f += m;
94  elevation_data = std::move(f);
95  }
96  else
97  {
98  MRPT_TODO("Imgs or txt matrix")
99  }
100 
101  // Load texture (optional):
102  mrpt::img::CImage mesh_image;
103  bool has_mesh_image = false;
104  if (!sTextureImgFile.empty())
105  {
106  sTextureImgFile = world_->xmlPathToActualPath(sTextureImgFile);
107 
108  if (!mesh_image.loadFromFile(sTextureImgFile))
109  throw std::runtime_error(mrpt::format(
110  "[ElevationMap] ERROR: Cannot read texture image '%s'",
111  sTextureImgFile.c_str()));
112  has_mesh_image = true;
113  }
114 
115  // Build mesh:
116  gl_mesh_ = mrpt::opengl::CMesh::Create();
117 
118  gl_mesh_->enableTransparency(false);
119 
120  if (has_mesh_image)
121  {
122  gl_mesh_->assignImageAndZ(mesh_image, elevation_data);
123 
124 #if MRPT_VERSION >= 0x270
125  gl_mesh_->setMeshTextureExtension(
127 #endif
128  }
129  else
130  {
131  gl_mesh_->setZ(elevation_data);
132  gl_mesh_->setColor_u8(mesh_color);
133  }
134 
135  // Save copy for calcs:
136  meshCacheZ_ = elevation_data;
137 
138  // Extension: X,Y
139  const double LX = (elevation_data.rows() - 1) * resolution_;
140  const double LY = (elevation_data.cols() - 1) * resolution_;
141 
142  if (corner_min_x == std::numeric_limits<double>::max())
143  corner_min_x = -0.5 * LX;
144 
145  if (corner_min_y == std::numeric_limits<double>::max())
146  corner_min_y = -0.5 * LY;
147 
148  // Important: the yMin/yMax in the next line are swapped to handle
149  // the "+y" different direction in image and map coordinates, it is not
150  // a bug:
151  gl_mesh_->setGridLimits(
152  corner_min_x, corner_min_x + LX, corner_min_y, corner_min_y + LY);
153 
154  gl_debugWheelsContactPoints_ = mrpt::opengl::CPointCloud::Create();
155  gl_debugWheelsContactPoints_->enableVariablePointSize(false);
156  gl_debugWheelsContactPoints_->setPointSize(7.0f);
157 }
158 
160  const mrpt::optional_ref<mrpt::opengl::COpenGLScene>& viz,
161  const mrpt::optional_ref<mrpt::opengl::COpenGLScene>& physical,
162  [[maybe_unused]] bool childrenOnly)
163 {
164  using namespace mrpt::math;
165 
166  ASSERTMSG_(
167  gl_mesh_,
168  "ERROR: Can't render Mesh before loading it! Have you called "
169  "loadConfigFrom() first?");
170 
171  // 1st time call?? -> Create objects
172  if (firstSceneRendering_ && viz && physical)
173  {
174  firstSceneRendering_ = false;
175  viz->get().insert(gl_mesh_);
176  physical->get().insert(gl_mesh_);
177 
178  viz->get().insert(gl_debugWheelsContactPoints_);
179  }
180 }
181 
183  [[maybe_unused]] const TSimulContext& context)
184 {
185  // For each vehicle:
186  // 1) Compute its 3D pose according to the mesh tilt angle.
187  // 2) Apply gravity force
188  const double gravity = parent()->get_gravity();
189 
190  ASSERT_(gl_mesh_);
191 
192  const World::VehicleList& lstVehs = this->world_->getListOfVehicles();
193  for (auto& nameVeh : lstVehs)
194  {
195  world_->getTimeLogger().enter("elevationmap.handle_vehicle");
196 
197  auto& veh = nameVeh.second;
198 
199  const size_t nWheels = veh->getNumWheels();
200 
201  // 1) Compute its 3D pose according to the mesh tilt angle.
202  // Idea: run a least-squares method to find the best
203  // SE(3) transformation that map the wheels contact point,
204  // as seen in local & global coordinates.
205  // (For large tilt angles, may have to run it iteratively...)
206  // -------------------------------------------------------------
207  // the final downwards direction (unit vector (0,0,-1)) as seen in
208  // vehicle local frame.
209  mrpt::math::TPoint3D dir_down;
210  for (int iter = 0; iter < 2; iter++)
211  {
212  const mrpt::math::TPose3D& cur_pose = veh->getPose();
213  // This object is faster for repeated point projections
214  const mrpt::poses::CPose3D cur_cpose(cur_pose);
215 
216  mrpt::math::TPose3D new_pose = cur_pose;
217  corrs_.clear();
218 
219  bool out_of_area = false;
220  for (size_t iW = 0; !out_of_area && iW < nWheels; iW++)
221  {
222  const Wheel& wheel = veh->getWheelInfo(iW);
223 
224  // Local frame
225  mrpt::tfest::TMatchingPair corr;
226 
227 #if MRPT_VERSION >= 0x240
228  corr.localIdx = iW;
229  corr.local = mrpt::math::TPoint3D(wheel.x, wheel.y, 0);
230 #else
231  corr.other_idx = iW;
232  corr.other_x = wheel.x;
233  corr.other_y = wheel.y;
234  corr.other_z = 0;
235 #endif
236  // Global frame
237  const mrpt::math::TPoint3D gPt =
238  cur_cpose.composePoint({wheel.x, wheel.y, 0.0});
239  float z;
240  if (!getElevationAt(gPt.x /*in*/, gPt.y /*in*/, z /*out*/))
241  {
242  out_of_area = true;
243  continue; // vehicle is out of bounds!
244  }
245 
246 #if MRPT_VERSION >= 0x240
247  corr.globalIdx = iW;
248  corr.global = mrpt::math::TPoint3D(gPt.x, gPt.y, z);
249 #else
250  corr.this_idx = iW;
251  corr.this_x = gPt.x;
252  corr.this_y = gPt.y;
253  corr.this_z = z;
254 #endif
255 
256  corrs_.push_back(corr);
257  }
258  if (out_of_area) continue;
259 
260  // Register:
261  double transf_scale;
262  mrpt::poses::CPose3DQuat tmpl;
263 
264  mrpt::tfest::se3_l2(
265  corrs_, tmpl, transf_scale, true /*force scale unity*/);
266 
267  optimalTf_ = mrpt::poses::CPose3D(tmpl);
268 
269 #if 0
270  std::cout << "iter: " << iter << " poseErr:"
271  << std::sqrt(corrs.overallSquareError(optimal_transf_))
272  << " p:" << optimal_transf_ << "\n";
273 #endif
274 
275  new_pose.z = optimalTf_.z();
276  new_pose.yaw = optimalTf_.yaw();
277  new_pose.pitch = optimalTf_.pitch();
278  new_pose.roll = optimalTf_.roll();
279 
280  veh->setPose(new_pose);
281 
282  } // end iters
283 
284  // debug contact points:
286  {
288  for (const auto& c : corrs_)
289  gl_debugWheelsContactPoints_->insertPoint(c.global);
290 
291 #if 0
292  // DEBUG:
293  for (float x = -60; x < 60; x += 0.5)
294  for (float y = -60; y < 60; y += 0.5)
295  {
296  float z;
297  if (!getElevationAt(x /*in*/, y /*in*/, z /*out*/))
298  continue;
299  gl_debugWheelsContactPoints_->insertPoint(x, y, z);
300  }
301  debugShowContactPoints_ = false;
302 #endif
303  }
304 
305  // compute "down" direction:
306  {
307  mrpt::poses::CPose3D rot_only;
308  rot_only.setRotationMatrix(optimalTf_.getRotationMatrix());
309  rot_only.inverseComposePoint(
310  .0, .0, -1.0, dir_down.x, dir_down.y, dir_down.z);
311  }
312 
313  // 2) Apply gravity force
314  // -------------------------------------------------------------
315  {
316  // To chassis:
317  const double chassis_weight = veh->getChassisMass() * gravity;
318  const mrpt::math::TPoint2D chassis_com =
319  veh->getChassisCenterOfMass();
320  veh->apply_force(
321  {dir_down.x * chassis_weight, dir_down.y * chassis_weight},
322  chassis_com);
323 
324  // To wheels:
325  for (size_t iW = 0; iW < nWheels; iW++)
326  {
327  const Wheel& wheel = veh->getWheelInfo(iW);
328  const double wheel_weight = wheel.mass * gravity;
329  veh->apply_force(
330  {dir_down.x * wheel_weight, dir_down.y * wheel_weight},
331  {wheel.x, wheel.y});
332  }
333  }
334 
335  world_->getTimeLogger().leave("elevationmap.handle_vehicle");
336  }
337 }
338 
340 {
342 
343  // TODO: Think if this is still applicable:
344  // Save all elements positions in prestep, then here scale their
345  // movements * cos(angle)
346 }
347 
348 static float calcz(
349  const mrpt::math::TPoint3Df& p1, const mrpt::math::TPoint3Df& p2,
350  const mrpt::math::TPoint3Df& p3, float x, float y)
351 {
352  const float det = (p2.x - p3.x) * (p1.y - p3.y) + //
353  (p3.y - p2.y) * (p1.x - p3.x);
354  ASSERT_(det != 0.0f);
355 
356  const float l1 =
357  ((p2.x - p3.x) * (y - p3.y) + (p3.y - p2.y) * (x - p3.x)) / det;
358  const float l2 =
359  ((p3.x - p1.x) * (y - p3.y) + (p1.y - p3.y) * (x - p3.x)) / det;
360  const float l3 = 1.0f - l1 - l2;
361 
362  return l1 * p1.z + l2 * p2.z + l3 * p3.z;
363 }
364 
365 bool ElevationMap::getElevationAt(double x, double y, float& z) const
366 {
367  const mrpt::opengl::CMesh* mesh = gl_mesh_.get();
368 
369  const float x0 = mesh->getxMin();
370  const float y0 = mesh->getyMin();
371  const float x1 = mesh->getxMax();
372  const float y1 = mesh->getyMax();
373 
374  const size_t nCellsX = meshCacheZ_.rows();
375  const size_t nCellsY = meshCacheZ_.cols();
376 
377  const float sCellX = (x1 - x0) / (nCellsX - 1);
378  const float sCellY = (y1 - y0) / (nCellsY - 1);
379 
380  // Discretize:
381  const int cx00 = ::floor((x - x0) / sCellX);
382  const int cy00 = ::floor((y - y0) / sCellY);
383 
384  if (cx00 < 1 || cx00 >= int(nCellsX - 1) || cy00 < 1 ||
385  cy00 >= int(nCellsY - 1))
386  return false;
387 
388  // Linear interpolation:
389  const float z00 = meshCacheZ_(cx00, cy00);
390  const float z01 = meshCacheZ_(cx00, cy00 + 1);
391  const float z10 = meshCacheZ_(cx00 + 1, cy00);
392  const float z11 = meshCacheZ_(cx00 + 1, cy00 + 1);
393 
394  //
395  // p01 ---- p11
396  // | |
397  // p00 ---- p10
398  //
399  const mrpt::math::TPoint3Df p00(.0f, .0f, z00);
400  const mrpt::math::TPoint3Df p01(.0f, sCellY, z01);
401  const mrpt::math::TPoint3Df p10(sCellX, .0f, z10);
402  const mrpt::math::TPoint3Df p11(sCellX, sCellY, z11);
403 
404  const float lx = x - (x0 + cx00 * sCellX);
405  const float ly = y - (y0 + cy00 * sCellY);
406 
407  if (ly >= lx)
408  z = calcz(p00, p01, p11, lx, ly);
409  else
410  z = calcz(p00, p10, p11, lx, ly);
411 
412  return true;
413 }
This file contains rapidxml parser and DOM implementation.
float textureExtensionX_
0=auto
Definition: ElevationMap.h:48
mrpt::opengl::CMesh::Ptr gl_mesh_
Definition: ElevationMap.h:43
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
virtual void loadConfigFrom(const rapidxml::xml_node< char > *root) override
f
virtual void simul_pre_timestep(const TSimulContext &context) override
virtual void internalGuiUpdate(const mrpt::optional_ref< mrpt::opengl::COpenGLScene > &viz, const mrpt::optional_ref< mrpt::opengl::COpenGLScene > &physical, bool childrenOnly) override
std::multimap< std::string, VehicleBase::Ptr > VehicleList
Definition: World.h:281
const VehicleList & getListOfVehicles() const
Definition: World.h:303
std::shared_ptr< mrpt::opengl::CPointCloud > gl_debugWheelsContactPoints_
Definition: ElevationMap.h:44
mrpt::math::CMatrixFloat meshCacheZ_
Definition: ElevationMap.h:52
double x
Definition: Wheel.h:38
double y
Definition: Wheel.h:38
const std::map< std::string, std::string > & user_defined_variables() const
Definition: World.h:391
float textureExtensionY_
0=auto
Definition: ElevationMap.h:49
virtual void simul_post_timestep(const TSimulContext &context)
Definition: Simulable.cpp:59
static float calcz(const mrpt::math::TPoint3Df &p1, const mrpt::math::TPoint3Df &p2, const mrpt::math::TPoint3Df &p3, float x, float y)
mrpt::system::CTimeLogger & getTimeLogger()
Definition: World.h:320
mrpt::poses::CPose3D optimalTf_
Definition: ElevationMap.h:59
double mass
[kg]
Definition: Wheel.h:45
std::string local_to_abs_path(const std::string &in_path) const
Definition: World.cpp:252
bool getElevationAt(double x, double y, float &z) const
return false if out of bounds
std::string xmlPathToActualPath(const std::string &modelURI) const
Definition: World.cpp:244
double get_gravity() const
Definition: World.h:136
mrpt::tfest::TMatchingPairList corrs_
Definition: ElevationMap.h:58
virtual void simul_post_timestep(const TSimulContext &context) override


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