mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-02 03:57:00 +08:00
5173 lines
139 KiB
C++
5173 lines
139 KiB
C++
//
|
|
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
|
|
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
|
|
// McNeel & Associates.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
|
|
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
|
|
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
|
|
//
|
|
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
#include "opennurbs.h"
|
|
|
|
#if !defined(ON_COMPILING_OPENNURBS)
|
|
// This check is included in all opennurbs source .c and .cpp files to insure
|
|
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
|
|
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
|
|
// and the opennurbs .h files alter what is declared and how it is declared.
|
|
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
|
|
#endif
|
|
|
|
ON_OBJECT_IMPLEMENT( ON_Viewport, ON_Geometry, "D66E5CCF-EA39-11d3-BFE5-0010830122F0" );
|
|
|
|
static double len2d( double x, double y )
|
|
{
|
|
double d= 0.0;
|
|
double fx = fabs(x);
|
|
double fy = fabs(y);
|
|
if ( fx > fy ) {
|
|
d = fy/fx;
|
|
d = fx*sqrt(1.0+d*d);
|
|
}
|
|
else if ( fy > fx ){
|
|
d = fx/fy;
|
|
d = fy*sqrt(1.0+d*d);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
static void unitize2d( double x, double y, double* ux, double* uy )
|
|
{
|
|
const double eps = 2.0*ON_SQRT_EPSILON;
|
|
// carefully turn two numbers into a 2d unit vector
|
|
double s, c, d;
|
|
c = x;
|
|
s = y;
|
|
if ( s == 0.0 ) {
|
|
c = (c < 0.0) ? -1.0 : 1.0;
|
|
}
|
|
else {
|
|
if ( fabs(s) > fabs(c) ) {
|
|
d = c/s;
|
|
d = fabs(s)*sqrt(1.0+d*d);
|
|
}
|
|
else {
|
|
d = s/c;
|
|
d = fabs(c)*sqrt(1.0+d*d);
|
|
}
|
|
d = 1.0/d;
|
|
if ( fabs(d-1.0) > eps ) {
|
|
s *= d;
|
|
c *= d;
|
|
}
|
|
if ( fabs(s) <= eps || fabs(c) >= 1.0-eps ) {
|
|
s = 0.0;
|
|
c = (c < 0.0) ? -1.0 : 1.0;
|
|
}
|
|
else if ( fabs(c) < eps || fabs(s) >= 1.0-eps) {
|
|
c = 0.0;
|
|
s = (s < 0.0) ? -1.0 : 1.0;
|
|
}
|
|
}
|
|
if ( ux )
|
|
*ux = c;
|
|
if ( uy )
|
|
*uy = s;
|
|
}
|
|
|
|
|
|
static
|
|
bool ON__IsCameraFrameUnitVectorHelper( const ON_3dVector& v )
|
|
{
|
|
// looser standard than ON_3dVector::IsUnitVector() so
|
|
// going to/from floats in OpenGL and Direct3d doesn't
|
|
// create "invalid" views.
|
|
return (v.x != ON_UNSET_VALUE && v.y != ON_UNSET_VALUE && v.z != ON_UNSET_VALUE && fabs(v.Length() - 1.0) <= 1.0e-6);
|
|
}
|
|
|
|
static
|
|
bool ON__IsCameraFramePerpindicular( const ON_3dVector& unit_vector0,const ON_3dVector& unit_vector1 )
|
|
{
|
|
return ( fabs(unit_vector0.x*unit_vector1.x + unit_vector0.y*unit_vector1.y + unit_vector0.z*unit_vector1.z) <= 1.0e-6 );
|
|
}
|
|
|
|
bool
|
|
ON_GetViewportRotationAngles(
|
|
const ON_3dVector& X, // X,Y,Z must be a right handed orthonormal basis
|
|
const ON_3dVector& Y,
|
|
const ON_3dVector& Z,
|
|
double* angle1, // returns rotation about world Z
|
|
double* angle2, // returns rotation about world X ( 0 <= a2 <= pi )
|
|
double* angle3 // returns rotation about world Z
|
|
)
|
|
{
|
|
// double a1 = 0.0; // rotation about world Z
|
|
// double a2 = 0.0; // rotation about world X ( 0 <= a2 <= pi )
|
|
// double a3 = 0.0; // rotation about world Z
|
|
bool bValidFrame = false;
|
|
double sin_a1 = 0.0;
|
|
double cos_a1 = 1.0;
|
|
double sin_a2 = 0.0;
|
|
double cos_a2 = 1.0;
|
|
double sin_a3 = 0.0;
|
|
double cos_a3 = 1.0;
|
|
|
|
// If si = sin(ai) and ci = cos(ai), then the relationship between the camera
|
|
// frame and the angles is defined by the matrix equation C = R3*R2*R1, where:
|
|
//
|
|
// c1 -s1 0 1 0 0 c3 -s3 0
|
|
// R1 = s1 c1 0 R2 = 0 c2 -s2 R3 = s3 c3 0
|
|
// 0 0 1 0 s2 c2 0 0 1
|
|
//
|
|
// CamX[0] CamY[0] CamZ[0]
|
|
// C = CamX[1] CamY[1] CamZ[1]
|
|
// CamX[2] CamY[2] CamZ[2]
|
|
//
|
|
// . . s2*s3
|
|
//
|
|
// R3*R2*R1 = . . -s2*c3
|
|
//
|
|
// s1*s2 c1*s2 c2
|
|
//
|
|
|
|
{
|
|
// don't attempt to work with slop
|
|
const double eps = 8.0*ON_SQRT_EPSILON;
|
|
double dx,dy,dz,d;
|
|
dx = X*X;
|
|
dy = Y*Y;
|
|
dz = Z*Z;
|
|
if ( fabs(dx-1.0) <= eps && fabs(dy-1.0) <= eps && fabs(dz-1.0) <= eps ) {
|
|
dx = X*Y;
|
|
dy = Y*Z;
|
|
dz = Z*X;
|
|
if ( fabs(dx) <= eps && fabs(dy) <= eps && fabs(dz) <= eps ) {
|
|
d = ON_TripleProduct( X, Y, Z );
|
|
bValidFrame = (d > 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bValidFrame )
|
|
{
|
|
// Usually "Z" = opposite of unitized camera direction.
|
|
// "Y" = camera up made ortho to "Z" and unitized.
|
|
// "X" = YxZ.
|
|
// So, when possible, I solve for angles in terms
|
|
// of "Z" and "Y" since "X" will generally have the most noise.
|
|
//
|
|
// Use C = R3*R2*R1 to get sin(a2), cos(a2).
|
|
cos_a2 = Z.z;
|
|
sin_a2 = len2d(Z.x,Z.y);
|
|
unitize2d(cos_a2,sin_a2,&cos_a2,&sin_a2); // kill noise
|
|
|
|
if ( sin_a2 > 0.0 ) {
|
|
// use bottom row to get angle1.
|
|
sin_a1 = X.z;
|
|
cos_a1 = Y.z;
|
|
unitize2d(cos_a1,sin_a1,&cos_a1,&sin_a1); // kill noise
|
|
|
|
// use right column to get angle3
|
|
cos_a3 = -Z.y;
|
|
sin_a3 = Z.x;
|
|
unitize2d(cos_a3,sin_a3,&cos_a3,&sin_a3); // kill noise
|
|
}
|
|
else if ( cos_a2 == 1.0 ) {
|
|
// R2 = identity and C determines (angle1+angle3)
|
|
// arbitrarily set angle1 = 0.
|
|
cos_a3 = Y.y; // = cos(angle3+angle1)
|
|
sin_a3 = -Y.x; // = sin(angle3+angle1)
|
|
}
|
|
else if ( cos_a2 == -1.0 ) {
|
|
// R2 = [1 0 0 / 0 -1 0/ 0 0 -1] and C determines (angle3-angle1)
|
|
// arbitrarily set angle1 = 0
|
|
cos_a3 = -Y.y; // = cos(angle3-angle1)
|
|
sin_a3 = Y.x; // = sin(angle3-angle1)
|
|
}
|
|
}
|
|
|
|
|
|
if ( cos_a1 == -1.0 && sin_a1 == 0.0 ) {
|
|
// when a1 = pi, juggle angles to get a1 = 0 with
|
|
// same effective rotation to keep legacy 3d apps
|
|
// happy.
|
|
// a1: pi -> 0
|
|
// a2: a2 -> 2pi - a2
|
|
// a1: a3 -> pi + a3
|
|
sin_a1 = 0.0;
|
|
cos_a1 = 0.0;
|
|
sin_a2 = -sin_a2;
|
|
sin_a3 = -sin_a3;
|
|
cos_a3 = -cos_a3;
|
|
}
|
|
|
|
if ( angle1 )
|
|
*angle1 = atan2( sin_a1, cos_a1 );
|
|
if ( angle2 )
|
|
*angle2 = atan2( sin_a2, cos_a2 );
|
|
if ( angle3 )
|
|
*angle3 = atan2( sin_a3, cos_a3 );
|
|
|
|
return bValidFrame;
|
|
}
|
|
|
|
void ON_Viewport::SetPerspectiveClippingPlaneConstraints(
|
|
unsigned int depth_buffer_bit_depth
|
|
)
|
|
{
|
|
double min_near_dist = 0.0;
|
|
double min_near_over_far = 0.0;
|
|
ON_Viewport::GetPerspectiveClippingPlaneConstraints(m_CamLoc,depth_buffer_bit_depth,&min_near_dist,&min_near_over_far);
|
|
SetPerspectiveMinNearDist(min_near_dist);
|
|
SetPerspectiveMinNearOverFar(min_near_over_far);
|
|
}
|
|
|
|
|
|
void ON_Viewport::SetPerspectiveMinNearOverFar(double min_near_over_far)
|
|
{
|
|
if ( ON_IsValid(min_near_over_far)
|
|
&& min_near_over_far > ON_ZERO_TOLERANCE
|
|
&& min_near_over_far < 1.0-ON_ZERO_TOLERANCE
|
|
)
|
|
{
|
|
m__MIN_NEAR_OVER_FAR = min_near_over_far;
|
|
}
|
|
}
|
|
|
|
double ON_Viewport::PerspectiveMinNearOverFar() const
|
|
{
|
|
return m__MIN_NEAR_OVER_FAR;
|
|
}
|
|
|
|
void ON_Viewport::SetPerspectiveMinNearDist(double min_near_dist)
|
|
{
|
|
if ( ON_IsValid(min_near_dist) && min_near_dist > ON_ZERO_TOLERANCE )
|
|
{
|
|
m__MIN_NEAR_DIST = min_near_dist;
|
|
}
|
|
}
|
|
|
|
double ON_Viewport::PerspectiveMinNearDist() const
|
|
{
|
|
return m__MIN_NEAR_DIST;
|
|
}
|
|
|
|
void ON_Viewport::Initialize()
|
|
{
|
|
*this = ON_Viewport::DefaultTopViewYUp;
|
|
}
|
|
|
|
|
|
|
|
bool ON_Viewport::Read( ON_BinaryArchive& file )
|
|
{
|
|
*this = ON_Viewport::DefaultTopViewYUp;
|
|
|
|
m_bValidCamera = false;
|
|
m_bValidFrustum = false;
|
|
m_bValidPort = false;
|
|
m_bValidCameraFrame = false;
|
|
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
|
|
int major_version = 0;
|
|
int minor_version = 1;
|
|
bool rc = file.Read3dmChunkVersion(&major_version,&minor_version);
|
|
if (rc && major_version==1)
|
|
{
|
|
// common to all 1.x versions
|
|
int i=0;
|
|
if (rc) rc = file.ReadInt( &i );
|
|
if (rc) m_bValidCamera = (i?true:false);
|
|
if (rc)
|
|
m_bValidCameraFrame = m_bValidCamera;
|
|
if (rc) rc = file.ReadInt( &i );
|
|
if (rc) m_bValidFrustum = (i?true:false);
|
|
if (rc) rc = file.ReadInt( &i );
|
|
if (rc) m_bValidPort = (i?true:false);
|
|
if (rc) rc = file.ReadInt( &i );
|
|
if (rc) m_projection = ON::ViewProjection(i);
|
|
if (rc) rc = file.ReadPoint( m_CamLoc );
|
|
if (rc) rc = file.ReadVector( m_CamDir );
|
|
if (rc) rc = file.ReadVector( m_CamUp );
|
|
if (rc) rc = file.ReadVector( m_CamX );
|
|
if (rc) rc = file.ReadVector( m_CamY );
|
|
if (rc) rc = file.ReadVector( m_CamZ );
|
|
if (rc) rc = file.ReadDouble( &m_frus_left );
|
|
if (rc) rc = file.ReadDouble( &m_frus_right );
|
|
if (rc) rc = file.ReadDouble( &m_frus_bottom );
|
|
if (rc) rc = file.ReadDouble( &m_frus_top );
|
|
if (rc) rc = file.ReadDouble( &m_frus_near );
|
|
if (rc) rc = file.ReadDouble( &m_frus_far );
|
|
if (rc) rc = file.ReadInt( &m_port_left );
|
|
if (rc) rc = file.ReadInt( &m_port_right );
|
|
if (rc) rc = file.ReadInt( &m_port_bottom );
|
|
if (rc) rc = file.ReadInt( &m_port_top );
|
|
if (rc) rc = file.ReadInt( &m_port_near );
|
|
if (rc) rc = file.ReadInt( &m_port_far );
|
|
|
|
if ( m_bValidCamera )
|
|
{
|
|
if ( !ON_Viewport::IsValidCameraLocation(m_CamLoc) )
|
|
{
|
|
ON_ERROR("ON_Viewport.m_bValidCamera in file was true and it should be false.");
|
|
m_bValidCamera = false;
|
|
}
|
|
if ( !ON_Viewport::IsValidCameraUpOrDirection(m_CamUp) || !ON_Viewport::IsValidCameraUpOrDirection(m_CamDir) )
|
|
{
|
|
ON_ERROR("ON_Viewport.m_bValidCamera in file was true and it should be false.");
|
|
m_bValidCamera = false;
|
|
m_bValidCameraFrame = false;
|
|
}
|
|
if (!m_bValidCamera)
|
|
{
|
|
if (ON::view_projection::perspective_view == m_projection)
|
|
SetCamera(ON_Viewport::DefaultPerspectiveViewZUp, true);
|
|
else
|
|
SetCamera(ON_Viewport::DefaultTopViewYUp, true);
|
|
}
|
|
}
|
|
|
|
if (rc && minor_version >= 1 )
|
|
{
|
|
// 1.1 fields
|
|
if (rc) rc = file.ReadUuid(m_viewport_id);
|
|
|
|
if (rc && minor_version >= 2 )
|
|
{
|
|
// 1.2 fields
|
|
bool b;
|
|
b = false;
|
|
if (rc) rc = file.ReadBool(&b);
|
|
if (rc) SetCameraUpLock(b);
|
|
|
|
b = false;
|
|
if (rc) rc = file.ReadBool(&b);
|
|
if (rc) SetCameraDirectionLock(b);
|
|
|
|
b = false;
|
|
if (rc) rc = file.ReadBool(&b);
|
|
if (rc) SetCameraLocationLock(b);
|
|
|
|
b = false;
|
|
if (rc) rc = file.ReadBool(&b);
|
|
if (rc) SetFrustumLeftRightSymmetry(b);
|
|
|
|
b = false;
|
|
if (rc) rc = file.ReadBool(&b);
|
|
if (rc) SetFrustumTopBottomSymmetry(b);
|
|
|
|
if (rc && minor_version >= 3 )
|
|
{
|
|
// added 18, June 2013 to V6
|
|
rc = file.ReadPoint(m_target_point);
|
|
|
|
if (rc && minor_version >= 4)
|
|
{
|
|
rc = file.ReadBool(&m_bValidCameraFrame);
|
|
|
|
if (rc && minor_version >= 5)
|
|
{
|
|
double scaleX = 1, scaleY = 1, scaleZ = 1;
|
|
rc = file.ReadDouble(&scaleX);
|
|
if (rc) rc = file.ReadDouble(&scaleY);
|
|
if (rc) rc = file.ReadDouble(&scaleZ);
|
|
if (rc)
|
|
{
|
|
SetViewScale(scaleX, scaleY, scaleZ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( m_bValidFrustum )
|
|
{
|
|
if ( !ON_IsValid(m_frus_left) || !ON_IsValid(m_frus_right)
|
|
|| !ON_IsValid(m_frus_top) || !ON_IsValid(m_frus_bottom)
|
|
|| !ON_IsValid(m_frus_near) || !ON_IsValid(m_frus_far)
|
|
|| !(m_frus_left < m_frus_right)
|
|
|| !(m_frus_bottom < m_frus_top)
|
|
|| !(0.0 < m_frus_near)
|
|
|| !(m_frus_near < m_frus_far)
|
|
|| !(-ON_NONSENSE_WORLD_COORDINATE_VALUE < m_frus_left)
|
|
|| !(m_frus_right < ON_NONSENSE_WORLD_COORDINATE_VALUE)
|
|
|| !(-ON_NONSENSE_WORLD_COORDINATE_VALUE < m_frus_bottom)
|
|
|| !(m_frus_top < ON_NONSENSE_WORLD_COORDINATE_VALUE)
|
|
|| !(m_frus_far < ON_NONSENSE_WORLD_COORDINATE_VALUE)
|
|
)
|
|
{
|
|
ON_ERROR("ON_Viewport.m_bValidFrustum in file was true and it should be false.");
|
|
m_bValidFrustum = false;
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::Write( ON_BinaryArchive& file ) const
|
|
{
|
|
// 10 Jan 2022 S. Baer
|
|
// version 1.5: write view scale
|
|
bool rc = file.Write3dmChunkVersion(1,5);
|
|
if (rc)
|
|
{
|
|
int i = m_bValidCamera?1:0;
|
|
if (rc) rc = file.WriteInt( i );
|
|
i = m_bValidFrustum?1:0;
|
|
if (rc) rc = file.WriteInt( i );
|
|
i = m_bValidPort?1:0;
|
|
if (rc) rc = file.WriteInt( i );
|
|
i = m_projection;
|
|
if ( file.Archive3dmVersion() <= 4 && IsPerspectiveProjection() )
|
|
{
|
|
// V4 files do not support 2 point perspective projection
|
|
i = ON::perspective_view;
|
|
}
|
|
if (rc) rc = file.WriteInt( i );
|
|
if (rc) rc = file.WritePoint( m_CamLoc );
|
|
if (rc) rc = file.WriteVector( m_CamDir );
|
|
if (rc) rc = file.WriteVector( m_CamUp );
|
|
if (rc) rc = file.WriteVector( m_CamX );
|
|
if (rc) rc = file.WriteVector( m_CamY );
|
|
if (rc) rc = file.WriteVector( m_CamZ );
|
|
if (rc) rc = file.WriteDouble( m_frus_left );
|
|
if (rc) rc = file.WriteDouble( m_frus_right );
|
|
if (rc) rc = file.WriteDouble( m_frus_bottom );
|
|
if (rc) rc = file.WriteDouble( m_frus_top );
|
|
if (rc) rc = file.WriteDouble( m_frus_near );
|
|
if (rc) rc = file.WriteDouble( m_frus_far );
|
|
if (rc) rc = file.WriteInt( m_port_left );
|
|
if (rc) rc = file.WriteInt( m_port_right );
|
|
if (rc) rc = file.WriteInt( m_port_bottom );
|
|
if (rc) rc = file.WriteInt( m_port_top );
|
|
if (rc) rc = file.WriteInt( m_port_near );
|
|
if (rc) rc = file.WriteInt( m_port_far );
|
|
|
|
// 1.1 fields
|
|
if (rc) rc = file.WriteUuid(m_viewport_id);
|
|
|
|
// 1.2 fields
|
|
bool b;
|
|
|
|
b = CameraUpIsLocked();
|
|
if (rc) rc = file.WriteBool(b);
|
|
|
|
b = CameraDirectionIsLocked();
|
|
if (rc) rc = file.WriteBool(b);
|
|
|
|
b = CameraLocationIsLocked();
|
|
if (rc) rc = file.WriteBool(b);
|
|
|
|
b = FrustumIsLeftRightSymmetric();
|
|
if (rc) rc = file.WriteBool(b);
|
|
|
|
b = FrustumIsTopBottomSymmetric();
|
|
if (rc) rc = file.WriteBool(b);
|
|
|
|
// 1.3 fields - added 18, June 2013 to V6
|
|
if (rc) rc = file.WritePoint(m_target_point);
|
|
|
|
// 1.4 fields - added Oct 13 2016 to V6
|
|
if (rc) rc = file.WriteBool(m_bValidCameraFrame);
|
|
|
|
// 1.5 fields - added Jan 10 2022 to V8
|
|
if (rc)
|
|
{
|
|
double scaleX = 1.0, scaleY = 1.0, scaleZ = 1.0;
|
|
GetViewScale(&scaleX, &scaleY, &scaleZ);
|
|
rc = file.WriteDouble(scaleX);
|
|
if (rc) rc = file.WriteDouble(scaleY);
|
|
if (rc) rc = file.WriteDouble(scaleZ);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
Description:
|
|
Copy camera location, up, direction and frame from source_viewport.
|
|
*/
|
|
bool ON_Viewport::SetCamera(
|
|
const ON_Viewport& source_viewport,
|
|
bool bBreakLocks
|
|
)
|
|
{
|
|
if (bBreakLocks)
|
|
{
|
|
SetCameraDirectionLock(false);
|
|
SetCameraUpLock(false);
|
|
SetCameraLocationLock(false);
|
|
}
|
|
|
|
SetCameraDirection(source_viewport.CameraDirection());
|
|
SetCameraUp(source_viewport.CameraUp());
|
|
SetCameraLocation(source_viewport.CameraLocation());
|
|
|
|
return m_bValidCamera;
|
|
}
|
|
|
|
bool ON_Viewport::SetFrustum(
|
|
const ON_Viewport& source_viewport,
|
|
bool bBreakLocks
|
|
)
|
|
{
|
|
if (bBreakLocks)
|
|
{
|
|
UnlockFrustumSymmetry();
|
|
}
|
|
|
|
const bool rc = SetFrustum(
|
|
source_viewport.FrustumLeft(),
|
|
source_viewport.FrustumRight(),
|
|
source_viewport.FrustumBottom(),
|
|
source_viewport.FrustumTop(),
|
|
source_viewport.FrustumNear(),
|
|
source_viewport.FrustumFar()
|
|
);
|
|
|
|
if (bBreakLocks && IsValidFrustum() )
|
|
{
|
|
SetFrustumLeftRightSymmetry(source_viewport.FrustumIsLeftRightSymmetric());
|
|
SetFrustumTopBottomSymmetry(source_viewport.FrustumIsTopBottomSymmetric());
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
ON_SHA1_Hash ON_Viewport::ViewProjectionContentHash() const
|
|
{
|
|
if (m_projection_content_sha1.IsZeroDigest())
|
|
{
|
|
ON_SHA1 sha1;
|
|
sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_projection));
|
|
if (ON_Viewport::IsValidCameraLocation(m_CamLoc))
|
|
sha1.Accumulate3dPoint(m_CamLoc);
|
|
if (m_bValidCameraFrame)
|
|
{
|
|
sha1.Accumulate3dVector(m_CamX);
|
|
sha1.Accumulate3dVector(m_CamY);
|
|
sha1.Accumulate3dVector(m_CamZ);
|
|
}
|
|
if (m_bValidFrustum)
|
|
{
|
|
sha1.AccumulateDouble(m_frus_left);
|
|
sha1.AccumulateDouble(m_frus_right);
|
|
sha1.AccumulateDouble(m_frus_bottom);
|
|
sha1.AccumulateDouble(m_frus_top);
|
|
sha1.AccumulateDouble(m_frus_near);
|
|
sha1.AccumulateDouble(m_frus_far);
|
|
}
|
|
if (m_bValidPort)
|
|
{
|
|
sha1.AccumulateInteger32(m_port_left);
|
|
sha1.AccumulateInteger32(m_port_right);
|
|
sha1.AccumulateInteger32(m_port_bottom);
|
|
sha1.AccumulateInteger32(m_port_top);
|
|
sha1.AccumulateInteger32(m_port_near);
|
|
sha1.AccumulateInteger32(m_port_far);
|
|
}
|
|
m_projection_content_sha1 = sha1.Hash();
|
|
}
|
|
return m_projection_content_sha1;
|
|
}
|
|
|
|
ON_Viewport* ON_Viewport::ShallowCopy(ON_Viewport* destination) const
|
|
{
|
|
if (nullptr == destination)
|
|
destination = new ON_Viewport();
|
|
else
|
|
{
|
|
destination->PurgeUserData();
|
|
if (this == destination)
|
|
return destination; // The caller is probably confused but this is what they should get.
|
|
*destination = ON_Viewport::DefaultTopViewYUp; // default ctor values
|
|
}
|
|
|
|
#define ON_INTERNAL_SHALLOW_FIELD_COPY(field) destination->field = this->field
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_bValidCamera);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_bValidFrustum);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_bValidPort);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_bValidCameraFrame);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_projection);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_bLockCamUp);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_bLockCamDir);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_bLockCamLoc);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_frustum_symmetry_flags);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_CamLoc);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_CamDir);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_CamUp);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_CamX);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_CamY);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_CamZ);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_frus_left);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_frus_right);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_frus_bottom);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_frus_top);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_frus_near);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_frus_far);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_port_left);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_port_right);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_port_bottom);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_port_top);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_port_near);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_port_far);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_target_point);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_viewport_id);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_clip_mods);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m_clip_mods_inverse);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m__MIN_NEAR_DIST);
|
|
ON_INTERNAL_SHALLOW_FIELD_COPY(m__MIN_NEAR_OVER_FAR);
|
|
#undef ON_INTERNAL_SHALLOW_COPY
|
|
return destination;
|
|
}
|
|
|
|
bool ON_Viewport::IsValidCameraLocation(
|
|
ON_3dPoint candidate_point
|
|
)
|
|
{
|
|
const double x
|
|
= candidate_point.IsValid()
|
|
? candidate_point.MaximumCoordinate()
|
|
: ON_NONSENSE_WORLD_COORDINATE_VALUE;
|
|
return (x < ON_NONSENSE_WORLD_COORDINATE_VALUE && x >= 0.0);
|
|
}
|
|
|
|
bool ON_Viewport::IsValidCameraUpOrDirection(
|
|
ON_3dVector candidate_vector
|
|
)
|
|
{
|
|
const double x
|
|
= candidate_vector.IsValid()
|
|
? candidate_vector.MaximumCoordinate()
|
|
: 0.0;
|
|
return (x < ON_NONSENSE_WORLD_COORDINATE_VALUE && x > ON_ZERO_TOLERANCE);
|
|
}
|
|
|
|
bool ON_Viewport::IsValidCamera() const
|
|
{
|
|
return ( m_bValidCamera );
|
|
}
|
|
|
|
bool ON_Viewport::IsValidCameraFrame() const
|
|
{
|
|
return ( m_bValidCameraFrame );
|
|
}
|
|
|
|
bool ON_Viewport::IsValidFrustum() const
|
|
{
|
|
return ( m_bValidFrustum );
|
|
}
|
|
|
|
bool ON_Viewport::IsValid( ON_TextLog* text_log ) const
|
|
{
|
|
if ( !IsValidCamera() )
|
|
{
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("invalid viewport camera settings.\n");
|
|
}
|
|
return false;
|
|
}
|
|
if ( !IsValidFrustum() )
|
|
{
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("invalid viewport frustum settings.\n");
|
|
}
|
|
return false;
|
|
}
|
|
if ( !m_bValidPort )
|
|
{
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("invalid viewport port extents settings.\n");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
int ON_Viewport::Dimension() const
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
bool ON_Viewport::GetNearPlane( ON_Plane& near_plane ) const
|
|
{
|
|
bool rc = IsValidFrustum() && IsValidCamera();
|
|
if ( rc )
|
|
{
|
|
near_plane.origin = m_CamLoc - m_frus_near*m_CamZ;
|
|
near_plane.xaxis = m_CamX;
|
|
near_plane.yaxis = m_CamY;
|
|
near_plane.zaxis = m_CamZ;
|
|
near_plane.UpdateEquation();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetNearPlaneEquation(
|
|
ON_PlaneEquation& near_plane_equation
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
rc = near_plane_equation.Create(m_CamLoc - m_frus_near*m_CamZ,m_CamZ);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetFarPlane( ON_Plane& far_plane ) const
|
|
{
|
|
bool rc = IsValidFrustum() && IsValidCamera();
|
|
if ( rc )
|
|
{
|
|
far_plane.origin = m_CamLoc - m_frus_far*m_CamZ;
|
|
far_plane.xaxis = m_CamX;
|
|
far_plane.yaxis = m_CamY;
|
|
far_plane.zaxis = m_CamZ;
|
|
far_plane.UpdateEquation();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetFarPlaneEquation(
|
|
ON_PlaneEquation& far_plane_equation
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
rc = far_plane_equation.Create(m_CamLoc - m_frus_far*m_CamZ,m_CamZ);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetViewPlane(
|
|
double view_plane_depth,
|
|
ON_Plane& view_plane
|
|
) const
|
|
{
|
|
bool rc = IsValidFrustum() && IsValidCamera();
|
|
if ( rc )
|
|
{
|
|
view_plane.origin = m_CamLoc - view_plane_depth*m_CamZ;
|
|
view_plane.xaxis = m_CamX;
|
|
view_plane.yaxis = m_CamY;
|
|
view_plane.zaxis = m_CamZ;
|
|
view_plane.UpdateEquation();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetViewPlaneEquation(
|
|
double view_plane_depth,
|
|
ON_PlaneEquation& view_plane_equation
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
rc = view_plane_equation.Create(m_CamLoc - view_plane_depth*m_CamZ,m_CamZ);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetNearRect(
|
|
ON_3dPoint& left_bottom,
|
|
ON_3dPoint& right_bottom,
|
|
ON_3dPoint& left_top,
|
|
ON_3dPoint& right_top
|
|
) const
|
|
{
|
|
ON_Plane near_plane;
|
|
bool rc = GetNearPlane( near_plane );
|
|
if (rc ) {
|
|
double x = 1.0, y = 1.0;
|
|
GetViewScale(&x,&y);
|
|
x = 1.0/x;
|
|
y = 1.0/y;
|
|
left_bottom = near_plane.PointAt( x*m_frus_left, y*m_frus_bottom );
|
|
right_bottom = near_plane.PointAt( x*m_frus_right, y*m_frus_bottom );
|
|
left_top = near_plane.PointAt( x*m_frus_left, y*m_frus_top );
|
|
right_top = near_plane.PointAt( x*m_frus_right, y*m_frus_top );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetFarRect(
|
|
ON_3dPoint& left_bottom,
|
|
ON_3dPoint& right_bottom,
|
|
ON_3dPoint& left_top,
|
|
ON_3dPoint& right_top
|
|
) const
|
|
{
|
|
ON_Plane far_plane;
|
|
bool rc = GetFarPlane( far_plane );
|
|
if (rc )
|
|
{
|
|
double s = IsPerspectiveProjection()
|
|
? m_frus_far/m_frus_near
|
|
: 1.0;
|
|
double x = 1.0, y = 1.0;
|
|
GetViewScale(&x,&y);
|
|
x = 1.0/x;
|
|
y = 1.0/y;
|
|
left_bottom = far_plane.PointAt( s*x*m_frus_left, s*y*m_frus_bottom );
|
|
right_bottom = far_plane.PointAt( s*x*m_frus_right, s*y*m_frus_bottom );
|
|
left_top = far_plane.PointAt( s*x*m_frus_left, s*y*m_frus_top );
|
|
right_top = far_plane.PointAt( s*x*m_frus_right, s*y*m_frus_top );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetViewPlaneRect(
|
|
double view_plane_depth,
|
|
ON_3dPoint& left_bottom,
|
|
ON_3dPoint& right_bottom,
|
|
ON_3dPoint& left_top,
|
|
ON_3dPoint& right_top
|
|
) const
|
|
{
|
|
ON_Plane view_plane;
|
|
bool rc = GetViewPlane( view_plane_depth, view_plane );
|
|
if (rc )
|
|
{
|
|
double s = IsPerspectiveProjection()
|
|
? view_plane_depth/m_frus_near
|
|
: 1.0;
|
|
double x = 1.0, y = 1.0;
|
|
GetViewScale(&x,&y);
|
|
x = 1.0/x;
|
|
y = 1.0/y;
|
|
left_bottom = view_plane.PointAt( s*x*m_frus_left, s*y*m_frus_bottom );
|
|
right_bottom = view_plane.PointAt( s*x*m_frus_right, s*y*m_frus_bottom );
|
|
left_top = view_plane.PointAt( s*x*m_frus_left, s*y*m_frus_top );
|
|
right_top = view_plane.PointAt( s*x*m_frus_right, s*y*m_frus_top );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetBBox(
|
|
double* boxmin,
|
|
double* boxmax,
|
|
bool bGrowBox
|
|
) const
|
|
{
|
|
ON_3dPoint corners[9];
|
|
bool rc = GetNearRect(corners[0],corners[1],corners[2],corners[3]);
|
|
if (rc)
|
|
rc = GetFarRect(corners[4],corners[5],corners[6],corners[7]);
|
|
corners[8] = m_CamLoc;
|
|
if (rc)
|
|
{
|
|
rc = ON_GetPointListBoundingBox(
|
|
3, 0, 9,
|
|
3, &corners[0].x,
|
|
boxmin, boxmax, bGrowBox?true:false
|
|
);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::Transform( const ON_Xform& xform )
|
|
{
|
|
bool rc = IsValidCamera() && xform.IsValid();
|
|
if (rc)
|
|
{
|
|
// save input settings
|
|
const ON_3dPoint c0 = m_CamLoc;
|
|
const ON_3dPoint u0 = m_CamUp;
|
|
const ON_3dPoint d0 = m_CamDir;
|
|
const ON_3dPoint x0 = m_CamX;
|
|
const ON_3dPoint y0 = m_CamY;
|
|
const ON_3dPoint z0 = m_CamZ;
|
|
const ON_3dPoint t0 = m_target_point;
|
|
const bool bValidCamera0 = m_bValidCamera;
|
|
const bool bValidCameraFrame0 = m_bValidCameraFrame;
|
|
|
|
// compute transformed settings
|
|
ON_3dPoint c = xform*c0;
|
|
ON_3dVector u = (xform*(c0 + u0)) - c;
|
|
ON_3dVector d = (xform*(c0 + d0)) - c;
|
|
ON_3dPoint t = t0.IsValid() ? (xform*t0) : ON_3dPoint::UnsetPoint;
|
|
|
|
if ( m_bLockCamLoc )
|
|
c = m_CamLoc;
|
|
if ( m_bLockCamUp )
|
|
u = m_CamY;
|
|
if ( m_bLockCamDir )
|
|
d = -m_CamZ;
|
|
|
|
if ( !u.IsValid() || !d.IsValid()
|
|
|| u.IsTiny() || d.IsTiny()
|
|
|| ON_CrossProduct(u,d).IsTiny() )
|
|
{
|
|
rc = false;
|
|
}
|
|
else
|
|
{
|
|
if ( m_bLockCamUp && !m_bLockCamDir )
|
|
{
|
|
d.Unitize();
|
|
if ( fabs(d*u) <= ON_ZERO_TOLERANCE )
|
|
d = -m_CamZ;
|
|
}
|
|
else if ( m_bLockCamDir && !m_bLockCamUp )
|
|
{
|
|
u.Unitize();
|
|
if ( fabs(d*u) <= ON_ZERO_TOLERANCE )
|
|
u = m_CamY;
|
|
}
|
|
|
|
// set new camera position
|
|
if ( !m_bLockCamLoc )
|
|
SetCameraLocation(c);
|
|
if ( !m_bLockCamDir )
|
|
SetCameraDirection(d);
|
|
if ( !m_bLockCamUp)
|
|
SetCameraUp(u);
|
|
|
|
SetTargetPoint(t);
|
|
|
|
rc = SetCameraFrame();
|
|
if ( !rc )
|
|
{
|
|
// restore input settings
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
m_CamLoc = c0;
|
|
m_CamUp = u0;
|
|
m_CamDir = d0;
|
|
m_CamX = x0;
|
|
m_CamY = y0;
|
|
m_CamZ = z0;
|
|
m_target_point = t0;
|
|
m_bValidCamera = bValidCamera0;
|
|
m_bValidCameraFrame = bValidCameraFrame0;
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::SetCameraLocation( const ON_3dPoint& p )
|
|
{
|
|
if (m_bLockCamLoc && ON_Viewport::IsValidCameraLocation(m_CamLoc))
|
|
{
|
|
return (p == m_CamLoc);
|
|
}
|
|
|
|
if (p == ON_3dPoint::UnsetPoint)
|
|
{
|
|
m_CamLoc = ON_3dPoint::UnsetPoint;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
m_bValidCamera = false;
|
|
}
|
|
else if (ON_Viewport::IsValidCameraLocation(p))
|
|
{
|
|
m_CamLoc = p;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
m_bValidCamera = m_bValidCameraFrame;
|
|
}
|
|
|
|
return m_bValidCamera;
|
|
}
|
|
|
|
bool ON_Viewport::SetCameraDirection( const ON_3dVector& v )
|
|
{
|
|
if ( m_bLockCamDir && ON_Viewport::IsValidCameraUpOrDirection(m_CamDir) )
|
|
{
|
|
return (v == m_CamDir);
|
|
}
|
|
|
|
if (v == ON_3dVector::UnsetVector)
|
|
{
|
|
m_CamDir = ON_3dVector::UnsetVector;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
m_bValidCameraFrame = false;
|
|
m_bValidCamera = false;
|
|
}
|
|
else
|
|
{
|
|
m_CamDir = v;
|
|
SetCameraFrame();
|
|
}
|
|
|
|
return m_bValidCamera;
|
|
}
|
|
|
|
bool ON_Viewport::SetCameraUp( const ON_3dVector& v )
|
|
{
|
|
if ( m_bLockCamUp && ON_Viewport::IsValidCameraUpOrDirection(m_CamUp) )
|
|
{
|
|
return (v == m_CamUp);
|
|
}
|
|
|
|
if (v == ON_3dVector::UnsetVector)
|
|
{
|
|
m_CamUp = ON_3dVector::UnsetVector;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
m_bValidCameraFrame = false;
|
|
m_bValidCamera = false;
|
|
}
|
|
else
|
|
{
|
|
m_CamUp = v;
|
|
SetCameraFrame();
|
|
}
|
|
|
|
return m_bValidCamera;
|
|
}
|
|
|
|
static bool Internal_SetCameraFameFailed()
|
|
{
|
|
ON_ERROR("ON_Viewport::SetCameraFrame() failed.");
|
|
return false;
|
|
}
|
|
|
|
bool ON_Viewport::SetCameraFrame()
|
|
{
|
|
m_bValidCamera = false;
|
|
m_bValidCameraFrame = false;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
|
|
|
|
if ( !ON_Viewport::IsValidCameraUpOrDirection(m_CamDir) || !ON_Viewport::IsValidCameraUpOrDirection(m_CamUp) )
|
|
return Internal_SetCameraFameFailed();
|
|
|
|
double d;
|
|
ON_3dVector CamX, CamY, CamZ;
|
|
|
|
if ( m_bLockCamUp && !m_bLockCamDir )
|
|
{
|
|
// up takes precedence over direction
|
|
CamY = m_CamUp;
|
|
if ( !CamY.IsValid() )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !CamY.Unitize() )
|
|
return Internal_SetCameraFameFailed();
|
|
|
|
d = m_CamDir*CamY;
|
|
CamZ = -m_CamDir + d*CamY;
|
|
if ( !CamZ.IsValid() )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !CamZ.Unitize() )
|
|
return false; // happens when up and dir are temporaily parallel.
|
|
}
|
|
else
|
|
{
|
|
// direction takes precedence over up
|
|
CamZ = -m_CamDir;
|
|
if ( !CamZ.IsValid() )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !CamZ.Unitize() )
|
|
return Internal_SetCameraFameFailed();
|
|
|
|
d = m_CamUp*CamZ;
|
|
CamY = m_CamUp - d*CamZ;
|
|
if ( !CamY.IsValid() )
|
|
return Internal_SetCameraFameFailed();
|
|
if (!CamY.Unitize())
|
|
return false; // happens when up and dir are temporaily parallel.
|
|
}
|
|
|
|
CamX = ON_CrossProduct( CamY, CamZ );
|
|
if ( !CamX.IsValid() )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !CamX.Unitize() )
|
|
return false; // happens when up and dir are temporaily parallel.
|
|
|
|
// Guard against garbage resulting from nearly parallel
|
|
// and/or ultra short short dir and up.
|
|
if ( !ON__IsCameraFrameUnitVectorHelper(CamX) )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !ON__IsCameraFrameUnitVectorHelper(CamY) )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !ON__IsCameraFrameUnitVectorHelper(CamZ) )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !ON__IsCameraFramePerpindicular(CamX,CamY) )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !ON__IsCameraFramePerpindicular(CamY,CamZ) )
|
|
return Internal_SetCameraFameFailed();
|
|
if ( !ON__IsCameraFramePerpindicular(CamZ,CamX) )
|
|
return Internal_SetCameraFameFailed();
|
|
|
|
m_CamX = CamX;
|
|
m_CamY = CamY;
|
|
m_CamZ = CamZ;
|
|
|
|
m_bValidCameraFrame = true;
|
|
m_bValidCamera = ON_Viewport::IsValidCameraLocation(m_CamLoc);
|
|
|
|
return m_bValidCamera;
|
|
}
|
|
|
|
ON_3dPoint ON_Viewport::CameraLocation() const
|
|
{
|
|
return m_CamLoc;
|
|
}
|
|
|
|
ON_3dVector ON_Viewport::CameraDirection() const
|
|
{
|
|
return m_CamDir;
|
|
}
|
|
|
|
ON_3dVector ON_Viewport::CameraUp() const
|
|
{
|
|
return m_CamUp;
|
|
}
|
|
|
|
bool ON_Viewport::CameraLocationIsLocked() const
|
|
{
|
|
return m_bLockCamLoc;
|
|
}
|
|
|
|
bool ON_Viewport::CameraDirectionIsLocked() const
|
|
{
|
|
return m_bLockCamDir;
|
|
}
|
|
|
|
bool ON_Viewport::CameraUpIsLocked() const
|
|
{
|
|
return m_bLockCamUp;
|
|
}
|
|
|
|
bool ON_Viewport::FrustumIsLeftRightSymmetric() const
|
|
{
|
|
return (0 != (0x02 & m_frustum_symmetry_flags));
|
|
}
|
|
|
|
bool ON_Viewport::FrustumIsTopBottomSymmetric() const
|
|
{
|
|
return (0 != (0x01 & m_frustum_symmetry_flags));
|
|
}
|
|
|
|
void ON_Viewport::UnlockCamera()
|
|
{
|
|
SetCameraLocationLock(false);
|
|
SetCameraDirectionLock(false);
|
|
SetCameraUpLock(false);
|
|
}
|
|
|
|
void ON_Viewport::UnlockFrustumSymmetry()
|
|
{
|
|
SetFrustumLeftRightSymmetry(false);
|
|
SetFrustumTopBottomSymmetry(false);
|
|
}
|
|
|
|
void ON_Viewport::SetCameraLocationLock( bool bLockCameraLocation )
|
|
{
|
|
m_bLockCamLoc = bLockCameraLocation ? true : false;
|
|
}
|
|
|
|
void ON_Viewport::SetCameraDirectionLock( bool bLockCameraDirection )
|
|
{
|
|
m_bLockCamDir = bLockCameraDirection ? true : false;
|
|
}
|
|
|
|
void ON_Viewport::SetCameraUpLock( bool bLockCameraUp )
|
|
{
|
|
m_bLockCamUp = bLockCameraUp ? true : false;
|
|
}
|
|
|
|
void ON_Viewport::SetFrustumLeftRightSymmetry( bool bForceLeftRightSymmetry )
|
|
{
|
|
if ( bForceLeftRightSymmetry )
|
|
m_frustum_symmetry_flags |= 0x02; // set bit 2
|
|
else
|
|
m_frustum_symmetry_flags &= 0xFD; // clear bit 2
|
|
}
|
|
|
|
void ON_Viewport::SetFrustumTopBottomSymmetry( bool bForceTopBottomSymmetry )
|
|
{
|
|
if ( bForceTopBottomSymmetry )
|
|
m_frustum_symmetry_flags |= 0x01; // set bit 1
|
|
else
|
|
m_frustum_symmetry_flags &= 0xFE; // clear bit 1
|
|
}
|
|
|
|
|
|
|
|
bool ON_Viewport::GetDollyCameraVector(
|
|
int x0, int y0, // (x,y) screen coords of start point
|
|
int x1, int y1, // (x,y) screen coords of end point
|
|
double distance_to_camera, // distance from camera
|
|
ON_3dVector& dolly_vector// dolly vector returned here
|
|
) const
|
|
{
|
|
int port_left, port_right, port_bottom, port_top;
|
|
ON_Xform c2w;
|
|
dolly_vector = ON_3dVector::ZeroVector;
|
|
bool rc = GetScreenPort( &port_left, &port_right, &port_bottom, &port_top );
|
|
if ( rc )
|
|
rc = GetXform( ON::clip_cs, ON::world_cs, c2w );
|
|
if ( rc ) {
|
|
double dx = 0.5*(port_right - port_left);
|
|
double dy = 0.5*(port_top - port_bottom);
|
|
double dz = 0.5*(FrustumFar() - FrustumNear());
|
|
if ( dx == 0.0 || dy == 0.0 || dz == 0.0 )
|
|
rc = false;
|
|
else {
|
|
double z = (distance_to_camera - FrustumNear())/dz - 1.0;
|
|
ON_3dPoint c0( (x0-port_left)/dx-1.0, (y0-port_bottom)/dy-1.0, z );
|
|
ON_3dPoint c1( (x1-port_left)/dx-1.0, (y1-port_bottom)/dy-1.0, z );
|
|
ON_3dPoint w0 = c2w*c0;
|
|
ON_3dPoint w1 = c2w*c1;
|
|
dolly_vector = w0 - w1;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::DollyCamera( const ON_3dVector& dolly )
|
|
{
|
|
bool rc = false;
|
|
if ( m_CamLoc.IsValid() && dolly.IsValid() )
|
|
{
|
|
const ON_3dPoint loc = m_CamLoc + dolly;
|
|
SetCameraLocation(loc);
|
|
rc = m_bValidCamera;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::DollyFrustum( double dollyDistance )
|
|
{
|
|
bool rc = false;
|
|
double new_near, new_far, scale;
|
|
if ( m_bValidFrustum )
|
|
{
|
|
new_near = m_frus_near + dollyDistance;
|
|
new_far = m_frus_far + dollyDistance;
|
|
if ( IsPerspectiveProjection() && new_near < m__MIN_NEAR_DIST )
|
|
{
|
|
new_near = m__MIN_NEAR_DIST;
|
|
}
|
|
scale = ( IsPerspectiveProjection() )
|
|
? new_near/m_frus_near
|
|
: 1.0;
|
|
if ( new_near > 0.0 && new_far > new_near && scale > 0.0 )
|
|
{
|
|
const double new_left = m_frus_left*scale;
|
|
const double new_right = m_frus_right*scale;
|
|
const double new_bottom = m_frus_bottom*scale;
|
|
const double new_top = m_frus_top*scale;
|
|
rc = SetFrustum(new_left, new_right, new_bottom, new_top, new_near, new_far);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetCameraFrame(
|
|
class ON_Plane& camera_frame
|
|
) const
|
|
{
|
|
bool rc = GetCameraFrame(
|
|
&camera_frame.origin.x,
|
|
&camera_frame.xaxis.x,
|
|
&camera_frame.yaxis.x,
|
|
&camera_frame.zaxis.x
|
|
);
|
|
if (rc)
|
|
rc = camera_frame.UpdateEquation();
|
|
if (false == rc)
|
|
camera_frame = ON_Plane::NanPlane;
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetCameraFrame(
|
|
double* CameraLocation,
|
|
double* CameraX,
|
|
double* CameraY,
|
|
double* CameraZ
|
|
) const
|
|
{
|
|
if ( CameraLocation ) {
|
|
CameraLocation[0] = m_CamLoc.x;
|
|
CameraLocation[1] = m_CamLoc.y;
|
|
CameraLocation[2] = m_CamLoc.z;
|
|
}
|
|
if ( CameraX ) {
|
|
CameraX[0] = m_CamX.x;
|
|
CameraX[1] = m_CamX.y;
|
|
CameraX[2] = m_CamX.z;
|
|
}
|
|
if ( CameraY ) {
|
|
CameraY[0] = m_CamY.x;
|
|
CameraY[1] = m_CamY.y;
|
|
CameraY[2] = m_CamY.z;
|
|
}
|
|
if ( CameraZ ) {
|
|
CameraZ[0] = m_CamZ.x;
|
|
CameraZ[1] = m_CamZ.y;
|
|
CameraZ[2] = m_CamZ.z;
|
|
}
|
|
return m_bValidCamera;
|
|
}
|
|
|
|
ON_3dVector ON_Viewport::CameraX() const
|
|
{
|
|
return m_CamX;
|
|
}
|
|
|
|
ON_3dVector ON_Viewport::CameraY() const
|
|
{
|
|
return m_CamY;
|
|
}
|
|
|
|
ON_3dVector ON_Viewport::CameraZ() const
|
|
{
|
|
return m_CamZ;
|
|
}
|
|
|
|
bool ON_Viewport::IsCameraFrameWorldPlan(
|
|
int* xindex,
|
|
int* yindex,
|
|
int* zindex
|
|
)
|
|
{
|
|
int i;
|
|
int ix = 0;
|
|
int iy = 0;
|
|
int iz = 0;
|
|
double X[3], Y[3], Z[3];
|
|
bool rc = GetCameraFrame( nullptr, X, Y, Z );
|
|
if ( rc ) {
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( X[i] == 1.0 ) {
|
|
ix = i+1;
|
|
break;
|
|
}
|
|
if ( X[i] == -1.0 ) {
|
|
ix = -(i+1);
|
|
break;
|
|
}
|
|
}
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( Y[i] == 1.0 ) {
|
|
iy = i+1;
|
|
break;
|
|
}
|
|
if ( Y[i] == -1.0 ) {
|
|
iy = -(i+1);
|
|
break;
|
|
}
|
|
}
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( Z[i] == 1.0 ) {
|
|
iz = i+1;
|
|
break;
|
|
}
|
|
if ( Z[i] == -1.0 ) {
|
|
iz = -(i+1);
|
|
break;
|
|
}
|
|
}
|
|
rc = ( iz != 0 ) ? 1 : 0;
|
|
}
|
|
if ( xindex ) *xindex = ix;
|
|
if ( yindex ) *yindex = iy;
|
|
if ( zindex ) *zindex = iz;
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetCameraExtents(
|
|
// returns bounding box in camera coordinates - this is useful information
|
|
// for setting view frustrums to include the point list
|
|
int count, // count = number of 3d points
|
|
int stride, // stride = number of doubles to skip between points (>=3)
|
|
const double* points, // 3d points in world coordinates
|
|
ON_BoundingBox& cbox, // bounding box in camera coordinates
|
|
int bGrowBox // set to true to grow existing box
|
|
) const
|
|
{
|
|
ON_Xform w2c;
|
|
bool rc = bGrowBox?true:false;
|
|
int i;
|
|
if ( count > 0 && stride >= 3 && points != nullptr ) {
|
|
rc = false;
|
|
if ( GetXform( ON::world_cs, ON::camera_cs, w2c ) ) {
|
|
rc = true;
|
|
for ( i = 0; i < count && rc; i++, points += stride ) {
|
|
rc = cbox.Set( w2c*ON_3dPoint(points), bGrowBox );
|
|
bGrowBox = true;
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetCameraExtents(
|
|
// returns bounding box in camera coordinates - this is useful information
|
|
// for setting view frustrums to include the point list
|
|
const ON_BoundingBox& wbox, // world coordinate bounding box
|
|
ON_BoundingBox& cbox, // bounding box in camera coordinates
|
|
int bGrowBox // set to true to grow existing box
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
ON_3dPointArray corners;
|
|
if ( wbox.GetCorners( corners ) ) {
|
|
rc = GetCameraExtents( corners.Count(), 3, &corners.Array()[0].x, cbox, bGrowBox );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetCameraExtents(
|
|
// returns bounding box in camera coordinates - this is useful information
|
|
// for setting view frustrums to include the point list
|
|
ON_3dPoint& worldSphereCenter,
|
|
double worldSphereRadius,
|
|
ON_BoundingBox& cbox, // bounding box in camera coordinates
|
|
int bGrowBox // set to true to grow existing box
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
ON_BoundingBox sbox;
|
|
if ( GetCameraExtents( 1, 3, &worldSphereCenter.x, sbox, false ) ) {
|
|
const double r = fabs( worldSphereRadius );
|
|
sbox[0][0] -= r;
|
|
sbox[0][1] -= r;
|
|
sbox[0][2] -= r;
|
|
sbox[1][0] += r;
|
|
sbox[1][1] += r;
|
|
sbox[1][2] += r;
|
|
if ( bGrowBox )
|
|
cbox.Union( sbox );
|
|
else
|
|
cbox = sbox;
|
|
rc = true;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
static void UpdateTargetPointHelper( ON_Viewport& vp, double target_distance )
|
|
{
|
|
if ( !vp.IsValidCamera() || !vp.IsValidFrustum() )
|
|
return;
|
|
if ( !ON_IsValid(target_distance) || target_distance <= 0.0 )
|
|
return;
|
|
|
|
ON_3dPoint old_tp = vp.TargetPoint();
|
|
|
|
// Put the target directly in front of the camera.
|
|
// The target_tol test is here to avoid making insignificant
|
|
// changes that appear in the user interface and upset users
|
|
// who find 1.00000000001 to be grossly different from 1.0.
|
|
double target_tol = 1.0e-5*(vp.FrustumWidth()+vp.FrustumHeight())
|
|
+ ON_ZERO_TOLERANCE;
|
|
ON_3dPoint new_tp = vp.CameraLocation() - target_distance*vp.CameraZ();
|
|
if ( new_tp.IsValid()
|
|
&& (!old_tp.IsValid() || new_tp.DistanceTo(old_tp) > target_tol)
|
|
)
|
|
{
|
|
vp.SetTargetPoint(new_tp);
|
|
}
|
|
}
|
|
|
|
bool ON_Viewport::ChangeToParallelProjection( bool bSymmetricFrustum )
|
|
{
|
|
bool rc = (m_bValidCamera && m_bValidFrustum);
|
|
|
|
SetCameraUpLock(false);
|
|
SetCameraDirectionLock(false);
|
|
|
|
if ( ON::parallel_view == m_projection
|
|
&& (bSymmetricFrustum?true:false) == FrustumIsLeftRightSymmetric()
|
|
&& (bSymmetricFrustum?true:false) == FrustumIsTopBottomSymmetric()
|
|
)
|
|
{
|
|
// no changes are required
|
|
if (rc) rc = SetViewScale(1.0, 1.0, 1.0);
|
|
return rc;
|
|
}
|
|
|
|
// if needed, make frustum symmetric
|
|
// If bSymmetricFrustum is true and the input frustum is not symmetric,
|
|
// then this will dolly the camera location.
|
|
ChangeToSymmetricFrustum(bSymmetricFrustum,bSymmetricFrustum,ON_UNSET_VALUE);
|
|
SetFrustumTopBottomSymmetry(bSymmetricFrustum);
|
|
SetFrustumLeftRightSymmetry(bSymmetricFrustum);
|
|
|
|
const ON::view_projection old_projection = m_projection;
|
|
double target_distance = TargetDistance(true);
|
|
if ( !ON_IsValid(target_distance)
|
|
|| !m_bValidFrustum
|
|
|| !ON_IsValid(m_frus_near)
|
|
|| m_frus_near <= 0.0
|
|
|| target_distance <= m_frus_near
|
|
)
|
|
{
|
|
target_distance = 0.0; // makes it easier to check for valid target distance
|
|
}
|
|
|
|
// if needed change projection
|
|
if ( ON::parallel_view != old_projection )
|
|
{
|
|
if ( !SetProjection(ON::parallel_view) )
|
|
rc = false;
|
|
}
|
|
|
|
if ( rc )
|
|
{
|
|
if ( ON::perspective_view == old_projection )
|
|
{
|
|
// change from a perspective to a parallel projection
|
|
if ( target_distance > 0.0 && 0.0 < m_frus_near && m_frus_near < m_frus_far )
|
|
{
|
|
// Update the frustum so that the plane through the target point
|
|
// is the one that is parallel projected. This is generally
|
|
// the best choice when switching from perspective to
|
|
// parallel projection. If needed, SetFrustum() will make the
|
|
// frustum symmetric
|
|
double s = target_distance/m_frus_near;
|
|
double l = m_frus_left*s;
|
|
double r = m_frus_right*s;
|
|
double t = m_frus_top*s;
|
|
double b = m_frus_bottom*s;
|
|
if ( !SetFrustum( l, r, b, t, m_frus_near, m_frus_far ))
|
|
rc = false;
|
|
}
|
|
}
|
|
if ( m_target_point.IsValid() )
|
|
UpdateTargetPointHelper(*this,target_distance);
|
|
}
|
|
if (rc) rc = SetViewScale(1.0, 1.0, 1.0);
|
|
return rc;
|
|
}
|
|
|
|
static bool ChangeFromParallelToPerspectiveHelper( ON_Viewport& vp, double target_distance, double lens_length )
|
|
{
|
|
// helper use by ChangeToPerspectiveProjection() and ChangeToTwoPointPerspectiveProjection()
|
|
if ( ON::perspective_view == vp.Projection() )
|
|
return true;
|
|
|
|
if ( !vp.SetProjection(ON::perspective_view) )
|
|
return false;
|
|
|
|
// change from a parallel to a perspective projection
|
|
double frus_left,frus_right,frus_bottom,frus_top,frus_near,frus_far;
|
|
if ( !vp.GetFrustum(&frus_left,&frus_right,&frus_bottom,&frus_top,&frus_near,&frus_far) )
|
|
return false;
|
|
|
|
// Using width because it works for both two point and ordinary perspective
|
|
const double width = fabs(frus_right - frus_left);
|
|
const ON_3dPoint width_point = ( ON_IsValid(target_distance) && target_distance > 0.0)
|
|
? vp.CameraLocation() - target_distance*vp.CameraZ()
|
|
: ON_3dPoint::UnsetPoint;
|
|
|
|
if ( frus_near < 1.0e-8 && frus_far >= 1.0e-7)
|
|
{
|
|
frus_near = 1.0e-8;
|
|
vp.SetFrustum(frus_left,frus_right,frus_bottom,frus_top,frus_near,frus_far);
|
|
vp.GetFrustum(&frus_left,&frus_right,&frus_bottom,&frus_top,&frus_near,&frus_far);
|
|
}
|
|
|
|
bool rc = false;
|
|
|
|
if ( ON_IsValid(lens_length) && lens_length > 0.0 )
|
|
{
|
|
rc = vp.SetCamera35mmLensLength(lens_length);
|
|
if ( rc
|
|
&& width_point.IsValid()
|
|
&& !vp.CameraLocationIsLocked()
|
|
&& vp.GetFrustum(&frus_left,&frus_right,&frus_bottom,&frus_top,&frus_near,&frus_far)
|
|
&& frus_near > 0.0
|
|
)
|
|
{
|
|
double d = (vp.CameraLocation() - width_point)*vp.CameraZ();
|
|
if ( d > frus_near )
|
|
{
|
|
// make sure target plane is visible
|
|
double w = fabs(frus_right - frus_left)*d/frus_near;
|
|
if ( width > w && w > 0.0 )
|
|
{
|
|
// move camera back to increase "w" back up to "width"
|
|
ON_3dPoint cam_loc0 = vp.CameraLocation();
|
|
double dz = d*(width/w - 1.0);
|
|
ON_3dPoint cam_loc1 = cam_loc0 + dz*vp.CameraZ();
|
|
vp.SetCameraLocation(cam_loc1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::ChangeToPerspectiveProjection(
|
|
double target_distance,
|
|
bool bSymmetricFrustum,
|
|
double lens_length
|
|
)
|
|
{
|
|
bool rc = (m_bValidCamera && m_bValidFrustum);
|
|
|
|
SetCameraUpLock(false);
|
|
SetCameraDirectionLock(false);
|
|
|
|
if ( ON::perspective_view == m_projection
|
|
&& (bSymmetricFrustum?true:false) == FrustumIsLeftRightSymmetric()
|
|
&& (bSymmetricFrustum?true:false) == FrustumIsTopBottomSymmetric()
|
|
)
|
|
{
|
|
double current_lens_length = lens_length;
|
|
if ( ON_IsValid(lens_length)
|
|
&& lens_length > 0.0
|
|
&& GetCamera35mmLensLength(¤t_lens_length)
|
|
&& fabs(current_lens_length - lens_length) > 0.125
|
|
)
|
|
{
|
|
SetCamera35mmLensLength(lens_length);
|
|
}
|
|
// no other changes are required
|
|
if (rc) rc = SetViewScale(1.0, 1.0, 1.0);
|
|
return rc;
|
|
}
|
|
|
|
if ( !ON_IsValid(target_distance) || target_distance <= 0.0 )
|
|
target_distance = TargetDistance(true);
|
|
|
|
// If needed, make frustum symmetric. This may move the
|
|
// camera location in a direction perpendicular to m_CamZ.
|
|
ChangeToSymmetricFrustum(bSymmetricFrustum,bSymmetricFrustum,target_distance);
|
|
SetFrustumTopBottomSymmetry(bSymmetricFrustum);
|
|
SetFrustumLeftRightSymmetry(bSymmetricFrustum);
|
|
|
|
// If needed change projection to perspective. If
|
|
// the input projection is parallel, this may move
|
|
// the camera in the m_CamZ direction to preserve
|
|
// viewing the target plane.
|
|
if (!ChangeFromParallelToPerspectiveHelper(*this,target_distance,lens_length))
|
|
rc = false;
|
|
|
|
if ( rc && m_target_point.IsValid() )
|
|
UpdateTargetPointHelper(*this,target_distance);
|
|
|
|
if (rc) rc = SetViewScale(1.0, 1.0, 1.0);
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
bool GetTwoPointPerspectiveUpAndDirHelper( const ON_3dVector& up,
|
|
const ON_3dVector& CamDir,
|
|
const ON_3dVector& CamY,
|
|
const ON_3dVector& CamZ,
|
|
ON_3dVector& new_up,
|
|
ON_3dVector& new_dir
|
|
)
|
|
{
|
|
// get up direction
|
|
ON_3dVector unit_up;
|
|
ON_3dVector unit_dir;
|
|
if ( up.IsZero() && CamY.IsValid() && CamY.IsUnitVector() )
|
|
{
|
|
new_up = CamY;
|
|
if ( fabs(new_up.z) >= fabs(new_up.y) && fabs(new_up.z) >= fabs(new_up.x) )
|
|
new_up.Set(0.0,0.0,new_up.z<0.0?-1.0:1.0);
|
|
else if ( fabs(new_up.y) >= fabs(new_up.z) && fabs(new_up.y) >= fabs(new_up.x) )
|
|
new_up.Set(0.0,new_up.y<0.0?-1.0:1.0,0.0);
|
|
else
|
|
new_up.Set(new_up.x<0.0?-1.0:1.0,0.0,0.0);
|
|
unit_up = new_up;
|
|
}
|
|
else if ( up.IsValid() && !up.IsTiny() )
|
|
{
|
|
unit_up = up;
|
|
if ( !unit_up.IsUnitVector() && !unit_up.Unitize() )
|
|
return false;
|
|
new_up = up;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// get camera dir
|
|
if ( CamDir.IsValid() && !CamDir.IsTiny() )
|
|
{
|
|
new_dir = CamDir;
|
|
unit_dir = new_dir;
|
|
if ( unit_dir.Unitize() && ON__IsCameraFramePerpindicular(unit_up,unit_dir) )
|
|
return true;
|
|
unit_dir = unit_dir - (unit_dir*unit_up)*unit_up;
|
|
if ( unit_dir.IsValid() && !unit_dir.IsTiny() && unit_dir.Unitize() )
|
|
{
|
|
new_dir = unit_dir;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( CamZ.IsValid() && CamZ.IsUnitVector() )
|
|
{
|
|
new_dir = -CamZ;
|
|
unit_dir = new_dir;
|
|
if ( unit_dir.Unitize() && ON__IsCameraFramePerpindicular(unit_up,unit_dir) )
|
|
return true;
|
|
unit_dir = unit_dir - (new_dir*unit_up)*unit_up;
|
|
if ( unit_dir.IsValid() && !unit_dir.IsTiny() && unit_dir.Unitize() )
|
|
{
|
|
new_dir = unit_dir;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Description:
|
|
When a viewport is set to Parallel Reflected projection, the geometry on the ceiling is shown as if it is mirrored to the floor below.
|
|
*/
|
|
bool ON_Viewport::ChangeToParallelReflectedProjection()
|
|
{
|
|
bool rc = false;
|
|
if (!IsParallelProjection())
|
|
rc = ChangeToParallelProjection(true);
|
|
if (rc) rc = SetViewScale(1.0, 1.0, -1.0);
|
|
return rc;
|
|
}
|
|
bool ON_Viewport::ChangeToTwoPointPerspectiveProjection(
|
|
double target_distance,
|
|
ON_3dVector up,
|
|
double lens_length
|
|
)
|
|
{
|
|
bool rc = (m_bValidCamera && m_bValidFrustum);
|
|
|
|
SetCameraDirectionLock(false);
|
|
|
|
if ( IsTwoPointPerspectiveProjection() )
|
|
{
|
|
double current_lens_length = lens_length;
|
|
if ( ON_IsValid(lens_length)
|
|
&& lens_length > 0.0
|
|
&& GetCamera35mmLensLength(¤t_lens_length)
|
|
&& fabs(current_lens_length - lens_length) > 0.125
|
|
)
|
|
{
|
|
SetCamera35mmLensLength(lens_length);
|
|
}
|
|
if (rc) rc = SetViewScale(1.0, 1.0, 1.0);
|
|
// no other changes are required
|
|
return rc;
|
|
}
|
|
|
|
if ( !ON_IsValid(target_distance) || target_distance <= 0.0 )
|
|
target_distance = TargetDistance(true);
|
|
|
|
// if needed, make frustum left/right symmetric. This may move the
|
|
// camera location in a direction perpendicular to m_CamZ.
|
|
ChangeToSymmetricFrustum(true,false,target_distance);
|
|
SetFrustumLeftRightSymmetry(true);
|
|
SetFrustumTopBottomSymmetry(false);
|
|
|
|
// If needed change projection to perspective. If
|
|
// the input projection is parallel, this may move
|
|
// the camera in the m_CamZ direction to preserve
|
|
// viewing the target plane.
|
|
if (!ChangeFromParallelToPerspectiveHelper(*this,target_distance,lens_length))
|
|
rc = false;
|
|
|
|
if ( rc )
|
|
{
|
|
ON_3dVector new_up = m_CamY;
|
|
ON_3dVector new_dir = -m_CamZ;
|
|
ON_3dPoint new_loc = m_CamLoc;
|
|
if ( !GetTwoPointPerspectiveUpAndDirHelper(up,m_CamDir,m_CamY,m_CamZ,new_up,new_dir) )
|
|
{
|
|
rc = false;
|
|
}
|
|
else
|
|
{
|
|
// move location so the stuff that is currently visible
|
|
// tends to end up in someplace in the new frustum.
|
|
ON_3dPoint center_point = FrustumCenterPoint(target_distance);
|
|
if ( center_point.IsValid() && (new_loc-center_point)*m_CamZ > 0.0 )
|
|
{
|
|
ON_Xform rot;
|
|
rot.Rotation(m_CamY,new_up,center_point);
|
|
new_loc = rot*m_CamLoc;
|
|
if ( !new_loc.IsValid() )
|
|
new_loc = m_CamLoc;
|
|
}
|
|
|
|
ON_3dVector saved_up = m_CamUp;
|
|
ON_3dVector saved_dir = m_CamDir;
|
|
bool bSavedLockCamUp = m_bLockCamUp;
|
|
m_CamUp = new_up; // intentionally ignoring m_bLockCamUp
|
|
m_CamDir = new_dir; // intentionally ignoring m_bLockDirUp
|
|
SetCameraUpLock(true);
|
|
if ( !SetCameraFrame() )
|
|
{
|
|
rc = false;
|
|
m_CamUp = saved_up;
|
|
m_CamDir = saved_dir;
|
|
m_bLockCamUp = bSavedLockCamUp;
|
|
}
|
|
SetCameraLocation(new_loc);
|
|
|
|
UpdateTargetPointHelper(*this,target_distance);
|
|
}
|
|
|
|
}
|
|
if (rc) rc = SetViewScale(1.0, 1.0, 1.0);
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::SetProjection( ON::view_projection projection )
|
|
{
|
|
// Debugging projection changes is easier if we
|
|
// do this initial check.
|
|
if ( projection == m_projection )
|
|
return true;
|
|
|
|
bool rc = false;
|
|
if ( projection == ON::perspective_view )
|
|
{
|
|
rc = true;
|
|
m_projection = ON::perspective_view;
|
|
}
|
|
else
|
|
{
|
|
rc = (projection == ON::parallel_view);
|
|
m_projection = ON::parallel_view;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
ON::view_projection ON_Viewport::Projection() const
|
|
{
|
|
return m_projection;
|
|
}
|
|
|
|
bool ON_Viewport::IsParallelProjection() const
|
|
{
|
|
return ( ON::parallel_view == m_projection );
|
|
}
|
|
|
|
bool ON_Viewport::IsPerspectiveProjection() const
|
|
{
|
|
return ( ON::perspective_view == m_projection );
|
|
}
|
|
|
|
bool ON_Viewport::IsTwoPointPerspectiveProjection() const
|
|
{
|
|
bool rc = IsPerspectiveProjection()
|
|
&& CameraUpIsLocked()
|
|
&& FrustumIsLeftRightSymmetric()
|
|
&& !FrustumIsTopBottomSymmetric();
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::SetFrustum(
|
|
double frus_left,
|
|
double frus_right,
|
|
double frus_bottom,
|
|
double frus_top,
|
|
double frus_near,
|
|
double frus_far
|
|
)
|
|
{
|
|
bool rc = false;
|
|
if (
|
|
ON_IsValid(frus_left)
|
|
&& ON_IsValid(frus_right)
|
|
&& ON_IsValid(frus_top)
|
|
&& ON_IsValid(frus_bottom)
|
|
&& ON_IsValid(frus_near)
|
|
&& ON_IsValid(frus_far)
|
|
&& frus_left < frus_right
|
|
&& frus_bottom < frus_top
|
|
&& 0.0 < frus_near
|
|
&& frus_near < frus_far
|
|
&& -ON_NONSENSE_WORLD_COORDINATE_VALUE < frus_left
|
|
&& frus_right < ON_NONSENSE_WORLD_COORDINATE_VALUE
|
|
&& -ON_NONSENSE_WORLD_COORDINATE_VALUE < frus_bottom
|
|
&& frus_top < ON_NONSENSE_WORLD_COORDINATE_VALUE
|
|
&& frus_far < ON_NONSENSE_WORLD_COORDINATE_VALUE
|
|
)
|
|
{
|
|
if ( IsPerspectiveProjection()
|
|
&& (frus_near <= 1.0e-8 || frus_far > 1.0001e8*frus_near)
|
|
)
|
|
{
|
|
ON_ERROR("ON_Viewport::SetFrustum - Beyond float precision perspective frus_near/frus_far values - will crash MS OpenGL");
|
|
}
|
|
|
|
if ( FrustumIsLeftRightSymmetric() && frus_left != -frus_right )
|
|
{
|
|
double d = 0.5*(frus_right-frus_left);
|
|
frus_right = d;
|
|
frus_left = -d;
|
|
}
|
|
|
|
if ( FrustumIsTopBottomSymmetric() && frus_bottom != -frus_top )
|
|
{
|
|
double d = 0.5*(frus_top-frus_bottom);
|
|
frus_top = d;
|
|
frus_bottom = -d;
|
|
}
|
|
|
|
m_frus_left = frus_left;
|
|
m_frus_right = frus_right;
|
|
m_frus_bottom = frus_bottom;
|
|
m_frus_top = frus_top;
|
|
m_frus_near = frus_near;
|
|
m_frus_far = frus_far;
|
|
m_bValidFrustum = true;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
rc = true;
|
|
}
|
|
else
|
|
{
|
|
// 17 March 2008 Dale Lear
|
|
// I added this to trap the bug that is creating
|
|
// invalid viewports. Developers: If you ever
|
|
// get this error, immediately investigate it.
|
|
ON_ERROR("ON_Viewport::SetFrustum - invalid input");
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetFrustum(
|
|
double* frus_left,
|
|
double* frus_right,
|
|
double* frus_bottom,
|
|
double* frus_top,
|
|
double* frus_near, // = nullptr
|
|
double* frus_far // = nullptr
|
|
) const
|
|
{
|
|
if ( frus_left )
|
|
*frus_left = m_frus_left;
|
|
if ( frus_right )
|
|
*frus_right = m_frus_right;
|
|
if ( frus_bottom )
|
|
*frus_bottom = m_frus_bottom;
|
|
if ( frus_top )
|
|
*frus_top = m_frus_top;
|
|
if ( frus_near )
|
|
*frus_near = m_frus_near;
|
|
if ( frus_far )
|
|
*frus_far = m_frus_far;
|
|
return m_bValidFrustum;
|
|
}
|
|
|
|
double ON_Viewport::FrustumLeft() const { return m_frus_left; }
|
|
double ON_Viewport::FrustumRight() const { return m_frus_right; }
|
|
double ON_Viewport::FrustumBottom() const { return m_frus_bottom; }
|
|
double ON_Viewport::FrustumTop() const { return m_frus_top; }
|
|
double ON_Viewport::FrustumNear() const { return m_frus_near; }
|
|
double ON_Viewport::FrustumFar() const { return m_frus_far; }
|
|
double ON_Viewport::FrustumWidth() const { return m_frus_right-m_frus_left; }
|
|
double ON_Viewport::FrustumHeight() const { return m_frus_top-m_frus_bottom; }
|
|
|
|
double ON_Viewport::FrustumMinimumDiameter() const
|
|
{
|
|
double w = fabs(m_frus_right-m_frus_left);
|
|
double h = fabs(m_frus_top-m_frus_bottom);
|
|
return (w<=h)?w:h;
|
|
}
|
|
|
|
double ON_Viewport::FrustumMaximumDiameter() const
|
|
{
|
|
double w = fabs(m_frus_right-m_frus_left);
|
|
double h = fabs(m_frus_top-m_frus_bottom);
|
|
return (w<=h)?w:h;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::SetFrustumAspect( double frustum_aspect )
|
|
{
|
|
// maintains camera angle
|
|
bool rc = false;
|
|
double w, h, d, left, right, bot, top, near_dist, far_dist;
|
|
if ( frustum_aspect > 0.0 && GetFrustum( &left, &right, &bot, &top, &near_dist, &far_dist ) ) {
|
|
w = right - left;
|
|
h = top - bot;
|
|
if ( fabs(h) > fabs(w) ) {
|
|
d = (h>=0.0) ? fabs(w) : -fabs(w);
|
|
d *= 0.5;
|
|
h = 0.5*(top+bot);
|
|
bot = h-d;
|
|
top = h+d;
|
|
h = top - bot;
|
|
}
|
|
else {
|
|
d = (w>=0.0) ? fabs(h) : -fabs(h);
|
|
d *= 0.5;
|
|
w = 0.5*(left+right);
|
|
left = w-d;
|
|
right = w+d;
|
|
w = right - left;
|
|
}
|
|
if ( frustum_aspect > 1.0 ) {
|
|
// increase width
|
|
d = 0.5*w*frustum_aspect;
|
|
w = 0.5*(left+right);
|
|
left = w-d;
|
|
right = w+d;
|
|
w = right - left;
|
|
}
|
|
else if ( frustum_aspect < 1.0 ) {
|
|
// increase height
|
|
d = 0.5*h/frustum_aspect;
|
|
h = 0.5*(bot+top);
|
|
bot = h-d;
|
|
top = h+d;
|
|
h = top - bot;
|
|
}
|
|
rc = SetFrustum( left, right, bot, top, near_dist, far_dist );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
bool ON_Viewport::GetFrustumAspect( double& frustum_aspect ) const
|
|
{
|
|
// frustum_aspect = frustum width / frustum height
|
|
double w, h, left, right, bot, top;
|
|
bool rc = m_bValidFrustum;
|
|
frustum_aspect = 0.0;
|
|
|
|
if ( GetFrustum( &left, &right, &bot, &top ) ) {
|
|
w = right - left;
|
|
h = top - bot;
|
|
if ( h == 0.0 )
|
|
rc = false;
|
|
else
|
|
frustum_aspect = w/h;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetFrustumCenter( double* frus_center ) const
|
|
{
|
|
double camZ[3], frus_near, frus_far, d;
|
|
if ( !frus_center )
|
|
return false;
|
|
if ( !GetCameraFrame( frus_center, nullptr, nullptr, camZ ) )
|
|
return false;
|
|
if ( !GetFrustum( nullptr, nullptr, nullptr, nullptr, &frus_near, &frus_far ) )
|
|
return false;
|
|
d = -0.5*(frus_near+frus_far);
|
|
frus_center[0] += d*camZ[0];
|
|
frus_center[1] += d*camZ[1];
|
|
frus_center[2] += d*camZ[2];
|
|
return true;
|
|
}
|
|
|
|
bool ON_Viewport::SetScreenPort(
|
|
int port_left,
|
|
int port_right,
|
|
int port_bottom,
|
|
int port_top,
|
|
int port_near, // = 0
|
|
int port_far // = 0
|
|
)
|
|
{
|
|
if ( port_left == port_right )
|
|
return false;
|
|
if ( port_bottom == port_top )
|
|
return false;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
m_port_left = port_left;
|
|
m_port_right = port_right;
|
|
m_port_bottom = port_bottom;
|
|
m_port_top = port_top;
|
|
if ( port_near || port_near != port_far )
|
|
{
|
|
m_port_near = port_near;
|
|
m_port_far = port_far;
|
|
}
|
|
m_bValidPort = true;
|
|
return m_bValidPort;
|
|
}
|
|
|
|
bool ON_Viewport::GetScreenPort(
|
|
int* port_left,
|
|
int* port_right,
|
|
int* port_bottom,
|
|
int* port_top,
|
|
int* port_near, // = nullptr
|
|
int* port_far // = nullptr
|
|
) const
|
|
{
|
|
if ( port_left )
|
|
*port_left = m_port_left;
|
|
if ( port_right )
|
|
*port_right = m_port_right;
|
|
if ( port_bottom )
|
|
*port_bottom = m_port_bottom;
|
|
if ( port_top )
|
|
*port_top = m_port_top;
|
|
if ( port_near )
|
|
*port_near = m_port_near;
|
|
if ( port_far )
|
|
*port_far = m_port_far;
|
|
return m_bValidPort;
|
|
}
|
|
|
|
int ON_Viewport::ScreenPortWidth() const
|
|
{
|
|
int width = m_port_right - m_port_left;
|
|
return width >= 0 ? width : -width;
|
|
}
|
|
|
|
int ON_Viewport::ScreenPortHeight() const
|
|
{
|
|
int height = m_port_top - m_port_bottom;
|
|
return height >= 0 ? height : -height;
|
|
}
|
|
|
|
ON_2iSize ON_Viewport::ScreenPortSize() const
|
|
{
|
|
return ON_2iSize(ScreenPortWidth(), ScreenPortHeight());
|
|
}
|
|
|
|
bool ON_Viewport::GetScreenPortAspect(double& aspect) const
|
|
{
|
|
const double width = m_port_right - m_port_left;
|
|
const double height = m_port_top - m_port_bottom;
|
|
aspect = ( m_bValidPort && ON_IsValid(height) && ON_IsValid(width) && height != 0.0 )
|
|
? fabs(width/height)
|
|
: 0.0;
|
|
return (m_bValidPort && aspect != 0.0);
|
|
}
|
|
|
|
bool ON_ViewportFromRhinoView(
|
|
ON::view_projection projection,
|
|
const ON_3dPoint& rhvp_target, // 3d point
|
|
double rhvp_angle1, double rhvp_angle2, double rhvp_angle3, // radians
|
|
double rhvp_viewsize, // > 0
|
|
double rhvp_cameradist, // > 0
|
|
int screen_width, int screen_height,
|
|
ON_Viewport& vp
|
|
)
|
|
/*****************************************************************************
|
|
Compute canonical view projection information from Rhino viewport settings
|
|
INPUT:
|
|
projection
|
|
rhvp_target
|
|
Rhino viewport target point (3d point that is center of view rotations)
|
|
rhvp_angle1, rhvp_angle2, rhvp_angle3
|
|
Rhino viewport angle settings
|
|
rhvp_viewsize
|
|
In perspective, rhvp_viewsize = tangent(half lens angle).
|
|
In parallel, rhvp_viewsize = 1/2 * minimum(frustum width,frustum height)
|
|
rhvp_cameradistance ( > 0 )
|
|
Distance from camera location to Rhino's "target" point
|
|
screen_width, screen_height (0,0) if not known
|
|
*****************************************************************************/
|
|
{
|
|
vp.SetProjection( projection );
|
|
/*
|
|
width, height
|
|
width and height of viewport
|
|
( = RhinoViewport->width, RhinoViewport->height )
|
|
z_buffer_depth
|
|
depth for the z buffer. 0xFFFF is currently used for Rhino
|
|
quick rendering.
|
|
*/
|
|
|
|
// In the situation where there is no physical display device, assume a
|
|
// 1000 x 1000 "screen" and set the parameters accordingly. Toolkit users
|
|
// that are using this class to actually draw a picture, can make a subsequent
|
|
// call to SetScreenPort().
|
|
|
|
const double height = (screen_width < 1 || screen_height < 1)
|
|
? 1000.0 : (double)screen_height;
|
|
const double width = (screen_width < 1 || screen_height < 1)
|
|
? 1000.0 : (double)screen_width;
|
|
//const int z_buffer_depth = 0xFFFF; // value Rhino "Shade" command uses
|
|
|
|
// Use this function to obtain standard view information from a Rhino VIEWPORT
|
|
// view. The Rhino viewport has many entries. As of 17 October, 1997 all Rhino
|
|
// world to clipping transformation information is derived from the VIEWPORT
|
|
// fields:
|
|
//
|
|
// target, angle1, angle2, angle3, viewsize, and cameradist.
|
|
//
|
|
// The width, height and zbuffer_depth arguments are used to determine the
|
|
// clipping to screen transformation.
|
|
|
|
ON_Xform R1, R2, R3, RhinoRot;
|
|
double frustum_left, frustum_right, frustum_bottom, frustum_top;
|
|
double near_clipping_distance, far_clipping_distance;
|
|
|
|
// Initialize default view in case input is garbage.
|
|
if (height < 1)
|
|
return false;
|
|
if ( width < 1 )
|
|
return false;
|
|
if ( rhvp_viewsize <= 0.0 )
|
|
return false;
|
|
if ( rhvp_cameradist <= 0.0 )
|
|
return false;
|
|
|
|
// A Rhino 1.0 VIEWPORT structure describes the camera's location, direction,
|
|
// and orientation by specifying a rotation transformation that is
|
|
// applied to an initial frame. The rotation transformation is defined
|
|
// as a sequence of 3 rotations abount fixed axes. The initial frame
|
|
// has the camera located at (0,0,cameradist), pointed in the direction
|
|
// (0,0,-1), and oriented so that up is (0,1,0).
|
|
|
|
R1.Rotation( rhvp_angle1, ON_3dVector::ZAxis, ON_3dPoint::Origin ); // so called "twist"
|
|
R2.Rotation( rhvp_angle2, ON_3dVector::XAxis, ON_3dPoint::Origin ); // so called "elevation"
|
|
R3.Rotation( rhvp_angle3, ON_3dVector::ZAxis, ON_3dPoint::Origin ); // so called "fudge factor"
|
|
RhinoRot = R3 * R2 * R1;
|
|
|
|
vp.SetCameraUp( RhinoRot*ON_3dVector::YAxis );
|
|
vp.SetCameraDirection( -(RhinoRot*ON_3dVector::ZAxis) );
|
|
vp.SetCameraLocation( rhvp_target - rhvp_cameradist*vp.CameraDirection() );
|
|
vp.SetTargetPoint( rhvp_target );
|
|
//vp.SetTargetDistance( rhvp_cameradist );
|
|
|
|
// Camera coordinates "X" = CameraRight = CameraDirection x CameraUp
|
|
// Camera coordinates "Y" = CameraUp
|
|
// Camera coordinates "Z" = -CameraDirection
|
|
|
|
// Rhino 1.0 did not support skew projections. In other words, the
|
|
// view frustum is symmetric and ray that begins at CameraLocation and
|
|
// goes along CameraDirection runs along the frustum's central axis.
|
|
// The aspect ratio of the view frustum equals
|
|
// (screen port width)/(screen port height)
|
|
// This means frus_left = -frus_right, frus_bottom = -frus_top, and
|
|
// frus_top/frus_right = height/width
|
|
|
|
// Set near and far clipping planes to some reasonable values. If
|
|
// the depth of the pixel is important, then the near and far clipping
|
|
// plane will need to be adjusted later.
|
|
// Rhino 1.0 didn't have a far clipping plane in wire frame (which explains
|
|
// why you can get perspective views reversed through the origin by using
|
|
// the SetCameraTarget() command. It's near clipping plane is set to
|
|
// a minuscule value. For mesh rendering, it must come up with some
|
|
// sort of reasonable near and far clipping planes because the zbuffer
|
|
// is used correctly. When time permits, I'll dig through the rendering
|
|
// code and determine what values are being used.
|
|
//
|
|
near_clipping_distance = rhvp_cameradist/64.0;
|
|
if ( near_clipping_distance > 1.0 )
|
|
near_clipping_distance = 1.0;
|
|
far_clipping_distance = 4.0*rhvp_cameradist;
|
|
|
|
|
|
if ( width <= height )
|
|
{
|
|
frustum_right = rhvp_viewsize;
|
|
frustum_top = frustum_right*height/width;
|
|
}
|
|
else
|
|
{
|
|
frustum_top = rhvp_viewsize;
|
|
frustum_right = frustum_top*width/height;
|
|
}
|
|
if ( vp.IsPerspectiveProjection() )
|
|
{
|
|
frustum_right *= near_clipping_distance;
|
|
frustum_top *= near_clipping_distance;
|
|
}
|
|
frustum_left = -frustum_right;
|
|
frustum_bottom = -frustum_top;
|
|
|
|
|
|
vp.SetFrustum(
|
|
frustum_left, frustum_right,
|
|
frustum_bottom, frustum_top,
|
|
near_clipping_distance, far_clipping_distance );
|
|
|
|
// Windows specific stuff that requires knowing size of client area in pixels
|
|
vp.SetScreenPort( 0, (int)width, // windows has screen X increasing accross
|
|
(int)height, 0, // windows has screen Y increasing downwards
|
|
0, 0xFFFF );
|
|
|
|
return (vp.IsValid()?true:false);
|
|
}
|
|
|
|
bool ON_Viewport::GetCameraAngle(
|
|
double* angle,
|
|
double* angle_h,
|
|
double* angle_w
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
if ( angle )
|
|
*angle = 0.0;
|
|
if ( angle_h )
|
|
*angle_h = 0.0;
|
|
if ( angle_w )
|
|
*angle_w = 0.0;
|
|
double half_w, half_h, left, right, bot, top, near_dist;
|
|
if ( GetFrustum( &left, &right, &bot, &top, &near_dist, nullptr ) )
|
|
{
|
|
half_w = ( right > -left ) ? right : -left;
|
|
half_h = ( top > -bot ) ? top : -bot;
|
|
if ( near_dist > 0.0 && ON_IsValid(near_dist) )
|
|
{
|
|
if ( angle )
|
|
*angle = atan( sqrt(half_w*half_w + half_h*half_h)/near_dist );
|
|
if ( angle_h )
|
|
*angle_h = atan( half_h/near_dist );
|
|
if ( angle_w )
|
|
*angle_w = atan( half_w/near_dist );
|
|
}
|
|
rc = true;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetCameraAngle(
|
|
double* angle
|
|
) const
|
|
{
|
|
double angle_h = 0.0;
|
|
double angle_w = 0.0;
|
|
bool rc = GetCameraAngle( nullptr, &angle_h, &angle_w );
|
|
if ( angle && rc ) {
|
|
*angle = (angle_h < angle_w) ? angle_h : angle_w;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::SetCameraAngle( double angle )
|
|
{
|
|
bool rc = false;
|
|
double r, d, aspect, half_w, half_h, near_dist, far_dist;
|
|
if ( angle > 0.0 && angle < 0.5*ON_PI*(1.0-ON_SQRT_EPSILON) ) {
|
|
if ( GetFrustum( nullptr, nullptr, nullptr, nullptr, &near_dist, &far_dist ) && GetFrustumAspect( aspect) ) {
|
|
r = near_dist*tan(angle);
|
|
// d = r/sqrt(1.0+aspect*aspect); // if angle is 1/2 diagonal angle
|
|
d = r; // angle is 1/2 smallest angle
|
|
if ( aspect >= 1.0 ) {
|
|
// width >= height
|
|
half_w = d*aspect;
|
|
half_h = d;
|
|
}
|
|
else {
|
|
// height > width
|
|
half_w = d;
|
|
half_h = d/aspect;
|
|
}
|
|
rc = SetFrustum( -half_w, half_w, -half_h, half_h, near_dist, far_dist );
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
// This version of the function has "lens" misspelled.
|
|
bool ON_Viewport::GetCamera35mmLenseLength( double* lens_length ) const
|
|
{
|
|
return GetCamera35mmLensLength( lens_length );
|
|
}
|
|
|
|
bool ON_Viewport::GetCamera35mmLensLength( double* lens_length ) const
|
|
{
|
|
// 35 mm film has a height of 24 mm and a width of 36 mm
|
|
double film_r, view_r, half_w, half_h;
|
|
double frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
|
|
if ( !lens_length )
|
|
return false;
|
|
*lens_length = 0.0;
|
|
if ( !GetFrustum( &frus_left, &frus_right, &frus_bottom, &frus_top,
|
|
&frus_near, &frus_far ) )
|
|
return false;
|
|
if ( frus_near <= 0.0 )
|
|
return false;
|
|
half_w = ( frus_right > -frus_left ) ? frus_right : -frus_left;
|
|
half_h = ( frus_top > -frus_bottom ) ? frus_top : -frus_bottom;
|
|
|
|
// 2009 May 8 Dale Lear - always use width in two point perspective
|
|
view_r = (half_w <= half_h || IsTwoPointPerspectiveProjection()) ? half_w : half_h;
|
|
film_r = 12.0;
|
|
if ( view_r <= 0.0 )
|
|
return false;
|
|
|
|
*lens_length = frus_near*film_r/view_r;
|
|
return true;
|
|
}
|
|
|
|
// This version of the function has "lens" misspelled.
|
|
bool ON_Viewport::SetCamera35mmLenseLength( double lens_length )
|
|
{
|
|
return SetCamera35mmLensLength( lens_length );
|
|
}
|
|
|
|
bool ON_Viewport::SetCamera35mmLensLength( double lens_length )
|
|
{
|
|
// 35 mm film has a height of 24 mm and a width of 36 mm
|
|
double film_r, view_r, half_w, half_h, s;
|
|
double frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
|
|
if ( !ON_IsValid(lens_length) || lens_length <= 0.0 )
|
|
return false;
|
|
if ( !GetFrustum( &frus_left, &frus_right, &frus_bottom, &frus_top,
|
|
&frus_near, &frus_far ) )
|
|
return false;
|
|
if ( frus_near <= 0.0 )
|
|
return false;
|
|
half_w = ( frus_right > -frus_left ) ? frus_right : -frus_left;
|
|
half_h = ( frus_top > -frus_bottom ) ? frus_top : -frus_bottom;
|
|
|
|
// 2009 May 8 Dale Lear - always use width in two point perspective
|
|
view_r = (half_w <= half_h || IsTwoPointPerspectiveProjection()) ? half_w : half_h;
|
|
film_r = 12.0;
|
|
if ( view_r <= 0.0 )
|
|
return false;
|
|
|
|
s = (film_r/view_r)*(frus_near/lens_length);
|
|
if ( fabs(s-1.0) < 1.0e-6 )
|
|
return true;
|
|
|
|
frus_left *= s;
|
|
frus_right *= s;
|
|
frus_bottom *= s;
|
|
frus_top *= s;
|
|
return SetFrustum( frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far );
|
|
}
|
|
|
|
bool ON_Viewport::GetXform(
|
|
ON::coordinate_system srcCS,
|
|
ON::coordinate_system destCS,
|
|
ON_Xform& xform
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
ON_Xform x0, x1;
|
|
|
|
xform = ON_Xform::IdentityTransformation;
|
|
|
|
switch( srcCS )
|
|
{
|
|
case ON::world_cs:
|
|
case ON::camera_cs:
|
|
case ON::clip_cs:
|
|
case ON::screen_cs:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
switch( destCS )
|
|
{
|
|
case ON::world_cs:
|
|
case ON::camera_cs:
|
|
case ON::clip_cs:
|
|
case ON::screen_cs:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (srcCS == destCS)
|
|
return true;
|
|
|
|
|
|
switch ( srcCS )
|
|
{
|
|
|
|
case ON::world_cs:
|
|
if ( !m_bValidCamera )
|
|
break;
|
|
|
|
switch ( destCS )
|
|
{
|
|
case ON::camera_cs:
|
|
xform.WorldToCamera( m_CamLoc, m_CamX, m_CamY, m_CamZ );
|
|
rc = true;
|
|
break;
|
|
|
|
case ON::clip_cs:
|
|
rc = GetXform( ON::world_cs, ON::camera_cs, x0 );
|
|
if (rc)
|
|
rc = GetXform( ON::camera_cs, ON::clip_cs, x1 );
|
|
if (rc)
|
|
xform = x1*x0;
|
|
break;
|
|
|
|
case ON::screen_cs:
|
|
rc = GetXform( ON::world_cs, ON::clip_cs, x0 );
|
|
if (rc)
|
|
rc = GetXform( ON::clip_cs, ON::screen_cs, x1 );
|
|
if (rc)
|
|
xform = x1*x0;
|
|
break;
|
|
|
|
case ON::world_cs:
|
|
// Never happens. This is here to quiet g++ warnings.
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ON::camera_cs:
|
|
if ( !m_bValidCamera )
|
|
break;
|
|
|
|
switch ( destCS )
|
|
{
|
|
case ON::world_cs:
|
|
xform.CameraToWorld( m_CamLoc, m_CamX, m_CamY, m_CamZ );
|
|
rc = true;
|
|
break;
|
|
|
|
case ON::clip_cs:
|
|
if ( m_bValidFrustum )
|
|
{
|
|
ON_Xform cam2clip;
|
|
cam2clip.CameraToClip(
|
|
IsPerspectiveProjection(),
|
|
m_frus_left, m_frus_right,
|
|
m_frus_bottom, m_frus_top,
|
|
m_frus_near, m_frus_far );
|
|
xform = m_clip_mods*cam2clip;
|
|
rc = true;
|
|
}
|
|
break;
|
|
|
|
case ON::screen_cs:
|
|
rc = GetXform( ON::camera_cs, ON::clip_cs, x0 );
|
|
if (rc)
|
|
rc = GetXform( ON::clip_cs, ON::screen_cs, x1 );
|
|
if (rc)
|
|
xform = x1*x0;
|
|
break;
|
|
|
|
case ON::camera_cs:
|
|
// Never happens. This is here to quiet g++ warnings.
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ON::clip_cs:
|
|
switch ( destCS )
|
|
{
|
|
case ON::world_cs:
|
|
rc = GetXform( ON::clip_cs, ON::camera_cs, x0 );
|
|
if (rc)
|
|
rc = GetXform( ON::camera_cs, ON::world_cs, x1 );
|
|
if (rc)
|
|
xform = x1*x0;
|
|
break;
|
|
|
|
case ON::camera_cs:
|
|
if ( m_bValidFrustum )
|
|
{
|
|
ON_Xform clip2cam;
|
|
clip2cam.ClipToCamera(
|
|
IsPerspectiveProjection(),
|
|
m_frus_left, m_frus_right,
|
|
m_frus_bottom, m_frus_top,
|
|
m_frus_near, m_frus_far );
|
|
xform = clip2cam*m_clip_mods_inverse;
|
|
rc = true;
|
|
}
|
|
break;
|
|
|
|
case ON::screen_cs:
|
|
if ( m_bValidPort )
|
|
{
|
|
xform.ClipToScreen(
|
|
m_port_left, m_port_right,
|
|
m_port_bottom, m_port_top,
|
|
m_port_near, m_port_far );
|
|
rc = true;
|
|
}
|
|
break;
|
|
|
|
case ON::clip_cs:
|
|
// Never happens. This is here to quiet g++ warnings.
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ON::screen_cs:
|
|
switch ( destCS )
|
|
{
|
|
case ON::world_cs:
|
|
rc = GetXform( ON::screen_cs, ON::camera_cs, x0 );
|
|
if (rc)
|
|
rc = GetXform( ON::camera_cs, ON::world_cs, x1 );
|
|
if (rc)
|
|
xform = x1*x0;
|
|
break;
|
|
case ON::camera_cs:
|
|
rc = GetXform( ON::screen_cs, ON::clip_cs, x0 );
|
|
if (rc)
|
|
rc = GetXform( ON::clip_cs, ON::camera_cs, x1 );
|
|
if (rc)
|
|
xform = x1*x0;
|
|
break;
|
|
case ON::clip_cs:
|
|
if ( m_bValidPort ) {
|
|
xform.ScreenToClip(
|
|
m_port_left, m_port_right,
|
|
m_port_bottom, m_port_top,
|
|
m_port_near, m_port_far );
|
|
rc = true;
|
|
}
|
|
break;
|
|
case ON::screen_cs:
|
|
// Never happens. This is here to quiet g++ warnings.
|
|
break;
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetFrustumLine( double screenx, double screeny, ON_Line& world_line ) const
|
|
{
|
|
ON_Xform s2c, c2w;
|
|
ON_3dPoint c;
|
|
ON_Line line;
|
|
bool rc;
|
|
|
|
rc = GetXform( ON::screen_cs, ON::clip_cs, s2c );
|
|
if ( rc )
|
|
rc = GetXform( ON::clip_cs, ON::world_cs, c2w );
|
|
if (rc )
|
|
{
|
|
// c = mouse point on near clipping plane
|
|
c.x = s2c.m_xform[0][0]*screenx + s2c.m_xform[0][1]*screeny + s2c.m_xform[0][3];
|
|
c.y = s2c.m_xform[1][0]*screenx + s2c.m_xform[1][1]*screeny + s2c.m_xform[1][3];
|
|
c.z = 1.0;
|
|
line.to = c2w*c; // line.to = near plane mouse point in world coords
|
|
c.z = -1.0;
|
|
line.from = c2w*c; // line.from = far plane mouse point in world coords
|
|
|
|
world_line = line;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static double clipDist( const double* camLoc, const double* camZ, const double* P )
|
|
{
|
|
return (camLoc[0]-P[0])*camZ[0]+(camLoc[1]-P[1])*camZ[1]+(camLoc[2]-P[2])*camZ[2];
|
|
}
|
|
|
|
|
|
bool ON_Viewport::SetFrustumNearFar(
|
|
const double* box_min,
|
|
const double* box_max
|
|
)
|
|
{
|
|
bool rc = false;
|
|
const double* box[2];
|
|
int i,j,k;
|
|
double n, f, d;
|
|
double camLoc[3], camZ[3], P[3];
|
|
|
|
if ( !box_min )
|
|
box_min = box_max;
|
|
if ( !box_max )
|
|
box_max = box_min;
|
|
if ( !box_min )
|
|
return false;
|
|
|
|
// 31 May 2007 Dale Lear RR 25980
|
|
// Add validation of box_min and box_max.
|
|
if ( !ON_IsValid(box_min[0]) || !ON_IsValid(box_min[1]) || !ON_IsValid(box_min[2]) )
|
|
return false;
|
|
if ( !ON_IsValid(box_max[0]) || !ON_IsValid(box_max[1]) || !ON_IsValid(box_max[2]) )
|
|
return false;
|
|
if ( box_min[0] > box_max[0]
|
|
|| box_min[1] > box_max[1]
|
|
|| box_min[2] > box_max[2]
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
box[0] = box_min;
|
|
box[1] = box_max;
|
|
|
|
if ( GetCameraFrame( camLoc, nullptr, nullptr, camZ ) ) {
|
|
n = f = -1.0;
|
|
for(i=0;i<2;i++)for(j=0;j<2;j++)for(k=0;k<2;k++) {
|
|
P[0] = box[i][0];
|
|
P[1] = box[j][1];
|
|
P[2] = box[k][2];
|
|
d = clipDist(camLoc,camZ,P);
|
|
if (!i&&!j&&!k)
|
|
n=f=d;
|
|
else if ( d < n )
|
|
n = d;
|
|
else if ( d > f )
|
|
f = d;
|
|
}
|
|
if ( !ON_IsValid(f) || !ON_IsValid(n) )
|
|
return false;
|
|
if ( f <= 0.0 )
|
|
return false; // box is behind camera
|
|
n *= 0.9375;
|
|
f *= 1.0625;
|
|
if ( n <= 0.0 )
|
|
n = m__MIN_NEAR_OVER_FAR*f;
|
|
if ( IsPerspectiveProjection() )
|
|
rc = SetFrustumNearFar( n, f, m__MIN_NEAR_DIST, m__MIN_NEAR_OVER_FAR, 0.5*(n+f) );
|
|
else
|
|
rc = SetFrustumNearFar( n, f );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::SetFrustumNearFar(
|
|
const double* center,
|
|
double radius
|
|
)
|
|
{
|
|
bool rc = false;
|
|
double n, f, d;
|
|
double camLoc[3], camZ[3], P[3];
|
|
|
|
if ( !center
|
|
|| !ON_IsValid(center[0])
|
|
|| !ON_IsValid(center[1])
|
|
|| !ON_IsValid(center[2])
|
|
|| !ON_IsValid(radius)
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( GetCameraFrame( camLoc, nullptr, nullptr, camZ ) )
|
|
{
|
|
d = fabs(radius);
|
|
P[0] = center[0] + d*camZ[0];
|
|
P[1] = center[1] + d*camZ[0];
|
|
P[2] = center[2] + d*camZ[0];
|
|
n = clipDist(camLoc,camZ,P);
|
|
P[0] = center[0] - d*camZ[0];
|
|
P[1] = center[1] - d*camZ[0];
|
|
P[2] = center[2] - d*camZ[0];
|
|
f = clipDist(camLoc,camZ,P);
|
|
if ( !ON_IsValid(f) || !ON_IsValid(n) )
|
|
return false;
|
|
if ( f <= 0.0 )
|
|
return false; // sphere is behind camera
|
|
n *= 0.9375;
|
|
f *= 1.0625;
|
|
if ( n <= 0.0 )
|
|
n = m__MIN_NEAR_OVER_FAR*f;
|
|
if ( IsPerspectiveProjection() )
|
|
rc = SetFrustumNearFar( n, f, m__MIN_NEAR_DIST, m__MIN_NEAR_OVER_FAR, 0.5*(n+f) );
|
|
else
|
|
rc = SetFrustumNearFar( n, f );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::SetFrustumNearFar( double n, double f )
|
|
{
|
|
// This is a bare bones setter.
|
|
// Except for the 0 < n < f < ON_NONSENSE_WORLD_COORDINATE_VALUE
|
|
// requirement, do not add checking here.
|
|
//
|
|
// Use the ON_Viewport::SetFrustumNearFar( near_dist,
|
|
// far_dist,
|
|
// min_near_dist,
|
|
// min_near_over_far,
|
|
// target_dist );
|
|
//
|
|
// version if you need lots of validation and automatic fixing.
|
|
|
|
double d, frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
|
|
bool rc = false;
|
|
if ( ON_IsValid(n) && ON_IsValid(f) && n > 0.0 && f > n && f < ON_NONSENSE_WORLD_COORDINATE_VALUE )
|
|
{
|
|
if ( GetFrustum( &frus_left, &frus_right,
|
|
&frus_bottom, &frus_top,
|
|
&frus_near, &frus_far ) )
|
|
{
|
|
// preserve valid frustum
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
d = n/frus_near;
|
|
frus_left *= d;
|
|
frus_right *= d;
|
|
frus_bottom *= d;
|
|
frus_top *= d;
|
|
}
|
|
frus_near = n;
|
|
frus_far = f;
|
|
rc = SetFrustum( frus_left, frus_right,
|
|
frus_bottom, frus_top,
|
|
frus_near, frus_far );
|
|
}
|
|
else
|
|
{
|
|
if ( IsPerspectiveProjection() && (n <= 1.0e-8 || f > 1.0001e8*n) )
|
|
{
|
|
ON_ERROR("ON_Viewport::SetFrustum - bogus perspective m_frus_near/far values - will crash MS OpenGL");
|
|
}
|
|
m_frus_near = n;
|
|
m_frus_far = f;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
rc = true;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::ChangeToSymmetricFrustum(
|
|
bool bLeftRightSymmetric,
|
|
bool bTopBottomSymmetric,
|
|
double target_distance
|
|
)
|
|
{
|
|
if ( bLeftRightSymmetric && m_frus_left == -m_frus_right )
|
|
bLeftRightSymmetric = false; // no left/right changes required.
|
|
|
|
if ( bTopBottomSymmetric && m_frus_bottom == -m_frus_top )
|
|
bTopBottomSymmetric = false; // no top/bottom changes required.
|
|
|
|
if ( !bLeftRightSymmetric && !bTopBottomSymmetric )
|
|
return true; // no changes required
|
|
|
|
if ( !m_bValidFrustum )
|
|
return false;
|
|
|
|
const double half_w = 0.5*(m_frus_right-m_frus_left);
|
|
const double half_h = 0.5*(m_frus_top-m_frus_bottom);
|
|
double dx = bLeftRightSymmetric ? (m_frus_right - half_w) : 0.0;
|
|
double dy = bTopBottomSymmetric ? (m_frus_top - half_h) : 0.0;
|
|
if ( bLeftRightSymmetric )
|
|
{
|
|
m_frus_right = half_w;
|
|
m_frus_left = -m_frus_right;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
}
|
|
if ( bTopBottomSymmetric )
|
|
{
|
|
m_frus_top = half_h;
|
|
m_frus_bottom = -m_frus_top;
|
|
m_projection_content_sha1 = ON_SHA1_Hash::ZeroDigest;
|
|
}
|
|
|
|
// if possible, dolly the camera so the original
|
|
// target plane is still visible.
|
|
if ( m_bValidCamera && (dx != 0.0 || dy != 0.0 ) )
|
|
{
|
|
if ( ON::perspective_view == m_projection )
|
|
{
|
|
if ( m_frus_near > 0.0 )
|
|
{
|
|
if ( ON_UNSET_VALUE == target_distance )
|
|
target_distance = TargetDistance(true);
|
|
if ( ON_IsValid(target_distance) && target_distance > 0.0 )
|
|
{
|
|
double s = target_distance/m_frus_near;
|
|
dx *= s;
|
|
dy *= s;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dx=dy = 0.0;
|
|
}
|
|
}
|
|
if ( dx != 0.0 || dy != 0.0 )
|
|
{
|
|
ON_3dPoint cam_loc = m_CamLoc + dx*m_CamX + dy*m_CamY;
|
|
SetCameraLocation(cam_loc);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetWorldToScreenScale(
|
|
ON_3dPoint world_point,
|
|
double* scale
|
|
) const
|
|
{
|
|
double frustum_depth = ON_UNSET_VALUE;
|
|
|
|
if (nullptr != scale)
|
|
*scale = 0.0;
|
|
|
|
if (IsPerspectiveProjection() && world_point.IsValid())
|
|
{
|
|
ON_3dPoint CamLoc;
|
|
ON_3dVector CamZ;
|
|
if (!GetCameraFrame(CamLoc, nullptr, nullptr, CamZ) || !(m_frus_near > 0.0))
|
|
return false;
|
|
frustum_depth = CamZ*(CamLoc - world_point);
|
|
if (!(frustum_depth > 0.0))
|
|
frustum_depth = ON_UNSET_VALUE;
|
|
}
|
|
|
|
return GetWorldToScreenScale(frustum_depth, scale);
|
|
}
|
|
|
|
|
|
|
|
bool ON_Viewport::GetWorldToScreenScale(
|
|
double frustum_depth,
|
|
double* scale
|
|
) const
|
|
{
|
|
if (nullptr != scale)
|
|
*scale = 0.0;
|
|
|
|
if (false == m_bValidFrustum)
|
|
return false;
|
|
|
|
if (false == m_bValidPort)
|
|
return false;
|
|
|
|
double s = 1.0;
|
|
|
|
if (IsPerspectiveProjection() && ON_IsValid(frustum_depth) && frustum_depth > 0.0)
|
|
{
|
|
if (!(m_frus_near > 0.0))
|
|
return false;
|
|
s = frustum_depth / m_frus_near;
|
|
if (!(s >= 0.0) && ON_IS_FINITE(s))
|
|
return false;
|
|
}
|
|
|
|
double x = 1.0;
|
|
GetViewScale(&x, nullptr);
|
|
if (x != 0.0 && 1.0 != x)
|
|
s /= fabs(x);
|
|
|
|
double fw = fabs(FrustumWidth());
|
|
if (!(fw > 0.0))
|
|
return false;
|
|
|
|
fw *= s;
|
|
|
|
double sw = fabs((double)ScreenPortWidth());
|
|
if (!(sw > 0.0))
|
|
return false;
|
|
|
|
s = sw / fw;
|
|
if (!(s > 0.0) && ON_IS_FINITE(s))
|
|
return false;
|
|
|
|
if (nullptr != scale)
|
|
*scale = s;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetCoordinateSprite(
|
|
int size,
|
|
int scrx, int scry,
|
|
int indx[3], // axis order by depth
|
|
double scr_coord[3][2] ) const
|
|
{
|
|
// size = length of axes in pixels
|
|
|
|
indx[0] = 0; indx[1] = 1; indx[2] = 2;
|
|
scr_coord[0][0] = scr_coord[1][0] = scr_coord[2][0] = scrx;
|
|
scr_coord[0][1] = scr_coord[1][1] = scr_coord[2][1] = scry;
|
|
|
|
ON_3dPoint C, XP, YP, ZP, ScrC, ScrXP;
|
|
ON_3dVector X, Z, Scr[3];
|
|
ON_Xform w2s;
|
|
if (!GetFrustumCenter( C ) )
|
|
return false;
|
|
if (!GetCameraFrame( nullptr, X, nullptr, Z ))
|
|
return false;
|
|
if (!GetXform( ON::world_cs, ON::screen_cs, w2s ))
|
|
return false;
|
|
|
|
// indx[] determines order that axes are drawn
|
|
// sorted from back to front
|
|
int i,j,k;
|
|
for (i = 0; i < 2; i++) for (j = i+1; j <= 2; j++) {
|
|
if (Z[indx[i]] > Z[indx[j]])
|
|
{k = indx[i]; indx[i] = indx[j]; indx[j] = k;}
|
|
}
|
|
|
|
// determine world length that corresponds to size pixels
|
|
XP = C+X;
|
|
ScrC = w2s*C;
|
|
ScrXP = w2s*XP;
|
|
if (ScrC.x == ScrXP.x)
|
|
return false;
|
|
double s = size/fabs( ScrC.x - ScrXP.x );
|
|
|
|
// transform world coord axes to screen
|
|
XP = C;
|
|
XP.x += s;
|
|
YP = C;
|
|
YP.y += s;
|
|
ZP = C;
|
|
ZP.z += s;
|
|
Scr[0] = w2s*XP;
|
|
Scr[1] = w2s*YP;
|
|
Scr[2] = w2s*ZP;
|
|
|
|
double dx = scrx - ScrC.x;
|
|
double dy = scry - ScrC.y;
|
|
for (i=0;i<3;i++) {
|
|
scr_coord[i][0] = dx + Scr[i].x;
|
|
scr_coord[i][1] = dy + Scr[i].y;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool GetRelativeScreenCoordinates(
|
|
int port_left, int port_right,
|
|
int port_bottom, int port_top,
|
|
bool bSortPoints,
|
|
int& x0, int& y0, int& x1, int& y1,
|
|
double& s0, double& t0, double& s1, double& t1
|
|
)
|
|
{
|
|
// convert screen rectangle into relative rectangle
|
|
if ( bSortPoints ) {
|
|
int i;
|
|
if ( x0 > x1 ) {
|
|
i = x0; x0 = x1; x1 = i;
|
|
}
|
|
if ( port_left > port_right ) {
|
|
i = x0; x0 = x1; x1 = i;
|
|
}
|
|
if ( y0 > y1 ) {
|
|
i = y0; y0 = y1; y1 = i;
|
|
}
|
|
if ( port_bottom > port_top ) {
|
|
i = y0; y0 = y1; y1 = i;
|
|
}
|
|
}
|
|
|
|
s0 = ((double)(x0 - port_left))/((double)(port_right - port_left));
|
|
s1 = ((double)(x1 - port_left))/((double)(port_right - port_left));
|
|
t0 = ((double)(y0 - port_bottom))/((double)(port_top - port_bottom));
|
|
t1 = ((double)(y1 - port_bottom))/((double)(port_top - port_bottom));
|
|
|
|
double tol = 0.001;
|
|
if ( fabs(s0) <= tol ) s0 = 0.0; else if (fabs(s0-1.0) <= tol ) s0 = 1.0;
|
|
if ( fabs(s1) <= tol ) s1 = 0.0; else if (fabs(s1-1.0) <= tol ) s1 = 1.0;
|
|
if ( fabs(t0) <= tol ) t0 = 0.0; else if (fabs(t0-1.0) <= tol ) t0 = 1.0;
|
|
if ( fabs(t1) <= tol ) t1 = 0.0; else if (fabs(t1-1.0) <= tol ) t1 = 1.0;
|
|
if ( fabs(s0-s1) <= tol )
|
|
return false;
|
|
if ( fabs(t0-t1) <= tol )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool ON_Viewport::ZoomToScreenRect( int x0, int y0, int x1, int y1 )
|
|
{
|
|
int port_left, port_right, port_bottom, port_top, port_near, port_far;
|
|
if ( !GetScreenPort( &port_left, &port_right,
|
|
&port_bottom, &port_top,
|
|
&port_near, &port_far ) )
|
|
return false;
|
|
|
|
// dolly camera sideways so it's looking at center of rectangle
|
|
int dx = (x0+x1)/2;
|
|
int dy = (y0+y1)/2;
|
|
int cx = (port_left+port_right)/2;
|
|
int cy = (port_bottom+port_top)/2;
|
|
|
|
ON_3dVector dolly_vector;
|
|
if ( !GetDollyCameraVector( dx, dy, cx, cy, 0.5*(FrustumNear()+FrustumFar()), dolly_vector ) )
|
|
return false;
|
|
if ( !DollyCamera( dolly_vector ) )
|
|
return false;
|
|
|
|
// adjust frustum
|
|
dx = cx - dx;
|
|
dy = cy - dy;
|
|
x0 += dx;
|
|
x1 += dx;
|
|
y0 += dy;
|
|
y1 += dy;
|
|
double frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
|
|
if ( !GetFrustum( &frus_left, &frus_right,
|
|
&frus_bottom, &frus_top,
|
|
&frus_near, &frus_far ) )
|
|
return false;
|
|
double s0,t0,s1,t1;
|
|
if ( !GetRelativeScreenCoordinates(port_left, port_right, port_bottom, port_top,
|
|
true,
|
|
x0,y0,x1,y1,
|
|
s0,t0,s1,t1) )
|
|
return false;
|
|
double w = frus_right - frus_left;
|
|
double h = frus_top - frus_bottom;
|
|
double a0 = (1.0-s0)*frus_left + s0*frus_right;
|
|
double a1 = (1.0-s1)*frus_left + s1*frus_right;
|
|
double b0 = (1.0-t0)*frus_bottom + t0*frus_top;
|
|
double b1 = (1.0-t1)*frus_bottom + t1*frus_top;
|
|
if ( -a0 > a1 ) a1 = -a0; else a0 = -a1;
|
|
if ( -b0 > b1 ) b1 = -b0; else b0 = -b1;
|
|
double d;
|
|
if ( (b1-b0)*w < (a1-a0)*h ) {
|
|
d = (a1-a0)*h/w;
|
|
d = 0.5*(d - (b1-b0));
|
|
b0 -= d;
|
|
b1 += d;
|
|
}
|
|
else {
|
|
d = (b1-b0)*w/h;
|
|
d = 0.5*(d - (a1-a0));
|
|
a0 -= d;
|
|
a1 += d;
|
|
}
|
|
|
|
return SetFrustum( a0, a1, b0, b1, frus_near, frus_far );
|
|
}
|
|
|
|
/*
|
|
bool ON_Viewport::DollyToScreenRect( double view_plane_distance,
|
|
int x0, int y0, int x1, int y1 )
|
|
{
|
|
// Only makes sense in a perspective projection. In a parallel projection,
|
|
// I resort to ZoomToScreenRect(0 and the visual result is the same.
|
|
if ( !IsPerspectiveProjection() )
|
|
return ZoomToScreenRect( x0, y0, x1, y1 );
|
|
|
|
int port_left, port_right, port_bottom, port_top;
|
|
if ( !GetScreenPort( &port_left, &port_right, &port_bottom, &port_top, nullptr, nullptr ) )
|
|
return false;
|
|
int dx = (x0+x1)/2;
|
|
int dy = (y0+y1)/2;
|
|
int cx = (port_left+port_right)/2;
|
|
int cy = (port_bottom+port_top)/2;
|
|
if ( !DollyAlongScreenChord( dx, dy, cx, cy ) )
|
|
return false;
|
|
dx = cx - dx;
|
|
dy = cy - dy;
|
|
x0 += dx;
|
|
x1 += dx;
|
|
y0 += dy;
|
|
y1 += dy;
|
|
|
|
double frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
|
|
if ( !GetFrustum( &frus_left, &frus_right,
|
|
&frus_bottom, &frus_top,
|
|
&frus_near, &frus_far ) )
|
|
return false;
|
|
|
|
double s0, t0, s1, t1;
|
|
if ( !GetRelativeScreenCoordinates(port_left, port_right, port_bottom, port_top,
|
|
true,
|
|
x0,y0,x1,y1,
|
|
s0,t0,s1,t1) )
|
|
return false;
|
|
|
|
double w = frus_right - frus_left;
|
|
double h = frus_top - frus_bottom;
|
|
double a0 = (1.0-s0)*frus_left + s0*frus_right;
|
|
double a1 = (1.0-s1)*frus_left + s1*frus_right;
|
|
double b0 = (1.0-t0)*frus_bottom + t0*frus_top;
|
|
double b1 = (1.0-t1)*frus_bottom + t1*frus_top;
|
|
if ( -a0 > a1 ) a1 = -a0; else a0 = -a1;
|
|
if ( -b0 > b1 ) b1 = -b0; else b0 = -b1;
|
|
double d;
|
|
if ( (b1-b0)*w < (a1-a0)*h ) {
|
|
d = (a1-a0)*h/w;
|
|
d = 0.5*(d - h);
|
|
b0 -= d;
|
|
b1 += d;
|
|
}
|
|
else {
|
|
d = (b1-b0)*w/h;
|
|
d = 0.5*(d - w);
|
|
a0 -= d;
|
|
a1 += d;
|
|
}
|
|
|
|
d = 0.5*((a1-a0)/w + (b1-b0)/h)*view_plane_distance;
|
|
double delta = d - view_plane_distance;
|
|
|
|
frus_near += delta;
|
|
frus_far += delta;
|
|
if ( frus_near <= 0.0 ) {
|
|
if ( frus_far <= 0.0 )
|
|
frus_far = 100.0;
|
|
frus_near = 0.001*frus_far;
|
|
}
|
|
if ( !SetFrustumNearFar( frus_near, frus_far ) )
|
|
return false;
|
|
|
|
double camLoc[3], camY[3], camZ[3];
|
|
if ( !GetCameraFrame( camLoc, nullptr, camY, camZ ) )
|
|
return false;
|
|
camLoc[0] += delta*camZ[0];
|
|
camLoc[1] += delta*camZ[1];
|
|
camLoc[2] += delta*camZ[2];
|
|
camZ[0] = -camZ[0];
|
|
camZ[1] = -camZ[1];
|
|
camZ[2] = -camZ[2];
|
|
if ( !SetCamera( camLoc, camZ, camY ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
*/
|
|
|
|
bool ON_Viewport::Extents( double angle, const ON_BoundingBox& bbox )
|
|
{
|
|
double radius;
|
|
double x, y, xmin, xmax, ymin, ymax;
|
|
int i,j,k;
|
|
|
|
if ( !bbox.IsValid() || !IsValid() )
|
|
return false;
|
|
ON_3dVector camX = CameraX();
|
|
ON_3dVector camY = CameraY();
|
|
ON_3dPoint center = bbox.Center();
|
|
xmin=xmax=ymin=ymax=0.0;
|
|
for (i=0;i<2;i++) for (j=0;j<2;j++) for (k=0;k<2;k++) {
|
|
ON_3dVector box_corner = bbox.Corner(i,j,k);
|
|
x = camX*box_corner;
|
|
y = camY*box_corner;
|
|
if ( i==0&&j==0&&k==0) {
|
|
xmin=xmax=x;
|
|
ymin=ymax=y;
|
|
}
|
|
else {
|
|
if ( x > xmax) xmax=x; else if (x < xmin) xmin = x;
|
|
if ( y > ymax) ymax=y; else if (y < ymin) ymin = y;
|
|
}
|
|
}
|
|
radius = xmax-xmin;
|
|
if ( ymax-ymin > radius )
|
|
radius = ymax-ymin;
|
|
if ( radius <= ON_SQRT_EPSILON ) {
|
|
radius = bbox.Diagonal().MaximumCoordinate();
|
|
}
|
|
radius *= 0.5;
|
|
if ( radius <= ON_SQRT_EPSILON )
|
|
radius = 1.0;
|
|
return Extents( angle, center, radius );
|
|
}
|
|
|
|
bool ON_Viewport::Extents( double angle, const ON_3dPoint& center, double radius )
|
|
{
|
|
if ( !IsValid() )
|
|
return false;
|
|
|
|
double target_dist, near_dist, far_dist;
|
|
|
|
if ( radius <= 0.0 ||
|
|
angle <= 0.0 ||
|
|
angle >= 0.5*ON_PI*(1.0-ON_SQRT_EPSILON) )
|
|
return false;
|
|
|
|
target_dist = radius/sin(angle);
|
|
if ( !IsPerspectiveProjection() )
|
|
{
|
|
target_dist += 1.0625*radius;
|
|
}
|
|
near_dist = target_dist - 1.0625*radius;
|
|
if ( near_dist < 0.0625*radius )
|
|
near_dist = 0.0625*radius;
|
|
if ( near_dist < m__MIN_NEAR_DIST )
|
|
near_dist = m__MIN_NEAR_DIST;
|
|
far_dist = target_dist + 1.0625*radius;
|
|
|
|
SetCameraLocation( center + target_dist*CameraZ() );
|
|
if ( !SetFrustumNearFar( near_dist, far_dist ) )
|
|
return false;
|
|
if ( !SetCameraAngle( angle ) )
|
|
return false;
|
|
|
|
return IsValid()?true:false;
|
|
}
|
|
|
|
void ON_Viewport::Dump( ON_TextLog& dump ) const
|
|
{
|
|
dump.Print("ON_Viewport\n");
|
|
dump.PushIndent();
|
|
|
|
dump.Print("Projection: ");
|
|
switch(m_projection)
|
|
{
|
|
case ON::parallel_view:
|
|
dump.Print("parallel\n");
|
|
break;
|
|
case ON::perspective_view:
|
|
dump.Print("perspective\n");
|
|
break;
|
|
default:
|
|
dump.Print("invalid\n");
|
|
break;
|
|
}
|
|
dump.Print("Camera: (m_bValidCamera = %s)\n",(m_bValidCamera?"true":"false"));
|
|
dump.PushIndent();
|
|
dump.Print("Location: "); if ( CameraLocationIsLocked() ) dump.Print("(locked) "); dump.Print(m_CamLoc); dump.Print("\n");
|
|
dump.Print("Direction: "); if ( CameraDirectionIsLocked() ) dump.Print("(locked) "); dump.Print(m_CamDir); dump.Print("\n");
|
|
dump.Print("Up: "); if ( CameraUpIsLocked() ) dump.Print("(locked) "); dump.Print(m_CamUp); dump.Print("\n");
|
|
dump.Print("X: "); dump.Print(m_CamX); dump.Print("\n");
|
|
dump.Print("Y: "); dump.Print(m_CamY); dump.Print("\n");
|
|
dump.Print("Z: "); dump.Print(m_CamZ); dump.Print("\n");
|
|
dump.PopIndent();
|
|
dump.Print("Target Point: "); dump.Print(m_target_point); dump.Print("\n");
|
|
dump.Print("target distance %g\n",TargetDistance(true));
|
|
|
|
double frus_aspect=0.0;
|
|
GetFrustumAspect(frus_aspect);
|
|
dump.Print("Frustum: (m_bValidFrustum = %s)\n",(m_bValidFrustum?"true":"false"));
|
|
dump.PushIndent();
|
|
dump.Print("left/right symmetry locked = %s\n",FrustumIsLeftRightSymmetric()?"true":"false");
|
|
dump.Print("top/bottom symmetry locked = %s\n",FrustumIsTopBottomSymmetric()?"true":"false");
|
|
dump.Print("left: "); dump.Print(m_frus_left); dump.Print("\n");
|
|
dump.Print("right: "); dump.Print(m_frus_right); dump.Print("\n");
|
|
dump.Print("bottom: "); dump.Print(m_frus_bottom); dump.Print("\n");
|
|
dump.Print("top: "); dump.Print(m_frus_top); dump.Print("\n");
|
|
dump.Print("near: "); dump.Print(m_frus_near); dump.Print("\n");
|
|
dump.Print("far: "); dump.Print(m_frus_far); dump.Print("\n");
|
|
dump.Print("aspect (width/height): "); dump.Print(frus_aspect); dump.Print("\n");
|
|
if ( ON::perspective_view == m_projection )
|
|
{
|
|
dump.PushIndent();
|
|
dump.Print("near/far: %g\n",m_frus_near/m_frus_far);
|
|
dump.Print("suggested minimum near: = %g\n",m__MIN_NEAR_DIST);
|
|
dump.Print("suggested minimum near/far: = %g\n",m__MIN_NEAR_OVER_FAR);
|
|
dump.PopIndent();
|
|
}
|
|
dump.PopIndent();
|
|
|
|
double port_aspect=0.0;
|
|
GetScreenPortAspect(port_aspect);
|
|
dump.Print("Port: (m_bValidPort = %s\n",(m_bValidPort?"true":"false"));
|
|
dump.PushIndent();
|
|
dump.Print("left: %d\n",m_port_left);
|
|
dump.Print("right: %d\n",m_port_right);
|
|
dump.Print("bottom: %d\n",m_port_bottom);
|
|
dump.Print("top: %d\n",m_port_top);
|
|
dump.Print("near: %d\n",m_port_near);
|
|
dump.Print("far: %d\n",m_port_far);
|
|
dump.Print("aspect (width/height): "); dump.Print(port_aspect); dump.Print("\n");
|
|
dump.PopIndent();
|
|
|
|
dump.PopIndent();
|
|
}
|
|
|
|
bool ON_Viewport::GetPointDepth(
|
|
ON_3dPoint point,
|
|
double* near_dist,
|
|
double* far_dist,
|
|
bool bGrowNearFar
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
if ( point.x != ON_UNSET_VALUE )
|
|
{
|
|
double depth = (m_CamLoc - point)*m_CamZ;
|
|
if ( 0 != near_dist && (*near_dist == ON_UNSET_VALUE || !bGrowNearFar || *near_dist > depth) )
|
|
*near_dist = depth;
|
|
if ( 0 != far_dist && (*far_dist == ON_UNSET_VALUE || !bGrowNearFar || *far_dist < depth) )
|
|
*far_dist = depth;
|
|
rc = true;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetPointDepth(
|
|
ON_3dPoint point,
|
|
double* view_plane_depth
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
if ( point.x != ON_UNSET_VALUE )
|
|
{
|
|
double depth = (m_CamLoc - point)*m_CamZ;
|
|
if ( 0 != view_plane_depth )
|
|
*view_plane_depth = depth;
|
|
rc = true;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int ON_Viewport::InViewFrustum(
|
|
bool bInfiniteFrustum,
|
|
const ON_BoundingBox& bbox,
|
|
const ON_Xform* bbox_xform
|
|
) const
|
|
{
|
|
double near_dist = ON_UNSET_VALUE;
|
|
double far_dist = ON_UNSET_VALUE;
|
|
bool bGrowNearFar = false;
|
|
int rc = GetBoundingBoxDepth(bbox,bbox_xform,&near_dist,&far_dist,bGrowNearFar);
|
|
if (rc == 2 && false == bInfiniteFrustum)
|
|
{
|
|
if ( near_dist < m_frus_near || far_dist > m_frus_far )
|
|
rc = 1;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetBoundingBoxDepth(
|
|
ON_BoundingBox bbox,
|
|
double* near_dist,
|
|
double* far_dist,
|
|
bool bGrowNearFar
|
|
) const
|
|
{
|
|
int rc = GetBoundingBoxDepth(bbox,nullptr,near_dist,far_dist,bGrowNearFar);
|
|
return (rc > 0);
|
|
}
|
|
|
|
int ON_Viewport::GetBoundingBoxDepth(
|
|
ON_BoundingBox bbox,
|
|
const ON_Xform* bbox_xform,
|
|
double* near_dist,
|
|
double* far_dist,
|
|
bool bGrowNearFar
|
|
) const
|
|
{
|
|
// The Xbuffer[] stuff is to skip wasting time in unneeded constructors.
|
|
// The buffers are double arrays to insure alignments are correct.
|
|
ON_3dPoint* C;
|
|
ON_3dPoint* P;
|
|
ON_PlaneEquation* S;
|
|
ON_Line* L;
|
|
ON_3dPoint Q;
|
|
double Pbuffer[(8+8+8+48)*(sizeof(P[0])/sizeof(double))];
|
|
double Sbuffer[5*(sizeof(S[0])/sizeof(double))];
|
|
double Lbuffer[4*(sizeof(L[0])/sizeof(double))];
|
|
double d, t[2], v[4][8], v0, v1;
|
|
const double tol = ON_SQRT_EPSILON*(1.0 + m_CamLoc.MaximumCoordinate());
|
|
C = (ON_3dPoint*)Pbuffer;
|
|
P = C+8;
|
|
S = (ON_PlaneEquation*)Sbuffer;
|
|
L = (ON_Line*)Lbuffer;
|
|
unsigned int i, j, k, Pin, Pout, Pcount;
|
|
bool rc;
|
|
bool bTrimmed = false;
|
|
const bool bPerspectiveProjection = (ON::perspective_view == m_projection);
|
|
|
|
for (;;)
|
|
{
|
|
rc = bbox.GetCorners(C);
|
|
if (!rc)
|
|
break;
|
|
|
|
if (nullptr != bbox_xform)
|
|
{
|
|
rc = bbox_xform->IsValid();
|
|
if (!rc)
|
|
break;
|
|
}
|
|
|
|
rc = GetFrustumLeftPlaneEquation(S[0]);
|
|
if (!rc)
|
|
break;
|
|
rc = GetFrustumRightPlaneEquation(S[1]);
|
|
if (!rc)
|
|
break;
|
|
rc = GetFrustumBottomPlaneEquation(S[2]);
|
|
if (!rc)
|
|
break;
|
|
rc = GetFrustumTopPlaneEquation(S[3]);
|
|
if (!rc)
|
|
break;
|
|
|
|
S[4].Create(m_CamLoc,-m_CamZ);
|
|
|
|
Pcount = 0;
|
|
Pin = 0;
|
|
Pout = 0;
|
|
|
|
for ( i = 0; i < 8; i++ )
|
|
{
|
|
if ( nullptr != bbox_xform )
|
|
C[i] = (*bbox_xform)*C[i];
|
|
|
|
k = 0;
|
|
if ( (v[0][i] = S[0].ValueAt(C[i])) >= -tol )
|
|
k |= 1;
|
|
else
|
|
Pout |= 1;
|
|
if ( (v[1][i] = S[1].ValueAt(C[i])) >= -tol )
|
|
k |= 2;
|
|
else
|
|
Pout |= 2;
|
|
if ( (v[2][i] = S[2].ValueAt(C[i])) >= -tol )
|
|
k |= 4;
|
|
else
|
|
Pout |= 4;
|
|
if ( (v[3][i] = S[3].ValueAt(C[i])) >= -tol )
|
|
k |= 8;
|
|
else
|
|
Pout |= 8;
|
|
|
|
if ( !bPerspectiveProjection || S[4].ValueAt(C[i]) > 0.0 )
|
|
k |= 16;
|
|
|
|
Pin |= k;
|
|
if ( (1|2|4|8|16) == k )
|
|
{
|
|
// C[i] is inside the infinite frustum
|
|
P[Pcount++] = C[i];
|
|
}
|
|
}
|
|
|
|
if ( Pcount < 8 )
|
|
{
|
|
bTrimmed = true;
|
|
// some portion of bbox is outside the infinite frustum
|
|
if ( (1|2|4|8|16) != Pin )
|
|
{
|
|
// bbox does not intersect the infinite frustum.
|
|
rc = false;
|
|
break;
|
|
}
|
|
|
|
j = 0;
|
|
if ( bPerspectiveProjection )
|
|
{
|
|
if ( bbox.MinimumDistanceTo(m_CamLoc) <= 0.0 )
|
|
{
|
|
// camera location is in the bounding box
|
|
P[Pcount++] = m_CamLoc;
|
|
j = 1; // j = 1 indicates m_CamLoc has been added to P[].
|
|
}
|
|
L[0].from = m_CamLoc;
|
|
L[1].from = m_CamLoc;
|
|
L[2].from = m_CamLoc;
|
|
L[3].from = m_CamLoc;
|
|
}
|
|
else
|
|
{
|
|
rc = GetNearRect(L[0].from,L[1].from,L[2].from,L[3].from);
|
|
if (!rc)
|
|
break;
|
|
}
|
|
|
|
rc = GetFarRect(L[0].to,L[1].to,L[2].to,L[3].to);
|
|
if (!rc)
|
|
break;
|
|
|
|
const unsigned int Linout[4] = {
|
|
1|4, // intersection of left and bottom frustum sides
|
|
2|4, // intersection of right and bottom frustum sides
|
|
1|8, // intersection of left and top frustum sides
|
|
2|8 // intersection of right and top frustum sides
|
|
};
|
|
|
|
k = Pin & Pout;
|
|
for ( i = 0; i < 4; i++ )
|
|
{
|
|
// The Linout[i] == ... test is true if bbox is on both sides
|
|
// of both planes whose intersection defines the line L[i].
|
|
// The fast integer test helps cull unnecessary calls to
|
|
// the expensive ON_Intersect() function.
|
|
if ( Linout[i] == (k & Linout[i])
|
|
&& ON_Intersect(bbox,L[i],tol,(ON_Interval*)t)
|
|
)
|
|
{
|
|
if ( bPerspectiveProjection )
|
|
{
|
|
if ( t[1] < 0.0 )
|
|
continue;
|
|
if ( t[0] < 0.0 )
|
|
{
|
|
if ( 0 == j )
|
|
{
|
|
P[Pcount++] = m_CamLoc;
|
|
j = 1; // j = 1 indicates m_CamLoc has been added to P[].
|
|
}
|
|
t[0] = t[1];
|
|
}
|
|
}
|
|
P[Pcount++] = L[i].PointAt(t[0]);
|
|
if ( t[1] > t[0] )
|
|
P[Pcount++] = L[i].PointAt(t[1]);
|
|
}
|
|
}
|
|
|
|
// intersect box edges with frustum sides
|
|
// The 12 bbox edges have endpoints
|
|
// C[e[*][0]] and C[E[*][1]]
|
|
const unsigned int e[12][2] = {
|
|
{0,1},{2,3},{4,5},{6,7},
|
|
{0,2},{1,3},{4,6},{5,7},
|
|
{0,4},{1,5},{2,6},{3,7}};
|
|
|
|
for ( i = 0; i < 4; i++ )
|
|
{
|
|
for ( j = 0; j < 12; j++ )
|
|
{
|
|
v0 = v[i][e[j][0]];
|
|
v1 = v[i][e[j][1]];
|
|
if ( v0*v1 < 0.0 )
|
|
{
|
|
// this box edge crosses the frustum side plane
|
|
d = v0/(v0-v1);
|
|
P[Pcount++] = Q = (1.0-d)*C[e[j][0]] + d*C[e[j][1]];
|
|
// verify that Q is in the frustum
|
|
for ( k = 0; k < 4; k++ )
|
|
{
|
|
if ( i != k && S[k].ValueAt(Q) <= -tol )
|
|
{
|
|
// Q is not in the view frustum
|
|
Pcount--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( 0 == Pcount )
|
|
{
|
|
rc = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
t[0] = t[1] = (m_CamLoc - P[0])*m_CamZ;
|
|
for ( i = 1; i < Pcount; i++ )
|
|
{
|
|
d = (m_CamLoc - P[i])*m_CamZ;
|
|
if ( d < t[0] )
|
|
t[0] = d;
|
|
else if ( d > t[1] )
|
|
t[1] = d;
|
|
}
|
|
|
|
if ( bPerspectiveProjection )
|
|
{
|
|
if ( t[1] < 0.0 )
|
|
{
|
|
rc = false;
|
|
break;
|
|
}
|
|
if ( t[0] < 0.0 )
|
|
t[0] = 0.0;
|
|
}
|
|
|
|
if ( 0 != near_dist && (!bGrowNearFar || !ON_IsValid(*near_dist) || t[0] < *near_dist) )
|
|
*near_dist = t[0];
|
|
if ( 0 != far_dist && (!bGrowNearFar || !ON_IsValid(*far_dist) || t[1] > *far_dist) )
|
|
*far_dist = t[1];
|
|
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
// 0 = out, 1 = partially in infinite frustum, 2 = all in infinite frustum
|
|
return (rc) ? (bTrimmed ? 1 : 2) : 0;
|
|
}
|
|
|
|
static bool TrimLineHelper( ON_PlaneEquation e, bool bFlipPlane, ON_Line& line )
|
|
{
|
|
// trims the line - keeping the portion "above" the plane
|
|
ON_3dPoint P;
|
|
double e0,e1,s;
|
|
|
|
e0 = e.ValueAt(line.from);
|
|
e1 = e.ValueAt(line.to);
|
|
if ( bFlipPlane )
|
|
{
|
|
e0 = -e0;
|
|
e1 = -e1;
|
|
}
|
|
|
|
if ( e0 <= 0.0 && e1 <= 0.0 )
|
|
return false;
|
|
|
|
if ( e0 < 0.0 || e1 < 0.0 )
|
|
{
|
|
s = e0/(e0-e1);
|
|
if ( ON_IsValid(s) && s > 0.0 && s < 1.0 )
|
|
{
|
|
P = line.PointAt(s);
|
|
if ( e0 > 0.0 )
|
|
{
|
|
line.to = P;
|
|
}
|
|
else if ( e1 > 0.0 )
|
|
{
|
|
line.from = P;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetBoundingBoxProjectionExtents(
|
|
ON_BoundingBox bbox,
|
|
ON_Interval& x_extents,
|
|
ON_Interval& y_extents
|
|
) const
|
|
{
|
|
const ON_Interval unit_interval(0.0,1.0);
|
|
x_extents = unit_interval;
|
|
y_extents = unit_interval;
|
|
|
|
if ( !bbox.IsValid() )
|
|
return false;
|
|
|
|
if ( !IsValidCamera() && !IsValidFrustum() )
|
|
return false;
|
|
|
|
const ON_3dPoint cam_loc = CameraLocation();
|
|
if ( !cam_loc.IsValid() )
|
|
return false;
|
|
|
|
// far_rect[] points run image ccw
|
|
// (image lower left, lower right, upper right, upper left)
|
|
ON_3dPoint far_rect[4];
|
|
if ( !GetFarRect(far_rect[0],far_rect[1],far_rect[3],far_rect[2]) )
|
|
return false;
|
|
|
|
ON_3dPoint clipping_point(ON_UNSET_VALUE,ON_UNSET_VALUE,0.0);
|
|
ON_BoundingBox clipping_bbox;
|
|
clipping_bbox.m_min.z = clipping_point.z;
|
|
clipping_bbox.m_max.z = clipping_point.z;
|
|
|
|
ON_Line camera_ray(cam_loc,ON_3dPoint::UnsetPoint);
|
|
for ( int i = 0; i < 4; i++ )
|
|
{
|
|
camera_ray.to = far_rect[i];
|
|
for ( int j = 0; j < 3; j++ )
|
|
{
|
|
ON_PlaneEquation e(0==j ? 1.0 : 0.0, 1 == j ? 1.0 : 0.0, 2==j?1.0:0.0, 0.0);
|
|
for ( int k = 0; k < 2; k++ )
|
|
{
|
|
ON_3dPoint bbox_point = k ? bbox.m_max : bbox.m_min;
|
|
e.d = 0.0;
|
|
e.d = -bbox_point[j];
|
|
double t = ON_UNSET_VALUE;
|
|
if ( ON_Intersect(camera_ray,e,&t) )
|
|
{
|
|
if ( t > 0.0 )
|
|
{
|
|
ON_3dPoint P = camera_ray.PointAt(t);
|
|
P[j] = bbox_point[j];
|
|
if ( bbox.IsPointIn(P) )
|
|
{
|
|
clipping_point.x = (1 == i || 2 == i) ? 1.0 : -1.0;
|
|
clipping_point.y = (i >= 2) ? 1.0 : -1.0;
|
|
clipping_bbox.Set(clipping_point,true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( clipping_bbox.m_min.x != -1.0 || clipping_bbox.m_min.y != -1.0
|
|
|| clipping_bbox.m_max.x != 1.0 || clipping_bbox.m_max.y != 1.0
|
|
)
|
|
{
|
|
ON_Xform w2c;
|
|
if ( !GetXform(ON::world_cs,ON::clip_cs,w2c) )
|
|
return false;
|
|
|
|
ON_PlaneEquation vp_near_plane;
|
|
if ( !GetNearPlaneEquation(vp_near_plane) )
|
|
return false;
|
|
|
|
ON_PlaneEquation vp_far_plane;
|
|
if ( !GetFarPlaneEquation(vp_far_plane) )
|
|
return false;
|
|
|
|
ON_PlaneEquation frustum_sides[4];
|
|
if ( !GetFrustumLeftPlaneEquation(frustum_sides[0]) )
|
|
return false;
|
|
if ( !GetFrustumBottomPlaneEquation(frustum_sides[1]) )
|
|
return false;
|
|
if ( !GetFrustumRightPlaneEquation(frustum_sides[2]) )
|
|
return false;
|
|
if ( !GetFrustumTopPlaneEquation(frustum_sides[3]) )
|
|
return false;
|
|
|
|
ON_Line bbox_lines[12];
|
|
bbox.GetEdges(bbox_lines);
|
|
|
|
for ( int i = 0; i < 12; i++ )
|
|
{
|
|
ON_Line line = bbox_lines[i];
|
|
|
|
if ( !TrimLineHelper( vp_near_plane, true, line ) )
|
|
continue;
|
|
|
|
if ( !TrimLineHelper( vp_far_plane, false, line ) )
|
|
continue;
|
|
|
|
for ( int j = 0; j < 4; j++ )
|
|
{
|
|
if ( !TrimLineHelper(frustum_sides[j],false,line) )
|
|
{
|
|
line.from = ON_3dPoint::UnsetPoint;
|
|
line.to = ON_3dPoint::UnsetPoint;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !line.IsValid() )
|
|
continue;
|
|
|
|
clipping_point = w2c*line.from;
|
|
clipping_bbox.Set(clipping_point,true);
|
|
|
|
clipping_point = w2c*line.to;
|
|
clipping_bbox.Set(clipping_point,true);
|
|
}
|
|
}
|
|
|
|
bool rc = clipping_bbox.IsValid();
|
|
|
|
if ( rc )
|
|
{
|
|
ON_Interval extents[2];
|
|
for ( int i = 0; i < 2; i++ )
|
|
{
|
|
extents[i].Set(
|
|
0.5*(clipping_bbox.m_min[i]+1.0),
|
|
0.5*(clipping_bbox.m_max[i]+1.0)
|
|
);
|
|
|
|
if ( extents[i].IsIncreasing() || extents[i].IsSingleton() )
|
|
{
|
|
if ( extents[i].Intersection(unit_interval) && extents[i].IsValid() )
|
|
{
|
|
if ( extents[i].IsIncreasing() )
|
|
continue;
|
|
if ( extents[i].IsSingleton() )
|
|
continue;
|
|
}
|
|
}
|
|
|
|
rc = false;
|
|
break;
|
|
}
|
|
|
|
if ( rc )
|
|
{
|
|
x_extents = extents[0];
|
|
y_extents = extents[1];
|
|
}
|
|
}
|
|
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetSphereDepth(
|
|
ON_Sphere sphere,
|
|
double* near_dist,
|
|
double* far_dist,
|
|
bool bGrowNearFar
|
|
) const
|
|
{
|
|
bool rc = GetPointDepth( sphere.Center(), near_dist, far_dist, bGrowNearFar );
|
|
if ( rc && sphere.Radius() > 0.0 )
|
|
{
|
|
if ( 0 != near_dist )
|
|
*near_dist -= sphere.Radius();
|
|
if ( 0 != far_dist )
|
|
*far_dist += sphere.Radius();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::SetFrustumNearFar(
|
|
double near_dist,
|
|
double far_dist,
|
|
double min_near_dist,
|
|
double min_near_over_far,
|
|
double target_dist
|
|
)
|
|
{
|
|
double relative_depth_bias = 0.0;
|
|
return SetFrustumNearFar(
|
|
near_dist,
|
|
far_dist,
|
|
min_near_dist,
|
|
min_near_over_far,
|
|
target_dist,
|
|
relative_depth_bias
|
|
);
|
|
}
|
|
|
|
bool ON_Viewport::SetFrustumNearFar(
|
|
double near_dist,
|
|
double far_dist,
|
|
double min_near_dist,
|
|
double min_near_over_far,
|
|
double target_dist,
|
|
double relative_depth_bias
|
|
)
|
|
{
|
|
if ( !ON_IsValid(near_dist)
|
|
|| !ON_IsValid(far_dist)
|
|
|| near_dist > far_dist )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// min_near_over_far needs to be < 1 and should be in the
|
|
// range 1e-6 to 1e-2. By setting negative min's to zero,
|
|
// the code below is simplified but still ignores a negative
|
|
// input.
|
|
const double tiny = ON_ZERO_TOLERANCE;
|
|
const double MIN_NEAR_DIST = ( ON_IsValid(m__MIN_NEAR_DIST) && m__MIN_NEAR_DIST <= tiny )
|
|
? m__MIN_NEAR_DIST
|
|
: ON_Viewport::DefaultMinNearDist;
|
|
const double MIN_NEAR_OVER_FAR = ( ON_IsValid(m__MIN_NEAR_OVER_FAR)
|
|
&& m__MIN_NEAR_OVER_FAR > tiny
|
|
&& m__MIN_NEAR_OVER_FAR < 1.0-tiny )
|
|
? m__MIN_NEAR_OVER_FAR
|
|
: ON_Viewport::DefaultMinNearOverFar;
|
|
|
|
// 30 May Dale Lear
|
|
// Add checks for validity of min_near_dist and min_near_over_far
|
|
if ( !ON_IsValid(min_near_dist) || min_near_dist <= tiny )
|
|
{
|
|
min_near_dist = MIN_NEAR_DIST;
|
|
}
|
|
|
|
if ( !ON_IsValid(min_near_over_far)
|
|
|| min_near_over_far <= tiny
|
|
|| min_near_over_far >= 1.0-tiny )
|
|
{
|
|
min_near_over_far = MIN_NEAR_OVER_FAR;
|
|
}
|
|
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
// make sure 0 < near_dist < far_dist
|
|
if ( near_dist < min_near_dist )
|
|
near_dist = min_near_dist;
|
|
|
|
if ( far_dist <= near_dist+tiny )
|
|
{
|
|
far_dist = 100.0*near_dist;
|
|
if ( target_dist > near_dist+min_near_dist && far_dist <= target_dist+min_near_dist )
|
|
{
|
|
far_dist = 2.0*target_dist - near_dist;
|
|
}
|
|
if ( near_dist < min_near_over_far*far_dist )
|
|
far_dist = near_dist/min_near_over_far;
|
|
}
|
|
// The 1.0001 fudge factor is to ensure successive calls to this function
|
|
// give identical results.
|
|
while ( near_dist < 1.0001*min_near_over_far*far_dist )
|
|
{
|
|
// need to move near and far closer together
|
|
if ( ON_IsValid(target_dist) && near_dist < target_dist && target_dist < far_dist )
|
|
{
|
|
// STEP 1
|
|
// If near and far are a long ways from the target
|
|
// point, move them towards the target so the
|
|
// fine tuning in step 2 makes sense.
|
|
if ( target_dist/far_dist < min_near_over_far )
|
|
{
|
|
if ( near_dist/target_dist >= sqrt(min_near_over_far) )
|
|
{
|
|
// assume near_dist is good and just pull back far_dist
|
|
far_dist = near_dist/min_near_over_far;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// move far_dist to within striking distance of the target
|
|
// and let STEP 2 fine tune things.
|
|
far_dist = target_dist/min_near_over_far;
|
|
}
|
|
}
|
|
|
|
if ( near_dist/target_dist < min_near_over_far )
|
|
{
|
|
if ( target_dist/far_dist <= sqrt(min_near_over_far)
|
|
&& far_dist <= 4.0*target_dist )
|
|
{
|
|
// assume far_dist is good and just move up near_dist
|
|
near_dist = far_dist*min_near_over_far;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// move near_dist to within striking distance of the target
|
|
// and let STEP 2 fine tune things.
|
|
near_dist = target_dist*min_near_over_far;
|
|
}
|
|
}
|
|
|
|
// STEP 2
|
|
// Move near and far towards target by
|
|
// an amount proportional to current
|
|
// distances from the target.
|
|
|
|
double b = (far_dist - target_dist)*min_near_over_far + (target_dist - near_dist);
|
|
if ( b > 0.0)
|
|
{
|
|
double s = target_dist*(1.0 - min_near_over_far)/b;
|
|
if ( s > 1.0 || s <= ON_ZERO_TOLERANCE || !ON_IsValid(s) )
|
|
{
|
|
if ( s > 1.00001 || s <= ON_ZERO_TOLERANCE )
|
|
{
|
|
// should never happen
|
|
ON_ERROR("ON_Viewport::SetFrustumNearFar arithmetic problem 1.");
|
|
}
|
|
s = 1.0;
|
|
}
|
|
|
|
// 19 Jan 2010, Mikko:
|
|
// Reordered the operations to guarantee n==near_dist and f==far_dist
|
|
// when s==1.0. The old system generated bogus problem reports when the dist
|
|
// difference was big.
|
|
double n = s*near_dist + target_dist*(1.0-s);
|
|
double f = s*far_dist + target_dist*(1.0-s);
|
|
//double n = target_dist + s*(near_dist-target_dist);
|
|
//double f = target_dist + s*(far_dist-target_dist);
|
|
|
|
#if defined(ON_DEBUG)
|
|
double m = ((f != 0.0) ? n/f : 0.0)/min_near_over_far;
|
|
if ( m < 0.95 || m > 1.05 )
|
|
{
|
|
ON_ERROR("ON_Viewport::SetFrustumNearFar arithmetic problem 2.");
|
|
}
|
|
#endif
|
|
|
|
if ( n < near_dist || n >= target_dist)
|
|
{
|
|
ON_ERROR("ON_Viewport::SetFrustumNearFar arithmetic problem 3.");
|
|
if ( target_dist < f && f < far_dist )
|
|
n = min_near_over_far*f;
|
|
else
|
|
n = near_dist;
|
|
}
|
|
if ( f > far_dist || f <= target_dist )
|
|
{
|
|
ON_ERROR("ON_Viewport::SetFrustumNearFar arithmetic problem 4.");
|
|
if ( near_dist < n && n < target_dist )
|
|
f = n/min_near_over_far;
|
|
else
|
|
f = far_dist;
|
|
}
|
|
|
|
if ( n < min_near_over_far*f )
|
|
n = min_near_over_far*f;
|
|
else
|
|
f = n/min_near_over_far;
|
|
|
|
near_dist = n;
|
|
far_dist = f;
|
|
}
|
|
else
|
|
{
|
|
near_dist = min_near_over_far*far_dist;
|
|
}
|
|
}
|
|
else if ( ON_IsValid(target_dist) && fabs(far_dist-target_dist) > fabs(near_dist-target_dist) )
|
|
{
|
|
far_dist = near_dist/min_near_over_far;
|
|
}
|
|
else
|
|
{
|
|
near_dist = min_near_over_far*far_dist;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// parallel projection
|
|
if ( far_dist <= near_dist+tiny)
|
|
{
|
|
double d = fabs(near_dist)*0.125;
|
|
if ( d <= MIN_NEAR_DIST || d < tiny || d < min_near_dist )
|
|
d = 1.0;
|
|
near_dist -= d;
|
|
far_dist += d;
|
|
}
|
|
|
|
if ( near_dist < min_near_dist || near_dist < MIN_NEAR_DIST )
|
|
{
|
|
if ( !m_bValidCamera )
|
|
return false;
|
|
// move camera back in parallel projection so everything shows
|
|
double h = fabs(m_frus_top - m_frus_bottom);
|
|
double w = fabs(m_frus_right - m_frus_left);
|
|
double r = 0.5*((h > w) ? h : w);
|
|
double n = 3.0*r;
|
|
if (n < 2.0*min_near_dist )
|
|
n = 2.0*min_near_dist;
|
|
if ( n < 2.0*MIN_NEAR_DIST )
|
|
n = 2.0*MIN_NEAR_DIST;
|
|
double d = n-near_dist;
|
|
ON_3dPoint new_loc = CameraLocation() + d*CameraZ();
|
|
SetCameraLocation(new_loc);
|
|
if ( m_bValidFrustum && fabs(m_frus_near) >= d*ON_SQRT_EPSILON )
|
|
{
|
|
m_frus_near += d;
|
|
m_frus_far += d;
|
|
}
|
|
near_dist = n;
|
|
far_dist += d;
|
|
target_dist += d;
|
|
if ( far_dist < near_dist )
|
|
{
|
|
// could happen if d is < ON_EPSILON*far_dist
|
|
far_dist = 1.125*near_dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
// call bare bones setter
|
|
bool rc = SetFrustumNearFar( near_dist, far_dist );
|
|
|
|
// if depth bias will be applied, then make an attempt
|
|
// to adjust the frustum's near plane to prevent
|
|
// clipping biased objects. This post-adjustment
|
|
// fixes display bugs like # 87514.
|
|
if ( rc
|
|
&& relative_depth_bias > 0.0 && relative_depth_bias <= 0.5
|
|
&& m_frus_near > min_near_dist
|
|
&& m_frus_far > m_frus_near
|
|
&& m_frus_near > MIN_NEAR_DIST
|
|
)
|
|
{
|
|
const double near0 = m_frus_near;
|
|
const double far0 = m_frus_far;
|
|
double bias_3d = 1.001*relative_depth_bias*(m_frus_far - m_frus_near);
|
|
double near1 = m_frus_near - bias_3d;
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
if ( near1 < min_near_over_far*far0 || near1 < MIN_NEAR_OVER_FAR*far0 )
|
|
{
|
|
if (near0 - near1 > 0.01*near0)
|
|
near1 = 0.99*near0;
|
|
}
|
|
}
|
|
|
|
// It is important that this test be applied in perspective
|
|
// and parallel views. Otherwise the camera location in
|
|
// parallel view will creep back when SetFrustumNearFar()
|
|
// is called multiple times.
|
|
if ( !(near1 >= min_near_dist && near1 >= MIN_NEAR_DIST) )
|
|
{
|
|
near1 = (min_near_dist >= MIN_NEAR_DIST)
|
|
? min_near_dist
|
|
: MIN_NEAR_DIST;
|
|
}
|
|
|
|
if ( near1 < near0 )
|
|
{
|
|
#if defined(ON_DEBUG)
|
|
const ON_3dPoint debug_camloc0(m_CamLoc);
|
|
#endif
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
rc = SetFrustumNearFar( near1, far0 );
|
|
if (!rc)
|
|
rc = SetFrustumNearFar( near0, far0 );
|
|
}
|
|
else
|
|
{
|
|
// call this function again with relative_depth_bias = 0.0
|
|
// to get camera location positioned correctly when near1
|
|
// is too small or negative.
|
|
rc = SetFrustumNearFar(
|
|
near1, far0,
|
|
min_near_dist, min_near_over_far,
|
|
target_dist,
|
|
0.0
|
|
);
|
|
if (!rc)
|
|
rc = SetFrustumNearFar(
|
|
near0, far0,
|
|
min_near_dist, min_near_over_far,
|
|
target_dist,
|
|
0.0
|
|
);
|
|
}
|
|
#if defined(ON_DEBUG)
|
|
if ( debug_camloc0 != m_CamLoc )
|
|
{
|
|
ON_WARNING("Relative depth bias changed camera location.");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetFrustumLeftPlane(
|
|
ON_Plane& left_plane
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
ON_2dVector v(m_frus_near,m_frus_left);
|
|
rc = v.Unitize();
|
|
left_plane.origin = m_CamLoc;
|
|
left_plane.xaxis = v.y*m_CamX - v.x*m_CamZ;
|
|
left_plane.yaxis = m_CamY;
|
|
left_plane.zaxis = v.x*m_CamX + v.y*m_CamZ;
|
|
}
|
|
else
|
|
{
|
|
left_plane.origin = m_CamLoc + m_frus_left*m_CamX;
|
|
left_plane.xaxis = -m_CamZ;
|
|
left_plane.yaxis = m_CamY;
|
|
left_plane.zaxis = m_CamX;
|
|
}
|
|
left_plane.UpdateEquation();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetFrustumLeftPlaneEquation(
|
|
ON_PlaneEquation& left_plane_equation
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
ON_2dVector v(m_frus_near,m_frus_left);
|
|
if ( 0 != (rc = v.Unitize()) )
|
|
{
|
|
rc = left_plane_equation.Create(m_CamLoc, v.x*m_CamX + v.y*m_CamZ);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = left_plane_equation.Create(m_CamLoc + m_frus_left*m_CamX, m_CamX);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetFrustumRightPlane(
|
|
ON_Plane& right_plane
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
ON_2dVector v(m_frus_near,-m_frus_right);
|
|
rc = v.Unitize();
|
|
right_plane.origin = m_CamLoc;
|
|
right_plane.xaxis = v.y*m_CamX + v.x*m_CamZ;
|
|
right_plane.yaxis = m_CamY;
|
|
right_plane.zaxis = -v.x*m_CamX + v.y*m_CamZ;
|
|
}
|
|
else
|
|
{
|
|
right_plane.origin = m_CamLoc + m_frus_right*m_CamX;
|
|
right_plane.xaxis = m_CamZ;
|
|
right_plane.yaxis = m_CamY;
|
|
right_plane.zaxis = -m_CamX;
|
|
}
|
|
right_plane.UpdateEquation();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetFrustumRightPlaneEquation(
|
|
ON_PlaneEquation& right_plane_equation
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
ON_2dVector v(m_frus_near,-m_frus_right);
|
|
if ( 0 != (rc = v.Unitize()) )
|
|
{
|
|
rc = right_plane_equation.Create(m_CamLoc, -v.x*m_CamX + v.y*m_CamZ);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = right_plane_equation.Create(m_CamLoc + m_frus_right*m_CamX, -m_CamX);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetFrustumBottomPlane(
|
|
ON_Plane& bottom_plane
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
ON_2dVector v(m_frus_near,m_frus_bottom);
|
|
rc = v.Unitize();
|
|
bottom_plane.origin = m_CamLoc;
|
|
bottom_plane.xaxis = -v.y*m_CamY + v.x*m_CamZ;
|
|
bottom_plane.yaxis = m_CamX;
|
|
bottom_plane.zaxis = v.x*m_CamY + v.y*m_CamZ;
|
|
}
|
|
else
|
|
{
|
|
bottom_plane.origin = m_CamLoc + m_frus_bottom*m_CamY;
|
|
bottom_plane.xaxis = m_CamZ;
|
|
bottom_plane.yaxis = m_CamX;
|
|
bottom_plane.zaxis = m_CamY;
|
|
}
|
|
bottom_plane.UpdateEquation();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetFrustumBottomPlaneEquation(
|
|
ON_PlaneEquation& bottom_plane_equation
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
ON_2dVector v(m_frus_near,m_frus_bottom);
|
|
if ( 0 != (rc = v.Unitize()) )
|
|
{
|
|
rc = bottom_plane_equation.Create(m_CamLoc, v.x*m_CamY + v.y*m_CamZ);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = bottom_plane_equation.Create(m_CamLoc + m_frus_bottom*m_CamY, m_CamY);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Viewport::GetFrustumTopPlane(
|
|
ON_Plane& top_plane
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
ON_2dVector v(m_frus_near,-m_frus_top);
|
|
rc = v.Unitize();
|
|
top_plane.origin = m_CamLoc;
|
|
top_plane.xaxis = -v.y*m_CamY - v.x*m_CamZ;
|
|
top_plane.yaxis = m_CamX;
|
|
top_plane.zaxis = -v.x*m_CamY + v.y*m_CamZ;
|
|
}
|
|
else
|
|
{
|
|
top_plane.origin = m_CamLoc + m_frus_top*m_CamY;
|
|
top_plane.xaxis = -m_CamZ;
|
|
top_plane.yaxis = m_CamX;
|
|
top_plane.zaxis = -m_CamY;
|
|
}
|
|
top_plane.UpdateEquation();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::GetFrustumTopPlaneEquation(
|
|
ON_PlaneEquation& top_plane_equation
|
|
) const
|
|
{
|
|
bool rc = m_bValidCamera && m_bValidFrustum;
|
|
if (rc)
|
|
{
|
|
|
|
if ( IsPerspectiveProjection() )
|
|
{
|
|
ON_2dVector v(m_frus_near,-m_frus_top);
|
|
if ( 0 != (rc = v.Unitize()) )
|
|
{
|
|
top_plane_equation.Create(m_CamLoc, -v.x*m_CamY + v.y*m_CamZ);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
top_plane_equation.Create(m_CamLoc + m_frus_top*m_CamY, -m_CamY);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
void ON_Viewport::GetViewScale(double* x, double* y) const
|
|
{
|
|
GetViewScale(x, y, nullptr);
|
|
}
|
|
|
|
void ON_Viewport::GetViewScale( double* x, double* y, double* z) const
|
|
{
|
|
if (x) *x = 1.0;
|
|
if (y) *y = 1.0;
|
|
if (z) *z = 1.0;
|
|
if ( !m_clip_mods.IsIdentity()
|
|
&& 0.0 == m_clip_mods.m_xform[3][0]
|
|
&& 0.0 == m_clip_mods.m_xform[3][1]
|
|
&& 0.0 == m_clip_mods.m_xform[3][2]
|
|
&& 1.0 == m_clip_mods.m_xform[3][3]
|
|
)
|
|
{
|
|
// 04 May 2020 S. Baer (RH-37076)
|
|
// Allow for negative scale values. See comments in SetViewScale
|
|
double sx = m_clip_mods.m_xform[0][0];
|
|
double sy = m_clip_mods.m_xform[1][1];
|
|
double sz = m_clip_mods.m_xform[2][2];
|
|
if ( fabs(sx) > ON_ZERO_TOLERANCE
|
|
&& fabs(sy) > ON_ZERO_TOLERANCE
|
|
&& fabs(sz) > ON_ZERO_TOLERANCE
|
|
&& 0.0 == m_clip_mods.m_xform[0][1]
|
|
&& 0.0 == m_clip_mods.m_xform[0][2]
|
|
&& 0.0 == m_clip_mods.m_xform[1][0]
|
|
&& 0.0 == m_clip_mods.m_xform[1][2]
|
|
)
|
|
{
|
|
if (x) *x = sx;
|
|
if (y) *y = sy;
|
|
if (z) *z = sz;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ON_Viewport::SetViewScale( double x, double y )
|
|
{
|
|
return SetViewScale(x, y, 1);
|
|
}
|
|
|
|
bool ON_Viewport::SetViewScale( double x, double y, double z)
|
|
{
|
|
// 22 May Dale Lear
|
|
// View scaling should have been done by adjusting the
|
|
// frustum left/right top/bottom but I was stupid and added a clipmodxform
|
|
// that is more trouble than it is worth.
|
|
// Someday I will fix this. In the mean time, I want all scaling requests
|
|
// to flow through SetViewScale/GetViewScale so I can easily find and fix
|
|
// things when I have time to do it right.
|
|
// 04 November 2011 S. Baer (RR93636)
|
|
// This function is used for printer calibration and it is commonly possible
|
|
// to need to apply a scale in both x and y. The reason for the need of x
|
|
// or y to be one is because the view scale is encoded in the clip mod xform
|
|
// and it is hard to be sure that we could accurately extract these values
|
|
// when calling GetViewScale. Removing the requirement to have one of the
|
|
// values == 1
|
|
// 04 May 2020 S. Baer (RH-37076)
|
|
// Users are requesting mirrored parallel viewports (reflected ceiling plans).
|
|
// Removing the limitation that this function imposes of only positive values
|
|
// allowed for scaling to see if a -1.0 for x is what these users are after.
|
|
// Scaling values are not saved with the 3dm file so this is not a long term
|
|
// solution for supporting RCP viewports. What this does do is let us have users
|
|
// experiment with a -1.0 horizontal scale in a custom display mode and tell
|
|
// us if this is the desired display that they are after.
|
|
// 5 Jan 2023 S. Baer (RH-71044)
|
|
// Always allow case where the (x,y,z) = (1,1,1) in order to reset the scale
|
|
// to 1 across the board
|
|
bool validInput = fabs(x) > ON_ZERO_TOLERANCE && ON_IsValid(x)
|
|
&& fabs(y) > ON_ZERO_TOLERANCE && ON_IsValid(y)
|
|
&& fabs(z) > ON_ZERO_TOLERANCE && ON_IsValid(z);
|
|
if (!validInput)
|
|
return false;
|
|
|
|
bool allOnes = fabs(x - 1.0) < ON_EPSILON
|
|
&& fabs(y - 1.0) < ON_EPSILON
|
|
&& fabs(z - 1.0) < ON_EPSILON;
|
|
if (allOnes)
|
|
return SetClipModXform(ON_Xform::IdentityTransformation);
|
|
|
|
bool rc = false;
|
|
if ( IsParallelProjection() )
|
|
{
|
|
ON_Xform xform(ON_Xform::IdentityTransformation);
|
|
xform.m_xform[0][0] = x;
|
|
xform.m_xform[1][1] = y;
|
|
xform.m_xform[2][2] = z;
|
|
rc = SetClipModXform(xform);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
double ON_Viewport::ClipCoordDepthBias( double relative_depth_bias, double clip_z, double clip_w ) const
|
|
{
|
|
double d;
|
|
if ( m_frus_far > m_frus_near
|
|
&& 0.0 != relative_depth_bias
|
|
&& 0.0 != clip_w
|
|
)
|
|
{
|
|
if ( ON::perspective_view == m_projection )
|
|
{
|
|
// To get the formula for the code in this claus:
|
|
//
|
|
// Set M = [Camera2Clip]*[translation by (0,0,relative_depth_bias*(f-n)]*[Clip2Camera]
|
|
// Note that M maps clipping coordinates to clipping coordinates.
|
|
//
|
|
// Calculate M([x,y,z,w]) = [p,q,r,s]
|
|
//
|
|
// This function returns (r/s - z/w)*w
|
|
//
|
|
// If you are actually doing this calculation and trying to
|
|
// get the formula used in the code below, it helps to notice
|
|
// that (f+n)/(f-n) = a/b.
|
|
//
|
|
// Note that there "should" be a small adjustment to the
|
|
// x and y coordinates that is not performed by tweaking
|
|
// the z clipping coordinate
|
|
// z += vp->ClipCoordDepthBias( rel_bias, z, w );
|
|
// but the effect is actually better when the goal is to
|
|
// make wires that are on shaded surfaces appear because
|
|
// their horizons are not altered.
|
|
//
|
|
// This method is more complicated that adding a constant
|
|
// depth buffer bias but is required for high quality images
|
|
// when values of far/near get to be around 1e4 or larger.
|
|
//
|
|
double a = m_frus_far + m_frus_near;
|
|
double b = m_frus_far - m_frus_near;
|
|
double c = 0.5*relative_depth_bias/(m_frus_far*m_frus_near);
|
|
double t = a + b*clip_z/clip_w;
|
|
d = c*t*t*clip_w/(1.0 - c*b*t);
|
|
}
|
|
else
|
|
{
|
|
// The "2.0*" is here because clipping coordinates run from
|
|
// -1 to +1, a distance of 2 units.
|
|
d = 2.0*relative_depth_bias*clip_w;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
d = 0.0;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
bool ON_Viewport::GetClipCoordDepthBiasXform(
|
|
double relative_depth_bias,
|
|
ON_Xform& clipbias
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
|
|
while ( 0.0 != relative_depth_bias
|
|
&& m_frus_far > m_frus_near
|
|
)
|
|
{
|
|
if ( ON::perspective_view == m_projection )
|
|
{
|
|
ON_Xform clip2cam, cam_delta(ON_Xform::IdentityTransformation), cam2clip;
|
|
if ( !cam2clip.CameraToClip(true,m_frus_left,m_frus_right,m_frus_bottom,m_frus_top,m_frus_near,m_frus_far) )
|
|
break;
|
|
if ( !clip2cam.ClipToCamera(true,m_frus_left,m_frus_right,m_frus_bottom,m_frus_top,m_frus_near,m_frus_far) )
|
|
break;
|
|
cam_delta.m_xform[2][3] = relative_depth_bias*(m_frus_far-m_frus_near);
|
|
clipbias = cam2clip*cam_delta*clip2cam;
|
|
}
|
|
else
|
|
{
|
|
clipbias = ON_Xform::IdentityTransformation;
|
|
clipbias.m_xform[2][3] = 2.0*relative_depth_bias;
|
|
}
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if (!rc)
|
|
clipbias = ON_Xform::IdentityTransformation;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::SetClipModXform( ON_Xform clip_mod_xform )
|
|
{
|
|
bool rc = false;
|
|
ON_Xform clip_mod_inverse_xform = clip_mod_xform;
|
|
rc = clip_mod_inverse_xform.Invert();
|
|
if ( rc )
|
|
{
|
|
ON_Xform id = clip_mod_inverse_xform*clip_mod_xform;
|
|
double e;
|
|
int i, j;
|
|
for ( i = 0; i < 4 && rc; i++ ) for ( j = 0; j < 4 && rc; j++ )
|
|
{
|
|
e = ( i == j ) ? 1.0 : 0.0;
|
|
if ( fabs(id.m_xform[i][j] - e) > ON_SQRT_EPSILON )
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
if (rc)
|
|
{
|
|
m_clip_mods = clip_mod_xform;
|
|
m_clip_mods_inverse = clip_mod_inverse_xform;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Viewport::ClipModXformIsIdentity() const
|
|
{
|
|
return m_clip_mods.IsIdentity();
|
|
}
|
|
|
|
ON_Xform ON_Viewport::ClipModXform() const
|
|
{
|
|
return m_clip_mods;
|
|
}
|
|
|
|
ON_Xform ON_Viewport::ClipModInverseXform() const
|
|
{
|
|
return m_clip_mods_inverse;
|
|
}
|
|
|
|
bool ON_Viewport::SetTargetPoint( ON_3dPoint target_point )
|
|
{
|
|
bool rc = (target_point.IsValid() || (ON_3dPoint::UnsetPoint == target_point));
|
|
if (rc)
|
|
m_target_point = target_point;
|
|
return rc;
|
|
}
|
|
|
|
ON_3dPoint ON_Viewport::FrustumCenterPoint( double target_distance ) const
|
|
{
|
|
double s,dx,dy,dz;
|
|
ON_3dPoint target_point = ON_3dPoint::UnsetPoint;
|
|
|
|
if (!m_bValidCamera || !m_bValidFrustum)
|
|
return target_point;
|
|
|
|
if ( ON_UNSET_VALUE == target_distance && m_bValidFrustum
|
|
&& m_frus_near > 0.0 && m_frus_far >= m_frus_near
|
|
)
|
|
{
|
|
target_distance = 0.5*(m_frus_near+m_frus_far);
|
|
if ( target_distance < m_frus_near )
|
|
target_distance = m_frus_near;
|
|
else if ( target_distance > m_frus_far )
|
|
target_distance = m_frus_far;
|
|
}
|
|
|
|
if ( !ON_IsValid(target_distance) || target_distance <= 0.0 )
|
|
return target_point;
|
|
|
|
if ( m_bValidFrustum )
|
|
{
|
|
s = (ON::perspective_view == m_projection && m_frus_near > 0.0)
|
|
? 0.5*target_distance/m_frus_near
|
|
: 0.5;
|
|
dx = FrustumIsLeftRightSymmetric()
|
|
? 0.0
|
|
: s*(m_frus_right+m_frus_left);
|
|
dy = FrustumIsTopBottomSymmetric()
|
|
? 0.0
|
|
: s*(m_frus_top+m_frus_bottom);
|
|
}
|
|
else
|
|
{
|
|
dx = dy = 0.0;
|
|
}
|
|
dz = -target_distance;
|
|
|
|
// Done this way instead of using ON_3dPoint/ON_3dVector arithmetic so the
|
|
// optimizer can generate maximum precision when using 64 bit mantissas.
|
|
target_point.x = (m_CamLoc.x + dx*m_CamX.x + dy*m_CamY.x + dz*m_CamZ.x);
|
|
target_point.y = (m_CamLoc.y + dx*m_CamX.y + dy*m_CamY.y + dz*m_CamZ.y);
|
|
target_point.z = (m_CamLoc.z + dx*m_CamX.z + dy*m_CamY.z + dz*m_CamZ.z);
|
|
|
|
return target_point;
|
|
}
|
|
|
|
ON_3dPoint ON_Viewport::TargetPoint() const
|
|
{
|
|
return m_target_point;
|
|
}
|
|
|
|
|
|
double ON_Viewport::TargetDistance( bool bUseFrustumCenterFallback ) const
|
|
{
|
|
double d = ON_UNSET_VALUE;
|
|
if ( m_bValidCamera )
|
|
{
|
|
if ( bUseFrustumCenterFallback && !m_bValidFrustum )
|
|
bUseFrustumCenterFallback = false;
|
|
if ( m_target_point.IsValid() )
|
|
{
|
|
d = (m_CamLoc - m_target_point)*m_CamZ;
|
|
if ( bUseFrustumCenterFallback && (!ON_IsValid(d) || d <= 0.0) )
|
|
d = ON_UNSET_VALUE;
|
|
}
|
|
if ( bUseFrustumCenterFallback
|
|
&& ON_UNSET_VALUE == d
|
|
&& m_frus_far >= m_frus_near
|
|
)
|
|
{
|
|
d = 0.5*(m_frus_near+m_frus_far);
|
|
if ( d < m_frus_near ) d = m_frus_near; else if (d > m_frus_far) d = m_frus_far;
|
|
if ( d <= 0.0 )
|
|
d = ON_UNSET_VALUE;
|
|
}
|
|
}
|
|
return d;
|
|
}
|
|
|
|
bool ON_Viewport::SetViewportId( const ON_UUID& id)
|
|
{
|
|
// Please discuss any code changes with Dale Lear.
|
|
// You should NEVER change the viewport id once
|
|
// it is set.
|
|
bool rc = (0 == memcmp(&m_viewport_id,&id,sizeof(m_viewport_id)));
|
|
if ( !rc && m_viewport_id == ON_nil_uuid )
|
|
{
|
|
m_viewport_id = id;
|
|
rc = true;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
void ON_Viewport::ChangeViewportId(const ON_UUID& viewport_id)
|
|
{
|
|
m_viewport_id = viewport_id; // <- good place for a breakpoint
|
|
}
|
|
|
|
|
|
ON_UUID ON_Viewport::ViewportId(void) const
|
|
{
|
|
return m_viewport_id;
|
|
}
|
|
|
|
|
|
class ON_PgonPt
|
|
{
|
|
public:
|
|
ON_3dPoint m_P;
|
|
ON_2dVector m_Q;
|
|
double m_negcotangle;
|
|
};
|
|
|
|
static
|
|
int comparePptAngle( const void* pa, const void* pb )
|
|
{
|
|
double a = ((const ON_PgonPt*)pa)->m_negcotangle;
|
|
double b = ((const ON_PgonPt*)pb)->m_negcotangle;
|
|
if ( a == b )
|
|
{
|
|
a = ((const ON_PgonPt*)pa)->m_Q.LengthSquared();
|
|
b = ((const ON_PgonPt*)pb)->m_Q.LengthSquared();
|
|
}
|
|
return ((a>b) ? 1 : ((a==b) ? 0 : -1));
|
|
}
|
|
|
|
bool ON_IntersectViewFrustumPlane(
|
|
const ON_Viewport& vp,
|
|
const ON_PlaneEquation& plane_equation,
|
|
ON_SimpleArray<ON_3dPoint>& points
|
|
)
|
|
{
|
|
|
|
double left, right, bottom, top, near_dist, far_dist;
|
|
double v[8], v0, v1, s;
|
|
ON_PgonPt ppt, ppt_list[24];
|
|
ON_3dPoint F[8], P0, P1, P;
|
|
ON_2dVector D;
|
|
const ON_3dPoint C = vp.CameraLocation();
|
|
const ON_3dVector X = vp.CameraX();
|
|
const ON_3dVector Y = vp.CameraY();
|
|
const ON_3dVector Z = -vp.CameraZ();
|
|
int e[12][2] = {{0,1},{1,2},{2,3},{3,0},
|
|
{4,5},{5,6},{6,7},{7,4},
|
|
{0,4},{1,5},{2,6},{3,7}};
|
|
int i, i0, i1;
|
|
int ppt_count = 0;
|
|
|
|
if ( !vp.IsValidCamera() || !vp.GetFrustum(&left,&right,&bottom,&top,&near_dist,&far_dist) )
|
|
return false;
|
|
|
|
const ON_Plane plane(plane_equation);
|
|
if ( !plane.IsValid() )
|
|
return false;
|
|
|
|
s = ON::perspective_view == vp.Projection()
|
|
? far_dist/near_dist
|
|
: 1.0;
|
|
F[0] = C + left*X + bottom*Y + near_dist*Z;
|
|
F[1] = C + right*X + bottom*Y + near_dist*Z;
|
|
F[2] = C + right*X + top*Y + near_dist*Z;
|
|
F[3] = C + left*X + top*Y + near_dist*Z;
|
|
|
|
F[4] = C + s*left*X + s*bottom*Y + far_dist*Z;
|
|
F[5] = C + s*right*X + s*bottom*Y + far_dist*Z;
|
|
F[6] = C + s*right*X + s*top*Y + far_dist*Z;
|
|
F[7] = C + s*left*X + s*top*Y + far_dist*Z;
|
|
|
|
for ( i = 0; i < 8; i++ )
|
|
{
|
|
v[i] = plane_equation.ValueAt(F[i]);
|
|
}
|
|
|
|
for ( i = 0; i < 12; i++ )
|
|
{
|
|
v0 = v[e[i][0]];
|
|
v1 = v[e[i][1]];
|
|
P0 = F[e[i][0]];
|
|
P1 = F[e[i][1]];
|
|
if ( (v0 <= 0.0 && v1 >= 0.0) || (v0 >= 0.0 && v1 <= 0.0) )
|
|
{
|
|
if ( v0 == v1 )
|
|
{
|
|
ppt_list[ppt_count++].m_P = P0;
|
|
ppt_list[ppt_count++].m_P = P1;
|
|
}
|
|
else
|
|
{
|
|
s = v1/(v1-v0);
|
|
P = s*P0 + (1.0-s)*P1;
|
|
ppt_list[ppt_count++].m_P = P;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ppt_count <= 0 )
|
|
return true; // plane misses frustum
|
|
|
|
i0 = 0;
|
|
for ( i = 0; i < ppt_count; i++ )
|
|
{
|
|
plane.ClosestPointTo( ppt_list[i].m_P, &ppt_list[i].m_Q.x, &ppt_list[i].m_Q.y );
|
|
if ( ppt_list[i].m_Q.y < ppt_list[i0].m_Q.y
|
|
|| (ppt_list[i].m_Q.y == ppt_list[i0].m_Q.y && ppt_list[i].m_Q.x < ppt_list[i0].m_Q.x) )
|
|
i0 = i;
|
|
}
|
|
|
|
// Use Gram scan to get the convex hull and save it in points[].
|
|
// See http://en.wikipedia.org/wiki/Graham_scan for details.
|
|
|
|
// put point with smallest m_Q.y coordinate in ppt_list[0].
|
|
ppt = ppt_list[i0];
|
|
if ( i0 )
|
|
{
|
|
ppt_list[i0] = ppt_list[0];
|
|
ppt_list[0] = ppt;
|
|
i0 = 0;
|
|
}
|
|
|
|
// sort points by the angle (ppt_list[i].m_Q = ppt_list[0].m_Q) makes
|
|
// with the positive x axis. This is the same as sorting them by
|
|
// -cot(angle) = -deltax/deltay.
|
|
ppt_list[0].m_negcotangle = -ON_DBL_MAX; // -cot(0) = - infinity
|
|
for ( i = 1; i < ppt_count; i++ )
|
|
{
|
|
ppt_list[i].m_Q.x -= ppt_list[0].m_Q.x;
|
|
ppt_list[i].m_Q.y -= ppt_list[0].m_Q.y;
|
|
ppt_list[i].m_negcotangle = (0.0 >= ppt_list[i].m_Q.y) ? -ON_DBL_MAX : -ppt_list[i].m_Q.x/ppt_list[i].m_Q.y;
|
|
}
|
|
ppt_list[0].m_Q.x = 0.0;
|
|
ppt_list[0].m_Q.y = 0.0;
|
|
ON_qsort(ppt_list+1,ppt_count-1,sizeof(ppt_list[0]),comparePptAngle);
|
|
|
|
points.Append(ppt_list[0].m_P);
|
|
i0 = 0;
|
|
i1 = 1;
|
|
D = ppt_list[i1].m_Q - ppt_list[i0].m_Q;
|
|
|
|
for ( i = 2; i < ppt_count; i++ )
|
|
{
|
|
if ( (ppt_list[i].m_Q.y - ppt_list[i0].m_Q.y)*D.x <= (ppt_list[i].m_Q.x - ppt_list[i0].m_Q.x)*D.y )
|
|
{
|
|
// ppt_list[i0], ppt_list[i1], ppt_list[i] is a "right" turn or collinear.
|
|
// Drop ppt_list[i1].
|
|
i1 = i;
|
|
}
|
|
else
|
|
{
|
|
// ppt_list[i0], ppt_list[i1], ppt_list[i] is a "left" turn.
|
|
points.Append(ppt_list[i1].m_P);
|
|
i0 = i1;
|
|
i1 = i;
|
|
}
|
|
D = ppt_list[i1].m_Q - ppt_list[i0].m_Q;
|
|
}
|
|
|
|
if ( i1 > i0 )
|
|
points.Append(ppt_list[i1].m_P);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ON_Viewport::GetPerspectiveClippingPlaneConstraints(
|
|
ON_3dPoint camera_location,
|
|
unsigned int depth_buffer_bit_depth,
|
|
double* min_near_dist,
|
|
double* min_near_over_far
|
|
)
|
|
{
|
|
double nof, n, d;
|
|
|
|
if ( camera_location.IsValid() )
|
|
{
|
|
/*
|
|
|
|
// This code was used prior to 14 July 2011.
|
|
//
|
|
d = camera_location.DistanceTo(ON_3dPoint::Origin);
|
|
if ( d >= 1.0e5 )
|
|
{
|
|
if ( depth_buffer_bit_depth >= 32 )
|
|
depth_buffer_bit_depth -= 24;
|
|
else
|
|
depth_buffer_bit_depth = 8;
|
|
}
|
|
else if ( d >= 1.0e4 )
|
|
{
|
|
if ( depth_buffer_bit_depth >= 24 )
|
|
depth_buffer_bit_depth -= 16;
|
|
else
|
|
depth_buffer_bit_depth = 8;
|
|
}
|
|
else if ( d >= 1.0e3 )
|
|
{
|
|
if ( depth_buffer_bit_depth >= 16 )
|
|
depth_buffer_bit_depth -= 8;
|
|
else
|
|
depth_buffer_bit_depth = 8;
|
|
}
|
|
*/
|
|
|
|
// 14 July 2011 - Dale Lear
|
|
// The reductions above were too harsh and were
|
|
// generating clipping artifacts in the perspective
|
|
// view in bug report 88216. Changing to
|
|
// to the code below gets rid of those
|
|
// artifacts at the risk of having a meaningless
|
|
// view to clip transform if the transformation is
|
|
// calculated with single precision numbers.
|
|
// If these values require further tuning, please
|
|
// discuss changes with me and attach example files
|
|
// to bug report 88216.
|
|
d = camera_location.MaximumCoordinate();
|
|
if ( d > 1.0e6 && depth_buffer_bit_depth >= 16 )
|
|
depth_buffer_bit_depth -= 8;
|
|
}
|
|
|
|
if ( depth_buffer_bit_depth >= 32 )
|
|
{
|
|
nof = 0.0005; // Changed to match 24 bit defaults: https://mcneel.myjetbrains.com/youtrack/issue/RH-77623
|
|
n = 0.005; // Do not change back unless you've fixed the problems shown in the YT somewhere else.
|
|
}
|
|
else if ( depth_buffer_bit_depth >= 24 )
|
|
{
|
|
nof = 0.0005;
|
|
n = 0.005;
|
|
}
|
|
else if ( depth_buffer_bit_depth >= 16 )
|
|
{
|
|
nof = 0.005;
|
|
n = 0.005;
|
|
}
|
|
else
|
|
{
|
|
nof = 0.01;
|
|
n = 0.01;
|
|
}
|
|
|
|
if ( min_near_dist )
|
|
*min_near_dist = n;
|
|
if ( min_near_over_far )
|
|
*min_near_over_far = nof;
|
|
}
|
|
|
|
|
|
int ON_Viewport::InViewFrustum(
|
|
ON_3dPoint P
|
|
) const
|
|
{
|
|
ON_ClippingRegion cr;
|
|
if ( false == cr.SetObjectToClipTransformation(*this))
|
|
return 0;
|
|
return cr.InViewFrustum(P);
|
|
}
|
|
|
|
int ON_Viewport::InViewFrustum(
|
|
const ON_BoundingBox& bbox
|
|
) const
|
|
{
|
|
ON_ClippingRegion cr;
|
|
if ( false == cr.SetObjectToClipTransformation(*this))
|
|
return 0;
|
|
return cr.InViewFrustum(bbox);
|
|
}
|
|
|
|
int ON_Viewport::InViewFrustum(
|
|
int count,
|
|
const ON_3fPoint* p
|
|
) const
|
|
{
|
|
ON_ClippingRegion cr;
|
|
if ( false == cr.SetObjectToClipTransformation(*this))
|
|
return 0;
|
|
return cr.InViewFrustum(count,p);
|
|
}
|
|
|
|
int ON_Viewport::InViewFrustum(
|
|
int count,
|
|
const ON_3dPoint* p
|
|
) const
|
|
{
|
|
ON_ClippingRegion cr;
|
|
if ( false == cr.SetObjectToClipTransformation(*this))
|
|
return 0;
|
|
return cr.InViewFrustum(count,p);
|
|
}
|
|
|
|
int ON_Viewport::InViewFrustum(
|
|
int count,
|
|
const ON_4dPoint* p
|
|
) const
|
|
{
|
|
ON_ClippingRegion cr;
|
|
if ( false == cr.SetObjectToClipTransformation(*this))
|
|
return 0;
|
|
return cr.InViewFrustum(count,p);
|
|
}
|
|
|
|
|
|
bool ON_DollyExtents(
|
|
const ON_Viewport& current_vp,
|
|
ON_BoundingBox camcoord_bbox,
|
|
ON_Viewport& zoomed_vp
|
|
)
|
|
{
|
|
if (&zoomed_vp != ¤t_vp)
|
|
zoomed_vp = current_vp;
|
|
|
|
if (!camcoord_bbox.IsValid() || !zoomed_vp.IsValid())
|
|
return false;
|
|
|
|
double aspect = 0.0;
|
|
if (!current_vp.GetFrustumAspect(aspect))
|
|
return false;
|
|
|
|
if (!ON_IsValid(aspect) || 0.0 == aspect)
|
|
return false;
|
|
|
|
// Handle non-uniform viewport scaling
|
|
ON_3dVector scale(1.0, 1.0, 0.0);
|
|
current_vp.GetViewScale(&scale.x, &scale.y);
|
|
|
|
const double xmin = camcoord_bbox.m_min.x;
|
|
const double xmax = camcoord_bbox.m_max.x;
|
|
const double ymin = camcoord_bbox.m_min.y;
|
|
const double ymax = camcoord_bbox.m_max.y;
|
|
double dx = 0.5 * (xmax - xmin) * scale.x;
|
|
double dy = 0.5 * (ymax - ymin) * scale.y;
|
|
if (dx <= ON_SQRT_EPSILON && dy <= ON_SQRT_EPSILON)
|
|
dx = dy = 0.5;
|
|
|
|
if (dx < dy * aspect)
|
|
dx = dy * aspect;
|
|
else
|
|
dy = dx / aspect;
|
|
|
|
// Pad depths a bit so clipping plane are not coplanar with displayed geometry
|
|
// zmax is on frustum near and zmin is on frustum far
|
|
double zmin = camcoord_bbox.m_min.z;
|
|
double zmax = camcoord_bbox.m_max.z;
|
|
|
|
double dz = (zmax - zmin) * 0.00390625; // 0.00390625 = 1/256
|
|
if (ON::perspective_view == current_vp.Projection())
|
|
{
|
|
// Do not increase zmax too much or you make zooming to small
|
|
// objects in perspective views impossible. To test any
|
|
// changes, make a line from (0,0,0) to (0.001,0.001,0.001).
|
|
// Make a perspective view with a 50mm lens angle. If you
|
|
// can't ZEA on the line, then you've adjusted dz too much.
|
|
if (dz <= 1.0e-6)
|
|
dz = 1.0e-6;
|
|
}
|
|
else if (dz <= 0.125)
|
|
{
|
|
// In parallel projection it is ok to be generous.
|
|
dz = 0.125;
|
|
}
|
|
zmax += dz;
|
|
|
|
// It is ok to adjust zmin by more generous amount because it
|
|
// does not effect the ability to zoom in on small objects a
|
|
// perspective view.
|
|
if (dz <= 0.125)
|
|
dz = 0.125;
|
|
zmin -= dz;
|
|
dz = zmax - zmin;
|
|
|
|
double frus_near = 0.0;
|
|
if (ON::parallel_view == current_vp.Projection())
|
|
{
|
|
// parallel projection
|
|
//double cota = 50.0/12.0; // 50 mm lens angle
|
|
//frus_near = ((dx > dy) ? dx : dy)*cota;
|
|
frus_near = 0.125 * dz;
|
|
}
|
|
else if (ON::perspective_view == current_vp.Projection())
|
|
{
|
|
// perspective projection
|
|
double ax, ay;
|
|
if (current_vp.GetCameraAngle(NULL, &ay, &ax))
|
|
{
|
|
double zx = (ON_IsValid(ax) && ax > 0.0) ? dx / tan(ax) : 0.0;
|
|
double zy = (ON_IsValid(ay) && ay > 0.0) ? dy / tan(ay) : 0.0;
|
|
frus_near = (zx > zy) ? zx : zy;
|
|
}
|
|
}
|
|
|
|
bool rc = false;
|
|
if (!ON_IsValid(frus_near) || frus_near <= ON_SQRT_EPSILON)
|
|
{
|
|
frus_near = 1.0;
|
|
}
|
|
|
|
ON_3dPoint camloc = current_vp.CameraLocation();
|
|
if (camloc.IsValid())
|
|
{
|
|
ON_3dVector dolly = 0.5 * (xmax + xmin) * zoomed_vp.CameraX()
|
|
+ 0.5 * (ymax + ymin) * zoomed_vp.CameraY()
|
|
+ (frus_near + zmax) * zoomed_vp.CameraZ();
|
|
camloc += dolly;
|
|
if (zoomed_vp.SetCameraLocation(camloc))
|
|
{
|
|
double frus_far = frus_near + dz;
|
|
rc = zoomed_vp.SetFrustum(-dx, dx, -dy, dy,
|
|
frus_near, frus_far);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|