TileBoundingS2Cell.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import defined from "../Core/defined.js";
  3. import Cartographic from "../Core/Cartographic.js";
  4. import Ellipsoid from "../Core/Ellipsoid.js";
  5. import Intersect from "../Core/Intersect.js";
  6. import Matrix3 from "../Core/Matrix3.js";
  7. import Plane from "../Core/Plane.js";
  8. import CoplanarPolygonOutlineGeometry from "../Core/CoplanarPolygonOutlineGeometry.js";
  9. import BoundingSphere from "../Core/BoundingSphere.js";
  10. import Check from "../Core/Check.js";
  11. import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribute.js";
  12. import defaultValue from "../Core/defaultValue.js";
  13. import GeometryInstance from "../Core/GeometryInstance.js";
  14. import Matrix4 from "../Core/Matrix4.js";
  15. import PerInstanceColorAppearance from "./PerInstanceColorAppearance.js";
  16. import Primitive from "./Primitive.js";
  17. import S2Cell from "../Core/S2Cell.js";
  18. let centerCartographicScratch = new Cartographic();
  19. /**
  20. * A tile bounding volume specified as an S2 cell token with minimum and maximum heights.
  21. * The bounding volume is a k DOP. A k-DOP is the Boolean intersection of extents along k directions.
  22. *
  23. * @alias TileBoundingS2Cell
  24. * @constructor
  25. *
  26. * @param {object} options Object with the following properties:
  27. * @param {string} options.token The token of the S2 cell.
  28. * @param {number} [options.minimumHeight=0.0] The minimum height of the bounding volume.
  29. * @param {number} [options.maximumHeight=0.0] The maximum height of the bounding volume.
  30. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid.
  31. * @param {boolean} [options.computeBoundingVolumes=true] True to compute the {@link TileBoundingS2Cell#boundingVolume} and
  32. * {@link TileBoundingS2Cell#boundingSphere}. If false, these properties will be undefined.
  33. *
  34. * @private
  35. */
  36. function TileBoundingS2Cell(options) {
  37. //>>includeStart('debug', pragmas.debug);
  38. Check.typeOf.object("options", options);
  39. Check.typeOf.string("options.token", options.token);
  40. //>>includeEnd('debug');
  41. const s2Cell = S2Cell.fromToken(options.token);
  42. const minimumHeight = defaultValue(options.minimumHeight, 0.0);
  43. const maximumHeight = defaultValue(options.maximumHeight, 0.0);
  44. const ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
  45. this.s2Cell = s2Cell;
  46. this.minimumHeight = minimumHeight;
  47. this.maximumHeight = maximumHeight;
  48. this.ellipsoid = ellipsoid;
  49. const boundingPlanes = computeBoundingPlanes(
  50. s2Cell,
  51. minimumHeight,
  52. maximumHeight,
  53. ellipsoid
  54. );
  55. this._boundingPlanes = boundingPlanes;
  56. // Pre-compute vertices to speed up the plane intersection test.
  57. const vertices = computeVertices(boundingPlanes);
  58. this._vertices = vertices;
  59. // Pre-compute edge normals to speed up the point-polygon distance check in distanceToCamera.
  60. this._edgeNormals = new Array(6);
  61. this._edgeNormals[0] = computeEdgeNormals(
  62. boundingPlanes[0],
  63. vertices.slice(0, 4)
  64. );
  65. let i;
  66. // Based on the way the edge normals are computed, the edge normals all point away from the "face"
  67. // of the polyhedron they surround, except the plane for the top plane. Therefore, we negate the normals
  68. // for the top plane.
  69. for (i = 0; i < 4; i++) {
  70. this._edgeNormals[0][i] = Cartesian3.negate(
  71. this._edgeNormals[0][i],
  72. this._edgeNormals[0][i]
  73. );
  74. }
  75. this._edgeNormals[1] = computeEdgeNormals(
  76. boundingPlanes[1],
  77. vertices.slice(4, 8)
  78. );
  79. for (i = 0; i < 4; i++) {
  80. // For each plane, iterate through the vertices in CCW order.
  81. this._edgeNormals[2 + i] = computeEdgeNormals(boundingPlanes[2 + i], [
  82. vertices[i % 4],
  83. vertices[(i + 1) % 4],
  84. vertices[4 + ((i + 1) % 4)],
  85. vertices[4 + i],
  86. ]);
  87. }
  88. this._planeVertices = [
  89. this._vertices.slice(0, 4),
  90. this._vertices.slice(4, 8),
  91. ];
  92. for (i = 0; i < 4; i++) {
  93. this._planeVertices.push([
  94. this._vertices[i % 4],
  95. this._vertices[(i + 1) % 4],
  96. this._vertices[4 + ((i + 1) % 4)],
  97. this._vertices[4 + i],
  98. ]);
  99. }
  100. const center = s2Cell.getCenter();
  101. centerCartographicScratch = ellipsoid.cartesianToCartographic(
  102. center,
  103. centerCartographicScratch
  104. );
  105. centerCartographicScratch.height = (maximumHeight + minimumHeight) / 2;
  106. this.center = ellipsoid.cartographicToCartesian(
  107. centerCartographicScratch,
  108. center
  109. );
  110. this._boundingSphere = BoundingSphere.fromPoints(vertices);
  111. }
  112. const centerGeodeticNormalScratch = new Cartesian3();
  113. const topCartographicScratch = new Cartographic();
  114. const topScratch = new Cartesian3();
  115. const vertexCartographicScratch = new Cartographic();
  116. const vertexScratch = new Cartesian3();
  117. const vertexGeodeticNormalScratch = new Cartesian3();
  118. const sideNormalScratch = new Cartesian3();
  119. const sideScratch = new Cartesian3();
  120. /**
  121. * Computes bounding planes of the kDOP.
  122. * @private
  123. */
  124. function computeBoundingPlanes(
  125. s2Cell,
  126. minimumHeight,
  127. maximumHeight,
  128. ellipsoid
  129. ) {
  130. const planes = new Array(6);
  131. const centerPoint = s2Cell.getCenter();
  132. // Compute top plane.
  133. // - Get geodetic surface normal at the center of the S2 cell.
  134. // - Get center point at maximum height of bounding volume.
  135. // - Create top plane from surface normal and top point.
  136. const centerSurfaceNormal = ellipsoid.geodeticSurfaceNormal(
  137. centerPoint,
  138. centerGeodeticNormalScratch
  139. );
  140. const topCartographic = ellipsoid.cartesianToCartographic(
  141. centerPoint,
  142. topCartographicScratch
  143. );
  144. topCartographic.height = maximumHeight;
  145. const top = ellipsoid.cartographicToCartesian(topCartographic, topScratch);
  146. const topPlane = Plane.fromPointNormal(top, centerSurfaceNormal);
  147. planes[0] = topPlane;
  148. // Compute bottom plane.
  149. // - Iterate through bottom vertices
  150. // - Get distance from vertex to top plane
  151. // - Find longest distance from vertex to top plane
  152. // - Translate top plane by the distance
  153. let maxDistance = 0;
  154. let i;
  155. const vertices = [];
  156. let vertex, vertexCartographic;
  157. for (i = 0; i < 4; i++) {
  158. vertex = s2Cell.getVertex(i);
  159. vertices[i] = vertex;
  160. vertexCartographic = ellipsoid.cartesianToCartographic(
  161. vertex,
  162. vertexCartographicScratch
  163. );
  164. vertexCartographic.height = minimumHeight;
  165. const distance = Plane.getPointDistance(
  166. topPlane,
  167. ellipsoid.cartographicToCartesian(vertexCartographic, vertexScratch)
  168. );
  169. if (distance < maxDistance) {
  170. maxDistance = distance;
  171. }
  172. }
  173. const bottomPlane = Plane.clone(topPlane);
  174. // Negate the normal of the bottom plane since we want all normals to point "outwards".
  175. bottomPlane.normal = Cartesian3.negate(
  176. bottomPlane.normal,
  177. bottomPlane.normal
  178. );
  179. bottomPlane.distance = bottomPlane.distance * -1 + maxDistance;
  180. planes[1] = bottomPlane;
  181. // Compute side planes.
  182. // - Iterate through vertices (in CCW order, by default)
  183. // - Get a vertex and another vertex adjacent to it.
  184. // - Compute geodetic surface normal at one vertex.
  185. // - Compute vector between vertices.
  186. // - Compute normal of side plane. (cross product of top dir and side dir)
  187. for (i = 0; i < 4; i++) {
  188. vertex = vertices[i];
  189. const adjacentVertex = vertices[(i + 1) % 4];
  190. const geodeticNormal = ellipsoid.geodeticSurfaceNormal(
  191. vertex,
  192. vertexGeodeticNormalScratch
  193. );
  194. const side = Cartesian3.subtract(adjacentVertex, vertex, sideScratch);
  195. let sideNormal = Cartesian3.cross(side, geodeticNormal, sideNormalScratch);
  196. sideNormal = Cartesian3.normalize(sideNormal, sideNormal);
  197. planes[2 + i] = Plane.fromPointNormal(vertex, sideNormal);
  198. }
  199. return planes;
  200. }
  201. let n0Scratch = new Cartesian3();
  202. let n1Scratch = new Cartesian3();
  203. let n2Scratch = new Cartesian3();
  204. let x0Scratch = new Cartesian3();
  205. let x1Scratch = new Cartesian3();
  206. let x2Scratch = new Cartesian3();
  207. const t0Scratch = new Cartesian3();
  208. const t1Scratch = new Cartesian3();
  209. const t2Scratch = new Cartesian3();
  210. let f0Scratch = new Cartesian3();
  211. let f1Scratch = new Cartesian3();
  212. let f2Scratch = new Cartesian3();
  213. let sScratch = new Cartesian3();
  214. const matrixScratch = new Matrix3();
  215. /**
  216. * Computes intersection of 3 planes.
  217. * @private
  218. */
  219. function computeIntersection(p0, p1, p2) {
  220. n0Scratch = p0.normal;
  221. n1Scratch = p1.normal;
  222. n2Scratch = p2.normal;
  223. x0Scratch = Cartesian3.multiplyByScalar(p0.normal, -p0.distance, x0Scratch);
  224. x1Scratch = Cartesian3.multiplyByScalar(p1.normal, -p1.distance, x1Scratch);
  225. x2Scratch = Cartesian3.multiplyByScalar(p2.normal, -p2.distance, x2Scratch);
  226. f0Scratch = Cartesian3.multiplyByScalar(
  227. Cartesian3.cross(n1Scratch, n2Scratch, t0Scratch),
  228. Cartesian3.dot(x0Scratch, n0Scratch),
  229. f0Scratch
  230. );
  231. f1Scratch = Cartesian3.multiplyByScalar(
  232. Cartesian3.cross(n2Scratch, n0Scratch, t1Scratch),
  233. Cartesian3.dot(x1Scratch, n1Scratch),
  234. f1Scratch
  235. );
  236. f2Scratch = Cartesian3.multiplyByScalar(
  237. Cartesian3.cross(n0Scratch, n1Scratch, t2Scratch),
  238. Cartesian3.dot(x2Scratch, n2Scratch),
  239. f2Scratch
  240. );
  241. matrixScratch[0] = n0Scratch.x;
  242. matrixScratch[1] = n1Scratch.x;
  243. matrixScratch[2] = n2Scratch.x;
  244. matrixScratch[3] = n0Scratch.y;
  245. matrixScratch[4] = n1Scratch.y;
  246. matrixScratch[5] = n2Scratch.y;
  247. matrixScratch[6] = n0Scratch.z;
  248. matrixScratch[7] = n1Scratch.z;
  249. matrixScratch[8] = n2Scratch.z;
  250. const determinant = Matrix3.determinant(matrixScratch);
  251. sScratch = Cartesian3.add(f0Scratch, f1Scratch, sScratch);
  252. sScratch = Cartesian3.add(sScratch, f2Scratch, sScratch);
  253. return new Cartesian3(
  254. sScratch.x / determinant,
  255. sScratch.y / determinant,
  256. sScratch.z / determinant
  257. );
  258. }
  259. /**
  260. * Compute the vertices of the kDOP.
  261. * @private
  262. */
  263. function computeVertices(boundingPlanes) {
  264. const vertices = new Array(8);
  265. for (let i = 0; i < 4; i++) {
  266. // Vertices on the top plane.
  267. vertices[i] = computeIntersection(
  268. boundingPlanes[0],
  269. boundingPlanes[2 + ((i + 3) % 4)],
  270. boundingPlanes[2 + (i % 4)]
  271. );
  272. // Vertices on the bottom plane.
  273. vertices[i + 4] = computeIntersection(
  274. boundingPlanes[1],
  275. boundingPlanes[2 + ((i + 3) % 4)],
  276. boundingPlanes[2 + (i % 4)]
  277. );
  278. }
  279. return vertices;
  280. }
  281. let edgeScratch = new Cartesian3();
  282. let edgeNormalScratch = new Cartesian3();
  283. /**
  284. * Compute edge normals on a plane.
  285. * @private
  286. */
  287. function computeEdgeNormals(plane, vertices) {
  288. const edgeNormals = [];
  289. for (let i = 0; i < 4; i++) {
  290. edgeScratch = Cartesian3.subtract(
  291. vertices[(i + 1) % 4],
  292. vertices[i],
  293. edgeScratch
  294. );
  295. edgeNormalScratch = Cartesian3.cross(
  296. plane.normal,
  297. edgeScratch,
  298. edgeNormalScratch
  299. );
  300. edgeNormalScratch = Cartesian3.normalize(
  301. edgeNormalScratch,
  302. edgeNormalScratch
  303. );
  304. edgeNormals[i] = Cartesian3.clone(edgeNormalScratch);
  305. }
  306. return edgeNormals;
  307. }
  308. Object.defineProperties(TileBoundingS2Cell.prototype, {
  309. /**
  310. * The underlying bounding volume.
  311. *
  312. * @memberof TileOrientedBoundingBox.prototype
  313. *
  314. * @type {object}
  315. * @readonly
  316. */
  317. boundingVolume: {
  318. get: function () {
  319. return this;
  320. },
  321. },
  322. /**
  323. * The underlying bounding sphere.
  324. *
  325. * @memberof TileOrientedBoundingBox.prototype
  326. *
  327. * @type {BoundingSphere}
  328. * @readonly
  329. */
  330. boundingSphere: {
  331. get: function () {
  332. return this._boundingSphere;
  333. },
  334. },
  335. });
  336. const facePointScratch = new Cartesian3();
  337. /**
  338. * The distance to point check for this kDOP involves checking the signed distance of the point to each bounding
  339. * plane. A plane qualifies for a distance check if the point being tested against is in the half-space in the direction
  340. * of the normal i.e. if the signed distance of the point from the plane is greater than 0.
  341. *
  342. * There are 4 possible cases for a point if it is outside the polyhedron:
  343. *
  344. * \ X / X \ / \ / \ /
  345. * ---\---------/--- ---\---------/--- ---X---------/--- ---\---------/---
  346. * \ / \ / \ / \ /
  347. * ---\-----/--- ---\-----/--- ---\-----/--- ---\-----/---
  348. * \ / \ / \ / \ /
  349. * \ /
  350. * \
  351. * / \
  352. * / X \
  353. *
  354. * I II III IV
  355. *
  356. * Case I: There is only one plane selected.
  357. * In this case, we project the point onto the plane and do a point polygon distance check to find the closest point on the polygon.
  358. * The point may lie inside the "face" of the polygon or outside. If it is outside, we need to determine which edges to test against.
  359. *
  360. * Case II: There are two planes selected.
  361. * In this case, the point will lie somewhere on the line created at the intersection of the selected planes or one of the planes.
  362. *
  363. * Case III: There are three planes selected.
  364. * In this case, the point will lie on the vertex, at the intersection of the selected planes.
  365. *
  366. * Case IV: There are more than three planes selected.
  367. * Since we are on an ellipsoid, this will only happen in the bottom plane, which is what we will use for the distance test.
  368. */
  369. TileBoundingS2Cell.prototype.distanceToCamera = function (frameState) {
  370. //>>includeStart('debug', pragmas.debug);
  371. Check.defined("frameState", frameState);
  372. //>>includeEnd('debug');
  373. const point = frameState.camera.positionWC;
  374. const selectedPlaneIndices = [];
  375. const vertices = [];
  376. let edgeNormals;
  377. if (Plane.getPointDistance(this._boundingPlanes[0], point) > 0) {
  378. selectedPlaneIndices.push(0);
  379. vertices.push(this._planeVertices[0]);
  380. edgeNormals = this._edgeNormals[0];
  381. } else if (Plane.getPointDistance(this._boundingPlanes[1], point) > 0) {
  382. selectedPlaneIndices.push(1);
  383. vertices.push(this._planeVertices[1]);
  384. edgeNormals = this._edgeNormals[1];
  385. }
  386. let i;
  387. let sidePlaneIndex;
  388. for (i = 0; i < 4; i++) {
  389. sidePlaneIndex = 2 + i;
  390. if (
  391. Plane.getPointDistance(this._boundingPlanes[sidePlaneIndex], point) > 0
  392. ) {
  393. selectedPlaneIndices.push(sidePlaneIndex);
  394. // Store vertices in CCW order.
  395. vertices.push(this._planeVertices[sidePlaneIndex]);
  396. edgeNormals = this._edgeNormals[sidePlaneIndex];
  397. }
  398. }
  399. // Check if inside all planes.
  400. if (selectedPlaneIndices.length === 0) {
  401. return 0.0;
  402. }
  403. // We use the skip variable when the side plane indices are non-consecutive.
  404. let facePoint;
  405. let selectedPlane;
  406. if (selectedPlaneIndices.length === 1) {
  407. // Handles Case I
  408. selectedPlane = this._boundingPlanes[selectedPlaneIndices[0]];
  409. facePoint = closestPointPolygon(
  410. Plane.projectPointOntoPlane(selectedPlane, point, facePointScratch),
  411. vertices[0],
  412. selectedPlane,
  413. edgeNormals
  414. );
  415. return Cartesian3.distance(facePoint, point);
  416. } else if (selectedPlaneIndices.length === 2) {
  417. // Handles Case II
  418. // Since we are on the ellipsoid, the dihedral angle between a top plane and a side plane
  419. // will always be acute, so we can do a faster check there.
  420. if (selectedPlaneIndices[0] === 0) {
  421. const edge = [
  422. this._vertices[
  423. 4 * selectedPlaneIndices[0] + (selectedPlaneIndices[1] - 2)
  424. ],
  425. this._vertices[
  426. 4 * selectedPlaneIndices[0] + ((selectedPlaneIndices[1] - 2 + 1) % 4)
  427. ],
  428. ];
  429. facePoint = closestPointLineSegment(point, edge[0], edge[1]);
  430. return Cartesian3.distance(facePoint, point);
  431. }
  432. let minimumDistance = Number.MAX_VALUE;
  433. let distance;
  434. for (i = 0; i < 2; i++) {
  435. selectedPlane = this._boundingPlanes[selectedPlaneIndices[i]];
  436. facePoint = closestPointPolygon(
  437. Plane.projectPointOntoPlane(selectedPlane, point, facePointScratch),
  438. vertices[i],
  439. selectedPlane,
  440. this._edgeNormals[selectedPlaneIndices[i]]
  441. );
  442. distance = Cartesian3.distanceSquared(facePoint, point);
  443. if (distance < minimumDistance) {
  444. minimumDistance = distance;
  445. }
  446. }
  447. return Math.sqrt(minimumDistance);
  448. } else if (selectedPlaneIndices.length > 3) {
  449. // Handles Case IV
  450. facePoint = closestPointPolygon(
  451. Plane.projectPointOntoPlane(
  452. this._boundingPlanes[1],
  453. point,
  454. facePointScratch
  455. ),
  456. this._planeVertices[1],
  457. this._boundingPlanes[1],
  458. this._edgeNormals[1]
  459. );
  460. return Cartesian3.distance(facePoint, point);
  461. }
  462. // Handles Case III
  463. const skip =
  464. selectedPlaneIndices[1] === 2 && selectedPlaneIndices[2] === 5 ? 0 : 1;
  465. // Vertex is on top plane.
  466. if (selectedPlaneIndices[0] === 0) {
  467. return Cartesian3.distance(
  468. point,
  469. this._vertices[(selectedPlaneIndices[1] - 2 + skip) % 4]
  470. );
  471. }
  472. // Vertex is on bottom plane.
  473. return Cartesian3.distance(
  474. point,
  475. this._vertices[4 + ((selectedPlaneIndices[1] - 2 + skip) % 4)]
  476. );
  477. };
  478. const dScratch = new Cartesian3();
  479. const pL0Scratch = new Cartesian3();
  480. /**
  481. * Finds point on a line segment closest to a given point.
  482. * @private
  483. */
  484. function closestPointLineSegment(p, l0, l1) {
  485. const d = Cartesian3.subtract(l1, l0, dScratch);
  486. const pL0 = Cartesian3.subtract(p, l0, pL0Scratch);
  487. let t = Cartesian3.dot(d, pL0);
  488. if (t <= 0) {
  489. return l0;
  490. }
  491. const dMag = Cartesian3.dot(d, d);
  492. if (t >= dMag) {
  493. return l1;
  494. }
  495. t = t / dMag;
  496. return new Cartesian3(
  497. (1 - t) * l0.x + t * l1.x,
  498. (1 - t) * l0.y + t * l1.y,
  499. (1 - t) * l0.z + t * l1.z
  500. );
  501. }
  502. const edgePlaneScratch = new Plane(Cartesian3.UNIT_X, 0.0);
  503. /**
  504. * Finds closes point on the polygon, created by the given vertices, from
  505. * a point. The test point and the polygon are all on the same plane.
  506. * @private
  507. */
  508. function closestPointPolygon(p, vertices, plane, edgeNormals) {
  509. let minDistance = Number.MAX_VALUE;
  510. let distance;
  511. let closestPoint;
  512. let closestPointOnEdge;
  513. for (let i = 0; i < vertices.length; i++) {
  514. const edgePlane = Plane.fromPointNormal(
  515. vertices[i],
  516. edgeNormals[i],
  517. edgePlaneScratch
  518. );
  519. const edgePlaneDistance = Plane.getPointDistance(edgePlane, p);
  520. // Skip checking against the edge if the point is not in the half-space that the
  521. // edgePlane's normal points towards i.e. if the edgePlane is facing away from the point.
  522. if (edgePlaneDistance < 0) {
  523. continue;
  524. }
  525. closestPointOnEdge = closestPointLineSegment(
  526. p,
  527. vertices[i],
  528. vertices[(i + 1) % 4]
  529. );
  530. distance = Cartesian3.distance(p, closestPointOnEdge);
  531. if (distance < minDistance) {
  532. minDistance = distance;
  533. closestPoint = closestPointOnEdge;
  534. }
  535. }
  536. if (!defined(closestPoint)) {
  537. return p;
  538. }
  539. return closestPoint;
  540. }
  541. /**
  542. * Determines which side of a plane this volume is located.
  543. *
  544. * @param {Plane} plane The plane to test against.
  545. * @returns {Intersect} {@link Intersect.INSIDE} if the entire volume is on the side of the plane
  546. * the normal is pointing, {@link Intersect.OUTSIDE} if the entire volume is
  547. * on the opposite side, and {@link Intersect.INTERSECTING} if the volume
  548. * intersects the plane.
  549. */
  550. TileBoundingS2Cell.prototype.intersectPlane = function (plane) {
  551. //>>includeStart('debug', pragmas.debug);
  552. Check.defined("plane", plane);
  553. //>>includeEnd('debug');
  554. let plusCount = 0;
  555. let negCount = 0;
  556. for (let i = 0; i < this._vertices.length; i++) {
  557. const distanceToPlane =
  558. Cartesian3.dot(plane.normal, this._vertices[i]) + plane.distance;
  559. if (distanceToPlane < 0) {
  560. negCount++;
  561. } else {
  562. plusCount++;
  563. }
  564. }
  565. if (plusCount === this._vertices.length) {
  566. return Intersect.INSIDE;
  567. } else if (negCount === this._vertices.length) {
  568. return Intersect.OUTSIDE;
  569. }
  570. return Intersect.INTERSECTING;
  571. };
  572. /**
  573. * Creates a debug primitive that shows the outline of the tile bounding
  574. * volume.
  575. *
  576. * @param {Color} color The desired color of the primitive's mesh
  577. * @return {Primitive}
  578. */
  579. TileBoundingS2Cell.prototype.createDebugVolume = function (color) {
  580. //>>includeStart('debug', pragmas.debug);
  581. Check.defined("color", color);
  582. //>>includeEnd('debug');
  583. const modelMatrix = new Matrix4.clone(Matrix4.IDENTITY);
  584. const topPlanePolygon = new CoplanarPolygonOutlineGeometry({
  585. polygonHierarchy: {
  586. positions: this._planeVertices[0],
  587. },
  588. });
  589. const topPlaneGeometry = CoplanarPolygonOutlineGeometry.createGeometry(
  590. topPlanePolygon
  591. );
  592. const topPlaneInstance = new GeometryInstance({
  593. geometry: topPlaneGeometry,
  594. id: "outline",
  595. modelMatrix: modelMatrix,
  596. attributes: {
  597. color: ColorGeometryInstanceAttribute.fromColor(color),
  598. },
  599. });
  600. const bottomPlanePolygon = new CoplanarPolygonOutlineGeometry({
  601. polygonHierarchy: {
  602. positions: this._planeVertices[1],
  603. },
  604. });
  605. const bottomPlaneGeometry = CoplanarPolygonOutlineGeometry.createGeometry(
  606. bottomPlanePolygon
  607. );
  608. const bottomPlaneInstance = new GeometryInstance({
  609. geometry: bottomPlaneGeometry,
  610. id: "outline",
  611. modelMatrix: modelMatrix,
  612. attributes: {
  613. color: ColorGeometryInstanceAttribute.fromColor(color),
  614. },
  615. });
  616. const sideInstances = [];
  617. for (let i = 0; i < 4; i++) {
  618. const sidePlanePolygon = new CoplanarPolygonOutlineGeometry({
  619. polygonHierarchy: {
  620. positions: this._planeVertices[2 + i],
  621. },
  622. });
  623. const sidePlaneGeometry = CoplanarPolygonOutlineGeometry.createGeometry(
  624. sidePlanePolygon
  625. );
  626. sideInstances[i] = new GeometryInstance({
  627. geometry: sidePlaneGeometry,
  628. id: "outline",
  629. modelMatrix: modelMatrix,
  630. attributes: {
  631. color: ColorGeometryInstanceAttribute.fromColor(color),
  632. },
  633. });
  634. }
  635. return new Primitive({
  636. geometryInstances: [
  637. sideInstances[0],
  638. sideInstances[1],
  639. sideInstances[2],
  640. sideInstances[3],
  641. bottomPlaneInstance,
  642. topPlaneInstance,
  643. ],
  644. appearance: new PerInstanceColorAppearance({
  645. translucent: false,
  646. flat: true,
  647. }),
  648. asynchronous: false,
  649. });
  650. };
  651. export default TileBoundingS2Cell;