CullingVolume.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import Cartesian3 from "./Cartesian3.js";
  2. import Cartesian4 from "./Cartesian4.js";
  3. import defaultValue from "./defaultValue.js";
  4. import defined from "./defined.js";
  5. import DeveloperError from "./DeveloperError.js";
  6. import Intersect from "./Intersect.js";
  7. import Plane from "./Plane.js";
  8. /**
  9. * The culling volume defined by planes.
  10. *
  11. * @alias CullingVolume
  12. * @constructor
  13. *
  14. * @param {Cartesian4[]} [planes] An array of clipping planes.
  15. */
  16. function CullingVolume(planes) {
  17. /**
  18. * Each plane is represented by a Cartesian4 object, where the x, y, and z components
  19. * define the unit vector normal to the plane, and the w component is the distance of the
  20. * plane from the origin.
  21. * @type {Cartesian4[]}
  22. * @default []
  23. */
  24. this.planes = defaultValue(planes, []);
  25. }
  26. const faces = [new Cartesian3(), new Cartesian3(), new Cartesian3()];
  27. Cartesian3.clone(Cartesian3.UNIT_X, faces[0]);
  28. Cartesian3.clone(Cartesian3.UNIT_Y, faces[1]);
  29. Cartesian3.clone(Cartesian3.UNIT_Z, faces[2]);
  30. const scratchPlaneCenter = new Cartesian3();
  31. const scratchPlaneNormal = new Cartesian3();
  32. const scratchPlane = new Plane(new Cartesian3(1.0, 0.0, 0.0), 0.0);
  33. /**
  34. * Constructs a culling volume from a bounding sphere. Creates six planes that create a box containing the sphere.
  35. * The planes are aligned to the x, y, and z axes in world coordinates.
  36. *
  37. * @param {BoundingSphere} boundingSphere The bounding sphere used to create the culling volume.
  38. * @param {CullingVolume} [result] The object onto which to store the result.
  39. * @returns {CullingVolume} The culling volume created from the bounding sphere.
  40. */
  41. CullingVolume.fromBoundingSphere = function (boundingSphere, result) {
  42. //>>includeStart('debug', pragmas.debug);
  43. if (!defined(boundingSphere)) {
  44. throw new DeveloperError("boundingSphere is required.");
  45. }
  46. //>>includeEnd('debug');
  47. if (!defined(result)) {
  48. result = new CullingVolume();
  49. }
  50. const length = faces.length;
  51. const planes = result.planes;
  52. planes.length = 2 * length;
  53. const center = boundingSphere.center;
  54. const radius = boundingSphere.radius;
  55. let planeIndex = 0;
  56. for (let i = 0; i < length; ++i) {
  57. const faceNormal = faces[i];
  58. let plane0 = planes[planeIndex];
  59. let plane1 = planes[planeIndex + 1];
  60. if (!defined(plane0)) {
  61. plane0 = planes[planeIndex] = new Cartesian4();
  62. }
  63. if (!defined(plane1)) {
  64. plane1 = planes[planeIndex + 1] = new Cartesian4();
  65. }
  66. Cartesian3.multiplyByScalar(faceNormal, -radius, scratchPlaneCenter);
  67. Cartesian3.add(center, scratchPlaneCenter, scratchPlaneCenter);
  68. plane0.x = faceNormal.x;
  69. plane0.y = faceNormal.y;
  70. plane0.z = faceNormal.z;
  71. plane0.w = -Cartesian3.dot(faceNormal, scratchPlaneCenter);
  72. Cartesian3.multiplyByScalar(faceNormal, radius, scratchPlaneCenter);
  73. Cartesian3.add(center, scratchPlaneCenter, scratchPlaneCenter);
  74. plane1.x = -faceNormal.x;
  75. plane1.y = -faceNormal.y;
  76. plane1.z = -faceNormal.z;
  77. plane1.w = -Cartesian3.dot(
  78. Cartesian3.negate(faceNormal, scratchPlaneNormal),
  79. scratchPlaneCenter
  80. );
  81. planeIndex += 2;
  82. }
  83. return result;
  84. };
  85. /**
  86. * Determines whether a bounding volume intersects the culling volume.
  87. *
  88. * @param {object} boundingVolume The bounding volume whose intersection with the culling volume is to be tested.
  89. * @returns {Intersect} Intersect.OUTSIDE, Intersect.INTERSECTING, or Intersect.INSIDE.
  90. */
  91. CullingVolume.prototype.computeVisibility = function (boundingVolume) {
  92. //>>includeStart('debug', pragmas.debug);
  93. if (!defined(boundingVolume)) {
  94. throw new DeveloperError("boundingVolume is required.");
  95. }
  96. //>>includeEnd('debug');
  97. const planes = this.planes;
  98. let intersecting = false;
  99. for (let k = 0, len = planes.length; k < len; ++k) {
  100. const result = boundingVolume.intersectPlane(
  101. Plane.fromCartesian4(planes[k], scratchPlane)
  102. );
  103. if (result === Intersect.OUTSIDE) {
  104. return Intersect.OUTSIDE;
  105. } else if (result === Intersect.INTERSECTING) {
  106. intersecting = true;
  107. }
  108. }
  109. return intersecting ? Intersect.INTERSECTING : Intersect.INSIDE;
  110. };
  111. /**
  112. * Determines whether a bounding volume intersects the culling volume.
  113. *
  114. * @param {object} boundingVolume The bounding volume whose intersection with the culling volume is to be tested.
  115. * @param {number} parentPlaneMask A bit mask from the boundingVolume's parent's check against the same culling
  116. * volume, such that if (planeMask & (1 << planeIndex) === 0), for k < 31, then
  117. * the parent (and therefore this) volume is completely inside plane[planeIndex]
  118. * and that plane check can be skipped.
  119. * @returns {number} A plane mask as described above (which can be applied to this boundingVolume's children).
  120. *
  121. * @private
  122. */
  123. CullingVolume.prototype.computeVisibilityWithPlaneMask = function (
  124. boundingVolume,
  125. parentPlaneMask
  126. ) {
  127. //>>includeStart('debug', pragmas.debug);
  128. if (!defined(boundingVolume)) {
  129. throw new DeveloperError("boundingVolume is required.");
  130. }
  131. if (!defined(parentPlaneMask)) {
  132. throw new DeveloperError("parentPlaneMask is required.");
  133. }
  134. //>>includeEnd('debug');
  135. if (
  136. parentPlaneMask === CullingVolume.MASK_OUTSIDE ||
  137. parentPlaneMask === CullingVolume.MASK_INSIDE
  138. ) {
  139. // parent is completely outside or completely inside, so this child is as well.
  140. return parentPlaneMask;
  141. }
  142. // Start with MASK_INSIDE (all zeros) so that after the loop, the return value can be compared with MASK_INSIDE.
  143. // (Because if there are fewer than 31 planes, the upper bits wont be changed.)
  144. let mask = CullingVolume.MASK_INSIDE;
  145. const planes = this.planes;
  146. for (let k = 0, len = planes.length; k < len; ++k) {
  147. // For k greater than 31 (since 31 is the maximum number of INSIDE/INTERSECTING bits we can store), skip the optimization.
  148. const flag = k < 31 ? 1 << k : 0;
  149. if (k < 31 && (parentPlaneMask & flag) === 0) {
  150. // boundingVolume is known to be INSIDE this plane.
  151. continue;
  152. }
  153. const result = boundingVolume.intersectPlane(
  154. Plane.fromCartesian4(planes[k], scratchPlane)
  155. );
  156. if (result === Intersect.OUTSIDE) {
  157. return CullingVolume.MASK_OUTSIDE;
  158. } else if (result === Intersect.INTERSECTING) {
  159. mask |= flag;
  160. }
  161. }
  162. return mask;
  163. };
  164. /**
  165. * For plane masks (as used in {@link CullingVolume#computeVisibilityWithPlaneMask}), this special value
  166. * represents the case where the object bounding volume is entirely outside the culling volume.
  167. *
  168. * @type {number}
  169. * @private
  170. */
  171. CullingVolume.MASK_OUTSIDE = 0xffffffff;
  172. /**
  173. * For plane masks (as used in {@link CullingVolume.prototype.computeVisibilityWithPlaneMask}), this value
  174. * represents the case where the object bounding volume is entirely inside the culling volume.
  175. *
  176. * @type {number}
  177. * @private
  178. */
  179. CullingVolume.MASK_INSIDE = 0x00000000;
  180. /**
  181. * For plane masks (as used in {@link CullingVolume.prototype.computeVisibilityWithPlaneMask}), this value
  182. * represents the case where the object bounding volume (may) intersect all planes of the culling volume.
  183. *
  184. * @type {number}
  185. * @private
  186. */
  187. CullingVolume.MASK_INDETERMINATE = 0x7fffffff;
  188. export default CullingVolume;