Browse Source

add set_frustum_from_corners

David Rose 24 years ago
parent
commit
46189dbae4
3 changed files with 416 additions and 0 deletions
  1. 13 0
      panda/src/gobj/lens.I
  2. 379 0
      panda/src/gobj/lens.cxx
  3. 24 0
      panda/src/gobj/lens.h

+ 13 - 0
panda/src/gobj/lens.I

@@ -338,6 +338,19 @@ set_view_vector(float x, float y, float z, float i, float j, float k) {
   set_view_vector(LVector3f(x, y, z), LVector3f(i, j, k));
   set_view_vector(LVector3f(x, y, z), LVector3f(i, j, k));
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::get_last_change
+//       Access: Public
+//  Description: Returns the UpdateSeq that is incremented whenever
+//               the lens properties are changed.  As long as this
+//               number remains the same, you may assume the lens
+//               properties are unchanged.
+////////////////////////////////////////////////////////////////////
+INLINE const UpdateSeq &Lens::
+get_last_change() const {
+  return _last_change;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::adjust_user_flags
 //     Function: Lens::adjust_user_flags
 //       Access: Protected
 //       Access: Protected

+ 379 - 0
panda/src/gobj/lens.cxx

@@ -24,6 +24,7 @@
 #include "boundingHexahedron.h"
 #include "boundingHexahedron.h"
 #include "indent.h"
 #include "indent.h"
 #include "config_gobj.h"
 #include "config_gobj.h"
+#include "plane.h"
 
 
 TypeHandle Lens::_type_handle;
 TypeHandle Lens::_type_handle;
 
 
@@ -540,6 +541,207 @@ get_view_mat() const {
   return _lens_mat;
   return _lens_mat;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::set_frustum_from_corners
+//       Access: Published
+//  Description: Sets up the lens to use the frustum defined by the
+//               four indicated points.  This is most useful for a
+//               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).
+//
+//               It is legal for the four points not to be arranged in
+//               a rectangle; if this is the case, the frustum will be
+//               fitted as tightly as possible to cover all four
+//               points.
+//
+//               The flags parameter contains the union of one or more
+//               of the following bits to control the behavior of this
+//               function:
+//
+//               FC_roll - If this is included, the camera may be
+//               rotated so that its up vector is perpendicular to the
+//               top line.  Otherwise, the standard up vector is used.
+//
+//               FC_camera_plane - This allows the camera plane to be
+//               adjusted to be as nearly perpendicular to the center
+//               of the frustum as possible.  Without this bit, the
+//               orientation camera plane is defined by position of
+//               the four points (which should all be coplanar).  With
+//               this bit, the camera plane is arbitarary, and may be
+//               chosen so that the four points do not themselves lie
+//               in the camera plane (but the points will still be
+//               within the frustum).
+//
+//               FC_off_axis - This allows the resulting frustum to be
+//               off-axis to get the tightest possible fit.  Without
+//               this bit, the viewing axis will be centered within
+//               the frustum, but there may be more wasted space along
+//               the edges.
+//
+//               FC_aspect_ratio - This allows the frustum to be
+//               scaled non-proportionately in the vertical and
+//               horizontal dimensions, if necessary, to get a tighter
+//               fit.  Without this bit, the current aspect ratio will
+//               be preserved.
+//
+//               FC_shear - This allows the frustum to be sheared, if
+//               necessary, to get the tightest possible fit.  This
+//               may result in a parallelogram-based frustum, which
+//               will give a slanted appearance to the rendered image.
+//               Without this bit, the frustum will be
+//               rectangle-based.
+//
+//               In general, if 0 is passed in as the value for flags,
+//               the generated frustum will be a loose fit but sane;
+//               if -1 is passed in, it will be a tighter fit and
+//               possibly screwy.
+////////////////////////////////////////////////////////////////////
+void Lens::
+set_frustum_from_corners(const LVecBase3f &ul, const LVecBase3f &ur,
+                         const LVecBase3f &ll, const LVecBase3f &lr,
+                         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.
+  const LMatrix4f &lens_mat_inv = get_lens_mat_inv();
+  LVector3f eye_offset;
+  lens_mat_inv.get_row3(eye_offset, 3);
+
+  // Now choose the viewing axis.  If FC_camera_plane is specified,
+  // we'll pass it through the centroid for the best camera plane;
+  // otherwise, it's perpendicular to the plane in which the points
+  // lie.
+  LVector3f view_vector;
+  if ((flags & FC_camera_plane) != 0) {
+    view_vector = (ul + ur + ll + lr) / 4.0f;
+  } else {
+    Planef plane(ll, ul, ur);
+    view_vector = plane.get_normal();
+  }
+
+  // Now determine the up axis.  If FC_roll is specified, or if our
+  // view vector is straight up, it is the vector perpendicular to
+  // both the viewing axis and the top line.  Otherwise, it is the
+  // standard up axis.
+  LVector3f up_vector = LVector3f::up(_cs);
+  if (view_vector == up_vector || ((flags & FC_roll) != 0)) {
+    LVector3f top = ul - ur;
+    up_vector = view_vector.cross(top);
+  }
+
+  // Now compute the matrix that applies this rotation.
+  LMatrix4f rot_mat;
+  look_at(rot_mat, view_vector, up_vector, CS_zup_right);
+
+  // And invert it.
+  LMatrix4f inv_rot_mat;
+  inv_rot_mat.invert_affine_from(rot_mat);
+
+  // Use that inverse matrix to convert the four corners to a local
+  // coordinate system, looking down the Y axis.
+  LPoint3f cul = inv_rot_mat.xform_point(ul);
+  LPoint3f cur = inv_rot_mat.xform_point(ur);
+  LPoint3f cll = inv_rot_mat.xform_point(ll);
+  LPoint3f clr = inv_rot_mat.xform_point(lr);
+
+  // Project all points into the Y == 1 plane, so we can do 2-d
+  // manipulation on them.
+  cul /= cul[1];
+  cur /= cur[1];
+  cll /= cll[1];
+  clr /= clr[1];
+
+  LMatrix4f shear_mat = LMatrix4f::ident_mat();
+  LMatrix4f inv_shear_mat = LMatrix4f::ident_mat();
+
+  // Now, if we're allowed to shear the frustum, do so.
+  if ((flags & FC_shear) != 0) {
+    build_shear_mat(shear_mat, cul, cur, cll, clr);
+    inv_shear_mat.invert_from(shear_mat);
+  } 
+
+  // Now build the complete view matrix.
+  LMatrix4f inv_view_mat =
+    inv_rot_mat *
+    inv_shear_mat;
+
+  // And reapply the eye offset to this matrix.
+  inv_view_mat.set_row(3, eye_offset);
+
+  LMatrix4f view_mat;
+  view_mat.invert_from(inv_view_mat);
+  set_view_mat(view_mat);
+
+  LPoint3f ful = inv_view_mat.xform_point(ul);
+  LPoint3f fur = inv_view_mat.xform_point(ur);
+  LPoint3f fll = inv_view_mat.xform_point(ll);
+  LPoint3f flr = inv_view_mat.xform_point(lr);
+
+  // Normalize *these* points into the y == 1 plane.
+  ful /= ful[1];
+  fur /= fur[1];
+  fll /= fll[1];
+  flr /= flr[1];
+
+  // Determine the minimum field of view necesary to cover all four
+  // transformed points.
+  float min_x = min(min(ful[0], fur[0]), min(fll[0], flr[0]));
+  float max_x = max(max(ful[0], fur[0]), max(fll[0], flr[0]));
+  float min_z = min(min(ful[2], fur[2]), min(fll[2], flr[2]));
+  float max_z = max(max(ful[2], fur[2]), max(fll[2], flr[2]));
+
+  float x_spread, x_center, z_spread, z_center;
+
+  if ((flags & FC_off_axis) != 0) {
+    // If we're allowed to make an off-axis projection, then pick the
+    // best center.
+    x_center = (max_x + min_x) / 2.0f;
+    z_center = (max_z + min_z) / 2.0f;
+    x_spread = x_center - min_x;
+    z_spread = z_center - min_z;
+  } else {
+    // Otherwise, the center must be (0, 0).
+    x_center = 0.0f;
+    z_center = 0.0f;
+    x_spread = max(cabs(max_x), cabs(min_x));
+    z_spread = max(cabs(max_z), cabs(min_z));
+  }
+
+  float aspect_ratio = get_aspect_ratio();
+  if ((flags & FC_aspect_ratio) == 0) {
+    // If we must preserve the aspect ratio, then the x and z spreads
+    // must be adjusted to match.
+    if (x_spread < z_spread * aspect_ratio) {
+      // x_spread is too small.
+      x_spread = z_spread * aspect_ratio;
+    } else if (z_spread < x_spread / aspect_ratio) {
+      // z_spread is too small.
+      z_spread = x_spread / aspect_ratio;
+    }
+  }
+
+  float hfov = rad_2_deg(catan(x_spread)) * 2.0f;
+  float vfov = rad_2_deg(catan(z_spread)) * 2.0f;
+
+  set_fov(hfov, vfov);
+
+  if ((flags & FC_aspect_ratio) == 0) {
+    // If we must preserve the aspect ratio, store it one more time.
+    // This is mainly in case we have a non-perspective lens with a
+    // funny relationship between fov and aspect ratio.
+    set_aspect_ratio(aspect_ratio);
+  }
+
+  const LVecBase2f &film_size = get_film_size();
+  set_film_offset(film_size[0] * x_center / (x_spread * 2.0f),
+                  film_size[1] * z_center / (z_spread * 2.0f));
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Lens::recompute_all
 //     Function: Lens::recompute_all
@@ -749,6 +951,8 @@ write(ostream &out, int indent_level) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Lens::
 void Lens::
 throw_change_event() {
 throw_change_event() {
+  ++_last_change;
+
   if (!_change_event.empty()) {
   if (!_change_event.empty()) {
     throw_event(_change_event);
     throw_event(_change_event);
   }
   }
@@ -1289,3 +1493,178 @@ define_geom_coords() {
 
 
   return num_segments;
   return num_segments;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::build_shear_mat
+//       Access: Private, Static
+//  Description: A support function for set_frustum_from_corners(),
+//               this computes a matrix that will shear the four
+//               indicated points to the most nearly rectangular.
+////////////////////////////////////////////////////////////////////
+void Lens::
+build_shear_mat(LMatrix4f &shear_mat,
+                const LPoint3f &cul, const LPoint3f &cur,
+                const LPoint3f &cll, const LPoint3f &clr) {
+  // Fit a parallelogram around these four points.
+
+  // Put the points in an array so we can rotate it around to find
+  // the longest edge.
+  LPoint3f points[4] = {
+    cul, cur, clr, cll
+  };
+
+  float edge_lengths[4];
+  float max_edge_length = -1.0f;
+  int base_edge = -1;
+  for (int i = 0; i < 4; i++) {
+    LVector3f edge = points[(i + 1) % 4] - points[i];
+    float length = edge.length_squared();
+    edge_lengths[i] = length;
+    if (length > max_edge_length) {
+      base_edge = i;
+      max_edge_length = length;
+    }
+  }
+
+  const LPoint3f &base_origin = points[base_edge];
+  LVector3f base_vec = points[(base_edge + 1) % 4] - base_origin;
+
+  float base_edge_length = csqrt(max_edge_length);
+
+  // The longest edge is the base of our parallelogram.  The parallel
+  // edge must pass through the point furthest from this edge.
+
+  int a = (base_edge + 2) % 4;
+  int b = (base_edge + 3) % 4;
+
+  float a_dist = sqr_dist_to_line(points[a], base_origin, base_vec);
+  float b_dist = sqr_dist_to_line(points[b], base_origin, base_vec);
+
+  int far_point;
+  float dist;
+  if (a_dist > b_dist) {
+    far_point = a;
+    dist = csqrt(a_dist);
+  } else {
+    far_point = b;
+    dist = csqrt(b_dist);
+  }
+
+  // Try to make the parallelogram as nearly rectangular as possible.
+  // How suitable is a true rectangle?
+  LVector3f perpendic = base_vec.cross(LVector3f(0.0, -1.0, 0.0));
+  perpendic.normalize();
+  perpendic *= dist;
+  LPoint3f parallel_origin = points[base_edge] + perpendic;
+
+  // It follows that far_point is on the line passing through the
+  // parallel edge.  Is it within the endpoints?
+  LVector3f base_norm_vec = base_vec / base_edge_length;
+
+  LVector3f far_point_delta = points[far_point] - parallel_origin;
+  float far_point_pos = far_point_delta.dot(base_norm_vec);
+
+  if (far_point_pos < 0.0f) {
+    // We have to slide the parallel_origin back to include far_point.
+    parallel_origin += base_norm_vec * far_point_pos;
+
+  } else if (far_point_pos > base_edge_length) {
+    // We have to slide the parallel_origin forward to include
+    // far_point.
+    parallel_origin += base_norm_vec * (far_point_pos - base_edge_length);
+  }
+
+  // Finally, is the other point within the parallelogram?
+  float t;
+  float Ox = parallel_origin[0];
+  float Oy = parallel_origin[2];
+  float Vx = base_vec[0];
+  float Vy = base_vec[2];
+  float Ax, Ay, Bx, By;
+
+  if (far_point == a) {
+    // near point is b
+    LVector3f v = points[b] - base_origin;
+    Ax = points[b][0];
+    Ay = points[b][2];
+    Bx = v[0];
+    By = v[2];
+  } else {
+    // near point is a
+    LVector3f v = points[a] - (base_origin + base_vec);
+    Ax = points[a][0];
+    Ay = points[a][2];
+    Bx = v[0];
+    By = v[2];
+  }
+  t = ((Ox - Ax) * By + (Ay - Oy) * Bx) / (Bx * Vy - By * Vx);
+
+  if (t < 0.0f) {
+    // We need to slide the parallel_origin back to include
+    // the near point.
+    parallel_origin += base_vec * t;
+  } else if (t > 1.0f) {
+    // We need to slide the parallel_origin forward to include the far
+    // point.
+    parallel_origin += base_vec * (1.0f - t);
+  }
+
+  LVector3f adjacent_norm_vec = parallel_origin - base_origin;
+  adjacent_norm_vec.normalize();
+
+  // Now we've defined a parallelogram that includes all four points,
+  // and we're ready to build a shear transform.
+  shear_mat = LMatrix4f::ident_mat();
+
+  // The edges of the parallelogram become the axes.
+  switch (base_edge) {
+  case 0:
+    // The base_origin is the upper-left corner.  X axis is base_norm_vec,
+    // Z axis is -adjacent_norm_vec.
+    shear_mat.set_row(0, base_norm_vec);
+    shear_mat.set_row(2, -adjacent_norm_vec);
+    break;
+
+  case 1:
+    // The base_origin is the upper-right corner.  X axis is
+    // -adjacent_norm_vec, Z axis is -base_norm_vec.
+    shear_mat.set_row(0, -adjacent_norm_vec);
+    shear_mat.set_row(2, -base_norm_vec);
+    break;
+
+  case 2:
+    // The base_origin is the lower-right corner.  X axis is
+    // -base_norm_vec, Z axis is adjacent_norm_vec.
+    shear_mat.set_row(0, -base_norm_vec);
+    shear_mat.set_row(2, adjacent_norm_vec);
+    break;
+
+  case 3:
+    // The base_origin is the lower-left corner.  X axis is
+    // adjacent_norm_vec, Z axis is base_norm_vec.
+    shear_mat.set_row(0, adjacent_norm_vec);
+    shear_mat.set_row(2, base_norm_vec);
+    break;
+    
+  default:
+    nassertv(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::sqr_dist_to_line
+//       Access: Private, Static
+//  Description: A support function for build_shear_mat(), this
+//               computes the minimum distance from a point to a line,
+//               and returns the distance squared.
+////////////////////////////////////////////////////////////////////
+float Lens::
+sqr_dist_to_line(const LPoint3f &point, const LPoint3f &origin, 
+                 const LVector3f &vector) {
+  LVector3f norm = vector;
+  norm.normalize();
+  LVector3f d = point - origin;
+  float hyp_2 = d.length_squared();
+  float leg = d.dot(norm);
+  return hyp_2 - leg * leg;
+}

+ 24 - 0
panda/src/gobj/lens.h

@@ -24,6 +24,7 @@
 #include "typedReferenceCount.h"
 #include "typedReferenceCount.h"
 #include "luse.h"
 #include "luse.h"
 #include "geom.h"
 #include "geom.h"
+#include "updateSeq.h"
 
 
 class BoundingVolume;
 class BoundingVolume;
 
 
@@ -103,6 +104,20 @@ PUBLISHED:
 
 
   void set_view_mat(const LMatrix4f &view_mat);
   void set_view_mat(const LMatrix4f &view_mat);
   const LMatrix4f &get_view_mat() const;
   const LMatrix4f &get_view_mat() const;
+  
+  // These flags are passed in as the last parameter to control the
+  // behavior of set_frustum_from_corners().  See the documentation
+  // for that method for an explanation of each flag.
+  enum FromCorners {
+    FC_roll         = 0x0001,
+    FC_camera_plane = 0x0002,
+    FC_off_axis     = 0x0004,
+    FC_aspect_ratio = 0x0008,
+    FC_shear        = 0x0010,
+  };
+  void set_frustum_from_corners(const LVecBase3f &ul, const LVecBase3f &ur,
+                                const LVecBase3f &ll, const LVecBase3f &lr,
+                                int flags);
 
 
   void recompute_all();
   void recompute_all();
 
 
@@ -114,6 +129,9 @@ PUBLISHED:
   const LMatrix4f &get_projection_mat() const;
   const LMatrix4f &get_projection_mat() const;
   const LMatrix4f &get_projection_mat_inv() const;
   const LMatrix4f &get_projection_mat_inv() const;
 
 
+public:
+  INLINE const UpdateSeq &get_last_change() const;
+
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level = 0) const;
   virtual void write(ostream &out, int indent_level = 0) const;
 
 
@@ -151,9 +169,15 @@ protected:
 private:
 private:
   static void resequence_fov_triad(char &newest, char &older_a, char &older_b);
   static void resequence_fov_triad(char &newest, char &older_a, char &older_b);
   int define_geom_coords();
   int define_geom_coords();
+  static void build_shear_mat(LMatrix4f &shear_mat,
+                              const LPoint3f &cul, const LPoint3f &cur,
+                              const LPoint3f &cll, const LPoint3f &clr);
+  static float sqr_dist_to_line(const LPoint3f &point, const LPoint3f &origin, 
+                                const LVector3f &vector);
 
 
 protected:
 protected:
   string _change_event;
   string _change_event;
+  UpdateSeq _last_change;
   CoordinateSystem _cs;
   CoordinateSystem _cs;
 
 
   LVecBase2f _film_size;
   LVecBase2f _film_size;