VisualObject.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/opengl/COpenGLScene.h>
11 #include <mrpt/opengl/CPolyhedron.h>
12 #include <mrpt/opengl/CSetOfObjects.h>
13 #include <mrpt/version.h>
14 #include <mvsim/Block.h>
16 #include <mvsim/Simulable.h>
17 #include <mvsim/VisualObject.h>
18 #include <mvsim/World.h>
19 
20 #include <atomic>
21 #include <rapidxml.hpp>
22 
23 #include "JointXMLnode.h"
24 #include "ModelsCache.h"
25 #include "xml_utils.h"
26 
27 using namespace mvsim;
28 
29 static std::atomic_int32_t g_uniqueCustomVisualId = 0;
30 double VisualObject::GeometryEpsilon = 1e-3;
31 
33  World* parent, bool insertCustomVizIntoViz, bool insertCustomVizIntoPhysical)
34  : world_(parent),
35  insertCustomVizIntoViz_(insertCustomVizIntoViz),
36  insertCustomVizIntoPhysical_(insertCustomVizIntoPhysical)
37 {
38  glCollision_ = mrpt::opengl::CSetOfObjects::Create();
39  glCollision_->setName("bbox");
40 }
41 
42 VisualObject::~VisualObject() = default;
43 
45  const mrpt::optional_ref<mrpt::opengl::COpenGLScene>& viz,
46  const mrpt::optional_ref<mrpt::opengl::COpenGLScene>& physical)
47 {
48  using namespace std::string_literals;
49 
50  const auto* meSim = dynamic_cast<Simulable*>(this);
51  ASSERT_(meSim);
52 
53  // If "viz" does not have a value, it's because we are already inside a
54  // setPose() change event, so my caller already holds the mutex and we don't
55  // need/can't acquire it again:
56  const auto objectPoseOrg = viz.has_value() ? meSim->getPose() : meSim->getPoseNoLock();
57  const auto objectPose = parent()->applyWorldRenderOffset(objectPoseOrg);
58 
59  if (glCustomVisual_ && viz.has_value() && physical.has_value())
60  {
61  // Assign a unique ID on first call:
62  if (glCustomVisualId_ < 0)
63  {
64  // Assign a unique name, so we can localize the object in the scene
65  // if needed.
67  const auto name = "_autoViz"s + std::to_string(glCustomVisualId_);
68  glCustomVisual_->setName(name);
69 
70  // Add to the 3D scene:
71  if (insertCustomVizIntoViz_) viz->get().insert(glCustomVisual_);
72 
73  if (insertCustomVizIntoPhysical_) physical->get().insert(glCustomVisual_);
74  }
75 
76  // Update pose:
77  glCustomVisual_->setPose(objectPose);
78  }
79 
80  if (glCollision_ && viz.has_value())
81  {
82  if (glCollision_->empty() && collisionShape_)
83  {
84  const auto& cs = collisionShape_.value();
85 
86  const double height = cs.zMax() - cs.zMin();
87  ASSERT_(height == height);
88  ASSERT_(height > 0);
89 
90  const auto c = cs.getContour();
91 
92  // Adapt mrpt::math geometry epsilon to the scale of the smallest
93  // edge in this polygon, so we don't get false positives about
94  // wrong aligned points in a 3D face just becuase it's too small:
95  const auto savedMrptGeomEps = mrpt::math::getEpsilon();
96 
97  double smallestEdge = std::abs(height);
98  for (size_t i = 0; i < c.size(); i++)
99  {
100  size_t im1 = i == 0 ? c.size() - 1 : i - 1;
101  const auto Ap = c[i] - c[im1];
102  mrpt::keep_min(smallestEdge, Ap.norm());
103  }
104  mrpt::math::setEpsilon(1e-5 * smallestEdge);
105 
106  mrpt::opengl::CPolyhedron::Ptr glCS;
107 
108  try
109  {
110  glCS = mrpt::opengl::CPolyhedron::CreateCustomPrism(c, height);
111  }
112  catch (const std::exception& e)
113  {
114 #if 0
115  std::cerr << "[mvsim::VisualObject] **WARNING**: Ignoring the "
116  "following error while building the visualization "
117  "of the collision shape for object named '"
118  << meSim->getName()
119  << "' placed by pose=" << meSim->getPose()
120  << "). Falling back to rectangular collision shape "
121  "from bounding box:\n"
122  << e.what() << std::endl;
123 #endif
124 
125  mrpt::math::TPoint2D bbMax, bbMin;
126  cs.getContour().getBoundingBox(bbMin, bbMax);
127  mrpt::math::TPolygon2D p;
128  p.emplace_back(bbMin.x, bbMin.y);
129  p.emplace_back(bbMin.x, bbMax.y);
130  p.emplace_back(bbMax.x, bbMax.y);
131  p.emplace_back(bbMax.x, bbMin.y);
132  glCS = mrpt::opengl::CPolyhedron::CreateCustomPrism(p, height);
133  }
134  glCS->setWireframe(true);
135 
136  mrpt::math::setEpsilon(savedMrptGeomEps);
137  // Default epsilon is restored now
138 
139  glCS->setLocation(0, 0, cs.zMin());
140 
141  glCollision_->insert(glCS);
142  glCollision_->setVisibility(false);
143  viz->get().insert(glCollision_);
144  }
145  glCollision_->setPose(objectPose);
146  }
147 
148  const bool childrenOnly = !!glCustomVisual_;
149 
150  internalGuiUpdate(viz, physical, childrenOnly);
151 }
152 
154 
156 {
157  MRPT_TRY_START
158 
159  bool any = false;
160  for (auto n = rootNode.first_node("visual"); n; n = n->next_sibling("visual"))
161  {
162  bool hasViz = implParseVisual(*n);
163  any = any || hasViz;
164  }
165  return any;
166 
167  MRPT_TRY_END
168 }
169 
171 {
172  MRPT_TRY_START
173 
174  bool any = false;
175  for (const auto& n : rootNode.getListOfNodes())
176  {
177  bool hasViz = parseVisual(*n);
178  any = any || hasViz;
179  }
180 
181  return any;
182  MRPT_TRY_END
183 }
184 
186 {
187  MRPT_TRY_START
188 
189  {
190  bool visualEnabled = true;
191  TParameterDefinitions auxPar;
192  auxPar["enabled"] = TParamEntry("%bool", &visualEnabled);
193  parse_xmlnode_attribs(visNode, auxPar);
194  if (!visualEnabled)
195  {
196  // "enabled=false" -> Ignore the rest of the contents
197  return false;
198  }
199  }
200 
201  std::string modelURI;
202  double modelScale = 1.0;
203  mrpt::math::TPose3D modelPose;
204  bool initialShowBoundingBox = false;
205  std::string objectName = "group";
206 
208 
209  TParameterDefinitions params;
210  params["model_uri"] = TParamEntry("%s", &modelURI);
211  params["model_scale"] = TParamEntry("%lf", &modelScale);
212  params["model_offset_x"] = TParamEntry("%lf", &modelPose.x);
213  params["model_offset_y"] = TParamEntry("%lf", &modelPose.y);
214  params["model_offset_z"] = TParamEntry("%lf", &modelPose.z);
215  params["model_yaw"] = TParamEntry("%lf_deg", &modelPose.yaw);
216  params["model_pitch"] = TParamEntry("%lf_deg", &modelPose.pitch);
217  params["model_roll"] = TParamEntry("%lf_deg", &modelPose.roll);
218  params["show_bounding_box"] = TParamEntry("%bool", &initialShowBoundingBox);
219  params["model_cull_faces"] = TParamEntry("%s", &opts.modelCull);
220  params["model_color"] = TParamEntry("%color", &opts.modelColor);
221  params["model_split_size"] = TParamEntry("%f", &opts.splitSize);
222  params["name"] = TParamEntry("%s", &objectName);
223 
224  // Parse XML params:
225  parse_xmlnode_children_as_param(visNode, params);
226 
227  if (modelURI.empty()) return false;
228 
229  const std::string localFileName = world_->xmlPathToActualPath(modelURI);
230 
231  auto& gModelsCache = ModelsCache::Instance();
232 
233  auto glModel = gModelsCache.get(localFileName, opts);
234 
235  // Add the 3D model as custom viz:
237  glModel, mrpt::poses::CPose3D(modelPose), modelScale, objectName, modelURI);
238 
239  return true; // yes, we have a custom viz model
240 
241  MRPT_TRY_END
242 }
243 
245 {
246  if (!glCollision_) return;
247  glCollision_->setVisibility(show);
248 }
249 
250 void VisualObject::customVisualVisible(const bool visible)
251 {
252  if (!glCustomVisual_) return;
253 
254  glCustomVisual_->setVisibility(visible);
255 }
256 
258 {
259  return glCustomVisual_ && glCustomVisual_->isVisible();
260 }
261 
263  const mrpt::opengl::CRenderizable::Ptr& glModel, const mrpt::poses::CPose3D& modelPose,
264  const float modelScale, const std::string& modelName,
265  const std::optional<std::string>& modelURI, const bool initialShowBoundingBox)
266 {
267  ASSERT_(glModel);
268 
269  auto& chc = CollisionShapeCache::Instance();
270 
271  float zMin = -std::numeric_limits<float>::max();
272  float zMax = std::numeric_limits<float>::max();
273 
274  if (const Block* block = dynamic_cast<const Block*>(this);
275  block && !block->default_block_z_min_max())
276  {
277  zMin = block->block_z_min() - GeometryEpsilon;
278  zMax = block->block_z_max() + GeometryEpsilon;
279  }
280 #if 0
281  std::cout << "MODEL: " << (modelURI ? *modelURI : "none")
282  << " glModel: " << glModel->GetRuntimeClass()->className
283  << " modelScale: " << modelScale << " zmin=" << zMin
284  << " zMax:" << zMax << "\n";
285 #endif
286 
287  // Calculate its convex hull:
288  const auto shape = chc.get(*glModel, zMin, zMax, modelPose, modelScale, modelURI);
289 
290  auto glGroup = mrpt::opengl::CSetOfObjects::Create();
291 
292  // Note: we cannot apply pose/scale to the original glModel since
293  // it may be shared (many instances of the same object):
294  glGroup->insert(glModel);
295  glGroup->setScale(modelScale);
296  glGroup->setPose(modelPose);
297 
298  glGroup->setName(modelName);
299 
300  if (!glCustomVisual_)
301  {
302  glCustomVisual_ = mrpt::opengl::CSetOfObjects::Create();
303  glCustomVisual_->setName("glCustomVisual");
304  }
305  glCustomVisual_->insert(glGroup);
306 
307  if (glCollision_) glCollision_->setVisibility(initialShowBoundingBox);
308 
309  // Auto bounds from visual model bounding-box:
310  if (!collisionShape_)
311  {
312  // Copy:
313  collisionShape_ = shape;
314  }
315  else
316  {
317  // ... or update collision volume:
318  collisionShape_->mergeWith(shape);
319  }
320 }
mvsim::VisualObject::parent
World * parent()
Definition: VisualObject.h:51
Block.h
mvsim
Definition: Client.h:21
mvsim::VisualObject::insertCustomVizIntoPhysical_
const bool insertCustomVizIntoPhysical_
Definition: VisualObject.h:82
mvsim::VisualObject::world_
World * world_
Definition: VisualObject.h:73
mvsim::VisualObject::glCustomVisualId_
int32_t glCustomVisualId_
Definition: VisualObject.h:79
mvsim::JointXMLnode
Definition: basic_types.h:53
mvsim::ModelsCache::Options::modelColor
mrpt::img::TColor modelColor
Definition: ModelsCache.h:23
mvsim::TParamEntry
Definition: TParameterDefinitions.h:38
JointXMLnode.h
s
XmlRpcServer s
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
g_uniqueCustomVisualId
static std::atomic_int32_t g_uniqueCustomVisualId
Definition: VisualObject.cpp:29
mvsim::VisualObject::FreeOpenGLResources
static void FreeOpenGLResources()
Definition: VisualObject.cpp:153
mvsim::World::applyWorldRenderOffset
mrpt::math::TPose3D applyWorldRenderOffset(mrpt::math::TPose3D p) const
Definition: World.h:631
World.h
mvsim::VisualObject::collisionShape_
std::optional< Shape2p5 > collisionShape_
Definition: VisualObject.h:98
mvsim::VisualObject::addCustomVisualization
void addCustomVisualization(const mrpt::opengl::CRenderizable::Ptr &glModel, const mrpt::poses::CPose3D &modelPose={}, const float modelScale=1.0f, const std::string &modelName="group", const std::optional< std::string > &modelURI=std::nullopt, const bool initialShowBoundingBox=false)
Definition: VisualObject.cpp:262
mvsim::VisualObject::implParseVisual
bool implParseVisual(const rapidxml::xml_node< char > &visual_node)
Called by parseVisual once per "visual" block.
Definition: VisualObject.cpp:185
xml_utils.h
mvsim::VisualObject::insertCustomVizIntoViz_
const bool insertCustomVizIntoViz_
Definition: VisualObject.h:81
mvsim::ModelsCache::clear
void clear()
Definition: ModelsCache.h:35
mvsim::CollisionShapeCache::Instance
static CollisionShapeCache & Instance()
Definition: CollisionShapeCache.cpp:20
mvsim::VisualObject::guiUpdate
virtual void guiUpdate(const mrpt::optional_ref< mrpt::opengl::COpenGLScene > &viz, const mrpt::optional_ref< mrpt::opengl::COpenGLScene > &physical)
Definition: VisualObject.cpp:44
mvsim::VisualObject::VisualObject
VisualObject(World *parent, bool insertCustomVizIntoViz=true, bool insertCustomVizIntoPhysical=true)
Definition: VisualObject.cpp:32
Simulable.h
mvsim::VisualObject::showCollisionShape
void showCollisionShape(bool show)
Definition: VisualObject.cpp:244
rapidxml::xml_node::first_node
xml_node< Ch > * first_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
Definition: rapidxml.hpp:936
mvsim::World::xmlPathToActualPath
std::string xmlPathToActualPath(const std::string &modelURI) const
Definition: World.cpp:98
mvsim::Block::default_block_z_min_max
bool default_block_z_min_max() const
Definition: Block.cpp:591
mvsim::TParameterDefinitions
std::map< std::string, TParamEntry > TParameterDefinitions
Definition: TParameterDefinitions.h:64
mvsim::VisualObject::GeometryEpsilon
static double GeometryEpsilon
Definition: VisualObject.h:66
mvsim::Block
Definition: Block.h:50
mvsim::World
Definition: World.h:82
mvsim::ModelsCache::Options
Definition: ModelsCache.h:21
ModelsCache.h
rapidxml::xml_node< char >
mvsim::VisualObject::~VisualObject
virtual ~VisualObject()
mvsim::ModelsCache::Options::modelCull
std::string modelCull
Definition: ModelsCache.h:24
mvsim::Simulable
Definition: Simulable.h:39
mvsim::ModelsCache::Instance
static ModelsCache & Instance()
Definition: ModelsCache.cpp:21
mvsim::VisualObject::glCollision_
std::shared_ptr< mrpt::opengl::CSetOfObjects > glCollision_
Definition: VisualObject.h:78
mvsim::VisualObject::parseVisual
bool parseVisual(const rapidxml::xml_node< char > &rootNode)
Returns true if there is at least one <visual>...</visual> entry.
Definition: VisualObject.cpp:155
mvsim::VisualObject::internalGuiUpdate
virtual void internalGuiUpdate(const mrpt::optional_ref< mrpt::opengl::COpenGLScene > &viz, const mrpt::optional_ref< mrpt::opengl::COpenGLScene > &physical, bool childrenOnly=false)=0
mvsim::VisualObject::glCustomVisual_
std::shared_ptr< mrpt::opengl::CSetOfObjects > glCustomVisual_
Definition: VisualObject.h:77
VisualObject.h
rapidxml.hpp
mvsim::parse_xmlnode_attribs
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:182
mvsim::ModelsCache::Options::splitSize
float splitSize
Definition: ModelsCache.h:30
CollisionShapeCache.h
mvsim::JointXMLnode::getListOfNodes
TListNodes & getListOfNodes()
Definition: JointXMLnode.h:41
mvsim::VisualObject::customVisualVisible
bool customVisualVisible() const
Definition: VisualObject.cpp:257
rapidxml::xml_node::next_sibling
xml_node< Ch > * next_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
Definition: rapidxml.hpp:1004


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