ElevationMap.cpp
Go to the documentation of this file.
1 /*+-------------------------------------------------------------------------+
2  | MultiVehicle simulator (libmvsim) |
3  | |
4  | Copyright (C) 2014-2024 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/maps/CSimplePointsMap.h>
11 #include <mrpt/opengl/COpenGLScene.h>
12 #include <mrpt/version.h>
13 #include <mvsim/VehicleBase.h>
14 #include <mvsim/World.h>
16 
17 #include <limits>
18 #include <rapidxml.hpp>
19 
20 #include "../parse_utils.h"
21 #include "xml_utils.h"
22 
23 using namespace rapidxml;
24 using namespace mvsim;
25 using namespace std;
26 
27 namespace
28 {
29 mrpt::math::CMatrixFloat applyConvolution(
30  const mrpt::math::CMatrixFloat& data, const mrpt::math::CMatrixDouble& kernel)
31 {
32  // Dimensions of the data and kernel
33  const size_t rows = data.rows();
34  const size_t cols = data.cols();
35  const size_t kernelSize = kernel.rows();
36  const size_t kernelRadius = kernelSize / 2;
37 
38  // Ensure kernel is square and normalized
39  ASSERT_EQUAL_(kernelSize, static_cast<size_t>(kernel.cols()));
40 
41  ASSERTMSG_(std::abs(kernel.sum() - 1.0) < 5e-3, "Kernel must be normalized (sum to 1)");
42 
43  // Output matrix
44  mrpt::math::CMatrixFloat result(rows, cols);
45 
46  // Apply convolution
47  for (size_t i = 0; i < rows; ++i)
48  {
49  for (size_t j = 0; j < cols; ++j)
50  {
51  double sum = 0.0;
52 
53  // Convolution loop over kernel
54  for (int ki = -kernelRadius; ki <= static_cast<int>(kernelRadius); ++ki)
55  {
56  for (int kj = -kernelRadius; kj <= static_cast<int>(kernelRadius); ++kj)
57  {
58  int ni = i + ki;
59  int nj = j + kj;
60 
61  // Boundary check
62  if (ni >= 0 && ni < static_cast<int>(rows) && nj >= 0 &&
63  nj < static_cast<int>(cols))
64  {
65  sum += data(ni, nj) * kernel(ki + kernelRadius, kj + kernelRadius);
66  }
67  }
68  }
69  result(i, j) = sum;
70  }
71  }
72  return result;
73 }
74 } // namespace
75 
76 ElevationMap::ElevationMap(World* parent, const rapidxml::xml_node<char>* root)
77  : WorldElementBase(parent)
78 {
80 }
81 
84 {
85  // Other general params:
86  TParameterDefinitions params;
87 
88  std::string sElevationImgFile;
89  params["elevation_image"] = TParamEntry("%s", &sElevationImgFile);
90  std::string sTextureImgFile;
91  params["texture_image"] = TParamEntry("%s", &sTextureImgFile);
92  int texture_rotate = 0;
93  params["texture_image_rotate"] = TParamEntry("%i", &texture_rotate);
94 
95  std::string sElevationMatrixData;
96  params["elevation_data_matrix"] = TParamEntry("%s", &sElevationMatrixData);
97 
98  std::string sDemTextFile;
99  params["dem_xyzrgb_file"] = TParamEntry("%s", &sDemTextFile);
100 
101  mrpt::math::TPoint3Df dem_xyz_offset = {0, 0, 0};
102  params["dem_offset_x"] = TParamEntry("%f", &dem_xyz_offset.x);
103  params["dem_offset_y"] = TParamEntry("%f", &dem_xyz_offset.y);
104  params["dem_offset_z"] = TParamEntry("%f", &dem_xyz_offset.z);
105 
106  double img_min_z = 0.0, img_max_z = 5.0;
107  params["elevation_image_min_z"] = TParamEntry("%lf", &img_min_z);
108  params["elevation_image_max_z"] = TParamEntry("%lf", &img_max_z);
109 
110  double corner_min_x = std::numeric_limits<double>::max();
111  double corner_min_y = std::numeric_limits<double>::max();
112 
113  params["corner_min_x"] = TParamEntry("%lf", &corner_min_x);
114  params["corner_min_y"] = TParamEntry("%lf", &corner_min_y);
115 
116  mrpt::img::TColor mesh_color(0xa0, 0xe0, 0xa0);
117  params["mesh_color"] = TParamEntry("%color", &mesh_color);
118 
119  params["resolution"] = TParamEntry("%lf", &resolution_);
120  params["texture_extension_x"] = TParamEntry("%lf", &textureExtensionX_);
121  params["texture_extension_y"] = TParamEntry("%lf", &textureExtensionY_);
122 
123  params["model_split_size"] = TParamEntry("%lf", &model_split_size_);
124 
125  std::string convolution_kernel_str;
126  params["apply_kernel"] = TParamEntry("%s", &convolution_kernel_str);
127 
129 
130  // Load elevation data & (optional) image data:
131  mrpt::math::CMatrixFloat elevation_data;
132  std::optional<mrpt::img::CImage> mesh_image;
133 
134  if (!sElevationImgFile.empty())
135  {
136  sElevationImgFile = world_->local_to_abs_path(sElevationImgFile);
137 
138  mrpt::img::CImage imgElev;
139  if (!imgElev.loadFromFile(sElevationImgFile, 0 /*force load grayscale*/))
140  throw std::runtime_error(mrpt::format(
141  "[ElevationMap] ERROR: Cannot read elevation image '%s'",
142  sElevationImgFile.c_str()));
143 
144  // Scale: [0,1] => [min_z,max_z]
145  // Get image normalized in range [0,1]
146  imgElev.getAsMatrix(elevation_data);
147  ASSERT_(img_min_z != img_max_z);
148 
149  const double vmin = elevation_data.minCoeff();
150  const double vmax = elevation_data.maxCoeff();
151  mrpt::math::CMatrixFloat f = elevation_data;
152  f -= vmin;
153  f *= (img_max_z - img_min_z) / (vmax - vmin);
154  mrpt::math::CMatrixFloat m(elevation_data.rows(), elevation_data.cols());
155  m.setConstant(img_min_z);
156  f += m;
157  elevation_data = std::move(f);
158  }
159  else if (!sElevationMatrixData.empty())
160  {
161  sElevationMatrixData = mrpt::system::trim(sElevationMatrixData);
162 
163  std::stringstream sErrors;
164  if (!elevation_data.fromMatlabStringFormat(sElevationMatrixData, sErrors))
165  {
166  THROW_EXCEPTION_FMT("Error parsing <elevation_data_matrix>: %s", sErrors.str().c_str());
167  }
168  }
169  else
170  {
171  ASSERTMSG_(
172  !sDemTextFile.empty(),
173  "Either <elevation_image>, <elevation_data_matrix> or <dem_xyzrgb_file> must be "
174  "provided");
175 
176  sDemTextFile = world_->local_to_abs_path(sDemTextFile);
177 
178  mrpt::math::CMatrixDouble data;
179  data.loadFromTextFile(sDemTextFile);
180  ASSERTMSG_(data.cols() == 6, "DEM txt file format error: expected 6 columns (x,y,z,r,g,b)");
181 
182  // Apply optional offset:
183  data.col(0).array() += dem_xyz_offset.x;
184  data.col(1).array() += dem_xyz_offset.y;
185  data.col(2).array() += dem_xyz_offset.z;
186 
187  // Points from DEM geographic sources are not sorted, not even uniformly sampled.
188  // Let's re-sample them:
189  const double minx = data.col(0).minCoeff();
190  const double maxx = data.col(0).maxCoeff();
191  const double miny = data.col(1).minCoeff();
192  const double maxy = data.col(1).maxCoeff();
193 
194  corner_min_x = minx;
195  corner_min_y = miny;
196 
197  const auto nx = static_cast<unsigned int>(std::ceil((maxx - minx) / resolution_));
198  const auto ny = static_cast<unsigned int>(std::ceil((maxy - miny) / resolution_));
199 
200  parent()->logFmt(
201  mrpt::system::LVL_DEBUG,
202  "[ElevationMap] Loaded %u points, min_corner=(%lf,%lf), max_corner=(%lf,%lf), "
203  "cells=(%u,%u)",
204  static_cast<unsigned>(data.rows()), minx, miny, maxx, maxy, nx, ny);
205 
206  // Store points in a map for using it as a KD-tree:
207  // (this could be avoided writing a custom adaptor for nanoflann, but I don't
208  // have time for it now)
209  mrpt::maps::CSimplePointsMap pts;
210  pts.reserve(data.rows());
211  // Insert points wrt the min. corner, to ensure accuracy with float's instead of double's:
212  for (int i = 0; i < data.rows(); i++)
213  pts.insertPoint(data(i, 0) - minx, data(i, 1) - miny, data(i, 2));
214 
215  pts.kdTreeEnsureIndexBuilt2D(); // 2D queries, not 3D!
216  elevation_data.resize(nx, ny);
217 
218  // Image data: rows=>+X in the world; cols=>+Y in the world
219  // So we access image like: mesh_image(col,row)=>(cy,cx)
220  mesh_image.emplace();
221  mesh_image->resize(ny, nx, mrpt::img::CH_RGB);
222 
223  for (unsigned int cx = 0; cx < nx; cx++)
224  {
225  const float lx = (0.5f + cx) * resolution_;
226  for (unsigned int cy = 0; cy < ny; cy++)
227  {
228  const float ly = (0.5f + cy) * resolution_;
229  float closestSqrErr = 0;
230  const auto idxPt = pts.kdTreeClosestPoint2D(lx, ly, closestSqrErr);
231  // Store data in the cell:
232  elevation_data(cx, cy) = data(idxPt, 2 /*z*/);
233  const uint8_t R = data(idxPt, 3);
234  const uint8_t G = data(idxPt, 4);
235  const uint8_t B = data(idxPt, 5);
236  // mesh_image->setPixel(cy, cx, mrpt::img::TColor(R, G, B));
237  auto* dest = &mesh_image->ptrLine<uint8_t>(cx)[3 * cy];
238  // Copy the color:
239  *dest++ = B;
240  *dest++ = G;
241  *dest++ = R;
242  }
243  }
244 
245  } // end resample DEM geographic data
246 
247  // Load texture (if not defined already above):
248  if (!mesh_image && !sTextureImgFile.empty())
249  {
250  sTextureImgFile = world_->xmlPathToActualPath(sTextureImgFile);
251  mesh_image.emplace();
252 
253  if (!mesh_image->loadFromFile(sTextureImgFile))
254  throw std::runtime_error(mrpt::format(
255  "[ElevationMap] ERROR: Cannot read texture image '%s'", sTextureImgFile.c_str()));
256 
257  // Apply rotation:
258  switch (texture_rotate)
259  {
260  case 0:
261  break;
262  case 90:
263  case -90:
264  case 180:
265  case -180:
266  {
267  mrpt::img::CImage im;
268  mesh_image->rotateImage(
269  im, mrpt::DEG2RAD(texture_rotate), mesh_image->getWidth() / 2,
270  mesh_image->getHeight() / 2);
271  mesh_image = std::move(im);
272  }
273  break;
274  default:
275  THROW_EXCEPTION("texture_image_rotate can only be: 0, 90, -90, 180");
276  }
277  }
278 
279  // Optional height filtering:
280  if (!convolution_kernel_str.empty())
281  {
282  mrpt::math::CMatrixDouble kernel;
283  std::stringstream ss(mvsim::trim(convolution_kernel_str));
284  try
285  {
286  kernel.loadFromTextFile(ss);
287  ASSERT_(kernel.cols() == kernel.rows());
288  ASSERT_(kernel.cols() > 1);
289  }
290  catch (const std::exception& e)
291  {
292  THROW_EXCEPTION_FMT(
293  "Error parsing kernel as matrix: '%s'.\nError: %s", convolution_kernel_str.c_str(),
294  e.what());
295  }
296 
297  parent()->logFmt(
298  mrpt::system::LVL_DEBUG, "[ElevationMap] Applying filtering convolution filter %ux%u",
299  static_cast<unsigned>(kernel.rows()), static_cast<unsigned>(kernel.cols()));
300 
301  elevation_data = applyConvolution(elevation_data, kernel);
302 
303  } // end apply convolution kernel
304 
305  // Extension: X,Y
306  const double LX = (elevation_data.rows() - 1) * resolution_;
307  const double LY = (elevation_data.cols() - 1) * resolution_;
308 
309  if (corner_min_x == std::numeric_limits<double>::max()) corner_min_x = -0.5 * LX;
310  if (corner_min_y == std::numeric_limits<double>::max()) corner_min_y = -0.5 * LY;
311 
312  // Propose to the "world" to use this coordinates as reference
313  // for opengl to work with very large coordinates (e.g. UTM)
314  parent()->worldRenderOffsetPropose({-corner_min_x, -corner_min_y, .0});
315 
316  // Save copy for calcs:
317  meshCacheZ_ = elevation_data;
318  meshMinX_ = corner_min_x;
319  meshMinY_ = corner_min_y;
320  meshMaxX_ = corner_min_x + LX;
321  meshMaxY_ = corner_min_y + LY;
322 
323  // Build mesh:
324  ASSERT_GE_(model_split_size_, .0f);
325  if (model_split_size_ == 0)
326  {
327  // One single mesh:
328  auto gl_mesh = mrpt::opengl::CMesh::Create();
329  gl_meshes_.push_back(gl_mesh);
330 
331  gl_mesh->enableTransparency(false);
332 
333  if (mesh_image)
334  {
335  gl_mesh->assignImageAndZ(*mesh_image, elevation_data);
336  gl_mesh->setMeshTextureExtension(textureExtensionX_, textureExtensionY_);
337  }
338  else
339  {
340  gl_mesh->setZ(elevation_data);
341  gl_mesh->setColor_u8(mesh_color);
342  }
343 
344  gl_mesh->setGridLimits(corner_min_x, corner_min_x + LX, corner_min_y, corner_min_y + LY);
345 
346  // hint for rendering z-order:
347  gl_mesh->setLocalRepresentativePoint(
348  mrpt::math::TPoint3Df(corner_min_x + 0.5 * LX, corner_min_y + 0.5 * LY, .0f));
349  }
350  else
351  {
352  // Split in smaller meshes:
353  const int M = static_cast<int>(std::ceil(model_split_size_ / resolution_));
354  const double subSize = M * resolution_;
355  const size_t NX = static_cast<size_t>(std::ceil(LX / subSize));
356  const size_t NY = static_cast<size_t>(std::ceil(LY / subSize));
357  for (size_t iX = 0; iX < NX; iX++)
358  {
359  // (recall: rows=X, cols=Y)
360  // M+1: we need to duplicate the elevation data from border cells to neighboring
361  // blocks to ensure continuity.
362 
363  const size_t startIx = iX * M;
364  const size_t lenIx_p = std::min<size_t>(M, elevation_data.rows() - startIx);
365  const size_t lenIx = std::min<size_t>(M + 1, elevation_data.rows() - startIx);
366 
367  for (size_t iY = 0; iY < NY; iY++)
368  {
369  const size_t startIy = iY * M;
370  const size_t lenIy_p = std::min<size_t>(M, elevation_data.cols() - startIy);
371  const size_t lenIy = std::min<size_t>(M + 1, elevation_data.cols() - startIy);
372 
373  // Extract sub-matrix for elevation data:
374  const auto subEle = elevation_data.extractMatrix(lenIx, lenIy, startIx, startIy);
375 
376  // One sub-mesh:
377  auto gl_mesh = mrpt::opengl::CMesh::Create();
378  gl_meshes_.push_back(gl_mesh);
379 
380  gl_mesh->enableTransparency(false);
381 
382  if (mesh_image)
383  {
384  gl_mesh->assignImageAndZ(*mesh_image, subEle);
385  gl_mesh->setMeshTextureExtension(textureExtensionX_, textureExtensionY_);
386  }
387  else
388  {
389  gl_mesh->setZ(subEle);
390  gl_mesh->setColor_u8(mesh_color);
391  }
392 
393  gl_mesh->setGridLimits(
394  corner_min_x + iX * subSize,
395  corner_min_x + iX * subSize + lenIx_p * resolution_,
396  corner_min_y + iY * subSize,
397  corner_min_y + iY * subSize + lenIy_p * resolution_);
398 
399  // hint for rendering z-order:
400  gl_mesh->setLocalRepresentativePoint(mrpt::math::TPoint3Df(
401  corner_min_x + (iX + 0.5) * subSize, corner_min_y + (iY + 0.5) * subSize,
402  subEle(0, 0)));
403  }
404  }
405  }
406 }
407 
409  const mrpt::optional_ref<mrpt::opengl::COpenGLScene>& viz,
410  const mrpt::optional_ref<mrpt::opengl::COpenGLScene>& physical,
411  [[maybe_unused]] bool childrenOnly)
412 {
413  ASSERTMSG_(
414  !gl_meshes_.empty(),
415  "ERROR: Can't render Mesh before loading it! Have you called "
416  "loadConfigFrom() first?");
417 
418  // 1st time call?? -> Create objects
419  if (firstSceneRendering_ && viz && physical)
420  {
421  firstSceneRendering_ = false;
422  for (const auto& glMesh : gl_meshes_)
423  {
424  glMesh->setPose(parent()->applyWorldRenderOffset(mrpt::poses::CPose3D::Identity()));
425 
426  viz->get().insert(glMesh);
427  physical->get().insert(glMesh);
428  }
429  }
430 }
431 
432 void ElevationMap::simul_pre_timestep([[maybe_unused]] const TSimulContext& context)
433 {
434  // Nothing special to do.
435  // Since Sep-2024, this functionality has moved to
436  // World::internal_simul_pre_step_terrain_elevation()
437 }
438 
440 {
442 
443  // TODO: Think if this is still applicable:
444  // Save all elements positions in prestep, then here scale their
445  // movements * cos(angle)
446 }
447 
448 namespace
449 {
450 double calcz(
451  const mrpt::math::TPoint3D& p1, const mrpt::math::TPoint3D& p2, const mrpt::math::TPoint3D& p3,
452  double x, double y)
453 {
454  const double det = (p2.x - p3.x) * (p1.y - p3.y) + //
455  (p3.y - p2.y) * (p1.x - p3.x);
456  ASSERT_(det != 0.0);
457 
458  const double l1 = ((p2.x - p3.x) * (y - p3.y) + (p3.y - p2.y) * (x - p3.x)) / det;
459  const double l2 = ((p3.x - p1.x) * (y - p3.y) + (p1.y - p3.y) * (x - p3.x)) / det;
460  const double l3 = 1.0 - l1 - l2;
461 
462  return l1 * p1.z + l2 * p2.z + l3 * p3.z;
463 }
464 } // namespace
465 
466 std::optional<float> ElevationMap::getElevationAt(const mrpt::math::TPoint2D& pt) const
467 {
468  // mesh->getxMin();
469  const double x0 = meshMinX_;
470  const double y0 = meshMinY_;
471  const double x1 = meshMaxX_;
472  const double y1 = meshMaxY_;
473 
474  const size_t nCellsX = meshCacheZ_.rows();
475  const size_t nCellsY = meshCacheZ_.cols();
476 
477  const double sCellX = (x1 - x0) / (nCellsX - 1);
478  const double sCellY = (y1 - y0) / (nCellsY - 1);
479 
480  // Discretize:
481  const int cx00 = ::floor((pt.x - x0) / sCellX);
482  const int cy00 = ::floor((pt.y - y0) / sCellY);
483 
484  if (cx00 < 0 || cx00 >= int(nCellsX - 1) || cy00 < 0 || cy00 >= int(nCellsY - 1)) //
485  return {}; // out of bounds!
486 
487  // Linear interpolation:
488  const double z00 = meshCacheZ_(cx00, cy00);
489  const double z01 = meshCacheZ_(cx00, cy00 + 1);
490  const double z10 = meshCacheZ_(cx00 + 1, cy00);
491  const double z11 = meshCacheZ_(cx00 + 1, cy00 + 1);
492 
493  //
494  // p01 ---- p11
495  // | |
496  // p00 ---- p10
497  //
498  const mrpt::math::TPoint3D p00(.0, .0, z00);
499  const mrpt::math::TPoint3D p01(.0, sCellY, z01);
500  const mrpt::math::TPoint3D p10(sCellX, .0, z10);
501  const mrpt::math::TPoint3D p11(sCellX, sCellY, z11);
502 
503  const double lx = pt.x - (x0 + cx00 * sCellX);
504  const double ly = pt.y - (y0 + cy00 * sCellY);
505 
506  if (ly >= lx)
507  return calcz(p00, p01, p11, lx, ly);
508  else
509  return calcz(p00, p10, p11, lx, ly);
510 }
move-object-example.R
int R
Definition: move-object-example.py:41
mvsim::VisualObject::parent
World * parent()
Definition: VisualObject.h:51
mvsim::ElevationMap::meshMaxY_
double meshMaxY_
Definition: ElevationMap.h:57
mvsim
Definition: Client.h:21
mvsim::VisualObject::world_
World * world_
Definition: VisualObject.h:73
mvsim::ElevationMap::meshMinX_
double meshMinX_
Definition: ElevationMap.h:57
mvsim::TParamEntry
Definition: TParameterDefinitions.h:38
mvsim::parse_xmlnode_children_as_param
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:215
mvsim::ElevationMap::loadConfigFrom
virtual void loadConfigFrom(const rapidxml::xml_node< char > *root) override
Definition: ElevationMap.cpp:83
World.h
mvsim::ElevationMap::meshCacheZ_
mrpt::math::CMatrixDouble meshCacheZ_
Definition: ElevationMap.h:56
mvsim::ElevationMap::textureExtensionX_
double textureExtensionX_
0=auto
Definition: ElevationMap.h:52
xml_utils.h
f
f
simple-obstacle-avoidance.dest
dest
Definition: simple-obstacle-avoidance.py:35
VehicleBase.h
mvsim::World::local_to_abs_path
std::string local_to_abs_path(const std::string &in_path) const
Definition: World.cpp:106
rapidxml
Definition: rapidxml.hpp:57
mvsim::TSimulContext
Definition: basic_types.h:58
mvsim::ElevationMap::~ElevationMap
virtual ~ElevationMap()
Definition: ElevationMap.cpp:82
mvsim::World::xmlPathToActualPath
std::string xmlPathToActualPath(const std::string &modelURI) const
Definition: World.cpp:98
mvsim::ElevationMap::firstSceneRendering_
bool firstSceneRendering_
Definition: ElevationMap.h:49
mvsim::TParameterDefinitions
std::map< std::string, TParamEntry > TParameterDefinitions
Definition: TParameterDefinitions.h:64
mvsim::Simulable::simul_post_timestep
virtual void simul_post_timestep(const TSimulContext &context)
Definition: Simulable.cpp:64
mvsim::ElevationMap::gl_meshes_
std::vector< mrpt::opengl::CMesh::Ptr > gl_meshes_
Definition: ElevationMap.h:48
mvsim::trim
std::string trim(const std::string &s)
Definition: parse_utils.cpp:251
mvsim::World
Definition: World.h:82
mvsim::ElevationMap::internalGuiUpdate
virtual void internalGuiUpdate(const mrpt::optional_ref< mrpt::opengl::COpenGLScene > &viz, const mrpt::optional_ref< mrpt::opengl::COpenGLScene > &physical, bool childrenOnly) override
Definition: ElevationMap.cpp:408
mvsim::ElevationMap::meshMinY_
double meshMinY_
Definition: ElevationMap.h:57
rapidxml::xml_node< char >
mvsim::World::user_defined_variables
const std::map< std::string, std::string > & user_defined_variables() const
Definition: World.h:390
mvsim::World::worldRenderOffsetPropose
void worldRenderOffsetPropose(const mrpt::math::TVector3D &v)
(See docs for worldRenderOffset_)
Definition: World.h:648
std
mvsim::ElevationMap::model_split_size_
double model_split_size_
Definition: ElevationMap.h:61
mvsim::ElevationMap::getElevationAt
std::optional< float > getElevationAt(const mrpt::math::TPoint2D &pt) const override
Definition: ElevationMap.cpp:466
mvsim::ElevationMap::textureExtensionY_
double textureExtensionY_
0=auto
Definition: ElevationMap.h:53
mvsim::ElevationMap::simul_pre_timestep
virtual void simul_pre_timestep(const TSimulContext &context) override
Definition: ElevationMap.cpp:432
root
root
ElevationMap.h
rapidxml.hpp
mvsim::ElevationMap::meshMaxX_
double meshMaxX_
Definition: ElevationMap.h:57
mvsim::ElevationMap::simul_post_timestep
virtual void simul_post_timestep(const TSimulContext &context) override
Definition: ElevationMap.cpp:439
mvsim::ElevationMap::resolution_
double resolution_
Definition: ElevationMap.h:50
mvsim::WorldElementBase
Definition: WorldElementBase.h:27


mvsim
Author(s):
autogenerated on Wed May 28 2025 02:13:07