From 46189dbae4dd8db9b51dedd38ebd326bc19793c4 Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 6 Dec 2001 23:11:08 +0000 Subject: [PATCH] add set_frustum_from_corners --- panda/src/gobj/lens.I | 13 ++ panda/src/gobj/lens.cxx | 379 ++++++++++++++++++++++++++++++++++++++++ panda/src/gobj/lens.h | 24 +++ 3 files changed, 416 insertions(+) diff --git a/panda/src/gobj/lens.I b/panda/src/gobj/lens.I index dc2ee92ae7..6de4c2b38e 100644 --- a/panda/src/gobj/lens.I +++ b/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)); } +//////////////////////////////////////////////////////////////////// +// 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 // Access: Protected diff --git a/panda/src/gobj/lens.cxx b/panda/src/gobj/lens.cxx index 931993f9fd..c5a6182b1c 100644 --- a/panda/src/gobj/lens.cxx +++ b/panda/src/gobj/lens.cxx @@ -24,6 +24,7 @@ #include "boundingHexahedron.h" #include "indent.h" #include "config_gobj.h" +#include "plane.h" TypeHandle Lens::_type_handle; @@ -540,6 +541,207 @@ get_view_mat() const { 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 @@ -749,6 +951,8 @@ write(ostream &out, int indent_level) const { //////////////////////////////////////////////////////////////////// void Lens:: throw_change_event() { + ++_last_change; + if (!_change_event.empty()) { throw_event(_change_event); } @@ -1289,3 +1493,178 @@ define_geom_coords() { 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; +} diff --git a/panda/src/gobj/lens.h b/panda/src/gobj/lens.h index 199b2e52dc..d55d298703 100644 --- a/panda/src/gobj/lens.h +++ b/panda/src/gobj/lens.h @@ -24,6 +24,7 @@ #include "typedReferenceCount.h" #include "luse.h" #include "geom.h" +#include "updateSeq.h" class BoundingVolume; @@ -103,6 +104,20 @@ PUBLISHED: void set_view_mat(const LMatrix4f &view_mat); 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(); @@ -114,6 +129,9 @@ PUBLISHED: const LMatrix4f &get_projection_mat() const; const LMatrix4f &get_projection_mat_inv() const; +public: + INLINE const UpdateSeq &get_last_change() const; + virtual void output(ostream &out) const; virtual void write(ostream &out, int indent_level = 0) const; @@ -151,9 +169,15 @@ protected: private: static void resequence_fov_triad(char &newest, char &older_a, char &older_b); 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: string _change_event; + UpdateSeq _last_change; CoordinateSystem _cs; LVecBase2f _film_size;