PerspectiveOffCenterFrustum.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. import Cartesian3 from "./Cartesian3.js";
  2. import Cartesian4 from "./Cartesian4.js";
  3. import CullingVolume from "./CullingVolume.js";
  4. import defaultValue from "./defaultValue.js";
  5. import defined from "./defined.js";
  6. import DeveloperError from "./DeveloperError.js";
  7. import CesiumMath from "./Math.js";
  8. import Matrix4 from "./Matrix4.js";
  9. /**
  10. * The viewing frustum is defined by 6 planes.
  11. * Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
  12. * define the unit vector normal to the plane, and the w component is the distance of the
  13. * plane from the origin/camera position.
  14. *
  15. * @alias PerspectiveOffCenterFrustum
  16. * @constructor
  17. *
  18. * @param {Object} [options] An object with the following properties:
  19. * @param {Number} [options.left] The left clipping plane distance.
  20. * @param {Number} [options.right] The right clipping plane distance.
  21. * @param {Number} [options.top] The top clipping plane distance.
  22. * @param {Number} [options.bottom] The bottom clipping plane distance.
  23. * @param {Number} [options.near=1.0] The near clipping plane distance.
  24. * @param {Number} [options.far=500000000.0] The far clipping plane distance.
  25. *
  26. * @example
  27. * const frustum = new Cesium.PerspectiveOffCenterFrustum({
  28. * left : -1.0,
  29. * right : 1.0,
  30. * top : 1.0,
  31. * bottom : -1.0,
  32. * near : 1.0,
  33. * far : 100.0
  34. * });
  35. *
  36. * @see PerspectiveFrustum
  37. */
  38. function PerspectiveOffCenterFrustum(options) {
  39. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  40. /**
  41. * Defines the left clipping plane.
  42. * @type {Number}
  43. * @default undefined
  44. */
  45. this.left = options.left;
  46. this._left = undefined;
  47. /**
  48. * Defines the right clipping plane.
  49. * @type {Number}
  50. * @default undefined
  51. */
  52. this.right = options.right;
  53. this._right = undefined;
  54. /**
  55. * Defines the top clipping plane.
  56. * @type {Number}
  57. * @default undefined
  58. */
  59. this.top = options.top;
  60. this._top = undefined;
  61. /**
  62. * Defines the bottom clipping plane.
  63. * @type {Number}
  64. * @default undefined
  65. */
  66. this.bottom = options.bottom;
  67. this._bottom = undefined;
  68. /**
  69. * The distance of the near plane.
  70. * @type {Number}
  71. * @default 1.0
  72. */
  73. this.near = defaultValue(options.near, 1.0);
  74. this._near = this.near;
  75. /**
  76. * The distance of the far plane.
  77. * @type {Number}
  78. * @default 500000000.0
  79. */
  80. this.far = defaultValue(options.far, 500000000.0);
  81. this._far = this.far;
  82. this._cullingVolume = new CullingVolume();
  83. this._perspectiveMatrix = new Matrix4();
  84. this._infinitePerspective = new Matrix4();
  85. }
  86. function update(frustum) {
  87. //>>includeStart('debug', pragmas.debug);
  88. if (
  89. !defined(frustum.right) ||
  90. !defined(frustum.left) ||
  91. !defined(frustum.top) ||
  92. !defined(frustum.bottom) ||
  93. !defined(frustum.near) ||
  94. !defined(frustum.far)
  95. ) {
  96. throw new DeveloperError(
  97. "right, left, top, bottom, near, or far parameters are not set."
  98. );
  99. }
  100. //>>includeEnd('debug');
  101. const t = frustum.top;
  102. const b = frustum.bottom;
  103. const r = frustum.right;
  104. const l = frustum.left;
  105. const n = frustum.near;
  106. const f = frustum.far;
  107. if (
  108. t !== frustum._top ||
  109. b !== frustum._bottom ||
  110. l !== frustum._left ||
  111. r !== frustum._right ||
  112. n !== frustum._near ||
  113. f !== frustum._far
  114. ) {
  115. //>>includeStart('debug', pragmas.debug);
  116. if (frustum.near <= 0 || frustum.near > frustum.far) {
  117. throw new DeveloperError(
  118. "near must be greater than zero and less than far."
  119. );
  120. }
  121. //>>includeEnd('debug');
  122. frustum._left = l;
  123. frustum._right = r;
  124. frustum._top = t;
  125. frustum._bottom = b;
  126. frustum._near = n;
  127. frustum._far = f;
  128. frustum._perspectiveMatrix = Matrix4.computePerspectiveOffCenter(
  129. l,
  130. r,
  131. b,
  132. t,
  133. n,
  134. f,
  135. frustum._perspectiveMatrix
  136. );
  137. frustum._infinitePerspective = Matrix4.computeInfinitePerspectiveOffCenter(
  138. l,
  139. r,
  140. b,
  141. t,
  142. n,
  143. frustum._infinitePerspective
  144. );
  145. }
  146. }
  147. Object.defineProperties(PerspectiveOffCenterFrustum.prototype, {
  148. /**
  149. * Gets the perspective projection matrix computed from the view frustum.
  150. * @memberof PerspectiveOffCenterFrustum.prototype
  151. * @type {Matrix4}
  152. * @readonly
  153. *
  154. * @see PerspectiveOffCenterFrustum#infiniteProjectionMatrix
  155. */
  156. projectionMatrix: {
  157. get: function () {
  158. update(this);
  159. return this._perspectiveMatrix;
  160. },
  161. },
  162. /**
  163. * Gets the perspective projection matrix computed from the view frustum with an infinite far plane.
  164. * @memberof PerspectiveOffCenterFrustum.prototype
  165. * @type {Matrix4}
  166. * @readonly
  167. *
  168. * @see PerspectiveOffCenterFrustum#projectionMatrix
  169. */
  170. infiniteProjectionMatrix: {
  171. get: function () {
  172. update(this);
  173. return this._infinitePerspective;
  174. },
  175. },
  176. });
  177. const getPlanesRight = new Cartesian3();
  178. const getPlanesNearCenter = new Cartesian3();
  179. const getPlanesFarCenter = new Cartesian3();
  180. const getPlanesNormal = new Cartesian3();
  181. /**
  182. * Creates a culling volume for this frustum.
  183. *
  184. * @param {Cartesian3} position The eye position.
  185. * @param {Cartesian3} direction The view direction.
  186. * @param {Cartesian3} up The up direction.
  187. * @returns {CullingVolume} A culling volume at the given position and orientation.
  188. *
  189. * @example
  190. * // Check if a bounding volume intersects the frustum.
  191. * const cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp);
  192. * const intersect = cullingVolume.computeVisibility(boundingVolume);
  193. */
  194. PerspectiveOffCenterFrustum.prototype.computeCullingVolume = function (
  195. position,
  196. direction,
  197. up
  198. ) {
  199. //>>includeStart('debug', pragmas.debug);
  200. if (!defined(position)) {
  201. throw new DeveloperError("position is required.");
  202. }
  203. if (!defined(direction)) {
  204. throw new DeveloperError("direction is required.");
  205. }
  206. if (!defined(up)) {
  207. throw new DeveloperError("up is required.");
  208. }
  209. //>>includeEnd('debug');
  210. const planes = this._cullingVolume.planes;
  211. const t = this.top;
  212. const b = this.bottom;
  213. const r = this.right;
  214. const l = this.left;
  215. const n = this.near;
  216. const f = this.far;
  217. const right = Cartesian3.cross(direction, up, getPlanesRight);
  218. const nearCenter = getPlanesNearCenter;
  219. Cartesian3.multiplyByScalar(direction, n, nearCenter);
  220. Cartesian3.add(position, nearCenter, nearCenter);
  221. const farCenter = getPlanesFarCenter;
  222. Cartesian3.multiplyByScalar(direction, f, farCenter);
  223. Cartesian3.add(position, farCenter, farCenter);
  224. const normal = getPlanesNormal;
  225. //Left plane computation
  226. Cartesian3.multiplyByScalar(right, l, normal);
  227. Cartesian3.add(nearCenter, normal, normal);
  228. Cartesian3.subtract(normal, position, normal);
  229. Cartesian3.normalize(normal, normal);
  230. Cartesian3.cross(normal, up, normal);
  231. Cartesian3.normalize(normal, normal);
  232. let plane = planes[0];
  233. if (!defined(plane)) {
  234. plane = planes[0] = new Cartesian4();
  235. }
  236. plane.x = normal.x;
  237. plane.y = normal.y;
  238. plane.z = normal.z;
  239. plane.w = -Cartesian3.dot(normal, position);
  240. //Right plane computation
  241. Cartesian3.multiplyByScalar(right, r, normal);
  242. Cartesian3.add(nearCenter, normal, normal);
  243. Cartesian3.subtract(normal, position, normal);
  244. Cartesian3.cross(up, normal, normal);
  245. Cartesian3.normalize(normal, normal);
  246. plane = planes[1];
  247. if (!defined(plane)) {
  248. plane = planes[1] = new Cartesian4();
  249. }
  250. plane.x = normal.x;
  251. plane.y = normal.y;
  252. plane.z = normal.z;
  253. plane.w = -Cartesian3.dot(normal, position);
  254. //Bottom plane computation
  255. Cartesian3.multiplyByScalar(up, b, normal);
  256. Cartesian3.add(nearCenter, normal, normal);
  257. Cartesian3.subtract(normal, position, normal);
  258. Cartesian3.cross(right, normal, normal);
  259. Cartesian3.normalize(normal, normal);
  260. plane = planes[2];
  261. if (!defined(plane)) {
  262. plane = planes[2] = new Cartesian4();
  263. }
  264. plane.x = normal.x;
  265. plane.y = normal.y;
  266. plane.z = normal.z;
  267. plane.w = -Cartesian3.dot(normal, position);
  268. //Top plane computation
  269. Cartesian3.multiplyByScalar(up, t, normal);
  270. Cartesian3.add(nearCenter, normal, normal);
  271. Cartesian3.subtract(normal, position, normal);
  272. Cartesian3.cross(normal, right, normal);
  273. Cartesian3.normalize(normal, normal);
  274. plane = planes[3];
  275. if (!defined(plane)) {
  276. plane = planes[3] = new Cartesian4();
  277. }
  278. plane.x = normal.x;
  279. plane.y = normal.y;
  280. plane.z = normal.z;
  281. plane.w = -Cartesian3.dot(normal, position);
  282. //Near plane computation
  283. plane = planes[4];
  284. if (!defined(plane)) {
  285. plane = planes[4] = new Cartesian4();
  286. }
  287. plane.x = direction.x;
  288. plane.y = direction.y;
  289. plane.z = direction.z;
  290. plane.w = -Cartesian3.dot(direction, nearCenter);
  291. //Far plane computation
  292. Cartesian3.negate(direction, normal);
  293. plane = planes[5];
  294. if (!defined(plane)) {
  295. plane = planes[5] = new Cartesian4();
  296. }
  297. plane.x = normal.x;
  298. plane.y = normal.y;
  299. plane.z = normal.z;
  300. plane.w = -Cartesian3.dot(normal, farCenter);
  301. return this._cullingVolume;
  302. };
  303. /**
  304. * Returns the pixel's width and height in meters.
  305. *
  306. * @param {Number} drawingBufferWidth The width of the drawing buffer.
  307. * @param {Number} drawingBufferHeight The height of the drawing buffer.
  308. * @param {Number} distance The distance to the near plane in meters.
  309. * @param {Number} pixelRatio The scaling factor from pixel space to coordinate space.
  310. * @param {Cartesian2} result The object onto which to store the result.
  311. * @returns {Cartesian2} The modified result parameter or a new instance of {@link Cartesian2} with the pixel's width and height in the x and y properties, respectively.
  312. *
  313. * @exception {DeveloperError} drawingBufferWidth must be greater than zero.
  314. * @exception {DeveloperError} drawingBufferHeight must be greater than zero.
  315. * @exception {DeveloperError} pixelRatio must be greater than zero.
  316. *
  317. * @example
  318. * // Example 1
  319. * // Get the width and height of a pixel.
  320. * const pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 1.0, scene.pixelRatio, new Cesium.Cartesian2());
  321. *
  322. * @example
  323. * // Example 2
  324. * // Get the width and height of a pixel if the near plane was set to 'distance'.
  325. * // For example, get the size of a pixel of an image on a billboard.
  326. * const position = camera.position;
  327. * const direction = camera.direction;
  328. * const toCenter = Cesium.Cartesian3.subtract(primitive.boundingVolume.center, position, new Cesium.Cartesian3()); // vector from camera to a primitive
  329. * const toCenterProj = Cesium.Cartesian3.multiplyByScalar(direction, Cesium.Cartesian3.dot(direction, toCenter), new Cesium.Cartesian3()); // project vector onto camera direction vector
  330. * const distance = Cesium.Cartesian3.magnitude(toCenterProj);
  331. * const pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, distance, scene.pixelRatio, new Cesium.Cartesian2());
  332. */
  333. PerspectiveOffCenterFrustum.prototype.getPixelDimensions = function (
  334. drawingBufferWidth,
  335. drawingBufferHeight,
  336. distance,
  337. pixelRatio,
  338. result
  339. ) {
  340. update(this);
  341. //>>includeStart('debug', pragmas.debug);
  342. if (!defined(drawingBufferWidth) || !defined(drawingBufferHeight)) {
  343. throw new DeveloperError(
  344. "Both drawingBufferWidth and drawingBufferHeight are required."
  345. );
  346. }
  347. if (drawingBufferWidth <= 0) {
  348. throw new DeveloperError("drawingBufferWidth must be greater than zero.");
  349. }
  350. if (drawingBufferHeight <= 0) {
  351. throw new DeveloperError("drawingBufferHeight must be greater than zero.");
  352. }
  353. if (!defined(distance)) {
  354. throw new DeveloperError("distance is required.");
  355. }
  356. if (!defined(pixelRatio)) {
  357. throw new DeveloperError("pixelRatio is required");
  358. }
  359. if (pixelRatio <= 0) {
  360. throw new DeveloperError("pixelRatio must be greater than zero.");
  361. }
  362. if (!defined(result)) {
  363. throw new DeveloperError("A result object is required.");
  364. }
  365. //>>includeEnd('debug');
  366. const inverseNear = 1.0 / this.near;
  367. let tanTheta = this.top * inverseNear;
  368. const pixelHeight =
  369. (2.0 * pixelRatio * distance * tanTheta) / drawingBufferHeight;
  370. tanTheta = this.right * inverseNear;
  371. const pixelWidth =
  372. (2.0 * pixelRatio * distance * tanTheta) / drawingBufferWidth;
  373. result.x = pixelWidth;
  374. result.y = pixelHeight;
  375. return result;
  376. };
  377. /**
  378. * Returns a duplicate of a PerspectiveOffCenterFrustum instance.
  379. *
  380. * @param {PerspectiveOffCenterFrustum} [result] The object onto which to store the result.
  381. * @returns {PerspectiveOffCenterFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided.
  382. */
  383. PerspectiveOffCenterFrustum.prototype.clone = function (result) {
  384. if (!defined(result)) {
  385. result = new PerspectiveOffCenterFrustum();
  386. }
  387. result.right = this.right;
  388. result.left = this.left;
  389. result.top = this.top;
  390. result.bottom = this.bottom;
  391. result.near = this.near;
  392. result.far = this.far;
  393. // force update of clone to compute matrices
  394. result._left = undefined;
  395. result._right = undefined;
  396. result._top = undefined;
  397. result._bottom = undefined;
  398. result._near = undefined;
  399. result._far = undefined;
  400. return result;
  401. };
  402. /**
  403. * Compares the provided PerspectiveOffCenterFrustum componentwise and returns
  404. * <code>true</code> if they are equal, <code>false</code> otherwise.
  405. *
  406. * @param {PerspectiveOffCenterFrustum} [other] The right hand side PerspectiveOffCenterFrustum.
  407. * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
  408. */
  409. PerspectiveOffCenterFrustum.prototype.equals = function (other) {
  410. return (
  411. defined(other) &&
  412. other instanceof PerspectiveOffCenterFrustum &&
  413. this.right === other.right &&
  414. this.left === other.left &&
  415. this.top === other.top &&
  416. this.bottom === other.bottom &&
  417. this.near === other.near &&
  418. this.far === other.far
  419. );
  420. };
  421. /**
  422. * Compares the provided PerspectiveOffCenterFrustum componentwise and returns
  423. * <code>true</code> if they pass an absolute or relative tolerance test,
  424. * <code>false</code> otherwise.
  425. *
  426. * @param {PerspectiveOffCenterFrustum} other The right hand side PerspectiveOffCenterFrustum.
  427. * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
  428. * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
  429. * @returns {Boolean} <code>true</code> if this and other are within the provided epsilon, <code>false</code> otherwise.
  430. */
  431. PerspectiveOffCenterFrustum.prototype.equalsEpsilon = function (
  432. other,
  433. relativeEpsilon,
  434. absoluteEpsilon
  435. ) {
  436. return (
  437. other === this ||
  438. (defined(other) &&
  439. other instanceof PerspectiveOffCenterFrustum &&
  440. CesiumMath.equalsEpsilon(
  441. this.right,
  442. other.right,
  443. relativeEpsilon,
  444. absoluteEpsilon
  445. ) &&
  446. CesiumMath.equalsEpsilon(
  447. this.left,
  448. other.left,
  449. relativeEpsilon,
  450. absoluteEpsilon
  451. ) &&
  452. CesiumMath.equalsEpsilon(
  453. this.top,
  454. other.top,
  455. relativeEpsilon,
  456. absoluteEpsilon
  457. ) &&
  458. CesiumMath.equalsEpsilon(
  459. this.bottom,
  460. other.bottom,
  461. relativeEpsilon,
  462. absoluteEpsilon
  463. ) &&
  464. CesiumMath.equalsEpsilon(
  465. this.near,
  466. other.near,
  467. relativeEpsilon,
  468. absoluteEpsilon
  469. ) &&
  470. CesiumMath.equalsEpsilon(
  471. this.far,
  472. other.far,
  473. relativeEpsilon,
  474. absoluteEpsilon
  475. ))
  476. );
  477. };
  478. export default PerspectiveOffCenterFrustum;