Browse Source

stereo rendering

David Rose 20 years ago
parent
commit
ffb92db022

+ 5 - 1
direct/src/showbase/ShowBase.py

@@ -746,7 +746,8 @@ class ShowBase(DirectObject.DirectObject):
         return aspectRatio
 
     def makeCamera(self, win, sort = 0, scene = None,
-                   displayRegion = (0, 1, 0, 1), aspectRatio = None,
+                   displayRegion = (0, 1, 0, 1), stereoChannel = None,
+                   aspectRatio = None,
                    lens = None, camName = 'cam'):
         """
         Makes a new 3-d camera associated with the indicated window,
@@ -755,6 +756,9 @@ class ShowBase(DirectObject.DirectObject):
         dr = win.makeDisplayRegion(*displayRegion)
         dr.setSort(sort)
 
+        if stereoChannel != None:
+            dr.setStereoChannel(stereoChannel)
+
         if scene == None:
             scene = self.render
 

+ 14 - 0
panda/src/display/displayRegion.I

@@ -52,6 +52,20 @@ get_sort() const {
   return cdata->_sort;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::get_stereo_channel
+//       Access: Published
+//  Description: Returns whether the DisplayRegion is specified as the
+//               left or right channel of a stereo pair, or whether it
+//               is a normal, monocular image.  See
+//               set_stereo_channel().
+////////////////////////////////////////////////////////////////////
+INLINE Lens::StereoChannel DisplayRegion::
+get_stereo_channel() {
+  CDReader cdata(_cycler);
+  return cdata->_stereo_channel;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegion::set_cube_map_index
 //       Access: Published

+ 80 - 3
panda/src/display/displayRegion.cxx

@@ -75,7 +75,7 @@ operator = (const DisplayRegion&) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegion::Destructor
-//       Access: Public
+//       Access: Public, Virtual
 //  Description:
 ////////////////////////////////////////////////////////////////////
 DisplayRegion::
@@ -305,8 +305,7 @@ set_active(bool active) {
 ////////////////////////////////////////////////////////////////////
 void DisplayRegion::
 set_sort(int sort) {
-  int pipeline_stage = Thread::get_current_pipeline_stage();
-  nassertv(pipeline_stage == 0);
+  nassertv(Thread::get_current_pipeline_stage() == 0);
   CDReader cdata(_cycler);
 
   if (sort != cdata->_sort) {
@@ -316,6 +315,53 @@ set_sort(int sort) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::set_stereo_channel
+//       Access: Published
+//  Description: Specifies whether the DisplayRegion represents the
+//               left or right channel of a stereo pair, or whether it
+//               is a normal, monocular image.  See
+//               set_stereo_channel().
+//
+//               This controls which direction--to the left or the
+//               right--the view from a PerspectiveLens is shifted
+//               when it is used to render into this DisplayRegion.
+//               Also see Lens::set_interocular_distance() and
+//               Lens::set_convergence_distance().
+//
+//               Normally you would create at least two DisplayRegions
+//               for a stereo window, one for each of the left and
+//               right channels.  The two DisplayRegions may share the
+//               same camera (and thus the same lens); this parameter
+//               is used to control the exact properties of the lens
+//               when it is used to render into this DisplayRegion.
+//
+//               When the DisplayRegion is attached to a stereo window
+//               (one in which FrameBufferProperties::FM_stereo is
+//               set), this also specifies which physical channel the
+//               DisplayRegion renders to.
+////////////////////////////////////////////////////////////////////
+void DisplayRegion::
+set_stereo_channel(Lens::StereoChannel stereo_channel) {
+  nassertv(Thread::get_current_pipeline_stage() == 0);
+
+  CDWriter cdata(_cycler);
+  cdata->_stereo_channel = stereo_channel;
+  switch (stereo_channel) {
+  case Lens::SC_left:
+    cdata->_draw_buffer_mask = ~(RenderBuffer::T_front_right | RenderBuffer::T_back_right);
+    break;
+
+  case Lens::SC_right:
+    cdata->_draw_buffer_mask = ~(RenderBuffer::T_front_left | RenderBuffer::T_back_left);
+    break;
+
+  case Lens::SC_both:
+    cdata->_draw_buffer_mask = ~0;
+    break;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegion::compute_pixels
 //       Access: Published
@@ -624,6 +670,33 @@ get_screenshot(PNMImage &image) {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::get_screenshot_buffer_type
+//       Access: Public, Virtual
+//  Description: Returns the RenderBuffer that should be used for
+//               capturing screenshots from this particular
+//               DrawableRegion.
+////////////////////////////////////////////////////////////////////
+int DisplayRegion::
+get_screenshot_buffer_type() const {
+  CDReader cdata(_cycler);
+  return _screenshot_buffer_type & cdata->_draw_buffer_mask;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::get_draw_buffer_type
+//       Access: Public, Virtual
+//  Description: Returns the RenderBuffer into which the GSG should
+//               issue draw commands.  Normally, this is the back
+//               buffer for double-buffered windows, and the front
+//               buffer for single-buffered windows.
+////////////////////////////////////////////////////////////////////
+int DisplayRegion::
+get_draw_buffer_type() const {
+  CDReader cdata(_cycler);
+  return _draw_buffer_type & cdata->_draw_buffer_mask;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegion::win_display_regions_changed
 //       Access: Private
@@ -686,6 +759,8 @@ CData() :
   _camera_node((Camera *)NULL),
   _active(true),
   _sort(0),
+  _stereo_channel(Lens::SC_both),
+  _draw_buffer_mask(~0),
   _cube_map_index(-1)
 {
 }
@@ -711,6 +786,8 @@ CData(const DisplayRegion::CData &copy) :
   _camera_node(copy._camera_node),
   _active(copy._active),
   _sort(copy._sort),
+  _stereo_channel(copy._stereo_channel),
+  _draw_buffer_mask(copy._draw_buffer_mask),
   _cube_map_index(copy._cube_map_index)
 {
 }

+ 10 - 1
panda/src/display/displayRegion.h

@@ -33,6 +33,7 @@
 #include "cycleDataStageWriter.h"
 #include "pipelineCycler.h"
 #include "config_display.h"
+#include "lens.h"
 
 #include "plist.h"
 
@@ -63,7 +64,7 @@ private:
   void operator = (const DisplayRegion &copy);
 
 public:
-  ~DisplayRegion();
+  virtual ~DisplayRegion();
   void cleanup();
 
   INLINE bool operator < (const DisplayRegion &other) const;
@@ -88,6 +89,9 @@ PUBLISHED:
   void set_sort(int sort);
   INLINE int get_sort() const;
 
+  void set_stereo_channel(Lens::StereoChannel stereo_channel);
+  INLINE Lens::StereoChannel get_stereo_channel();
+
   INLINE void set_cube_map_index(int cube_map_index);
   INLINE int get_cube_map_index() const;
 
@@ -114,6 +118,9 @@ public:
   INLINE CullResult *get_cull_result() const;
   INLINE SceneSetup *get_scene_setup() const;
 
+  virtual int get_screenshot_buffer_type() const;
+  virtual int get_draw_buffer_type() const;
+
 private:
   class CData;
 
@@ -155,6 +162,8 @@ private:
     
     bool _active;
     int _sort;
+    Lens::StereoChannel _stereo_channel;
+    int _draw_buffer_mask;
     int _cube_map_index;
   };
 

+ 0 - 25
panda/src/display/drawableRegion.I

@@ -202,28 +202,3 @@ INLINE bool DrawableRegion::
 is_any_clear_active() const {
   return (_flags & F_clear_all) != 0;
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: DrawableRegion::get_draw_buffer_type
-//       Access: Public
-//  Description: Returns the RenderBuffer into which the GSG should
-//               issue draw commands.  Normally, this is the back
-//               buffer for double-buffered windows, and the front
-//               buffer for single-buffered windows.
-////////////////////////////////////////////////////////////////////
-INLINE int DrawableRegion::
-get_draw_buffer_type() const {
-  return _draw_buffer_type;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DrawableRegion::get_screenshot_buffer_type
-//       Access: Public, Virtual
-//  Description: Returns the RenderBuffer that should be used for
-//               capturing screenshots from this particular
-//               DrawableRegion.
-////////////////////////////////////////////////////////////////////
-INLINE int DrawableRegion::
-get_screenshot_buffer_type() const {
-  return _screenshot_buffer_type;
-}

+ 35 - 0
panda/src/display/drawableRegion.cxx

@@ -17,3 +17,38 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "drawableRegion.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: DrawableRegion::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+DrawableRegion::
+~DrawableRegion() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DrawableRegion::get_screenshot_buffer_type
+//       Access: Public, Virtual
+//  Description: Returns the RenderBuffer that should be used for
+//               capturing screenshots from this particular
+//               DrawableRegion.
+////////////////////////////////////////////////////////////////////
+int DrawableRegion::
+get_screenshot_buffer_type() const {
+  return _screenshot_buffer_type;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DrawableRegion::get_draw_buffer_type
+//       Access: Public, Virtual
+//  Description: Returns the RenderBuffer into which the GSG should
+//               issue draw commands.  Normally, this is the back
+//               buffer for double-buffered windows, and the front
+//               buffer for single-buffered windows.
+////////////////////////////////////////////////////////////////////
+int DrawableRegion::
+get_draw_buffer_type() const {
+  return _draw_buffer_type;
+}

+ 3 - 2
panda/src/display/drawableRegion.h

@@ -38,6 +38,7 @@ public:
   INLINE DrawableRegion();
   INLINE DrawableRegion(const DrawableRegion &copy);
   INLINE void operator = (const DrawableRegion &copy);
+  virtual ~DrawableRegion();
 
   INLINE void copy_clear_settings(const DrawableRegion &copy);
 
@@ -57,8 +58,8 @@ PUBLISHED:
   INLINE bool is_any_clear_active() const;
 
 public:
-  INLINE int get_screenshot_buffer_type() const;
-  INLINE int get_draw_buffer_type() const;
+  virtual int get_screenshot_buffer_type() const;
+  virtual int get_draw_buffer_type() const;
 
 protected:
   int _screenshot_buffer_type;

+ 1 - 1
panda/src/display/graphicsStateGuardian.I

@@ -574,7 +574,7 @@ set_scene(SceneSetup *scene_setup) {
   if (_current_lens == (Lens *)NULL) {
     return false;
   }
-  return prepare_lens();
+  return prepare_lens(scene_setup->get_display_region()->get_stereo_channel());
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/display/graphicsStateGuardian.cxx

@@ -625,7 +625,7 @@ clear(DrawableRegion *clearable) {
 //               false if it is not.
 ////////////////////////////////////////////////////////////////////
 bool GraphicsStateGuardian::
-prepare_lens() {
+prepare_lens(Lens::StereoChannel stereo_channel) {
   return false;
 }
 

+ 2 - 2
panda/src/display/graphicsStateGuardian.h

@@ -25,7 +25,7 @@
 #include "displayRegionStack.h"
 #include "lensStack.h"
 #include "preparedGraphicsObjects.h"
-
+#include "lens.h"
 #include "graphicsStateGuardianBase.h"
 #include "graphicsThreadingModel.h"
 #include "graphicsPipe.h"
@@ -161,7 +161,7 @@ public:
   INLINE void clear(DisplayRegion *dr);
 
   virtual void prepare_display_region()=0;
-  virtual bool prepare_lens();
+  virtual bool prepare_lens(Lens::StereoChannel stereo_channel);
 
   INLINE int force_normals();
   INLINE int undo_force_normals();

+ 4 - 3
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -609,7 +609,7 @@ prepare_display_region() {
 //               false if it is not.
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian8::
-prepare_lens() {
+prepare_lens(Lens::StereoChannel stereo_channel) {
   if (_current_lens == (Lens *)NULL) {
     return false;
   }
@@ -619,7 +619,7 @@ prepare_lens() {
   }
 
   // Start with the projection matrix from the lens.
-  const LMatrix4f &lens_mat = _current_lens->get_projection_mat();
+  const LMatrix4f &lens_mat = _current_lens->get_projection_mat(stereo_channel);
 
   // The projection matrix must always be left-handed Y-up internally,
   // to match DirectX's convention, even if our coordinate system of
@@ -1301,7 +1301,8 @@ end_draw_primitives() {
 
   if (_vertex_data->is_vertex_transformed()) {
     // Restore the projection matrix that we wiped out above.
-    prepare_lens();
+    _d3d_device->SetTransform(D3DTS_PROJECTION,
+                              (D3DMATRIX*)_projection_mat.get_data());
   }
 
   GraphicsStateGuardian::end_draw_primitives();

+ 1 - 1
panda/src/dxgsg8/dxGraphicsStateGuardian8.h

@@ -67,7 +67,7 @@ public:
   virtual void do_clear(const RenderBuffer &buffer);
 
   virtual void prepare_display_region();
-  virtual bool prepare_lens();
+  virtual bool prepare_lens(Lens::StereoChannel stereo_channel);
 
   virtual bool begin_frame();
   virtual bool begin_scene();

+ 4 - 3
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -826,7 +826,7 @@ prepare_display_region() {
 //               false if it is not.
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian9::
-prepare_lens() {
+prepare_lens(Lens::StereoChannel stereo_channel) {
   if (_current_lens == (Lens *)NULL) {
     return false;
   }
@@ -836,7 +836,7 @@ prepare_lens() {
   }
 
   // Start with the projection matrix from the lens.
-  const LMatrix4f &lens_mat = _current_lens->get_projection_mat();
+  const LMatrix4f &lens_mat = _current_lens->get_projection_mat(stereo_channel);
 
   // The projection matrix must always be left-handed Y-up internally,
   // to match DirectX's convention, even if our coordinate system of
@@ -1716,7 +1716,8 @@ end_draw_primitives() {
 
   if (_vertex_data->is_vertex_transformed()) {
     // Restore the projection matrix that we wiped out above.
-    prepare_lens();
+    _d3d_device->SetTransform(D3DTS_PROJECTION,
+                              (D3DMATRIX*)_projection_mat.get_data());
   }
 
   GraphicsStateGuardian::end_draw_primitives();

+ 1 - 1
panda/src/dxgsg9/dxGraphicsStateGuardian9.h

@@ -103,7 +103,7 @@ public:
   virtual void do_clear(const RenderBuffer &buffer);
 
   virtual void prepare_display_region();
-  virtual bool prepare_lens();
+  virtual bool prepare_lens(Lens::StereoChannel stereo_channel);
 
   virtual bool begin_frame();
   virtual bool begin_scene();

+ 24 - 6
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -794,6 +794,7 @@ reset() {
     GLP(Disable)(GL_MULTISAMPLE);
   }
 
+  _stereo = ((get_properties().get_frame_buffer_mode() & FrameBufferProperties::FM_stereo) != 0);
 
   // Set up all the enabled/disabled flags to GL's known initial
   // values: everything off.
@@ -984,6 +985,7 @@ prepare_display_region() {
     GLsizei width = GLsizei(w);
     GLsizei height = GLsizei(h);
 
+    set_draw_buffer(get_render_buffer(_actual_display_region->get_draw_buffer_type()));
     enable_scissor(true);
     GLP(Scissor)(x, y, width, height);
     GLP(Viewport)(x, y, width, height);
@@ -1006,7 +1008,7 @@ prepare_display_region() {
 //               false if it is not.
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
-prepare_lens() {
+prepare_lens(Lens::StereoChannel stereo_channel) {
   if (_current_lens == (Lens *)NULL) {
     return false;
   }
@@ -1015,7 +1017,7 @@ prepare_lens() {
     return false;
   }
 
-  const LMatrix4f &lens_mat = _current_lens->get_projection_mat();
+  const LMatrix4f &lens_mat = _current_lens->get_projection_mat(stereo_channel);
 
   // The projection matrix must always be right-handed Y-up, even if
   // our coordinate system of choice is otherwise, because certain GL
@@ -3732,19 +3734,35 @@ set_draw_buffer(const RenderBuffer &rb) {
     break;
 
   case RenderBuffer::T_front_right:
-    GLP(DrawBuffer)(GL_FRONT_RIGHT);
+    if (_stereo) {
+      GLP(DrawBuffer)(GL_FRONT_RIGHT);
+    } else {
+      GLP(DrawBuffer)(GL_FRONT);
+    }
     break;
 
   case RenderBuffer::T_front_left:
-    GLP(DrawBuffer)(GL_FRONT_LEFT);
+    if (_stereo) {
+      GLP(DrawBuffer)(GL_FRONT_LEFT);
+    } else {
+      GLP(DrawBuffer)(GL_FRONT);
+    }
     break;
 
   case RenderBuffer::T_back_right:
-    GLP(DrawBuffer)(GL_BACK_RIGHT);
+    if (_stereo) {
+      GLP(DrawBuffer)(GL_BACK_RIGHT);
+    } else {
+      GLP(DrawBuffer)(GL_BACK);
+    }
     break;
 
   case RenderBuffer::T_back_left:
-    GLP(DrawBuffer)(GL_BACK_LEFT);
+    if (_stereo) {
+      GLP(DrawBuffer)(GL_BACK_LEFT);
+    } else {
+      GLP(DrawBuffer)(GL_BACK);
+    }
     break;
 
   default:

+ 3 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -78,7 +78,7 @@ public:
   virtual void do_clear(const RenderBuffer &buffer);
 
   virtual void prepare_display_region();
-  virtual bool prepare_lens();
+  virtual bool prepare_lens(Lens::StereoChannel stereo_channel);
 
   virtual bool begin_frame();
   virtual void end_frame();
@@ -282,6 +282,8 @@ protected:
     MM_alpha_mask = 0x0004,
   };
 
+  bool _stereo;
+
   int _multisample_mode;
   bool _line_smooth_enabled;
   bool _point_smooth_enabled;

+ 185 - 117
panda/src/gobj/lens.cxx

@@ -66,6 +66,13 @@ operator = (const Lens &copy) {
   _aspect_ratio = copy._aspect_ratio;
   _near_distance = copy._near_distance;
   _far_distance = copy._far_distance;
+
+  _view_hpr = copy._view_hpr;
+  _view_vector = copy._view_vector;
+  _interocular_distance = copy._interocular_distance;
+  _convergence_distance = copy._convergence_distance;
+  _keystone = copy._keystone;
+
   _user_flags = copy._user_flags;
   _comp_flags = 0;
 
@@ -105,7 +112,8 @@ clear() {
   _view_hpr.set(0.0f, 0.0f, 0.0f);
   _view_vector.set(0.0f, 1.0f, 0.0f);
   _up_vector.set(0.0f, 0.0f, 1.0f);
-  _iod_offset = 0.0f;
+  _interocular_distance = 0.0f;
+  _convergence_distance = 0.0f;
   _keystone.set(0.0f, 0.0f);
 
   _user_flags = 0;
@@ -432,7 +440,7 @@ set_view_hpr(const LVecBase3f &view_hpr) {
   _view_hpr = view_hpr;
   adjust_user_flags(UF_view_vector | UF_view_mat,
                     UF_view_hpr);
-  adjust_comp_flags(CF_mat | CF_view_vector | CF_iod_offset,
+  adjust_comp_flags(CF_mat | CF_view_vector,
                     CF_view_hpr);
   throw_change_event();
 }
@@ -465,7 +473,7 @@ set_view_vector(const LVector3f &view_vector, const LVector3f &up_vector) {
   _up_vector = up_vector;
   adjust_user_flags(UF_view_hpr | UF_view_mat,
                     UF_view_vector);
-  adjust_comp_flags(CF_mat | CF_view_hpr | CF_iod_offset,
+  adjust_comp_flags(CF_mat | CF_view_hpr,
                     CF_view_vector);
   throw_change_event();
 }
@@ -509,39 +517,71 @@ get_nodal_point() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: Lens::set_iod_offset
+//     Function: Lens::set_interocular_distance
 //       Access: Published
-//  Description: Sets the amount by which the lens is shifted to the
-//               right, perpendicular to its view vector and up
-//               vector.  This is normally used to shift one or both
-//               lens of a stereo camera to generate parallax.  You
-//               can also simply set a complete transformation matrix
-//               (via set_view_mat()) that includes an arbitrary
-//               translation.
+//  Description: Sets the distance between the left and right eyes of
+//               a stereo camera.  This distance is used to apply a
+//               stereo effect when the lens is rendered on a stereo
+//               display region.  It only has an effect on a
+//               PerspectiveLens.
+//
+//               Also see set_interocular_distance(), which relates.
 ////////////////////////////////////////////////////////////////////
 void Lens::
-set_iod_offset(float iod_offset) {
-  _iod_offset = iod_offset;
-  adjust_user_flags(UF_view_mat,
-                    UF_iod_offset);
-  adjust_comp_flags(CF_mat | CF_view_hpr | CF_view_vector,
-                    CF_iod_offset);
+set_interocular_distance(float interocular_distance) {
+  _interocular_distance = interocular_distance;
+  if (_interocular_distance == 0.0f) {
+    adjust_user_flags(UF_interocular_distance, 0);
+  } else {
+    adjust_user_flags(0, UF_interocular_distance);
+  }
+
+  adjust_comp_flags(CF_mat, 0);
   throw_change_event();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: Lens::get_iod_offset
+//     Function: Lens::get_interocular_distance
 //       Access: Published
-//  Description: Returns the aspect ratio of the Lens.  This is
-//               determined based on the indicated film size; see
-//               set_film_size().
+//  Description: See set_interocular_distance().
 ////////////////////////////////////////////////////////////////////
 float Lens::
-get_iod_offset() const {
-  if ((_comp_flags & CF_iod_offset) == 0) {
-    ((Lens *)this)->compute_iod_offset();
+get_interocular_distance() const {
+  return _interocular_distance;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::set_convergence_distance
+//       Access: Published
+//  Description: Sets the distance between the left and right eyes of
+//               a stereo camera.  This distance is used to apply a
+//               stereo effect when the lens is rendered on a stereo
+//               display region.  It only has an effect on a
+//               PerspectiveLens.
+//
+//               Also see set_interocular_distance(), which relates.
+////////////////////////////////////////////////////////////////////
+void Lens::
+set_convergence_distance(float convergence_distance) {
+  _convergence_distance = convergence_distance;
+  if (_convergence_distance == 0.0f) {
+    adjust_user_flags(UF_convergence_distance, 0);
+  } else {
+    adjust_user_flags(0, UF_convergence_distance);
   }
-  return _iod_offset;
+
+  adjust_comp_flags(CF_mat, 0);
+  throw_change_event();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::get_convergence_distance
+//       Access: Published
+//  Description: See set_convergence_distance().
+////////////////////////////////////////////////////////////////////
+float Lens::
+get_convergence_distance() const {
+  return _convergence_distance;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -549,7 +589,7 @@ get_iod_offset() const {
 //       Access: Published
 //  Description: Sets an arbitrary transformation on the lens.  This
 //               replaces the individual transformation components
-//               like set_view_hpr() or set_iod_offset().
+//               like set_view_hpr().
 //
 //               Setting a transformation here will have a slightly
 //               different effect than putting one on the LensNode
@@ -562,9 +602,11 @@ get_iod_offset() const {
 void Lens::
 set_view_mat(const LMatrix4f &view_mat) {
   _lens_mat = view_mat;
-  adjust_user_flags(UF_view_vector | UF_view_hpr | UF_iod_offset,
+  adjust_user_flags(UF_view_vector | UF_view_hpr,
                     UF_view_mat);
-  adjust_comp_flags(CF_projection_mat | CF_projection_mat_inv | CF_lens_mat_inv | CF_view_hpr | CF_view_vector | CF_iod_offset,
+  adjust_comp_flags(CF_projection_mat | CF_projection_mat_inv | 
+                    CF_projection_mat_left_inv | CF_projection_mat_right_inv | 
+                    CF_lens_mat_inv | CF_view_hpr | CF_view_vector,
                     CF_lens_mat);
   throw_change_event();
 }
@@ -590,8 +632,10 @@ get_view_mat() const {
 void Lens::
 clear_view_mat() {
   _lens_mat = LMatrix4f::ident_mat();
-  adjust_user_flags(0, UF_view_vector | UF_view_hpr | UF_iod_offset | UF_view_mat);
-  adjust_comp_flags(CF_projection_mat | CF_projection_mat_inv | CF_lens_mat_inv | CF_view_hpr | CF_view_vector | CF_iod_offset,
+  adjust_user_flags(0, UF_view_vector | UF_view_hpr | UF_view_mat);
+  adjust_comp_flags(CF_projection_mat | CF_projection_mat_inv | 
+                    CF_projection_mat_left_inv | CF_projection_mat_right_inv | 
+                    CF_lens_mat_inv | CF_view_hpr | CF_view_vector,
                     CF_lens_mat);
   throw_change_event();
 }
@@ -617,7 +661,9 @@ void Lens::
 set_keystone(const LVecBase2f &keystone) {
   _keystone = keystone;
   adjust_user_flags(0, UF_keystone);
-  adjust_comp_flags(CF_projection_mat | CF_projection_mat_inv | CF_film_mat | CF_film_mat_inv, 0);
+  adjust_comp_flags(CF_projection_mat | CF_projection_mat_inv |
+                    CF_projection_mat_left_inv | CF_projection_mat_right_inv | 
+                    CF_film_mat | CF_film_mat_inv, 0);
   throw_change_event();
 }
 
@@ -630,7 +676,9 @@ void Lens::
 clear_keystone() {
   _keystone.set(0.0f, 0.0f);
   adjust_user_flags(UF_keystone, 0);
-  adjust_comp_flags(CF_projection_mat | CF_projection_mat_inv | CF_film_mat | CF_film_mat_inv, 0);
+  adjust_comp_flags(CF_projection_mat | CF_projection_mat_inv | 
+                    CF_projection_mat_left_inv | CF_projection_mat_right_inv | 
+                    CF_film_mat | CF_film_mat_inv, 0);
   throw_change_event();
 }
 
@@ -642,9 +690,9 @@ clear_keystone() {
 //               PerspectiveLens, but it may be called for other kinds
 //               of lenses as well.
 //
-//               The frustum will be rooted at the origin (or offset
-//               by iod_offset, or by whatever translation might have
-//               been specified in a previous call to set_view_mat).
+//               The frustum will be rooted at the origin (or by
+//               whatever translation might have been specified in a
+//               previous call to set_view_mat).
 //
 //               It is legal for the four points not to be arranged in
 //               a rectangle; if this is the case, the frustum will be
@@ -699,8 +747,7 @@ set_frustum_from_corners(const LVecBase3f &ul, const LVecBase3f &ur,
                          int flags) {
   // We'll need to know the pre-existing eyepoint translation from the
   // center, so we can preserve it in the new frustum.  This is
-  // usually just a shift along the x axis, if anything at all, for
-  // the iod offset, but it could be an arbitrary vector.
+  // usually (0, 0, 0), but it could be an arbitrary vector.
   const LMatrix4f &lens_mat_inv = get_lens_mat_inv();
   LVector3f eye_offset;
   lens_mat_inv.get_row3(eye_offset, 3);
@@ -1018,22 +1065,59 @@ make_bounds() const {
 //               nonlinear.
 ////////////////////////////////////////////////////////////////////
 const LMatrix4f &Lens::
-get_projection_mat() const {
+get_projection_mat(StereoChannel channel) const {
   if ((_comp_flags & CF_projection_mat) == 0) {
     ((Lens *)this)->compute_projection_mat();
   }
+
+  switch (channel) {
+  case SC_left:
+    return _projection_mat_left;
+  case SC_right:
+    return _projection_mat_right;
+  case SC_both:
+    return _projection_mat;
+  }
+
   return _projection_mat;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::get_projection_mat_inv
-//       Access: Public
+//       Access: Published
 //  Description: Returns the matrix that transforms from a 2-d point
 //               on the film to a 3-d vector in space, if such a
 //               matrix exists.
 ////////////////////////////////////////////////////////////////////
 const LMatrix4f &Lens::
-get_projection_mat_inv() const {
+get_projection_mat_inv(StereoChannel stereo_channel) const {
+  switch (stereo_channel) {
+  case SC_left:
+    {
+      if ((_comp_flags & CF_projection_mat_left_inv) == 0) {
+        Lens *non_const = (Lens *)this;
+        const LMatrix4f &projection_mat_left = get_projection_mat(SC_left);
+        non_const->_projection_mat_left_inv.invert_from(projection_mat_left);
+        non_const->adjust_comp_flags(0, CF_projection_mat_left_inv);
+      }
+    }
+    return _projection_mat_left_inv;
+
+  case SC_right:
+    {
+      if ((_comp_flags & CF_projection_mat_right_inv) == 0) {
+        Lens *non_const = (Lens *)this;
+        const LMatrix4f &projection_mat_right = get_projection_mat(SC_right);
+        non_const->_projection_mat_right_inv.invert_from(projection_mat_right);
+        non_const->adjust_comp_flags(0, CF_projection_mat_right_inv);
+      }
+    }
+    return _projection_mat_right_inv;
+
+  case SC_both:
+    break;
+  }
+
   if ((_comp_flags & CF_projection_mat_inv) == 0) {
     Lens *non_const = (Lens *)this;
     const LMatrix4f &projection_mat = get_projection_mat();
@@ -1043,57 +1127,9 @@ get_projection_mat_inv() const {
   return _projection_mat_inv;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: Lens::output
-//       Access: Published, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void Lens::
-output(ostream &out) const {
-  out << get_type();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Lens::write
-//       Access: Published, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void Lens::
-write(ostream &out, int indent_level) const {
-  indent(out, indent_level) << get_type() << " fov = " << get_fov() << "\n";
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Lens::throw_change_event
-//       Access: Protected
-//  Description: Throws the event associated with changing properties
-//               on this Lens, if any.
-////////////////////////////////////////////////////////////////////
-void Lens::
-throw_change_event() {
-  ++_last_change;
-
-  if (!_change_event.empty()) {
-    throw_event(_change_event, this);
-  }
-
-  if (!_geom_data.is_null()) {
-    if (_geom_data->get_ref_count() == 1) {
-      // No one's using the data any more (there are no references to
-      // it other than this one), so don't bother to recompute it;
-      // just release it.
-      _geom_data.clear();
-    } else {
-      // Someone still has a handle to the data, so recompute it for
-      // them.
-      define_geom_data();
-    }
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::get_film_mat
-//       Access: Protected
+//       Access: Published
 //  Description: Returns the matrix that transforms from a point
 //               behind the lens to a point on the film.
 ////////////////////////////////////////////////////////////////////
@@ -1107,7 +1143,7 @@ get_film_mat() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::get_film_mat_inv
-//       Access: Protected
+//       Access: Published
 //  Description: Returns the matrix that transforms from a point on
 //               the film to a point behind the lens.
 ////////////////////////////////////////////////////////////////////
@@ -1124,7 +1160,7 @@ get_film_mat_inv() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::get_lens_mat
-//       Access: Protected
+//       Access: Published
 //  Description: Returns the matrix that transforms from a point
 //               in front of the lens to a point in space.
 ////////////////////////////////////////////////////////////////////
@@ -1138,7 +1174,7 @@ get_lens_mat() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::get_lens_mat_inv
-//       Access: Protected
+//       Access: Published
 //  Description: Returns the matrix that transforms from a point in
 //               space to a point in front of the lens.
 ////////////////////////////////////////////////////////////////////
@@ -1153,6 +1189,54 @@ get_lens_mat_inv() const {
   return _lens_mat_inv;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::output
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void Lens::
+output(ostream &out) const {
+  out << get_type();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::write
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void Lens::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level) << get_type() << " fov = " << get_fov() << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::throw_change_event
+//       Access: Protected
+//  Description: Throws the event associated with changing properties
+//               on this Lens, if any.
+////////////////////////////////////////////////////////////////////
+void Lens::
+throw_change_event() {
+  ++_last_change;
+
+  if (!_change_event.empty()) {
+    throw_event(_change_event, this);
+  }
+
+  if (!_geom_data.is_null()) {
+    if (_geom_data->get_ref_count() == 1) {
+      // No one's using the data any more (there are no references to
+      // it other than this one), so don't bother to recompute it;
+      // just release it.
+      _geom_data.clear();
+    } else {
+      // Someone still has a handle to the data, so recompute it for
+      // them.
+      define_geom_data();
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::extrude_impl
 //       Access: Protected, Virtual
@@ -1411,23 +1495,6 @@ compute_view_vector() {
   adjust_comp_flags(0, CF_view_vector);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: Lens::compute_iod_offset
-//       Access: Protected, Virtual
-//  Description: Computes the IOD offset: the translation along the
-//               "right" axis.
-////////////////////////////////////////////////////////////////////
-void Lens::
-compute_iod_offset() {
-  if ((_user_flags & UF_iod_offset) == 0) {
-    const LMatrix4f &lens_mat_inv = get_lens_mat_inv();
-    LVector3f translate;
-    lens_mat_inv.get_row3(translate, 3);
-    _iod_offset = -translate.dot(LVector3f::right(_cs));
-  }
-  adjust_comp_flags(0, CF_iod_offset);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::compute_projection_mat
 //       Access: Protected, Virtual
@@ -1436,9 +1503,15 @@ compute_iod_offset() {
 ////////////////////////////////////////////////////////////////////
 void Lens::
 compute_projection_mat() {
-  _projection_mat = LMatrix4f::ident_mat();
-  _projection_mat_inv = _projection_mat;
-  adjust_comp_flags(0, CF_projection_mat | CF_projection_mat_inv);
+  _projection_mat = 
+    _projection_mat_left = 
+    _projection_mat_right =
+    _projection_mat_inv = 
+    _projection_mat_left_inv = 
+    _projection_mat_right_inv = 
+    LMatrix4f::ident_mat();
+  adjust_comp_flags(0, CF_projection_mat | CF_projection_mat_inv |
+                    CF_projection_mat_left_inv |CF_projection_mat_right_inv);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1498,11 +1571,6 @@ compute_lens_mat() {
     } else {
       _lens_mat = LMatrix4f::ident_mat();
     }
-
-    if ((_user_flags & UF_iod_offset) != 0) {
-      LVector3f iod_vector = _iod_offset * LVector3f::right(_cs);
-      _lens_mat = LMatrix4f::translate_mat(iod_vector) * _lens_mat;
-    }
   }
   adjust_comp_flags(CF_lens_mat_inv,
                     CF_lens_mat);

+ 40 - 27
panda/src/gobj/lens.h

@@ -49,6 +49,12 @@ public:
   void operator = (const Lens &copy);
 
 PUBLISHED:
+  enum StereoChannel {
+    SC_left  = 0x01,
+    SC_right = 0x02,
+    SC_both  = 0x03,  // == SC_left | SC_right
+  };
+
   virtual PT(Lens) make_copy() const=0;
 
   INLINE bool extrude(const LPoint2f &point2d,
@@ -107,8 +113,11 @@ PUBLISHED:
   const LVector3f &get_view_vector() const;
   const LVector3f &get_up_vector() const;
   LPoint3f get_nodal_point() const;
-  void set_iod_offset(float offset);
-  float get_iod_offset() const;
+
+  void set_interocular_distance(float interocular_distance);
+  float get_interocular_distance() const;
+  void set_convergence_distance(float convergence_distance);
+  float get_convergence_distance() const;
 
   void set_view_mat(const LMatrix4f &view_mat);
   const LMatrix4f &get_view_mat() const;
@@ -142,8 +151,14 @@ PUBLISHED:
 
   virtual PT(BoundingVolume) make_bounds() const;
 
-  const LMatrix4f &get_projection_mat() const;
-  const LMatrix4f &get_projection_mat_inv() const;
+  const LMatrix4f &get_projection_mat(StereoChannel channel = SC_both) const;
+  const LMatrix4f &get_projection_mat_inv(StereoChannel channel = SC_both) const;
+
+  const LMatrix4f &get_film_mat() const;
+  const LMatrix4f &get_film_mat_inv() const;
+
+  const LMatrix4f &get_lens_mat() const;
+  const LMatrix4f &get_lens_mat_inv() const;
 
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level = 0) const;
@@ -157,12 +172,6 @@ protected:
 
   void throw_change_event();
 
-  const LMatrix4f &get_film_mat() const;
-  const LMatrix4f &get_film_mat_inv() const;
-
-  const LMatrix4f &get_lens_mat() const;
-  const LMatrix4f &get_lens_mat_inv() const;
-
   virtual bool extrude_impl(const LPoint3f &point2d,
                             LPoint3f &near_point, LPoint3f &far_point) const;
   virtual bool extrude_vec_impl(const LPoint3f &point2d, LVector3f &vec) const;
@@ -174,7 +183,6 @@ protected:
   virtual void compute_aspect_ratio();
   virtual void compute_view_hpr();
   virtual void compute_view_vector();
-  virtual void compute_iod_offset();
   virtual void compute_projection_mat();
   virtual void compute_film_mat();
   virtual void compute_lens_mat();
@@ -206,26 +214,30 @@ protected:
 
   LVecBase3f _view_hpr;
   LVector3f _view_vector, _up_vector;
-  float _iod_offset;
+  float _interocular_distance;
+  float _convergence_distance;
   LVecBase2f _keystone;
 
   LMatrix4f _film_mat, _film_mat_inv;
   LMatrix4f _lens_mat, _lens_mat_inv;
   LMatrix4f _projection_mat, _projection_mat_inv;
+  LMatrix4f _projection_mat_left, _projection_mat_left_inv;
+  LMatrix4f _projection_mat_right, _projection_mat_right_inv;
 
   enum UserFlags {
     // Parameters the user may have explicitly specified.
-    UF_film_width          = 0x0001,
-    UF_film_height         = 0x0002,
-    UF_focal_length        = 0x0004,
-    UF_hfov                = 0x0008,
-    UF_vfov                = 0x0010,
-    UF_aspect_ratio        = 0x0020,
-    UF_view_hpr            = 0x0040,
-    UF_view_vector         = 0x0080,
-    UF_iod_offset          = 0x0100,
-    UF_view_mat            = 0x0200,
-    UF_keystone            = 0x0400,
+    UF_film_width           = 0x0001,
+    UF_film_height          = 0x0002,
+    UF_focal_length         = 0x0004,
+    UF_hfov                 = 0x0008,
+    UF_vfov                 = 0x0010,
+    UF_aspect_ratio         = 0x0020,
+    UF_view_hpr             = 0x0040,
+    UF_view_vector          = 0x0080,
+    UF_interocular_distance = 0x0100,
+    UF_convergence_distance = 0x0200,
+    UF_view_mat             = 0x0400,
+    UF_keystone             = 0x0800,
   };
 
   enum CompFlags {
@@ -236,15 +248,16 @@ protected:
     CF_lens_mat_inv        = 0x0008,
     CF_projection_mat      = 0x0010,
     CF_projection_mat_inv  = 0x0020,
-    CF_mat                 = 0x003f,  // all of the above.
+    CF_projection_mat_left_inv  = 0x0040,
+    CF_projection_mat_right_inv = 0x0080,
+    CF_mat                 = 0x00ff,  // all of the above.
 
-    CF_focal_length        = 0x0040,
-    CF_fov                 = 0x0080,
     CF_film_size           = 0x0100,
     CF_aspect_ratio        = 0x0200,
     CF_view_hpr            = 0x0400,
     CF_view_vector         = 0x0800,
-    CF_iod_offset          = 0x1000,
+    CF_focal_length        = 0x1000,
+    CF_fov                 = 0x2000,
   };
   short _user_flags;
   short _comp_flags;

+ 4 - 2
panda/src/gobj/orthographicLens.cxx

@@ -119,9 +119,11 @@ compute_projection_mat() {
     canonical = LMatrix4f::ident_mat();
   }
 
-
   _projection_mat = get_lens_mat_inv() * canonical * get_film_mat();
-  adjust_comp_flags(CF_projection_mat_inv, 
+  _projection_mat_left = _projection_mat_right = _projection_mat;
+
+  adjust_comp_flags(CF_projection_mat_inv | CF_projection_mat_left_inv | 
+                    CF_projection_mat_right_inv,
                     CF_projection_mat);
 }
 

+ 24 - 4
panda/src/gobj/perspectiveLens.cxx

@@ -76,8 +76,8 @@ compute_projection_mat() {
   float a = (fFar + fNear);
   float b = -2.0f * fFar * fNear;
 
-  a/=far_minus_near;
-  b/=far_minus_near;
+  a /= far_minus_near;
+  b /= far_minus_near;
 
   LMatrix4f canonical;
   switch (cs) {
@@ -115,9 +115,29 @@ compute_projection_mat() {
     canonical = LMatrix4f::ident_mat();
   }
 
-
   _projection_mat = get_lens_mat_inv() * canonical * get_film_mat();
-  adjust_comp_flags(CF_projection_mat_inv, 
+
+  if ((_user_flags & UF_interocular_distance) == 0) {
+    _projection_mat_left = _projection_mat_right = _projection_mat;
+
+  } else {
+    // Compute the left and right projection matrices in case this
+    // lens is assigned to a stereo DisplayRegion.
+
+    LVector3f iod = _interocular_distance * 0.5f * LVector3f::left(_cs);
+    _projection_mat_left = get_lens_mat_inv() * LMatrix4f::translate_mat(-iod) * canonical * get_film_mat();
+    _projection_mat_right = get_lens_mat_inv() * LMatrix4f::translate_mat(iod) * canonical * get_film_mat();
+    
+    if (_user_flags & UF_convergence_distance) {
+      nassertv(_convergence_distance != 0.0f);
+      LVector3f cd = (0.25f / _convergence_distance) * LVector3f::left(_cs);
+      _projection_mat_left *= LMatrix4f::translate_mat(cd);
+      _projection_mat_right *= LMatrix4f::translate_mat(-cd);
+    }
+  }
+
+  adjust_comp_flags(CF_projection_mat_inv | CF_projection_mat_left_inv | 
+                    CF_projection_mat_right_inv,
                     CF_projection_mat);
 }