1 /* $NoKeywords: $ */
2 /*
3 //
4 // Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
5 // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
6 // McNeel & Associates.
7 //
8 // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
9 // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
10 // MERCHANTABILITY ARE HEREBY DISCLAIMED.
11 //
12 // For complete openNURBS copyright information see <http://www.opennurbs.org>.
13 //
14 ////////////////////////////////////////////////////////////////
15 */
16 
17 #include "pcl/surface/3rdparty/opennurbs/opennurbs.h"
18 
19 ON_OBJECT_IMPLEMENT( ON_Viewport, ON_Geometry, "D66E5CCF-EA39-11d3-BFE5-0010830122F0" );
20 
len2d(double x,double y)21 static double len2d( double x, double y )
22 {
23   double d= 0.0;
24   double fx = fabs(x);
25   double fy = fabs(y);
26   if ( fx > fy ) {
27     d = fy/fx;
28     d = fx*sqrt(1.0+d*d);
29   }
30   else if ( fy > fx ){
31     d = fx/fy;
32     d = fy*sqrt(1.0+d*d);
33   }
34   return d;
35 }
36 
unitize2d(double x,double y,double * ux,double * uy)37 static void unitize2d( double x, double y, double* ux, double* uy )
38 {
39   const double eps = 2.0*ON_SQRT_EPSILON;
40   // carefully turn two numbers into a 2d unit vector
41   double s, c, d;
42   c = x;
43   s = y;
44   if ( s == 0.0 ) {
45     c = (c < 0.0) ? -1.0 : 1.0;
46   }
47   else {
48     if ( fabs(s) > fabs(c) ) {
49       d = c/s;
50       d = fabs(s)*sqrt(1.0+d*d);
51     }
52     else {
53       d = s/c;
54       d = fabs(c)*sqrt(1.0+d*d);
55     }
56     d = 1.0/d;
57     if ( fabs(d-1.0) > eps ) {
58       s *= d;
59       c *= d;
60     }
61     if ( fabs(s) <= eps || fabs(c) >= 1.0-eps ) {
62       s = 0.0;
63       c = (c < 0.0) ? -1.0 : 1.0;
64     }
65     else if ( fabs(c) < eps || fabs(s) >= 1.0-eps) {
66       c = 0.0;
67       s = (s < 0.0) ? -1.0 : 1.0;
68     }
69   }
70   if ( ux )
71     *ux = c;
72   if ( uy )
73     *uy = s;
74 }
75 
76 
77 static
ON__IsCameraFrameUnitVectorHelper(const ON_3dVector & v)78 bool ON__IsCameraFrameUnitVectorHelper( const ON_3dVector& v )
79 {
80   // looser standard than ON_3dVector::IsUnitVector() so
81   // going to/from floats in OpenGL and Direct3d doesn't
82   // create "invalid" views.
83   return (v.x != ON_UNSET_VALUE && v.y != ON_UNSET_VALUE && v.z != ON_UNSET_VALUE && fabs(v.Length() - 1.0) <= 1.0e-6);
84 }
85 
86 static
ON__IsCameraFramePerpindicular(const ON_3dVector & unit_vector0,const ON_3dVector & unit_vector1)87 bool ON__IsCameraFramePerpindicular( const ON_3dVector& unit_vector0,const ON_3dVector& unit_vector1 )
88 {
89   return ( fabs(unit_vector0.x*unit_vector1.x + unit_vector0.y*unit_vector1.y + unit_vector0.z*unit_vector1.z) <= 1.0e-6 );
90 }
91 
92 bool
ON_GetViewportRotationAngles(const ON_3dVector & X,const ON_3dVector & Y,const ON_3dVector & Z,double * angle1,double * angle2,double * angle3)93 ON_GetViewportRotationAngles(
94     const ON_3dVector& X, // X,Y,Z must be a right handed orthonormal basis
95     const ON_3dVector& Y,
96     const ON_3dVector& Z,
97     double* angle1, // returns rotation about world Z
98     double* angle2, // returns rotation about world X ( 0 <= a2 <= pi )
99     double* angle3  // returns rotation about world Z
100     )
101 {
102   // double a1 = 0.0;  // rotation about world Z
103   // double a2 = 0.0;  // rotation about world X ( 0 <= a2 <= pi )
104   // double a3 = 0.0;  // rotation about world Z
105   bool bValidFrame = false;
106   double sin_a1 = 0.0;
107   double cos_a1 = 1.0;
108   double sin_a2 = 0.0;
109   double cos_a2 = 1.0;
110   double sin_a3 = 0.0;
111   double cos_a3 = 1.0;
112 
113   // If si = sin(ai) and ci = cos(ai), then the relationship between the camera
114   // frame and the angles is defined by the matrix equation C = R3*R2*R1, where:
115   //
116   //      c1 -s1  0        1   0   0         c3 -s3  0
117   // R1 = s1  c1  0  R2 =  0  c2 -s2   R3 =  s3  c3  0
118   //       0   0  1        0  s2  c2          0   0  1
119   //
120   //     CamX[0]  CamY[0] CamZ[0]
121   // C = CamX[1]  CamY[1] CamZ[1]
122   //     CamX[2]  CamY[2] CamZ[2]
123   //
124   //              .       .     s2*s3
125   //
126   // R3*R2*R1 =   .       .    -s2*c3
127   //
128   //            s1*s2   c1*s2    c2
129   //
130 
131   {
132     // don't attempt to work with slop
133     const double eps = 8.0*ON_SQRT_EPSILON;
134     double dx,dy,dz,d;
135     dx = X*X;
136     dy = Y*Y;
137     dz = Z*Z;
138     if ( fabs(dx-1.0) <= eps && fabs(dy-1.0) <= eps && fabs(dz-1.0) <= eps ) {
139       dx = X*Y;
140       dy = Y*Z;
141       dz = Z*X;
142       if ( fabs(dx) <= eps && fabs(dy) <= eps && fabs(dz) <= eps ) {
143         d = ON_TripleProduct( X, Y, Z );
144         bValidFrame = (d > 0.0);
145       }
146     }
147   }
148 
149   if ( bValidFrame )
150   {
151     // Usually "Z" = opposite of unitized camera direction.
152     //         "Y" = camera up made ortho to "Z" and unitized.
153     //         "X" = YxZ.
154     // So, when possible, I solve for angles in terms
155     // of "Z" and "Y" since "X" will generally have the most noise.
156     //
157     // Use C = R3*R2*R1 to get sin(a2), cos(a2).
158     cos_a2 = Z.z;
159     sin_a2 = len2d(Z.x,Z.y);
160     unitize2d(cos_a2,sin_a2,&cos_a2,&sin_a2); // kill noise
161 
162     if ( sin_a2 > 0.0 ) {
163       // use bottom row to get angle1.
164       sin_a1 = X.z;
165       cos_a1 = Y.z;
166       unitize2d(cos_a1,sin_a1,&cos_a1,&sin_a1); // kill noise
167 
168       // use right column to get angle3
169       cos_a3 = -Z.y;
170       sin_a3 =  Z.x;
171       unitize2d(cos_a3,sin_a3,&cos_a3,&sin_a3); // kill noise
172     }
173     else if ( cos_a2 == 1.0 ) {
174       // R2 = identity and C determines (angle1+angle3)
175       //      arbitrarily set angle1 = 0.
176       cos_a3 =  Y.y; // = cos(angle3+angle1)
177       sin_a3 = -Y.x; // = sin(angle3+angle1)
178     }
179     else if ( cos_a2 == -1.0 ) {
180       // R2 = [1 0 0 / 0 -1 0/ 0 0 -1] and C determines (angle3-angle1)
181       //      arbitrarily set angle1 = 0
182       cos_a3 = -Y.y; // = cos(angle3-angle1)
183       sin_a3 =  Y.x; // = sin(angle3-angle1)
184     }
185   }
186 
187 
188   if ( cos_a1 == -1.0 && sin_a1 == 0.0 ) {
189     // when a1 = pi, juggle angles to get a1 = 0 with
190     // same effective rotation to keep legacy 3d apps
191     // happy.
192     // a1: pi -> 0
193     // a2: a2 -> 2pi - a2
194     // a1: a3 ->  pi + a3
195     sin_a1 = 0.0;
196     cos_a1 = 0.0;
197     sin_a2 = -sin_a2;
198     sin_a3 = -sin_a3;
199     cos_a3 = -cos_a3;
200   }
201 
202   if ( angle1 )
203     *angle1 = atan2( sin_a1, cos_a1 );
204   if ( angle2 )
205     *angle2 = atan2( sin_a2, cos_a2 );
206   if ( angle3 )
207     *angle3 = atan2( sin_a3, cos_a3 );
208 
209   return bValidFrame;
210 }
211 
SetPerspectiveClippingPlaneConstraints(unsigned int depth_buffer_bit_depth)212 void ON_Viewport::SetPerspectiveClippingPlaneConstraints(
213         unsigned int depth_buffer_bit_depth
214         )
215 {
216   double min_near_dist = 0.0;
217   double min_near_over_far = 0.0;
218   ON_Viewport::GetPerspectiveClippingPlaneConstraints(m_CamLoc,depth_buffer_bit_depth,&min_near_dist,&min_near_over_far);
219   SetPerspectiveMinNearDist(min_near_dist);
220   SetPerspectiveMinNearOverFar(min_near_over_far);
221 }
222 
223 
SetPerspectiveMinNearOverFar(double min_near_over_far)224 void ON_Viewport::SetPerspectiveMinNearOverFar(double min_near_over_far)
225 {
226   if (    ON_IsValid(min_near_over_far)
227        && min_near_over_far > ON_ZERO_TOLERANCE
228        && min_near_over_far < 1.0-ON_ZERO_TOLERANCE
229      )
230   {
231     m__MIN_NEAR_OVER_FAR = min_near_over_far;
232   }
233 }
234 
PerspectiveMinNearOverFar() const235 double ON_Viewport::PerspectiveMinNearOverFar() const
236 {
237   return m__MIN_NEAR_OVER_FAR;
238 }
239 
SetPerspectiveMinNearDist(double min_near_dist)240 void ON_Viewport::SetPerspectiveMinNearDist(double min_near_dist)
241 {
242   if ( ON_IsValid(min_near_dist) && min_near_dist > ON_ZERO_TOLERANCE )
243   {
244     m__MIN_NEAR_DIST = min_near_dist;
245   }
246 }
247 
PerspectiveMinNearDist() const248 double ON_Viewport::PerspectiveMinNearDist() const
249 {
250   return m__MIN_NEAR_DIST;
251 }
252 
253 
254 
255 // Discuss any changes of these values with Dale Lear
256 const double ON_Viewport::DefaultNearDist = 0.005;
257 const double ON_Viewport::DefaultFarDist = 1000.0;
258 const double ON_Viewport::DefaultMinNearDist = 0.0001;
259 const double ON_Viewport::DefaultMinNearOverFar = 0.0001;
260 
261 // For 32 bit float based OpenGL drivers, the value of
262 // the ON_Viewport::DefaultMinNearOverFar constant must
263 // be <0.01 and >= 0.0001.
264 // If you change this value, you need to retest RR 8902 on OpenGL
265 // drivers that (internally) use float precision transformations.
266 // Some OpenGL drivers, like the Microsoft software emulation
267 // driver for XP crash in some cases when near/far > 1e8.
268 //
269 // ON_Viewport::DefaultMinNearOverFar = 0.001    // used in Rhino 3.0 beta testing until 11 Sep 2002
270 // ON_Viewport::DefaultMinNearOverFar = 0.01     // used for Rhino 3.0 CD1 and CD2
271 // ON_Viewport::DefaultMinNearOverFar = 0.000001 // used for Rhino 3.0 CD3
272 // ON_Viewport::DefaultMinNearOverFar = 0.0001   // used for Rhino 4.0 Fixes RR 8902
273 
274 void
Initialize()275 ON_Viewport::Initialize()
276 {
277   m__MIN_NEAR_DIST     = ON_Viewport::DefaultMinNearDist;
278   m__MIN_NEAR_OVER_FAR = ON_Viewport::DefaultMinNearOverFar;
279 
280   m_bValidCamera = true;
281   m_bValidFrustum = true;
282   m_bValidPort = false;
283   m_reserved1 = 0;
284   m_projection = ON::parallel_view;
285 
286   m_bLockCamUp = false;
287   m_bLockCamDir = false;
288   m_bLockCamLoc = false;
289   m_frustum_symmetry_flags = 0;
290 
291   m_CamLoc.x =   0.0;
292   m_CamLoc.y =   0.0;
293   m_CamLoc.z = 100.0;
294   m_CamDir = -ON_zaxis;
295   m_CamUp = ON_yaxis;
296   m_CamX = ON_xaxis;
297   m_CamY = ON_yaxis;
298   m_CamZ = ON_zaxis;
299   m_frus_left = -20.0;
300   m_frus_right = 20.0;
301   m_frus_bottom = -20.0;
302   m_frus_top = 20.0;
303   m_frus_near = m__MIN_NEAR_DIST;
304   m_frus_far = ON_Viewport::DefaultFarDist;
305   m_port_left = 0;
306   m_port_right = 1000;
307   m_port_bottom = 0;
308   m_port_top = 1000;
309   m_port_near = 0;//0xffff;
310   m_port_far =  1;//0;
311   m_clip_mods.Identity();
312   m_clip_mods_inverse.Identity();
313   m_target_point = ON_UNSET_POINT;
314   m_viewport_id = ON_nil_uuid;
315 }
316 
317 const ON_3dVector ON_Viewport::Default3dCameraDirection(-0.43301270189221932338186158537647,0.75,-0.5);
318 
ON_Viewport()319 ON_Viewport::ON_Viewport()
320 {
321   Initialize();
322 }
323 
~ON_Viewport()324 ON_Viewport::~ON_Viewport()
325 {}
326 
operator =(const ON_Viewport & src)327 ON_Viewport& ON_Viewport::operator=( const ON_Viewport& src )
328 {
329   if ( this != &src )
330   {
331     ON_Object::operator=(src);
332 
333     m_bValidCamera   = src.m_bValidCamera;
334     m_bValidFrustum  = src.m_bValidFrustum;
335     m_bValidPort     = src.m_bValidPort;
336 
337     m_projection     = src.m_projection;
338 
339     m_bLockCamUp  = src.m_bLockCamUp;
340     m_bLockCamDir = src.m_bLockCamDir;
341     m_bLockCamLoc = src.m_bLockCamLoc;
342     m_frustum_symmetry_flags = src.m_frustum_symmetry_flags;
343 
344     m_CamLoc = src.m_CamLoc;
345     m_CamDir = src.m_CamDir;
346     m_CamUp  = src.m_CamUp;
347     m_CamX   = src.m_CamX;
348     m_CamY   = src.m_CamY;
349     m_CamZ   = src.m_CamZ;
350 
351     m_frus_left      = src.m_frus_left;
352     m_frus_right     = src.m_frus_right;
353     m_frus_bottom    = src.m_frus_bottom;
354     m_frus_top       = src.m_frus_top;
355     m_frus_near      = src.m_frus_near;
356     m_frus_far       = src.m_frus_far;
357 
358     m_port_left      = src.m_port_left;
359     m_port_right     = src.m_port_right;
360     m_port_bottom    = src.m_port_bottom;
361     m_port_top       = src.m_port_top;
362     m_port_near      = src.m_port_near;
363     m_port_far       = src.m_port_far;
364 
365     m_target_point = src.m_target_point;
366 
367     m_clip_mods         = src.m_clip_mods;
368     m_clip_mods_inverse = src.m_clip_mods_inverse;
369 
370     m__MIN_NEAR_OVER_FAR = src.m__MIN_NEAR_OVER_FAR;
371     m__MIN_NEAR_DIST     = src.m__MIN_NEAR_DIST;
372 
373     m_viewport_id = src.m_viewport_id;
374   }
375   return *this;
376 }
377 
378 
Read(ON_BinaryArchive & file)379 ON_BOOL32 ON_Viewport::Read( ON_BinaryArchive& file )
380 {
381   Initialize();
382   int major_version = 0;
383   int minor_version = 1;
384   bool rc = file.Read3dmChunkVersion(&major_version,&minor_version);
385   if (rc && major_version==1)
386   {
387     // common to all 1.x versions
388     int i=0;
389     if (rc) rc = file.ReadInt( &i );
390     if (rc) m_bValidCamera = (i?true:false);
391     if (rc) rc = file.ReadInt( &i );
392     if (rc) m_bValidFrustum = (i?true:false);
393     if (rc) rc = file.ReadInt( &i );
394     if (rc) m_bValidPort = (i?true:false);
395     if (rc) rc = file.ReadInt( &i );
396     if (rc) m_projection = ON::ViewProjection(i);
397     if (rc) rc = file.ReadPoint( m_CamLoc );
398     if (rc) rc = file.ReadVector( m_CamDir );
399     if (rc) rc = file.ReadVector( m_CamUp );
400     if (rc) rc = file.ReadVector( m_CamX );
401     if (rc) rc = file.ReadVector( m_CamY );
402     if (rc) rc = file.ReadVector( m_CamZ );
403     if (rc) rc = file.ReadDouble( &m_frus_left );
404     if (rc) rc = file.ReadDouble( &m_frus_right );
405     if (rc) rc = file.ReadDouble( &m_frus_bottom );
406     if (rc) rc = file.ReadDouble( &m_frus_top );
407     if (rc) rc = file.ReadDouble( &m_frus_near );
408     if (rc) rc = file.ReadDouble( &m_frus_far );
409     if (rc) rc = file.ReadInt( &m_port_left );
410     if (rc) rc = file.ReadInt( &m_port_right );
411     if (rc) rc = file.ReadInt( &m_port_bottom );
412     if (rc) rc = file.ReadInt( &m_port_top );
413     if (rc) rc = file.ReadInt( &m_port_near );
414     if (rc) rc = file.ReadInt( &m_port_far );
415 
416     if (rc && minor_version >= 1 )
417     {
418       // 1.1 fields
419       if (rc) rc = file.ReadUuid(m_viewport_id);
420 
421       if (rc && minor_version >= 2 )
422       {
423         // 1.2 fields
424         bool b;
425         b = false;
426         if (rc) rc = file.ReadBool(&b);
427         if (rc) SetCameraUpLock(b);
428 
429         b = false;
430         if (rc) rc = file.ReadBool(&b);
431         if (rc) SetCameraDirectionLock(b);
432 
433         b = false;
434         if (rc) rc = file.ReadBool(&b);
435         if (rc) SetCameraLocationLock(b);
436 
437         b = false;
438         if (rc) rc = file.ReadBool(&b);
439         if (rc) SetFrustumLeftRightSymmetry(b);
440 
441         b = false;
442         if (rc) rc = file.ReadBool(&b);
443         if (rc) SetFrustumTopBottomSymmetry(b);
444       }
445     }
446 
447     if ( m_bValidCamera )
448     {
449       if ( !m_CamLoc.IsValid() || !m_CamUp.IsValid() || !m_CamDir.IsValid() )
450       {
451         ON_ERROR("ON_Viewport.m_bValidCamera in file was true and it should be false.");
452         m_bValidCamera = false;
453       }
454     }
455 
456     if( m_bValidFrustum )
457     {
458       if (    !ON_IsValid(m_frus_left) || !ON_IsValid(m_frus_right)
459            || !ON_IsValid(m_frus_top)  || !ON_IsValid(m_frus_bottom)
460            || !ON_IsValid(m_frus_near) || !ON_IsValid(m_frus_far)
461            || m_frus_right <= m_frus_left
462            || m_frus_top   <= m_frus_bottom
463            || m_frus_near  <= 0.0
464            || m_frus_far   <= m_frus_near
465          )
466       {
467         ON_ERROR("ON_Viewport.m_bValidFrustum in file was true and it should be false.");
468         m_bValidFrustum = false;
469       }
470     }
471   }
472   return rc;
473 }
474 
Write(ON_BinaryArchive & file) const475 ON_BOOL32 ON_Viewport::Write( ON_BinaryArchive& file ) const
476 {
477   int i;
478   bool rc = file.Write3dmChunkVersion(1,2);
479   if (rc)
480   {
481     i = m_bValidCamera?1:0;
482     if (rc) rc = file.WriteInt( i );
483     i = m_bValidFrustum?1:0;
484     if (rc) rc = file.WriteInt( i );
485     i = m_bValidPort?1:0;
486     if (rc) rc = file.WriteInt( i );
487     i = m_projection;
488     if ( file.Archive3dmVersion() <= 4 && IsPerspectiveProjection() )
489     {
490       // V4 files do not support 2 point perspective projection
491       i = ON::perspective_view;
492     }
493     if (rc) rc = file.WriteInt( i );
494     if (rc) rc = file.WritePoint( m_CamLoc );
495     if (rc) rc = file.WriteVector( m_CamDir );
496     if (rc) rc = file.WriteVector( m_CamUp );
497     if (rc) rc = file.WriteVector( m_CamX );
498     if (rc) rc = file.WriteVector( m_CamY );
499     if (rc) rc = file.WriteVector( m_CamZ );
500     if (rc) rc = file.WriteDouble( m_frus_left );
501     if (rc) rc = file.WriteDouble( m_frus_right );
502     if (rc) rc = file.WriteDouble( m_frus_bottom );
503     if (rc) rc = file.WriteDouble( m_frus_top );
504     if (rc) rc = file.WriteDouble( m_frus_near );
505     if (rc) rc = file.WriteDouble( m_frus_far );
506     if (rc) rc = file.WriteInt( m_port_left );
507     if (rc) rc = file.WriteInt( m_port_right );
508     if (rc) rc = file.WriteInt( m_port_bottom );
509     if (rc) rc = file.WriteInt( m_port_top );
510     if (rc) rc = file.WriteInt( m_port_near );
511     if (rc) rc = file.WriteInt( m_port_far );
512 
513     // 1.1 fields
514     if (rc) rc = file.WriteUuid(m_viewport_id);
515 
516     // 1.2 fields
517     bool b;
518 
519     b = CameraUpIsLocked();
520     if (rc) rc = file.WriteBool(b);
521 
522     b = CameraDirectionIsLocked();
523     if (rc) rc = file.WriteBool(b);
524 
525     b = CameraLocationIsLocked();
526     if (rc) rc = file.WriteBool(b);
527 
528     b = FrustumIsLeftRightSymmetric();
529     if (rc) rc = file.WriteBool(b);
530 
531     b = FrustumIsTopBottomSymmetric();
532     if (rc) rc = file.WriteBool(b);
533   }
534   return rc;
535 }
536 
IsValidCamera() const537 bool ON_Viewport::IsValidCamera() const
538 {
539   return ( m_bValidCamera );
540 }
541 
IsValidFrustum() const542 bool ON_Viewport::IsValidFrustum() const
543 {
544   return ( m_bValidFrustum );
545 }
546 
IsValid(ON_TextLog * text_log) const547 ON_BOOL32 ON_Viewport::IsValid( ON_TextLog* text_log ) const
548 {
549   if ( !IsValidCamera() )
550   {
551     if ( 0 != text_log )
552     {
553       text_log->Print("invalid viewport camera settings.\n");
554     }
555     return false;
556   }
557   if ( !IsValidFrustum() )
558   {
559     if ( 0 != text_log )
560     {
561       text_log->Print("invalid viewport frustum settings.\n");
562     }
563     return false;
564   }
565   if ( !m_bValidPort )
566   {
567     if ( 0 != text_log )
568     {
569       text_log->Print("invalid viewport port extents settings.\n");
570     }
571     return false;
572   }
573   return true;
574 }
575 
576 
Dimension() const577 int ON_Viewport::Dimension() const
578 {
579   return 3;
580 }
581 
GetNearPlane(ON_Plane & near_plane) const582 bool ON_Viewport::GetNearPlane( ON_Plane& near_plane ) const
583 {
584   bool rc = IsValidFrustum() && IsValidCamera();
585   if ( rc )
586   {
587     near_plane.origin = m_CamLoc - m_frus_near*m_CamZ;
588     near_plane.xaxis = m_CamX;
589     near_plane.yaxis = m_CamY;
590     near_plane.zaxis = m_CamZ;
591     near_plane.UpdateEquation();
592   }
593   return rc;
594 }
595 
GetNearPlaneEquation(ON_PlaneEquation & near_plane_equation) const596 bool ON_Viewport::GetNearPlaneEquation(
597   ON_PlaneEquation& near_plane_equation
598   ) const
599 {
600   bool rc = m_bValidCamera && m_bValidFrustum;
601   if (rc)
602   {
603     near_plane_equation.ON_3dVector::operator=(m_CamZ);
604     near_plane_equation.d = -near_plane_equation.ON_3dVector::operator*(m_CamLoc - m_frus_near*m_CamZ);
605   }
606   return rc;
607 }
608 
609 
GetFarPlane(ON_Plane & far_plane) const610 bool ON_Viewport::GetFarPlane( ON_Plane& far_plane ) const
611 {
612   bool rc = IsValidFrustum() && IsValidCamera();
613   if ( rc )
614   {
615     far_plane.origin = m_CamLoc - m_frus_far*m_CamZ;
616     far_plane.xaxis = m_CamX;
617     far_plane.yaxis = m_CamY;
618     far_plane.zaxis = m_CamZ;
619     far_plane.UpdateEquation();
620   }
621   return rc;
622 }
623 
GetFarPlaneEquation(ON_PlaneEquation & far_plane_equation) const624 bool ON_Viewport::GetFarPlaneEquation(
625   ON_PlaneEquation& far_plane_equation
626   ) const
627 {
628   bool rc = m_bValidCamera && m_bValidFrustum;
629   if (rc)
630   {
631     far_plane_equation.ON_3dVector::operator=(m_CamZ);
632     far_plane_equation.d = -far_plane_equation.ON_3dVector::operator*(m_CamLoc - m_frus_far*m_CamZ);
633   }
634   return rc;
635 }
636 
637 
GetViewPlane(double view_plane_depth,ON_Plane & view_plane) const638 bool ON_Viewport::GetViewPlane(
639   double view_plane_depth,
640   ON_Plane& view_plane
641   ) const
642 {
643   bool rc = IsValidFrustum() && IsValidCamera();
644   if ( rc )
645   {
646     view_plane.origin = m_CamLoc - view_plane_depth*m_CamZ;
647     view_plane.xaxis = m_CamX;
648     view_plane.yaxis = m_CamY;
649     view_plane.zaxis = m_CamZ;
650     view_plane.UpdateEquation();
651   }
652   return rc;
653 }
654 
GetViewPlaneEquation(double view_plane_depth,ON_PlaneEquation & view_plane_equation) const655 bool ON_Viewport::GetViewPlaneEquation(
656   double view_plane_depth,
657   ON_PlaneEquation& view_plane_equation
658   ) const
659 {
660   bool rc = m_bValidCamera && m_bValidFrustum;
661   if (rc)
662   {
663     view_plane_equation.ON_3dVector::operator=(m_CamZ);
664     view_plane_equation.d = -view_plane_equation.ON_3dVector::operator*(m_CamLoc - view_plane_depth*m_CamZ);
665   }
666   return rc;
667 }
668 
669 
GetNearRect(ON_3dPoint & left_bottom,ON_3dPoint & right_bottom,ON_3dPoint & left_top,ON_3dPoint & right_top) const670 bool ON_Viewport::GetNearRect(
671        ON_3dPoint& left_bottom,
672        ON_3dPoint& right_bottom,
673        ON_3dPoint& left_top,
674        ON_3dPoint& right_top
675        ) const
676 {
677   ON_Plane near_plane;
678   bool rc = GetNearPlane( near_plane );
679   if (rc ) {
680     double x = 1.0, y = 1.0;
681     GetViewScale(&x,&y);
682     x = 1.0/x;
683     y = 1.0/y;
684     left_bottom  = near_plane.PointAt( x*m_frus_left,  y*m_frus_bottom );
685     right_bottom = near_plane.PointAt( x*m_frus_right, y*m_frus_bottom );
686     left_top     = near_plane.PointAt( x*m_frus_left,  y*m_frus_top );
687     right_top    = near_plane.PointAt( x*m_frus_right, y*m_frus_top );
688   }
689   return rc;
690 }
691 
GetFarRect(ON_3dPoint & left_bottom,ON_3dPoint & right_bottom,ON_3dPoint & left_top,ON_3dPoint & right_top) const692 bool ON_Viewport::GetFarRect(
693        ON_3dPoint& left_bottom,
694        ON_3dPoint& right_bottom,
695        ON_3dPoint& left_top,
696        ON_3dPoint& right_top
697        ) const
698 {
699   ON_Plane far_plane;
700   bool rc = GetFarPlane( far_plane );
701   if (rc )
702   {
703     double s = IsPerspectiveProjection()
704             ? m_frus_far/m_frus_near
705             : 1.0;
706     double x = 1.0, y = 1.0;
707     GetViewScale(&x,&y);
708     x = 1.0/x;
709     y = 1.0/y;
710     left_bottom  = far_plane.PointAt( s*x*m_frus_left,  s*y*m_frus_bottom );
711     right_bottom = far_plane.PointAt( s*x*m_frus_right, s*y*m_frus_bottom );
712     left_top     = far_plane.PointAt( s*x*m_frus_left,  s*y*m_frus_top );
713     right_top    = far_plane.PointAt( s*x*m_frus_right, s*y*m_frus_top );
714   }
715   return rc;
716 }
717 
GetViewPlaneRect(double view_plane_depth,ON_3dPoint & left_bottom,ON_3dPoint & right_bottom,ON_3dPoint & left_top,ON_3dPoint & right_top) const718 bool ON_Viewport::GetViewPlaneRect(
719         double view_plane_depth,
720         ON_3dPoint& left_bottom,
721         ON_3dPoint& right_bottom,
722         ON_3dPoint& left_top,
723         ON_3dPoint& right_top
724         ) const
725 {
726   ON_Plane view_plane;
727   bool rc = GetViewPlane( view_plane_depth, view_plane );
728   if (rc )
729   {
730     double s = IsPerspectiveProjection()
731             ? view_plane_depth/m_frus_near
732             : 1.0;
733     double x = 1.0, y = 1.0;
734     GetViewScale(&x,&y);
735     x = 1.0/x;
736     y = 1.0/y;
737     left_bottom  = view_plane.PointAt( s*x*m_frus_left,  s*y*m_frus_bottom );
738     right_bottom = view_plane.PointAt( s*x*m_frus_right, s*y*m_frus_bottom );
739     left_top     = view_plane.PointAt( s*x*m_frus_left,  s*y*m_frus_top );
740     right_top    = view_plane.PointAt( s*x*m_frus_right, s*y*m_frus_top );
741   }
742   return rc;
743 }
744 
745 
GetBBox(double * boxmin,double * boxmax,ON_BOOL32 bGrowBox) const746 ON_BOOL32 ON_Viewport::GetBBox(
747        double* boxmin,
748        double* boxmax,
749        ON_BOOL32 bGrowBox
750        ) const
751 {
752   ON_3dPoint corners[9];
753   bool rc = GetNearRect(corners[0],corners[1],corners[2],corners[3]);
754   if (rc)
755     rc = GetFarRect(corners[4],corners[5],corners[6],corners[7]);
756   corners[8] = m_CamLoc;
757   if (rc)
758   {
759     rc = ON_GetPointListBoundingBox(
760             3, 0, 9,
761             3, &corners[0].x,
762             boxmin, boxmax,  bGrowBox?true:false
763             );
764   }
765   return rc;
766 }
767 
Transform(const ON_Xform & xform)768 ON_BOOL32 ON_Viewport::Transform( const ON_Xform& xform )
769 {
770   ON_BOOL32 rc = IsValidCamera();
771   if (rc) {
772     // save input settings
773     const ON_3dPoint c0 = m_CamLoc;
774     const ON_3dPoint u0 = m_CamUp;
775     const ON_3dPoint d0 = m_CamDir;
776     const ON_3dPoint x0 = m_CamX;
777     const ON_3dPoint y0 = m_CamY;
778     const ON_3dPoint z0 = m_CamZ;
779 
780     // compute transformed settings
781     ON_3dPoint c = xform*c0;
782     ON_3dVector u = (xform*(c0 + u0)) - c;
783     ON_3dVector d = (xform*(c0 + d0)) - c;
784 
785     if ( m_bLockCamLoc )
786       c = m_CamLoc;
787     if ( m_bLockCamUp )
788       u = m_CamY;
789     if ( m_bLockCamDir )
790       d = -m_CamZ;
791 
792     if (    !u.IsValid() || !d.IsValid()
793          || u.IsTiny()   || d.IsTiny()
794          || ON_CrossProduct(u,d).IsTiny() )
795     {
796       rc = false;
797     }
798     else
799     {
800       if ( m_bLockCamUp && !m_bLockCamDir )
801       {
802         d.Unitize();
803         if ( fabs(d*u) <= ON_ZERO_TOLERANCE )
804           d = -m_CamZ;
805       }
806       else if ( m_bLockCamDir && !m_bLockCamUp )
807       {
808         u.Unitize();
809         if ( fabs(d*u) <= ON_ZERO_TOLERANCE )
810           u = m_CamY;
811       }
812 
813       // set new camera position
814       if ( !m_bLockCamLoc )
815         SetCameraLocation(c);
816       if ( !m_bLockCamDir )
817         SetCameraDirection(d);
818       if ( !m_bLockCamUp)
819         SetCameraUp(u);
820       rc = SetCameraFrame();
821       if ( !rc )
822       {
823         // restore input settings
824         m_CamLoc = c0;
825         m_CamUp  = u0;
826         m_CamDir = d0;
827         m_CamX = x0;
828         m_CamY = y0;
829         m_CamZ = z0;
830       }
831     }
832   }
833   return rc;
834 }
835 
SetCameraLocation(const ON_3dPoint & p)836 bool ON_Viewport::SetCameraLocation( const ON_3dPoint& p )
837 {
838   if ( m_bLockCamLoc )
839   {
840     if ( m_CamLoc.IsValid() )
841     {
842       return (p == m_CamLoc);
843     }
844   }
845   if ( p != ON_3dPoint::UnsetPoint && !p.IsValid() )
846     return false;
847   m_CamLoc = p;
848   if ( !m_CamLoc.IsValid() )
849     m_bValidCamera = false;
850   return m_bValidCamera;
851 }
852 
SetCameraDirection(const ON_3dVector & v)853 bool ON_Viewport::SetCameraDirection( const ON_3dVector& v )
854 {
855   if ( m_bLockCamDir )
856   {
857     if ( m_CamDir.IsValid() && !m_CamDir.IsTiny() )
858     {
859       return (v == m_CamDir);
860     }
861   }
862   if ( !v.IsValid() || v.IsTiny() )
863     return false;
864   m_CamDir = v;
865   return SetCameraFrame();
866 }
867 
SetCameraUp(const ON_3dVector & v)868 bool ON_Viewport::SetCameraUp( const ON_3dVector& v )
869 {
870   if ( m_bLockCamUp )
871   {
872     if ( m_CamUp.IsValid() && !m_CamUp.IsTiny() )
873     {
874       return (v == m_CamUp);
875     }
876   }
877   if ( !v.IsValid() || v.IsTiny() )
878     return false;
879   m_CamUp = v;
880   return SetCameraFrame();
881 }
882 
883 //ON_BOOL32 ON_Viewport::SetTargetDistance( double d )
884 //{
885 //  m_target_distance = (d>0.0) ? d : 0.0;
886 //  return (d>=0.0) ? true : false;
887 //}
888 
SetCameraFrame()889 bool ON_Viewport::SetCameraFrame()
890 {
891   m_bValidCamera = false;
892 
893   if ( !m_CamDir.IsValid() || !m_CamUp.IsValid() )
894     return false;
895 
896   double d;
897   ON_3dVector CamX, CamY, CamZ;
898 
899   if ( m_bLockCamUp && !m_bLockCamDir )
900   {
901     // up takes precedence over direction
902     CamY = m_CamUp;
903     if ( !CamY.IsValid() )
904       return false;
905     if ( !CamY.Unitize() )
906       return false;
907 
908     d = m_CamDir*CamY;
909     CamZ = -m_CamDir + d*CamY;
910     if ( !CamZ.IsValid() )
911       return false;
912     if ( !CamZ.Unitize() )
913       return false;
914   }
915   else
916   {
917     // direction takes precedence over up
918     CamZ = -m_CamDir;
919     if ( !CamZ.IsValid() )
920       return false;
921     if ( !CamZ.Unitize() )
922       return false;
923 
924     d = m_CamUp*CamZ;
925     CamY = m_CamUp - d*CamZ;
926     if ( !CamY.IsValid() )
927       return false;
928     if ( !CamY.Unitize() )
929       return false;
930   }
931 
932   CamX = ON_CrossProduct( CamY, CamZ );
933   if ( !CamX.IsValid() )
934     return false;
935   if ( !CamX.Unitize() )
936     return false;
937 
938   // Gaurd against garbage resulting from nearly parallel
939   // and/or ultra short short dir and up.
940   if ( !ON__IsCameraFrameUnitVectorHelper(CamX) )
941     return false;
942   if ( !ON__IsCameraFrameUnitVectorHelper(CamY) )
943     return false;
944   if ( !ON__IsCameraFrameUnitVectorHelper(CamZ) )
945     return false;
946   if ( !ON__IsCameraFramePerpindicular(CamX,CamY) )
947     return false;
948   if ( !ON__IsCameraFramePerpindicular(CamY,CamZ) )
949     return false;
950   if ( !ON__IsCameraFramePerpindicular(CamZ,CamX) )
951     return false;
952 
953   m_CamX = CamX;
954   m_CamY = CamY;
955   m_CamZ = CamZ;
956 
957   m_bValidCamera = m_CamLoc.IsValid();
958   return m_bValidCamera;
959 }
960 
CameraLocation() const961 ON_3dPoint ON_Viewport::CameraLocation() const
962 {
963   return m_CamLoc;
964 }
965 
CameraDirection() const966 ON_3dVector ON_Viewport::CameraDirection() const
967 {
968   return m_CamDir;
969 }
970 
CameraUp() const971 ON_3dVector ON_Viewport::CameraUp() const
972 {
973   return m_CamUp;
974 }
975 
CameraLocationIsLocked() const976 bool ON_Viewport::CameraLocationIsLocked() const
977 {
978   return m_bLockCamLoc;
979 }
980 
CameraDirectionIsLocked() const981 bool ON_Viewport::CameraDirectionIsLocked() const
982 {
983   return m_bLockCamDir;
984 }
985 
CameraUpIsLocked() const986 bool ON_Viewport::CameraUpIsLocked() const
987 {
988   return m_bLockCamUp;
989 }
990 
FrustumIsLeftRightSymmetric() const991 bool ON_Viewport::FrustumIsLeftRightSymmetric() const
992 {
993   return (0 != (0x02 & m_frustum_symmetry_flags));
994 }
995 
FrustumIsTopBottomSymmetric() const996 bool ON_Viewport::FrustumIsTopBottomSymmetric() const
997 {
998   return (0 != (0x01 & m_frustum_symmetry_flags));
999 }
1000 
UnlockCamera()1001 void ON_Viewport::UnlockCamera()
1002 {
1003   SetCameraLocationLock(false);
1004   SetCameraDirectionLock(false);
1005   SetCameraUpLock(false);
1006 }
1007 
UnlockFrustumSymmetry()1008 void ON_Viewport::UnlockFrustumSymmetry()
1009 {
1010   SetFrustumLeftRightSymmetry(false);
1011   SetFrustumTopBottomSymmetry(false);
1012 }
1013 
SetCameraLocationLock(bool bLockCameraLocation)1014 void ON_Viewport::SetCameraLocationLock( bool bLockCameraLocation )
1015 {
1016   m_bLockCamLoc = bLockCameraLocation ? true : false;
1017 }
1018 
SetCameraDirectionLock(bool bLockCameraDirection)1019 void ON_Viewport::SetCameraDirectionLock( bool bLockCameraDirection )
1020 {
1021   m_bLockCamDir = bLockCameraDirection ? true : false;
1022 }
1023 
SetCameraUpLock(bool bLockCameraUp)1024 void ON_Viewport::SetCameraUpLock( bool bLockCameraUp )
1025 {
1026   m_bLockCamUp = bLockCameraUp ? true : false;
1027 }
1028 
SetFrustumLeftRightSymmetry(bool bForceLeftRightSymmetry)1029 void ON_Viewport::SetFrustumLeftRightSymmetry( bool bForceLeftRightSymmetry )
1030 {
1031   if ( bForceLeftRightSymmetry )
1032     m_frustum_symmetry_flags |= 0x02; // set bit 2
1033   else
1034     m_frustum_symmetry_flags &= 0xFD; // clear bit 2
1035 }
1036 
SetFrustumTopBottomSymmetry(bool bForceTopBottomSymmetry)1037 void ON_Viewport::SetFrustumTopBottomSymmetry( bool bForceTopBottomSymmetry )
1038 {
1039   if ( bForceTopBottomSymmetry )
1040     m_frustum_symmetry_flags |= 0x01; // set bit 1
1041   else
1042     m_frustum_symmetry_flags &= 0xFE; // clear bit 1
1043 }
1044 
1045 
1046 
GetDollyCameraVector(int x0,int y0,int x1,int y1,double distance_to_camera,ON_3dVector & dolly_vector) const1047 bool ON_Viewport::GetDollyCameraVector(
1048          int x0, int y0,    // (x,y) screen coords of start point
1049          int x1, int y1,    // (x,y) screen coords of end point
1050          double distance_to_camera, // distance from camera
1051          ON_3dVector& dolly_vector// dolly vector returned here
1052          ) const
1053 {
1054   int port_left, port_right, port_bottom, port_top;
1055   ON_Xform c2w;
1056   dolly_vector.Zero();
1057   bool rc = GetScreenPort( &port_left, &port_right, &port_bottom, &port_top );
1058   if ( rc )
1059     rc = GetXform( ON::clip_cs, ON::world_cs, c2w );
1060   if ( rc ) {
1061     double dx = 0.5*(port_right - port_left);
1062     double dy = 0.5*(port_top - port_bottom);
1063     double dz = 0.5*(FrustumFar() - FrustumNear());
1064     if ( dx == 0.0 || dy == 0.0 || dz == 0.0 )
1065       rc = false;
1066     else {
1067       double z = (distance_to_camera - FrustumNear())/dz - 1.0;
1068       ON_3dPoint c0( (x0-port_left)/dx-1.0, (y0-port_bottom)/dy-1.0, z );
1069       ON_3dPoint c1( (x1-port_left)/dx-1.0, (y1-port_bottom)/dy-1.0, z );
1070       ON_3dPoint w0 = c2w*c0;
1071       ON_3dPoint w1 = c2w*c1;
1072       dolly_vector = w0 - w1;
1073     }
1074   }
1075   return rc;
1076 }
1077 
DollyCamera(const ON_3dVector & dolly)1078 bool ON_Viewport::DollyCamera( const ON_3dVector& dolly )
1079 {
1080   bool rc = false;
1081   if ( m_CamLoc.IsValid() && dolly.IsValid() )
1082   {
1083     m_CamLoc += dolly;
1084     rc = m_bValidCamera;
1085   }
1086   return rc;
1087 }
1088 
DollyFrustum(double dollyDistance)1089 bool ON_Viewport::DollyFrustum( double dollyDistance )
1090 {
1091   bool rc = false;
1092   double new_near, new_far, scale;
1093   if ( m_bValidFrustum )
1094   {
1095     new_near = m_frus_near + dollyDistance;
1096     new_far  = m_frus_far + dollyDistance;
1097     if ( IsPerspectiveProjection() && new_near < m__MIN_NEAR_DIST )
1098     {
1099       new_near = m__MIN_NEAR_DIST;
1100     }
1101     scale = ( IsPerspectiveProjection() )
1102           ? new_near/m_frus_near
1103           : 1.0;
1104     if ( new_near > 0.0 && new_far > new_near && scale > 0.0 )
1105     {
1106       m_frus_near = new_near;
1107       m_frus_far  = new_far;
1108       m_frus_left   *= scale;
1109       m_frus_right  *= scale;
1110       m_frus_top    *= scale;
1111       m_frus_bottom *= scale;
1112       rc = true;
1113     }
1114   }
1115   return rc;
1116 }
1117 
GetCameraFrame(double * CameraLocation,double * CameraX,double * CameraY,double * CameraZ) const1118 bool ON_Viewport::GetCameraFrame(
1119     double* CameraLocation,
1120     double* CameraX,
1121     double* CameraY,
1122     double* CameraZ
1123     ) const
1124 {
1125   if ( CameraLocation ) {
1126     CameraLocation[0] = m_CamLoc.x;
1127     CameraLocation[1] = m_CamLoc.y;
1128     CameraLocation[2] = m_CamLoc.z;
1129   }
1130   if ( CameraX ) {
1131     CameraX[0] = m_CamX.x;
1132     CameraX[1] = m_CamX.y;
1133     CameraX[2] = m_CamX.z;
1134   }
1135   if ( CameraY ) {
1136     CameraY[0] = m_CamY.x;
1137     CameraY[1] = m_CamY.y;
1138     CameraY[2] = m_CamY.z;
1139   }
1140   if ( CameraZ ) {
1141     CameraZ[0] = m_CamZ.x;
1142     CameraZ[1] = m_CamZ.y;
1143     CameraZ[2] = m_CamZ.z;
1144   }
1145   return m_bValidCamera;
1146 }
1147 
CameraX() const1148 ON_3dVector ON_Viewport::CameraX() const
1149 {
1150   return m_CamX;
1151 }
1152 
CameraY() const1153 ON_3dVector ON_Viewport::CameraY() const
1154 {
1155   return m_CamY;
1156 }
1157 
CameraZ() const1158 ON_3dVector ON_Viewport::CameraZ() const
1159 {
1160   return m_CamZ;
1161 }
1162 
IsCameraFrameWorldPlan(int * xindex,int * yindex,int * zindex)1163 bool ON_Viewport::IsCameraFrameWorldPlan(
1164       int* xindex,
1165       int* yindex,
1166       int* zindex
1167       )
1168 {
1169   int i;
1170   int ix = 0;
1171   int iy = 0;
1172   int iz = 0;
1173   double X[3], Y[3], Z[3];
1174   bool rc = GetCameraFrame( NULL, X, Y, Z );
1175   if ( rc ) {
1176     for ( i = 0; i < 3; i++ ) {
1177       if ( X[i] == 1.0 ) {
1178         ix = i+1;
1179         break;
1180       }
1181       if ( X[i] == -1.0 ) {
1182         ix = -(i+1);
1183         break;
1184       }
1185     }
1186     for ( i = 0; i < 3; i++ ) {
1187       if ( Y[i] == 1.0 ) {
1188         iy = i+1;
1189         break;
1190       }
1191       if ( Y[i] == -1.0 ) {
1192         iy = -(i+1);
1193         break;
1194       }
1195     }
1196     for ( i = 0; i < 3; i++ ) {
1197       if ( Z[i] == 1.0 ) {
1198         iz = i+1;
1199         break;
1200       }
1201       if ( Z[i] == -1.0 ) {
1202         iz = -(i+1);
1203         break;
1204       }
1205     }
1206     rc = ( iz != 0 ) ? 1 : 0;
1207   }
1208   if ( xindex ) *xindex = ix;
1209   if ( yindex ) *yindex = iy;
1210   if ( zindex ) *zindex = iz;
1211   return rc;
1212 }
1213 
1214 
GetCameraExtents(int count,int stride,const double * points,ON_BoundingBox & cbox,int bGrowBox) const1215 bool ON_Viewport::GetCameraExtents(
1216     // returns bounding box in camera coordinates - this is useful information
1217     // for setting view frustrums to include the point list
1218     int count,            // count = number of 3d points
1219     int stride,           // stride = number of doubles to skip between points (>=3)
1220     const double* points, // 3d points in world coordinates
1221     ON_BoundingBox& cbox, // bounding box in camera coordinates
1222     int bGrowBox         // set to true to grow existing box
1223     ) const
1224 {
1225   ON_Xform w2c;
1226   bool rc = bGrowBox?true:false;
1227   int i;
1228   if ( count > 0 && stride >= 3 && points != NULL ) {
1229     rc = false;
1230     if ( GetXform( ON::world_cs, ON::camera_cs, w2c ) ) {
1231       rc = true;
1232       for ( i = 0; i < count && rc; i++, points += stride ) {
1233         rc = cbox.Set( w2c*ON_3dPoint(points), bGrowBox );
1234         bGrowBox = true;
1235       }
1236     }
1237   }
1238   return rc;
1239 }
1240 
GetCameraExtents(const ON_BoundingBox & wbox,ON_BoundingBox & cbox,int bGrowBox) const1241 bool ON_Viewport::GetCameraExtents(
1242     // returns bounding box in camera coordinates - this is useful information
1243     // for setting view frustrums to include the point list
1244     const ON_BoundingBox& wbox, // world coordinate bounding box
1245     ON_BoundingBox& cbox, // bounding box in camera coordinates
1246     int bGrowBox         // set to true to grow existing box
1247     ) const
1248 {
1249   bool rc = false;
1250   ON_3dPointArray corners;
1251   if ( wbox.GetCorners( corners ) ) {
1252     rc = GetCameraExtents( corners.Count(), 3, &corners.Array()[0].x, cbox, bGrowBox );
1253   }
1254   return rc;
1255 }
1256 
GetCameraExtents(ON_3dPoint & worldSphereCenter,double worldSphereRadius,ON_BoundingBox & cbox,int bGrowBox) const1257 bool ON_Viewport::GetCameraExtents(
1258     // returns bounding box in camera coordinates - this is useful information
1259     // for setting view frustrums to include the point list
1260     ON_3dPoint& worldSphereCenter,
1261     double worldSphereRadius,
1262     ON_BoundingBox& cbox, // bounding box in camera coordinates
1263     int bGrowBox         // set to true to grow existing box
1264     ) const
1265 {
1266   bool rc = false;
1267   ON_BoundingBox sbox;
1268   if ( GetCameraExtents( 1, 3, &worldSphereCenter.x, sbox, false ) ) {
1269     const double r = fabs( worldSphereRadius );
1270     sbox[0][0] -= r;
1271     sbox[0][1] -= r;
1272     sbox[0][2] -= r;
1273     sbox[1][0] += r;
1274     sbox[1][1] += r;
1275     sbox[1][2] += r;
1276     if ( bGrowBox )
1277       cbox.Union( sbox );
1278     else
1279       cbox = sbox;
1280     rc = true;
1281   }
1282   return rc;
1283 }
1284 
1285 
UpdateTargetPointHelper(ON_Viewport & vp,double target_distance)1286 static void UpdateTargetPointHelper( ON_Viewport& vp, double target_distance )
1287 {
1288   if ( !vp.IsValidCamera() || !vp.IsValidFrustum() )
1289     return;
1290   if ( !ON_IsValid(target_distance) || target_distance <= 0.0 )
1291     return;
1292 
1293   ON_3dPoint old_tp = vp.TargetPoint();
1294 
1295   // Put the target directly in front of the camera.
1296   // The target_tol test is here to avoid making insignificant
1297   // changes that appear in the user interface and upset users
1298   // who find 1.00000000001 to be grossly different from 1.0.
1299   double target_tol = 1.0e-5*(vp.FrustumWidth()+vp.FrustumHeight())
1300                     + ON_ZERO_TOLERANCE;
1301   ON_3dPoint new_tp = vp.CameraLocation() - target_distance*vp.CameraZ();
1302   if ( new_tp.IsValid()
1303        && (!old_tp.IsValid() || new_tp.DistanceTo(old_tp) > target_tol)
1304      )
1305   {
1306     vp.SetTargetPoint(new_tp);
1307   }
1308 }
1309 
ChangeToParallelProjection(bool bSymmetricFrustum)1310 bool ON_Viewport::ChangeToParallelProjection( bool bSymmetricFrustum )
1311 {
1312   bool rc = (m_bValidCamera && m_bValidFrustum);
1313 
1314   SetCameraUpLock(false);
1315   SetCameraDirectionLock(false);
1316 
1317   if (    ON::parallel_view == m_projection
1318        && (bSymmetricFrustum?true:false) == FrustumIsLeftRightSymmetric()
1319        && (bSymmetricFrustum?true:false) == FrustumIsTopBottomSymmetric()
1320      )
1321   {
1322     // no changes are required
1323     return rc;
1324   }
1325 
1326   // if needed, make frustum symmetric
1327   // If bSymmetricFrustum is true and the input frustum is not symmetric,
1328   // then this will dolly the camera location.
1329   ChangeToSymmetricFrustum(bSymmetricFrustum,bSymmetricFrustum,ON_UNSET_VALUE);
1330   SetFrustumTopBottomSymmetry(bSymmetricFrustum);
1331   SetFrustumLeftRightSymmetry(bSymmetricFrustum);
1332 
1333   const ON::view_projection old_projection = m_projection;
1334   double target_distance = TargetDistance(true);
1335   if ( !ON_IsValid(target_distance)
1336        || !m_bValidFrustum
1337        || !ON_IsValid(m_frus_near)
1338        || m_frus_near <= 0.0
1339        || target_distance <= m_frus_near
1340        )
1341   {
1342     target_distance = 0.0; // makes it easier to check for valid target distance
1343   }
1344 
1345   // if needed change projection
1346   if ( ON::parallel_view != old_projection )
1347   {
1348     if ( !SetProjection(ON::parallel_view) )
1349       rc = false;
1350   }
1351 
1352   if ( rc )
1353   {
1354     if ( ON::perspective_view == old_projection )
1355     {
1356       // change from a perspective to a parallel projection
1357       if ( target_distance > 0.0 && 0.0 < m_frus_near && m_frus_near < m_frus_far )
1358       {
1359         // Update the frustum so that the plane through the target point
1360         // is the one that is parallel projected.  This is generally
1361         // the best choice when switching from perspective to
1362         // parallel projection. If needed, SetFrustum() will make the
1363         // frustum symmetric
1364         double s = target_distance/m_frus_near;
1365         double l = m_frus_left*s;
1366         double r = m_frus_right*s;
1367         double t = m_frus_top*s;
1368         double b = m_frus_bottom*s;
1369         if ( !SetFrustum( l, r, b, t, m_frus_near, m_frus_far ))
1370           rc = false;
1371       }
1372     }
1373     if ( m_target_point.IsValid() )
1374       UpdateTargetPointHelper(*this,target_distance);
1375   }
1376 
1377   return rc;
1378 }
1379 
ChangeFromParallelToPerspectiveHelper(ON_Viewport & vp,double target_distance,double lens_length)1380 static bool ChangeFromParallelToPerspectiveHelper( ON_Viewport& vp, double target_distance, double lens_length )
1381 {
1382   // helper use by ChangeToPerspectiveProjection() and ChangeToTwoPointPerspectiveProjection()
1383   if ( ON::perspective_view == vp.Projection() )
1384     return true;
1385 
1386   if ( !vp.SetProjection(ON::perspective_view) )
1387     return false;
1388 
1389   // change from a parallel to a perspective  projection
1390   double frus_left,frus_right,frus_bottom,frus_top,frus_near,frus_far;
1391   if ( !vp.GetFrustum(&frus_left,&frus_right,&frus_bottom,&frus_top,&frus_near,&frus_far) )
1392     return false;
1393 
1394   // Using width because it works for both two point and ordinary perspective
1395   const double width = fabs(frus_right - frus_left);
1396   const ON_3dPoint width_point = ( ON_IsValid(target_distance) && target_distance > 0.0)
1397                                ? vp.CameraLocation() - target_distance*vp.CameraZ()
1398                                : ON_3dPoint::UnsetPoint;
1399 
1400   if ( frus_near < 1.0e-8 && frus_far >= 1.0e-7)
1401   {
1402     frus_near = 1.0e-8;
1403     vp.SetFrustum(frus_left,frus_right,frus_bottom,frus_top,frus_near,frus_far);
1404     vp.GetFrustum(&frus_left,&frus_right,&frus_bottom,&frus_top,&frus_near,&frus_far);
1405   }
1406 
1407   bool rc = false;
1408 
1409   if ( ON_IsValid(lens_length) && lens_length > 0.0 )
1410   {
1411     rc = vp.SetCamera35mmLensLength(lens_length);
1412     if ( rc
1413          && width_point.IsValid()
1414          && !vp.CameraLocationIsLocked()
1415          && vp.GetFrustum(&frus_left,&frus_right,&frus_bottom,&frus_top,&frus_near,&frus_far)
1416          && frus_near > 0.0
1417        )
1418     {
1419       double d = (vp.CameraLocation() - width_point)*vp.CameraZ();
1420       if ( d > frus_near )
1421       {
1422         // make sure target plane is visible
1423         double w = fabs(frus_right - frus_left)*d/frus_near;
1424         if ( width > w && w > 0.0 )
1425         {
1426           // move camera back to increase "w" back up to "width"
1427           ON_3dPoint cam_loc0 = vp.CameraLocation();
1428           double dz = d*(width/w - 1.0);
1429           ON_3dPoint cam_loc1 = cam_loc0 + dz*vp.CameraZ();
1430           vp.SetCameraLocation(cam_loc1);
1431         }
1432       }
1433     }
1434   }
1435   return rc;
1436 }
1437 
ChangeToPerspectiveProjection(double target_distance,bool bSymmetricFrustum,double lens_length)1438 bool ON_Viewport::ChangeToPerspectiveProjection(
1439           double target_distance,
1440           bool bSymmetricFrustum,
1441           double lens_length
1442         )
1443 {
1444   bool rc = (m_bValidCamera && m_bValidFrustum);
1445 
1446   SetCameraUpLock(false);
1447   SetCameraDirectionLock(false);
1448 
1449   if (    ON::perspective_view == m_projection
1450        && (bSymmetricFrustum?true:false) == FrustumIsLeftRightSymmetric()
1451        && (bSymmetricFrustum?true:false) == FrustumIsTopBottomSymmetric()
1452      )
1453   {
1454     double current_lens_length = lens_length;
1455     if ( ON_IsValid(lens_length)
1456          && lens_length > 0.0
1457          && GetCamera35mmLensLength(&current_lens_length)
1458          && fabs(current_lens_length - lens_length) > 0.125
1459         )
1460     {
1461       SetCamera35mmLensLength(lens_length);
1462     }
1463     // no other changes are required
1464     return rc;
1465   }
1466 
1467   if ( !ON_IsValid(target_distance) || target_distance <= 0.0 )
1468     target_distance = TargetDistance(true);
1469 
1470   // If needed, make frustum symmetric.  This may move the
1471   // camera location in a direction perpendicular to m_CamZ.
1472   ChangeToSymmetricFrustum(bSymmetricFrustum,bSymmetricFrustum,target_distance);
1473   SetFrustumTopBottomSymmetry(bSymmetricFrustum);
1474   SetFrustumLeftRightSymmetry(bSymmetricFrustum);
1475 
1476   // If needed change projection to perspective.  If
1477   // the input projection is parallel, this may move
1478   // the camera in the m_CamZ direction to preserve
1479   // viewing the target plane.
1480   if (!ChangeFromParallelToPerspectiveHelper(*this,target_distance,lens_length))
1481     rc = false;
1482 
1483   if ( rc && m_target_point.IsValid() )
1484     UpdateTargetPointHelper(*this,target_distance);
1485 
1486   return rc;
1487 }
1488 
1489 static
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)1490 bool GetTwoPointPerspectiveUpAndDirHelper( const ON_3dVector& up,
1491                                            const ON_3dVector& CamDir,
1492                                            const ON_3dVector& CamY,
1493                                            const ON_3dVector& CamZ,
1494                                            ON_3dVector& new_up,
1495                                            ON_3dVector& new_dir
1496                                            )
1497 {
1498   // get up direction
1499   ON_3dVector unit_up;
1500   ON_3dVector unit_dir;
1501   if ( up.IsZero() && CamY.IsValid() && CamY.IsUnitVector() )
1502   {
1503     new_up = CamY;
1504     if ( fabs(new_up.z) >= fabs(new_up.y) && fabs(new_up.z) >= fabs(new_up.x) )
1505       new_up.Set(0.0,0.0,new_up.z<0.0?-1.0:1.0);
1506     else if ( fabs(new_up.y) >= fabs(new_up.z) && fabs(new_up.y) >= fabs(new_up.x) )
1507       new_up.Set(0.0,new_up.y<0.0?-1.0:1.0,0.0);
1508     else
1509       new_up.Set(new_up.x<0.0?-1.0:1.0,0.0,0.0);
1510     unit_up = new_up;
1511   }
1512   else if ( up.IsValid() && !up.IsTiny() )
1513   {
1514     unit_up = up;
1515     if ( !unit_up.IsUnitVector() && !unit_up.Unitize() )
1516       return false;
1517     new_up = up;
1518   }
1519   else
1520   {
1521     return false;
1522   }
1523 
1524   // get camera dir
1525   if ( CamDir.IsValid() && !CamDir.IsTiny() )
1526   {
1527     new_dir = CamDir;
1528     unit_dir = new_dir;
1529     if ( unit_dir.Unitize() && ON__IsCameraFramePerpindicular(unit_up,unit_dir) )
1530       return true;
1531     unit_dir = unit_dir - (unit_dir*unit_up)*unit_up;
1532     if ( unit_dir.IsValid() && !unit_dir.IsTiny() && unit_dir.Unitize() )
1533     {
1534       new_dir = unit_dir;
1535       return true;
1536     }
1537   }
1538 
1539   if ( CamZ.IsValid() && CamZ.IsUnitVector() )
1540   {
1541     new_dir = -CamZ;
1542     unit_dir = new_dir;
1543     if ( unit_dir.Unitize() && ON__IsCameraFramePerpindicular(unit_up,unit_dir) )
1544       return true;
1545     unit_dir = unit_dir - (new_dir*unit_up)*unit_up;
1546     if ( unit_dir.IsValid() && !unit_dir.IsTiny() && unit_dir.Unitize() )
1547     {
1548       new_dir = unit_dir;
1549       return true;
1550     }
1551   }
1552 
1553   return false;
1554 }
1555 
ChangeToTwoPointPerspectiveProjection(double target_distance,ON_3dVector up,double lens_length)1556 bool ON_Viewport::ChangeToTwoPointPerspectiveProjection(
1557         double target_distance,
1558         ON_3dVector up,
1559         double lens_length
1560         )
1561 {
1562   bool rc = (m_bValidCamera && m_bValidFrustum);
1563 
1564   SetCameraDirectionLock(false);
1565 
1566   if ( IsTwoPointPerspectiveProjection() )
1567   {
1568     double current_lens_length = lens_length;
1569     if ( ON_IsValid(lens_length)
1570          && lens_length > 0.0
1571          && GetCamera35mmLensLength(&current_lens_length)
1572          && fabs(current_lens_length - lens_length) > 0.125
1573         )
1574     {
1575       SetCamera35mmLensLength(lens_length);
1576     }
1577     // no other changes are required
1578     return rc;
1579   }
1580 
1581   if ( !ON_IsValid(target_distance) || target_distance <= 0.0 )
1582     target_distance = TargetDistance(true);
1583 
1584   // if needed, make frustum left/right symmetric. This may move the
1585   // camera location in a direction perpendicular to m_CamZ.
1586   ChangeToSymmetricFrustum(true,false,target_distance);
1587   SetFrustumLeftRightSymmetry(true);
1588   SetFrustumTopBottomSymmetry(false);
1589 
1590   // If needed change projection to perspective.  If
1591   // the input projection is parallel, this may move
1592   // the camera in the m_CamZ direction to preserve
1593   // viewing the target plane.
1594   if (!ChangeFromParallelToPerspectiveHelper(*this,target_distance,lens_length))
1595     rc = false;
1596 
1597   if ( rc )
1598   {
1599     ON_3dVector new_up = m_CamY;
1600     ON_3dVector new_dir = -m_CamZ;
1601     ON_3dPoint  new_loc = m_CamLoc;
1602     if ( !GetTwoPointPerspectiveUpAndDirHelper(up,m_CamDir,m_CamY,m_CamZ,new_up,new_dir) )
1603     {
1604       rc = false;
1605     }
1606     else
1607     {
1608       // move location so the stuff that is currently visible
1609       // tends to end up in someplace in the new frustum.
1610       ON_3dPoint center_point = FrustumCenterPoint(target_distance);
1611       if ( center_point.IsValid() && (new_loc-center_point)*m_CamZ > 0.0 )
1612       {
1613         ON_Xform rot;
1614         rot.Rotation(m_CamY,new_up,center_point);
1615         new_loc = rot*m_CamLoc;
1616         if ( !new_loc.IsValid() )
1617           new_loc = m_CamLoc;
1618       }
1619 
1620       ON_3dVector saved_up = m_CamUp;
1621       ON_3dVector saved_dir = m_CamDir;
1622       bool bSavedLockCamUp = m_bLockCamUp;
1623       m_CamUp = new_up;    // intentionally ignoring m_bLockCamUp
1624       m_CamDir = new_dir;  // intentionally ignoring m_bLockDirUp
1625       SetCameraUpLock(true);
1626       if ( !SetCameraFrame() )
1627       {
1628         rc = false;
1629         m_CamUp = saved_up;
1630         m_CamDir = saved_dir;
1631         m_bLockCamUp = bSavedLockCamUp;
1632       }
1633       SetCameraLocation(new_loc);
1634 
1635       UpdateTargetPointHelper(*this,target_distance);
1636     }
1637 
1638   }
1639 
1640   return rc;
1641 }
1642 
SetProjection(ON::view_projection projection)1643 bool ON_Viewport::SetProjection( ON::view_projection projection )
1644 {
1645   // Debugging projection changes is easier if we
1646   // do this initial check.
1647   if ( projection == m_projection )
1648     return true;
1649 
1650   bool rc = false;
1651   if ( projection == ON::perspective_view )
1652   {
1653     rc = true;
1654     m_projection = ON::perspective_view;
1655   }
1656   else
1657   {
1658     rc = (projection == ON::parallel_view);
1659     m_projection = ON::parallel_view;
1660   }
1661 
1662   return rc;
1663 }
1664 
Projection() const1665 ON::view_projection ON_Viewport::Projection() const
1666 {
1667   return m_projection;
1668 }
1669 
IsParallelProjection() const1670 bool ON_Viewport::IsParallelProjection() const
1671 {
1672   return ( ON::parallel_view == m_projection );
1673 }
1674 
IsPerspectiveProjection() const1675 bool ON_Viewport::IsPerspectiveProjection() const
1676 {
1677   return ( ON::perspective_view == m_projection );
1678 }
1679 
IsTwoPointPerspectiveProjection() const1680 bool ON_Viewport::IsTwoPointPerspectiveProjection() const
1681 {
1682   bool rc =    IsPerspectiveProjection()
1683             && CameraUpIsLocked()
1684             && FrustumIsLeftRightSymmetric()
1685             && !FrustumIsTopBottomSymmetric();
1686   return rc;
1687 }
1688 
SetFrustum(double frus_left,double frus_right,double frus_bottom,double frus_top,double frus_near,double frus_far)1689 bool ON_Viewport::SetFrustum(
1690       double frus_left,
1691       double frus_right,
1692       double frus_bottom,
1693       double frus_top,
1694       double frus_near,
1695       double frus_far
1696       )
1697 {
1698   bool rc = false;
1699   if (
1700           ON_IsValid(frus_left)
1701        && ON_IsValid(frus_right)
1702        && ON_IsValid(frus_top)
1703        && ON_IsValid(frus_bottom)
1704        && ON_IsValid(frus_near)
1705        && ON_IsValid(frus_far)
1706        && frus_left < frus_right
1707        && frus_bottom < frus_top
1708        && 0.0 < frus_near
1709        && frus_near < frus_far
1710      )
1711   {
1712     if ( IsPerspectiveProjection()
1713          && (frus_near <= 1.0e-8 || frus_far > 1.0001e8*frus_near)
1714        )
1715     {
1716       ON_ERROR("ON_Viewport::SetFrustum - Beyond float precision perspective frus_near/frus_far values - will crash MS OpenGL");
1717     }
1718 
1719     if ( FrustumIsLeftRightSymmetric() && frus_left != -frus_right )
1720     {
1721       double d = 0.5*(frus_right-frus_left);
1722       frus_right = d;
1723       frus_left = -d;
1724     }
1725 
1726     if ( FrustumIsTopBottomSymmetric() && frus_bottom != -frus_top )
1727     {
1728       double d = 0.5*(frus_top-frus_bottom);
1729       frus_top = d;
1730       frus_bottom = -d;
1731     }
1732 
1733     m_frus_left   = frus_left;
1734     m_frus_right  = frus_right;
1735     m_frus_bottom = frus_bottom;
1736     m_frus_top    = frus_top;
1737     m_frus_near   = frus_near;
1738     m_frus_far    = frus_far;
1739     m_bValidFrustum = true;
1740     rc = true;
1741   }
1742   else
1743   {
1744     // 17 March 2008 Dale Lear
1745     //   I added this to trap the bug that is creating
1746     //   invalid viewports.  Developers: If you ever
1747     //   get this error, immediately investigate it.
1748     ON_ERROR("ON_Viewport::SetFrustum - invalid input");
1749   }
1750   return rc;
1751 }
1752 
1753 
GetFrustum(double * frus_left,double * frus_right,double * frus_bottom,double * frus_top,double * frus_near,double * frus_far) const1754 bool ON_Viewport::GetFrustum(
1755       double* frus_left,
1756       double* frus_right,
1757       double* frus_bottom,
1758       double* frus_top,
1759       double* frus_near,   // = NULL
1760       double* frus_far     // = NULL
1761       ) const
1762 {
1763   if ( frus_left )
1764     *frus_left = m_frus_left;
1765   if ( frus_right )
1766     *frus_right = m_frus_right;
1767   if ( frus_bottom )
1768     *frus_bottom = m_frus_bottom;
1769   if ( frus_top )
1770     *frus_top = m_frus_top;
1771   if ( frus_near )
1772     *frus_near = m_frus_near;
1773   if ( frus_far )
1774     *frus_far = m_frus_far;
1775   return m_bValidFrustum;
1776 }
1777 
FrustumLeft() const1778 double ON_Viewport::FrustumLeft()   const { return m_frus_left; }
FrustumRight() const1779 double ON_Viewport::FrustumRight()  const { return m_frus_right; }
FrustumBottom() const1780 double ON_Viewport::FrustumBottom() const { return m_frus_bottom; }
FrustumTop() const1781 double ON_Viewport::FrustumTop()    const { return m_frus_top; }
FrustumNear() const1782 double ON_Viewport::FrustumNear()   const { return m_frus_near; }
FrustumFar() const1783 double ON_Viewport::FrustumFar()    const { return m_frus_far;  }
FrustumWidth() const1784 double ON_Viewport::FrustumWidth()  const { return m_frus_right-m_frus_left; }
FrustumHeight() const1785 double ON_Viewport::FrustumHeight() const { return m_frus_top-m_frus_bottom; }
1786 
FrustumMinimumDiameter() const1787 double ON_Viewport::FrustumMinimumDiameter() const
1788 {
1789   double w = fabs(m_frus_right-m_frus_left);
1790   double h = fabs(m_frus_top-m_frus_bottom);
1791   return (w<=h)?w:h;
1792 }
1793 
FrustumMaximumDiameter() const1794 double ON_Viewport::FrustumMaximumDiameter() const
1795 {
1796   double w = fabs(m_frus_right-m_frus_left);
1797   double h = fabs(m_frus_top-m_frus_bottom);
1798   return (w<=h)?w:h;
1799 }
1800 
1801 
SetFrustumAspect(double frustum_aspect)1802 bool ON_Viewport::SetFrustumAspect( double frustum_aspect )
1803 {
1804   // maintains camera angle
1805   bool rc = false;
1806   double w, h, d, left, right, bot, top, near_dist, far_dist;
1807   if ( frustum_aspect > 0.0 && GetFrustum( &left, &right, &bot, &top, &near_dist, &far_dist ) ) {
1808     w = right - left;
1809     h = top - bot;
1810     if ( fabs(h) > fabs(w) ) {
1811       d = (h>=0.0) ? fabs(w) : -fabs(w);
1812       d *= 0.5;
1813       h = 0.5*(top+bot);
1814       bot = h-d;
1815       top = h+d;
1816       h = top - bot;
1817     }
1818     else {
1819       d = (w>=0.0) ? fabs(h) : -fabs(h);
1820       d *= 0.5;
1821       w = 0.5*(left+right);
1822       left  = w-d;
1823       right = w+d;
1824       w = right - left;
1825     }
1826     if ( frustum_aspect > 1.0 ) {
1827       // increase width
1828       d = 0.5*w*frustum_aspect;
1829       w = 0.5*(left+right);
1830       left = w-d;
1831       right = w+d;
1832       w = right - left;
1833     }
1834     else if ( frustum_aspect < 1.0 ) {
1835       // increase height
1836       d = 0.5*h/frustum_aspect;
1837       h = 0.5*(bot+top);
1838       bot = h-d;
1839       top = h+d;
1840       h = top - bot;
1841     }
1842     rc = SetFrustum( left, right, bot, top, near_dist, far_dist );
1843   }
1844   return rc;
1845 }
1846 
1847 
1848 
GetFrustumAspect(double & frustum_aspect) const1849 bool ON_Viewport::GetFrustumAspect( double& frustum_aspect ) const
1850 {
1851   // frustum_aspect = frustum width / frustum height
1852   double w, h, left, right, bot, top;
1853   bool rc = m_bValidFrustum;
1854   frustum_aspect = 0.0;
1855 
1856   if ( GetFrustum( &left, &right, &bot, &top ) ) {
1857     w = right - left;
1858     h = top - bot;
1859     if ( h == 0.0 )
1860       rc = false;
1861     else
1862       frustum_aspect = w/h;
1863   }
1864   return rc;
1865 }
1866 
GetFrustumCenter(double * frus_center) const1867 bool ON_Viewport::GetFrustumCenter( double* frus_center ) const
1868 {
1869   double camZ[3], frus_near, frus_far, d;
1870   if ( !frus_center )
1871     return false;
1872   if ( !GetCameraFrame( frus_center, NULL, NULL, camZ ) )
1873     return false;
1874   if ( !GetFrustum( NULL, NULL, NULL, NULL, &frus_near, &frus_far ) )
1875     return false;
1876   d = -0.5*(frus_near+frus_far);
1877   frus_center[0] += d*camZ[0];
1878   frus_center[1] += d*camZ[1];
1879   frus_center[2] += d*camZ[2];
1880   return true;
1881 }
1882 
SetScreenPort(int port_left,int port_right,int port_bottom,int port_top,int port_near,int port_far)1883 bool ON_Viewport::SetScreenPort(
1884       int port_left,
1885       int port_right,
1886       int port_bottom,
1887       int port_top,
1888       int port_near, // = 0
1889       int port_far   // = 0
1890       )
1891 {
1892   if ( port_left == port_right )
1893     return false;
1894   if ( port_bottom == port_top )
1895     return false;
1896   m_port_left   = port_left;
1897   m_port_right  = port_right;
1898   m_port_bottom = port_bottom;
1899   m_port_top    = port_top;
1900   if ( port_near || port_near != port_far )
1901   {
1902     m_port_near   = port_near;
1903     m_port_far    = port_far;
1904   }
1905   m_bValidPort = true;
1906   return m_bValidPort;
1907 }
1908 
GetScreenPort(int * port_left,int * port_right,int * port_bottom,int * port_top,int * port_near,int * port_far) const1909 bool ON_Viewport::GetScreenPort(
1910       int* port_left,
1911       int* port_right,
1912       int* port_bottom,
1913       int* port_top,
1914       int* port_near, // = NULL
1915       int* port_far   // = NULL
1916       ) const
1917 {
1918   if ( port_left )
1919     *port_left = m_port_left;
1920   if ( port_right )
1921     *port_right = m_port_right;
1922   if ( port_bottom )
1923     *port_bottom = m_port_bottom;
1924   if ( port_top )
1925     *port_top = m_port_top;
1926   if ( port_near )
1927     *port_near = m_port_near;
1928   if ( port_far )
1929     *port_far = m_port_far;
1930   return m_bValidPort;
1931 }
1932 
ScreenPortWidth() const1933 int ON_Viewport::ScreenPortWidth() const
1934 {
1935   int width = m_port_right - m_port_left;
1936   return width >= 0 ? width : -width;
1937 }
1938 
ScreenPortHeight() const1939 int ON_Viewport::ScreenPortHeight() const
1940 {
1941   int height = m_port_top - m_port_bottom;
1942   return height >= 0 ? height : -height;
1943 }
1944 
GetScreenPortAspect(double & aspect) const1945 bool ON_Viewport::GetScreenPortAspect(double& aspect) const
1946 {
1947   const double width = m_port_right - m_port_left;
1948   const double height = m_port_top - m_port_bottom;
1949   aspect = ( m_bValidPort && ON_IsValid(height) && ON_IsValid(width) && height != 0.0 )
1950          ? fabs(width/height)
1951          : 0.0;
1952   return (m_bValidPort && aspect != 0.0);
1953 }
1954 
ON_ViewportFromRhinoView(ON::view_projection projection,const ON_3dPoint & rhvp_target,double rhvp_angle1,double rhvp_angle2,double rhvp_angle3,double rhvp_viewsize,double rhvp_cameradist,int screen_width,int screen_height,ON_Viewport & vp)1955 bool ON_ViewportFromRhinoView(
1956         ON::view_projection projection,
1957         const ON_3dPoint& rhvp_target, // 3d point
1958         double rhvp_angle1, double rhvp_angle2, double rhvp_angle3, // radians
1959         double rhvp_viewsize,     // > 0
1960         double rhvp_cameradist,   // > 0
1961         int screen_width, int screen_height,
1962         ON_Viewport& vp
1963         )
1964 /*****************************************************************************
1965 Compute canonical view projection information from Rhino viewport settings
1966 INPUT:
1967   projection
1968   rhvp_target
1969     Rhino viewport target point (3d point that is center of view rotations)
1970   rhvp_angle1, rhvp_angle2, rhvp_angle3
1971     Rhino viewport angle settings
1972   rhvp_viewsize
1973     In perspective, rhvp_viewsize = tangent(half lens angle).
1974     In parallel, rhvp_viewsize = 1/2 * minimum(frustum width,frustum height)
1975   rhvp_cameradistance ( > 0 )
1976     Distance from camera location to Rhino's "target" point
1977   screen_width, screen_height (0,0) if not known
1978 *****************************************************************************/
1979 {
1980   vp.SetProjection( projection );
1981   /*
1982   width, height
1983     width and height of viewport
1984     ( = RhinoViewport->width, RhinoViewport->height )
1985   z_buffer_depth
1986     depth for the z buffer.  0xFFFF is currently used for Rhino
1987     quick rendering.
1988   */
1989 
1990   // In the situation where there is no physical display device, assume a
1991   // 1000 x 1000 "screen" and set the parameters accordingly.  Toolkit users
1992   // that are using this class to actually draw a picture, can make a subsequent
1993   // call to SetScreenPort().
1994 
1995   const double height = (screen_width < 1 || screen_height < 1)
1996                       ? 1000.0 : (double)screen_height;
1997   const double width  = (screen_width < 1 || screen_height < 1)
1998                       ? 1000.0 : (double)screen_width;
1999   //const int z_buffer_depth = 0xFFFF; // value Rhino "Shade" command uses
2000 
2001   // Use this function to obtain standard view information from a Rhino VIEWPORT
2002   // view. The Rhino viewport has many entries.  As of 17 October, 1997 all Rhino
2003   // world to clipping transformation information is derived from the VIEWPORT
2004   // fields:
2005   //
2006   //   target, angle1, angle2, angle3, viewsize, and cameradist.
2007   //
2008   // The width, height and zbuffer_depth arguments are used to determing the
2009   // clipping to screen transformation.
2010 
2011   ON_Xform R1, R2, R3, RhinoRot;
2012   double frustum_left, frustum_right, frustum_bottom, frustum_top;
2013   double near_clipping_distance, far_clipping_distance;
2014 
2015   // Initialize default view in case input is garbage.
2016   if (height < 1)
2017     return false;
2018   if ( width < 1 )
2019     return false;
2020   if ( rhvp_viewsize <= 0.0 )
2021     return false;
2022   if ( rhvp_cameradist <= 0.0 )
2023     return false;
2024 
2025   // A Rhino 1.0 VIEWPORT structure describes the camera's location, direction,
2026   // and  orientation by specifying a rotation transformation that is
2027   // applied to an initial frame.  The rotation transformation is defined
2028   // as a sequence of 3 rotations abount fixed axes.  The initial frame
2029   // has the camera located at (0,0,cameradist), pointed in the direction
2030   // (0,0,-1), and oriented so that up is (0,1,0).
2031 
2032   R1.Rotation( rhvp_angle1, ON_zaxis, ON_origin ); // so called "twist"
2033   R2.Rotation( rhvp_angle2, ON_xaxis, ON_origin ); // so called "elevation"
2034   R3.Rotation( rhvp_angle3, ON_zaxis, ON_origin ); // so called "fudge factor"
2035   RhinoRot = R3 * R2 * R1;
2036 
2037   vp.SetCameraUp( RhinoRot*ON_yaxis );
2038   vp.SetCameraDirection( -(RhinoRot*ON_zaxis) );
2039   vp.SetCameraLocation( rhvp_target - rhvp_cameradist*vp.CameraDirection() );
2040   vp.SetTargetPoint( rhvp_target );
2041   //vp.SetTargetDistance( rhvp_cameradist );
2042 
2043   // Camera coordinates "X" = CameraRight = CameraDirection x CameraUp
2044   // Camera coordinates "Y" = CameraUp
2045   // Camera coordinates "Z" = -CameraDirection
2046 
2047   // Rhino 1.0 did not support skew projections.  In other words, the
2048   // view frustum is symmetric and ray that begins at CameraLocation and
2049   // goes along CameraDirection runs along the frustum's central axis.
2050   // The aspect ratio of the view frustum equals
2051   // (screen port width)/(screen port height)
2052   // This means frus_left = -frus_right, frus_bottom = -frus_top, and
2053   // frus_top/frus_right = height/width
2054 
2055   // Set near and far clipping planes to some reasonable values.  If
2056   // the depth of the pixel is important, then the near and far clipping
2057   // plane will need to be adjusted later.
2058   // Rhino 1.0 didn't have a far clipping plane in wire frame (which explains
2059   // why you can get perspective views reversed through the origin by using
2060   // the SetCameraTarget() command.  It's near clipping plane is set to
2061   // a miniscule value.  For mesh rendering, it must come up with some
2062   // sort of reasonable near and far clipping planes because the zbuffer
2063   // is used correctly.  When time permits, I'll dig through the rendering
2064   // code and determine what values are being used.
2065   //
2066   near_clipping_distance = rhvp_cameradist/64.0;
2067   if ( near_clipping_distance > 1.0 )
2068     near_clipping_distance = 1.0;
2069   far_clipping_distance = 4.0*rhvp_cameradist;
2070 
2071 
2072   if ( width <= height )
2073   {
2074     frustum_right = rhvp_viewsize;
2075     frustum_top = frustum_right*height/width;
2076   }
2077   else
2078   {
2079     frustum_top = rhvp_viewsize;
2080     frustum_right = frustum_top*width/height;
2081   }
2082   if ( vp.IsPerspectiveProjection() )
2083   {
2084     frustum_right *= near_clipping_distance;
2085     frustum_top   *= near_clipping_distance;
2086   }
2087   frustum_left   = -frustum_right;
2088   frustum_bottom = -frustum_top;
2089 
2090 
2091   vp.SetFrustum(
2092          frustum_left,   frustum_right,
2093          frustum_bottom, frustum_top,
2094          near_clipping_distance, far_clipping_distance );
2095 
2096   // Windows specific stuff that requires knowing size of client area in pixels
2097   vp.SetScreenPort( 0, (int)width, // windows has screen X increasing accross
2098                     (int)height,  0, // windows has screen Y increasing downwards
2099                     0, 0xFFFF );
2100 
2101   return (vp.IsValid()?true:false);
2102 }
2103 
GetCameraAngle(double * angle,double * angle_h,double * angle_w) const2104 bool ON_Viewport::GetCameraAngle(
2105        double* angle,
2106        double* angle_h,
2107        double* angle_w
2108        ) const
2109 {
2110   bool rc = false;
2111   if ( angle )
2112     *angle = 0.0;
2113   if ( angle_h )
2114     *angle_h = 0.0;
2115   if ( angle_w )
2116     *angle_w = 0.0;
2117   double half_w, half_h, left, right, bot, top, near_dist;
2118   if ( GetFrustum( &left, &right, &bot, &top, &near_dist, NULL ) )
2119   {
2120     half_w = ( right > -left ) ? right : -left;
2121     half_h = ( top   > -bot  ) ? top   : -bot;
2122     if ( near_dist > 0.0 && ON_IsValid(near_dist) )
2123     {
2124       if ( angle )
2125         *angle = atan( sqrt(half_w*half_w + half_h*half_h)/near_dist );
2126       if ( angle_h )
2127         *angle_h = atan( half_h/near_dist );
2128       if ( angle_w )
2129         *angle_w = atan( half_w/near_dist );
2130     }
2131     rc = true;
2132   }
2133   return rc;
2134 }
2135 
GetCameraAngle(double * angle) const2136 bool ON_Viewport::GetCameraAngle(
2137        double* angle
2138        ) const
2139 {
2140   double angle_h = 0.0;
2141   double angle_w = 0.0;
2142   bool rc = GetCameraAngle( NULL, &angle_h, &angle_w );
2143   if ( angle && rc ) {
2144     *angle = (angle_h < angle_w) ? angle_h : angle_w;
2145   }
2146   return rc;
2147 }
2148 
SetCameraAngle(double angle)2149 bool ON_Viewport::SetCameraAngle( double angle )
2150 {
2151   bool rc = false;
2152   double r, d, aspect, half_w, half_h, near_dist, far_dist;
2153   if ( angle > 0.0  && angle < 0.5*ON_PI*(1.0-ON_SQRT_EPSILON) ) {
2154     if ( GetFrustum( NULL, NULL, NULL, NULL, &near_dist, &far_dist ) && GetFrustumAspect( aspect) ) {
2155       r = near_dist*tan(angle);
2156       // d = r/sqrt(1.0+aspect*aspect); // if angle is 1/2 diagonal angle
2157       d = r; // angle is 1/2 smallest angle
2158       if ( aspect >= 1.0 ) {
2159         // width >= height
2160         half_w = d*aspect;
2161         half_h = d;
2162       }
2163       else {
2164         // height > width
2165         half_w = d;
2166         half_h = d/aspect;
2167       }
2168       rc = SetFrustum( -half_w, half_w, -half_h, half_h, near_dist, far_dist );
2169     }
2170   }
2171   return rc;
2172 }
2173 
2174 // This version of the function has "lens" misspelled.
GetCamera35mmLenseLength(double * lens_length) const2175 bool ON_Viewport::GetCamera35mmLenseLength( double* lens_length ) const
2176 {
2177   return GetCamera35mmLensLength( lens_length );
2178 }
2179 
GetCamera35mmLensLength(double * lens_length) const2180 bool ON_Viewport::GetCamera35mmLensLength( double* lens_length ) const
2181 {
2182   // 35 mm film has a height of 24 mm and a width of 36 mm
2183   double film_r, view_r, half_w, half_h;
2184   double frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
2185   if ( !lens_length )
2186     return false;
2187   *lens_length = 0.0;
2188   if ( !GetFrustum( &frus_left, &frus_right, &frus_bottom, &frus_top,
2189                      &frus_near, &frus_far ) )
2190     return false;
2191   if ( frus_near <= 0.0 )
2192     return false;
2193   half_w = ( frus_right > -frus_left ) ? frus_right : -frus_left;
2194   half_h = ( frus_top   > -frus_bottom ) ? frus_top : -frus_bottom;
2195 
2196   // 2009 May 8 Dale Lear - always use width in two point perspective
2197   view_r = (half_w <= half_h || IsTwoPointPerspectiveProjection()) ? half_w : half_h;
2198   film_r = 12.0;
2199   if ( view_r <= 0.0 )
2200     return false;
2201 
2202   *lens_length = frus_near*film_r/view_r;
2203   return true;
2204 }
2205 
2206 // This version of the function has "lens" misspelled.
SetCamera35mmLenseLength(double lens_length)2207 bool ON_Viewport::SetCamera35mmLenseLength( double lens_length )
2208 {
2209   return SetCamera35mmLensLength( lens_length );
2210 }
2211 
SetCamera35mmLensLength(double lens_length)2212 bool ON_Viewport::SetCamera35mmLensLength( double lens_length )
2213 {
2214   // 35 mm film has a height of 24 mm and a width of 36 mm
2215   double film_r, view_r, half_w, half_h, s;
2216   double frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
2217   if ( !ON_IsValid(lens_length) || lens_length <= 0.0 )
2218     return false;
2219   if ( !GetFrustum( &frus_left, &frus_right, &frus_bottom, &frus_top,
2220                      &frus_near, &frus_far ) )
2221     return false;
2222   if ( frus_near <= 0.0 )
2223     return false;
2224   half_w = ( frus_right > -frus_left ) ? frus_right : -frus_left;
2225   half_h = ( frus_top   > -frus_bottom  ) ? frus_top   : -frus_bottom;
2226 
2227   // 2009 May 8 Dale Lear - always use width in two point perspective
2228   view_r = (half_w <= half_h || IsTwoPointPerspectiveProjection()) ? half_w : half_h;
2229   film_r = 12.0;
2230   if ( view_r <= 0.0 )
2231     return false;
2232 
2233   s = (film_r/view_r)*(frus_near/lens_length);
2234   if ( fabs(s-1.0) < 1.0e-6 )
2235     return true;
2236 
2237   frus_left *= s;
2238   frus_right *= s;
2239   frus_bottom *= s;
2240   frus_top *= s;
2241   return SetFrustum( frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far );
2242 }
2243 
GetXform(ON::coordinate_system srcCS,ON::coordinate_system destCS,ON_Xform & xform) const2244 bool ON_Viewport::GetXform(
2245        ON::coordinate_system srcCS,
2246        ON::coordinate_system destCS,
2247        ON_Xform& xform
2248        ) const
2249 {
2250   bool rc = false;
2251   ON_Xform x0, x1;
2252 
2253   xform.Identity();
2254 
2255   switch( srcCS )
2256   {
2257   case ON::world_cs:
2258   case ON::camera_cs:
2259   case ON::clip_cs:
2260   case ON::screen_cs:
2261     break;
2262   default:
2263     return false;
2264   }
2265   switch( destCS )
2266   {
2267   case ON::world_cs:
2268   case ON::camera_cs:
2269   case ON::clip_cs:
2270   case ON::screen_cs:
2271     break;
2272   default:
2273     return false;
2274   }
2275 
2276   if (srcCS == destCS)
2277     return true;
2278 
2279 
2280   switch ( srcCS )
2281   {
2282 
2283   case ON::world_cs:
2284     if ( !m_bValidCamera )
2285       break;
2286 
2287     switch ( destCS )
2288     {
2289     case ON::camera_cs:
2290       xform.WorldToCamera( m_CamLoc, m_CamX, m_CamY, m_CamZ );
2291       rc = true;
2292       break;
2293 
2294     case ON::clip_cs:
2295       rc = GetXform( ON::world_cs,  ON::camera_cs, x0 );
2296       if (rc)
2297         rc = GetXform( ON::camera_cs, ON::clip_cs,   x1 );
2298       if (rc)
2299         xform = x1*x0;
2300       break;
2301 
2302     case ON::screen_cs:
2303       rc = GetXform( ON::world_cs,  ON::clip_cs,   x0 );
2304       if (rc)
2305         rc = GetXform( ON::clip_cs,   ON::screen_cs, x1 );
2306       if (rc)
2307         xform = x1*x0;
2308       break;
2309 
2310     case ON::world_cs:
2311       // Never happens.  This is here to quiet g++ warnings.
2312       break;
2313     }
2314     break;
2315 
2316   case ON::camera_cs:
2317     if ( !m_bValidCamera )
2318       break;
2319 
2320     switch ( destCS )
2321     {
2322     case ON::world_cs:
2323       xform.CameraToWorld( m_CamLoc, m_CamX, m_CamY, m_CamZ );
2324       rc = true;
2325       break;
2326 
2327     case ON::clip_cs:
2328       if ( m_bValidFrustum )
2329       {
2330         ON_Xform cam2clip;
2331         cam2clip.CameraToClip(
2332           IsPerspectiveProjection(),
2333           m_frus_left, m_frus_right,
2334           m_frus_bottom, m_frus_top,
2335           m_frus_near, m_frus_far );
2336         xform = m_clip_mods*cam2clip;
2337         rc = true;
2338       }
2339       break;
2340 
2341     case ON::screen_cs:
2342       rc = GetXform( ON::camera_cs,  ON::clip_cs,  x0 );
2343       if (rc)
2344         rc = GetXform( ON::clip_cs,   ON::screen_cs, x1 );
2345       if (rc)
2346         xform = x1*x0;
2347       break;
2348 
2349     case ON::camera_cs:
2350       // Never happens.  This is here to quiet g++ warnings.
2351       break;
2352     }
2353     break;
2354 
2355   case ON::clip_cs:
2356     switch ( destCS )
2357     {
2358     case ON::world_cs:
2359       rc = GetXform( ON::clip_cs,   ON::camera_cs, x0 );
2360       if (rc)
2361         rc = GetXform( ON::camera_cs,  ON::world_cs, x1 );
2362       if (rc)
2363         xform = x1*x0;
2364       break;
2365 
2366     case ON::camera_cs:
2367       if ( m_bValidFrustum )
2368       {
2369         ON_Xform clip2cam;
2370         clip2cam.ClipToCamera(
2371           IsPerspectiveProjection(),
2372           m_frus_left, m_frus_right,
2373           m_frus_bottom, m_frus_top,
2374           m_frus_near, m_frus_far );
2375         xform = clip2cam*m_clip_mods_inverse;
2376         rc = true;
2377       }
2378       break;
2379 
2380     case ON::screen_cs:
2381       if ( m_bValidPort )
2382       {
2383         xform.ClipToScreen(
2384           m_port_left, m_port_right,
2385           m_port_bottom, m_port_top,
2386           m_port_near, m_port_far );
2387         rc = true;
2388       }
2389       break;
2390 
2391     case ON::clip_cs:
2392       // Never happens.  This is here to quiet g++ warnings.
2393       break;
2394     }
2395     break;
2396 
2397   case ON::screen_cs:
2398     switch ( destCS )
2399     {
2400     case ON::world_cs:
2401       rc = GetXform( ON::screen_cs, ON::camera_cs, x0 );
2402       if (rc)
2403         rc = GetXform( ON::camera_cs, ON::world_cs,  x1 );
2404       if (rc)
2405         xform = x1*x0;
2406       break;
2407     case ON::camera_cs:
2408       rc = GetXform( ON::screen_cs, ON::clip_cs,   x0 );
2409       if (rc)
2410         rc = GetXform( ON::clip_cs,   ON::camera_cs, x1 );
2411       if (rc)
2412         xform = x1*x0;
2413       break;
2414     case ON::clip_cs:
2415       if ( m_bValidPort ) {
2416         xform.ScreenToClip(
2417           m_port_left, m_port_right,
2418           m_port_bottom, m_port_top,
2419           m_port_near, m_port_far );
2420         rc = true;
2421       }
2422       break;
2423     case ON::screen_cs:
2424       // Never happens.  This is here to quiet g++ warnings.
2425       break;
2426     }
2427     break;
2428 
2429   }
2430 
2431   return rc;
2432 }
2433 
GetFrustumLine(double screenx,double screeny,ON_Line & world_line) const2434 bool ON_Viewport::GetFrustumLine( double screenx, double screeny, ON_Line& world_line ) const
2435 {
2436   ON_Xform s2c, c2w;
2437   ON_3dPoint c;
2438   ON_Line line;
2439   bool rc;
2440 
2441   rc = GetXform( ON::screen_cs, ON::clip_cs, s2c );
2442   if ( rc )
2443     rc = GetXform( ON::clip_cs, ON::world_cs, c2w );
2444   if (rc )
2445   {
2446     // c = mouse point on near clipping plane
2447     c.x = s2c.m_xform[0][0]*screenx + s2c.m_xform[0][1]*screeny + s2c.m_xform[0][3];
2448     c.y = s2c.m_xform[1][0]*screenx + s2c.m_xform[1][1]*screeny + s2c.m_xform[1][3];
2449     c.z = 1.0;
2450     line.to = c2w*c;   // line.to = near plane mouse point in world coords
2451     c.z = -1.0;
2452     line.from = c2w*c; // line.from = far plane mouse point in world coords
2453 
2454     world_line = line;
2455   }
2456   return rc;
2457 }
2458 
clipDist(const double * camLoc,const double * camZ,const double * P)2459 static double clipDist( const double* camLoc, const double* camZ, const double* P )
2460 {
2461   return (camLoc[0]-P[0])*camZ[0]+(camLoc[1]-P[1])*camZ[1]+(camLoc[2]-P[2])*camZ[2];
2462 }
2463 
2464 
SetFrustumNearFar(const double * box_min,const double * box_max)2465 bool ON_Viewport::SetFrustumNearFar(
2466        const double* box_min,
2467        const double* box_max
2468        )
2469 {
2470   bool rc = false;
2471   const double* box[2];
2472   int i,j,k;
2473   double n, f, d;
2474   double camLoc[3], camZ[3], P[3];
2475 
2476   if ( !box_min )
2477     box_min = box_max;
2478   if ( !box_max )
2479     box_max = box_min;
2480   if ( !box_min )
2481     return false;
2482 
2483   // 31 May 2007 Dale Lear RR 25980
2484   //    Add validation of box_min and box_max.
2485   if ( !ON_IsValid(box_min[0]) || !ON_IsValid(box_min[1]) || !ON_IsValid(box_min[2]) )
2486     return false;
2487   if ( !ON_IsValid(box_max[0]) || !ON_IsValid(box_max[1]) || !ON_IsValid(box_max[2]) )
2488     return false;
2489   if (    box_min[0] > box_max[0]
2490        || box_min[1] > box_max[1]
2491        || box_min[2] > box_max[2]
2492      )
2493   {
2494     return false;
2495   }
2496   box[0] = box_min;
2497   box[1] = box_max;
2498 
2499   if ( GetCameraFrame( camLoc, NULL, NULL, camZ ) ) {
2500     n = f = -1.0;
2501     for(i=0;i<2;i++)for(j=0;j<2;j++)for(k=0;k<2;k++) {
2502       P[0] = box[i][0];
2503       P[1] = box[j][1];
2504       P[2] = box[k][2];
2505       d = clipDist(camLoc,camZ,P);
2506       if (!i&&!j&&!k)
2507         n=f=d;
2508       else if ( d < n )
2509         n = d;
2510       else if ( d > f )
2511         f = d;
2512     }
2513     if ( !ON_IsValid(f) || !ON_IsValid(n) )
2514       return false;
2515     if ( f <= 0.0 )
2516       return false; // box is behind camera
2517     n *= 0.9375;
2518     f *= 1.0625;
2519     if ( n <= 0.0 )
2520       n = m__MIN_NEAR_OVER_FAR*f;
2521     if ( IsPerspectiveProjection() )
2522       rc = SetFrustumNearFar( n, f, m__MIN_NEAR_DIST, m__MIN_NEAR_OVER_FAR, 0.5*(n+f) );
2523     else
2524       rc = SetFrustumNearFar( n, f );
2525   }
2526   return rc;
2527 }
2528 
SetFrustumNearFar(const double * center,double radius)2529 bool ON_Viewport::SetFrustumNearFar(
2530        const double* center,
2531        double        radius
2532        )
2533 {
2534   bool rc = false;
2535   double n, f, d;
2536   double camLoc[3], camZ[3], P[3];
2537 
2538   if ( !center
2539        || !ON_IsValid(center[0])
2540        || !ON_IsValid(center[1])
2541        || !ON_IsValid(center[2])
2542        || !ON_IsValid(radius)
2543      )
2544   {
2545     return false;
2546   }
2547 
2548   if ( GetCameraFrame( camLoc, NULL, NULL, camZ ) )
2549   {
2550     d = fabs(radius);
2551     P[0] = center[0] + d*camZ[0];
2552     P[1] = center[1] + d*camZ[0];
2553     P[2] = center[2] + d*camZ[0];
2554     n = clipDist(camLoc,camZ,P);
2555     P[0] = center[0] - d*camZ[0];
2556     P[1] = center[1] - d*camZ[0];
2557     P[2] = center[2] - d*camZ[0];
2558     f = clipDist(camLoc,camZ,P);
2559     if ( !ON_IsValid(f) || !ON_IsValid(n) )
2560       return false;
2561     if ( f <= 0.0 )
2562       return false; // sphere is behind camera
2563     n *= 0.9375;
2564     f *= 1.0625;
2565     if ( n <= 0.0 )
2566       n = m__MIN_NEAR_OVER_FAR*f;
2567     if ( IsPerspectiveProjection() )
2568       rc = SetFrustumNearFar( n, f, m__MIN_NEAR_DIST, m__MIN_NEAR_OVER_FAR, 0.5*(n+f) );
2569     else
2570       rc = SetFrustumNearFar( n, f );
2571   }
2572   return rc;
2573 }
2574 
SetFrustumNearFar(double n,double f)2575 bool ON_Viewport::SetFrustumNearFar( double n, double f )
2576 {
2577   // This is a bare bones setter.  Except for the perspective 0 < n < f
2578   // requirement, do not add checking here.
2579   //
2580   // Use the ON_Viewport::SetFrustumNearFar( near_dist,
2581   //                                         far_dist,
2582   //                                         min_near_dist,
2583   //                                         min_near_over_far,
2584   //                                         target_dist );
2585   //
2586   // version if you need lots of validation and automatic fixing.
2587 
2588   double d, frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
2589   bool rc = false;
2590   if ( ON_IsValid(n) && ON_IsValid(f) && n > 0.0 && f > n )
2591   {
2592     if ( GetFrustum( &frus_left,   &frus_right,
2593                       &frus_bottom, &frus_top,
2594                       &frus_near,   &frus_far ) )
2595     {
2596       // preserve valid frustum
2597       if ( IsPerspectiveProjection() )
2598       {
2599         d = n/frus_near;
2600         frus_left *= d;
2601         frus_right *= d;
2602         frus_bottom *= d;
2603         frus_top *= d;
2604       }
2605       frus_near = n;
2606       frus_far = f;
2607       rc = SetFrustum( frus_left,   frus_right,
2608                        frus_bottom, frus_top,
2609                        frus_near,   frus_far );
2610     }
2611     else
2612     {
2613       if ( IsPerspectiveProjection() && (n <= 1.0e-8 || f > 1.0001e8*n) )
2614       {
2615         ON_ERROR("ON_Viewport::SetFrustum - bogus perspective m_frus_near/far values - will crash MS OpenGL");
2616       }
2617       m_frus_near = n;
2618       m_frus_far = f;
2619       rc = true;
2620     }
2621   }
2622   return rc;
2623 }
2624 
2625 
ChangeToSymmetricFrustum(bool bLeftRightSymmetric,bool bTopBottomSymmetric,double target_distance)2626 bool ON_Viewport::ChangeToSymmetricFrustum(
2627     bool bLeftRightSymmetric,
2628     bool bTopBottomSymmetric,
2629     double target_distance
2630     )
2631 {
2632   if ( bLeftRightSymmetric && m_frus_left == -m_frus_right )
2633     bLeftRightSymmetric = false; // no left/right chnages required.
2634 
2635   if ( bTopBottomSymmetric && m_frus_bottom == -m_frus_top )
2636     bTopBottomSymmetric = false; // no top/bottom chagnes required.
2637 
2638   if ( !bLeftRightSymmetric && !bTopBottomSymmetric )
2639     return true; // no changes required
2640 
2641   if ( !m_bValidFrustum )
2642     return false;
2643 
2644   const double half_w = 0.5*(m_frus_right-m_frus_left);
2645   const double half_h = 0.5*(m_frus_top-m_frus_bottom);
2646   double dx = bLeftRightSymmetric ? (m_frus_right - half_w) : 0.0;
2647   double dy = bTopBottomSymmetric ? (m_frus_top   - half_h) : 0.0;
2648   if ( bLeftRightSymmetric )
2649   {
2650     m_frus_right = half_w;
2651     m_frus_left = -m_frus_right;
2652   }
2653   if ( bTopBottomSymmetric )
2654   {
2655     m_frus_top = half_h;
2656     m_frus_bottom = -m_frus_top;
2657   }
2658 
2659   // if possible, dolly the camera so the original
2660   // target plane is still visible.
2661   if ( m_bValidCamera && (dx != 0.0 || dy != 0.0 ) )
2662   {
2663     if ( ON::perspective_view == m_projection )
2664     {
2665       if ( m_frus_near > 0.0 )
2666       {
2667         if ( ON_UNSET_VALUE == target_distance )
2668           target_distance = TargetDistance(true);
2669         if ( ON_IsValid(target_distance) && target_distance > 0.0 )
2670         {
2671           double s = target_distance/m_frus_near;
2672           dx *= s;
2673           dy *= s;
2674         }
2675       }
2676       else
2677       {
2678         dx=dy = 0.0;
2679       }
2680     }
2681     if ( dx != 0.0 || dy != 0.0 )
2682     {
2683       ON_3dPoint cam_loc = m_CamLoc + dx*m_CamX + dy*m_CamY;
2684       SetCameraLocation(cam_loc);
2685     }
2686   }
2687 
2688   return true;
2689 }
2690 
2691 
GetWorldToScreenScale(const ON_3dPoint & P,double * scale) const2692 bool ON_Viewport::GetWorldToScreenScale( const ON_3dPoint& P, double* scale ) const
2693 {
2694   if ( scale ) {
2695     ON_Xform w2s;
2696     ON_3dVector X;
2697     ON_3dPoint Q, ScrC, ScrQ;
2698     if (!GetCameraFrame( NULL, X, NULL, NULL ))
2699       return false;
2700     if (!GetXform( ON::world_cs, ON::screen_cs, w2s ))
2701       return false;
2702     Q = P+X;
2703     ScrC = w2s*P;
2704     ScrQ = w2s*Q;
2705     *scale = fabs(ScrC.x - ScrQ.x);
2706   }
2707   return true;
2708 }
2709 
GetCoordinateSprite(int size,int scrx,int scry,int indx[3],double scr_coord[3][2]) const2710 bool ON_Viewport::GetCoordinateSprite(
2711                      int size,
2712                      int scrx, int scry,
2713                      int indx[3], // axis order by depth
2714                      double scr_coord[3][2] ) const
2715 {
2716   // size = length of axes in pixels
2717 
2718   indx[0] = 0; indx[1] = 1; indx[2] = 2;
2719   scr_coord[0][0] = scr_coord[1][0] = scr_coord[2][0] = scrx;
2720   scr_coord[0][1] = scr_coord[1][1] = scr_coord[2][1] = scry;
2721 
2722   ON_3dPoint C, XP, YP, ZP, ScrC, ScrXP;
2723   ON_3dVector X, Z, Scr[3];
2724   ON_Xform w2s;
2725   if (!GetFrustumCenter( C ) )
2726     return false;
2727   if (!GetCameraFrame( NULL, X, NULL, Z ))
2728     return false;
2729   if (!GetXform( ON::world_cs, ON::screen_cs, w2s ))
2730     return false;
2731 
2732   // indx[] determines order that axes are drawn
2733   // sorted from back to front
2734   int i,j,k;
2735   for (i = 0; i < 2; i++) for (j = i+1; j <= 2; j++) {
2736     if (Z[indx[i]] > Z[indx[j]])
2737       {k = indx[i]; indx[i] = indx[j]; indx[j] = k;}
2738   }
2739 
2740   // determine world length that corresponds to size pixels
2741   XP = C+X;
2742   ScrC = w2s*C;
2743   ScrXP = w2s*XP;
2744   if (ScrC.x == ScrXP.x)
2745     return false;
2746   double s = size/fabs( ScrC.x - ScrXP.x );
2747 
2748   // transform world coord axes to screen
2749   XP = C;
2750   XP.x += s;
2751   YP = C;
2752   YP.y += s;
2753   ZP = C;
2754   ZP.z += s;
2755   Scr[0] = w2s*XP;
2756   Scr[1] = w2s*YP;
2757   Scr[2] = w2s*ZP;
2758 
2759   double dx = scrx - ScrC.x;
2760   double dy = scry - ScrC.y;
2761   for (i=0;i<3;i++) {
2762     scr_coord[i][0] = dx + Scr[i].x;
2763     scr_coord[i][1] = dy + Scr[i].y;
2764   }
2765 
2766   return true;
2767 }
2768 
GetRelativeScreenCoordinates(int port_left,int port_right,int port_bottom,int port_top,ON_BOOL32 bSortPoints,int & x0,int & y0,int & x1,int & y1,double & s0,double & t0,double & s1,double & t1)2769 static ON_BOOL32 GetRelativeScreenCoordinates(
2770           int port_left, int port_right,
2771           int port_bottom, int port_top,
2772           ON_BOOL32 bSortPoints,
2773           int& x0, int& y0, int& x1, int& y1,
2774           double& s0, double& t0, double& s1, double& t1
2775           )
2776 {
2777   // convert screen rectangle into relative rectangle
2778   if ( bSortPoints ) {
2779     int i;
2780     if ( x0 > x1 ) {
2781       i = x0; x0 = x1; x1 = i;
2782     }
2783     if ( port_left > port_right ) {
2784       i = x0; x0 = x1; x1 = i;
2785     }
2786     if ( y0 > y1 ) {
2787       i = y0; y0 = y1; y1 = i;
2788     }
2789     if ( port_bottom > port_top ) {
2790       i = y0; y0 = y1; y1 = i;
2791     }
2792   }
2793 
2794   s0 = ((double)(x0 - port_left))/((double)(port_right - port_left));
2795   s1 = ((double)(x1 - port_left))/((double)(port_right - port_left));
2796   t0 = ((double)(y0 - port_bottom))/((double)(port_top - port_bottom));
2797   t1 = ((double)(y1 - port_bottom))/((double)(port_top - port_bottom));
2798 
2799   double tol = 0.001;
2800   if ( fabs(s0) <= tol ) s0 = 0.0; else if (fabs(s0-1.0) <= tol ) s0 = 1.0;
2801   if ( fabs(s1) <= tol ) s1 = 0.0; else if (fabs(s1-1.0) <= tol ) s1 = 1.0;
2802   if ( fabs(t0) <= tol ) t0 = 0.0; else if (fabs(t0-1.0) <= tol ) t0 = 1.0;
2803   if ( fabs(t1) <= tol ) t1 = 0.0; else if (fabs(t1-1.0) <= tol ) t1 = 1.0;
2804   if ( fabs(s0-s1) <= tol )
2805     return false;
2806   if ( fabs(t0-t1) <= tol )
2807     return false;
2808   return true;
2809 }
2810 
ZoomToScreenRect(int x0,int y0,int x1,int y1)2811 bool ON_Viewport::ZoomToScreenRect( int x0, int y0, int x1, int y1 )
2812 {
2813   int port_left, port_right, port_bottom, port_top, port_near, port_far;
2814   if ( !GetScreenPort( &port_left, &port_right,
2815                        &port_bottom, &port_top,
2816                        &port_near, &port_far ) )
2817     return false;
2818 
2819   // dolly camera sideways so it's looking at center of rectangle
2820   int dx = (x0+x1)/2;
2821   int dy = (y0+y1)/2;
2822   int cx = (port_left+port_right)/2;
2823   int cy = (port_bottom+port_top)/2;
2824 
2825   ON_3dVector dolly_vector;
2826   if ( !GetDollyCameraVector( dx, dy, cx, cy, 0.5*(FrustumNear()+FrustumFar()), dolly_vector ) )
2827     return false;
2828   if ( !DollyCamera( dolly_vector ) )
2829     return false;
2830 
2831   // adjust frustum
2832   dx = cx - dx;
2833   dy = cy - dy;
2834   x0 += dx;
2835   x1 += dx;
2836   y0 += dy;
2837   y1 += dy;
2838   double frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
2839   if ( !GetFrustum( &frus_left,   &frus_right,
2840                      &frus_bottom, &frus_top,
2841                      &frus_near,   &frus_far ) )
2842     return false;
2843   double s0,t0,s1,t1;
2844   if ( !GetRelativeScreenCoordinates(port_left, port_right, port_bottom, port_top,
2845                               true,
2846                               x0,y0,x1,y1,
2847                               s0,t0,s1,t1) )
2848     return false;
2849   double w = frus_right - frus_left;
2850   double h = frus_top - frus_bottom;
2851   double a0 = (1.0-s0)*frus_left   + s0*frus_right;
2852   double a1 = (1.0-s1)*frus_left   + s1*frus_right;
2853   double b0 = (1.0-t0)*frus_bottom + t0*frus_top;
2854   double b1 = (1.0-t1)*frus_bottom + t1*frus_top;
2855   if ( -a0 > a1 ) a1 = -a0; else a0 = -a1;
2856   if ( -b0 > b1 ) b1 = -b0; else b0 = -b1;
2857   double d;
2858   if ( (b1-b0)*w < (a1-a0)*h ) {
2859     d = (a1-a0)*h/w;
2860     d = 0.5*(d - (b1-b0));
2861     b0 -= d;
2862     b1 += d;
2863   }
2864   else {
2865     d = (b1-b0)*w/h;
2866     d = 0.5*(d - (a1-a0));
2867     a0 -= d;
2868     a1 += d;
2869   }
2870 
2871   return SetFrustum( a0, a1, b0, b1, frus_near, frus_far );
2872 }
2873 
2874 /*
2875 ON_BOOL32 ON_Viewport::DollyToScreenRect( double view_plane_distance,
2876                                         int x0, int y0, int x1, int y1 )
2877 {
2878   // Only makes sense in a perspective projection. In a parallel projection,
2879   // I resort to ZoomToScreenRect(0 and the visual result is the same.
2880   if ( !IsPerspectiveProjection() )
2881     return ZoomToScreenRect( x0, y0, x1, y1 );
2882 
2883   int port_left, port_right, port_bottom, port_top;
2884   if ( !GetScreenPort( &port_left, &port_right, &port_bottom, &port_top, NULL, NULL ) )
2885     return false;
2886   int dx = (x0+x1)/2;
2887   int dy = (y0+y1)/2;
2888   int cx = (port_left+port_right)/2;
2889   int cy = (port_bottom+port_top)/2;
2890   if ( !DollyAlongScreenChord( dx, dy, cx, cy ) )
2891     return false;
2892   dx = cx - dx;
2893   dy = cy - dy;
2894   x0 += dx;
2895   x1 += dx;
2896   y0 += dy;
2897   y1 += dy;
2898 
2899   double frus_left, frus_right, frus_bottom, frus_top, frus_near, frus_far;
2900   if ( !GetFrustum( &frus_left,   &frus_right,
2901                      &frus_bottom, &frus_top,
2902                      &frus_near,   &frus_far ) )
2903     return false;
2904 
2905   double s0, t0, s1, t1;
2906   if ( !GetRelativeScreenCoordinates(port_left, port_right, port_bottom, port_top,
2907                               true,
2908                               x0,y0,x1,y1,
2909                               s0,t0,s1,t1) )
2910     return false;
2911 
2912   double w = frus_right - frus_left;
2913   double h = frus_top - frus_bottom;
2914   double a0 = (1.0-s0)*frus_left   + s0*frus_right;
2915   double a1 = (1.0-s1)*frus_left   + s1*frus_right;
2916   double b0 = (1.0-t0)*frus_bottom + t0*frus_top;
2917   double b1 = (1.0-t1)*frus_bottom + t1*frus_top;
2918   if ( -a0 > a1 ) a1 = -a0; else a0 = -a1;
2919   if ( -b0 > b1 ) b1 = -b0; else b0 = -b1;
2920   double d;
2921   if ( (b1-b0)*w < (a1-a0)*h ) {
2922     d = (a1-a0)*h/w;
2923     d = 0.5*(d - h);
2924     b0 -= d;
2925     b1 += d;
2926   }
2927   else {
2928     d = (b1-b0)*w/h;
2929     d = 0.5*(d - w);
2930     a0 -= d;
2931     a1 += d;
2932   }
2933 
2934   d = 0.5*((a1-a0)/w + (b1-b0)/h)*view_plane_distance;
2935   double delta = d - view_plane_distance;
2936 
2937   frus_near += delta;
2938   frus_far  += delta;
2939   if ( frus_near <= 0.0 ) {
2940     if ( frus_far <= 0.0 )
2941       frus_far = 100.0;
2942     frus_near = 0.001*frus_far;
2943   }
2944   if ( !SetFrustumNearFar( frus_near, frus_far ) )
2945     return false;
2946 
2947   double camLoc[3], camY[3], camZ[3];
2948   if ( !GetCameraFrame( camLoc, NULL, camY, camZ ) )
2949     return false;
2950   camLoc[0] += delta*camZ[0];
2951   camLoc[1] += delta*camZ[1];
2952   camLoc[2] += delta*camZ[2];
2953   camZ[0] = -camZ[0];
2954   camZ[1] = -camZ[1];
2955   camZ[2] = -camZ[2];
2956   if ( !SetCamera( camLoc, camZ, camY ) )
2957     return false;
2958 
2959   return true;
2960 }
2961 */
2962 
Extents(double angle,const ON_BoundingBox & bbox)2963 bool ON_Viewport::Extents( double angle, const ON_BoundingBox& bbox )
2964 {
2965   double radius;
2966   double x, y, xmin, xmax, ymin, ymax;
2967   int i,j,k;
2968 
2969   if ( !bbox.IsValid() || !IsValid() )
2970     return false;
2971   ON_3dVector camX = CameraX();
2972   ON_3dVector camY = CameraY();
2973   ON_3dPoint center = bbox.Center();
2974   xmin=xmax=ymin=ymax=0.0;
2975   for (i=0;i<2;i++) for (j=0;j<2;j++) for (k=0;k<2;k++) {
2976     ON_3dVector box_corner = bbox.Corner(i,j,k);
2977     x = camX*box_corner;
2978     y = camY*box_corner;
2979     if ( i==0&&j==0&&k==0) {
2980       xmin=xmax=x;
2981       ymin=ymax=y;
2982     }
2983     else {
2984       if ( x > xmax) xmax=x; else if (x < xmin) xmin = x;
2985       if ( y > ymax) ymax=y; else if (y < ymin) ymin = y;
2986     }
2987   }
2988   radius = xmax-xmin;
2989   if ( ymax-ymin > radius )
2990     radius = ymax-ymin;
2991   if ( radius <= ON_SQRT_EPSILON ) {
2992     radius = bbox.Diagonal().MaximumCoordinate();
2993   }
2994   radius *= 0.5;
2995   if ( radius <= ON_SQRT_EPSILON )
2996     radius = 1.0;
2997   return Extents( angle, center, radius );
2998 }
2999 
Extents(double angle,const ON_3dPoint & center,double radius)3000 bool ON_Viewport::Extents( double angle, const ON_3dPoint& center, double radius )
3001 {
3002   if ( !IsValid() )
3003     return false;
3004 
3005   double target_dist, near_dist, far_dist;
3006 
3007   if ( radius <= 0.0 ||
3008        angle  <= 0.0 ||
3009        angle  >= 0.5*ON_PI*(1.0-ON_SQRT_EPSILON) )
3010     return false;
3011 
3012   target_dist = radius/sin(angle);
3013   if ( !IsPerspectiveProjection() )
3014   {
3015     target_dist += 1.0625*radius;
3016   }
3017   near_dist = target_dist - 1.0625*radius;
3018   if ( near_dist < 0.0625*radius )
3019     near_dist = 0.0625*radius;
3020   if ( near_dist < m__MIN_NEAR_DIST )
3021     near_dist = m__MIN_NEAR_DIST;
3022   far_dist = target_dist +  1.0625*radius;
3023 
3024   SetCameraLocation( center + target_dist*CameraZ() );
3025   if ( !SetFrustumNearFar( near_dist, far_dist ) )
3026     return false;
3027   if ( !SetCameraAngle( angle ) )
3028     return false;
3029 
3030   return IsValid()?true:false;
3031 }
3032 
Dump(ON_TextLog & dump) const3033 void ON_Viewport::Dump( ON_TextLog& dump ) const
3034 {
3035   dump.Print("ON_Viewport\n");
3036   dump.PushIndent();
3037 
3038   dump.Print("Projection: ");
3039   switch(m_projection)
3040   {
3041   case ON::parallel_view:
3042     dump.Print("parallel\n");
3043     break;
3044   case ON::perspective_view:
3045     dump.Print("perspective\n");
3046     break;
3047   default:
3048     dump.Print("invalid\n");
3049     break;
3050   }
3051   dump.Print("Camera: (m_bValidCamera = %s)\n",(m_bValidCamera?"true":"false"));
3052   dump.PushIndent();
3053   dump.Print("Location: "); if ( CameraLocationIsLocked() ) dump.Print("(locked) "); dump.Print(m_CamLoc); dump.Print("\n");
3054   dump.Print("Direction: "); if ( CameraDirectionIsLocked() ) dump.Print("(locked) "); dump.Print(m_CamDir); dump.Print("\n");
3055   dump.Print("Up: "); if ( CameraUpIsLocked() ) dump.Print("(locked) "); dump.Print(m_CamUp); dump.Print("\n");
3056   dump.Print("X: "); dump.Print(m_CamX); dump.Print("\n");
3057   dump.Print("Y: "); dump.Print(m_CamY); dump.Print("\n");
3058   dump.Print("Z: "); dump.Print(m_CamZ); dump.Print("\n");
3059   dump.PopIndent();
3060   dump.Print("Target Point: "); dump.Print(m_target_point); dump.Print("\n");
3061   dump.Print("target distance %g\n",TargetDistance(true));
3062 
3063   double frus_aspect=0.0;
3064   GetFrustumAspect(frus_aspect);
3065   dump.Print("Frustum: (m_bValidFrustum = %s)\n",(m_bValidFrustum?"true":"false"));
3066   dump.PushIndent();
3067   dump.Print("left/right symmetry locked = %s\n",FrustumIsLeftRightSymmetric()?"true":"false");
3068   dump.Print("top/bottom symmetry locked = %s\n",FrustumIsTopBottomSymmetric()?"true":"false");
3069   dump.Print("left: "); dump.Print(m_frus_left); dump.Print("\n");
3070   dump.Print("right: "); dump.Print(m_frus_right); dump.Print("\n");
3071   dump.Print("bottom: "); dump.Print(m_frus_bottom); dump.Print("\n");
3072   dump.Print("top: "); dump.Print(m_frus_top); dump.Print("\n");
3073   dump.Print("near: "); dump.Print(m_frus_near); dump.Print("\n");
3074   dump.Print("far: "); dump.Print(m_frus_far); dump.Print("\n");
3075   dump.Print("aspect (width/height): "); dump.Print(frus_aspect); dump.Print("\n");
3076   if ( ON::perspective_view == m_projection )
3077   {
3078     dump.PushIndent();
3079     dump.Print("near/far: %g\n",m_frus_near/m_frus_far);
3080     dump.Print("suggested minimum near: = %g\n",m__MIN_NEAR_DIST);
3081     dump.Print("suggested minimum near/far: = %g\n",m__MIN_NEAR_OVER_FAR);
3082     dump.PopIndent();
3083   }
3084   dump.PopIndent();
3085 
3086   double port_aspect=0.0;
3087   GetScreenPortAspect(port_aspect);
3088   dump.Print("Port: (m_bValidPort = %s\n",(m_bValidPort?"true":"false"));
3089   dump.PushIndent();
3090   dump.Print("left: %d\n",m_port_left);
3091   dump.Print("right: %d\n",m_port_right);
3092   dump.Print("bottom: %d\n",m_port_bottom);
3093   dump.Print("top: %d\n",m_port_top);
3094   dump.Print("near: %d\n",m_port_near);
3095   dump.Print("far: %d\n",m_port_far);
3096   dump.Print("aspect (width/height): "); dump.Print(port_aspect); dump.Print("\n");
3097   dump.PopIndent();
3098 
3099   dump.PopIndent();
3100 }
3101 
GetPointDepth(ON_3dPoint point,double * near_dist,double * far_dist,bool bGrowNearFar) const3102 bool ON_Viewport::GetPointDepth(
3103        ON_3dPoint point,
3104        double* near_dist,
3105        double* far_dist,
3106        bool bGrowNearFar
3107        ) const
3108 {
3109   bool rc = false;
3110   if ( point.x != ON_UNSET_VALUE )
3111   {
3112     double depth = (m_CamLoc - point)*m_CamZ;
3113     if ( 0 != near_dist && (*near_dist == ON_UNSET_VALUE || !bGrowNearFar || *near_dist > depth) )
3114       *near_dist = depth;
3115     if ( 0 != far_dist && (*far_dist == ON_UNSET_VALUE || !bGrowNearFar || *far_dist < depth) )
3116       *far_dist = depth;
3117     rc = true;
3118   }
3119   return rc;
3120 }
3121 
GetPointDepth(ON_3dPoint point,double * view_plane_depth) const3122 bool ON_Viewport::GetPointDepth(
3123        ON_3dPoint point,
3124        double* view_plane_depth
3125        ) const
3126 {
3127   bool rc = false;
3128   if ( point.x != ON_UNSET_VALUE )
3129   {
3130     double depth = (m_CamLoc - point)*m_CamZ;
3131     if ( 0 != view_plane_depth )
3132       *view_plane_depth = depth;
3133     rc = true;
3134   }
3135   return rc;
3136 }
3137 
GetBoundingBoxDepth(ON_BoundingBox bbox,double * near_dist,double * far_dist,bool bGrowNearFar) const3138 bool ON_Viewport::GetBoundingBoxDepth(
3139        ON_BoundingBox bbox,
3140        double* near_dist,
3141        double* far_dist,
3142        bool bGrowNearFar
3143        ) const
3144 {
3145   // The Xbuffer[] stuff is to skip wasting time in unneeded constructors.
3146   // The buffers are double arrays to insure alignments are correct.
3147   ON_3dPoint* C;
3148   ON_3dPoint* P;
3149   ON_PlaneEquation* S;
3150   ON_Line* L;
3151   ON_3dPoint Q;
3152   double Pbuffer[(8+8+8+48)*(sizeof(P[0])/sizeof(double))];
3153   double Sbuffer[5*(sizeof(S[0])/sizeof(double))];
3154   double Lbuffer[4*(sizeof(L[0])/sizeof(double))];
3155   double d, t[2], v[4][8], v0, v1;
3156   const double tol = ON_SQRT_EPSILON*(1.0 + m_CamLoc.MaximumCoordinate());
3157   C = (ON_3dPoint*)Pbuffer;
3158   P = C+8;
3159   S = (ON_PlaneEquation*)Sbuffer;
3160   L = (ON_Line*)Lbuffer;
3161   unsigned int i, j, k, Pin, Pout, Pcount;
3162   bool rc;
3163   const bool bPerspectiveProjection = (ON::perspective_view == m_projection);
3164 
3165   for (;;)
3166   {
3167     rc = bbox.GetCorners(C);
3168     if (!rc)
3169       break;
3170     rc = GetFrustumLeftPlaneEquation(S[0]);
3171     if (!rc)
3172       break;
3173     rc = GetFrustumRightPlaneEquation(S[1]);
3174     if (!rc)
3175       break;
3176     rc = GetFrustumBottomPlaneEquation(S[2]);
3177     if (!rc)
3178       break;
3179     rc = GetFrustumTopPlaneEquation(S[3]);
3180     if (!rc)
3181       break;
3182 
3183     S[4].ON_3dVector::operator=(-m_CamZ);
3184     S[4].d = -S[4].ON_3dVector::operator*(m_CamLoc);
3185 
3186     Pcount = 0;
3187     Pin = 0;
3188     Pout = 0;
3189 
3190     for ( i = 0; i < 8; i++ )
3191     {
3192       k = 0;
3193       if ( (v[0][i] = S[0].ValueAt(C[i])) >= -tol )
3194         k |= 1;
3195       else
3196         Pout |= 1;
3197       if ( (v[1][i] = S[1].ValueAt(C[i])) >= -tol )
3198         k |= 2;
3199       else
3200         Pout |= 2;
3201       if ( (v[2][i] = S[2].ValueAt(C[i])) >= -tol )
3202         k |= 4;
3203       else
3204         Pout |= 4;
3205       if ( (v[3][i] = S[3].ValueAt(C[i])) >= -tol )
3206         k |= 8;
3207       else
3208         Pout |= 8;
3209 
3210       if ( !bPerspectiveProjection || S[4].ValueAt(C[i]) > 0.0 )
3211         k |= 16;
3212 
3213       Pin |= k;
3214       if ( (1|2|4|8|16) == k )
3215       {
3216         // C[i] is inside the infinte frustum
3217         P[Pcount++] = C[i];
3218       }
3219     }
3220 
3221     if ( Pcount < 8 )
3222     {
3223       // some portion of bbox is outside the infinte frustum
3224       if ( (1|2|4|8|16) != Pin )
3225       {
3226         // bbox does not intersect the infinite frustum.
3227         rc = false;
3228         break;
3229       }
3230 
3231       j = 0;
3232       if ( bPerspectiveProjection )
3233       {
3234         if ( bbox.MinimumDistanceTo(m_CamLoc) <= 0.0 )
3235         {
3236           // camera location is in the bounding box
3237           P[Pcount++] = m_CamLoc;
3238           j = 1; // j = 1 indicates m_CamLoc has been added to P[].
3239         }
3240         L[0].from = m_CamLoc;
3241         L[1].from = m_CamLoc;
3242         L[2].from = m_CamLoc;
3243         L[3].from = m_CamLoc;
3244       }
3245       else
3246       {
3247         rc = GetNearRect(L[0].from,L[1].from,L[2].from,L[3].from);
3248         if (!rc)
3249           break;
3250       }
3251 
3252       rc = GetFarRect(L[0].to,L[1].to,L[2].to,L[3].to);
3253       if (!rc)
3254         break;
3255 
3256       const unsigned int Linout[4] = {
3257         1|4, // intersection of left and bottom frustum sides
3258         2|4, // intersection of right and bottom frustum sides
3259         1|8, // intersection of left and top frustum sides
3260         2|8  // intersection of right and top frustum sides
3261         };
3262 
3263       k = Pin & Pout;
3264       for ( i = 0; i < 4; i++ )
3265       {
3266         // The Linout[i] == ... test is true if bbox is on both sides
3267         // of both planes whose intersection defines the line L[i].
3268         // The fast integer test helps cull unnecessary calls to
3269         // the expensive ON_Intersect() function.
3270         if (    Linout[i] == (k & Linout[i])
3271              && ON_Intersect(bbox,L[i],tol,(ON_Interval*)t)
3272            )
3273         {
3274           if ( bPerspectiveProjection )
3275           {
3276             if ( t[1] < 0.0 )
3277               continue;
3278             if ( t[0] < 0.0 )
3279             {
3280               if ( 0 == j )
3281               {
3282                 P[Pcount++] = m_CamLoc;
3283                 j = 1; // j = 1 indicates m_CamLoc has been added to P[].
3284               }
3285               t[0] = t[1];
3286             }
3287           }
3288           P[Pcount++] = L[i].PointAt(t[0]);
3289           if ( t[1] > t[0] )
3290             P[Pcount++] = L[i].PointAt(t[1]);
3291         }
3292       }
3293 
3294       // intersect box edges with frustum sides
3295       // The 12 bbox edges have endpoints
3296       // C[e[*][0]] and C[E[*][1]]
3297       const unsigned int e[12][2] = {
3298         {0,1},{2,3},{4,5},{6,7},
3299         {0,2},{1,3},{4,6},{5,7},
3300         {0,4},{1,5},{2,6},{3,7}};
3301 
3302       for ( i = 0; i < 4; i++ )
3303       {
3304         for ( j = 0; j < 12; j++ )
3305         {
3306           v0 = v[i][e[j][0]];
3307           v1 = v[i][e[j][1]];
3308           if ( v0*v1 < 0.0 )
3309           {
3310             // this box edge crosses the frustum side plane
3311             d = v0/(v0-v1);
3312             P[Pcount++] = Q = (1.0-d)*C[e[j][0]] + d*C[e[j][1]];
3313             // verify that Q is in the frustum
3314             for ( k = 0; k < 4; k++ )
3315             {
3316               if ( i != k && S[k].ValueAt(Q) <= -tol )
3317               {
3318                 // Q is not in the view frustum
3319                 Pcount--;
3320                 break;
3321               }
3322             }
3323           }
3324         }
3325       }
3326       if ( 0 == Pcount )
3327       {
3328         rc = false;
3329         break;
3330       }
3331     }
3332 
3333     t[0] = t[1] = (m_CamLoc - P[0])*m_CamZ;
3334     for ( i = 1; i < Pcount; i++ )
3335     {
3336       d = (m_CamLoc - P[i])*m_CamZ;
3337       if ( d < t[0] )
3338         t[0] = d;
3339       else if ( d > t[1] )
3340         t[1] = d;
3341     }
3342 
3343     if ( bPerspectiveProjection )
3344     {
3345       if ( t[1] < 0.0 )
3346       {
3347         rc = false;
3348         break;
3349       }
3350       if ( t[0] < 0.0 )
3351         t[0] = 0.0;
3352     }
3353 
3354     if ( 0 != near_dist && (!bGrowNearFar || !ON_IsValid(*near_dist) || t[0] < *near_dist) )
3355       *near_dist = t[0];
3356     if ( 0 != far_dist && (!bGrowNearFar || !ON_IsValid(*far_dist) || t[1] > *far_dist) )
3357       *far_dist = t[1];
3358 
3359     rc = true;
3360     break;
3361   }
3362 
3363   return rc;
3364 }
3365 
GetSphereDepth(ON_Sphere sphere,double * near_dist,double * far_dist,bool bGrowNearFar) const3366 bool ON_Viewport::GetSphereDepth(
3367        ON_Sphere sphere,
3368        double* near_dist,
3369        double* far_dist,
3370        bool bGrowNearFar
3371        ) const
3372 {
3373   bool rc = GetPointDepth( sphere.Center(), near_dist, far_dist, bGrowNearFar );
3374   if ( rc && sphere.Radius() > 0.0 )
3375   {
3376     if ( 0 != near_dist )
3377       *near_dist -= sphere.Radius();
3378     if ( 0 != far_dist )
3379       *far_dist += sphere.Radius();
3380   }
3381   return rc;
3382 }
3383 
SetFrustumNearFar(double near_dist,double far_dist,double min_near_dist,double min_near_over_far,double target_dist)3384 bool ON_Viewport::SetFrustumNearFar(
3385        double near_dist,
3386        double far_dist,
3387        double min_near_dist,
3388        double min_near_over_far,
3389        double target_dist
3390        )
3391 {
3392   double relative_depth_bias = 0.0;
3393   return SetFrustumNearFar(
3394             near_dist,
3395             far_dist,
3396             min_near_dist,
3397             min_near_over_far,
3398             target_dist,
3399             relative_depth_bias
3400             );
3401 }
3402 
SetFrustumNearFar(double near_dist,double far_dist,double min_near_dist,double min_near_over_far,double target_dist,double relative_depth_bias)3403 bool ON_Viewport::SetFrustumNearFar(
3404        double near_dist,
3405        double far_dist,
3406        double min_near_dist,
3407        double min_near_over_far,
3408        double target_dist,
3409        double relative_depth_bias
3410        )
3411 {
3412   if (    !ON_IsValid(near_dist)
3413        || !ON_IsValid(far_dist)
3414        || near_dist > far_dist )
3415   {
3416     return false;
3417   }
3418 
3419   // min_near_over_far needs to be < 1 and should be in the
3420   // range 1e-6 to 1e-2.  By setting negative min's to zero,
3421   // the code below is simplified but still ignores a negative
3422   // input.
3423   const double tiny = ON_ZERO_TOLERANCE;
3424   const double MIN_NEAR_DIST = ( ON_IsValid(m__MIN_NEAR_DIST) &&  m__MIN_NEAR_DIST <= tiny )
3425                             ? m__MIN_NEAR_DIST
3426                             : ON_Viewport::DefaultMinNearDist;
3427   const double MIN_NEAR_OVER_FAR = (    ON_IsValid(m__MIN_NEAR_OVER_FAR)
3428                                      && m__MIN_NEAR_OVER_FAR > tiny
3429                                      && m__MIN_NEAR_OVER_FAR < 1.0-tiny )
3430                                  ? m__MIN_NEAR_OVER_FAR
3431                                  : ON_Viewport::DefaultMinNearOverFar;
3432 
3433   // 30 May Dale Lear
3434   //    Add checks for validity of min_near_dist and min_near_over_far
3435   if ( !ON_IsValid(min_near_dist) || min_near_dist <= tiny )
3436   {
3437     min_near_dist = MIN_NEAR_DIST;
3438   }
3439 
3440   if (    !ON_IsValid(min_near_over_far)
3441        || min_near_over_far <= tiny
3442        || min_near_over_far >= 1.0-tiny )
3443   {
3444     min_near_over_far = MIN_NEAR_OVER_FAR;
3445   }
3446 
3447   if ( IsPerspectiveProjection() )
3448   {
3449     // make sure 0 < near_dist < far_dist
3450     if ( near_dist < min_near_dist )
3451       near_dist = min_near_dist;
3452 
3453     if ( far_dist <= near_dist+tiny )
3454     {
3455       far_dist =  100.0*near_dist;
3456       if ( target_dist > near_dist+min_near_dist && far_dist <= target_dist+min_near_dist )
3457       {
3458         far_dist =  2.0*target_dist - near_dist;
3459       }
3460       if ( near_dist < min_near_over_far*far_dist )
3461         far_dist = near_dist/min_near_over_far;
3462     }
3463     // The 1.0001 fudge factor is to ensure successive calls to this function
3464     // give identical results.
3465     while ( near_dist < 1.0001*min_near_over_far*far_dist )
3466     {
3467       // need to move near and far closer together
3468       if ( ON_IsValid(target_dist) && near_dist < target_dist && target_dist < far_dist )
3469       {
3470         // STEP 1
3471         // If near and far are a long ways from the target
3472         // point, move them towards the target so the
3473         // fine tuning in step 2 makes sense.
3474         if ( target_dist/far_dist < min_near_over_far )
3475         {
3476           if ( near_dist/target_dist >= sqrt(min_near_over_far) )
3477           {
3478             // assume near_dist is good and just pull back far_dist
3479             far_dist = near_dist/min_near_over_far;
3480             break;
3481           }
3482           else
3483           {
3484             // move far_dist to within striking distance of the target
3485             // and let STEP 2 fine tune things.
3486             far_dist = target_dist/min_near_over_far;
3487           }
3488         }
3489 
3490         if ( near_dist/target_dist < min_near_over_far )
3491         {
3492           if ( target_dist/far_dist <= sqrt(min_near_over_far)
3493                && far_dist <= 4.0*target_dist )
3494           {
3495             // assume far_dist is good and just move up near_dist
3496             near_dist = far_dist*min_near_over_far;
3497             break;
3498           }
3499           else
3500           {
3501             // move near_dist to within striking distance of the target
3502             // and let STEP 2 fine tune things.
3503             near_dist = target_dist*min_near_over_far;
3504           }
3505         }
3506 
3507         // STEP 2
3508         // Move near and far towards target by
3509         // an amount proportional to current
3510         // distances from the target.
3511 
3512         double b = (far_dist - target_dist)*min_near_over_far + (target_dist - near_dist);
3513         if ( b > 0.0)
3514         {
3515           double s = target_dist*(1.0 - min_near_over_far)/b;
3516           if ( s > 1.0 || s <= ON_ZERO_TOLERANCE || !ON_IsValid(s) )
3517           {
3518             if ( s > 1.00001 || s <= ON_ZERO_TOLERANCE )
3519             {
3520               // should never happen
3521               ON_ERROR("ON_Viewport::SetFrustumNearFar arithmetic problem 1.");
3522             }
3523             s = 1.0;
3524           }
3525 
3526           // 19 Jan 2010, Mikko:
3527           // Reordered the operations to guarantee n==near_dist and f==far_dist
3528           // when s==1.0. The old system generated bogus problem reports when the dist
3529           // difference was big.
3530           double n = s*near_dist + target_dist*(1.0-s);
3531           double f = s*far_dist + target_dist*(1.0-s);
3532           //double n = target_dist + s*(near_dist-target_dist);
3533           //double f = target_dist + s*(far_dist-target_dist);
3534 
3535 #if defined(ON_DEBUG)
3536           double m = ((f != 0.0) ? n/f : 0.0)/min_near_over_far;
3537           if ( m < 0.95 || m > 1.05 )
3538           {
3539             ON_ERROR("ON_Viewport::SetFrustumNearFar arithmetic problem 2.");
3540           }
3541 #endif
3542 
3543           if ( n < near_dist || n >= target_dist)
3544           {
3545             ON_ERROR("ON_Viewport::SetFrustumNearFar arithmetic problem 3.");
3546             if ( target_dist < f && f < far_dist )
3547               n = min_near_over_far*f;
3548             else
3549               n = near_dist;
3550           }
3551           if ( f > far_dist || f <= target_dist )
3552           {
3553             ON_ERROR("ON_Viewport::SetFrustumNearFar arithmetic problem 4.");
3554             if ( near_dist < n && n < target_dist )
3555               f = n/min_near_over_far;
3556             else
3557               f = far_dist;
3558           }
3559 
3560           if ( n < min_near_over_far*f )
3561             n = min_near_over_far*f;
3562           else
3563             f = n/min_near_over_far;
3564 
3565           near_dist = n;
3566           far_dist = f;
3567         }
3568         else
3569         {
3570           near_dist = min_near_over_far*far_dist;
3571         }
3572       }
3573       else if ( ON_IsValid(target_dist) && fabs(far_dist-target_dist) > fabs(near_dist-target_dist) )
3574       {
3575         far_dist = near_dist/min_near_over_far;
3576       }
3577       else
3578       {
3579         near_dist = min_near_over_far*far_dist;
3580       }
3581       break;
3582     }
3583   }
3584   else
3585   {
3586     // parallel projection
3587     if ( far_dist <= near_dist+tiny)
3588     {
3589       double d = fabs(near_dist)*0.125;
3590       if ( d <= MIN_NEAR_DIST || d < tiny || d < min_near_dist )
3591         d = 1.0;
3592       near_dist -= d;
3593       far_dist += d;
3594     }
3595 
3596     if ( near_dist < min_near_dist || near_dist < MIN_NEAR_DIST )
3597     {
3598       if ( !m_bValidCamera )
3599         return false;
3600       // move camera back in parallel projection so everything shows
3601       double h = fabs(m_frus_top - m_frus_bottom);
3602       double w = fabs(m_frus_right - m_frus_left);
3603       double r = 0.5*((h > w) ? h : w);
3604       double n = 3.0*r;
3605       if (n < 2.0*min_near_dist )
3606         n = 2.0*min_near_dist;
3607       if ( n < 2.0*MIN_NEAR_DIST )
3608         n = 2.0*MIN_NEAR_DIST;
3609       double d = n-near_dist;
3610       ON_3dPoint new_loc = CameraLocation() + d*CameraZ();
3611       SetCameraLocation(new_loc);
3612       if ( m_bValidFrustum && fabs(m_frus_near) >= d*ON_SQRT_EPSILON )
3613       {
3614         m_frus_near += d;
3615         m_frus_far += d;
3616       }
3617       near_dist = n;
3618       far_dist += d;
3619       target_dist += d;
3620       if ( far_dist < near_dist )
3621       {
3622         // could happen if d is < ON_EPSILON*far_dist
3623         far_dist = 1.125*near_dist;
3624       }
3625     }
3626   }
3627 
3628   // call bare bones setter
3629   bool rc = SetFrustumNearFar( near_dist, far_dist );
3630 
3631   // if depth bias will be applied, then make an attempt
3632   // to adust the frustum's near plane to prevent
3633   // clipping biased objects.  This post-adjustment
3634   // fixes display bugs like # 87514.
3635   if ( rc
3636        && relative_depth_bias > 0.0 && relative_depth_bias <= 0.5
3637        && m_frus_near > min_near_dist
3638        && m_frus_far > m_frus_near
3639        && m_frus_near > MIN_NEAR_DIST
3640        )
3641   {
3642     const double near0 = m_frus_near;
3643     const double far0 = m_frus_far;
3644     double bias_3d = 1.001*relative_depth_bias*(m_frus_far - m_frus_near);
3645     double near1 = m_frus_near - bias_3d;
3646     if ( IsPerspectiveProjection() )
3647     {
3648       if ( near1 < min_near_over_far*far0 || near1 < MIN_NEAR_OVER_FAR*far0 )
3649       {
3650         if (near0 - near1 > 0.01*near0)
3651           near1 = 0.99*near0;
3652       }
3653     }
3654 
3655     // It is important that this test be applied in perspective
3656     // and parallel views.  Otherwise the camera location in
3657     // parallel view will creep back when SetFrustumNearFar()
3658     // is called multiple times.
3659     if ( !(near1 >= min_near_dist && near1 >= MIN_NEAR_DIST) )
3660     {
3661       near1 = (min_near_dist >= MIN_NEAR_DIST)
3662             ? min_near_dist
3663             : MIN_NEAR_DIST;
3664     }
3665 
3666     if ( near1 < near0 )
3667     {
3668 #if defined(ON_DEBUG)
3669       const ON_3dPoint debug_camloc0(m_CamLoc);
3670 #endif
3671       if ( IsPerspectiveProjection() )
3672       {
3673         rc = SetFrustumNearFar( near1, far0 );
3674         if (!rc)
3675           rc = SetFrustumNearFar( near0, far0 );
3676       }
3677       else
3678       {
3679         // call this function again with relative_depth_bias = 0.0
3680         // to get cameral location positioned correctly when near1
3681         // is too small or negative.
3682         rc = SetFrustumNearFar(
3683           near1, far0,
3684           min_near_dist, min_near_over_far,
3685           target_dist,
3686           0.0
3687           );
3688         if (!rc)
3689           rc = SetFrustumNearFar(
3690             near0, far0,
3691             min_near_dist, min_near_over_far,
3692             target_dist,
3693             0.0
3694             );
3695       }
3696 #if defined(ON_DEBUG)
3697       if ( debug_camloc0 != m_CamLoc )
3698       {
3699         ON_WARNING("Relative depth bias changed camera location.");
3700       }
3701 #endif
3702     }
3703   }
3704 
3705   return rc;
3706 }
3707 
3708 
GetFrustumLeftPlane(ON_Plane & left_plane) const3709 bool ON_Viewport::GetFrustumLeftPlane(
3710   ON_Plane& left_plane
3711   ) const
3712 {
3713   bool rc = m_bValidCamera && m_bValidFrustum;
3714   if (rc)
3715   {
3716     if ( IsPerspectiveProjection() )
3717     {
3718       ON_2dVector v(m_frus_near,m_frus_left);
3719       rc = v.Unitize();
3720       left_plane.origin = m_CamLoc;
3721       left_plane.xaxis =  v.y*m_CamX - v.x*m_CamZ;
3722       left_plane.yaxis =  m_CamY;
3723       left_plane.zaxis =  v.x*m_CamX + v.y*m_CamZ;
3724     }
3725     else
3726     {
3727       left_plane.origin = m_CamLoc + m_frus_left*m_CamX;
3728       left_plane.xaxis = -m_CamZ;
3729       left_plane.yaxis =  m_CamY;
3730       left_plane.zaxis =  m_CamX;
3731     }
3732     left_plane.UpdateEquation();
3733   }
3734   return rc;
3735 }
3736 
GetFrustumLeftPlaneEquation(ON_PlaneEquation & left_plane_equation) const3737 bool ON_Viewport::GetFrustumLeftPlaneEquation(
3738   ON_PlaneEquation& left_plane_equation
3739   ) const
3740 {
3741   bool rc = m_bValidCamera && m_bValidFrustum;
3742   if (rc)
3743   {
3744 
3745     if ( IsPerspectiveProjection() )
3746     {
3747       ON_2dVector v(m_frus_near,m_frus_left);
3748       if ( 0 != (rc = v.Unitize()) )
3749       {
3750         left_plane_equation.ON_3dVector::operator=(v.x*m_CamX + v.y*m_CamZ);
3751         left_plane_equation.d = -left_plane_equation.ON_3dVector::operator*(m_CamLoc);
3752       }
3753     }
3754     else
3755     {
3756       left_plane_equation.ON_3dVector::operator=(m_CamX);
3757       left_plane_equation.d = -left_plane_equation.ON_3dVector::operator*(m_CamLoc + m_frus_left*m_CamX);
3758     }
3759   }
3760   return rc;
3761 }
3762 
3763 
GetFrustumRightPlane(ON_Plane & right_plane) const3764 bool ON_Viewport::GetFrustumRightPlane(
3765   ON_Plane& right_plane
3766   ) const
3767 {
3768   bool rc = m_bValidCamera && m_bValidFrustum;
3769   if (rc)
3770   {
3771     if ( IsPerspectiveProjection() )
3772     {
3773       ON_2dVector v(m_frus_near,-m_frus_right);
3774       rc = v.Unitize();
3775       right_plane.origin = m_CamLoc;
3776       right_plane.xaxis =  v.y*m_CamX + v.x*m_CamZ;
3777       right_plane.yaxis =  m_CamY;
3778       right_plane.zaxis = -v.x*m_CamX + v.y*m_CamZ;
3779     }
3780     else
3781     {
3782       right_plane.origin = m_CamLoc + m_frus_right*m_CamX;
3783       right_plane.xaxis =  m_CamZ;
3784       right_plane.yaxis =  m_CamY;
3785       right_plane.zaxis = -m_CamX;
3786     }
3787     right_plane.UpdateEquation();
3788   }
3789   return rc;
3790 }
3791 
GetFrustumRightPlaneEquation(ON_PlaneEquation & right_plane_equation) const3792 bool ON_Viewport::GetFrustumRightPlaneEquation(
3793   ON_PlaneEquation& right_plane_equation
3794   ) const
3795 {
3796   bool rc = m_bValidCamera && m_bValidFrustum;
3797   if (rc)
3798   {
3799 
3800     if ( IsPerspectiveProjection() )
3801     {
3802       ON_2dVector v(m_frus_near,-m_frus_right);
3803       if ( 0 != (rc = v.Unitize()) )
3804       {
3805         right_plane_equation.ON_3dVector::operator=(-v.x*m_CamX + v.y*m_CamZ);
3806         right_plane_equation.d = -right_plane_equation.ON_3dVector::operator*(m_CamLoc);
3807       }
3808     }
3809     else
3810     {
3811       right_plane_equation.ON_3dVector::operator=(-m_CamX);
3812       right_plane_equation.d = -right_plane_equation.ON_3dVector::operator*(m_CamLoc + m_frus_right*m_CamX);
3813     }
3814   }
3815   return rc;
3816 }
3817 
3818 
GetFrustumBottomPlane(ON_Plane & bottom_plane) const3819 bool ON_Viewport::GetFrustumBottomPlane(
3820   ON_Plane& bottom_plane
3821   ) const
3822 {
3823   bool rc = m_bValidCamera && m_bValidFrustum;
3824   if (rc)
3825   {
3826     if ( IsPerspectiveProjection() )
3827     {
3828       ON_2dVector v(m_frus_near,m_frus_bottom);
3829       rc = v.Unitize();
3830       bottom_plane.origin = m_CamLoc;
3831       bottom_plane.xaxis = -v.y*m_CamY + v.x*m_CamZ;
3832       bottom_plane.yaxis =  m_CamX;
3833       bottom_plane.zaxis =  v.x*m_CamY + v.y*m_CamZ;
3834     }
3835     else
3836     {
3837       bottom_plane.origin = m_CamLoc + m_frus_bottom*m_CamY;
3838       bottom_plane.xaxis =  m_CamZ;
3839       bottom_plane.yaxis =  m_CamX;
3840       bottom_plane.zaxis =  m_CamY;
3841     }
3842     bottom_plane.UpdateEquation();
3843   }
3844   return rc;
3845 }
3846 
3847 
GetFrustumBottomPlaneEquation(ON_PlaneEquation & bottom_plane_equation) const3848 bool ON_Viewport::GetFrustumBottomPlaneEquation(
3849   ON_PlaneEquation& bottom_plane_equation
3850   ) const
3851 {
3852   bool rc = m_bValidCamera && m_bValidFrustum;
3853   if (rc)
3854   {
3855 
3856     if ( IsPerspectiveProjection() )
3857     {
3858       ON_2dVector v(m_frus_near,m_frus_bottom);
3859       if ( 0 != (rc = v.Unitize()) )
3860       {
3861         bottom_plane_equation.ON_3dVector::operator=(v.x*m_CamY + v.y*m_CamZ);
3862         bottom_plane_equation.d = -bottom_plane_equation.ON_3dVector::operator*(m_CamLoc);
3863       }
3864     }
3865     else
3866     {
3867       bottom_plane_equation.ON_3dVector::operator=(m_CamY);
3868       bottom_plane_equation.d = -bottom_plane_equation.ON_3dVector::operator*(m_CamLoc + m_frus_bottom*m_CamY);
3869     }
3870   }
3871   return rc;
3872 }
3873 
3874 
GetFrustumTopPlane(ON_Plane & top_plane) const3875 bool ON_Viewport::GetFrustumTopPlane(
3876   ON_Plane& top_plane
3877   ) const
3878 {
3879   bool rc = m_bValidCamera && m_bValidFrustum;
3880   if (rc)
3881   {
3882     if ( IsPerspectiveProjection() )
3883     {
3884       ON_2dVector v(m_frus_near,-m_frus_top);
3885       rc = v.Unitize();
3886       top_plane.origin = m_CamLoc;
3887       top_plane.xaxis = -v.y*m_CamY - v.x*m_CamZ;
3888       top_plane.yaxis =  m_CamX;
3889       top_plane.zaxis = -v.x*m_CamY + v.y*m_CamZ;
3890     }
3891     else
3892     {
3893       top_plane.origin = m_CamLoc + m_frus_top*m_CamY;
3894       top_plane.xaxis = -m_CamZ;
3895       top_plane.yaxis =  m_CamX;
3896       top_plane.zaxis = -m_CamY;
3897     }
3898     top_plane.UpdateEquation();
3899   }
3900   return rc;
3901 }
3902 
GetFrustumTopPlaneEquation(ON_PlaneEquation & top_plane_equation) const3903 bool ON_Viewport::GetFrustumTopPlaneEquation(
3904   ON_PlaneEquation& top_plane_equation
3905   ) const
3906 {
3907   bool rc = m_bValidCamera && m_bValidFrustum;
3908   if (rc)
3909   {
3910 
3911     if ( IsPerspectiveProjection() )
3912     {
3913       ON_2dVector v(m_frus_near,-m_frus_top);
3914       if ( 0 != (rc = v.Unitize()) )
3915       {
3916         top_plane_equation.ON_3dVector::operator=(-v.x*m_CamY + v.y*m_CamZ);
3917         top_plane_equation.d = -top_plane_equation.ON_3dVector::operator*(m_CamLoc);
3918       }
3919     }
3920     else
3921     {
3922       top_plane_equation.ON_3dVector::operator=(-m_CamY);
3923       top_plane_equation.d = -top_plane_equation.ON_3dVector::operator*(m_CamLoc + m_frus_top*m_CamY);
3924     }
3925   }
3926   return rc;
3927 }
3928 
GetViewScale(double * x,double * y) const3929 void ON_Viewport::GetViewScale( double* x, double* y ) const
3930 {
3931   if ( x ) *x = 1.0;
3932   if ( y ) *y = 1.0;
3933   if ( !m_clip_mods.IsIdentity()
3934        && 0.0 == m_clip_mods.m_xform[3][0]
3935        && 0.0 == m_clip_mods.m_xform[3][1]
3936        && 0.0 == m_clip_mods.m_xform[3][2]
3937        && 1.0 == m_clip_mods.m_xform[3][3]
3938      )
3939   {
3940     // 04 November 2011 S. Baer (RR93636)
3941     //   See comments in SetViewScale about why we are ignoring the test for 1
3942     //   on either sx or sy
3943     double sx = m_clip_mods.m_xform[0][0];
3944     double sy = m_clip_mods.m_xform[1][1];
3945     if (    sx > ON_ZERO_TOLERANCE
3946          && sy > ON_ZERO_TOLERANCE
3947          && 0.0 == m_clip_mods.m_xform[0][1]
3948          && 0.0 == m_clip_mods.m_xform[0][2]
3949          && 0.0 == m_clip_mods.m_xform[1][0]
3950          && 0.0 == m_clip_mods.m_xform[1][2]
3951          // && (1.0 == sx || 1.0 == sy )
3952         )
3953     {
3954       if ( x ) *x = sx;
3955       if ( y ) *y = sy;
3956     }
3957   }
3958 }
3959 
3960 //bool ON_Viewport::ScaleView( double x, double y, double z )
3961 //{
3962 //  // z ignored on purpose - it was a mistake to include z
3963 //  return (!IsPerspectiveProjection()) ? SetViewScale(x,y) : false;
3964 //}
3965 
SetViewScale(double x,double y)3966 bool ON_Viewport::SetViewScale( double x, double y )
3967 {
3968   // 22 May Dale Lear
3969   //   View scaling should have been done by adjusting the
3970   //   frustum left/right top/bottom but I was stupid and added a clipmodxform
3971   //   that is more trouble than it is worth.
3972   //   Someday I will fix this.  In the mean time, I want all scaling requests
3973   //   to flow through SetViewScale/GetViewScale so I can easly find and fix
3974   //   things when I have time to do it right.
3975   // 04 November 2011 S. Baer (RR93636)
3976   //   This function is used for printer calibration and it is commonly possible
3977   //   to need to apply a scale in both x and y.  The reason for the need of x
3978   //   or y to be one is because the view scale is encoded in the clip mod xform
3979   //   and it is hard to be sure that we could accurately extract these values
3980   //   when calling GetViewScale.  Removing the requirement to have one of the
3981   //   values == 1
3982   bool rc = false;
3983   if (    !IsPerspectiveProjection()
3984        && x > ON_ZERO_TOLERANCE && ON_IsValid(x)
3985        && y > ON_ZERO_TOLERANCE && ON_IsValid(y)
3986        // && (1.0 == x || 1.0 == y)
3987        )
3988   {
3989     ON_Xform xform(1.0);
3990     xform.m_xform[0][0] = x;
3991     xform.m_xform[1][1] = y;
3992     rc = SetClipModXform(xform);
3993   }
3994   return rc;
3995 }
3996 
ClipCoordDepthBias(double relative_depth_bias,double clip_z,double clip_w) const3997 double ON_Viewport::ClipCoordDepthBias( double relative_depth_bias, double clip_z, double clip_w ) const
3998 {
3999   double d;
4000   if ( m_frus_far > m_frus_near
4001        && 0.0 != relative_depth_bias
4002        && 0.0 != clip_w
4003      )
4004   {
4005     if ( ON::perspective_view == m_projection )
4006     {
4007       // To get the formula for the code in this claus:
4008       //
4009       // Set M = [Camera2Clip]*[translation by (0,0,relative_depth_bias*(f-n)]*[Clip2Camera]
4010       // Note that M maps clipping coordinates to clipping coordinates.
4011       //
4012       // Calculate M([x,y,z,w]) = [p,q,r,s]
4013       //
4014       // This function returns (r/s - z/w)*w
4015       //
4016       // If you are actually doing this calculation and trying to
4017       // get the formula used in the code below, it helps to notice
4018       // that (f+n)/(f-n) = a/b.
4019       //
4020       // Note that there "should" be a small adjustment to the
4021       // x and y coordinates that is not performed by tweaking
4022       // the z clipping coordiante
4023       //    z += vp->ClipCoordDepthBias( rel_bias, z, w );
4024       // but the effect is actually better when the goal is to
4025       // make wires that are on shaded surfaces appear because
4026       // their horizons are not altered.
4027       //
4028       // This method is more complicated that adding a constant
4029       // depth buffer bias but is required for high quality images
4030       // when values of far/near get to be around 1e4 or larger.
4031       //
4032       double a = m_frus_far + m_frus_near;
4033       double b = m_frus_far - m_frus_near;
4034       double c = 0.5*relative_depth_bias/(m_frus_far*m_frus_near);
4035       double t = a + b*clip_z/clip_w;
4036       d = c*t*t*clip_w/(1.0 - c*b*t);
4037     }
4038     else
4039     {
4040       // The "2.0*" is here because clipping coordinates run from
4041       // -1 to +1, a distance of 2 units.
4042       d = 2.0*relative_depth_bias*clip_w;
4043     }
4044   }
4045   else
4046   {
4047     d = 0.0;
4048   }
4049   return d;
4050 }
4051 
GetClipCoordDepthBiasXform(double relative_depth_bias,ON_Xform & clipbias) const4052 bool ON_Viewport::GetClipCoordDepthBiasXform(
4053     double relative_depth_bias,
4054     ON_Xform& clipbias
4055     ) const
4056 {
4057   bool rc = false;
4058 
4059   while ( 0.0 != relative_depth_bias
4060        && m_frus_far > m_frus_near
4061        )
4062   {
4063     if ( ON::perspective_view == m_projection )
4064     {
4065       ON_Xform clip2cam, cam_delta(1.0), cam2clip;
4066       if ( !cam2clip.CameraToClip(true,m_frus_left,m_frus_right,m_frus_bottom,m_frus_top,m_frus_near,m_frus_far) )
4067         break;
4068       if ( !clip2cam.ClipToCamera(true,m_frus_left,m_frus_right,m_frus_bottom,m_frus_top,m_frus_near,m_frus_far) )
4069         break;
4070       cam_delta.m_xform[2][3] = relative_depth_bias*(m_frus_far-m_frus_near);
4071       clipbias = cam2clip*cam_delta*clip2cam;
4072     }
4073     else
4074     {
4075       clipbias.Identity();
4076       clipbias.m_xform[2][3] = 2.0*relative_depth_bias;
4077     }
4078     rc = true;
4079     break;
4080   }
4081 
4082   if (!rc)
4083     clipbias.Identity();
4084 
4085   return rc;
4086 }
4087 
SetClipModXform(ON_Xform clip_mod_xform)4088 bool ON_Viewport::SetClipModXform( ON_Xform clip_mod_xform )
4089 {
4090   bool rc = false;
4091   ON_Xform clip_mod_inverse_xform = clip_mod_xform;
4092   rc = clip_mod_inverse_xform.Invert();
4093   if ( rc )
4094   {
4095     ON_Xform id = clip_mod_inverse_xform*clip_mod_xform;
4096     double e;
4097     int i, j;
4098     for ( i = 0; i < 4 && rc; i++ ) for ( j = 0; j < 4 && rc; j++ )
4099     {
4100       e = ( i == j ) ? 1.0 : 0.0;
4101       if ( fabs(id.m_xform[i][j] - e) > ON_SQRT_EPSILON )
4102       {
4103         rc = false;
4104       }
4105     }
4106     if (rc)
4107     {
4108       m_clip_mods = clip_mod_xform;
4109       m_clip_mods_inverse = clip_mod_inverse_xform;
4110     }
4111   }
4112   return rc;
4113 }
4114 
ClipModXformIsIdentity() const4115 bool ON_Viewport::ClipModXformIsIdentity() const
4116 {
4117   return m_clip_mods.IsIdentity();
4118 }
4119 
ClipModXform() const4120 ON_Xform ON_Viewport::ClipModXform() const
4121 {
4122   return m_clip_mods;
4123 }
4124 
ClipModInverseXform() const4125 ON_Xform ON_Viewport::ClipModInverseXform() const
4126 {
4127   return m_clip_mods_inverse;
4128 }
4129 
SetTargetPoint(ON_3dPoint target_point)4130 bool ON_Viewport::SetTargetPoint( ON_3dPoint target_point )
4131 {
4132   bool rc = (target_point.IsValid() || (ON_UNSET_POINT == target_point));
4133   if (rc)
4134     m_target_point = target_point;
4135   return rc;
4136 }
4137 
FrustumCenterPoint(double target_distance) const4138 ON_3dPoint ON_Viewport::FrustumCenterPoint( double target_distance ) const
4139 {
4140   double s,dx,dy,dz;
4141   ON_3dPoint target_point = ON_3dPoint::UnsetPoint;
4142 
4143   if (!m_bValidCamera || !m_bValidFrustum)
4144     return target_point;
4145 
4146   if ( ON_UNSET_VALUE == target_distance && m_bValidFrustum
4147        && m_frus_near > 0.0 && m_frus_far >= m_frus_near
4148      )
4149   {
4150     target_distance = 0.5*(m_frus_near+m_frus_far);
4151     if ( target_distance < m_frus_near )
4152       target_distance = m_frus_near;
4153     else if ( target_distance > m_frus_far )
4154       target_distance = m_frus_far;
4155   }
4156 
4157   if ( !ON_IsValid(target_distance) || target_distance <= 0.0 )
4158     return target_point;
4159 
4160   if ( m_bValidFrustum )
4161   {
4162     s  = (ON::perspective_view == m_projection && m_frus_near > 0.0)
4163        ? 0.5*target_distance/m_frus_near
4164        : 0.5;
4165     dx = FrustumIsLeftRightSymmetric()
4166        ?  0.0
4167        : s*(m_frus_right+m_frus_left);
4168     dy = FrustumIsTopBottomSymmetric()
4169        ?  0.0
4170        : s*(m_frus_top+m_frus_bottom);
4171   }
4172   else
4173   {
4174     dx = dy = 0.0;
4175   }
4176   dz = -target_distance;
4177 
4178   // Done this way instead of using ON_3dPoint/ON_3dVector arithmetic so the
4179   // optimizer can generate maximum precision when using 64 bit mantissas.
4180   target_point.x = (m_CamLoc.x + dx*m_CamX.x + dy*m_CamY.x + dz*m_CamZ.x);
4181   target_point.y = (m_CamLoc.y + dx*m_CamX.y + dy*m_CamY.y + dz*m_CamZ.y);
4182   target_point.z = (m_CamLoc.z + dx*m_CamX.z + dy*m_CamY.z + dz*m_CamZ.z);
4183 
4184   return target_point;
4185 }
4186 
TargetPoint() const4187 ON_3dPoint ON_Viewport::TargetPoint() const
4188 {
4189   return m_target_point;
4190 }
4191 
4192 
TargetDistance(bool bUseFrustumCenterFallback) const4193 double ON_Viewport::TargetDistance( bool bUseFrustumCenterFallback ) const
4194 {
4195   double d = ON_UNSET_VALUE;
4196   if ( m_bValidCamera )
4197   {
4198     if ( bUseFrustumCenterFallback && !m_bValidFrustum )
4199       bUseFrustumCenterFallback = false;
4200     if ( m_target_point.IsValid() )
4201     {
4202       d = (m_CamLoc - m_target_point)*m_CamZ;
4203       if ( bUseFrustumCenterFallback && (!ON_IsValid(d) || d <= 0.0) )
4204         d = ON_UNSET_VALUE;
4205     }
4206     if ( bUseFrustumCenterFallback
4207         && ON_UNSET_VALUE == d
4208         && m_frus_far >= m_frus_near
4209         )
4210     {
4211       d = 0.5*(m_frus_near+m_frus_far);
4212       if ( d < m_frus_near ) d = m_frus_near; else if (d > m_frus_far) d = m_frus_far;
4213       if ( d <= 0.0 )
4214         d = ON_UNSET_VALUE;
4215     }
4216   }
4217   return d;
4218 }
4219 
SetViewportId(const ON_UUID & id)4220 bool ON_Viewport::SetViewportId( const ON_UUID& id)
4221 {
4222   // Please discuss any code changes with Dale Lear.
4223   // You should NEVER change the viewport id once
4224   // it is set.
4225   bool rc = (0 == memcmp(&m_viewport_id,&id,sizeof(m_viewport_id)));
4226   if ( !rc && m_viewport_id == ON_nil_uuid )
4227   {
4228     m_viewport_id = id;
4229     rc = true;
4230   }
4231   return rc;
4232 }
4233 
ChangeViewportId(const ON_UUID & viewport_id)4234 void ON_Viewport::ChangeViewportId(const ON_UUID& viewport_id)
4235 {
4236   m_viewport_id = viewport_id; // <- good place for a breakpoint
4237 }
4238 
4239 
ViewportId(void) const4240 ON_UUID  ON_Viewport::ViewportId(void) const
4241 {
4242   return m_viewport_id;
4243 }
4244 
4245 
4246 class ON_PgonPt
4247 {
4248 public:
4249   ON_3dPoint m_P;
4250   ON_2dVector m_Q;
4251   double m_negcotangle;
4252 };
4253 
4254 static
comparePptAngle(const void * pa,const void * pb)4255 int comparePptAngle( const void* pa, const void* pb )
4256 {
4257   double a = ((const ON_PgonPt*)pa)->m_negcotangle;
4258   double b = ((const ON_PgonPt*)pb)->m_negcotangle;
4259   if ( a == b )
4260   {
4261     a = ((const ON_PgonPt*)pa)->m_Q.LengthSquared();
4262     b = ((const ON_PgonPt*)pb)->m_Q.LengthSquared();
4263   }
4264   return ((a>b) ? 1 : ((a==b) ? 0 : -1));
4265 }
4266 
ON_IntersectViewFrustumPlane(const ON_Viewport & vp,const ON_PlaneEquation & plane_equation,ON_SimpleArray<ON_3dPoint> & points)4267 bool ON_IntersectViewFrustumPlane(
4268     const ON_Viewport& vp,
4269     const ON_PlaneEquation& plane_equation,
4270     ON_SimpleArray<ON_3dPoint>& points
4271     )
4272 {
4273 
4274   double left, right, bottom, top, near_dist, far_dist;
4275   double v[8], v0, v1, s;
4276   ON_PgonPt ppt, ppt_list[24];
4277   ON_3dPoint F[8], P0, P1, P;
4278   ON_2dVector D;
4279   const ON_3dPoint  C = vp.CameraLocation();
4280   const ON_3dVector X = vp.CameraX();
4281   const ON_3dVector Y = vp.CameraY();
4282   const ON_3dVector Z = -vp.CameraZ();
4283   int e[12][2] = {{0,1},{1,2},{2,3},{3,0},
4284                   {4,5},{5,6},{6,7},{7,4},
4285                   {0,4},{1,5},{2,6},{3,7}};
4286   int i, i0, i1;
4287   int ppt_count = 0;
4288 
4289   if ( !vp.IsValidCamera() || !vp.GetFrustum(&left,&right,&bottom,&top,&near_dist,&far_dist) )
4290     return false;
4291 
4292   const ON_Plane plane(plane_equation);
4293   if ( !plane.IsValid() )
4294     return false;
4295 
4296   s = ON::perspective_view == vp.Projection()
4297     ? far_dist/near_dist
4298     : 1.0;
4299   F[0] = C + left*X  + bottom*Y + near_dist*Z;
4300   F[1] = C + right*X + bottom*Y + near_dist*Z;
4301   F[2] = C + right*X + top*Y    + near_dist*Z;
4302   F[3] = C + left*X  + top*Y    + near_dist*Z;
4303 
4304   F[4] = C + s*left*X  + s*bottom*Y + far_dist*Z;
4305   F[5] = C + s*right*X + s*bottom*Y + far_dist*Z;
4306   F[6] = C + s*right*X + s*top*Y    + far_dist*Z;
4307   F[7] = C + s*left*X  + s*top*Y    + far_dist*Z;
4308 
4309   for ( i = 0; i < 8; i++ )
4310   {
4311     v[i] = plane_equation.ValueAt(F[i]);
4312   }
4313 
4314   for ( i = 0; i < 12; i++ )
4315   {
4316     v0 = v[e[i][0]];
4317     v1 = v[e[i][1]];
4318     P0 = F[e[i][0]];
4319     P1 = F[e[i][1]];
4320     if ( (v0 <= 0.0 && v1 >= 0.0) || (v0 >= 0.0 && v1 <= 0.0) )
4321     {
4322       if ( v0 == v1 )
4323       {
4324         ppt_list[ppt_count++].m_P = P0;
4325         ppt_list[ppt_count++].m_P = P1;
4326       }
4327       else
4328       {
4329         s = v1/(v1-v0);
4330         P = s*P0 + (1.0-s)*P1;
4331         ppt_list[ppt_count++].m_P = P;
4332       }
4333     }
4334   }
4335 
4336   if ( ppt_count <= 0 )
4337     return true; // plane misses frustum
4338 
4339   i0 = 0;
4340   for ( i = 0; i < ppt_count; i++ )
4341   {
4342     plane.ClosestPointTo( ppt_list[i].m_P, &ppt_list[i].m_Q.x, &ppt_list[i].m_Q.y );
4343     if (     ppt_list[i].m_Q.y < ppt_list[i0].m_Q.y
4344          || (ppt_list[i].m_Q.y == ppt_list[i0].m_Q.y && ppt_list[i].m_Q.x < ppt_list[i0].m_Q.x) )
4345       i0 = i;
4346   }
4347 
4348   // Use Gram scan to get the convex hull and save it in points[].
4349   // See http://en.wikipedia.org/wiki/Graham_scan for details.
4350 
4351   // put point with smallest m_Q.y coordinate in ppt_list[0].
4352   ppt = ppt_list[i0];
4353   if ( i0 )
4354   {
4355     ppt_list[i0] = ppt_list[0];
4356     ppt_list[0] = ppt;
4357     i0 = 0;
4358   }
4359 
4360   // sort points by the angle (ppt_list[i].m_Q = ppt_list[0].m_Q) makes
4361   // with the positve x axis.  This is the same as sorting them by
4362   // -cot(angle) = -deltax/deltay.
4363   ppt_list[0].m_negcotangle = -ON_DBL_MAX; // -cot(0) = - infinity
4364   for ( i = 1; i < ppt_count; i++ )
4365   {
4366     ppt_list[i].m_Q.x -= ppt_list[0].m_Q.x;
4367     ppt_list[i].m_Q.y -= ppt_list[0].m_Q.y;
4368     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;
4369   }
4370   ppt_list[0].m_Q.x = 0.0;
4371   ppt_list[0].m_Q.y = 0.0;
4372   ON_qsort(ppt_list+1,ppt_count-1,sizeof(ppt_list[0]),comparePptAngle);
4373 
4374   points.Append(ppt_list[0].m_P);
4375   i0 = 0;
4376   i1 = 1;
4377   D = ppt_list[i1].m_Q - ppt_list[i0].m_Q;
4378 
4379   for ( i = 2; i < ppt_count; i++ )
4380   {
4381     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 )
4382     {
4383       // ppt_list[i0], ppt_list[i1], ppt_list[i] is a "right" turn or colinear.
4384       // Drop ppt_list[i1].
4385       i1 = i;
4386     }
4387     else
4388     {
4389       // ppt_list[i0], ppt_list[i1], ppt_list[i] is a "left" turn.
4390       points.Append(ppt_list[i1].m_P);
4391       i0 = i1;
4392       i1 = i;
4393     }
4394     D = ppt_list[i1].m_Q - ppt_list[i0].m_Q;
4395   }
4396 
4397   if ( i1 > i0 )
4398     points.Append(ppt_list[i1].m_P);
4399 
4400   return true;
4401 }
4402 
GetPerspectiveClippingPlaneConstraints(ON_3dPoint camera_location,unsigned int depth_buffer_bit_depth,double * min_near_dist,double * min_near_over_far)4403 void ON_Viewport::GetPerspectiveClippingPlaneConstraints(
4404   ON_3dPoint camera_location,
4405   unsigned int depth_buffer_bit_depth,
4406   double* min_near_dist,
4407   double* min_near_over_far
4408   )
4409 {
4410   double nof, n, d;
4411 
4412   if ( camera_location.IsValid() )
4413   {
4414     /*
4415 
4416     // This code was used prior to 14 July 2011.
4417     //
4418     d = camera_location.DistanceTo(ON_3dPoint::Origin);
4419     if ( d >= 1.0e5 )
4420     {
4421       if ( depth_buffer_bit_depth >= 32 )
4422         depth_buffer_bit_depth -= 24;
4423       else
4424         depth_buffer_bit_depth = 8;
4425     }
4426     else if ( d >= 1.0e4 )
4427     {
4428       if ( depth_buffer_bit_depth >= 24 )
4429         depth_buffer_bit_depth -= 16;
4430       else
4431         depth_buffer_bit_depth = 8;
4432     }
4433     else if ( d >= 1.0e3 )
4434     {
4435       if ( depth_buffer_bit_depth >= 16 )
4436         depth_buffer_bit_depth -= 8;
4437       else
4438         depth_buffer_bit_depth = 8;
4439     }
4440     */
4441 
4442     // 14 July 2011 - Dale Lear
4443     //   The reductions above were too harsh and were
4444     //   generating clipping artifacts in the perspective
4445     //   view in bug report 88216.  Changing to
4446     //   to the code below gets rid of those
4447     //   artifacts at the risk of having a meaninless
4448     //   view to clip transform if the transformation is
4449     //   calculated with single precision numbers.
4450     //   If these values require further tuning, please
4451     //   discuss changes with me and attach example files
4452     //   to bug report 88216.
4453     d = camera_location.MaximumCoordinate();
4454     if ( d > 1.0e6 && depth_buffer_bit_depth >= 16 )
4455       depth_buffer_bit_depth -= 8;
4456   }
4457 
4458   if ( depth_buffer_bit_depth >= 32 )
4459   {
4460     nof = 0.0001;
4461     n = 0.001;
4462   }
4463   else if ( depth_buffer_bit_depth >= 24 )
4464   {
4465     nof = 0.0005;
4466     n = 0.005;
4467   }
4468   else if ( depth_buffer_bit_depth >= 16 )
4469   {
4470     nof = 0.005;
4471     n = 0.005;
4472   }
4473   else
4474   {
4475     nof = 0.01;
4476     n = 0.01;
4477   }
4478 
4479   if ( min_near_dist )
4480     *min_near_dist = n;
4481   if ( min_near_over_far )
4482     *min_near_over_far = nof;
4483 }
4484