Occluder.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. import BoundingSphere from "./BoundingSphere.js";
  2. import Cartesian3 from "./Cartesian3.js";
  3. import defaultValue from "./defaultValue.js";
  4. import defined from "./defined.js";
  5. import DeveloperError from "./DeveloperError.js";
  6. import Ellipsoid from "./Ellipsoid.js";
  7. import CesiumMath from "./Math.js";
  8. import Rectangle from "./Rectangle.js";
  9. import Visibility from "./Visibility.js";
  10. /**
  11. * Creates an Occluder derived from an object's position and radius, as well as the camera position.
  12. * The occluder can be used to determine whether or not other objects are visible or hidden behind the
  13. * visible horizon defined by the occluder and camera position.
  14. *
  15. * @alias Occluder
  16. *
  17. * @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
  18. * @param {Cartesian3} cameraPosition The coordinate of the viewer/camera.
  19. *
  20. * @constructor
  21. *
  22. * @example
  23. * // Construct an occluder one unit away from the origin with a radius of one.
  24. * const cameraPosition = Cesium.Cartesian3.ZERO;
  25. * const occluderBoundingSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 1);
  26. * const occluder = new Cesium.Occluder(occluderBoundingSphere, cameraPosition);
  27. */
  28. function Occluder(occluderBoundingSphere, cameraPosition) {
  29. //>>includeStart('debug', pragmas.debug);
  30. if (!defined(occluderBoundingSphere)) {
  31. throw new DeveloperError("occluderBoundingSphere is required.");
  32. }
  33. if (!defined(cameraPosition)) {
  34. throw new DeveloperError("camera position is required.");
  35. }
  36. //>>includeEnd('debug');
  37. this._occluderPosition = Cartesian3.clone(occluderBoundingSphere.center);
  38. this._occluderRadius = occluderBoundingSphere.radius;
  39. this._horizonDistance = 0.0;
  40. this._horizonPlaneNormal = undefined;
  41. this._horizonPlanePosition = undefined;
  42. this._cameraPosition = undefined;
  43. // cameraPosition fills in the above values
  44. this.cameraPosition = cameraPosition;
  45. }
  46. const scratchCartesian3 = new Cartesian3();
  47. Object.defineProperties(Occluder.prototype, {
  48. /**
  49. * The position of the occluder.
  50. * @memberof Occluder.prototype
  51. * @type {Cartesian3}
  52. */
  53. position: {
  54. get: function () {
  55. return this._occluderPosition;
  56. },
  57. },
  58. /**
  59. * The radius of the occluder.
  60. * @memberof Occluder.prototype
  61. * @type {number}
  62. */
  63. radius: {
  64. get: function () {
  65. return this._occluderRadius;
  66. },
  67. },
  68. /**
  69. * The position of the camera.
  70. * @memberof Occluder.prototype
  71. * @type {Cartesian3}
  72. */
  73. cameraPosition: {
  74. set: function (cameraPosition) {
  75. //>>includeStart('debug', pragmas.debug);
  76. if (!defined(cameraPosition)) {
  77. throw new DeveloperError("cameraPosition is required.");
  78. }
  79. //>>includeEnd('debug');
  80. cameraPosition = Cartesian3.clone(cameraPosition, this._cameraPosition);
  81. const cameraToOccluderVec = Cartesian3.subtract(
  82. this._occluderPosition,
  83. cameraPosition,
  84. scratchCartesian3
  85. );
  86. let invCameraToOccluderDistance = Cartesian3.magnitudeSquared(
  87. cameraToOccluderVec
  88. );
  89. const occluderRadiusSqrd = this._occluderRadius * this._occluderRadius;
  90. let horizonDistance;
  91. let horizonPlaneNormal;
  92. let horizonPlanePosition;
  93. if (invCameraToOccluderDistance > occluderRadiusSqrd) {
  94. horizonDistance = Math.sqrt(
  95. invCameraToOccluderDistance - occluderRadiusSqrd
  96. );
  97. invCameraToOccluderDistance =
  98. 1.0 / Math.sqrt(invCameraToOccluderDistance);
  99. horizonPlaneNormal = Cartesian3.multiplyByScalar(
  100. cameraToOccluderVec,
  101. invCameraToOccluderDistance,
  102. scratchCartesian3
  103. );
  104. const nearPlaneDistance =
  105. horizonDistance * horizonDistance * invCameraToOccluderDistance;
  106. horizonPlanePosition = Cartesian3.add(
  107. cameraPosition,
  108. Cartesian3.multiplyByScalar(
  109. horizonPlaneNormal,
  110. nearPlaneDistance,
  111. scratchCartesian3
  112. ),
  113. scratchCartesian3
  114. );
  115. } else {
  116. horizonDistance = Number.MAX_VALUE;
  117. }
  118. this._horizonDistance = horizonDistance;
  119. this._horizonPlaneNormal = horizonPlaneNormal;
  120. this._horizonPlanePosition = horizonPlanePosition;
  121. this._cameraPosition = cameraPosition;
  122. },
  123. },
  124. });
  125. /**
  126. * Creates an occluder from a bounding sphere and the camera position.
  127. *
  128. * @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
  129. * @param {Cartesian3} cameraPosition The coordinate of the viewer/camera.
  130. * @param {Occluder} [result] The object onto which to store the result.
  131. * @returns {Occluder} The occluder derived from an object's position and radius, as well as the camera position.
  132. */
  133. Occluder.fromBoundingSphere = function (
  134. occluderBoundingSphere,
  135. cameraPosition,
  136. result
  137. ) {
  138. //>>includeStart('debug', pragmas.debug);
  139. if (!defined(occluderBoundingSphere)) {
  140. throw new DeveloperError("occluderBoundingSphere is required.");
  141. }
  142. if (!defined(cameraPosition)) {
  143. throw new DeveloperError("camera position is required.");
  144. }
  145. //>>includeEnd('debug');
  146. if (!defined(result)) {
  147. return new Occluder(occluderBoundingSphere, cameraPosition);
  148. }
  149. Cartesian3.clone(occluderBoundingSphere.center, result._occluderPosition);
  150. result._occluderRadius = occluderBoundingSphere.radius;
  151. result.cameraPosition = cameraPosition;
  152. return result;
  153. };
  154. const tempVecScratch = new Cartesian3();
  155. /**
  156. * Determines whether or not a point, the <code>occludee</code>, is hidden from view by the occluder.
  157. *
  158. * @param {Cartesian3} occludee The point surrounding the occludee object.
  159. * @returns {boolean} <code>true</code> if the occludee is visible; otherwise <code>false</code>.
  160. *
  161. *
  162. * @example
  163. * const cameraPosition = new Cesium.Cartesian3(0, 0, 0);
  164. * const littleSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 0.25);
  165. * const occluder = new Cesium.Occluder(littleSphere, cameraPosition);
  166. * const point = new Cesium.Cartesian3(0, 0, -3);
  167. * occluder.isPointVisible(point); //returns true
  168. *
  169. * @see Occluder#computeVisibility
  170. */
  171. Occluder.prototype.isPointVisible = function (occludee) {
  172. if (this._horizonDistance !== Number.MAX_VALUE) {
  173. let tempVec = Cartesian3.subtract(
  174. occludee,
  175. this._occluderPosition,
  176. tempVecScratch
  177. );
  178. let temp = this._occluderRadius;
  179. temp = Cartesian3.magnitudeSquared(tempVec) - temp * temp;
  180. if (temp > 0.0) {
  181. temp = Math.sqrt(temp) + this._horizonDistance;
  182. tempVec = Cartesian3.subtract(occludee, this._cameraPosition, tempVec);
  183. return temp * temp > Cartesian3.magnitudeSquared(tempVec);
  184. }
  185. }
  186. return false;
  187. };
  188. const occludeePositionScratch = new Cartesian3();
  189. /**
  190. * Determines whether or not a sphere, the <code>occludee</code>, is hidden from view by the occluder.
  191. *
  192. * @param {BoundingSphere} occludee The bounding sphere surrounding the occludee object.
  193. * @returns {boolean} <code>true</code> if the occludee is visible; otherwise <code>false</code>.
  194. *
  195. *
  196. * @example
  197. * const cameraPosition = new Cesium.Cartesian3(0, 0, 0);
  198. * const littleSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 0.25);
  199. * const occluder = new Cesium.Occluder(littleSphere, cameraPosition);
  200. * const bigSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -3), 1);
  201. * occluder.isBoundingSphereVisible(bigSphere); //returns true
  202. *
  203. * @see Occluder#computeVisibility
  204. */
  205. Occluder.prototype.isBoundingSphereVisible = function (occludee) {
  206. const occludeePosition = Cartesian3.clone(
  207. occludee.center,
  208. occludeePositionScratch
  209. );
  210. const occludeeRadius = occludee.radius;
  211. if (this._horizonDistance !== Number.MAX_VALUE) {
  212. let tempVec = Cartesian3.subtract(
  213. occludeePosition,
  214. this._occluderPosition,
  215. tempVecScratch
  216. );
  217. let temp = this._occluderRadius - occludeeRadius;
  218. temp = Cartesian3.magnitudeSquared(tempVec) - temp * temp;
  219. if (occludeeRadius < this._occluderRadius) {
  220. if (temp > 0.0) {
  221. temp = Math.sqrt(temp) + this._horizonDistance;
  222. tempVec = Cartesian3.subtract(
  223. occludeePosition,
  224. this._cameraPosition,
  225. tempVec
  226. );
  227. return (
  228. temp * temp + occludeeRadius * occludeeRadius >
  229. Cartesian3.magnitudeSquared(tempVec)
  230. );
  231. }
  232. return false;
  233. }
  234. // Prevent against the case where the occludee radius is larger than the occluder's; since this is
  235. // an uncommon case, the following code should rarely execute.
  236. if (temp > 0.0) {
  237. tempVec = Cartesian3.subtract(
  238. occludeePosition,
  239. this._cameraPosition,
  240. tempVec
  241. );
  242. const tempVecMagnitudeSquared = Cartesian3.magnitudeSquared(tempVec);
  243. const occluderRadiusSquared = this._occluderRadius * this._occluderRadius;
  244. const occludeeRadiusSquared = occludeeRadius * occludeeRadius;
  245. if (
  246. (this._horizonDistance * this._horizonDistance +
  247. occluderRadiusSquared) *
  248. occludeeRadiusSquared >
  249. tempVecMagnitudeSquared * occluderRadiusSquared
  250. ) {
  251. // The occludee is close enough that the occluder cannot possible occlude the occludee
  252. return true;
  253. }
  254. temp = Math.sqrt(temp) + this._horizonDistance;
  255. return temp * temp + occludeeRadiusSquared > tempVecMagnitudeSquared;
  256. }
  257. // The occludee completely encompasses the occluder
  258. return true;
  259. }
  260. return false;
  261. };
  262. const tempScratch = new Cartesian3();
  263. /**
  264. * Determine to what extent an occludee is visible (not visible, partially visible, or fully visible).
  265. *
  266. * @param {BoundingSphere} occludeeBS The bounding sphere of the occludee.
  267. * @returns {Visibility} Visibility.NONE if the occludee is not visible,
  268. * Visibility.PARTIAL if the occludee is partially visible, or
  269. * Visibility.FULL if the occludee is fully visible.
  270. *
  271. *
  272. * @example
  273. * const sphere1 = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1.5), 0.5);
  274. * const sphere2 = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -2.5), 0.5);
  275. * const cameraPosition = new Cesium.Cartesian3(0, 0, 0);
  276. * const occluder = new Cesium.Occluder(sphere1, cameraPosition);
  277. * occluder.computeVisibility(sphere2); //returns Visibility.NONE
  278. *
  279. * @see Occluder#isVisible
  280. */
  281. Occluder.prototype.computeVisibility = function (occludeeBS) {
  282. //>>includeStart('debug', pragmas.debug);
  283. if (!defined(occludeeBS)) {
  284. throw new DeveloperError("occludeeBS is required.");
  285. }
  286. //>>includeEnd('debug');
  287. // If the occludee radius is larger than the occluders, this will return that
  288. // the entire ocludee is visible, even though that may not be the case, though this should
  289. // not occur too often.
  290. const occludeePosition = Cartesian3.clone(occludeeBS.center);
  291. const occludeeRadius = occludeeBS.radius;
  292. if (occludeeRadius > this._occluderRadius) {
  293. return Visibility.FULL;
  294. }
  295. if (this._horizonDistance !== Number.MAX_VALUE) {
  296. // The camera is outside the occluder
  297. let tempVec = Cartesian3.subtract(
  298. occludeePosition,
  299. this._occluderPosition,
  300. tempScratch
  301. );
  302. let temp = this._occluderRadius - occludeeRadius;
  303. const occluderToOccludeeDistSqrd = Cartesian3.magnitudeSquared(tempVec);
  304. temp = occluderToOccludeeDistSqrd - temp * temp;
  305. if (temp > 0.0) {
  306. // The occludee is not completely inside the occluder
  307. // Check to see if the occluder completely hides the occludee
  308. temp = Math.sqrt(temp) + this._horizonDistance;
  309. tempVec = Cartesian3.subtract(
  310. occludeePosition,
  311. this._cameraPosition,
  312. tempVec
  313. );
  314. const cameraToOccludeeDistSqrd = Cartesian3.magnitudeSquared(tempVec);
  315. if (
  316. temp * temp + occludeeRadius * occludeeRadius <
  317. cameraToOccludeeDistSqrd
  318. ) {
  319. return Visibility.NONE;
  320. }
  321. // Check to see whether the occluder is fully or partially visible
  322. // when the occludee does not intersect the occluder
  323. temp = this._occluderRadius + occludeeRadius;
  324. temp = occluderToOccludeeDistSqrd - temp * temp;
  325. if (temp > 0.0) {
  326. // The occludee does not intersect the occluder.
  327. temp = Math.sqrt(temp) + this._horizonDistance;
  328. return cameraToOccludeeDistSqrd <
  329. temp * temp + occludeeRadius * occludeeRadius
  330. ? Visibility.FULL
  331. : Visibility.PARTIAL;
  332. }
  333. //Check to see if the occluder is fully or partially visible when the occludee DOES
  334. //intersect the occluder
  335. tempVec = Cartesian3.subtract(
  336. occludeePosition,
  337. this._horizonPlanePosition,
  338. tempVec
  339. );
  340. return Cartesian3.dot(tempVec, this._horizonPlaneNormal) > -occludeeRadius
  341. ? Visibility.PARTIAL
  342. : Visibility.FULL;
  343. }
  344. }
  345. return Visibility.NONE;
  346. };
  347. const occludeePointScratch = new Cartesian3();
  348. /**
  349. * Computes a point that can be used as the occludee position to the visibility functions.
  350. * Use a radius of zero for the occludee radius. Typically, a user computes a bounding sphere around
  351. * an object that is used for visibility; however it is also possible to compute a point that if
  352. * seen/not seen would also indicate if an object is visible/not visible. This function is better
  353. * called for objects that do not move relative to the occluder and is large, such as a chunk of
  354. * terrain. You are better off not calling this and using the object's bounding sphere for objects
  355. * such as a satellite or ground vehicle.
  356. *
  357. * @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
  358. * @param {Cartesian3} occludeePosition The point where the occludee (bounding sphere of radius 0) is located.
  359. * @param {Cartesian3[]} positions List of altitude points on the horizon near the surface of the occluder.
  360. * @returns {object} An object containing two attributes: <code>occludeePoint</code> and <code>valid</code>
  361. * which is a boolean value.
  362. *
  363. * @exception {DeveloperError} <code>positions</code> must contain at least one element.
  364. * @exception {DeveloperError} <code>occludeePosition</code> must have a value other than <code>occluderBoundingSphere.center</code>.
  365. *
  366. * @example
  367. * const cameraPosition = new Cesium.Cartesian3(0, 0, 0);
  368. * const occluderBoundingSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -8), 2);
  369. * const occluder = new Cesium.Occluder(occluderBoundingSphere, cameraPosition);
  370. * const positions = [new Cesium.Cartesian3(-0.25, 0, -5.3), new Cesium.Cartesian3(0.25, 0, -5.3)];
  371. * const tileOccluderSphere = Cesium.BoundingSphere.fromPoints(positions);
  372. * const occludeePosition = tileOccluderSphere.center;
  373. * const occludeePt = Cesium.Occluder.computeOccludeePoint(occluderBoundingSphere, occludeePosition, positions);
  374. */
  375. Occluder.computeOccludeePoint = function (
  376. occluderBoundingSphere,
  377. occludeePosition,
  378. positions
  379. ) {
  380. //>>includeStart('debug', pragmas.debug);
  381. if (!defined(occluderBoundingSphere)) {
  382. throw new DeveloperError("occluderBoundingSphere is required.");
  383. }
  384. if (!defined(positions)) {
  385. throw new DeveloperError("positions is required.");
  386. }
  387. if (positions.length === 0) {
  388. throw new DeveloperError("positions must contain at least one element");
  389. }
  390. //>>includeEnd('debug');
  391. const occludeePos = Cartesian3.clone(occludeePosition);
  392. const occluderPosition = Cartesian3.clone(occluderBoundingSphere.center);
  393. const occluderRadius = occluderBoundingSphere.radius;
  394. const numPositions = positions.length;
  395. //>>includeStart('debug', pragmas.debug);
  396. if (Cartesian3.equals(occluderPosition, occludeePosition)) {
  397. throw new DeveloperError(
  398. "occludeePosition must be different than occluderBoundingSphere.center"
  399. );
  400. }
  401. //>>includeEnd('debug');
  402. // Compute a plane with a normal from the occluder to the occludee position.
  403. const occluderPlaneNormal = Cartesian3.normalize(
  404. Cartesian3.subtract(occludeePos, occluderPosition, occludeePointScratch),
  405. occludeePointScratch
  406. );
  407. const occluderPlaneD = -Cartesian3.dot(occluderPlaneNormal, occluderPosition);
  408. //For each position, determine the horizon intersection. Choose the position and intersection
  409. //that results in the greatest angle with the occcluder plane.
  410. const aRotationVector = Occluder._anyRotationVector(
  411. occluderPosition,
  412. occluderPlaneNormal,
  413. occluderPlaneD
  414. );
  415. let dot = Occluder._horizonToPlaneNormalDotProduct(
  416. occluderBoundingSphere,
  417. occluderPlaneNormal,
  418. occluderPlaneD,
  419. aRotationVector,
  420. positions[0]
  421. );
  422. if (!dot) {
  423. //The position is inside the mimimum radius, which is invalid
  424. return undefined;
  425. }
  426. let tempDot;
  427. for (let i = 1; i < numPositions; ++i) {
  428. tempDot = Occluder._horizonToPlaneNormalDotProduct(
  429. occluderBoundingSphere,
  430. occluderPlaneNormal,
  431. occluderPlaneD,
  432. aRotationVector,
  433. positions[i]
  434. );
  435. if (!tempDot) {
  436. //The position is inside the minimum radius, which is invalid
  437. return undefined;
  438. }
  439. if (tempDot < dot) {
  440. dot = tempDot;
  441. }
  442. }
  443. //Verify that the dot is not near 90 degress
  444. // eslint-disable-next-line no-loss-of-precision
  445. if (dot < 0.00174532836589830883577820272085) {
  446. return undefined;
  447. }
  448. const distance = occluderRadius / dot;
  449. return Cartesian3.add(
  450. occluderPosition,
  451. Cartesian3.multiplyByScalar(
  452. occluderPlaneNormal,
  453. distance,
  454. occludeePointScratch
  455. ),
  456. occludeePointScratch
  457. );
  458. };
  459. const computeOccludeePointFromRectangleScratch = [];
  460. /**
  461. * Computes a point that can be used as the occludee position to the visibility functions from a rectangle.
  462. *
  463. * @param {Rectangle} rectangle The rectangle used to create a bounding sphere.
  464. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine positions of the rectangle.
  465. * @returns {object} An object containing two attributes: <code>occludeePoint</code> and <code>valid</code>
  466. * which is a boolean value.
  467. */
  468. Occluder.computeOccludeePointFromRectangle = function (rectangle, ellipsoid) {
  469. //>>includeStart('debug', pragmas.debug);
  470. if (!defined(rectangle)) {
  471. throw new DeveloperError("rectangle is required.");
  472. }
  473. //>>includeEnd('debug');
  474. ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
  475. const positions = Rectangle.subsample(
  476. rectangle,
  477. ellipsoid,
  478. 0.0,
  479. computeOccludeePointFromRectangleScratch
  480. );
  481. const bs = BoundingSphere.fromPoints(positions);
  482. // TODO: get correct ellipsoid center
  483. const ellipsoidCenter = Cartesian3.ZERO;
  484. if (!Cartesian3.equals(ellipsoidCenter, bs.center)) {
  485. return Occluder.computeOccludeePoint(
  486. new BoundingSphere(ellipsoidCenter, ellipsoid.minimumRadius),
  487. bs.center,
  488. positions
  489. );
  490. }
  491. return undefined;
  492. };
  493. const tempVec0Scratch = new Cartesian3();
  494. Occluder._anyRotationVector = function (
  495. occluderPosition,
  496. occluderPlaneNormal,
  497. occluderPlaneD
  498. ) {
  499. const tempVec0 = Cartesian3.abs(occluderPlaneNormal, tempVec0Scratch);
  500. let majorAxis = tempVec0.x > tempVec0.y ? 0 : 1;
  501. if (
  502. (majorAxis === 0 && tempVec0.z > tempVec0.x) ||
  503. (majorAxis === 1 && tempVec0.z > tempVec0.y)
  504. ) {
  505. majorAxis = 2;
  506. }
  507. const tempVec = new Cartesian3();
  508. let tempVec1;
  509. if (majorAxis === 0) {
  510. tempVec0.x = occluderPosition.x;
  511. tempVec0.y = occluderPosition.y + 1.0;
  512. tempVec0.z = occluderPosition.z + 1.0;
  513. tempVec1 = Cartesian3.UNIT_X;
  514. } else if (majorAxis === 1) {
  515. tempVec0.x = occluderPosition.x + 1.0;
  516. tempVec0.y = occluderPosition.y;
  517. tempVec0.z = occluderPosition.z + 1.0;
  518. tempVec1 = Cartesian3.UNIT_Y;
  519. } else {
  520. tempVec0.x = occluderPosition.x + 1.0;
  521. tempVec0.y = occluderPosition.y + 1.0;
  522. tempVec0.z = occluderPosition.z;
  523. tempVec1 = Cartesian3.UNIT_Z;
  524. }
  525. const u =
  526. (Cartesian3.dot(occluderPlaneNormal, tempVec0) + occluderPlaneD) /
  527. -Cartesian3.dot(occluderPlaneNormal, tempVec1);
  528. return Cartesian3.normalize(
  529. Cartesian3.subtract(
  530. Cartesian3.add(
  531. tempVec0,
  532. Cartesian3.multiplyByScalar(tempVec1, u, tempVec),
  533. tempVec0
  534. ),
  535. occluderPosition,
  536. tempVec0
  537. ),
  538. tempVec0
  539. );
  540. };
  541. const posDirectionScratch = new Cartesian3();
  542. Occluder._rotationVector = function (
  543. occluderPosition,
  544. occluderPlaneNormal,
  545. occluderPlaneD,
  546. position,
  547. anyRotationVector
  548. ) {
  549. //Determine the angle between the occluder plane normal and the position direction
  550. let positionDirection = Cartesian3.subtract(
  551. position,
  552. occluderPosition,
  553. posDirectionScratch
  554. );
  555. positionDirection = Cartesian3.normalize(
  556. positionDirection,
  557. positionDirection
  558. );
  559. if (
  560. Cartesian3.dot(occluderPlaneNormal, positionDirection) <
  561. // eslint-disable-next-line no-loss-of-precision
  562. 0.99999998476912904932780850903444
  563. ) {
  564. const crossProduct = Cartesian3.cross(
  565. occluderPlaneNormal,
  566. positionDirection,
  567. positionDirection
  568. );
  569. const length = Cartesian3.magnitude(crossProduct);
  570. if (length > CesiumMath.EPSILON13) {
  571. return Cartesian3.normalize(crossProduct, new Cartesian3());
  572. }
  573. }
  574. //The occluder plane normal and the position direction are colinear. Use any
  575. //vector in the occluder plane as the rotation vector
  576. return anyRotationVector;
  577. };
  578. const posScratch1 = new Cartesian3();
  579. const occluerPosScratch = new Cartesian3();
  580. const posScratch2 = new Cartesian3();
  581. const horizonPlanePosScratch = new Cartesian3();
  582. Occluder._horizonToPlaneNormalDotProduct = function (
  583. occluderBS,
  584. occluderPlaneNormal,
  585. occluderPlaneD,
  586. anyRotationVector,
  587. position
  588. ) {
  589. const pos = Cartesian3.clone(position, posScratch1);
  590. const occluderPosition = Cartesian3.clone(
  591. occluderBS.center,
  592. occluerPosScratch
  593. );
  594. const occluderRadius = occluderBS.radius;
  595. //Verify that the position is outside the occluder
  596. let positionToOccluder = Cartesian3.subtract(
  597. occluderPosition,
  598. pos,
  599. posScratch2
  600. );
  601. const occluderToPositionDistanceSquared = Cartesian3.magnitudeSquared(
  602. positionToOccluder
  603. );
  604. const occluderRadiusSquared = occluderRadius * occluderRadius;
  605. if (occluderToPositionDistanceSquared < occluderRadiusSquared) {
  606. return false;
  607. }
  608. //Horizon parameters
  609. const horizonDistanceSquared =
  610. occluderToPositionDistanceSquared - occluderRadiusSquared;
  611. const horizonDistance = Math.sqrt(horizonDistanceSquared);
  612. const occluderToPositionDistance = Math.sqrt(
  613. occluderToPositionDistanceSquared
  614. );
  615. const invOccluderToPositionDistance = 1.0 / occluderToPositionDistance;
  616. const cosTheta = horizonDistance * invOccluderToPositionDistance;
  617. const horizonPlaneDistance = cosTheta * horizonDistance;
  618. positionToOccluder = Cartesian3.normalize(
  619. positionToOccluder,
  620. positionToOccluder
  621. );
  622. const horizonPlanePosition = Cartesian3.add(
  623. pos,
  624. Cartesian3.multiplyByScalar(
  625. positionToOccluder,
  626. horizonPlaneDistance,
  627. horizonPlanePosScratch
  628. ),
  629. horizonPlanePosScratch
  630. );
  631. const horizonCrossDistance = Math.sqrt(
  632. horizonDistanceSquared - horizonPlaneDistance * horizonPlaneDistance
  633. );
  634. //Rotate the position to occluder vector 90 degrees
  635. let tempVec = this._rotationVector(
  636. occluderPosition,
  637. occluderPlaneNormal,
  638. occluderPlaneD,
  639. pos,
  640. anyRotationVector
  641. );
  642. let horizonCrossDirection = Cartesian3.fromElements(
  643. tempVec.x * tempVec.x * positionToOccluder.x +
  644. (tempVec.x * tempVec.y - tempVec.z) * positionToOccluder.y +
  645. (tempVec.x * tempVec.z + tempVec.y) * positionToOccluder.z,
  646. (tempVec.x * tempVec.y + tempVec.z) * positionToOccluder.x +
  647. tempVec.y * tempVec.y * positionToOccluder.y +
  648. (tempVec.y * tempVec.z - tempVec.x) * positionToOccluder.z,
  649. (tempVec.x * tempVec.z - tempVec.y) * positionToOccluder.x +
  650. (tempVec.y * tempVec.z + tempVec.x) * positionToOccluder.y +
  651. tempVec.z * tempVec.z * positionToOccluder.z,
  652. posScratch1
  653. );
  654. horizonCrossDirection = Cartesian3.normalize(
  655. horizonCrossDirection,
  656. horizonCrossDirection
  657. );
  658. //Horizon positions
  659. const offset = Cartesian3.multiplyByScalar(
  660. horizonCrossDirection,
  661. horizonCrossDistance,
  662. posScratch1
  663. );
  664. tempVec = Cartesian3.normalize(
  665. Cartesian3.subtract(
  666. Cartesian3.add(horizonPlanePosition, offset, posScratch2),
  667. occluderPosition,
  668. posScratch2
  669. ),
  670. posScratch2
  671. );
  672. const dot0 = Cartesian3.dot(occluderPlaneNormal, tempVec);
  673. tempVec = Cartesian3.normalize(
  674. Cartesian3.subtract(
  675. Cartesian3.subtract(horizonPlanePosition, offset, tempVec),
  676. occluderPosition,
  677. tempVec
  678. ),
  679. tempVec
  680. );
  681. const dot1 = Cartesian3.dot(occluderPlaneNormal, tempVec);
  682. return dot0 < dot1 ? dot0 : dot1;
  683. };
  684. export default Occluder;