ImathFrustumTest.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. //
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Copyright Contributors to the OpenEXR Project.
  4. //
  5. //
  6. // A viewing frustum class
  7. //
  8. // This file contains algorithms applied to or in conjunction with
  9. // Frustum visibility testing (Imath::Frustum).
  10. //
  11. // Methods for frustum-based rejection of primitives are contained here.
  12. //
  13. #ifndef INCLUDED_IMATHFRUSTUMTEST_H
  14. #define INCLUDED_IMATHFRUSTUMTEST_H
  15. #include "ImathExport.h"
  16. #include "ImathNamespace.h"
  17. #include "ImathBox.h"
  18. #include "ImathFrustum.h"
  19. #include "ImathMatrix.h"
  20. #include "ImathSphere.h"
  21. #include "ImathVec.h"
  22. IMATH_INTERNAL_NAMESPACE_HEADER_ENTER
  23. ///
  24. /// template class FrustumTest<T>
  25. ///
  26. /// This is a helper class, designed to accelerate the case
  27. /// where many tests are made against the same frustum.
  28. /// That's a really common case.
  29. ///
  30. /// The acceleration is achieved by pre-computing the planes of
  31. /// the frustum, along with the ablsolute values of the plane normals.
  32. ///
  33. /// How to use this
  34. ///
  35. /// Given that you already have:
  36. /// Imath::Frustum myFrustum
  37. /// Imath::Matrix44 myCameraWorldMatrix
  38. ///
  39. /// First, make a frustum test object:
  40. /// FrustumTest myFrustumTest(myFrustum, myCameraWorldMatrix)
  41. ///
  42. /// Whenever the camera or frustum changes, call:
  43. /// myFrustumTest.setFrustum(myFrustum, myCameraWorldMatrix)
  44. ///
  45. /// For each object you want to test for visibility, call:
  46. /// myFrustumTest.isVisible(myBox)
  47. /// myFrustumTest.isVisible(mySphere)
  48. /// myFrustumTest.isVisible(myVec3)
  49. /// myFrustumTest.completelyContains(myBox)
  50. /// myFrustumTest.completelyContains(mySphere)
  51. ///
  52. /// Explanation of how it works
  53. ///
  54. /// We store six world-space Frustum planes (nx, ny, nz, offset)
  55. ///
  56. /// Points: To test a Vec3 for visibility, test it against each plane
  57. /// using the normal (v dot n - offset) method. (the result is exact)
  58. ///
  59. /// BBoxes: To test an axis-aligned bbox, test the center against each plane
  60. /// using the normal (v dot n - offset) method, but offset by the
  61. /// box extents dot the abs of the plane normal. (the result is NOT
  62. /// exact, but will not return false-negatives.)
  63. ///
  64. /// Spheres: To test a sphere, test the center against each plane
  65. /// using the normal (v dot n - offset) method, but offset by the
  66. /// sphere's radius. (the result is NOT exact, but will not return
  67. /// false-negatives.)
  68. ///
  69. ///
  70. /// SPECIAL NOTE: "Where are the dot products?"
  71. /// Actual dot products are currently slow for most SIMD architectures.
  72. /// In order to keep this code optimization-ready, the dot products
  73. /// are all performed using vector adds and multipies.
  74. ///
  75. /// In order to do this, the plane equations are stored in "transpose"
  76. /// form, with the X components grouped into an X vector, etc.
  77. ///
  78. template <class T> class IMATH_EXPORT_TEMPLATE_TYPE FrustumTest
  79. {
  80. public:
  81. /// @{
  82. /// @name Constructors
  83. /// Initialize camera matrix to identity
  84. FrustumTest() IMATH_NOEXCEPT
  85. {
  86. Frustum<T> frust;
  87. Matrix44<T> cameraMat;
  88. cameraMat.makeIdentity();
  89. setFrustum (frust, cameraMat);
  90. }
  91. /// Initialize to a given frustum and camera matrix.
  92. FrustumTest (const Frustum<T>& frustum, const Matrix44<T>& cameraMat) IMATH_NOEXCEPT
  93. {
  94. setFrustum (frustum, cameraMat);
  95. }
  96. /// @}
  97. /// @{
  98. /// @name Set Value
  99. /// Update the frustum test with a new frustum and matrix.
  100. /// This should usually be called just once per frame, or however
  101. /// often the camera moves.
  102. void setFrustum (const Frustum<T>& frustum, const Matrix44<T>& cameraMat) IMATH_NOEXCEPT;
  103. /// @}
  104. /// @{
  105. /// @name Query
  106. /// Return true if any part of the sphere is inside the frustum.
  107. /// The result MAY return close false-positives, but not false-negatives.
  108. bool isVisible (const Sphere3<T>& sphere) const IMATH_NOEXCEPT;
  109. /// Return true if any part of the box is inside the frustum.
  110. /// The result MAY return close false-positives, but not false-negatives.
  111. bool isVisible (const Box<Vec3<T>>& box) const IMATH_NOEXCEPT;
  112. /// Return true if the point is inside the frustum.
  113. bool isVisible (const Vec3<T>& vec) const IMATH_NOEXCEPT;
  114. /// Return true if every part of the sphere is inside the frustum.
  115. /// The result MAY return close false-negatives, but not false-positives.
  116. bool completelyContains (const Sphere3<T>& sphere) const IMATH_NOEXCEPT;
  117. /// Return true if every part of the box is inside the frustum.
  118. /// The result MAY return close false-negatives, but not false-positives.
  119. bool completelyContains (const Box<Vec3<T>>& box) const IMATH_NOEXCEPT;
  120. /// Return the camera matrix (primarily for debugging)
  121. IMATH_INTERNAL_NAMESPACE::Matrix44<T> cameraMat() const IMATH_NOEXCEPT { return cameraMatrix; }
  122. /// Return the viewing frustum (primarily for debugging)
  123. IMATH_INTERNAL_NAMESPACE::Frustum<T> currentFrustum() const IMATH_NOEXCEPT { return currFrustum; }
  124. /// @}
  125. protected:
  126. // To understand why the planes are stored this way, see
  127. // the SPECIAL NOTE above.
  128. /// @cond Doxygen_Suppress
  129. Vec3<T> planeNormX[2]; // The X components from 6 plane equations
  130. Vec3<T> planeNormY[2]; // The Y components from 6 plane equations
  131. Vec3<T> planeNormZ[2]; // The Z components from 6 plane equations
  132. Vec3<T> planeOffsetVec[2]; // The distance offsets from 6 plane equations
  133. // The absolute values are stored to assist with bounding box tests.
  134. Vec3<T> planeNormAbsX[2]; // The abs(X) components from 6 plane equations
  135. Vec3<T> planeNormAbsY[2]; // The abs(X) components from 6 plane equations
  136. Vec3<T> planeNormAbsZ[2]; // The abs(X) components from 6 plane equations
  137. // These are kept primarily for debugging tools.
  138. Frustum<T> currFrustum;
  139. Matrix44<T> cameraMatrix;
  140. /// @endcond
  141. };
  142. template <class T>
  143. void
  144. FrustumTest<T>::setFrustum (const Frustum<T>& frustum, const Matrix44<T>& cameraMat) IMATH_NOEXCEPT
  145. {
  146. Plane3<T> frustumPlanes[6];
  147. frustum.planes (frustumPlanes, cameraMat);
  148. // Here's where we effectively transpose the plane equations.
  149. // We stuff all six X's into the two planeNormX vectors, etc.
  150. for (int i = 0; i < 2; ++i)
  151. {
  152. int index = i * 3;
  153. planeNormX[i] = Vec3<T> (frustumPlanes[index + 0].normal.x,
  154. frustumPlanes[index + 1].normal.x,
  155. frustumPlanes[index + 2].normal.x);
  156. planeNormY[i] = Vec3<T> (frustumPlanes[index + 0].normal.y,
  157. frustumPlanes[index + 1].normal.y,
  158. frustumPlanes[index + 2].normal.y);
  159. planeNormZ[i] = Vec3<T> (frustumPlanes[index + 0].normal.z,
  160. frustumPlanes[index + 1].normal.z,
  161. frustumPlanes[index + 2].normal.z);
  162. planeNormAbsX[i] = Vec3<T> (std::abs (planeNormX[i].x),
  163. std::abs (planeNormX[i].y),
  164. std::abs (planeNormX[i].z));
  165. planeNormAbsY[i] = Vec3<T> (std::abs (planeNormY[i].x),
  166. std::abs (planeNormY[i].y),
  167. std::abs (planeNormY[i].z));
  168. planeNormAbsZ[i] = Vec3<T> (std::abs (planeNormZ[i].x),
  169. std::abs (planeNormZ[i].y),
  170. std::abs (planeNormZ[i].z));
  171. planeOffsetVec[i] = Vec3<T> (frustumPlanes[index + 0].distance,
  172. frustumPlanes[index + 1].distance,
  173. frustumPlanes[index + 2].distance);
  174. }
  175. currFrustum = frustum;
  176. cameraMatrix = cameraMat;
  177. }
  178. template <typename T>
  179. bool
  180. FrustumTest<T>::isVisible (const Sphere3<T>& sphere) const IMATH_NOEXCEPT
  181. {
  182. Vec3<T> center = sphere.center;
  183. Vec3<T> radiusVec = Vec3<T> (sphere.radius, sphere.radius, sphere.radius);
  184. // This is a vertical dot-product on three vectors at once.
  185. Vec3<T> d0 = planeNormX[0] * center.x + planeNormY[0] * center.y + planeNormZ[0] * center.z -
  186. radiusVec - planeOffsetVec[0];
  187. if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
  188. return false;
  189. Vec3<T> d1 = planeNormX[1] * center.x + planeNormY[1] * center.y + planeNormZ[1] * center.z -
  190. radiusVec - planeOffsetVec[1];
  191. if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
  192. return false;
  193. return true;
  194. }
  195. template <typename T>
  196. bool
  197. FrustumTest<T>::completelyContains (const Sphere3<T>& sphere) const IMATH_NOEXCEPT
  198. {
  199. Vec3<T> center = sphere.center;
  200. Vec3<T> radiusVec = Vec3<T> (sphere.radius, sphere.radius, sphere.radius);
  201. // This is a vertical dot-product on three vectors at once.
  202. Vec3<T> d0 = planeNormX[0] * center.x + planeNormY[0] * center.y + planeNormZ[0] * center.z +
  203. radiusVec - planeOffsetVec[0];
  204. if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
  205. return false;
  206. Vec3<T> d1 = planeNormX[1] * center.x + planeNormY[1] * center.y + planeNormZ[1] * center.z +
  207. radiusVec - planeOffsetVec[1];
  208. if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
  209. return false;
  210. return true;
  211. }
  212. template <typename T>
  213. bool
  214. FrustumTest<T>::isVisible (const Box<Vec3<T>>& box) const IMATH_NOEXCEPT
  215. {
  216. if (box.isEmpty())
  217. return false;
  218. Vec3<T> center = (box.min + box.max) / 2;
  219. Vec3<T> extent = (box.max - center);
  220. // This is a vertical dot-product on three vectors at once.
  221. Vec3<T> d0 = planeNormX[0] * center.x + planeNormY[0] * center.y + planeNormZ[0] * center.z -
  222. planeNormAbsX[0] * extent.x - planeNormAbsY[0] * extent.y -
  223. planeNormAbsZ[0] * extent.z - planeOffsetVec[0];
  224. if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
  225. return false;
  226. Vec3<T> d1 = planeNormX[1] * center.x + planeNormY[1] * center.y + planeNormZ[1] * center.z -
  227. planeNormAbsX[1] * extent.x - planeNormAbsY[1] * extent.y -
  228. planeNormAbsZ[1] * extent.z - planeOffsetVec[1];
  229. if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
  230. return false;
  231. return true;
  232. }
  233. template <typename T>
  234. bool
  235. FrustumTest<T>::completelyContains (const Box<Vec3<T>>& box) const IMATH_NOEXCEPT
  236. {
  237. if (box.isEmpty())
  238. return false;
  239. Vec3<T> center = (box.min + box.max) / 2;
  240. Vec3<T> extent = (box.max - center);
  241. // This is a vertical dot-product on three vectors at once.
  242. Vec3<T> d0 = planeNormX[0] * center.x + planeNormY[0] * center.y + planeNormZ[0] * center.z +
  243. planeNormAbsX[0] * extent.x + planeNormAbsY[0] * extent.y +
  244. planeNormAbsZ[0] * extent.z - planeOffsetVec[0];
  245. if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
  246. return false;
  247. Vec3<T> d1 = planeNormX[1] * center.x + planeNormY[1] * center.y + planeNormZ[1] * center.z +
  248. planeNormAbsX[1] * extent.x + planeNormAbsY[1] * extent.y +
  249. planeNormAbsZ[1] * extent.z - planeOffsetVec[1];
  250. if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
  251. return false;
  252. return true;
  253. }
  254. template <typename T>
  255. bool
  256. FrustumTest<T>::isVisible (const Vec3<T>& vec) const IMATH_NOEXCEPT
  257. {
  258. // This is a vertical dot-product on three vectors at once.
  259. Vec3<T> d0 = (planeNormX[0] * vec.x) + (planeNormY[0] * vec.y) + (planeNormZ[0] * vec.z) -
  260. planeOffsetVec[0];
  261. if (d0.x >= 0 || d0.y >= 0 || d0.z >= 0)
  262. return false;
  263. Vec3<T> d1 = (planeNormX[1] * vec.x) + (planeNormY[1] * vec.y) + (planeNormZ[1] * vec.z) -
  264. planeOffsetVec[1];
  265. if (d1.x >= 0 || d1.y >= 0 || d1.z >= 0)
  266. return false;
  267. return true;
  268. }
  269. /// FrustymTest of type float
  270. typedef FrustumTest<float> FrustumTestf;
  271. /// FrustymTest of type double
  272. typedef FrustumTest<double> FrustumTestd;
  273. IMATH_INTERNAL_NAMESPACE_HEADER_EXIT
  274. #endif // INCLUDED_IMATHFRUSTUMTEST_H