Browse Source

support multiple viewers

David Rose 23 years ago
parent
commit
df65deb57f

+ 0 - 24
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;
-}

+ 301 - 95
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::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::set_viewer
+//     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();
   }
 
-  if (_viewer_node != (LensNode *)NULL && 
-      _viewer_node->get_lens() != (Lens *)NULL) {
-    _viewer_lens_change = _viewer_node->get_lens()->get_last_change();
+  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();
+    }
   }
+
   _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._texture = texture;
-  screen._mesh.set_texture(texture);
-  screen._last_screen = screen._screen->get_last_screen();
+  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

+ 68 - 27
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;
+
+  int add_viewer(DisplayRegion *dr);
+  int find_viewer(DisplayRegion *dr) const;
+  void remove_viewer(int index);
+  void remove_all_viewers();
 
-  void set_viewer(const NodePath &viewer);
-  INLINE const NodePath &get_viewer() const;
+  void set_viewer_camera(int index, const NodePath &viewer_camera);
+  NodePath get_viewer_camera(int index) const;
 
-  INLINE NodePath get_internal_scene() 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<Viewer> Viewers;
+
+  class Mesh {
+  public:
+    NodePath _mesh;
+    UpdateSeq _last_screen;
+  };
+  typedef pvector<Mesh> 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<Screen> 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<Screen> 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"