1 ///////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2011-2012, Industrial Light & Magic, a division of Lucas
4 // Digital Ltd. LLC
5 //
6 // All rights reserved.
7 //
8 // Redistribution and use in source and binary forms, with or without
9 // modification, are permitted provided that the following conditions are
10 // met:
11 // *       Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 // *       Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 // *       Neither the name of Industrial Light & Magic nor the names of
18 // its contributors may be used to endorse or promote products derived
19 // from this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 ///////////////////////////////////////////////////////////////////////////
34 
35 
36 #ifndef INCLUDED_IMATHFRUSTUMTEST_H
37 #define INCLUDED_IMATHFRUSTUMTEST_H
38 
39 //-------------------------------------------------------------------------
40 //
41 //  This file contains algorithms applied to or in conjunction with
42 //  Frustum visibility testing (Imath::Frustum).
43 //
44 //  Methods for frustum-based rejection of primitives are contained here.
45 //
46 //-------------------------------------------------------------------------
47 
48 #include "ImathFrustum.h"
49 #include "ImathBox.h"
50 #include "ImathSphere.h"
51 #include "ImathMatrix.h"
52 #include "ImathVec.h"
53 #include "ImathNamespace.h"
54 
55 IMATH_INTERNAL_NAMESPACE_HEADER_ENTER
56 
57 /////////////////////////////////////////////////////////////////
58 // FrustumTest
59 //
60 //	template class FrustumTest<T>
61 //
62 // This is a helper class, designed to accelerate the case
63 // where many tests are made against the same frustum.
64 // That's a really common case.
65 //
66 // The acceleration is achieved by pre-computing the planes of
67 // the frustum, along with the ablsolute values of the plane normals.
68 //
69 
70 
71 
72 //////////////////////////////////////////////////////////////////
73 // How to use this
74 //
75 // Given that you already have:
76 //    Imath::Frustum   myFrustum
77 //    Imath::Matrix44  myCameraWorldMatrix
78 //
79 // First, make a frustum test object:
80 //    FrustumTest myFrustumTest(myFrustum, myCameraWorldMatrix)
81 //
82 // Whenever the camera or frustum changes, call:
83 //    myFrustumTest.setFrustum(myFrustum, myCameraWorldMatrix)
84 //
85 // For each object you want to test for visibility, call:
86 //    myFrustumTest.isVisible(myBox)
87 //    myFrustumTest.isVisible(mySphere)
88 //    myFrustumTest.isVisible(myVec3)
89 //    myFrustumTest.completelyContains(myBox)
90 //    myFrustumTest.completelyContains(mySphere)
91 //
92 
93 
94 
95 
96 //////////////////////////////////////////////////////////////////
97 // Explanation of how it works
98 //
99 //
100 // We store six world-space Frustum planes (nx, ny, nz, offset)
101 //
102 // Points: To test a Vec3 for visibility, test it against each plane
103 //         using the normal (v dot n - offset) method. (the result is exact)
104 //
105 // BBoxes: To test an axis-aligned bbox, test the center against each plane
106 //         using the normal (v dot n - offset) method, but offset by the
107 //         box extents dot the abs of the plane normal. (the result is NOT
108 //         exact, but will not return false-negatives.)
109 //
110 // Spheres: To test a sphere, test the center against each plane
111 //         using the normal (v dot n - offset) method, but offset by the
112 //         sphere's radius. (the result is NOT exact, but will not return
113 //         false-negatives.)
114 //
115 //
116 // SPECIAL NOTE: "Where are the dot products?"
117 //     Actual dot products are currently slow for most SIMD architectures.
118 //     In order to keep this code optimization-ready, the dot products
119 //     are all performed using vector adds and multipies.
120 //
121 //     In order to do this, the plane equations are stored in "transpose"
122 //     form, with the X components grouped into an X vector, etc.
123 //
124 
125 
126 template <class T>
127 class FrustumTest
128 {
129 public:
FrustumTest()130     FrustumTest()
131     {
132         Frustum<T>  frust;
133         Matrix44<T> cameraMat;
134         cameraMat.makeIdentity();
135         setFrustum(frust, cameraMat);
136     }
FrustumTest(const Frustum<T> & frustum,const Matrix44<T> & cameraMat)137     FrustumTest(const Frustum<T> &frustum, const Matrix44<T> &cameraMat)
138     {
139         setFrustum(frustum, cameraMat);
140     }
141 
142     ////////////////////////////////////////////////////////////////////
143     // setFrustum()
144     // This updates the frustum test with a new frustum and matrix.
145     // This should usually be called just once per frame.
146     void setFrustum(const Frustum<T> &frustum, const Matrix44<T> &cameraMat);
147 
148     ////////////////////////////////////////////////////////////////////
149     // isVisible()
150     // Check to see if shapes are visible.
151     bool isVisible(const Sphere3<T> &sphere) const;
152     bool isVisible(const Box<Vec3<T> > &box) const;
153     bool isVisible(const Vec3<T> &vec) const;
154 
155     ////////////////////////////////////////////////////////////////////
156     // completelyContains()
157     // Check to see if shapes are entirely contained.
158     bool completelyContains(const Sphere3<T> &sphere) const;
159     bool completelyContains(const Box<Vec3<T> > &box) const;
160 
161     // These next items are kept primarily for debugging tools.
162     // It's useful for drawing the culling environment, and also
163     // for getting an "outside view" of the culling frustum.
cameraMat()164     IMATH_INTERNAL_NAMESPACE::Matrix44<T> cameraMat() const {return cameraMatrix;}
currentFrustum()165     IMATH_INTERNAL_NAMESPACE::Frustum<T> currentFrustum() const {return currFrustum;}
166 
167 protected:
168     // To understand why the planes are stored this way, see
169     // the SPECIAL NOTE above.
170     Vec3<T> planeNormX[2];  // The X compunents from 6 plane equations
171     Vec3<T> planeNormY[2];  // The Y compunents from 6 plane equations
172     Vec3<T> planeNormZ[2];  // The Z compunents from 6 plane equations
173 
174     Vec3<T> planeOffsetVec[2]; // The distance offsets from 6 plane equations
175 
176     // The absolute values are stored to assist with bounding box tests.
177     Vec3<T> planeNormAbsX[2];  // The abs(X) compunents from 6 plane equations
178     Vec3<T> planeNormAbsY[2];  // The abs(X) compunents from 6 plane equations
179     Vec3<T> planeNormAbsZ[2];  // The abs(X) compunents from 6 plane equations
180 
181     // These are kept primarily for debugging tools.
182     Frustum<T> currFrustum;
183     Matrix44<T> cameraMatrix;
184 };
185 
186 
187 ////////////////////////////////////////////////////////////////////
188 // setFrustum()
189 // This should usually only be called once per frame, or however
190 // often the camera moves.
191 template<class T>
setFrustum(const Frustum<T> & frustum,const Matrix44<T> & cameraMat)192 void FrustumTest<T>::setFrustum(const Frustum<T> &frustum,
193                                 const Matrix44<T> &cameraMat)
194 {
195     Plane3<T> frustumPlanes[6];
196     frustum.planes(frustumPlanes, cameraMat);
197 
198     // Here's where we effectively transpose the plane equations.
199     // We stuff all six X's into the two planeNormX vectors, etc.
200     for (int i = 0; i < 2; ++i)
201     {
202         int index = i * 3;
203 
204         planeNormX[i]     = Vec3<T>(frustumPlanes[index + 0].normal.x,
205                                     frustumPlanes[index + 1].normal.x,
206                                     frustumPlanes[index + 2].normal.x);
207         planeNormY[i]     = Vec3<T>(frustumPlanes[index + 0].normal.y,
208                                     frustumPlanes[index + 1].normal.y,
209                                     frustumPlanes[index + 2].normal.y);
210         planeNormZ[i]     = Vec3<T>(frustumPlanes[index + 0].normal.z,
211                                     frustumPlanes[index + 1].normal.z,
212                                     frustumPlanes[index + 2].normal.z);
213 
214         planeNormAbsX[i]  = Vec3<T>(IMATH_INTERNAL_NAMESPACE::abs(planeNormX[i].x),
215                                     IMATH_INTERNAL_NAMESPACE::abs(planeNormX[i].y),
216                                     IMATH_INTERNAL_NAMESPACE::abs(planeNormX[i].z));
217         planeNormAbsY[i]  = Vec3<T>(IMATH_INTERNAL_NAMESPACE::abs(planeNormY[i].x),
218                                     IMATH_INTERNAL_NAMESPACE::abs(planeNormY[i].y),
219                                     IMATH_INTERNAL_NAMESPACE::abs(planeNormY[i].z));
220         planeNormAbsZ[i]  = Vec3<T>(IMATH_INTERNAL_NAMESPACE::abs(planeNormZ[i].x),
221                                     IMATH_INTERNAL_NAMESPACE::abs(planeNormZ[i].y),
222                                     IMATH_INTERNAL_NAMESPACE::abs(planeNormZ[i].z));
223 
224         planeOffsetVec[i] = Vec3<T>(frustumPlanes[index + 0].distance,
225                                     frustumPlanes[index + 1].distance,
226                                     frustumPlanes[index + 2].distance);
227     }
228     currFrustum = frustum;
229     cameraMatrix = cameraMat;
230 }
231 
232 
233 ////////////////////////////////////////////////////////////////////
234 // isVisible(Sphere)
235 // Returns true if any part of the sphere is inside
236 // the frustum.
237 // The result MAY return close false-positives, but not false-negatives.
238 //
239 template<typename T>
isVisible(const Sphere3<T> & sphere)240 bool FrustumTest<T>::isVisible(const Sphere3<T> &sphere) const
241 {
242     Vec3<T> center = sphere.center;
243     Vec3<T> radiusVec = Vec3<T>(sphere.radius, sphere.radius, sphere.radius);
244 
245     // This is a vertical dot-product on three vectors at once.
246     Vec3<T> d0  = planeNormX[0] * center.x
247                 + planeNormY[0] * center.y
248                 + planeNormZ[0] * center.z
249                 - radiusVec
250                 - planeOffsetVec[0];
251 
252     if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
253         return false;
254 
255     Vec3<T> d1  = planeNormX[1] * center.x
256                 + planeNormY[1] * center.y
257                 + planeNormZ[1] * center.z
258                 - radiusVec
259                 - planeOffsetVec[1];
260 
261     if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
262         return false;
263 
264     return true;
265 }
266 
267 ////////////////////////////////////////////////////////////////////
268 // completelyContains(Sphere)
269 // Returns true if every part of the sphere is inside
270 // the frustum.
271 // The result MAY return close false-negatives, but not false-positives.
272 //
273 template<typename T>
completelyContains(const Sphere3<T> & sphere)274 bool FrustumTest<T>::completelyContains(const Sphere3<T> &sphere) const
275 {
276     Vec3<T> center = sphere.center;
277     Vec3<T> radiusVec = Vec3<T>(sphere.radius, sphere.radius, sphere.radius);
278 
279     // This is a vertical dot-product on three vectors at once.
280     Vec3<T> d0  = planeNormX[0] * center.x
281                 + planeNormY[0] * center.y
282                 + planeNormZ[0] * center.z
283                 + radiusVec
284                 - planeOffsetVec[0];
285 
286     if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
287         return false;
288 
289     Vec3<T> d1  = planeNormX[1] * center.x
290                 + planeNormY[1] * center.y
291                 + planeNormZ[1] * center.z
292                 + radiusVec
293                 - planeOffsetVec[1];
294 
295     if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
296         return false;
297 
298     return true;
299 }
300 
301 ////////////////////////////////////////////////////////////////////
302 // isVisible(Box)
303 // Returns true if any part of the axis-aligned box
304 // is inside the frustum.
305 // The result MAY return close false-positives, but not false-negatives.
306 //
307 template<typename T>
isVisible(const Box<Vec3<T>> & box)308 bool FrustumTest<T>::isVisible(const Box<Vec3<T> > &box) const
309 {
310     if (box.isEmpty())
311         return false;
312 
313     Vec3<T> center = (box.min + box.max) / 2;
314     Vec3<T> extent = (box.max - center);
315 
316     // This is a vertical dot-product on three vectors at once.
317     Vec3<T> d0  = planeNormX[0] * center.x
318                 + planeNormY[0] * center.y
319                 + planeNormZ[0] * center.z
320                 - planeNormAbsX[0] * extent.x
321                 - planeNormAbsY[0] * extent.y
322                 - planeNormAbsZ[0] * extent.z
323                 - planeOffsetVec[0];
324 
325     if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
326         return false;
327 
328     Vec3<T> d1  = planeNormX[1] * center.x
329                 + planeNormY[1] * center.y
330                 + planeNormZ[1] * center.z
331                 - planeNormAbsX[1] * extent.x
332                 - planeNormAbsY[1] * extent.y
333                 - planeNormAbsZ[1] * extent.z
334                 - planeOffsetVec[1];
335 
336     if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
337         return false;
338 
339     return true;
340 }
341 
342 ////////////////////////////////////////////////////////////////////
343 // completelyContains(Box)
344 // Returns true if every part of the axis-aligned box
345 // is inside the frustum.
346 // The result MAY return close false-negatives, but not false-positives.
347 //
348 template<typename T>
completelyContains(const Box<Vec3<T>> & box)349 bool FrustumTest<T>::completelyContains(const Box<Vec3<T> > &box) const
350 {
351     if (box.isEmpty())
352         return false;
353 
354     Vec3<T> center = (box.min + box.max) / 2;
355     Vec3<T> extent = (box.max - center);
356 
357     // This is a vertical dot-product on three vectors at once.
358     Vec3<T> d0  = planeNormX[0] * center.x
359                 + planeNormY[0] * center.y
360                 + planeNormZ[0] * center.z
361                 + planeNormAbsX[0] * extent.x
362                 + planeNormAbsY[0] * extent.y
363                 + planeNormAbsZ[0] * extent.z
364                 - planeOffsetVec[0];
365 
366     if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
367         return false;
368 
369     Vec3<T> d1  = planeNormX[1] * center.x
370                 + planeNormY[1] * center.y
371                 + planeNormZ[1] * center.z
372                 + planeNormAbsX[1] * extent.x
373                 + planeNormAbsY[1] * extent.y
374                 + planeNormAbsZ[1] * extent.z
375                 - planeOffsetVec[1];
376 
377     if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
378         return false;
379 
380     return true;
381 }
382 
383 
384 ////////////////////////////////////////////////////////////////////
385 // isVisible(Vec3)
386 // Returns true if the point is inside the frustum.
387 //
388 template<typename T>
isVisible(const Vec3<T> & vec)389 bool FrustumTest<T>::isVisible(const Vec3<T> &vec) const
390 {
391     // This is a vertical dot-product on three vectors at once.
392     Vec3<T> d0  = (planeNormX[0] * vec.x)
393                 + (planeNormY[0] * vec.y)
394                 + (planeNormZ[0] * vec.z)
395                 - planeOffsetVec[0];
396 
397     if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
398         return false;
399 
400     Vec3<T> d1  = (planeNormX[1] * vec.x)
401                 + (planeNormY[1] * vec.y)
402                 + (planeNormZ[1] * vec.z)
403                 - planeOffsetVec[1];
404 
405     if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
406         return false;
407 
408     return true;
409 }
410 
411 
412 typedef FrustumTest<float>	FrustumTestf;
413 typedef FrustumTest<double> FrustumTestd;
414 
415 IMATH_INTERNAL_NAMESPACE_HEADER_EXIT
416 
417 #endif // INCLUDED_IMATHFRUSTUMTEST_H
418