1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 
25 #include "pxr/pxr.h"
26 #include "pxr/base/gf/frustum.h"
27 #include "pxr/base/gf/bbox3d.h"
28 #include "pxr/base/gf/matrix4d.h"
29 #include "pxr/base/gf/ostreamHelpers.h"
30 #include "pxr/base/gf/plane.h"
31 #include "pxr/base/gf/vec2d.h"
32 #include "pxr/base/gf/vec2f.h"
33 #include "pxr/base/gf/math.h"
34 #include "pxr/base/tf/enum.h"
35 #include "pxr/base/tf/type.h"
36 
37 #include <algorithm>
38 #include <ostream>
39 
40 using namespace std;
41 
42 PXR_NAMESPACE_OPEN_SCOPE
43 
44 // CODE_COVERAGE_OFF_GCOV_BUG
TF_REGISTRY_FUNCTION(TfType)45 TF_REGISTRY_FUNCTION(TfType) {
46     TfType::Define<GfFrustum>();
47 }
48 // CODE_COVERAGE_ON_GCOV_BUG
49 
TF_REGISTRY_FUNCTION(TfEnum)50 TF_REGISTRY_FUNCTION(TfEnum) {
51     TF_ADD_ENUM_NAME(GfFrustum::Orthographic);
52     TF_ADD_ENUM_NAME(GfFrustum::Perspective);
53 }
54 
55 
GfFrustum()56 GfFrustum::GfFrustum() :
57     _position(0),
58     _window(GfVec2d(-1.0, -1.0), GfVec2d(1.0, 1.0)),
59     _nearFar(1.0, 10.0),
60     _viewDistance(5.0),
61     _projectionType(GfFrustum::Perspective),
62     _planes(nullptr)
63 {
64     _rotation.SetIdentity();
65 }
66 
GfFrustum(const GfVec3d & position,const GfRotation & rotation,const GfRange2d & window,const GfRange1d & nearFar,GfFrustum::ProjectionType projectionType,double viewDistance)67 GfFrustum::GfFrustum(const GfVec3d &position, const GfRotation &rotation,
68                      const GfRange2d &window, const GfRange1d &nearFar,
69                      GfFrustum::ProjectionType projectionType,
70                      double viewDistance) :
71     _position(position),
72     _rotation(rotation),
73     _window(window),
74     _nearFar(nearFar),
75     _viewDistance(viewDistance),
76     _projectionType(projectionType),
77     _planes(nullptr)
78 {
79 }
80 
GfFrustum(const GfMatrix4d & camToWorldXf,const GfRange2d & window,const GfRange1d & nearFar,GfFrustum::ProjectionType projectionType,double viewDistance)81 GfFrustum::GfFrustum(const GfMatrix4d &camToWorldXf,
82                      const GfRange2d &window, const GfRange1d &nearFar,
83                      GfFrustum::ProjectionType projectionType,
84                      double viewDistance) :
85     _window(window),
86     _nearFar(nearFar),
87     _viewDistance(viewDistance),
88     _projectionType(projectionType),
89     _planes(nullptr)
90 {
91     SetPositionAndRotationFromMatrix(camToWorldXf);
92 }
93 
~GfFrustum()94 GfFrustum::~GfFrustum()
95 {
96     delete _planes.load(std::memory_order_relaxed);
97 }
98 
99 void
SetPerspective(double fieldOfViewHeight,double aspectRatio,double nearDistance,double farDistance)100 GfFrustum::SetPerspective(double fieldOfViewHeight, double aspectRatio,
101                           double nearDistance, double farDistance)
102 {
103     SetPerspective(fieldOfViewHeight, true, aspectRatio,
104                    nearDistance, farDistance);
105 }
106 
107 void
SetPerspective(double fieldOfView,bool isFovVertical,double aspectRatio,double nearDistance,double farDistance)108 GfFrustum::SetPerspective(double fieldOfView, bool isFovVertical,
109                           double aspectRatio,
110                           double nearDistance, double farDistance)
111 {
112     _projectionType = GfFrustum::Perspective;
113 
114     double yDist = 1.0;
115     double xDist = 1.0;
116 
117     // Check for 0, use 1 in that case
118     if (aspectRatio == 0.0) {
119         aspectRatio = 1.0;
120     }
121 
122     if (isFovVertical) {
123         // vertical is taken from the given field of view
124         yDist = tan(GfDegreesToRadians(fieldOfView / 2.0))
125                         * GetReferencePlaneDepth();
126         // horizontal is determined by aspect ratio
127         xDist = yDist * aspectRatio;
128     } else {
129         // horizontal is taken from the given field of view
130         xDist = tan(GfDegreesToRadians(fieldOfView / 2.0))
131                         * GetReferencePlaneDepth();
132         // vertical is determined by aspect ratio
133         yDist = xDist / aspectRatio;
134     }
135 
136     _window.SetMin(GfVec2d(-xDist, -yDist));
137     _window.SetMax(GfVec2d(xDist, yDist));
138     _nearFar.SetMin(nearDistance);
139     _nearFar.SetMax(farDistance);
140 
141     _DirtyFrustumPlanes();
142 }
143 
144 bool
GetPerspective(double * fieldOfViewHeight,double * aspectRatio,double * nearDistance,double * farDistance) const145 GfFrustum::GetPerspective(double *fieldOfViewHeight, double *aspectRatio,
146                           double *nearDistance, double *farDistance) const
147 {
148     return GetPerspective(true, fieldOfViewHeight, aspectRatio,
149                           nearDistance, farDistance);
150 }
151 
152 bool
GetPerspective(bool isFovVertical,double * fieldOfView,double * aspectRatio,double * nearDistance,double * farDistance) const153 GfFrustum::GetPerspective(bool isFovVertical,
154                           double *fieldOfView, double *aspectRatio,
155                           double *nearDistance, double *farDistance) const
156 {
157     if (_projectionType != GfFrustum::Perspective)
158         return false;
159 
160     GfVec2d winSize = _window.GetSize();
161 
162     if (isFovVertical) {
163         *fieldOfView =
164             2.0 * GfRadiansToDegrees(
165                 atan( winSize[1] / (2.0 * GetReferencePlaneDepth() )));
166     } else {
167         *fieldOfView =
168             2.0 * GfRadiansToDegrees(
169                 atan( winSize[0] / (2.0 * GetReferencePlaneDepth() )));
170     }
171     *aspectRatio       = winSize[0] / winSize[1];
172 
173     *nearDistance = _nearFar.GetMin();
174     *farDistance  = _nearFar.GetMax();
175 
176     return true;
177 }
178 
179 double
GetFOV(bool isFovVertical)180 GfFrustum::GetFOV(bool isFovVertical /* = false */)
181 {
182     double result = 0.0;
183 
184     if (GetProjectionType() == GfFrustum::Perspective) {
185         double aspectRatio;
186         double nearDistance;
187         double farDistance;
188 
189         GetPerspective(isFovVertical,
190                        &result,
191                        &aspectRatio,
192                        &nearDistance,
193                        &farDistance);
194     }
195 
196     return result;
197 }
198 
199 void
SetOrthographic(double left,double right,double bottom,double top,double nearPlane,double farPlane)200 GfFrustum::SetOrthographic(double left, double right,
201                            double bottom, double top,
202                            double nearPlane, double farPlane)
203 {
204     _projectionType = GfFrustum::Orthographic;
205 
206     _window.SetMin(GfVec2d(left, bottom));
207     _window.SetMax(GfVec2d(right, top));
208     _nearFar.SetMin(nearPlane);
209     _nearFar.SetMax(farPlane);
210 
211     _DirtyFrustumPlanes();
212 }
213 
214 bool
GetOrthographic(double * left,double * right,double * bottom,double * top,double * nearPlane,double * farPlane) const215 GfFrustum::GetOrthographic(double *left, double *right,
216                            double *bottom, double *top,
217                            double *nearPlane, double *farPlane) const
218 {
219     if (_projectionType != GfFrustum::Orthographic)
220         return false;
221 
222     *left   = _window.GetMin()[0];
223     *right  = _window.GetMax()[0];
224     *bottom = _window.GetMin()[1];
225     *top    = _window.GetMax()[1];
226 
227     *nearPlane	= _nearFar.GetMin();
228     *farPlane   = _nearFar.GetMax();
229 
230     return true;
231 }
232 
233 void
FitToSphere(const GfVec3d & center,double radius,double slack)234 GfFrustum::FitToSphere(const GfVec3d &center, double radius, double slack)
235 {
236     //
237     // The first part of this computes a good value for
238     // _viewDistance and modifies the side (left, right, bottom,
239     // and top) coordinates of the frustum as necessary.
240     //
241 
242     if (_projectionType == GfFrustum::Orthographic) {
243         // Set the distance so the viewpoint is outside the sphere.
244         _viewDistance = radius + slack;
245         // Set the camera window to enclose the sphere.
246         _window = GfRange2d(GfVec2d(-radius, -radius),
247                             GfVec2d(radius, radius));
248     }
249     else {
250         // Find the plane coordinate to use to compute the view.
251         // Assuming symmetry, it should be the half-size of the
252         // smaller of the two viewing angles. If asymmetric in a
253         // dimension, use the larger size in that dimension.
254         size_t whichDim = ComputeAspectRatio() > 1.0 ? 1 : 0;
255 
256         // XXX-doc
257         double min = _window.GetMin()[whichDim];
258         double max = _window.GetMax()[whichDim];
259         double halfSize;
260         if (min > 0.0) {
261             halfSize = max;
262         } else if (max < 0.0) {
263             // CODE_COVERAGE_OFF_GCOV_BUG - seems to be hit but gcov disagrees
264             halfSize = min;
265             // CODE_COVERAGE_ON_GCOV_BUG
266         } else if (-min > max) {
267             halfSize = min;
268         } else {
269             halfSize = max;
270         }
271 
272         if (halfSize < 0.0) {
273             halfSize = -halfSize;
274         } else if (halfSize == 0.0) {
275             halfSize = 1.0;     // Why not?
276         }
277 
278         // Determine the distance of the viewpoint from the center of
279         // the sphere to make the frustum tangent to the sphere. Use
280         // similar triangles: the right triangle formed by the
281         // viewpoint and the half-size on the plane is similar to the
282         // right triangle formed by the viewpoint and the radius of
283         // the sphere at the point of tangency.
284         _viewDistance =
285             radius * (1.0/halfSize) *
286              sqrt(GfSqr(halfSize) + GfSqr(_nearFar.GetMin()));
287 
288         // XXX.
289         // Hmmm. This is not really used anywhere but in tests, so
290         // not gonna fix right now but it seems to me the above equation is
291         // off.
292         // In the diagram below, similar triangles yield the following
293         // equal ratios:
294         //    halfSize / referencePlaneDepth = radius / tanDist
295         // So tanDist = (radius * referencePlaneDepth) / halfSize
296         // Then, because it's a right triangle:
297         // viewDistance = sqrt( GfSqr(radius) + GfSqr(tanDist))
298 
299         /*
300 
301           -----    |\                  /
302             ^      |  \ ra            /
303             |      |    \ di         /
304             |      |      \ us      /
305             |      |        \      /
306             |      |          \   /
307             |      |            \/      <---- make believe this is a right angle
308             |      |------------/ ------
309             |      |           /     ^
310             |      |          /      |
311       viewDistance |         /       |
312             |      |        /        |
313             |      |       /t        |
314             |      |      /s        referencePlaneDepth
315             |      |     /i          |
316             |      |    /d           |
317             |      |   /n            |
318             |      |  /a             |
319             |      | /t              v
320             v      |/            ------
321          ------
322                    |            |
323                    |<-halfSize->|
324                    |            |
325                    |            |
326         */
327     }
328 
329     // Adjust the camera so the near plane touches the sphere and the
330     // far plane encloses the sphere.
331     _nearFar.SetMin(_viewDistance - (radius + slack));
332     _nearFar.SetMax(_nearFar.GetMin() + 2.0 * (radius + slack));
333 
334     // Set the camera to use the new position. The view direction
335     // should not have changed.
336     _position = center - _viewDistance * ComputeViewDirection();
337 }
338 
339 GfFrustum &
Transform(const GfMatrix4d & matrix)340 GfFrustum::Transform(const GfMatrix4d &matrix)
341 {
342     // We'll need the old parameters as we build up the new ones, so, work
343     // on a newly instantiated frustum. We'll replace the contents of
344     // this frustum with it once we are done. Note that _dirty is true
345     // by default, so, there is no need to initialize it here.
346     GfFrustum frustum;
347 
348     // Copy the projection type
349     frustum._projectionType = _projectionType;
350 
351     // Transform the position of the frustum
352     frustum._position = matrix.Transform(_position);
353 
354     // Transform the rotation as follows:
355     //   1. build view and direction vectors
356     //   2. transform them with the given matrix
357     //   3. normalize the vectors and cross them to build an orthonormal frame
358     //   4. construct a rotation matrix
359     //   5. extract the new rotation from the matrix
360 
361     // Generate view direction and up vector
362     GfVec3d viewDir = ComputeViewDirection();
363     GfVec3d upVec   = ComputeUpVector();
364 
365     // Transform by matrix
366     GfVec3d viewDirPrime = matrix.TransformDir(viewDir);
367     GfVec3d upVecPrime = matrix.TransformDir(upVec);
368 
369     // Normalize. Save the vec size since it will be used to scale near/far.
370     double scale = viewDirPrime.Normalize();
371     upVecPrime.Normalize();
372 
373     // Cross them to get the third axis. Voila. We have an orthonormal frame.
374     GfVec3d viewRightPrime = GfCross(viewDirPrime, upVecPrime);
375     viewRightPrime.Normalize();
376 
377     // Construct a rotation matrix using the axes.
378     //
379     //  [ right     0 ]
380     //  [ up        1 ]
381     //  [ -viewDir  0 ]
382     //  [ 0  0   0  1 ]
383     GfMatrix4d rotMatrix;
384     rotMatrix.SetIdentity();
385     // first row
386     rotMatrix[0][0] = viewRightPrime[0];
387     rotMatrix[0][1] = viewRightPrime[1];
388     rotMatrix[0][2] = viewRightPrime[2];
389 
390     // second row
391     rotMatrix[1][0] = upVecPrime[0];
392     rotMatrix[1][1] = upVecPrime[1];
393     rotMatrix[1][2] = upVecPrime[2];
394 
395     // third row
396     rotMatrix[2][0] = -viewDirPrime[0];
397     rotMatrix[2][1] = -viewDirPrime[1];
398     rotMatrix[2][2] = -viewDirPrime[2];
399 
400     // Extract rotation
401     frustum._rotation = rotMatrix.ExtractRotation();
402 
403     // Since we applied the matrix to the direction vector, we can use
404     // its length to find out the scaling that needs to applied to the
405     // near and far plane.
406     frustum._nearFar = _nearFar * scale;
407 
408     // Use the same length to scale the view distance
409     frustum._viewDistance = _viewDistance * scale;
410 
411     // Transform the reference plane as follows:
412     //
413     //   - construct two 3D points that are on the reference plane
414     //     (left/bottom and right/top corner of the reference window)
415     //   - transform the points with the given matrix
416     //   - move the window back to one unit from the viewpoint and
417     //     extract the 2D coordinates that would form the new reference
418     //     window
419     //
420     //     A note on how we do the last "move" of the reference window:
421     //     Using similar triangles and the fact that the reference window
422     //     is one unit away from the viewpoint, one can show that it's
423     //     sufficient to divide the x and y components of the transformed
424     //     corners by the length of the transformed direction vector.
425     //
426     //     A 2D diagram helps:
427     //
428     //                            |
429     //                            |
430     //               |            |
431     //       * ------+------------+
432     //      vp       |y1          |
433     //                            |
434     //       \--d1--/             |y2
435     //
436     //       \-------d2----------/
437     //
438     //     So, y1/y2 = d1/d2 ==> y1 = y2 * d1/d2
439     //     Since d1 = 1 ==> y1 = y2 / d2
440     //     The same argument applies to the x coordinate.
441     //
442     // NOTE: In an orthographic projection, the last step (division by
443     // the length of the vector) is skipped.
444     //
445     // XXX NOTE2:  The above derivation relies on the
446     // fact that GetReferecePlaneDepth() is 1.0.
447     // If we ever allow this to NOT be 1, we'll need to fix this up.
448 
449     const GfVec2d &min = _window.GetMin();
450     const GfVec2d &max = _window.GetMax();
451 
452     // Construct the corner points in 3D as follows: construct a starting
453     // point by using the x and y coordinates of the reference plane and
454     // -1 as the z coordinate. Add the position of the frustum to generate
455     // the actual points in world-space coordinates.
456     GfVec3d leftBottom =
457         _position + _rotation.TransformDir(GfVec3d(min[0], min[1], -1.0));
458     GfVec3d rightTop =
459         _position + _rotation.TransformDir(GfVec3d(max[0], max[1], -1.0));
460 
461     // Now, transform the corner points by the given matrix
462     leftBottom = matrix.Transform(leftBottom);
463     rightTop   = matrix.Transform(rightTop);
464 
465     // Subtract the transformed frustum position from the transformed
466     // corner points. Then, rotate the points using the rotation that would
467     // transform the view direction vector back to (0, 0, -1). This brings
468     // the corner points from the woorld coordinate system into the local
469     // frustum one.
470     leftBottom -= frustum._position;
471     rightTop   -= frustum._position;
472     leftBottom = frustum._rotation.GetInverse().TransformDir(leftBottom);
473     rightTop   = frustum._rotation.GetInverse().TransformDir(rightTop);
474 
475     // Finally, use the similar triangles trick to bring the corner
476     // points back at one unit away from the point. These scaled x and
477     // y coordinates can be directly used to construct the new
478     // transformed reference plane.  Skip the scaling step for an
479     // orthographic projection, though.
480     if (_projectionType == Perspective) {
481         leftBottom /= scale;
482         rightTop   /= scale;
483     }
484 
485     frustum._window.SetMin(GfVec2d(leftBottom[0], leftBottom[1]));
486     frustum._window.SetMax(GfVec2d(rightTop[0],   rightTop[1]));
487 
488     // Note that negative scales in the transform have the potential
489     // to flip the window.  Fix it if necessary.
490     GfVec2d wMin = frustum._window.GetMin();
491     GfVec2d wMax = frustum._window.GetMax();
492     // Make sure left < right
493     if ( wMin[0] > wMax[0] ) {
494         std::swap( wMin[0], wMax[0] );
495     }
496     // Make sure bottom < top
497     if ( wMin[1] > wMax[1] ) {
498         std::swap( wMin[1], wMax[1] );
499     }
500     frustum._window.SetMin( wMin );
501     frustum._window.SetMax( wMax );
502 
503     *this = frustum;
504 
505     return *this;
506 }
507 
508 GfVec3d
ComputeViewDirection() const509 GfFrustum::ComputeViewDirection() const
510 {
511     return _rotation.TransformDir(-GfVec3d::ZAxis());
512 }
513 
514 GfVec3d
ComputeUpVector() const515 GfFrustum::ComputeUpVector() const
516 {
517     return _rotation.TransformDir(GfVec3d::YAxis());
518 }
519 
520 void
ComputeViewFrame(GfVec3d * side,GfVec3d * up,GfVec3d * view) const521 GfFrustum::ComputeViewFrame(GfVec3d *side,
522                             GfVec3d *up,
523                             GfVec3d *view) const
524 {
525     *up   = ComputeUpVector();
526     *view = ComputeViewDirection();
527     *side = GfCross(*view, *up);
528 }
529 
530 GfVec3d
ComputeLookAtPoint() const531 GfFrustum::ComputeLookAtPoint() const
532 {
533     return _position + _viewDistance * ComputeViewDirection();
534 }
535 
536 GfMatrix4d
ComputeViewMatrix() const537 GfFrustum::ComputeViewMatrix() const
538 {
539     GfMatrix4d m;
540     m.SetLookAt(_position, _rotation);
541     return m;
542 }
543 
544 GfMatrix4d
ComputeViewInverse() const545 GfFrustum::ComputeViewInverse() const
546 {
547     return ComputeViewMatrix().GetInverse();
548 }
549 
550 GfMatrix4d
ComputeProjectionMatrix() const551 GfFrustum::ComputeProjectionMatrix() const
552 {
553     // Build the projection matrix per Section 2.11 of
554     // The OpenGL Specification: Coordinate Transforms.
555     GfMatrix4d matrix;
556     matrix.SetIdentity();
557 
558     const double l = _window.GetMin()[0];
559     const double r = _window.GetMax()[0];
560     const double b = _window.GetMin()[1];
561     const double t = _window.GetMax()[1];
562     const double n = _nearFar.GetMin();
563     const double f = _nearFar.GetMax();
564 
565     const double rl = r - l;
566     const double tb = t - b;
567     const double fn = f - n;
568 
569     if (_projectionType == GfFrustum::Orthographic) {
570         matrix[0][0] =  2.0 / rl;
571         matrix[1][1] =  2.0 / tb;
572         matrix[2][2] = -2.0 / fn;
573         matrix[3][0] = -(r + l) / rl;
574         matrix[3][1] = -(t + b) / tb;
575         matrix[3][2] = -(f + n) / fn;
576     }
577     else {
578         // Perspective:
579         // The window coordinates are specified with respect to the
580         // reference plane (near == 1).
581         // XXX Note: If we ever allow reference plane depth to be other
582         // than 1.0, we'll need to revisit this.
583         matrix[0][0] = 2.0 / rl;
584         matrix[1][1] = 2.0 / tb;
585         matrix[2][2] = -(f + n) / fn;
586         matrix[2][0] =  (r + l) / rl;
587         matrix[2][1] =  (t + b) / tb;
588         matrix[3][2] = -2.0 * n * f / fn;
589         matrix[2][3] = -1.0;
590         matrix[3][3] =  0.0;
591     }
592 
593     return matrix;
594 }
595 
596 double
ComputeAspectRatio() const597 GfFrustum::ComputeAspectRatio() const
598 {
599     GfVec2d winSize = _window.GetSize();
600     double aspectRatio = 0.0;
601 
602     if (winSize[1] != 0.0)
603         // Negative winsize is used for envcubes, believe it or not.
604         aspectRatio = fabs(winSize[0] / winSize[1]);
605 
606     return aspectRatio;
607 }
608 
609 vector<GfVec3d>
ComputeCorners() const610 GfFrustum::ComputeCorners() const
611 {
612     const GfVec2d &winMin = _window.GetMin();
613     const GfVec2d &winMax = _window.GetMax();
614     double near           = _nearFar.GetMin();
615     double far            = _nearFar.GetMax();
616 
617     vector<GfVec3d> corners;
618     corners.reserve(8);
619 
620     if (_projectionType == Perspective) {
621         // Compute the eye-space corners of the near-plane and
622         // far-plane frustum rectangles using similar triangles. The
623         // reference plane in which the window rectangle is defined is
624         // a distance of 1 from the eyepoint. By similar triangles,
625         // just multiply the window points by near and far to get the
626         // near and far rectangles.
627         // XXX Note: If we ever allow reference plane depth to be other
628         // than 1.0, we'll need to revisit this.
629         corners.push_back(GfVec3d(near * winMin[0], near * winMin[1], -near));
630         corners.push_back(GfVec3d(near * winMax[0], near * winMin[1], -near));
631         corners.push_back(GfVec3d(near * winMin[0], near * winMax[1], -near));
632         corners.push_back(GfVec3d(near * winMax[0], near * winMax[1], -near));
633         corners.push_back(GfVec3d(far  * winMin[0], far  * winMin[1], -far));
634         corners.push_back(GfVec3d(far  * winMax[0], far  * winMin[1], -far));
635         corners.push_back(GfVec3d(far  * winMin[0], far  * winMax[1], -far));
636         corners.push_back(GfVec3d(far  * winMax[0], far  * winMax[1], -far));
637     }
638     else {
639         // Just use the reference plane rectangle as is, translated to
640         // the near and far planes.
641         corners.push_back(GfVec3d(winMin[0], winMin[1], -near));
642         corners.push_back(GfVec3d(winMax[0], winMin[1], -near));
643         corners.push_back(GfVec3d(winMin[0], winMax[1], -near));
644         corners.push_back(GfVec3d(winMax[0], winMax[1], -near));
645         corners.push_back(GfVec3d(winMin[0], winMin[1], -far));
646         corners.push_back(GfVec3d(winMax[0], winMin[1], -far));
647         corners.push_back(GfVec3d(winMin[0], winMax[1], -far));
648         corners.push_back(GfVec3d(winMax[0], winMax[1], -far));
649     }
650 
651     // Each corner is then transformed into world space by the inverse
652     // of the view matrix.
653     GfMatrix4d m = ComputeViewInverse();
654     for (int i = 0; i < 8; i++)
655         corners[i] = m.Transform(corners[i]);
656 
657     return corners;
658 }
659 
660 vector<GfVec3d>
ComputeCornersAtDistance(double d) const661 GfFrustum::ComputeCornersAtDistance(double d) const
662 {
663     const GfVec2d &winMin = _window.GetMin();
664     const GfVec2d &winMax = _window.GetMax();
665 
666     vector<GfVec3d> corners;
667     corners.reserve(4);
668 
669     if (_projectionType == Perspective) {
670         // Similar to ComputeCorners
671         corners.push_back(GfVec3d(d * winMin[0], d * winMin[1], -d));
672         corners.push_back(GfVec3d(d * winMax[0], d * winMin[1], -d));
673         corners.push_back(GfVec3d(d * winMin[0], d * winMax[1], -d));
674         corners.push_back(GfVec3d(d * winMax[0], d * winMax[1], -d));
675     }
676     else {
677         corners.push_back(GfVec3d(winMin[0], winMin[1], -d));
678         corners.push_back(GfVec3d(winMax[0], winMin[1], -d));
679         corners.push_back(GfVec3d(winMin[0], winMax[1], -d));
680         corners.push_back(GfVec3d(winMax[0], winMax[1], -d));
681     }
682 
683     // Each corner is then transformed into world space by the inverse
684     // of the view matrix.
685     const GfMatrix4d m = ComputeViewInverse();
686     for (int i = 0; i < 4; i++)
687         corners[i] = m.Transform(corners[i]);
688 
689     return corners;
690 }
691 
692 GfFrustum
ComputeNarrowedFrustum(const GfVec2d & point,const GfVec2d & halfSize) const693 GfFrustum::ComputeNarrowedFrustum(const GfVec2d &point,
694                                   const GfVec2d &halfSize) const
695 {
696     // Map the point from normalized space (-1 to 1) onto the frustum's
697     // window. First, convert the point into the range from 0 to 1,
698     // then interpolate in the window rectangle.
699     GfVec2d scaledPoint = .5 * (GfVec2d(1.0, 1.0) + point);
700     GfVec2d windowPoint = _window.GetMin() + GfCompMult(scaledPoint,
701                                                         _window.GetSize());
702 
703     return _ComputeNarrowedFrustumSub(windowPoint, halfSize);
704 }
705 
706 GfFrustum
ComputeNarrowedFrustum(const GfVec3d & worldPoint,const GfVec2d & halfSize) const707 GfFrustum::ComputeNarrowedFrustum(const GfVec3d &worldPoint,
708                                   const GfVec2d &halfSize) const
709 {
710     // Map the point from worldspace onto the frustum's window
711     GfVec3d lclPt = ComputeViewMatrix().Transform(worldPoint);
712     if (lclPt[2] >= 0) {
713         TF_WARN("Given worldPoint is behind or at the eye");
714         // Start with this frustum
715         return *this;
716     }
717     double scaleFactor = _nearFar.GetMin() / -lclPt[2];
718     GfVec2d windowPoint(lclPt[0] * scaleFactor, lclPt[1] * scaleFactor);
719 
720     return _ComputeNarrowedFrustumSub(windowPoint, halfSize);
721 }
722 
723 GfFrustum
_ComputeNarrowedFrustumSub(const GfVec2d windowPoint,const GfVec2d & halfSize) const724 GfFrustum::_ComputeNarrowedFrustumSub(const GfVec2d windowPoint,
725                                   const GfVec2d &halfSize) const
726 {
727     // Start with this frustum
728     GfFrustum narrowedFrustum = *this;
729 
730     // Also convert the sizes.
731     GfVec2d halfSizeOnRefPlane = .5 * GfCompMult(halfSize, _window.GetSize());
732 
733     // Shrink the narrowed frustum's window to surround the point.
734     GfVec2d min = windowPoint - halfSizeOnRefPlane;
735     GfVec2d max = windowPoint + halfSizeOnRefPlane;
736 
737     // Make sure the new bounds are within the old window.
738     if (min[0] < _window.GetMin()[0])
739         min[0] = _window.GetMin()[0];
740     if (min[1] < _window.GetMin()[1])
741         min[1] = _window.GetMin()[1];
742     if (max[0] > _window.GetMax()[0])
743         max[0] = _window.GetMax()[0];
744     if (max[1] > _window.GetMax()[1])
745         max[1] = _window.GetMax()[1];
746 
747     // Set the window to the result.
748     narrowedFrustum.SetWindow(GfRange2d(min, max));
749 
750     return narrowedFrustum;
751 }
752 
753 // Utility function for mapping an input value from
754 // one range to another.
755 static double
_Rescale(double in,double inA,double inB,double outA,double outB)756 _Rescale(double in,
757         double inA, double inB,
758         double outA, double outB )
759 {
760     double factor = (inA==inB) ? 0.0 : ((inA-in) / (inA-inB));
761     return outA + ((outB-outA)*factor);
762 }
763 
_ComputeUntransformedRay(GfFrustum::ProjectionType projectionType,const GfRange2d & window,const GfVec2d & windowPos)764 static GfRay _ComputeUntransformedRay(GfFrustum::ProjectionType projectionType,
765                                       const GfRange2d &window,
766                                       const GfVec2d &windowPos)
767 {
768     // Compute position on window, from provided normalized
769     // (-1 to 1) coordinates.
770     double winX = _Rescale(windowPos[0], -1.0, 1.0,
771                            window.GetMin()[0], window.GetMax()[0]);
772     double winY = _Rescale(windowPos[1], -1.0, 1.0,
773                            window.GetMin()[1], window.GetMax()[1]);
774 
775     // Compute the camera-space starting point (the viewpoint) and
776     // direction (toward the point on the window).
777     GfVec3d pos;
778     GfVec3d dir;
779     if (projectionType == GfFrustum::Perspective) {
780         pos = GfVec3d(0);
781         dir = GfVec3d(winX, winY, -1.0).GetNormalized();
782     }
783     else {
784         pos.Set(winX, winY, 0.0);
785         dir = -GfVec3d::ZAxis();
786     }
787 
788     // Build and return the ray
789     return GfRay(pos, dir);
790 }
791 
792 GfRay
ComputeRay(const GfVec2d & windowPos) const793 GfFrustum::ComputeRay(const GfVec2d &windowPos) const
794 {
795     GfRay ray = _ComputeUntransformedRay(_projectionType, _window, windowPos);
796 
797     // Transform these by the inverse of the view matrix.
798     const GfMatrix4d &viewInverse = ComputeViewInverse();
799     GfVec3d rayFrom = viewInverse.Transform(ray.GetStartPoint());
800     GfVec3d rayDir = viewInverse.TransformDir(ray.GetDirection());
801 
802     // Build and return the ray
803     return GfRay(rayFrom, rayDir);
804 }
805 
806 GfRay
ComputePickRay(const GfVec2d & windowPos) const807 GfFrustum::ComputePickRay(const GfVec2d &windowPos) const
808 {
809     GfRay ray = _ComputeUntransformedRay(_projectionType, _window, windowPos);
810     return _ComputePickRayOffsetToNearPlane(ray.GetStartPoint(),
811                                             ray.GetDirection());
812 }
813 
814 GfRay
ComputeRay(const GfVec3d & worldSpacePos) const815 GfFrustum::ComputeRay(const GfVec3d &worldSpacePos) const
816 {
817     GfVec3d camSpaceToPos = ComputeViewMatrix().Transform(worldSpacePos);
818 
819     // Compute the camera-space starting point (the viewpoint) and
820     // direction (toward the point camSpaceToPos).
821     GfVec3d pos;
822     GfVec3d dir;
823     if (_projectionType == Perspective) {
824         pos = GfVec3d(0);
825         dir = camSpaceToPos.GetNormalized();
826     }
827     else {
828         pos.Set(camSpaceToPos[0], camSpaceToPos[1], 0.0);
829         dir = -GfVec3d::ZAxis();
830     }
831 
832     // Transform these by the inverse of the view matrix.
833     const GfMatrix4d &viewInverse = ComputeViewInverse();
834     GfVec3d rayFrom = viewInverse.Transform(pos);
835     GfVec3d rayDir = viewInverse.TransformDir(dir);
836 
837     // Build and return the ray
838     return GfRay(rayFrom, rayDir);
839 }
840 
841 GfRay
ComputePickRay(const GfVec3d & worldSpacePos) const842 GfFrustum::ComputePickRay(const GfVec3d &worldSpacePos) const
843 {
844     GfVec3d camSpaceToPos = ComputeViewMatrix().Transform(worldSpacePos);
845 
846     // Compute the camera-space starting point (the viewpoint) and
847     // direction (toward the point camSpaceToPos).
848     GfVec3d pos;
849     GfVec3d dir;
850     if (_projectionType == Perspective) {
851         pos = GfVec3d(0);
852         dir = camSpaceToPos.GetNormalized();
853     }
854     else {
855         pos.Set(camSpaceToPos[0], camSpaceToPos[1], 0.0);
856         dir = -GfVec3d::ZAxis();
857     }
858 
859     return _ComputePickRayOffsetToNearPlane(pos, dir);
860 }
861 
862 GfRay
_ComputePickRayOffsetToNearPlane(const GfVec3d & camSpaceFrom,const GfVec3d & camSpaceDir) const863 GfFrustum::_ComputePickRayOffsetToNearPlane(const GfVec3d &camSpaceFrom,
864                           const GfVec3d &camSpaceDir) const
865 {
866     // Move the starting point to the near plane so we don't pick
867     // anything that's clipped out of view.
868     GfVec3d rayFrom = camSpaceFrom + _nearFar.GetMin() * camSpaceDir;
869 
870     // Transform these by the inverse of the view matrix.
871     const GfMatrix4d &viewInverse = ComputeViewInverse();
872     rayFrom = viewInverse.Transform(rayFrom);
873     GfVec3d rayDir = viewInverse.TransformDir(camSpaceDir);
874 
875     // Build and return the ray
876     return GfRay(rayFrom, rayDir);
877 }
878 
879 bool
Intersects(const GfBBox3d & bbox) const880 GfFrustum::Intersects(const GfBBox3d &bbox) const
881 {
882     if (bbox.GetBox().IsEmpty())
883         return false;
884 
885     // Recalculate frustum planes if necessary
886     _CalculateFrustumPlanes();
887 
888     // Get the bbox in its local space and the matrix that converts
889     // world space to that local space.
890     const GfRange3d  &localBBox    = bbox.GetRange();
891     const GfMatrix4d &worldToLocal = bbox.GetInverseMatrix();
892 
893     // Test the bbox against each of the frustum planes, transforming
894     // the plane by the inverse of the matrix to bring it into the
895     // bbox's local space.
896     for (GfPlane localPlane: *_planes) {
897         localPlane.Transform(worldToLocal);
898         if (! localPlane.IntersectsPositiveHalfSpace(localBBox)) {
899             return false;
900         }
901     }
902 
903     return true;
904 }
905 
906 bool
Intersects(const GfVec3d & point) const907 GfFrustum::Intersects(const GfVec3d &point) const
908 {
909     // Recalculate frustum planes if necessary
910     _CalculateFrustumPlanes();
911 
912     // Determine if the point is inside/intersecting the frustum. Quit early
913     // if the point is outside of any of the frustum planes.
914     for (GfPlane plane: *_planes) {
915         if (!plane.IntersectsPositiveHalfSpace(point)) {
916             return false;
917         }
918     }
919 
920     return true;
921 }
922 
923 inline static uint32_t
_CalcIntersectionBitMask(const std::array<GfPlane,6> & planes,GfVec3d const & p)924 _CalcIntersectionBitMask(const std::array<GfPlane, 6> &planes,
925                          GfVec3d const &p)
926 {
927     return
928         (planes[0].IntersectsPositiveHalfSpace(p) << 0) |
929         (planes[1].IntersectsPositiveHalfSpace(p) << 1) |
930         (planes[2].IntersectsPositiveHalfSpace(p) << 2) |
931         (planes[3].IntersectsPositiveHalfSpace(p) << 3) |
932         (planes[4].IntersectsPositiveHalfSpace(p) << 4) |
933         (planes[5].IntersectsPositiveHalfSpace(p) << 5);
934 }
935 
936 // NOTE! caller must ensure that _CalculateFrustumPlanes() has been called
937 // before calling this function.
938 bool
_SegmentIntersects(GfVec3d const & p0,uint32_t p0Mask,GfVec3d const & p1,uint32_t p1Mask) const939 GfFrustum::_SegmentIntersects(GfVec3d const &p0, uint32_t p0Mask,
940                               GfVec3d const &p1, uint32_t p1Mask) const
941 {
942     // If any of the 6 bits is 0 in both masks, then both points are
943     // on the bad side of the corresponding plane. This means that
944     // there can't be any intersection.
945     if ((p0Mask | p1Mask) != 0x3F)
946         return false;
947 
948     // If either of the masks has all 6 planes set, the point is
949     // inside the frustum, so there's an intersection.
950     if ((p0Mask == 0x3F) || (p1Mask == 0x3F))
951         return true;
952 
953     // If we get here, the 2 points of the segment are both outside
954     // the frustum, but not both on the outside of any single plane.
955 
956     // Now we can clip the segment against each plane that it
957     // straddles to see if the resulting segment has any length.
958     // Perform the clipping using parametric coordinates, where t=0
959     // represents p0 and t=1 represents p1. Use v = the vector from p0
960     // to p1.
961     double t0 = 0.0;
962     double t1 = 1.0;
963     GfVec3d v = p1 - p0;
964 
965     auto const &planes = *_planes;
966     for (size_t i=0; i < planes.size(); ++i) {
967         const GfPlane & plane = planes[i];
968         const uint32_t planeBit = 1 << i;
969 
970         uint32_t p0Bit = p0Mask & planeBit;
971         uint32_t p1Bit = p1Mask & planeBit;
972 
973         // Do this only if the points straddle the plane, meaning they
974         // have different values for the bit.
975         if (p0Bit == p1Bit)
976             continue;
977 
978         // To find the parametric distance t at the intersection of a
979         // plane and the line defined by (p0 + t * v):
980         //
981         //   Substitute the intersection point (p0 + t * v) into the
982         //   plane equation to get   n . (p0 + t * v) - d = 0
983         //
984         //   Solve for t:  t = - (n . p0 - d) / (n . v)
985         //      But (n . p0 - d) is the distance of p0 from the plane.
986         double t = -plane.GetDistance(p0) / (plane.GetNormal() * v);
987 
988         // If p0 is inside and p1 is outside, replace t1. Otherwise,
989         // replace t0.
990         if (p0Bit) {
991             if (t < t1)
992                 t1 = t;
993         } else {
994             if (t > t0)
995                 t0 = t;
996         }
997 
998         // If there is no line segment left, there's no intersection.
999         if (t0 > t1)
1000             return false;
1001     }
1002 
1003     // If we get here, there's an intersection
1004     return true;
1005 }
1006 
1007 bool
Intersects(const GfVec3d & p0,const GfVec3d & p1) const1008 GfFrustum::Intersects(const GfVec3d &p0, const GfVec3d &p1) const
1009 {
1010     // Recalculate frustum planes if necessary
1011     _CalculateFrustumPlanes();
1012 
1013     // Compute the intersection masks for each point. There is one bit
1014     // in each mask for each of the 6 planes.
1015     auto const &planes = *_planes;
1016     return _SegmentIntersects(p0, _CalcIntersectionBitMask(planes, p0),
1017                               p1, _CalcIntersectionBitMask(planes, p1));
1018 
1019 }
1020 
1021 bool
Intersects(const GfVec3d & p0,const GfVec3d & p1,const GfVec3d & p2) const1022 GfFrustum::Intersects(const GfVec3d &p0,
1023                       const GfVec3d &p1,
1024                       const GfVec3d &p2) const
1025 {
1026     // Recalculate frustum planes if necessary
1027     _CalculateFrustumPlanes();
1028 
1029     // Compute the intersection masks for each point. There is one bit
1030     // in each mask for each of the 6 planes.
1031     auto const &planes = *_planes;
1032     uint32_t p0Mask = _CalcIntersectionBitMask(planes, p0);
1033     uint32_t p1Mask = _CalcIntersectionBitMask(planes, p1);
1034     uint32_t p2Mask = _CalcIntersectionBitMask(planes, p2);
1035 
1036     // If any of the 6 bits is 0 in all masks, then all 3 points are
1037     // on the bad side of the corresponding plane. This means that
1038     // there can't be any intersection.
1039     if ((p0Mask | p1Mask | p2Mask) != 0x3F)
1040         return false;
1041 
1042     // If any of the masks has all 6 planes set, the point is inside
1043     // the frustum, so there's an intersection.
1044     if ((p0Mask == 0x3F) || (p1Mask == 0x3F) || (p2Mask == 0x3F))
1045         return true;
1046 
1047     // If we get here, the 3 points of the triangle are all outside
1048     // the frustum, but not all on the outside of any single plane.
1049     // There are now 3 remaining possibilities:
1050     //
1051     //  (1) At least one edge of the triangle intersects the frustum.
1052     //  (2) The triangle completely encloses the frustum.
1053     //  (3) Neither of the above is true, so there is no intersection.
1054 
1055     // Test case (1) by intersecting all three edges with the
1056     // frustum.
1057     if (_SegmentIntersects(p0, p0Mask, p1, p1Mask) ||
1058         _SegmentIntersects(p1, p1Mask, p2, p2Mask) ||
1059         _SegmentIntersects(p2, p2Mask, p0, p0Mask))
1060         return true;
1061 
1062 
1063     // That leaves cases (2) and (3).
1064 
1065     // Test for case 2 by computing rays from the viewpoint
1066     // to the far corners, and doing a ray-triangle
1067     // intersection test.
1068     // If all 3 points of the triangle lie between the near/far planes,
1069     // then we only need to test intersection of 1 corner's ray.
1070     // Otherwise, we test all 4 corners and if any hit, the frustum is inside
1071     // the triangle.  If all miss, then the frustum is outside.
1072     // If the points don't lie between near/far, then  we have to test all
1073     // 4 corners to catch the case when the triangle is being partially
1074     // clipped by the near/far plane.
1075     size_t numCornersToCheck = 4;
1076     // XXX Note: 4 & 5 below are highly dependent on
1077     // _CalculateFrustumPlanes implementation
1078     uint32_t nearBit = (1 << 4);
1079     uint32_t farBit  = (1 << 5);
1080     if ( (p0Mask & nearBit) && (p1Mask & nearBit) && (p2Mask & nearBit) &&
1081          (p0Mask & farBit)  && (p1Mask & farBit)  && (p2Mask & farBit) ) {
1082         numCornersToCheck = 1;
1083     }
1084 
1085     for (size_t i=0; i<numCornersToCheck; ++i) {
1086         GfVec2d pickPoint = (i==0) ? GfVec2d(-1.0, -1.0) :
1087                             (i==1) ? GfVec2d(-1.0,  1.0) :
1088                             (i==2) ? GfVec2d( 1.0,  1.0) :
1089                                      GfVec2d( 1.0, -1.0);
1090         GfRay pickRay = ComputePickRay(pickPoint);
1091         double distance;
1092         if ( pickRay.Intersect(p0, p1, p2, &distance, NULL, NULL) ) {
1093             return true;
1094         }
1095     }
1096 
1097 
1098     // Must be case 3.
1099     return false;
1100 }
1101 
1102 void
_DirtyFrustumPlanes()1103 GfFrustum::_DirtyFrustumPlanes()
1104 {
1105     delete _planes.exchange(nullptr, std::memory_order_relaxed);
1106 }
1107 
1108 void
_CalculateFrustumPlanes() const1109 GfFrustum::_CalculateFrustumPlanes() const
1110 {
1111     auto *planes = _planes.load();
1112     if (planes) {
1113         return;
1114     }
1115 
1116     std::unique_ptr<std::array<GfPlane, 6>>
1117         newPlanesOwner(new std::array<GfPlane, 6>);
1118 
1119     auto &newPlanes = *newPlanesOwner;
1120 
1121     // These are values we need to construct the planes.
1122     const GfVec2d &winMin = _window.GetMin();
1123     const GfVec2d &winMax = _window.GetMax();
1124     double near           = _nearFar.GetMin();
1125     double far            = _nearFar.GetMax();
1126     GfMatrix4d m          = ComputeViewInverse();
1127 
1128     // For a perspective frustum, we use the viewpoint and four
1129     // corners of the near-plane frustum rectangle to define the 4
1130     // planes forming the left, right, top, and bottom sides of the
1131     // frustum.
1132     if (_projectionType == GfFrustum::Perspective) {
1133 
1134         //
1135         // Get the eye-space viewpoint (the origin) and the four corners
1136         // of the near-plane frustum rectangle using similar triangles.
1137         //
1138         // This picture may help:
1139         //
1140         //                  top of near plane
1141         //                  frustum rectangle
1142         //
1143         //                  + --
1144         //                / |  |
1145         //              /   |  |
1146         //            /     |  | h
1147         //          /       |  |
1148         //        /         |  |
1149         //   vp +-----------+ --
1150         //                    center of near plane frustum rectangle
1151         //      |___________|
1152         //           near
1153         //
1154         // The height (h) of this triangle is found by the following
1155         // equation, based on the definition of the _window member
1156         // variable, which is the size of the image rectangle in the
1157         // reference plane (a distance of 1 from the viewpoint):
1158         //
1159         //      h       _window.GetMax()[1]
1160         //    ------ = --------------------
1161         //     near             1
1162         //
1163         // Solving for h gets the height of the triangle. Doing the
1164         // similar math for the other 3 sizes of the near-plane
1165         // rectangle is left as an exercise for the reader.
1166         //
1167         // XXX Note: If we ever allow reference plane depth to be other
1168         // than 1.0, we'll need to revisit this.
1169 
1170         GfVec3d vp(0.0, 0.0, 0.0);
1171         GfVec3d lb(near * winMin[0], near * winMin[1], -near);
1172         GfVec3d rb(near * winMax[0], near * winMin[1], -near);
1173         GfVec3d lt(near * winMin[0], near * winMax[1], -near);
1174         GfVec3d rt(near * winMax[0], near * winMax[1], -near);
1175 
1176         // Transform all 5 points into world space by the inverse of the
1177         // view matrix (which converts from world space to eye space).
1178         vp = m.Transform(vp);
1179         lb = m.Transform(lb);
1180         rb = m.Transform(rb);
1181         lt = m.Transform(lt);
1182         rt = m.Transform(rt);
1183 
1184         // Construct the 6 planes. The three points defining each plane
1185         // should obey the right-hand-rule; they should be in counter-clockwise
1186         // order on the inside of the frustum. This makes the intersection of
1187         // the half-spaces defined by the planes the contents of the frustum.
1188         newPlanes[0] = GfPlane(vp, lb, lt);     // Left
1189         newPlanes[1] = GfPlane(vp, rt, rb);     // Right
1190         newPlanes[2] = GfPlane(vp, rb, lb);     // Bottom
1191         newPlanes[3] = GfPlane(vp, lt, rt);     // Top
1192         newPlanes[4] = GfPlane(rb, lb, lt);     // Near
1193                                                 // Far computed below
1194     }
1195 
1196     // For an orthographic projection, we need only the four corners
1197     // of the near-plane frustum rectangle and the view direction to
1198     // define the 4 planes forming the left, right, top, and bottom
1199     // sides of the frustum.
1200     else {
1201 
1202         //
1203         // The math here is much easier than in the perspective case,
1204         // because we have parallel lines instead of triangles. Just
1205         // use the size of the image rectangle in the reference plane,
1206         // which is the same in the near plane.
1207         //
1208         GfVec3d lb(winMin[0], winMin[1], -near);
1209         GfVec3d rb(winMax[0], winMin[1], -near);
1210         GfVec3d lt(winMin[0], winMax[1], -near);
1211         GfVec3d rt(winMax[0], winMax[1], -near);
1212 
1213         // Transform the 4 points into world space by the inverse of
1214         // the view matrix (which converts from world space to eye
1215         // space).
1216         lb = m.Transform(lb);
1217         rb = m.Transform(rb);
1218         lt = m.Transform(lt);
1219         rt = m.Transform(rt);
1220 
1221         // Transform the canonical view direction (-z axis) into world
1222         // space.
1223         GfVec3d dir = m.TransformDir(-GfVec3d::ZAxis());
1224 
1225         // Construct the 5 planes from these 4 points and the
1226         // eye-space view direction.
1227         newPlanes[0] = GfPlane(lt + dir, lt, lb);       // Left
1228         newPlanes[1] = GfPlane(rb + dir, rb, rt);       // Right
1229         newPlanes[2] = GfPlane(lb + dir, lb, rb);       // Bottom
1230         newPlanes[3] = GfPlane(rt + dir, rt, lt);       // Top
1231         newPlanes[4] = GfPlane(rb, lb, lt);             // Near
1232                                                         // Far computed below
1233     }
1234 
1235     // The far plane is the opposite to the near plane. To compute the
1236     // distance from the origin for the far plane, we take the distance
1237     // for the near plane, add the difference between the far and the near
1238     // and then negate that. We do the negation since the far plane
1239     // faces the opposite direction. A small drawing would help:
1240     //
1241     //                               far - near
1242     //                     /---------------------------\ *
1243     //
1244     //        |           |                             |
1245     //        |           |                             |
1246     //        |           |                             |
1247     //   <----|---->      |                             |
1248     // fnormal|nnormal    |                             |
1249     //        |           |                             |
1250     //                near plane                     far plane
1251     //
1252     //         \---------/ *
1253     //          ndistance
1254     //
1255     //         \---------------------------------------/ *
1256     //                         fdistance
1257     //
1258     // So, fdistance = - (ndistance + (far - near))
1259     newPlanes[5] = GfPlane(
1260         -newPlanes[4].GetNormal(),
1261         -(newPlanes[4].GetDistanceFromOrigin() + (far - near)));
1262 
1263     // Now attempt to set the planes.
1264     if (_planes.compare_exchange_strong(planes, &newPlanes)) {
1265         // We set the _planes, so don't delete them.
1266         newPlanesOwner.release();
1267     }
1268 }
1269 
1270 bool
IntersectsViewVolume(const GfBBox3d & bbox,const GfMatrix4d & viewProjMat)1271 GfFrustum::IntersectsViewVolume(const GfBBox3d &bbox,
1272                                 const GfMatrix4d &viewProjMat)
1273 {
1274     // This implementation is a standard technique employed in frustum
1275     // culling during rendering.  It correctly culls the box even from
1276     // view volumes that are not representable by a GfFrustum because of
1277     // skewed near/far planes, such as the ones produced by
1278     // presto shadowmap cameras.
1279     //
1280     // Its principle of operation:  If all 8 points of
1281     // the box, when transformed into clip coordinates,
1282     // are on one side or the other of each dimension's
1283     // clipping interval, then the entire
1284     // box volume must lie outside the view volume.
1285 
1286     // Compute the 8 points of the bbox in
1287     // bbox local space.
1288     GfVec4d points[8];
1289     const GfVec3d &localMin = bbox.GetRange().GetMin();
1290     const GfVec3d &localMax = bbox.GetRange().GetMax();
1291     points[0] = GfVec4d(localMin[0], localMin[1], localMin[2], 1);
1292     points[1] = GfVec4d(localMin[0], localMin[1], localMax[2], 1);
1293     points[2] = GfVec4d(localMin[0], localMax[1], localMin[2], 1);
1294     points[3] = GfVec4d(localMin[0], localMax[1], localMax[2], 1);
1295     points[4] = GfVec4d(localMax[0], localMin[1], localMin[2], 1);
1296     points[5] = GfVec4d(localMax[0], localMin[1], localMax[2], 1);
1297     points[6] = GfVec4d(localMax[0], localMax[1], localMin[2], 1);
1298     points[7] = GfVec4d(localMax[0], localMax[1], localMax[2], 1);
1299 
1300     // Transform bbox local space points into clip space
1301     for (int i = 0; i < 8; ++i) {
1302         points[i] = points[i] * bbox.GetMatrix() * viewProjMat;
1303     }
1304 
1305     // clipFlags is a 6-bit field with one bit per +/- per x,y,z,
1306     // or one per frustum plane.  If the points overlap the
1307     // clip volume in any axis, then clipFlags will be 0x3f (0b111111).
1308     int clipFlags = 0;
1309     for (int i = 0; i < 8; ++i) {
1310         GfVec4d clipPos = points[i];
1311 
1312         // flag is used as a 6-bit shift register, as we append
1313         // results of plane-side testing.  OR-ing all the flags
1314         // combines all the records of what plane-side the points
1315         // have been on.
1316         int flag = 0;
1317         for (int j = 0; j < 3; ++j) {
1318             // We use +/-clipPos[3] as the interval bound instead of
1319             // 1,-1 because these coordinates are not normalized.
1320             flag = (flag << 1) | (clipPos[j] <  clipPos[3]);
1321             flag = (flag << 1) | (clipPos[j] > -clipPos[3]);
1322         }
1323         clipFlags |= flag;
1324     }
1325 
1326     return clipFlags == 0x3f;
1327 }
1328 
1329 void
SetPositionAndRotationFromMatrix(const GfMatrix4d & camToWorldXf)1330 GfFrustum::SetPositionAndRotationFromMatrix(
1331     const GfMatrix4d &camToWorldXf)
1332 {
1333     // First conform matrix to be...
1334     GfMatrix4d conformedXf = camToWorldXf;
1335     // ... right handed
1336     if (!conformedXf.IsRightHanded()) {
1337         static GfMatrix4d flip(GfVec4d(-1.0, 1.0, 1.0, 1.0));
1338         conformedXf = flip * conformedXf;
1339     }
1340 
1341     // ... and orthonormal
1342     conformedXf.Orthonormalize();
1343 
1344     SetRotation(conformedXf.ExtractRotation());
1345     SetPosition(conformedXf.ExtractTranslation());
1346 }
1347 
1348 std::ostream &
operator <<(std::ostream & out,const GfFrustum & f)1349 operator<<(std::ostream& out, const GfFrustum& f)
1350 {
1351     out << '['
1352             << Gf_OstreamHelperP(f.GetPosition())     << " "
1353             << Gf_OstreamHelperP(f.GetRotation())     << " "
1354             << Gf_OstreamHelperP(f.GetWindow())       << " "
1355             << Gf_OstreamHelperP(f.GetNearFar())      << " "
1356             << Gf_OstreamHelperP(f.GetViewDistance()) << " "
1357             << TfEnum::GetName(TfEnum(f.GetProjectionType()))
1358         << ']';
1359 
1360     return out;
1361 }
1362 
1363 PXR_NAMESPACE_CLOSE_SCOPE
1364