Panda3D
nonlinearImager.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file nonlinearImager.cxx
10  * @author drose
11  * @date 2001-12-12
12  */
13 
14 #include "nonlinearImager.h"
15 #include "config_distort.h"
16 
17 #include "graphicsStateGuardian.h"
18 #include "matrixLens.h"
19 #include "graphicsOutput.h"
20 #include "graphicsEngine.h"
21 #include "dcast.h"
22 #include "cPointerCallbackObject.h"
23 #include "asyncTaskManager.h"
24 #include "genericAsyncTask.h"
25 
26 /**
27  *
28  */
29 NonlinearImager::
30 NonlinearImager() {
31  _engine = nullptr;
32  _stale = true;
33 }
34 
35 /**
36  *
37  */
38 NonlinearImager::
39 ~NonlinearImager() {
42 
43  if (_recompute_task != nullptr) {
45  task_mgr->remove(_recompute_task);
46  }
47 }
48 
49 /**
50  * This version of this method is deprecated and will soon be removed. Use
51  * the version that takes two parameters instead.
52  */
55  return add_screen(NodePath(screen), screen->get_name());
56 }
57 
58 /**
59  * Adds a new ProjectionScreen to the list of screens that will be processed
60  * by the NonlinearImager. Each ProjectionScreen represents a view into the
61  * world. It must be based on a linear camera (or whatever kind of camera is
62  * respected by the graphics engine).
63  *
64  * Each ProjectionScreen object should already have some screen geometry
65  * created.
66  *
67  * As each frame is rendered, an offscreen image will be rendered from the
68  * source camera associated with each ProjectionScreen, and the resulting
69  * image will be applied to the screen geometry.
70  *
71  * The return value is the index number of the new screen.
72  */
74 add_screen(const NodePath &screen, const std::string &name) {
75  nassertr(!screen.is_empty() &&
76  screen.node()->is_of_type(ProjectionScreen::get_class_type()), -1);
77 
78  ProjectionScreen *screen_node = DCAST(ProjectionScreen, screen.node());
79 
80  _screens.push_back(Screen());
81  Screen &new_screen = _screens.back();
82  new_screen._screen = screen;
83  new_screen._screen_node = screen_node;
84  new_screen._name = name;
85  new_screen._buffer = nullptr;
86  new_screen._tex_width = 256;
87  new_screen._tex_height = 256;
88  new_screen._active = true;
89 
90  // Slot a mesh for each viewer.
91  size_t vi;
92  for (vi = 0; vi < _viewers.size(); ++vi) {
93  new_screen._meshes.push_back(Mesh());
94  new_screen._meshes[vi]._last_screen = screen_node->get_last_screen();
95  }
96 
97  _stale = true;
98 
99  if (_dark_room.is_empty()) {
100  _dark_room = screen.get_top();
101  } else {
102  nassertr(_dark_room.is_same_graph(screen), _screens.size() - 1);
103  }
104 
105  return _screens.size() - 1;
106 }
107 
108 /**
109  * Returns the index number of the first appearance of the indicated screen
110  * within the imager's list, or -1 if it does not appear.
111  */
113 find_screen(const NodePath &screen) const {
114  for (size_t i = 0; i < _screens.size(); i++) {
115  if (_screens[i]._screen == screen) {
116  return i;
117  }
118  }
119 
120  return -1;
121 }
122 
123 /**
124  * Removes the screen with the indicated index number from the imager.
125  */
127 remove_screen(int index) {
128  nassertv_always(index >= 0 && index < (int)_screens.size());
129  Screen &screen = _screens[index];
130  for (size_t vi = 0; vi < screen._meshes.size(); vi++) {
131  screen._meshes[vi]._mesh.remove_node();
132  }
133  _screens.erase(_screens.begin() + index);
134 }
135 
136 /**
137  * Removes all screens from the imager.
138  */
141  while (!_screens.empty()) {
142  remove_screen(_screens.size() - 1);
143  }
144 }
145 
146 /**
147  * Returns the number of screens that have been added to the imager.
148  */
149 int NonlinearImager::
150 get_num_screens() const {
151  return _screens.size();
152 }
153 
154 /**
155  * Returns the nth screen that has been added to the imager.
156  */
158 get_screen(int index) const {
159  nassertr(index >= 0 && index < (int)_screens.size(), NodePath());
160  return _screens[index]._screen;
161 }
162 
163 /**
164  * Returns the offscreen buffer that is automatically created for the nth
165  * projection screen. This may return NULL if the screen is inactive or if it
166  * has not been rendered yet.
167  */
169 get_buffer(int index) const {
170  nassertr(index >= 0 && index < (int)_screens.size(), nullptr);
171  return _screens[index]._buffer;
172 }
173 
174 /**
175  * Sets the width and height of the texture used to render the scene for the
176  * indicated screen. This must be less than or equal to the window size, and
177  * it should be a power of two.
178  *
179  * In general, the larger the texture, the greater the detail of the rendered
180  * scene.
181  */
183 set_texture_size(int index, int width, int height) {
184  nassertv(index >= 0 && index < (int)_screens.size());
185 
186  Screen &screen = _screens[index];
187 
188  screen._tex_width = width;
189  screen._tex_height = height;
190 
191  if (screen._buffer != nullptr) {
192  bool removed = _engine->remove_window(screen._buffer);
193  screen._buffer = nullptr;
194  nassertv(removed);
195  }
196 
197  _stale = true;
198 }
199 
200 /**
201  * Specifies the camera that will be used to render the image for this
202  * particular screen.
203  *
204  * The parameter must be a NodePath whose node is a Camera. The camera itself
205  * indicates the scene that is to be rendered.
206  */
208 set_source_camera(int index, const NodePath &source_camera) {
209  nassertv(index >= 0 && index < (int)_screens.size());
210  nassertv(!source_camera.is_empty() &&
211  source_camera.node()->is_of_type(Camera::get_class_type()));
212  _screens[index]._source_camera = source_camera;
213 }
214 
215 /**
216  * Sets the active flag on the indicated screen. If the active flag is true,
217  * the screen will be used; otherwise, it will not appear.
218  */
220 set_screen_active(int index, bool active) {
221  nassertv(index >= 0 && index < (int)_screens.size());
222 
223  Screen &screen = _screens[index];
224  screen._active = active;
225 
226  if (!active) {
227  // If we've just made this screen inactive, remove its meshes.
228  for (size_t vi = 0; vi < screen._meshes.size(); vi++) {
229  screen._meshes[vi]._mesh.remove_node();
230  }
231 
232  // Also remove its buffer.
233  if (screen._buffer != nullptr) {
234  bool removed = _engine->remove_window(screen._buffer);
235  screen._buffer = nullptr;
236  nassertv(removed);
237  }
238 
239  // Hide the screen in the dark room. This doesn't really matter, since
240  // the dark room isn't normally rendered, but hide it anyway in case the
241  // user stuck a camera in there for fun.
242  screen._screen.hide();
243 
244  } else {
245  // If we've just made it active, it needs to be recomputed.
246  _stale = true;
247 
248  screen._screen.show();
249  }
250 }
251 
252 /**
253  * Returns the active flag on the indicated screen.
254  */
256 get_screen_active(int index) const {
257  nassertr(index >= 0 && index < (int)_screens.size(), false);
258  return _screens[index]._active;
259 }
260 
261 
262 /**
263  * Adds the indicated DisplayRegion as a viewer into the NonlinearImager room.
264  * The camera associated with the DisplayRegion at the time add_viewer() is
265  * called is used as the initial viewer camera; it may have a nonlinear lens,
266  * like a fisheye or cylindrical lens.
267  *
268  * This sets up a special scene graph for this DisplayRegion alone and sets up
269  * the DisplayRegion with a specialty camera. If future changes to the camera
270  * are desired, you should use the set_viewer_camera() interface.
271  *
272  * All viewers must share the same GraphicsEngine.
273  *
274  * The return value is the index of the new viewer.
275  */
278  GraphicsOutput *window = dr->get_window();
279  nassertr(window != nullptr, -1);
280 
281  GraphicsStateGuardian *gsg = window->get_gsg();
282  nassertr(gsg != nullptr, -1);
283 
284  GraphicsEngine *engine = gsg->get_engine();
285  nassertr(engine != nullptr, -1);
286 
287  nassertr(_viewers.empty() || (engine == _engine), -1);
288  if (_engine == nullptr) {
289  _engine = engine;
290  }
291 
292  if (_recompute_task == nullptr) {
293  _recompute_task =
294  new GenericAsyncTask("nli_recompute", recompute_callback, (void *)this);
296  task_mgr->add(_recompute_task);
297  }
298 
299  int previous_vi = find_viewer(dr);
300  if (previous_vi >= 0) {
301  return previous_vi;
302  }
303 
304  size_t vi = _viewers.size();
305  _viewers.push_back(Viewer());
306  Viewer &viewer = _viewers[vi];
307 
308  viewer._dr = dr;
309 
310  // Get the current camera off of the DisplayRegion, if any.
311  viewer._viewer = dr->get_camera();
312  if (viewer._viewer.is_empty()) {
313  viewer._viewer_node = nullptr;
314  } else {
315  viewer._viewer_node = DCAST(LensNode, viewer._viewer.node());
316  }
317 
318  // The internal camera is an identity-matrix camera that simply views the
319  // meshes that represent the user's specified camera.
320  viewer._internal_camera = new Camera("internal_camera");
321  viewer._internal_camera->set_lens(new MatrixLens);
322  viewer._internal_scene = NodePath("internal_screens");
323  viewer._internal_camera->set_scene(viewer._internal_scene);
324 
325  NodePath camera_np = viewer._internal_scene.attach_new_node(viewer._internal_camera);
326  viewer._dr->set_camera(camera_np);
327 
328  // Enable face culling on the wireframe mesh. This will help us to cull out
329  // invalid polygons that result from vertices crossing a singularity (for
330  // instance, at the back of a fisheye lens).
331  viewer._internal_scene.set_two_sided(0);
332 
333  // Finally, slot a new mesh for each screen.
334  Screens::iterator si;
335  for (si = _screens.begin(); si != _screens.end(); ++si) {
336  Screen &screen = (*si);
337  screen._meshes.push_back(Mesh());
338  nassertr(screen._meshes.size() == _viewers.size(), -1);
339  }
340 
341  _stale = true;
342 
343  if (_dark_room.is_empty()) {
344  _dark_room = viewer._viewer.get_top();
345  } else {
346  nassertr(_dark_room.is_same_graph(viewer._viewer), vi);
347  }
348 
349  return vi;
350 }
351 
352 /**
353  * Returns the index number of the indicated DisplayRegion within the list of
354  * viewers, or -1 if it is not found.
355  */
358  for (size_t vi = 0; vi < _viewers.size(); vi++) {
359  if (_viewers[vi]._dr == dr) {
360  return vi;
361  }
362  }
363 
364  return -1;
365 }
366 
367 /**
368  * Removes the viewer with the indicated index number from the imager.
369  */
371 remove_viewer(int index) {
372  nassertv_always(index >= 0 && index < (int)_viewers.size());
373  Viewer &viewer = _viewers[index];
374  viewer._internal_camera->set_scene(NodePath());
375  viewer._dr->set_camera(viewer._viewer);
376 
377  // Also remove the corresponding mesh from each screen.
378  Screens::iterator si;
379  for (si = _screens.begin(); si != _screens.end(); ++si) {
380  Screen &screen = (*si);
381  nassertv(index < (int)screen._meshes.size());
382  screen._meshes[index]._mesh.remove_node();
383  screen._meshes.erase(screen._meshes.begin() + index);
384  }
385 
386  _viewers.erase(_viewers.begin() + index);
387 }
388 
389 /**
390  * Removes all viewers from the imager.
391  */
394  while (!_viewers.empty()) {
395  remove_viewer(_viewers.size() - 1);
396  }
397 }
398 
399 /**
400  * Specifies the LensNode that is to serve as the viewer for this screen. The
401  * relative position of the LensNode to the NonlinearImager, as well as the
402  * properties of the lens associated with the LensNode, determines the UV's
403  * that will be assigned to the geometry within the NonlinearImager.
404  *
405  * It is not necessary to call this except to change the camera after a viewer
406  * has been added, since the default is to use whatever camera is associated
407  * with the DisplayRegion at the time the viewer is added.
408  *
409  * The NodePath must refer to a LensNode (or a Camera).
410  */
412 set_viewer_camera(int index, const NodePath &viewer_camera) {
413  nassertv(index >= 0 && index < (int)_viewers.size());
414  nassertv(!viewer_camera.is_empty() &&
415  viewer_camera.node()->is_of_type(LensNode::get_class_type()));
416  Viewer &viewer = _viewers[index];
417  viewer._viewer = viewer_camera;
418  viewer._viewer_node = DCAST(LensNode, viewer_camera.node());
419  _stale = true;
420 
421  if (_dark_room.is_empty()) {
422  _dark_room = viewer._viewer.get_top();
423  } else {
424  nassertv(_dark_room.is_same_graph(viewer._viewer));
425  }
426 }
427 
428 /**
429  * Returns the NodePath to the LensNode that is to serve as nth viewer for
430  * this screen.
431  */
433 get_viewer_camera(int index) const {
434  nassertr(index >= 0 && index < (int)_viewers.size(), NodePath());
435  return _viewers[index]._viewer;
436 }
437 
438 /**
439  * Returns a pointer to the root node of the internal scene graph for the nth
440  * viewer, which is used to render all of the screen meshes for this viewer.
441  *
442  * This is the scene graph in which the screen meshes within the dark room
443  * have been flattened into the appropriate transformation according to the
444  * viewer's lens properties (and position relative to the screens). It is
445  * this scene graph that is finally rendered to the window.
446  */
448 get_viewer_scene(int index) const {
449  nassertr(index >= 0 && index < (int)_viewers.size(), NodePath());
450  return _viewers[index]._internal_scene;
451 }
452 
453 /**
454  * Returns the number of viewers that have been added to the imager.
455  */
456 int NonlinearImager::
457 get_num_viewers() const {
458  return _viewers.size();
459 }
460 
461 /**
462  * Returns the nth viewer's DisplayRegion that has been added to the imager.
463  */
465 get_viewer(int index) const {
466  nassertr(index >= 0 && index < (int)_viewers.size(), nullptr);
467  return _viewers[index]._dr;
468 }
469 
470 /**
471  * Returns the NodePath to the root of the dark room scene. This is the scene
472  * in which all of the ProjectionScreens and the viewer cameras reside. It's
473  * a standalone scene with a few projection screens arranged artfully around
474  * one or more viewers; it's so named because it's a little virtual theater.
475  *
476  * Normally this scene is not rendered directly; it only exists as an abstract
477  * concept, and to define the relation between the ProjectionScreens and the
478  * viewers. But it may be rendered to help visualize the NonlinearImager's
479  * behavior.
480  */
482 get_dark_room() const {
483  return _dark_room;
484 }
485 
486 /**
487  * Returns the GraphicsEngine that all of the viewers added to the
488  * NonlinearImager have in common.
489  */
492  return _engine;
493 }
494 
495 /**
496  * Forces a regeneration of all the mesh objects, etc.
497  */
500  size_t vi;
501  for (vi = 0; vi < _viewers.size(); ++vi) {
502  Viewer &viewer = _viewers[vi];
503 
504  Screens::iterator si;
505  for (si = _screens.begin(); si != _screens.end(); ++si) {
506  Screen &screen = (*si);
507  if (screen._active) {
508  recompute_screen(screen, vi);
509  }
510  }
511 
512  if (viewer._viewer_node != nullptr &&
513  viewer._viewer_node->get_lens() != nullptr) {
514  viewer._viewer_lens_change =
515  viewer._viewer_node->get_lens()->get_last_change();
516  }
517  }
518 
519  _stale = false;
520 }
521 
522 /**
523  * This function is added as a task, to ensure that all frames are up-to-date.
524  */
525 AsyncTask::DoneStatus NonlinearImager::
527  NonlinearImager *self = (NonlinearImager *)data;
528  self->recompute_if_stale();
529  return AsyncTask::DS_cont;
530 }
531 
532 /**
533  * Calls recompute() if it needs to be called.
534  */
537  if (_stale) {
538  recompute();
539  } else {
540  size_t vi;
541  for (vi = 0; vi < _viewers.size(); ++vi) {
542  Viewer &viewer = _viewers[vi];
543  if (viewer._viewer_node != nullptr) {
544  UpdateSeq lens_change =
545  viewer._viewer_node->get_lens()->get_last_change();
546  if (lens_change != viewer._viewer_lens_change) {
547  // The viewer has changed, so we need to recompute all screens on
548  // this viewer.
549  Screens::iterator si;
550  for (si = _screens.begin(); si != _screens.end(); ++si) {
551  Screen &screen = (*si);
552  if (screen._active) {
553  recompute_screen(screen, vi);
554  }
555  }
556 
557  } else {
558  // We may not need to recompute all screens, but maybe some of them.
559  Screens::iterator si;
560  for (si = _screens.begin(); si != _screens.end(); ++si) {
561  Screen &screen = (*si);
562  if (screen._active &&
563  screen._meshes[vi]._last_screen != screen._screen_node->get_last_screen()) {
564  recompute_screen(screen, vi);
565  } else {
566  screen._screen_node->recompute_if_stale(screen._screen);
567  }
568  }
569  }
570  }
571  }
572  }
573 }
574 
575 /**
576  * Regenerates the mesh objects just for the indicated screen.
577  */
578 void NonlinearImager::
579 recompute_screen(NonlinearImager::Screen &screen, size_t vi) {
580  nassertv(vi < screen._meshes.size());
581  screen._meshes[vi]._mesh.remove_node();
582  if (!screen._active) {
583  return;
584  }
585 
586  screen._screen_node->recompute_if_stale(screen._screen);
587 
588  Viewer &viewer = _viewers[vi];
589  PT(PandaNode) mesh =
590  screen._screen_node->make_flat_mesh(screen._screen, viewer._viewer);
591  if (mesh != nullptr) {
592  screen._meshes[vi]._mesh = viewer._internal_scene.attach_new_node(mesh);
593  }
594 
595  if (screen._buffer == nullptr) {
596  GraphicsOutput *win = viewer._dr->get_window();
597  GraphicsOutput *buffer = win->make_texture_buffer
598  (screen._name, screen._tex_width, screen._tex_height, nullptr, false);
599 
600  if (buffer != nullptr) {
601  screen._buffer = buffer;
602  DisplayRegion *dr = buffer->make_display_region();
603  dr->set_camera(screen._source_camera);
604 
605  } else {
606  screen._meshes[vi]._mesh.clear_texture();
607  }
608  }
609 
610  if (screen._buffer != nullptr) {
611  screen._meshes[vi]._mesh.set_texture(screen._buffer->get_texture());
612 
613  // We don't really need to set the texture on the dark room screen, since
614  // that's normally not rendered, but we do anyway just for debugging
615  // purposes (in case the user does try to render it, to see what's going
616  // on).
617  screen._screen.set_texture(screen._buffer->get_texture());
618  }
619 
620  screen._meshes[vi]._last_screen = screen._screen_node->get_last_screen();
621 }
void set_texture_size(int index, int width, int height)
Sets the width and height of the texture used to render the scene for the indicated screen.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A basic node of the scene graph or data graph.
Definition: pandaNode.h:64
get_screen
Returns the nth screen that has been added to the imager.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool remove(AsyncTask *task)
Removes the indicated task from the active queue.
bool get_screen_active(int index) const
Returns the active flag on the indicated screen.
A completely generic linear lens.
Definition: matrixLens.h:28
get_viewer
Returns the nth viewer's DisplayRegion that has been added to the imager.
void recompute()
Forces a regeneration of all the mesh objects, etc.
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188
A class to manage a loose queue of isolated tasks, which can be performed either synchronously (in th...
void remove_all_screens()
Removes all screens from the imager.
A node that contains a Lens.
Definition: lensNode.h:29
void remove_all_viewers()
Removes all viewers from the imager.
int find_screen(const NodePath &screen) const
Returns the index number of the first appearance of the indicated screen within the imager's list,...
static AsyncTask::DoneStatus recompute_callback(GenericAsyncTask *task, void *data)
This function is added as a task, to ensure that all frames are up-to-date.
void recompute_if_stale()
Calls recompute() if it needs to be called.
void set_viewer_camera(int index, const NodePath &viewer_camera)
Specifies the LensNode that is to serve as the viewer for this screen.
NodePath get_viewer_camera(int index) const
Returns the NodePath to the LensNode that is to serve as nth viewer for this screen.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_screen_active(int index, bool active)
Sets the active flag on the indicated screen.
DisplayRegion * make_display_region()
Creates a new DisplayRegion that covers the entire window.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GraphicsEngine * get_engine() const
Returns the graphics engine that created this GSG.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class object combines the rendered output of a 3-d from one or more linear (e....
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void add(AsyncTask *task)
Adds the indicated task to the active queue.
NodePath attach_new_node(PandaNode *node, int sort=0, Thread *current_thread=Thread::get_current_thread()) const
Attaches a new node, with or without existing parents, to the scene graph below the referenced node o...
Definition: nodePath.cxx:563
int add_viewer(DisplayRegion *dr)
Adds the indicated DisplayRegion as a viewer into the NonlinearImager room.
get_camera
Returns the camera associated with this DisplayRegion, or an empty NodePath if no camera is associate...
Definition: displayRegion.h:94
int add_screen(ProjectionScreen *screen)
This version of this method is deprecated and will soon be removed.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Associates a generic C-style function pointer with an AsyncTask object.
A ProjectionScreen implements a simple system for projective texturing.
int find_viewer(DisplayRegion *dr) const
Returns the index number of the indicated DisplayRegion within the list of viewers,...
NodePath get_viewer_scene(int index) const
Returns a pointer to the root node of the internal scene graph for the nth viewer,...
This is a base class for the various different classes that represent the result of a frame of render...
NodePath get_dark_room() const
Returns the NodePath to the root of the dark room scene.
set_camera
Sets the camera that is associated with this DisplayRegion.
Definition: displayRegion.h:94
bool is_same_graph(const NodePath &other, Thread *current_thread=Thread::get_current_thread()) const
Returns true if the node represented by this NodePath is parented within the same graph as that of th...
Definition: nodePath.I:275
void remove_screen(int index)
Removes the screen with the indicated index number from the imager.
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
get_window
Returns the GraphicsOutput that this DisplayRegion is ultimately associated with, or NULL if no windo...
Definition: displayRegion.h:88
Encapsulates all the communication with a particular instance of a given rendering backend.
void remove_viewer(int index)
Removes the viewer with the indicated index number from the imager.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
NodePath get_top(Thread *current_thread=Thread::get_current_thread()) const
Returns a singleton NodePath that represents the top of the path, or empty NodePath if this path is e...
Definition: nodePath.cxx:209
get_buffer
Returns the offscreen buffer that is automatically created for the nth projection screen.
A rectangular subregion within a window for rendering into.
Definition: displayRegion.h:57
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
This class is the main interface to controlling the render process.
void set_source_camera(int index, const NodePath &source_camera)
Specifies the camera that will be used to render the image for this particular screen.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a sequence number that increments monotonically.
Definition: updateSeq.h:37
A node that can be positioned around in the scene graph to represent a point of view for rendering a ...
Definition: camera.h:35
static AsyncTaskManager * get_global_ptr()
Returns a pointer to the global AsyncTaskManager.
void set_two_sided(bool two_sided, int priority=0)
Specifically sets or disables two-sided rendering mode on this particular node.
Definition: nodePath.cxx:4430
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_gsg
Returns the GSG that is associated with this window.
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:161
GraphicsEngine * get_graphics_engine() const
Returns the GraphicsEngine that all of the viewers added to the NonlinearImager have in common.