ImplicitTileCoordinates.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. import CesiumMath from "../Core/Math.js";
  2. import Check from "../Core/Check.js";
  3. import DeveloperError from "../Core/DeveloperError.js";
  4. import MortonOrder from "../Core/MortonOrder.js";
  5. import ImplicitSubdivisionScheme from "./ImplicitSubdivisionScheme.js";
  6. /**
  7. * The coordinates for a tile in an implicit tileset. The coordinates
  8. * are (level, x, y) for quadtrees or (level, x, y, z) for octrees.
  9. * <p>
  10. * Level numbers are 0-indexed and typically start at the root of the implicit
  11. * tileset (the tile with either implicitTiling in its JSON (3D Tiles 1.1) or
  12. * the <code>3DTILES_implicit_tiling</code> extension).
  13. * This object can also represent the relative offset from one set of coordinates
  14. * to another. See {@link ImplicitTileCoordinates#getOffsetCoordinates}. The term
  15. * local coordinates refers to coordinates that are relative to the root of a
  16. * subtree and the term global coordinates refers to coordinates relative to the
  17. * root of an implicit tileset.
  18. * </p>
  19. * <p>
  20. * For box bounding volumes, x, y, z increase along the +x, +y, and +z
  21. * directions defined by the half axes.
  22. * </p>
  23. * <p>
  24. * For region bounding volumes, x increases in the +longitude direction, y
  25. * increases in the +latitude direction, and z increases in the +height
  26. * direction.
  27. * </p>
  28. * <p>
  29. * Care must be taken when converting between implicit coordinates and Morton
  30. * indices because there is a 16-bit limit on {@link MortonOrder#encode2D} and
  31. * a 10-bit limit on {@link MortonOrder#encode3D}. Typically these conversions
  32. * should be done on local coordinates, not global coordinates, and the maximum
  33. * number of levels in the subtree should be 15 for quadtree and 9 for octree (to
  34. * account for the extra level needed by child subtree coordinates).
  35. * </p>
  36. *
  37. * @alias ImplicitTileCoordinates
  38. * @constructor
  39. *
  40. * @param {Object} options An object with the following properties:
  41. * @param {ImplicitSubdivisionScheme} options.subdivisionScheme Whether the coordinates are for a quadtree or octree
  42. * @param {Number} options.subtreeLevels The number of distinct levels within the coordinate's subtree
  43. * @param {Number} options.level The level of a tile relative to the tile with the extension
  44. * @param {Number} options.x The x coordinate of the tile
  45. * @param {Number} options.y The y coordinate of the tile
  46. * @param {Number} [options.z] The z coordinate of the tile. Only required when options.subdivisionScheme is ImplicitSubdivisionScheme.OCTREE
  47. * @private
  48. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  49. */
  50. export default function ImplicitTileCoordinates(options) {
  51. //>>includeStart('debug', pragmas.debug);
  52. Check.typeOf.string("options.subdivisionScheme", options.subdivisionScheme);
  53. Check.typeOf.number("options.subtreeLevels", options.subtreeLevels);
  54. Check.typeOf.number("options.level", options.level);
  55. Check.typeOf.number("options.x", options.x);
  56. Check.typeOf.number("options.y", options.y);
  57. if (options.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  58. Check.typeOf.number("options.z", options.z);
  59. }
  60. // Check for values that are negative
  61. if (options.level < 0) {
  62. throw new DeveloperError("level must be non-negative");
  63. }
  64. if (options.x < 0) {
  65. throw new DeveloperError("x must be non-negative");
  66. }
  67. if (options.y < 0) {
  68. throw new DeveloperError("y must be non-negative");
  69. }
  70. if (options.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  71. if (options.z < 0) {
  72. throw new DeveloperError("z must be non-negative");
  73. }
  74. }
  75. // Check for values that are too large
  76. const dimensionAtLevel = 1 << options.level;
  77. if (options.x >= dimensionAtLevel) {
  78. throw new DeveloperError("x is out of range");
  79. }
  80. if (options.y >= dimensionAtLevel) {
  81. throw new DeveloperError("y is out of range");
  82. }
  83. if (options.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  84. if (options.z >= dimensionAtLevel) {
  85. throw new DeveloperError("z is out of range");
  86. }
  87. }
  88. //>>includeEnd('debug');
  89. /**
  90. * Whether the tileset is a quadtree or octree
  91. *
  92. * @type {ImplicitSubdivisionScheme}
  93. * @readonly
  94. * @private
  95. */
  96. this.subdivisionScheme = options.subdivisionScheme;
  97. /**
  98. * The number of distinct levels within the coordinate's subtree
  99. *
  100. * @type {Number}
  101. * @readonly
  102. * @private
  103. */
  104. this.subtreeLevels = options.subtreeLevels;
  105. /**
  106. * Level of this tile, relative to the tile with implicit tiling in its JSON
  107. * (3D Tiles 1.1) or the <code>3DTILES_implicit_tiling</code> extension.
  108. * Level numbers start at 0.
  109. *
  110. * @type {Number}
  111. * @readonly
  112. * @private
  113. */
  114. this.level = options.level;
  115. /**
  116. * X coordinate of this tile
  117. *
  118. * @type {Number}
  119. * @readonly
  120. * @private
  121. */
  122. this.x = options.x;
  123. /**
  124. * Y coordinate of this tile
  125. *
  126. * @type {Number}
  127. * @readonly
  128. * @private
  129. */
  130. this.y = options.y;
  131. /**
  132. * Z coordinate of this tile. Only defined for octrees.
  133. *
  134. * @type {Number|undefined}
  135. * @readonly
  136. * @private
  137. */
  138. this.z = undefined;
  139. if (options.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  140. this.z = options.z;
  141. }
  142. }
  143. Object.defineProperties(ImplicitTileCoordinates.prototype, {
  144. /**
  145. * An index in the range of [0, branchingFactor) that indicates
  146. * which child of the parent cell these coordinates correspond to.
  147. * This can be viewed as a morton index within the parent tile.
  148. * <p>
  149. * This is the last 3 bits of the morton index of the tile, but it can
  150. * be computed more directly by concatenating the bits [z0] y0 x0
  151. * </p>
  152. *
  153. * @type {Number}
  154. * @readonly
  155. * @private
  156. */
  157. childIndex: {
  158. get: function () {
  159. let childIndex = 0;
  160. childIndex |= this.x & 1;
  161. childIndex |= (this.y & 1) << 1;
  162. if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  163. childIndex |= (this.z & 1) << 2;
  164. }
  165. return childIndex;
  166. },
  167. },
  168. /**
  169. * Get the Morton index for this tile within the current level by interleaving
  170. * the bits of the x, y and z coordinates.
  171. *
  172. * @type {Number}
  173. * @readonly
  174. * @private
  175. */
  176. mortonIndex: {
  177. get: function () {
  178. if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  179. return MortonOrder.encode3D(this.x, this.y, this.z);
  180. }
  181. return MortonOrder.encode2D(this.x, this.y);
  182. },
  183. },
  184. /**
  185. * Get the tile index by adding the Morton index to the level offset
  186. *
  187. * @type {Number}
  188. * @readonly
  189. * @private
  190. */
  191. tileIndex: {
  192. get: function () {
  193. const levelOffset =
  194. this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE
  195. ? // (8^N - 1) / (8-1)
  196. ((1 << (3 * this.level)) - 1) / 7
  197. : // (4^N - 1) / (4-1)
  198. ((1 << (2 * this.level)) - 1) / 3;
  199. const mortonIndex = this.mortonIndex;
  200. return levelOffset + mortonIndex;
  201. },
  202. },
  203. });
  204. /**
  205. * Check that the two coordinates are compatible
  206. * @param {ImplicitTileCoordinates} a
  207. * @param {ImplicitTileCoordinates} b
  208. * @private
  209. */
  210. function checkMatchingSubtreeShape(a, b) {
  211. if (a.subdivisionScheme !== b.subdivisionScheme) {
  212. throw new DeveloperError("coordinates must have same subdivisionScheme");
  213. }
  214. if (a.subtreeLevels !== b.subtreeLevels) {
  215. throw new DeveloperError("coordinates must have same subtreeLevels");
  216. }
  217. }
  218. /**
  219. * Compute the coordinates of a tile deeper in the tree with a (level, x, y, [z]) relative offset.
  220. *
  221. * @param {ImplicitTileCoordinates} offsetCoordinates The offset from the ancestor
  222. * @returns {ImplicitTileCoordinates} The coordinates of the descendant
  223. * @private
  224. */
  225. ImplicitTileCoordinates.prototype.getDescendantCoordinates = function (
  226. offsetCoordinates
  227. ) {
  228. //>>includeStart('debug', pragmas.debug);
  229. Check.typeOf.object("offsetCoordinates", offsetCoordinates);
  230. checkMatchingSubtreeShape(this, offsetCoordinates);
  231. //>>includeEnd('debug');
  232. const descendantLevel = this.level + offsetCoordinates.level;
  233. const descendantX = (this.x << offsetCoordinates.level) + offsetCoordinates.x;
  234. const descendantY = (this.y << offsetCoordinates.level) + offsetCoordinates.y;
  235. if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  236. const descendantZ =
  237. (this.z << offsetCoordinates.level) + offsetCoordinates.z;
  238. return new ImplicitTileCoordinates({
  239. subdivisionScheme: this.subdivisionScheme,
  240. subtreeLevels: this.subtreeLevels,
  241. level: descendantLevel,
  242. x: descendantX,
  243. y: descendantY,
  244. z: descendantZ,
  245. });
  246. }
  247. // Quadtree
  248. return new ImplicitTileCoordinates({
  249. subdivisionScheme: this.subdivisionScheme,
  250. subtreeLevels: this.subtreeLevels,
  251. level: descendantLevel,
  252. x: descendantX,
  253. y: descendantY,
  254. });
  255. };
  256. /**
  257. * Compute the coordinates of a tile higher up in the tree by going up a number of levels.
  258. *
  259. * @param {Number} offsetLevels The number of levels to go up in the tree
  260. * @returns {ImplicitTileCoordinates} The coordinates of the ancestor
  261. * @private
  262. */
  263. ImplicitTileCoordinates.prototype.getAncestorCoordinates = function (
  264. offsetLevels
  265. ) {
  266. //>>includeStart('debug', pragmas.debug);
  267. Check.typeOf.number("offsetLevels", offsetLevels);
  268. if (offsetLevels < 0) {
  269. throw new DeveloperError("offsetLevels must be non-negative");
  270. }
  271. if (offsetLevels > this.level) {
  272. throw new DeveloperError("ancestor cannot be above the tileset root");
  273. }
  274. //>>includeEnd('debug');
  275. const divisor = 1 << offsetLevels;
  276. const ancestorLevel = this.level - offsetLevels;
  277. const ancestorX = Math.floor(this.x / divisor);
  278. const ancestorY = Math.floor(this.y / divisor);
  279. if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  280. const ancestorZ = Math.floor(this.z / divisor);
  281. return new ImplicitTileCoordinates({
  282. subdivisionScheme: this.subdivisionScheme,
  283. subtreeLevels: this.subtreeLevels,
  284. level: ancestorLevel,
  285. x: ancestorX,
  286. y: ancestorY,
  287. z: ancestorZ,
  288. });
  289. }
  290. // Quadtree
  291. return new ImplicitTileCoordinates({
  292. subdivisionScheme: this.subdivisionScheme,
  293. subtreeLevels: this.subtreeLevels,
  294. level: ancestorLevel,
  295. x: ancestorX,
  296. y: ancestorY,
  297. });
  298. };
  299. /**
  300. * Compute the (level, x, y, [z]) offset to a descendant
  301. *
  302. * @param {ImplicitTileCoordinates} descendantCoordinates The descendant coordinates
  303. * @returns {ImplicitTileCoordinates} The offset between the ancestor and the descendant
  304. */
  305. ImplicitTileCoordinates.prototype.getOffsetCoordinates = function (
  306. descendantCoordinates
  307. ) {
  308. //>>includeStart('debug', pragmas.debug);
  309. Check.typeOf.object("descendantCoordinates", descendantCoordinates);
  310. if (
  311. !this.isEqual(descendantCoordinates) &&
  312. !this.isAncestor(descendantCoordinates)
  313. ) {
  314. throw new DeveloperError("this is not an ancestor of descendant");
  315. }
  316. checkMatchingSubtreeShape(this, descendantCoordinates);
  317. //>>includeEnd('debug');
  318. const offsetLevel = descendantCoordinates.level - this.level;
  319. const dimensionAtOffsetLevel = 1 << offsetLevel;
  320. const offsetX = descendantCoordinates.x % dimensionAtOffsetLevel;
  321. const offsetY = descendantCoordinates.y % dimensionAtOffsetLevel;
  322. if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  323. const offsetZ = descendantCoordinates.z % dimensionAtOffsetLevel;
  324. return new ImplicitTileCoordinates({
  325. subdivisionScheme: this.subdivisionScheme,
  326. subtreeLevels: this.subtreeLevels,
  327. level: offsetLevel,
  328. x: offsetX,
  329. y: offsetY,
  330. z: offsetZ,
  331. });
  332. }
  333. // Quadtree
  334. return new ImplicitTileCoordinates({
  335. subdivisionScheme: this.subdivisionScheme,
  336. subtreeLevels: this.subtreeLevels,
  337. level: offsetLevel,
  338. x: offsetX,
  339. y: offsetY,
  340. });
  341. };
  342. /**
  343. * Given the morton index of the child, compute the coordinates of the child.
  344. * This is a special case of {@link ImplicitTileCoordinates#getDescendantCoordinates}.
  345. *
  346. * @param {Number} childIndex The morton index of the child tile relative to its parent
  347. * @returns {ImplicitTileCoordinates} The tile coordinates of the child
  348. * @private
  349. */
  350. ImplicitTileCoordinates.prototype.getChildCoordinates = function (childIndex) {
  351. //>>includeStart('debug', pragmas.debug);
  352. Check.typeOf.number("childIndex", childIndex);
  353. const branchingFactor = ImplicitSubdivisionScheme.getBranchingFactor(
  354. this.subdivisionScheme
  355. );
  356. if (childIndex < 0 || branchingFactor <= childIndex) {
  357. throw new DeveloperError(
  358. `childIndex must be at least 0 and less than ${branchingFactor}`
  359. );
  360. }
  361. //>>includeEnd('debug');
  362. const level = this.level + 1;
  363. const x = 2 * this.x + (childIndex % 2);
  364. const y = 2 * this.y + (Math.floor(childIndex / 2) % 2);
  365. if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  366. const z = 2 * this.z + (Math.floor(childIndex / 4) % 2);
  367. return new ImplicitTileCoordinates({
  368. subdivisionScheme: this.subdivisionScheme,
  369. subtreeLevels: this.subtreeLevels,
  370. level: level,
  371. x: x,
  372. y: y,
  373. z: z,
  374. });
  375. }
  376. // Quadtree
  377. return new ImplicitTileCoordinates({
  378. subdivisionScheme: this.subdivisionScheme,
  379. subtreeLevels: this.subtreeLevels,
  380. level: level,
  381. x: x,
  382. y: y,
  383. });
  384. };
  385. /**
  386. * Get the coordinates of the subtree that contains this tile. If the tile is
  387. * the root of the subtree, the root of the subtree is returned.
  388. *
  389. * @returns {ImplicitTileCoordinates} The subtree that contains this tile
  390. * @private
  391. */
  392. ImplicitTileCoordinates.prototype.getSubtreeCoordinates = function () {
  393. return this.getAncestorCoordinates(this.level % this.subtreeLevels);
  394. };
  395. /**
  396. * Get the coordinates of the parent subtree that contains this tile
  397. *
  398. * @returns {ImplicitTileCoordinates} The parent subtree that contains this tile
  399. * @private
  400. */
  401. ImplicitTileCoordinates.prototype.getParentSubtreeCoordinates = function () {
  402. return this.getAncestorCoordinates(
  403. (this.level % this.subtreeLevels) + this.subtreeLevels
  404. );
  405. };
  406. /**
  407. * Returns whether this tile is an ancestor of another tile
  408. *
  409. * @param {ImplicitTileCoordinates} descendantCoordinates the descendant coordinates
  410. * @returns {Boolean} <code>true</code> if this tile is an ancestor of the other tile
  411. * @private
  412. */
  413. ImplicitTileCoordinates.prototype.isAncestor = function (
  414. descendantCoordinates
  415. ) {
  416. //>>includeStart('debug', pragmas.debug);
  417. Check.typeOf.object("descendantCoordinates", descendantCoordinates);
  418. checkMatchingSubtreeShape(this, descendantCoordinates);
  419. //>>includeEnd('debug');
  420. const levelDifference = descendantCoordinates.level - this.level;
  421. if (levelDifference <= 0) {
  422. return false;
  423. }
  424. const ancestorX = descendantCoordinates.x >> levelDifference;
  425. const ancestorY = descendantCoordinates.y >> levelDifference;
  426. const isAncestorX = this.x === ancestorX;
  427. const isAncestorY = this.y === ancestorY;
  428. if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  429. const ancestorZ = descendantCoordinates.z >> levelDifference;
  430. const isAncestorZ = this.z === ancestorZ;
  431. return isAncestorX && isAncestorY && isAncestorZ;
  432. }
  433. // Quadtree
  434. return isAncestorX && isAncestorY;
  435. };
  436. /**
  437. * Returns whether the provided coordinates are equal to this coordinate
  438. *
  439. * @param {ImplicitTileCoordinates} otherCoordinates the other coordinates
  440. * @returns {Boolean} <code>true</code> if the coordinates are equal
  441. * @private
  442. */
  443. ImplicitTileCoordinates.prototype.isEqual = function (otherCoordinates) {
  444. //>>includeStart('debug', pragmas.debug);
  445. Check.typeOf.object("otherCoordinates", otherCoordinates);
  446. //>>includeEnd('debug');
  447. return (
  448. this.subdivisionScheme === otherCoordinates.subdivisionScheme &&
  449. this.subtreeLevels === otherCoordinates.subtreeLevels &&
  450. this.level === otherCoordinates.level &&
  451. this.x === otherCoordinates.x &&
  452. this.y === otherCoordinates.y &&
  453. (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE
  454. ? this.z === otherCoordinates.z
  455. : true)
  456. );
  457. };
  458. /**
  459. * Returns whether this tile is the root of the implicit tileset
  460. *
  461. * @returns {Boolean} <code>true</code> if this tile is the root
  462. * @private
  463. */
  464. ImplicitTileCoordinates.prototype.isImplicitTilesetRoot = function () {
  465. return this.level === 0;
  466. };
  467. /**
  468. * Returns whether this tile is the root of the subtree
  469. *
  470. * @returns {Boolean} <code>true</code> if this tile is the root of the subtree
  471. * @private
  472. */
  473. ImplicitTileCoordinates.prototype.isSubtreeRoot = function () {
  474. return this.level % this.subtreeLevels === 0;
  475. };
  476. /**
  477. * Returns whether this tile is on the last row of tiles in the subtree
  478. *
  479. * @returns {Boolean} <code>true</code> if this tile is on the last row of tiles in the subtree
  480. * @private
  481. */
  482. ImplicitTileCoordinates.prototype.isBottomOfSubtree = function () {
  483. return this.level % this.subtreeLevels === this.subtreeLevels - 1;
  484. };
  485. /**
  486. * Get a dictionary of values for templating into an implicit template URI.
  487. *
  488. * @returns {Object} An object suitable for use with {@link Resource#getDerivedResource}
  489. * @private
  490. */
  491. ImplicitTileCoordinates.prototype.getTemplateValues = function () {
  492. const values = {
  493. level: this.level,
  494. x: this.x,
  495. y: this.y,
  496. };
  497. if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  498. values.z = this.z;
  499. }
  500. return values;
  501. };
  502. const scratchCoordinatesArray = [0, 0, 0];
  503. /**
  504. * Given a level number, morton index, and whether the tileset is an
  505. * octree/quadtree, compute the (level, x, y, [z]) coordinates
  506. *
  507. * @param {ImplicitSubdivisionScheme} subdivisionScheme Whether the coordinates are for a quadtree or octree
  508. * @param {Number} subtreeLevels The number of distinct levels within the coordinate's subtree
  509. * @param {Number} level The level of the tree
  510. * @param {Number} mortonIndex The morton index of the tile.
  511. * @returns {ImplicitTileCoordinates} The coordinates of the tile with the given Morton index
  512. * @private
  513. */
  514. ImplicitTileCoordinates.fromMortonIndex = function (
  515. subdivisionScheme,
  516. subtreeLevels,
  517. level,
  518. mortonIndex
  519. ) {
  520. let coordinatesArray;
  521. if (subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  522. coordinatesArray = MortonOrder.decode3D(
  523. mortonIndex,
  524. scratchCoordinatesArray
  525. );
  526. return new ImplicitTileCoordinates({
  527. subdivisionScheme: subdivisionScheme,
  528. subtreeLevels: subtreeLevels,
  529. level: level,
  530. x: coordinatesArray[0],
  531. y: coordinatesArray[1],
  532. z: coordinatesArray[2],
  533. });
  534. }
  535. coordinatesArray = MortonOrder.decode2D(mortonIndex, scratchCoordinatesArray);
  536. return new ImplicitTileCoordinates({
  537. subdivisionScheme: subdivisionScheme,
  538. subtreeLevels: subtreeLevels,
  539. level: level,
  540. x: coordinatesArray[0],
  541. y: coordinatesArray[1],
  542. });
  543. };
  544. /**
  545. * Given a tile index and whether the tileset is an octree/quadtree, compute
  546. * the (level, x, y, [z]) coordinates
  547. *
  548. * @param {ImplicitSubdivisionScheme} subdivisionScheme Whether the coordinates are for a quadtree or octree
  549. * @param {Number} subtreeLevels The number of distinct levels within the coordinate's subtree
  550. * @param {Number} tileIndex The tile's index
  551. * @returns {ImplicitTileCoordinates} The coordinates of the tile with the given tile index
  552. * @private
  553. */
  554. ImplicitTileCoordinates.fromTileIndex = function (
  555. subdivisionScheme,
  556. subtreeLevels,
  557. tileIndex
  558. ) {
  559. let level;
  560. let levelOffset;
  561. let mortonIndex;
  562. if (subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  563. // Node count up to octree level: (8^L - 1) / (8-1)
  564. // (8^L - 1) / (8-1) <= X < (8^(L+1) - 1) / (8-1)
  565. // 8^L <= (7x + 1) < 8^(L+1)
  566. // L <= log8(7x + 1) < L + 1
  567. // L = floor(log8(7x + 1))
  568. // L = floor(log2(7x + 1) / log2(8))
  569. // L = floor(log2(7x + 1) / 3)
  570. level = Math.floor(CesiumMath.log2(7 * tileIndex + 1) / 3);
  571. levelOffset = ((1 << (3 * level)) - 1) / 7;
  572. mortonIndex = tileIndex - levelOffset;
  573. } else {
  574. // Node count up to quadtree level: (4^L - 1) / (4-1)
  575. // (4^L - 1) / (4-1) <= X < (4^(L+1) - 1) / (4-1)
  576. // 4^L <= (3x + 1) < 4^(L+1)
  577. // L <= log4(3x + 1) < L + 1
  578. // L = floor(log4(3x + 1))
  579. // L = floor(log2(3x + 1) / log2(4))
  580. // L = floor(log2(3x + 1) / 2)
  581. level = Math.floor(CesiumMath.log2(3 * tileIndex + 1) / 2);
  582. levelOffset = ((1 << (2 * level)) - 1) / 3;
  583. mortonIndex = tileIndex - levelOffset;
  584. }
  585. return ImplicitTileCoordinates.fromMortonIndex(
  586. subdivisionScheme,
  587. subtreeLevels,
  588. level,
  589. mortonIndex
  590. );
  591. };