iMSTK
Interactive Medical Simulation Toolkit
imstkScene.cpp
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #include "imstkScene.h"
8 #include "imstkCamera.h"
9 #include "imstkDeviceControl.h"
10 #include "imstkCameraController.h"
11 #include "imstkCollisionDetectionAlgorithm.h"
12 #include "imstkFeDeformableObject.h"
13 #include "imstkFemDeformableBodyModel.h"
14 #include "imstkLight.h"
15 #include "imstkLogger.h"
16 #include "imstkParallelUtils.h"
17 
18 #include "imstkSequentialTaskGraphController.h"
19 #include "imstkTaskGraph.h"
20 #include "imstkTaskGraphVizWriter.h"
21 #include "imstkTbbTaskGraphController.h"
22 #include "imstkTimer.h"
23 #include "imstkTrackingDeviceControl.h"
24 #include "imstkVisualModel.h"
25 #include "imstkAbstractDynamicalModel.h"
26 
27 namespace imstk
28 {
29 Scene::Scene(const std::string& name, std::shared_ptr<SceneConfig> config) :
30  m_config(config),
31  m_name(name),
32  m_activeCamera(nullptr),
33  m_taskGraph(std::make_shared<TaskGraph>("Scene_" + name + "_Source", "Scene_" + name + "_Sink")),
34  m_computeTimesLock(std::make_shared<ParallelUtils::SpinLock>())
35 {
36  auto defaultCam = std::make_shared<Camera>();
37  defaultCam->setPosition(0.0, 2.0, -15.0);
38  defaultCam->setFocalPoint(0.0, 0.0, 0.0);
39 
40  auto debugCam = std::make_shared<Camera>();
41  debugCam->setPosition(0.0, 4.0, -30.0);
42  debugCam->setFocalPoint(0.0, 0.0, 0.0);
43 
44  m_cameras["default"] = defaultCam;
45  m_cameras["debug"] = debugCam;
46  setActiveCamera("default");
47 }
48 
49 bool
51 {
52  // Gather all the systems from the object components
53  // Right now this just includes DynamicalModel's
54  std::unordered_set<std::shared_ptr<AbstractDynamicalModel>> systems;
55  for (const auto& ent : m_sceneEntities)
56  {
57  if (auto dynObj = std::dynamic_pointer_cast<DynamicObject>(ent))
58  {
59  systems.insert(dynObj->getDynamicalModel());
60  }
61  }
62 
63  // Initialize all the SceneObjects
64  for (const auto& ent : m_sceneEntities)
65  {
66  if (auto obj = std::dynamic_pointer_cast<SceneObject>(ent))
67  {
68  CHECK(obj->initialize()) << "Error initializing scene object: " << obj->getName();
69 
70  // Print any controls
71  if (auto deviceObj = std::dynamic_pointer_cast<DeviceControl>(obj))
72  {
73  deviceObj->printControls();
74  }
75  }
76  }
77 
78  // Initialize all components
79  // If any components are added during initialize, initialize those
80  // as well, do this until all are initialized
81  std::unordered_map<std::shared_ptr<Component>, bool> compIsInitd;
82  for (const auto& ent : m_sceneEntities)
83  {
84  std::vector<std::shared_ptr<Component>> compsToInit = ent->getComponents();
85  while (compsToInit.size() > 0)
86  {
87  // Initialize all components and denote which are now complete
88  for (const auto& comp : compsToInit)
89  {
90  comp->initialize();
91  compIsInitd[comp] = true;
92  }
93 
94  // Rnu through all the components again, if any were added (found not init'd)
95  // then add them to the compsToInit
96  std::vector<std::shared_ptr<Component>> newComps = ent->getComponents();
97  compsToInit.clear();
98  for (const auto& comp : newComps)
99  {
100  if (!compIsInitd[comp])
101  {
102  compsToInit.push_back(comp);
103  }
104  }
105  }
106  }
107 
108  // Initialize all systems
109  for (const auto& system : systems)
110  {
111  CHECK(system->initialize()) << "Error initializing system";
112  }
113 
114  // Build the compute graph
115  buildTaskGraph();
116 
117  // Opportunity for user configuration
118  this->postEvent(Event(Scene::configureTaskGraph()));
119 
120  // Initialize the task graph
121  initTaskGraph();
122 
123  // Init the debug camera to the bounding box of the visual geometries
124  if (m_config->debugCamBoundingBox)
125  {
126  Vec3d globalMin, globalMax;
127  Scene::computeBoundingBox(globalMin, globalMax);
128 
129  const Vec3d center = (globalMin + globalMax) * 0.5;
130  const double size = (globalMax - globalMin).norm();
131  m_cameras["debug"]->setFocalPoint(center);
132  m_cameras["debug"]->setPosition(center + Vec3d(0.0, 1.0, 1.0).normalized() * size);
133  }
134 
135  m_sceneTime = 0.0;
136 
137  LOG(INFO) << "Scene '" << this->getName() << "' initialized!";
138  return true;
139 }
140 
141 void
142 Scene::computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent)
143 {
144  if (this->getSceneObjects().size() == 0)
145  {
146  lowerCorner = Vec3d(0., 0., 0.);
147  upperCorner = Vec3d(0., 0., 0.);
148  return;
149  }
150 
151  lowerCorner = Vec3d(IMSTK_DOUBLE_MAX, IMSTK_DOUBLE_MAX, IMSTK_DOUBLE_MAX);
152  upperCorner = Vec3d(IMSTK_DOUBLE_MIN, IMSTK_DOUBLE_MIN, IMSTK_DOUBLE_MIN);
153 
154  for (const auto& ent : m_sceneEntities)
155  {
156  for (const auto& visualModel : ent->getComponents<VisualModel>())
157  {
158  Vec3d min = Vec3d(IMSTK_DOUBLE_MAX, IMSTK_DOUBLE_MAX, IMSTK_DOUBLE_MAX);
159  Vec3d max = Vec3d(IMSTK_DOUBLE_MIN, IMSTK_DOUBLE_MIN, IMSTK_DOUBLE_MIN);
160  std::shared_ptr<Geometry> geom = visualModel->getGeometry();
161  if (geom != nullptr)
162  {
163  geom->computeBoundingBox(min, max);
164  lowerCorner = lowerCorner.cwiseMin(min);
165  upperCorner = upperCorner.cwiseMax(max);
166  }
167  }
168  }
169  const Vec3d range = upperCorner - lowerCorner;
170  lowerCorner = lowerCorner - range * (paddingPercent / 100.0);
171  upperCorner = upperCorner + range * (paddingPercent / 100.0);
172 }
173 
174 void
176 {
177  // Configures the edges/flow of the graph
178 
179  // Clear the compute graph of all nodes/edges except source+sink
180  m_taskGraph->clear();
181  m_taskGraph->addEdge(m_taskGraph->getSource(), m_taskGraph->getSink());
182 
183  // Setup all SceneObject & their components compute graphs
184  // \todo: Remove SceneObject TaskGraphs
185  for (const auto& ent : m_sceneEntities)
186  {
187  if (auto obj = std::dynamic_pointer_cast<SceneObject>(ent))
188  {
189  obj->initGraphEdges();
190  }
191  for (const auto& comp : ent->getComponents())
192  {
193  auto behaviour = std::dynamic_pointer_cast<SceneBehaviour>(comp);
194  if (behaviour != nullptr && behaviour->getTaskGraph() != nullptr)
195  {
196  behaviour->initTaskGraphEdges();
197  }
198  }
199  }
200 
201  // Nest all the SceneObject graphs & TaskBehaviour graphs within this Scene's ComputeGraph
202  // \todo: Remove SceneObject TaskGraphs
203  for (const auto& ent : m_sceneEntities)
204  {
205  if (auto obj = std::dynamic_pointer_cast<SceneObject>(ent))
206  {
207  std::shared_ptr<TaskGraph> taskGraph = obj->getTaskGraph();
208  if (taskGraph != nullptr)
209  {
210  // Remove any unused nodes
211  taskGraph = TaskGraph::removeUnusedNodes(taskGraph);
212  // Sum and nest the graph
213  m_taskGraph->nestGraph(taskGraph, m_taskGraph->getSource(), m_taskGraph->getSink());
214  }
215  }
216  for (const auto& comp : ent->getComponents())
217  {
218  if (auto taskBehaviour = std::dynamic_pointer_cast<SceneBehaviour>(comp))
219  {
220  std::shared_ptr<TaskGraph> taskGraph = taskBehaviour->getTaskGraph();
221  if (taskGraph != nullptr)
222  {
223  // Remove any unused nodes
224  taskGraph = TaskGraph::removeUnusedNodes(taskGraph);
225  // Sum and nest the graph
226  m_taskGraph->nestGraph(taskGraph, m_taskGraph->getSource(), m_taskGraph->getSink());
227  }
228  }
229  }
230  }
231 
232  // Remove any possible unused nodes
233  m_taskGraph = TaskGraph::removeUnusedNodes(m_taskGraph);
234 }
235 
236 void
238 {
239  m_taskGraphController = std::make_shared<SequentialTaskGraphController>();
240 
242  {
243  if (m_config->writeTaskGraph)
244  {
245  TaskGraphVizWriter writer;
246  writer.setInput(m_taskGraph);
247  writer.setFileName("sceneTaskGraph.svg");
248  writer.write();
249  }
250  LOG(FATAL) << "Scene TaskGraph is cyclic, cannot proceed";
251  return;
252  }
253  // Clean up graph if user wants
254  if (m_config->graphReductionEnabled)
255  {
257  }
258 
259  // If user wants to benchmark, tell all the nodes to time themselves
260  for (std::shared_ptr<TaskNode> node : m_taskGraph->getNodes())
261  {
262  node->m_enableTiming = m_config->taskTimingEnabled;
263  }
264 
265  // Generate unique names among the nodes
267  m_nodeComputeTimes.clear();
268 
269  if (m_config->writeTaskGraph)
270  {
271  TaskGraphVizWriter writer;
272  writer.setInput(m_taskGraph);
273  writer.setFileName("sceneTaskGraph.svg");
274  writer.write();
275  }
276 
277  m_taskGraphController->setTaskGraph(m_taskGraph);
278  m_taskGraphController->initialize();
279 }
280 
281 void
282 Scene::setEnableTaskTiming(const bool enabled)
283 {
284  m_config->taskTimingEnabled = enabled;
285  // If user wants to benchmark, tell all the nodes to time themselves
286  for (std::shared_ptr<TaskNode> node : m_taskGraph->getNodes())
287  {
288  node->m_enableTiming = m_config->taskTimingEnabled;
289  }
290 }
291 
292 void
293 Scene::addInteraction(std::shared_ptr<Entity> interaction)
294 {
295  addSceneObject(interaction);
296 }
297 
298 std::shared_ptr<Entity>
299 Scene::getSceneObject(const std::string& name) const
300 {
301  auto iter = std::find_if(m_sceneEntities.begin(), m_sceneEntities.end(),
302  [name](const std::shared_ptr<Entity>& i) { return i->getName() == name; });
303  return (iter == m_sceneEntities.end()) ? nullptr : *iter;
304 }
305 
306 void
307 Scene::addSceneObject(std::shared_ptr<Entity> entity)
308 {
309  // If already exists, exit
310  if (m_sceneEntities.find(entity) != m_sceneEntities.end())
311  {
312  LOG(WARNING) << "Entity " << entity->getName() << " already in the scene, not added";
313  return;
314  }
315 
316  // Ensure the name is unique
317  const std::string orgName = entity->getName();
318  const std::string uniqueName = getUniqueName(orgName);
319  if (orgName != uniqueName)
320  {
321  LOG(INFO) << "Entity with name " << orgName << " already in scene. Renamed to " << uniqueName;
322  entity->setName(uniqueName);
323  }
324 
325  m_sceneEntities.insert(entity);
326  this->postEvent(Event(modified()));
327  LOG(INFO) << uniqueName << " entity added to " << m_name << " scene";
328 }
329 
330 void
331 Scene::removeSceneObject(const std::string& name)
332 {
333  std::shared_ptr<Entity> ent = getSceneObject(name);
334  if (ent == nullptr)
335  {
336  LOG(WARNING) << "No entity named '" << name
337  << "' was registered in this scene.";
338  return;
339  }
340  removeSceneObject(ent);
341 }
342 
343 void
344 Scene::removeSceneObject(std::shared_ptr<Entity> entity)
345 {
346  if (m_sceneEntities.count(entity) != 0)
347  {
348  m_sceneEntities.erase(entity);
349  this->postEvent(Event(modified()));
350  LOG(INFO) << entity->getName() << " object removed from scene " << m_name;
351  }
352  else
353  {
354  LOG(WARNING) << "Could not remove Entity '" << entity->getName() << "', does not exist in the scene";
355  return;
356  }
357 }
358 
359 const std::vector<std::shared_ptr<Light>>
361 {
362  std::vector<std::shared_ptr<Light>> v;
363 
364  for (auto it = m_lightsMap.begin();
365  it != m_lightsMap.end();
366  ++it)
367  {
368  v.push_back(it->second);
369  }
370 
371  return v;
372 }
373 
374 std::shared_ptr<Light>
375 Scene::getLight(const std::string& lightName) const
376 {
377  if ((m_lightsMap.find(lightName) == m_lightsMap.end()))
378  {
379  LOG(WARNING) << "No light named '" << lightName
380  << "' was registered in this scene.";
381  return nullptr;
382  }
383 
384  return m_lightsMap.at(lightName);
385 }
386 
387 void
388 Scene::addLight(const std::string& name, std::shared_ptr<Light> newLight)
389 {
390  if (m_lightsMap.find(name) != m_lightsMap.cend())
391  {
392  LOG(WARNING) << "Cannot add light: '" << name
393  << "' is already registered in this scene.";
394  return;
395  }
396 
397  m_lightsMap[name] = newLight;
398  this->postEvent(Event(modified()));
399  LOG(INFO) << name << " light added to " << m_name;
400 }
401 
402 void
403 Scene::removeLight(const std::string& lightName)
404 {
405  if (this->getLight(lightName) == nullptr)
406  {
407  LOG(WARNING) << "No light named '" << lightName
408  << "' was registered in this scene.";
409  return;
410  }
411 
412  m_lightsMap.erase(lightName);
413  LOG(INFO) << lightName << " light removed from " << m_name;
414 }
415 
416 std::string
417 Scene::getUniqueName(const std::string& name) const
418 {
419  int i = 1;
420  std::string uniqueName = name;
421  // While name is not unique, iterate it
422  while (getSceneObject(uniqueName) != nullptr)
423  {
424  uniqueName = name + "_" + std::to_string(i);
425  i++;
426  }
427  return uniqueName;
428 }
429 
430 std::string
431 Scene::getCameraName(const std::shared_ptr<Camera> cam) const
432 {
433  using MapType = std::unordered_map<std::string, std::shared_ptr<Camera>>;
434  auto i = std::find_if(m_cameras.begin(), m_cameras.end(),
435  [&cam](const MapType::value_type& j) { return j.second == cam; });
436  if (i != m_cameras.end())
437  {
438  return i->first;
439  }
440  else
441  {
442  return "";
443  }
444 }
445 
446 std::shared_ptr<imstk::Camera>
447 Scene::getCamera(const std::string name) const
448 {
449  auto i = m_cameras.find(name);
450  if (i != m_cameras.end())
451  {
452  return i->second;
453  }
454  else
455  {
456  return nullptr;
457  }
458 }
459 
460 void
461 Scene::addCamera(const std::string& name, std::shared_ptr<Camera> cam)
462 {
463  if (m_cameras.find(name) != m_cameras.end())
464  {
465  LOG(WARNING) << "Cannot add camera: Camera with the name " << name << " already exists.";
466  }
467  m_cameras[name] = cam;
468 }
469 
470 void
471 Scene::setActiveCamera(const std::string name)
472 {
473  auto i = m_cameras.find(name);
474  if (i != m_cameras.end())
475  {
476  m_activeCamera = m_cameras[name];
477  }
478 }
479 
480 void
481 Scene::removeCamera(const std::string name)
482 {
483  auto i = m_cameras.find(name);
484  if (i != m_cameras.end() && !(name == "default" || name == "debug"))
485  {
486  m_cameras.erase(name);
487  LOG(INFO) << name << " camera removed from " << m_name;
488  }
489  else
490  {
491  LOG(WARNING) << "No camera named '" << name
492  << "' is part of the scene.";
493  }
494 }
495 
496 void
497 Scene::addControl(std::shared_ptr<DeviceControl> control)
498 {
499  auto obj = std::make_shared<SceneObject>();
500  obj->addComponent(control);
501  addSceneObject(obj);
502  this->postEvent(Event(modified()));
503 }
504 
505 void
507 {
508  m_resetRequested = true;
509 }
510 
511 void
513 {
514  // Apply the geometry and apply maps to all the objects
515  for (const auto& ent : m_sceneEntities)
516  {
517  if (auto obj = std::dynamic_pointer_cast<SceneObject>(ent))
518  {
519  obj->reset();
520  }
521  }
522 }
523 
524 void
525 Scene::advance(const double dt)
526 {
527  StopWatch wwt;
528  wwt.start();
529 
530  for (auto obj : this->getSceneObjects())
531  {
532  if (auto dynaObj = std::dynamic_pointer_cast<DynamicObject>(obj))
533  {
534  if (dynaObj->getDynamicalModel()->getTimeStepSizeType() == TimeSteppingType::RealTime)
535  {
536  dynaObj->getDynamicalModel()->setTimeStep(dt);
537  }
538  }
539  }
540 
541  // Reset Contact forces to 0
542  for (auto obj : this->getSceneObjects())
543  {
544  if (auto defObj = std::dynamic_pointer_cast<FeDeformableObject>(obj))
545  {
546  defObj->getFEMModel()->getContactForce().setConstant(0.0);
547  }
548  }
549 
550  // Process all behaviours before updating the scene.
551  // This includes controls such as haptics, keyboard, mouse, etc.
552  for (auto obj : this->getSceneObjects())
553  {
554  // SceneObject update for supporting old imstk
555  if (auto sceneObj = std::dynamic_pointer_cast<SceneObject>(obj))
556  {
557  sceneObj->update();
558  }
559  for (auto comp : obj->getComponents())
560  {
561  if (auto behaviour = std::dynamic_pointer_cast<SceneBehaviour>(comp))
562  {
563  behaviour->update(dt);
564  }
565  }
566  }
567 
568  // Execute the computational graph
569  if (m_taskGraphController != nullptr)
570  {
571  m_taskGraphController->execute();
572  }
573 
574  m_sceneTime += dt;
575  if (m_resetRequested)
576  {
577  resetSceneObjects();
578  m_sceneTime = 0.0;
579  m_resetRequested = false;
580  }
581 
582  // FPS of physics is given by the measured time, not the given time step dt
583  const double elapsedTime = wwt.getTimeElapsed(StopWatch::TimeUnitType::seconds);
584  m_fps = 1.0 / elapsedTime;
585  m_frameTimes.pushBack(elapsedTime);
586 
587  // If benchmarking enabled, produce a time table for each step
588  if (m_config->taskTimingEnabled)
589  {
590  lockComputeTimes();
591  for (std::shared_ptr<TaskNode> node : m_taskGraph->getNodes())
592  {
593  m_nodeComputeTimes[node->m_name] = node->m_computeTime;
594  }
595  unlockComputeTimes();
596  }
597 }
598 
599 void
600 Scene::updateVisuals(const double dt)
601 {
602  for (const auto& ent : m_sceneEntities)
603  {
604  if (auto obj = std::dynamic_pointer_cast<SceneObject>(ent))
605  {
606  obj->visualUpdate();
607  }
608  for (auto comp : ent->getComponents())
609  {
610  if (auto behaviour = std::dynamic_pointer_cast<SceneBehaviour>(comp))
611  {
612  behaviour->visualUpdate(dt);
613  }
614  }
615  }
616 }
617 
618 void
620 {
621  m_computeTimesLock->lock();
622 }
623 
624 void
625 Scene::unlockComputeTimes()
626 {
627  m_computeTimesLock->unlock();
628 }
629 } // namespace imstk
void setActiveCamera(const std::string name)
Switch the active camera to the one requested by name. If the requested on doesn&#39;t exist...
Definition: imstkScene.cpp:471
void initTaskGraph()
Initializes the graph after its in a built state.
Definition: imstkScene.cpp:237
void computeBoundingBox(Vec3d &lowerCorner, Vec3d &upperCorner, const double paddingPercent=0.0)
Compute the bounding box of the scene as an union of bounding boxes of its objects.
Definition: imstkScene.cpp:142
Base class for events which contain a type, priority, and data priority defaults to 0 and uses a grea...
virtual bool initialize()
Initialize the scene.
Definition: imstkScene.cpp:50
static bool isCyclic(std::shared_ptr< TaskGraph > graph)
Returns if Graph is cyclic or not.
Writes a TaskGraph to an svg file. Produces unique node names from duplicates with postfix...
const std::vector< std::shared_ptr< Light > > getLights() const
Return a vector of lights in the scene.
Definition: imstkScene.cpp:360
virtual void start()
Start the appropriate timer.
Definition: imstkTimer.cpp:29
void lockComputeTimes()
Lock/Unlock the compute times resource.
Definition: imstkScene.cpp:619
Compound Geometry.
void setEnableTaskTiming(const bool enabled)
If true, tasks will be time and a table produced every scene advance of the times.
Definition: imstkScene.cpp:282
static std::shared_ptr< TaskGraph > reduce(std::shared_ptr< TaskGraph > graph)
Simplifies the graph in a way that retains functionality Performs transitive reduction then redundant...
std::string getCameraName(const std::shared_ptr< Camera > cam) const
Get the name of the camera given the object (if it exists)
Definition: imstkScene.cpp:431
void write()
Writes the graph to a file given the filename.
void setFileName(std::string fileName)
The filename and path to write too.
A Behaviour represents a single component system A template is used here for UpdateInfo to keep the C...
void addCamera(const std::string &name, std::shared_ptr< Camera > cam)
Set the camera for the scene.
Definition: imstkScene.cpp:461
virtual void updateVisuals(const double dt)
Update visuals of all scene objects.
Definition: imstkScene.cpp:600
void addControl(std::shared_ptr< DeviceControl > control)
Adds a device control to a newly created SceneObject.
Definition: imstkScene.cpp:497
void reset()
Async reset the scene, will reset next update.
Definition: imstkScene.cpp:506
const std::string & getName() const
Get/Set the name of the entity.
Definition: imstkEntity.h:47
static TaskNodeNameMap getUniqueNodeNames(std::shared_ptr< TaskGraph > graph, bool apply=false)
Nodes may not have unique names. Iterates the names with numeric postfix to generate uniques...
void removeLight(const std::string &lightName)
Remove light with a given name from the scene.
Definition: imstkScene.cpp:403
void buildTaskGraph()
Setup the task graph, this completely rebuilds the graph.
Definition: imstkScene.cpp:175
std::shared_ptr< Entity > getSceneObject(const std::string &name) const
Get SceneObject by name, returns nullptr if doesn&#39;t exist.
Definition: imstkScene.cpp:299
void initTaskGraphEdges()
Setup the edges/connections of the TaskGraph.
std::string getUniqueName(const std::string &name) const
Given a desired name, produce a unique one. This name would be iterated with a postfix # should one a...
Definition: imstkScene.cpp:417
void addSceneObject(std::shared_ptr< Entity > entity)
Add a scene object.
Definition: imstkScene.cpp:307
Stop Watch utility class.
Definition: imstkTimer.h:19
void setInput(std::shared_ptr< TaskGraph > graph)
The graph to write.
void addLight(const std::string &name, std::shared_ptr< Light > newLight)
Add light from the scene.
Definition: imstkScene.cpp:388
void removeSceneObject(const std::string &name)
Remove scene object by name.
Definition: imstkScene.cpp:331
void removeCamera(const std::string name)
Remove the camera with a given name.
Definition: imstkScene.cpp:481
void postEvent(const T &e)
Emits the event Direct observers will be immediately called, in sync Queued observers will receive th...
void addInteraction(std::shared_ptr< Entity > interaction)
Add an interaction.
Definition: imstkScene.cpp:293
std::shared_ptr< TaskGraph > m_taskGraph
Computational Graph.
virtual double getTimeElapsed(const TimeUnitType unitType=TimeUnitType::milliSeconds)
Returns the time elapsed since calling start.
Definition: imstkTimer.cpp:136
std::string m_name
Not unique name.
Definition: imstkEntity.h:157
void resetSceneObjects()
Sync reset, resets immediately.
Definition: imstkScene.cpp:512
std::shared_ptr< Light > getLight(const std::string &lightName) const
Get a light with a given name.
Definition: imstkScene.cpp:375
virtual void advance(const double dt)
Advance the scene from current to next frame with specified timestep.
Definition: imstkScene.cpp:525
std::shared_ptr< Camera > getCamera(const std::string name) const
Get camera object given the name.
Definition: imstkScene.cpp:447
Contains geometric, material, and render information.