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 ¢er, 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