add set_frustum_from_corners

This commit is contained in:
David Rose 2001-12-06 23:11:08 +00:00
parent f73c39d96b
commit 46189dbae4
3 changed files with 416 additions and 0 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -24,6 +24,7 @@
#include "typedReferenceCount.h"
#include "luse.h"
#include "geom.h"
#include "updateSeq.h"
class BoundingVolume;
@ -104,6 +105,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();
virtual bool is_linear() const;
@ -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;