All files / engine/Source/Core CullingVolume.js

100% Statements 80/80
88.88% Branches 32/36
100% Functions 4/4
100% Lines 75/75

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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