diff --git a/panda/src/distort/nonlinearImager.I b/panda/src/distort/nonlinearImager.I index 48f5e575a6..7a8d3ba0b1 100644 --- a/panda/src/distort/nonlinearImager.I +++ b/panda/src/distort/nonlinearImager.I @@ -16,27 +16,3 @@ // //////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////// -// Function: NonlinearImager::get_viewer -// Access: Published -// Description: Returns the NodePath to the LensNode that is to serve -// as the viewer for this screen, or empty if no -// viewer is associated. -//////////////////////////////////////////////////////////////////// -INLINE const NodePath &NonlinearImager:: -get_viewer() const { - return _viewer; -} - -//////////////////////////////////////////////////////////////////// -// Function: NonlinearImager::get_internal_scene -// Access: Published -// Description: Returns a pointer to the root node of the internal -// scene graph, which is used to render all of the -// screen meshes. -//////////////////////////////////////////////////////////////////// -INLINE NodePath NonlinearImager:: -get_internal_scene() const { - return _internal_scene; -} diff --git a/panda/src/distort/nonlinearImager.cxx b/panda/src/distort/nonlinearImager.cxx index 603ebd79a5..b4f50d31e3 100644 --- a/panda/src/distort/nonlinearImager.cxx +++ b/panda/src/distort/nonlinearImager.cxx @@ -28,31 +28,11 @@ //////////////////////////////////////////////////////////////////// // Function: NonlinearImager::Constructor // Access: Published -// Description: The NonlinearImager is associated with a particular -// DisplayRegion when it is created. It will throw away -// whatever camera is currently associated with the -// DisplayRegion, and create a speciality camera for -// itself. +// Description: //////////////////////////////////////////////////////////////////// NonlinearImager:: -NonlinearImager(DisplayRegion *dr) { - _dr = dr; - - // The internal camera is an identity-matrix camera that simply - // views the meshes that represent the user's specified camera. - _internal_camera = new Camera("NonlinearImager"); - _internal_camera->set_lens(new MatrixLens); - _internal_scene = NodePath("screens"); - _internal_camera->set_scene(_internal_scene); - - NodePath camera_np = _internal_scene.attach_new_node(_internal_camera); - _dr->set_camera(camera_np); - - // Enable face culling on the wireframe mesh. This will help us to - // cull out invalid polygons that result from vertices crossing a - // singularity (for instance, at the back of a fisheye lens). - _internal_scene.set_two_sided(0); - +NonlinearImager() { + _gsg = (GraphicsStateGuardian *)NULL; _stale = true; } @@ -63,9 +43,8 @@ NonlinearImager(DisplayRegion *dr) { //////////////////////////////////////////////////////////////////// NonlinearImager:: ~NonlinearImager() { - _internal_camera->set_scene(NodePath()); - _dr->set_camera(NodePath()); remove_all_screens(); + remove_all_viewers(); } //////////////////////////////////////////////////////////////////// @@ -79,7 +58,7 @@ NonlinearImager:: // // width and height indicate the size of the texture // that will be created to render the scene for the -// screen. See set_size(). +// screen. See set_texture_size(). // // Each ProjectionScreen object should already have some // screen geometry created. @@ -101,9 +80,15 @@ add_screen(ProjectionScreen *screen) { new_screen._texture = (Texture *)NULL; new_screen._tex_width = 256; new_screen._tex_height = 256; - new_screen._last_screen = screen->get_last_screen(); new_screen._active = true; + // Slot a mesh for each viewer. + size_t vi; + for (vi = 0; vi < _viewers.size(); ++vi) { + new_screen._meshes.push_back(Mesh()); + new_screen._meshes[vi]._last_screen = screen->get_last_screen(); + } + _stale = true; return _screens.size() - 1; } @@ -136,7 +121,9 @@ void NonlinearImager:: remove_screen(int index) { nassertv_always(index >= 0 && index < (int)_screens.size()); Screen &screen = _screens[index]; - screen._mesh.remove_node(); + for (size_t vi = 0; vi < screen._meshes.size(); vi++) { + screen._meshes[vi]._mesh.remove_node(); + } _screens.erase(_screens.begin() + index); } @@ -147,13 +134,9 @@ remove_screen(int index) { //////////////////////////////////////////////////////////////////// void NonlinearImager:: remove_all_screens() { - Screens::iterator si; - for (si = _screens.begin(); si != _screens.end(); ++si) { - Screen &screen = (*si); - screen._mesh.remove_node(); + while (!_screens.empty()) { + remove_screen(_screens.size() - 1); } - - _screens.clear(); } //////////////////////////////////////////////////////////////////// @@ -180,7 +163,7 @@ get_screen(int index) const { } //////////////////////////////////////////////////////////////////// -// Function: NonlinearImager::set_size +// Function: NonlinearImager::set_texture_size // Access: Published // Description: Sets the width and height of the texture used to // render the scene for the indicated screen. This must @@ -191,7 +174,7 @@ get_screen(int index) const { // detail of the rendered scene. //////////////////////////////////////////////////////////////////// void NonlinearImager:: -set_size(int index, int width, int height) { +set_texture_size(int index, int width, int height) { nassertv(index >= 0 && index < (int)_screens.size()); _screens[index]._tex_width = width; _screens[index]._tex_height = height; @@ -216,21 +199,23 @@ set_source_camera(int index, const NodePath &source_camera) { } //////////////////////////////////////////////////////////////////// -// Function: NonlinearImager::set_active +// Function: NonlinearImager::set_screen_active // Access: Published // Description: Sets the active flag on the indicated screen. If the // active flag is true, the screen will be used; // otherwise, it will not appear. //////////////////////////////////////////////////////////////////// void NonlinearImager:: -set_active(int index, bool active) { +set_screen_active(int index, bool active) { nassertv(index >= 0 && index < (int)_screens.size()); _screens[index]._active = active; if (!active) { Screen &screen = _screens[index]; - // If we've just made this screen inactive, remove its mesh. - screen._mesh.remove_node(); + // If we've just made this screen inactive, remove its meshes. + for (size_t vi = 0; vi < screen._meshes.size(); vi++) { + screen._meshes[vi]._mesh.remove_node(); + } screen._texture.clear(); } else { // If we've just made it active, it needs to be recomputed. @@ -239,18 +224,147 @@ set_active(int index, bool active) { } //////////////////////////////////////////////////////////////////// -// Function: NonlinearImager::get_active +// Function: NonlinearImager::get_screen_active // Access: Published // Description: Returns the active flag on the indicated screen. //////////////////////////////////////////////////////////////////// bool NonlinearImager:: -get_active(int index) const { +get_screen_active(int index) const { nassertr(index >= 0 && index < (int)_screens.size(), false); return _screens[index]._active; } + //////////////////////////////////////////////////////////////////// -// Function: NonlinearImager::set_viewer +// Function: NonlinearImager::add_viewer +// Access: Published +// Description: Adds the indicated DisplayRegion as a viewer into the +// NonlinearImager room. The camera associated with the +// DisplayRegion at the time add_viewer() is called is +// used as the initial viewer camera; it may have a +// nonlinear lens, like a fisheye or cylindrical lens. +// +// This sets up a special scene graph for this +// DisplayRegion alone and sets up the DisplayRegion +// with a specialty camera. If future changes to the +// camera are desired, you should use the +// set_viewer_camera() interface. +// +// All viewers must share the same +// GraphicsStateGuardian. +// +// The return value is the index of the new viewer. +//////////////////////////////////////////////////////////////////// +int NonlinearImager:: +add_viewer(DisplayRegion *dr) { + GraphicsWindow *win = dr->get_window(); + GraphicsStateGuardian *gsg = win->get_gsg(); + nassertr(_viewers.empty() || gsg == _gsg, -1); + _gsg = gsg; + + int previous_vi = find_viewer(dr); + if (previous_vi >= 0) { + return previous_vi; + } + + size_t vi = _viewers.size(); + _viewers.push_back(Viewer()); + Viewer &viewer = _viewers[vi]; + + viewer._dr = dr; + + // Get the current camera off of the DisplayRegion, if any. + viewer._viewer = dr->get_camera(); + if (viewer._viewer.is_empty()) { + viewer._viewer_node = (LensNode *)NULL; + } else { + viewer._viewer_node = DCAST(LensNode, viewer._viewer.node()); + } + + // The internal camera is an identity-matrix camera that simply + // views the meshes that represent the user's specified camera. + viewer._internal_camera = new Camera("NonlinearImager"); + viewer._internal_camera->set_lens(new MatrixLens); + viewer._internal_scene = NodePath("screens"); + viewer._internal_camera->set_scene(viewer._internal_scene); + + NodePath camera_np = viewer._internal_scene.attach_new_node(viewer._internal_camera); + viewer._dr->set_camera(camera_np); + + // Enable face culling on the wireframe mesh. This will help us to + // cull out invalid polygons that result from vertices crossing a + // singularity (for instance, at the back of a fisheye lens). + viewer._internal_scene.set_two_sided(0); + + // Finally, slot a new mesh for each screen. + Screens::iterator si; + for (si = _screens.begin(); si != _screens.end(); ++si) { + Screen &screen = (*si); + screen._meshes.push_back(Mesh()); + nassertr(screen._meshes.size() == _viewers.size(), -1); + } + + _stale = true; + return vi; +} + +//////////////////////////////////////////////////////////////////// +// Function: NonlinearImager::find_viewer +// Access: Published +// Description: Returns the index number of the indicated +// DisplayRegion within the list of viewers, or -1 if it +// is not found. +//////////////////////////////////////////////////////////////////// +int NonlinearImager:: +find_viewer(DisplayRegion *dr) const { + for (size_t vi = 0; vi < _viewers.size(); vi++) { + if (_viewers[vi]._dr == dr) { + return vi; + } + } + + return -1; +} + +//////////////////////////////////////////////////////////////////// +// Function: NonlinearImager::remove_viewer +// Access: Published +// Description: Removes the viewer with the indicated index number +// from the imager. +//////////////////////////////////////////////////////////////////// +void NonlinearImager:: +remove_viewer(int index) { + nassertv_always(index >= 0 && index < (int)_viewers.size()); + Viewer &viewer = _viewers[index]; + viewer._internal_camera->set_scene(NodePath()); + viewer._dr->set_camera(viewer._viewer); + + // Also remove the corresponding mesh from each screen. + Screens::iterator si; + for (si = _screens.begin(); si != _screens.end(); ++si) { + Screen &screen = (*si); + nassertv(index < (int)screen._meshes.size()); + screen._meshes[index]._mesh.remove_node(); + screen._meshes.erase(screen._meshes.begin() + index); + } + + _viewers.erase(_viewers.begin() + index); +} + +//////////////////////////////////////////////////////////////////// +// Function: NonlinearImager::remove_all_viewers +// Access: Published +// Description: Removes all viewers from the imager. +//////////////////////////////////////////////////////////////////// +void NonlinearImager:: +remove_all_viewers() { + while (!_viewers.empty()) { + remove_viewer(_viewers.size() - 1); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: NonlinearImager::set_viewer_camera // Access: Published // Description: Specifies the LensNode that is to serve as the // viewer for this screen. The relative position of @@ -259,16 +373,70 @@ get_active(int index) const { // determines the UV's that will be assigned to the // geometry within the NonlinearImager. // +// It is not necessary to call this except to change the +// camera after a viewer has been added, since the +// default is to use whatever camera is associated with +// the DisplayRegion at the time the viewer is added. +// // The NodePath must refer to a LensNode (or a Camera). //////////////////////////////////////////////////////////////////// void NonlinearImager:: -set_viewer(const NodePath &viewer) { - _viewer_node = (LensNode *)NULL; - _viewer = viewer; +set_viewer_camera(int index, const NodePath &viewer_camera) { + nassertv(index >= 0 && index < (int)_viewers.size()); + nassertv(!viewer_camera.is_empty() && + viewer_camera.node()->is_of_type(LensNode::get_class_type())); + Viewer &viewer = _viewers[index]; + viewer._viewer = viewer_camera; + viewer._viewer_node = DCAST(LensNode, viewer_camera.node()); _stale = true; - nassertv(!viewer.is_empty() && - viewer.node()->is_of_type(LensNode::get_class_type())); - _viewer_node = DCAST(LensNode, viewer.node()); +} + +//////////////////////////////////////////////////////////////////// +// Function: NonlinearImager::get_viewer_camera +// Access: Published +// Description: Returns the NodePath to the LensNode that is to serve +// as nth viewer for this screen. +//////////////////////////////////////////////////////////////////// +NodePath NonlinearImager:: +get_viewer_camera(int index) const { + nassertr(index >= 0 && index < (int)_viewers.size(), NodePath()); + return _viewers[index]._viewer; +} + +//////////////////////////////////////////////////////////////////// +// Function: NonlinearImager::get_internal_scene +// Access: Published +// Description: Returns a pointer to the root node of the internal +// scene graph for the nth viewer, which is used to +// render all of the screen meshes for this viewer. +//////////////////////////////////////////////////////////////////// +NodePath NonlinearImager:: +get_internal_scene(int index) const { + nassertr(index >= 0 && index < (int)_viewers.size(), NodePath()); + return _viewers[index]._internal_scene; +} + +//////////////////////////////////////////////////////////////////// +// Function: NonlinearImager::get_num_viewers +// Access: Published +// Description: Returns the number of viewers that have been added to +// the imager. +//////////////////////////////////////////////////////////////////// +int NonlinearImager:: +get_num_viewers() const { + return _viewers.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: NonlinearImager::get_viewer +// Access: Published +// Description: Returns the nth viewer's DisplayRegion that has been +// added to the imager. +//////////////////////////////////////////////////////////////////// +DisplayRegion *NonlinearImager:: +get_viewer(int index) const { + nassertr(index >= 0 && index < (int)_viewers.size(), (DisplayRegion *)NULL); + return _viewers[index]._dr; } //////////////////////////////////////////////////////////////////// @@ -278,17 +446,31 @@ set_viewer(const NodePath &viewer) { //////////////////////////////////////////////////////////////////// void NonlinearImager:: recompute() { + // First, force all the textures to clear. Screens::iterator si; for (si = _screens.begin(); si != _screens.end(); ++si) { - if ((*si)._active) { - recompute_screen(*si); + Screen &screen = (*si); + screen._texture.clear(); + } + + size_t vi; + for (vi = 0; vi < _viewers.size(); ++vi) { + Viewer &viewer = _viewers[vi]; + + for (si = _screens.begin(); si != _screens.end(); ++si) { + Screen &screen = (*si); + if (screen._active) { + recompute_screen(screen, vi); + } + } + + if (viewer._viewer_node != (LensNode *)NULL && + viewer._viewer_node->get_lens() != (Lens *)NULL) { + viewer._viewer_lens_change = + viewer._viewer_node->get_lens()->get_last_change(); } } - if (_viewer_node != (LensNode *)NULL && - _viewer_node->get_lens() != (Lens *)NULL) { - _viewer_lens_change = _viewer_node->get_lens()->get_last_change(); - } _stale = false; } @@ -321,20 +503,37 @@ render(GraphicsEngine *engine) { //////////////////////////////////////////////////////////////////// void NonlinearImager:: recompute_if_stale() { - if (_viewer_node != (LensNode *)NULL && - _viewer_node->get_lens() != (Lens *)NULL) { - UpdateSeq lens_change = _viewer_node->get_lens()->get_last_change(); - if (_stale || lens_change != _viewer_lens_change) { - recompute(); - } else { - // We're not overall stale, but maybe we need to recompute one - // or more of our screens. - Screens::iterator si; - for (si = _screens.begin(); si != _screens.end(); ++si) { - Screen &screen = (*si); - if (screen._active && - screen._last_screen != screen._screen->get_last_screen()) { - recompute_screen(screen); + if (_stale) { + recompute(); + } else { + size_t vi; + for (vi = 0; vi < _viewers.size(); ++vi) { + Viewer &viewer = _viewers[vi]; + if (viewer._viewer_node != (LensNode *)NULL) { + UpdateSeq lens_change = + viewer._viewer_node->get_lens()->get_last_change(); + if (lens_change != viewer._viewer_lens_change) { + // The viewer has changed, so we need to recompute all screens + // on this viewer. + Screens::iterator si; + for (si = _screens.begin(); si != _screens.end(); ++si) { + Screen &screen = (*si); + if (screen._active) { + recompute_screen(screen, vi); + } + } + + } else { + // We may not need to recompute all screens, but maybe some of + // them. + Screens::iterator si; + for (si = _screens.begin(); si != _screens.end(); ++si) { + Screen &screen = (*si); + if (screen._active && + screen._meshes[vi]._last_screen != screen._screen->get_last_screen()) { + recompute_screen(screen, vi); + } + } } } } @@ -348,28 +547,36 @@ recompute_if_stale() { // screen. //////////////////////////////////////////////////////////////////// void NonlinearImager:: -recompute_screen(NonlinearImager::Screen &screen) { - screen._mesh.remove_node(); - screen._texture.clear(); - if (_viewer_node == (LensNode *)NULL || !screen._active) { - // Not much we can do without a viewer. +recompute_screen(NonlinearImager::Screen &screen, size_t vi) { + nassertv(vi < screen._meshes.size()); + screen._meshes[vi]._mesh.remove_node(); + if (!screen._active) { return; } - PT(PandaNode) mesh = screen._screen->make_flat_mesh(_viewer); - screen._mesh = _internal_scene.attach_new_node(mesh); + Viewer &viewer = _viewers[vi]; + PT(PandaNode) mesh = screen._screen->make_flat_mesh(viewer._viewer); + if (mesh != (PandaNode *)NULL) { + screen._meshes[vi]._mesh = viewer._internal_scene.attach_new_node(mesh); + } - PT(Texture) texture = new Texture; - texture->set_minfilter(Texture::FT_linear); - texture->set_magfilter(Texture::FT_linear); - texture->set_wrapu(Texture::WM_clamp); - texture->set_wrapv(Texture::WM_clamp); - texture->_pbuffer->set_xsize(screen._tex_width); - texture->_pbuffer->set_ysize(screen._tex_height); + if (screen._texture == (Texture *)NULL || + screen._texture->_pbuffer == (PixelBuffer *)NULL || + screen._texture->_pbuffer->get_xsize() != screen._tex_width || + screen._texture->_pbuffer->get_ysize() != screen._tex_height) { + PT(Texture) texture = new Texture; + texture->set_minfilter(Texture::FT_linear); + texture->set_magfilter(Texture::FT_linear); + texture->set_wrapu(Texture::WM_clamp); + texture->set_wrapv(Texture::WM_clamp); + texture->_pbuffer->set_xsize(screen._tex_width); + texture->_pbuffer->set_ysize(screen._tex_height); - screen._texture = texture; - screen._mesh.set_texture(texture); - screen._last_screen = screen._screen->get_last_screen(); + screen._texture = texture; + } + + screen._meshes[vi]._mesh.set_texture(screen._texture); + screen._meshes[vi]._last_screen = screen._screen->get_last_screen(); } //////////////////////////////////////////////////////////////////// @@ -387,24 +594,23 @@ render_screen(GraphicsEngine *engine, NonlinearImager::Screen &screen) { return; } - // Got to update this to new scene graph. - GraphicsStateGuardian *gsg = _dr->get_window()->get_gsg(); + nassertv(_gsg != (GraphicsStateGuardian *)NULL); // Make a display region of the proper size and clear it to prepare for // rendering the scene. PT(DisplayRegion) scratch_region = - gsg->get_window()->make_scratch_display_region(screen._tex_width, screen._tex_height); + _gsg->get_window()->make_scratch_display_region(screen._tex_width, screen._tex_height); scratch_region->set_camera(screen._source_camera); - gsg->clear(gsg->get_render_buffer(RenderBuffer::T_back | - RenderBuffer::T_depth), - scratch_region); - engine->render_subframe(gsg, scratch_region); + _gsg->clear(_gsg->get_render_buffer(RenderBuffer::T_back | + RenderBuffer::T_depth), + scratch_region); + engine->render_subframe(_gsg, scratch_region); // Copy the results of the render from the frame buffer into the // screen's texture. - screen._texture->copy(gsg, scratch_region, - gsg->get_render_buffer(RenderBuffer::T_back)); + screen._texture->copy(_gsg, scratch_region, + _gsg->get_render_buffer(RenderBuffer::T_back)); // It might be nice if we didn't throw away the scratch region every // time, which prevents us from preserving cull state from one frame diff --git a/panda/src/distort/nonlinearImager.h b/panda/src/distort/nonlinearImager.h index 66f25e1ac2..4e9e706f94 100644 --- a/panda/src/distort/nonlinearImager.h +++ b/panda/src/distort/nonlinearImager.h @@ -31,12 +31,14 @@ #include "pvector.h" class GraphicsEngine; +class GraphicsStateGuardian; //////////////////////////////////////////////////////////////////// // Class : NonlinearImager // Description : This class object combines the rendered output of a -// 3-d from one or more linear cameras, as seen through -// a single, possibly non-linear camera. +// 3-d from one or more linear (e.g. perspective) +// cameras, as seen through a single, possibly nonlinear +// camera. // // This can be used to generate real-time imagery of a // 3-d scene using a nonlinear camera, for instance a @@ -44,7 +46,7 @@ class GraphicsEngine; // only supports linear cameras. // // -// A NonlinearImager may be visualized as a theater room +// A NonlinearImager may be visualized as a dark room // into which a number of projection screens have been // placed, of arbitrary size and shape and at any // arbitrary position and orientation to each other. @@ -52,14 +54,33 @@ class GraphicsEngine; // seen by a normal perspective camera that exists in // the world (that is, under render). // -// There is also in the theater a single, possibly -// nonlinear, camera that observes these screens. The -// user's window (or DisplayRegion) will display the -// output of this camera. +// There also exists in the theater one or more +// (possibly nonlinear) cameras, called viewers, that +// observe these screens. Each of these viewers is +// associated with a single DisplayRegion, where the +// final results are presented. +// +// +// There are several different LensNode (Camera) objects +// involved at each stage in the process. To help keep +// them all straight, different words are used to refer +// to each different kind of Camera used within this +// object. The camera(s) under render, that capture the +// original view of the world to be projected onto the +// screens, are called source cameras, and are set per +// screen via set_source_camera(). The LensNode that is +// associated with each screen to project the image as +// seen from the screen's source camera is called a +// projector; these are set via the +// ProjectionScreen::set_projector() interface. +// Finally, the (possibly nonlinear) cameras that view +// the whole configuration of screens are called +// viewers; each of these is associated with a +// DisplayRegion, and they are set via set_viewer_camera(). //////////////////////////////////////////////////////////////////// class EXPCL_PANDAFX NonlinearImager { PUBLISHED: - NonlinearImager(DisplayRegion *dr); + NonlinearImager(); ~NonlinearImager(); int add_screen(ProjectionScreen *screen); @@ -69,49 +90,69 @@ PUBLISHED: int get_num_screens() const; ProjectionScreen *get_screen(int index) const; - void set_size(int index, int width, int height); + void set_texture_size(int index, int width, int height); void set_source_camera(int index, const NodePath &source_camera); - void set_active(int index, bool active); - bool get_active(int index) const; + void set_screen_active(int index, bool active); + bool get_screen_active(int index) const; - void set_viewer(const NodePath &viewer); - INLINE const NodePath &get_viewer() const; + int add_viewer(DisplayRegion *dr); + int find_viewer(DisplayRegion *dr) const; + void remove_viewer(int index); + void remove_all_viewers(); - INLINE NodePath get_internal_scene() const; + void set_viewer_camera(int index, const NodePath &viewer_camera); + NodePath get_viewer_camera(int index) const; + + NodePath get_internal_scene(int index) const; + + int get_num_viewers() const; + DisplayRegion *get_viewer(int index) const; void recompute(); void render(GraphicsEngine *engine); private: + class Viewer { + public: + PT(DisplayRegion) _dr; + PT(Camera) _internal_camera; + NodePath _internal_scene; + NodePath _viewer; + PT(LensNode) _viewer_node; + UpdateSeq _viewer_lens_change; + }; + typedef pvector Viewers; + + class Mesh { + public: + NodePath _mesh; + UpdateSeq _last_screen; + }; + typedef pvector Meshes; + class Screen { public: PT(ProjectionScreen) _screen; - NodePath _mesh; PT(Texture) _texture; NodePath _source_camera; int _tex_width, _tex_height; - UpdateSeq _last_screen; bool _active; + + // One mesh per viewer. + Meshes _meshes; }; + typedef pvector Screens; void recompute_if_stale(); - void recompute_screen(Screen &screen); + void recompute_screen(Screen &screen, size_t vi); void render_screen(GraphicsEngine *engine, Screen &screen); - PT(DisplayRegion) _dr; - - typedef pvector Screens; + Viewers _viewers; Screens _screens; - - NodePath _viewer; - PT(LensNode) _viewer_node; - - PT(Camera) _internal_camera; - NodePath _internal_scene; + GraphicsStateGuardian *_gsg; bool _stale; - UpdateSeq _viewer_lens_change; }; #include "nonlinearImager.I"