Implicit3DTileContent.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import Check from "../Core/Check.js";
  3. import clone from "../Core/clone.js";
  4. import combine from "../Core/combine.js";
  5. import defaultValue from "../Core/defaultValue.js";
  6. import defined from "../Core/defined.js";
  7. import deprecationWarning from "../Core/deprecationWarning.js";
  8. import destroyObject from "../Core/destroyObject.js";
  9. import DeveloperError from "../Core/DeveloperError.js";
  10. import CesiumMath from "../Core/Math.js";
  11. import HilbertOrder from "../Core/HilbertOrder.js";
  12. import Matrix3 from "../Core/Matrix3.js";
  13. import Rectangle from "../Core/Rectangle.js";
  14. import S2Cell from "../Core/S2Cell.js";
  15. import ImplicitSubtree from "./ImplicitSubtree.js";
  16. import hasExtension from "./hasExtension.js";
  17. import MetadataSemantic from "./MetadataSemantic.js";
  18. import parseBoundingVolumeSemantics from "./parseBoundingVolumeSemantics.js";
  19. /**
  20. * A specialized {@link Cesium3DTileContent} that lazily evaluates an implicit
  21. * tileset. It is somewhat similar in operation to a
  22. * {@link Tileset3DTileContent} in that once the content is constructed, it
  23. * updates the tileset tree with more tiles. However, unlike external tilesets,
  24. * child subtrees are represented as additional placeholder nodes with
  25. * Implicit3DTileContent.
  26. * <p>
  27. * Implements the {@link Cesium3DTileContent} interface.
  28. * </p>
  29. * This object is normally not instantiated directly, use {@link Implicit3DTileContent.fromSubtreeJson}.
  30. *
  31. * @alias Implicit3DTileContent
  32. * @constructor
  33. *
  34. * @param {Cesium3DTileset} tileset The tileset this content belongs to
  35. * @param {Cesium3DTile} tile The tile this content belongs to.
  36. * @param {Resource} resource The resource for the tileset
  37. * @param {object} [json] The JSON object containing the subtree. Mutually exclusive with arrayBuffer.
  38. * @param {ArrayBuffer} [arrayBuffer] The array buffer that stores the content payload. Mutually exclusive with json.
  39. * @param {number} [byteOffset=0] The offset into the array buffer, if one was provided
  40. *
  41. * @exception {DeveloperError} One of json and arrayBuffer must be defined.
  42. *
  43. * @private
  44. * @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.
  45. */
  46. function Implicit3DTileContent(tileset, tile, resource) {
  47. //>>includeStart('debug', pragmas.debug);
  48. Check.defined("tile.implicitTileset", tile.implicitTileset);
  49. Check.defined("tile.implicitCoordinates", tile.implicitCoordinates);
  50. //>>includeEnd('debug');
  51. const implicitTileset = tile.implicitTileset;
  52. const implicitCoordinates = tile.implicitCoordinates;
  53. this._implicitTileset = implicitTileset;
  54. this._implicitCoordinates = implicitCoordinates;
  55. this._implicitSubtree = undefined;
  56. this._tileset = tileset;
  57. this._tile = tile;
  58. this._resource = resource;
  59. this._metadata = undefined;
  60. this.featurePropertiesDirty = false;
  61. this._group = undefined;
  62. const templateValues = implicitCoordinates.getTemplateValues();
  63. const subtreeResource = implicitTileset.subtreeUriTemplate.getDerivedResource(
  64. {
  65. templateValues: templateValues,
  66. }
  67. );
  68. this._url = subtreeResource.getUrlComponent(true);
  69. this._ready = false;
  70. this._readyPromise = undefined;
  71. }
  72. Object.defineProperties(Implicit3DTileContent.prototype, {
  73. featuresLength: {
  74. get: function () {
  75. return 0;
  76. },
  77. },
  78. pointsLength: {
  79. get: function () {
  80. return 0;
  81. },
  82. },
  83. trianglesLength: {
  84. get: function () {
  85. return 0;
  86. },
  87. },
  88. geometryByteLength: {
  89. get: function () {
  90. return 0;
  91. },
  92. },
  93. texturesByteLength: {
  94. get: function () {
  95. return 0;
  96. },
  97. },
  98. batchTableByteLength: {
  99. get: function () {
  100. return 0;
  101. },
  102. },
  103. innerContents: {
  104. get: function () {
  105. return undefined;
  106. },
  107. },
  108. /**
  109. * Returns true when the tile's content is ready to render; otherwise false
  110. *
  111. * @memberof Implicit3DTileContent.prototype
  112. *
  113. * @type {boolean}
  114. * @readonly
  115. * @private
  116. */
  117. ready: {
  118. get: function () {
  119. return this._ready;
  120. },
  121. },
  122. /**
  123. * Gets the promise that will be resolved when the tile's content is ready to render.
  124. *
  125. * @memberof Implicit3DTileContent.prototype
  126. *
  127. * @type {Promise<Implicit3DTileContent>}
  128. * @readonly
  129. * @deprecated
  130. * @private
  131. */
  132. readyPromise: {
  133. get: function () {
  134. deprecationWarning(
  135. "Implicit3DTileContent.readyPromise",
  136. "Implicit3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Implicit3DTileContent.ready to return true instead."
  137. );
  138. return this._readyPromise;
  139. },
  140. },
  141. tileset: {
  142. get: function () {
  143. return this._tileset;
  144. },
  145. },
  146. tile: {
  147. get: function () {
  148. return this._tile;
  149. },
  150. },
  151. url: {
  152. get: function () {
  153. return this._url;
  154. },
  155. },
  156. /**
  157. * Part of the {@link Cesium3DTileContent} interface. <code>Implicit3DTileContent</code>
  158. * always returns <code>undefined</code>. Only transcoded tiles have content metadata.
  159. * @memberof Implicit3DTileContent.prototype
  160. * @private
  161. */
  162. metadata: {
  163. get: function () {
  164. return undefined;
  165. },
  166. set: function () {
  167. //>>includeStart('debug', pragmas.debug);
  168. throw new DeveloperError("Implicit3DTileContent cannot have metadata");
  169. //>>includeEnd('debug');
  170. },
  171. },
  172. batchTable: {
  173. get: function () {
  174. return undefined;
  175. },
  176. },
  177. group: {
  178. get: function () {
  179. return this._group;
  180. },
  181. set: function (value) {
  182. this._group = value;
  183. },
  184. },
  185. });
  186. /**
  187. * Initialize the implicit content by parsing the subtree resource and setting
  188. * up a promise chain to expand the immediate subtree.
  189. *
  190. * @param {Cesium3DTileset} tileset The tileset this content belongs to
  191. * @param {Cesium3DTile} tile The tile this content belongs to.
  192. * @param {Resource} resource The resource for the tileset
  193. * @param {object} [json] The JSON containing the subtree. Mutually exclusive with arrayBuffer.
  194. * @param {ArrayBuffer} [arrayBuffer] The ArrayBuffer containing a subtree binary. Mutually exclusive with json.
  195. * @param {number} [byteOffset=0] The byte offset into the arrayBuffer
  196. * @return {Promise<Implicit3DTileContent>}
  197. *
  198. * @exception {DeveloperError} One of json and arrayBuffer must be defined.
  199. *
  200. * @private
  201. */
  202. Implicit3DTileContent.fromSubtreeJson = async function (
  203. tileset,
  204. tile,
  205. resource,
  206. json,
  207. arrayBuffer,
  208. byteOffset
  209. ) {
  210. //>>includeStart('debug', pragmas.debug);
  211. Check.defined("tile.implicitTileset", tile.implicitTileset);
  212. Check.defined("tile.implicitCoordinates", tile.implicitCoordinates);
  213. if (defined(json) === defined(arrayBuffer)) {
  214. throw new DeveloperError("One of json and arrayBuffer must be defined.");
  215. }
  216. //>>includeEnd('debug');
  217. byteOffset = defaultValue(byteOffset, 0);
  218. let uint8Array;
  219. if (defined(arrayBuffer)) {
  220. uint8Array = new Uint8Array(arrayBuffer, byteOffset);
  221. }
  222. const implicitTileset = tile.implicitTileset;
  223. const implicitCoordinates = tile.implicitCoordinates;
  224. const subtree = await ImplicitSubtree.fromSubtreeJson(
  225. resource,
  226. json,
  227. uint8Array,
  228. implicitTileset,
  229. implicitCoordinates
  230. );
  231. const content = new Implicit3DTileContent(tileset, tile, resource);
  232. content._implicitSubtree = subtree;
  233. expandSubtree(content, subtree);
  234. content._ready = true;
  235. content._readyPromise = Promise.resolve(content);
  236. return content;
  237. };
  238. /**
  239. * Expand a single subtree placeholder tile. This transcodes the subtree into
  240. * a tree of {@link Cesium3DTile}. The root of this tree is stored in
  241. * the placeholder tile's children array. This method also creates placeholder
  242. * tiles for the child subtrees to be lazily expanded as needed.
  243. *
  244. * @param {Implicit3DTileContent} content The content
  245. * @param {ImplicitSubtree} subtree The parsed subtree
  246. * @private
  247. */
  248. function expandSubtree(content, subtree) {
  249. const placeholderTile = content._tile;
  250. // Parse the tiles inside this immediate subtree
  251. const childIndex = content._implicitCoordinates.childIndex;
  252. const results = transcodeSubtreeTiles(
  253. content,
  254. subtree,
  255. placeholderTile,
  256. childIndex
  257. );
  258. const statistics = content._tileset.statistics;
  259. // Link the new subtree to the existing placeholder tile.
  260. placeholderTile.children.push(results.rootTile);
  261. statistics.numberOfTilesTotal++;
  262. // for each child subtree, make new placeholder tiles
  263. const childSubtrees = listChildSubtrees(content, subtree, results.bottomRow);
  264. for (let i = 0; i < childSubtrees.length; i++) {
  265. const subtreeLocator = childSubtrees[i];
  266. const leafTile = subtreeLocator.tile;
  267. const implicitChildTile = makePlaceholderChildSubtree(
  268. content,
  269. leafTile,
  270. subtreeLocator.childIndex
  271. );
  272. leafTile.children.push(implicitChildTile);
  273. statistics.numberOfTilesTotal++;
  274. }
  275. }
  276. /**
  277. * A pair of (tile, childIndex) used for finding child subtrees.
  278. *
  279. * @typedef {object} ChildSubtreeLocator
  280. * @property {Cesium3DTile} tile One of the tiles in the bottommost row of the subtree.
  281. * @property {number} childIndex The morton index of the child tile relative to its parent
  282. * @private
  283. */
  284. /**
  285. * Determine what child subtrees exist and return a list of information
  286. *
  287. * @param {Implicit3DTileContent} content The implicit content
  288. * @param {ImplicitSubtree} subtree The subtree for looking up availability
  289. * @param {Array<Cesium3DTile|undefined>} bottomRow The bottom row of tiles in a transcoded subtree
  290. * @returns {ChildSubtreeLocator[]} A list of identifiers for the child subtrees.
  291. * @private
  292. */
  293. function listChildSubtrees(content, subtree, bottomRow) {
  294. const results = [];
  295. const branchingFactor = content._implicitTileset.branchingFactor;
  296. for (let i = 0; i < bottomRow.length; i++) {
  297. const leafTile = bottomRow[i];
  298. if (!defined(leafTile)) {
  299. continue;
  300. }
  301. for (let j = 0; j < branchingFactor; j++) {
  302. const index = i * branchingFactor + j;
  303. if (subtree.childSubtreeIsAvailableAtIndex(index)) {
  304. results.push({
  305. tile: leafTile,
  306. childIndex: j,
  307. });
  308. }
  309. }
  310. }
  311. return results;
  312. }
  313. /**
  314. * Results of transcodeSubtreeTiles, containing the root tile of the
  315. * subtree and the bottom row of nodes for further processing.
  316. *
  317. * @typedef {object} TranscodedSubtree
  318. * @property {Cesium3DTile} rootTile The transcoded root tile of the subtree
  319. * @property {Array<Cesium3DTile|undefined>} bottomRow The bottom row of transcoded tiles. This is helpful for processing child subtrees
  320. * @private
  321. */
  322. /**
  323. * Transcode the implicitly-defined tiles within this subtree and generate
  324. * explicit {@link Cesium3DTile} objects. This function only transcode tiles,
  325. * child subtrees are handled separately.
  326. *
  327. * @param {Implicit3DTileContent} content The implicit content
  328. * @param {ImplicitSubtree} subtree The subtree to get availability information
  329. * @param {Cesium3DTile} placeholderTile The placeholder tile, used for constructing the subtree root tile
  330. * @param {number} childIndex The Morton index of the root tile relative to parentOfRootTile
  331. * @returns {TranscodedSubtree} The newly created subtree of tiles
  332. * @private
  333. */
  334. function transcodeSubtreeTiles(content, subtree, placeholderTile, childIndex) {
  335. const rootBitIndex = 0;
  336. const rootParentIsPlaceholder = true;
  337. const rootTile = deriveChildTile(
  338. content,
  339. subtree,
  340. placeholderTile,
  341. childIndex,
  342. rootBitIndex,
  343. rootParentIsPlaceholder
  344. );
  345. const statistics = content._tileset.statistics;
  346. // Sliding window over the levels of the tree.
  347. // Each row is branchingFactor * length of previous row
  348. // Tiles within a row are ordered by Morton index.
  349. let parentRow = [rootTile];
  350. let currentRow = [];
  351. const implicitTileset = content._implicitTileset;
  352. for (let level = 1; level < implicitTileset.subtreeLevels; level++) {
  353. const levelOffset = subtree.getLevelOffset(level);
  354. const numberOfChildren = implicitTileset.branchingFactor * parentRow.length;
  355. for (
  356. let childMortonIndex = 0;
  357. childMortonIndex < numberOfChildren;
  358. childMortonIndex++
  359. ) {
  360. const childBitIndex = levelOffset + childMortonIndex;
  361. if (!subtree.tileIsAvailableAtIndex(childBitIndex)) {
  362. currentRow.push(undefined);
  363. continue;
  364. }
  365. const parentMortonIndex = subtree.getParentMortonIndex(childMortonIndex);
  366. const parentTile = parentRow[parentMortonIndex];
  367. const childChildIndex =
  368. childMortonIndex % implicitTileset.branchingFactor;
  369. const childTile = deriveChildTile(
  370. content,
  371. subtree,
  372. parentTile,
  373. childChildIndex,
  374. childBitIndex
  375. );
  376. parentTile.children.push(childTile);
  377. statistics.numberOfTilesTotal++;
  378. currentRow.push(childTile);
  379. }
  380. parentRow = currentRow;
  381. currentRow = [];
  382. }
  383. return {
  384. rootTile: rootTile,
  385. // At the end of the last loop, bottomRow was moved to parentRow
  386. bottomRow: parentRow,
  387. };
  388. }
  389. function getGeometricError(tileMetadata, implicitTileset, implicitCoordinates) {
  390. const semantic = MetadataSemantic.TILE_GEOMETRIC_ERROR;
  391. if (defined(tileMetadata) && tileMetadata.hasPropertyBySemantic(semantic)) {
  392. return tileMetadata.getPropertyBySemantic(semantic);
  393. }
  394. return (
  395. implicitTileset.geometricError / Math.pow(2, implicitCoordinates.level)
  396. );
  397. }
  398. /**
  399. * Given a parent tile and information about which child to create, derive
  400. * the properties of the child tile implicitly.
  401. * <p>
  402. * This creates a real tile for rendering, not a placeholder tile like some of
  403. * the other methods of ImplicitTileset.
  404. * </p>
  405. *
  406. * @param {Implicit3DTileContent} implicitContent The implicit content
  407. * @param {ImplicitSubtree} subtree The subtree the child tile belongs to
  408. * @param {Cesium3DTile} parentTile The parent of the new child tile
  409. * @param {number} childIndex The morton index of the child tile relative to its parent
  410. * @param {number} childBitIndex The index of the child tile within the tile's availability information.
  411. * @param {boolean} [parentIsPlaceholderTile=false] True if parentTile is a placeholder tile. This is true for the root of each subtree.
  412. * @returns {Cesium3DTile} The new child tile.
  413. * @private
  414. */
  415. function deriveChildTile(
  416. implicitContent,
  417. subtree,
  418. parentTile,
  419. childIndex,
  420. childBitIndex,
  421. parentIsPlaceholderTile
  422. ) {
  423. const implicitTileset = implicitContent._implicitTileset;
  424. let implicitCoordinates;
  425. if (defaultValue(parentIsPlaceholderTile, false)) {
  426. implicitCoordinates = parentTile.implicitCoordinates;
  427. } else {
  428. implicitCoordinates = parentTile.implicitCoordinates.getChildCoordinates(
  429. childIndex
  430. );
  431. }
  432. // Parse metadata and bounding volume semantics at the beginning
  433. // as the bounding volumes are needed below.
  434. let tileMetadata;
  435. let tileBounds;
  436. let contentBounds;
  437. if (defined(subtree.tilePropertyTableJson)) {
  438. tileMetadata = subtree.getTileMetadataView(implicitCoordinates);
  439. const boundingVolumeSemantics = parseBoundingVolumeSemantics(tileMetadata);
  440. tileBounds = boundingVolumeSemantics.tile;
  441. contentBounds = boundingVolumeSemantics.content;
  442. }
  443. // Content is not loaded at this point, so this flag is set for future reference.
  444. const contentPropertyTableJsons = subtree.contentPropertyTableJsons;
  445. const length = contentPropertyTableJsons.length;
  446. let hasImplicitContentMetadata = false;
  447. for (let i = 0; i < length; i++) {
  448. if (subtree.contentIsAvailableAtCoordinates(implicitCoordinates, i)) {
  449. hasImplicitContentMetadata = true;
  450. break;
  451. }
  452. }
  453. const boundingVolume = getTileBoundingVolume(
  454. implicitTileset,
  455. implicitCoordinates,
  456. childIndex,
  457. parentIsPlaceholderTile,
  458. parentTile,
  459. tileBounds
  460. );
  461. const contentJsons = [];
  462. for (let i = 0; i < implicitTileset.contentCount; i++) {
  463. if (!subtree.contentIsAvailableAtIndex(childBitIndex, i)) {
  464. continue;
  465. }
  466. const childContentTemplate = implicitTileset.contentUriTemplates[i];
  467. const childContentUri = childContentTemplate.getDerivedResource({
  468. templateValues: implicitCoordinates.getTemplateValues(),
  469. }).url;
  470. const contentJson = {
  471. uri: childContentUri,
  472. };
  473. const contentBoundingVolume = getContentBoundingVolume(
  474. boundingVolume,
  475. contentBounds
  476. );
  477. if (defined(contentBoundingVolume)) {
  478. contentJson.boundingVolume = contentBoundingVolume;
  479. }
  480. // combine() is used to pass through any additional properties the
  481. // user specified such as extras or extensions
  482. contentJsons.push(combine(contentJson, implicitTileset.contentHeaders[i]));
  483. }
  484. const childGeometricError = getGeometricError(
  485. tileMetadata,
  486. implicitTileset,
  487. implicitCoordinates
  488. );
  489. const tileJson = {
  490. boundingVolume: boundingVolume,
  491. geometricError: childGeometricError,
  492. refine: implicitTileset.refine,
  493. contents: contentJsons,
  494. };
  495. // combine() is used to pass through any additional properties the
  496. // user specified such as extras or extensions.
  497. const deep = true;
  498. const rootHeader = clone(implicitTileset.tileHeader, deep);
  499. delete rootHeader.boundingVolume;
  500. delete rootHeader.transform;
  501. const combinedTileJson = combine(tileJson, rootHeader, deep);
  502. const childTile = makeTile(
  503. implicitContent,
  504. implicitTileset.baseResource,
  505. combinedTileJson,
  506. parentTile
  507. );
  508. childTile.implicitCoordinates = implicitCoordinates;
  509. childTile.implicitSubtree = subtree;
  510. childTile.metadata = tileMetadata;
  511. childTile.hasImplicitContentMetadata = hasImplicitContentMetadata;
  512. return childTile;
  513. }
  514. /**
  515. * Checks whether the bounding volume's heights can be updated.
  516. * Returns true if the minimumHeight/maximumHeight parameter
  517. * is defined and the bounding volume is a region or S2 cell.
  518. *
  519. * @param {object} [boundingVolume] The bounding volume
  520. * @param {object} [tileBounds] The tile bounds
  521. * @param {number} [tileBounds.minimumHeight] The minimum height
  522. * @param {number} [tileBounds.maximumHeight] The maximum height
  523. * @returns {boolean} Whether the bounding volume's heights can be updated
  524. * @private
  525. */
  526. function canUpdateHeights(boundingVolume, tileBounds) {
  527. return (
  528. defined(boundingVolume) &&
  529. defined(tileBounds) &&
  530. (defined(tileBounds.minimumHeight) || defined(tileBounds.maximumHeight)) &&
  531. (hasExtension(boundingVolume, "3DTILES_bounding_volume_S2") ||
  532. defined(boundingVolume.region))
  533. );
  534. }
  535. /**
  536. * Update the minimum and maximum height of the bounding volume.
  537. * This is typically used to tighten a bounding volume using the
  538. * <code>TILE_MINIMUM_HEIGHT</code> and <code>TILE_MAXIMUM_HEIGHT</code>
  539. * semantics. Heights are only updated if the respective
  540. * minimumHeight/maximumHeight parameter is defined and the
  541. * bounding volume is a region or S2 cell.
  542. *
  543. * @param {object} boundingVolume The bounding volume
  544. * @param {object} [tileBounds] The tile bounds
  545. * @param {number} [tileBounds.minimumHeight] The new minimum height
  546. * @param {number} [tileBounds.maximumHeight] The new maximum height
  547. * @private
  548. */
  549. function updateHeights(boundingVolume, tileBounds) {
  550. if (!defined(tileBounds)) {
  551. return;
  552. }
  553. if (hasExtension(boundingVolume, "3DTILES_bounding_volume_S2")) {
  554. updateS2CellHeights(
  555. boundingVolume.extensions["3DTILES_bounding_volume_S2"],
  556. tileBounds.minimumHeight,
  557. tileBounds.maximumHeight
  558. );
  559. } else if (defined(boundingVolume.region)) {
  560. updateRegionHeights(
  561. boundingVolume.region,
  562. tileBounds.minimumHeight,
  563. tileBounds.maximumHeight
  564. );
  565. }
  566. }
  567. /**
  568. * For a bounding region, update the minimum and maximum height. This
  569. * is typically used to tighten a bounding volume using the
  570. * <code>TILE_MINIMUM_HEIGHT</code> and <code>TILE_MAXIMUM_HEIGHT</code>
  571. * semantics. Heights are only updated if the respective
  572. * minimumHeight/maximumHeight parameter is defined.
  573. *
  574. * @param {Array} region A 6-element array describing the bounding region
  575. * @param {number} [minimumHeight] The new minimum height
  576. * @param {number} [maximumHeight] The new maximum height
  577. * @private
  578. */
  579. function updateRegionHeights(region, minimumHeight, maximumHeight) {
  580. if (defined(minimumHeight)) {
  581. region[4] = minimumHeight;
  582. }
  583. if (defined(maximumHeight)) {
  584. region[5] = maximumHeight;
  585. }
  586. }
  587. /**
  588. * For a bounding S2 cell, update the minimum and maximum height. This
  589. * is typically used to tighten a bounding volume using the
  590. * <code>TILE_MINIMUM_HEIGHT</code> and <code>TILE_MAXIMUM_HEIGHT</code>
  591. * semantics. Heights are only updated if the respective
  592. * minimumHeight/maximumHeight parameter is defined.
  593. *
  594. * @param {object} s2CellVolume An object describing the S2 cell
  595. * @param {number} [minimumHeight] The new minimum height
  596. * @param {number} [maximumHeight] The new maximum height
  597. * @private
  598. */
  599. function updateS2CellHeights(s2CellVolume, minimumHeight, maximumHeight) {
  600. if (defined(minimumHeight)) {
  601. s2CellVolume.minimumHeight = minimumHeight;
  602. }
  603. if (defined(maximumHeight)) {
  604. s2CellVolume.maximumHeight = maximumHeight;
  605. }
  606. }
  607. /**
  608. * Gets the tile's bounding volume, which may be specified via
  609. * metadata semantics such as TILE_BOUNDING_BOX or implicitly
  610. * derived from the implicit root tile's bounding volume.
  611. * <p>
  612. * Priority of bounding volume types:
  613. * <ol>
  614. * <li>Explicit min/max height
  615. * <ol>
  616. * <li>With explicit region</li>
  617. * <li>With implicit S2</li>
  618. * <li>With implicit region</li>
  619. * </ol>
  620. * </li>
  621. * <li>Explicit box</li>
  622. * <li>Explicit region</li>
  623. * <li>Explicit sphere</li>
  624. * <li>Implicit S2</li>
  625. * <li>Implicit box</li>
  626. * <li>Implicit region</li>
  627. * </ol>
  628. * </p>
  629. *
  630. * @param {ImplicitTileset} implicitTileset The implicit tileset struct which holds the root bounding volume
  631. * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the child tile
  632. * @param {number} childIndex The morton index of the child tile relative to its parent
  633. * @param {boolean} parentIsPlaceholderTile True if parentTile is a placeholder tile. This is true for the root of each subtree.
  634. * @param {Cesium3DTile} parentTile The parent of the new child tile
  635. * @param {object} [tileBounds] The tile bounds
  636. * @returns {object} An object containing the JSON for a bounding volume
  637. * @private
  638. */
  639. function getTileBoundingVolume(
  640. implicitTileset,
  641. implicitCoordinates,
  642. childIndex,
  643. parentIsPlaceholderTile,
  644. parentTile,
  645. tileBounds
  646. ) {
  647. let boundingVolume;
  648. if (
  649. !defined(tileBounds) ||
  650. !defined(tileBounds.boundingVolume) ||
  651. (!canUpdateHeights(tileBounds.boundingVolume, tileBounds) &&
  652. canUpdateHeights(implicitTileset.boundingVolume, tileBounds))
  653. ) {
  654. boundingVolume = deriveBoundingVolume(
  655. implicitTileset,
  656. implicitCoordinates,
  657. childIndex,
  658. defaultValue(parentIsPlaceholderTile, false),
  659. parentTile
  660. );
  661. } else {
  662. boundingVolume = tileBounds.boundingVolume;
  663. }
  664. // The TILE_MINIMUM_HEIGHT and TILE_MAXIMUM_HEIGHT metadata semantics
  665. // can be used to tighten the bounding volume
  666. updateHeights(boundingVolume, tileBounds);
  667. return boundingVolume;
  668. }
  669. /**
  670. * Gets the content bounding volume, which may be specified via
  671. * metadata semantics such as CONTENT_BOUNDING_BOX.
  672. * <p>
  673. * Priority of bounding volume types:
  674. * <ol>
  675. * <li>Explicit min/max height
  676. * <ol>
  677. * <li>With explicit region</li>
  678. * <li>With tile bounding volume (S2 or region)</li>
  679. * </ol>
  680. * </li>
  681. * <li>Explicit box</li>
  682. * <li>Explicit region</li>
  683. * <li>Explicit sphere</li>
  684. * <li>Tile bounding volume (when content.boundingVolume is undefined)</li>
  685. * </ol>
  686. * </p>
  687. *
  688. * @param {object} tileBoundingVolume An object containing the JSON for the tile's bounding volume
  689. * @param {object} [contentBounds] The content bounds
  690. * @returns {object|undefined} An object containing the JSON for a bounding volume, or <code>undefined</code> if there is no bounding volume
  691. * @private
  692. */
  693. function getContentBoundingVolume(tileBoundingVolume, contentBounds) {
  694. // content bounding volumes can only be specified via
  695. // metadata semantics such as CONTENT_BOUNDING_BOX
  696. let contentBoundingVolume;
  697. if (defined(contentBounds)) {
  698. contentBoundingVolume = contentBounds.boundingVolume;
  699. }
  700. // The CONTENT_MINIMUM_HEIGHT and CONTENT_MAXIMUM_HEIGHT metadata semantics
  701. // can be used to tighten the bounding volume
  702. if (canUpdateHeights(contentBoundingVolume, contentBounds)) {
  703. updateHeights(contentBoundingVolume, contentBounds);
  704. } else if (canUpdateHeights(tileBoundingVolume, contentBounds)) {
  705. contentBoundingVolume = clone(tileBoundingVolume, true);
  706. updateHeights(contentBoundingVolume, contentBounds);
  707. }
  708. return contentBoundingVolume;
  709. }
  710. /**
  711. * Given the coordinates of a tile, derive its bounding volume from the root.
  712. *
  713. * @param {ImplicitTileset} implicitTileset The implicit tileset struct which holds the root bounding volume
  714. * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the child tile
  715. * @param {number} childIndex The morton index of the child tile relative to its parent
  716. * @param {boolean} parentIsPlaceholderTile True if parentTile is a placeholder tile. This is true for the root of each subtree.
  717. * @param {Cesium3DTile} parentTile The parent of the new child tile
  718. * @returns {object} An object containing the JSON for a bounding volume
  719. * @private
  720. */
  721. function deriveBoundingVolume(
  722. implicitTileset,
  723. implicitCoordinates,
  724. childIndex,
  725. parentIsPlaceholderTile,
  726. parentTile
  727. ) {
  728. const rootBoundingVolume = implicitTileset.boundingVolume;
  729. if (hasExtension(rootBoundingVolume, "3DTILES_bounding_volume_S2")) {
  730. return deriveBoundingVolumeS2(
  731. parentIsPlaceholderTile,
  732. parentTile,
  733. childIndex,
  734. implicitCoordinates.level,
  735. implicitCoordinates.x,
  736. implicitCoordinates.y,
  737. implicitCoordinates.z
  738. );
  739. }
  740. if (defined(rootBoundingVolume.region)) {
  741. const childRegion = deriveBoundingRegion(
  742. rootBoundingVolume.region,
  743. implicitCoordinates.level,
  744. implicitCoordinates.x,
  745. implicitCoordinates.y,
  746. implicitCoordinates.z
  747. );
  748. return {
  749. region: childRegion,
  750. };
  751. }
  752. const childBox = deriveBoundingBox(
  753. rootBoundingVolume.box,
  754. implicitCoordinates.level,
  755. implicitCoordinates.x,
  756. implicitCoordinates.y,
  757. implicitCoordinates.z
  758. );
  759. return {
  760. box: childBox,
  761. };
  762. }
  763. /**
  764. * Derive a bounding volume for a descendant tile (child, grandchild, etc.),
  765. * assuming a quadtree or octree implicit tiling scheme. The (level, x, y, [z])
  766. * coordinates are given to select the descendant tile and compute its position
  767. * and dimensions.
  768. * <p>
  769. * If z is present, octree subdivision is used. Otherwise, quadtree subdivision
  770. * is used. Quadtrees are always divided at the midpoint of the the horizontal
  771. * dimensions, i.e. (x, y), leaving the z axis unchanged.
  772. * </p>
  773. *
  774. * @param {boolean} parentIsPlaceholderTile True if parentTile is a placeholder tile. This is true for the root of each subtree.
  775. * @param {Cesium3DTile} parentTile The parent of the new child tile
  776. * @param {number} childIndex The morton index of the child tile relative to its parent
  777. * @param {number} level The level of the descendant tile relative to the root implicit tile
  778. * @param {number} x The x coordinate of the descendant tile
  779. * @param {number} y The y coordinate of the descendant tile
  780. * @param {number} [z] The z coordinate of the descendant tile (octree only)
  781. * @returns {object} An object with the 3DTILES_bounding_volume_S2 extension.
  782. * @private
  783. */
  784. function deriveBoundingVolumeS2(
  785. parentIsPlaceholderTile,
  786. parentTile,
  787. childIndex,
  788. level,
  789. x,
  790. y,
  791. z
  792. ) {
  793. //>>includeStart('debug', pragmas.debug);
  794. Check.typeOf.bool("parentIsPlaceholderTile", parentIsPlaceholderTile);
  795. Check.typeOf.object("parentTile", parentTile);
  796. Check.typeOf.number("childIndex", childIndex);
  797. Check.typeOf.number("level", level);
  798. Check.typeOf.number("x", x);
  799. Check.typeOf.number("y", y);
  800. if (defined(z)) {
  801. Check.typeOf.number("z", z);
  802. }
  803. //>>includeEnd('debug');
  804. const boundingVolumeS2 = parentTile._boundingVolume;
  805. // Handle the placeholder tile case, where we just duplicate the placeholder's bounding volume.
  806. if (parentIsPlaceholderTile) {
  807. return {
  808. extensions: {
  809. "3DTILES_bounding_volume_S2": {
  810. token: S2Cell.getTokenFromId(boundingVolumeS2.s2Cell._cellId),
  811. minimumHeight: boundingVolumeS2.minimumHeight,
  812. maximumHeight: boundingVolumeS2.maximumHeight,
  813. },
  814. },
  815. };
  816. }
  817. // Extract the first 3 face bits from the 64-bit S2 cell ID.
  818. // eslint-disable-next-line no-undef
  819. const face = Number(parentTile._boundingVolume.s2Cell._cellId >> BigInt(61));
  820. // The Hilbert curve is rotated for the "odd" faces on the S2 Earthcube.
  821. // See http://s2geometry.io/devguide/img/s2cell_global.jpg
  822. const position =
  823. face % 2 === 0
  824. ? HilbertOrder.encode2D(level, x, y)
  825. : HilbertOrder.encode2D(level, y, x);
  826. // eslint-disable-next-line no-undef
  827. const cell = S2Cell.fromFacePositionLevel(face, BigInt(position), level);
  828. let minHeight, maxHeight;
  829. if (defined(z)) {
  830. const midpointHeight =
  831. (boundingVolumeS2.maximumHeight + boundingVolumeS2.minimumHeight) / 2;
  832. minHeight =
  833. childIndex < 4 ? boundingVolumeS2.minimumHeight : midpointHeight;
  834. maxHeight =
  835. childIndex < 4 ? midpointHeight : boundingVolumeS2.maximumHeight;
  836. } else {
  837. minHeight = boundingVolumeS2.minimumHeight;
  838. maxHeight = boundingVolumeS2.maximumHeight;
  839. }
  840. return {
  841. extensions: {
  842. "3DTILES_bounding_volume_S2": {
  843. token: S2Cell.getTokenFromId(cell._cellId),
  844. minimumHeight: minHeight,
  845. maximumHeight: maxHeight,
  846. },
  847. },
  848. };
  849. }
  850. const scratchScaleFactors = new Cartesian3();
  851. const scratchRootCenter = new Cartesian3();
  852. const scratchCenter = new Cartesian3();
  853. const scratchHalfAxes = new Matrix3();
  854. /**
  855. * Derive a bounding volume for a descendant tile (child, grandchild, etc.),
  856. * assuming a quadtree or octree implicit tiling scheme. The (level, x, y, [z])
  857. * coordinates are given to select the descendant tile and compute its position
  858. * and dimensions.
  859. * <p>
  860. * If z is present, octree subdivision is used. Otherwise, quadtree subdivision
  861. * is used. Quadtrees are always divided at the midpoint of the the horizontal
  862. * dimensions, i.e. (x, y), leaving the z axis unchanged.
  863. * </p>
  864. * <p>
  865. * This computes the child volume directly from the root bounding volume rather
  866. * than recursively subdividing to minimize floating point error.
  867. * </p>
  868. *
  869. * @param {number[]} rootBox An array of 12 numbers representing the bounding box of the root tile
  870. * @param {number} level The level of the descendant tile relative to the root implicit tile
  871. * @param {number} x The x coordinate of the descendant tile
  872. * @param {number} y The y coordinate of the descendant tile
  873. * @param {number} [z] The z coordinate of the descendant tile (octree only)
  874. * @returns {number[]} An array of 12 numbers representing the bounding box of the descendant tile.
  875. * @private
  876. */
  877. function deriveBoundingBox(rootBox, level, x, y, z) {
  878. //>>includeStart('debug', pragmas.debug);
  879. Check.typeOf.object("rootBox", rootBox);
  880. Check.typeOf.number("level", level);
  881. Check.typeOf.number("x", x);
  882. Check.typeOf.number("y", y);
  883. if (defined(z)) {
  884. Check.typeOf.number("z", z);
  885. }
  886. //>>includeEnd('debug');
  887. if (level === 0) {
  888. return rootBox;
  889. }
  890. const rootCenter = Cartesian3.unpack(rootBox, 0, scratchRootCenter);
  891. const rootHalfAxes = Matrix3.unpack(rootBox, 3, scratchHalfAxes);
  892. const tileScale = Math.pow(2, -level);
  893. const modelSpaceX = -1 + (2 * x + 1) * tileScale;
  894. const modelSpaceY = -1 + (2 * y + 1) * tileScale;
  895. let modelSpaceZ = 0;
  896. const scaleFactors = Cartesian3.fromElements(
  897. tileScale,
  898. tileScale,
  899. 1,
  900. scratchScaleFactors
  901. );
  902. if (defined(z)) {
  903. modelSpaceZ = -1 + (2 * z + 1) * tileScale;
  904. scaleFactors.z = tileScale;
  905. }
  906. let center = Cartesian3.fromElements(
  907. modelSpaceX,
  908. modelSpaceY,
  909. modelSpaceZ,
  910. scratchCenter
  911. );
  912. center = Matrix3.multiplyByVector(rootHalfAxes, center, scratchCenter);
  913. center = Cartesian3.add(center, rootCenter, scratchCenter);
  914. let halfAxes = Matrix3.clone(rootHalfAxes);
  915. halfAxes = Matrix3.multiplyByScale(halfAxes, scaleFactors, halfAxes);
  916. const childBox = new Array(12);
  917. Cartesian3.pack(center, childBox);
  918. Matrix3.pack(halfAxes, childBox, 3);
  919. return childBox;
  920. }
  921. const scratchRectangle = new Rectangle();
  922. /**
  923. * Derive a bounding volume for a descendant tile (child, grandchild, etc.),
  924. * assuming a quadtree or octree implicit tiling scheme. The (level, x, y, [z])
  925. * coordinates are given to select the descendant tile and compute its position
  926. * and dimensions.
  927. * <p>
  928. * If z is present, octree subdivision is used. Otherwise, quadtree subdivision
  929. * is used. Quadtrees are always divided at the midpoint of the the horizontal
  930. * dimensions, i.e. (mid_longitude, mid_latitude), leaving the height values
  931. * unchanged.
  932. * </p>
  933. * <p>
  934. * This computes the child volume directly from the root bounding volume rather
  935. * than recursively subdividing to minimize floating point error.
  936. * </p>
  937. * @param {number[]} rootRegion An array of 6 numbers representing the root implicit tile
  938. * @param {number} level The level of the descendant tile relative to the root implicit tile
  939. * @param {number} x The x coordinate of the descendant tile
  940. * @param {number} y The x coordinate of the descendant tile
  941. * @param {number} [z] The z coordinate of the descendant tile (octree only)
  942. * @returns {number[]} An array of 6 numbers representing the bounding region of the descendant tile
  943. * @private
  944. */
  945. function deriveBoundingRegion(rootRegion, level, x, y, z) {
  946. //>>includeStart('debug', pragmas.debug);
  947. Check.typeOf.object("rootRegion", rootRegion);
  948. Check.typeOf.number("level", level);
  949. Check.typeOf.number("x", x);
  950. Check.typeOf.number("y", y);
  951. if (defined(z)) {
  952. Check.typeOf.number("z", z);
  953. }
  954. //>>includeEnd('debug');
  955. if (level === 0) {
  956. return rootRegion.slice();
  957. }
  958. const rectangle = Rectangle.unpack(rootRegion, 0, scratchRectangle);
  959. const rootMinimumHeight = rootRegion[4];
  960. const rootMaximumHeight = rootRegion[5];
  961. const tileScale = Math.pow(2, -level);
  962. const childWidth = tileScale * rectangle.width;
  963. const west = CesiumMath.negativePiToPi(rectangle.west + x * childWidth);
  964. const east = CesiumMath.negativePiToPi(west + childWidth);
  965. const childHeight = tileScale * rectangle.height;
  966. const south = CesiumMath.negativePiToPi(rectangle.south + y * childHeight);
  967. const north = CesiumMath.negativePiToPi(south + childHeight);
  968. // Height is only subdivided for octrees; It remains constant for quadtrees.
  969. let minimumHeight = rootMinimumHeight;
  970. let maximumHeight = rootMaximumHeight;
  971. if (defined(z)) {
  972. const childThickness = tileScale * (rootMaximumHeight - rootMinimumHeight);
  973. minimumHeight += z * childThickness;
  974. maximumHeight = minimumHeight + childThickness;
  975. }
  976. return [west, south, east, north, minimumHeight, maximumHeight];
  977. }
  978. /**
  979. * Create a placeholder 3D Tile whose content will be an Implicit3DTileContent
  980. * for lazy evaluation of a child subtree.
  981. *
  982. * @param {Implicit3DTileContent} content The content object.
  983. * @param {Cesium3DTile} parentTile The parent of the new child subtree.
  984. * @param {number} childIndex The morton index of the child tile relative to its parent
  985. * @returns {Cesium3DTile} The new placeholder tile
  986. * @private
  987. */
  988. function makePlaceholderChildSubtree(content, parentTile, childIndex) {
  989. const implicitTileset = content._implicitTileset;
  990. const implicitCoordinates = parentTile.implicitCoordinates.getChildCoordinates(
  991. childIndex
  992. );
  993. const childBoundingVolume = deriveBoundingVolume(
  994. implicitTileset,
  995. implicitCoordinates,
  996. childIndex,
  997. false,
  998. parentTile
  999. );
  1000. // Ignore tile metadata when computing geometric error for the placeholder tile
  1001. // since the child subtree's metadata hasn't been loaded yet.
  1002. // The actual geometric error will be computed in deriveChildTile.
  1003. const childGeometricError = getGeometricError(
  1004. undefined,
  1005. implicitTileset,
  1006. implicitCoordinates
  1007. );
  1008. const childContentUri = implicitTileset.subtreeUriTemplate.getDerivedResource(
  1009. {
  1010. templateValues: implicitCoordinates.getTemplateValues(),
  1011. }
  1012. ).url;
  1013. const tileJson = {
  1014. boundingVolume: childBoundingVolume,
  1015. geometricError: childGeometricError,
  1016. refine: implicitTileset.refine,
  1017. contents: [
  1018. {
  1019. uri: childContentUri,
  1020. },
  1021. ],
  1022. };
  1023. const tile = makeTile(
  1024. content,
  1025. implicitTileset.baseResource,
  1026. tileJson,
  1027. parentTile
  1028. );
  1029. tile.implicitTileset = implicitTileset;
  1030. tile.implicitCoordinates = implicitCoordinates;
  1031. return tile;
  1032. }
  1033. /**
  1034. * Make a {@link Cesium3DTile}. This uses the content's tile's constructor instead
  1035. * of importing Cesium3DTile. This is to avoid a circular dependency between
  1036. * this file and Cesium3DTile.js
  1037. * @param {Implicit3DTileContent} content The implicit content
  1038. * @param {Resource} baseResource The base resource for the tileset
  1039. * @param {object} tileJson The JSON header for the tile
  1040. * @param {Cesium3DTile} parentTile The parent of the new tile
  1041. * @returns {Cesium3DTile} The newly created tile.
  1042. * @private
  1043. */
  1044. function makeTile(content, baseResource, tileJson, parentTile) {
  1045. const Cesium3DTile = content._tile.constructor;
  1046. return new Cesium3DTile(content._tileset, baseResource, tileJson, parentTile);
  1047. }
  1048. /**
  1049. * Part of the {@link Cesium3DTileContent} interface. <code>Implicit3DTileContent</code>
  1050. * always returns <code>false</code> since a tile of this type does not have any features.
  1051. * @private
  1052. */
  1053. Implicit3DTileContent.prototype.hasProperty = function (batchId, name) {
  1054. return false;
  1055. };
  1056. /**
  1057. * Part of the {@link Cesium3DTileContent} interface. <code>Implicit3DTileContent</code>
  1058. * always returns <code>undefined</code> since a tile of this type does not have any features.
  1059. * @private
  1060. */
  1061. Implicit3DTileContent.prototype.getFeature = function (batchId) {
  1062. return undefined;
  1063. };
  1064. Implicit3DTileContent.prototype.applyDebugSettings = function (
  1065. enabled,
  1066. color
  1067. ) {};
  1068. Implicit3DTileContent.prototype.applyStyle = function (style) {};
  1069. Implicit3DTileContent.prototype.update = function (tileset, frameState) {};
  1070. Implicit3DTileContent.prototype.isDestroyed = function () {
  1071. return false;
  1072. };
  1073. Implicit3DTileContent.prototype.destroy = function () {
  1074. this._implicitSubtree =
  1075. this._implicitSubtree && this._implicitSubtree.destroy();
  1076. return destroyObject(this);
  1077. };
  1078. // Exposed for testing
  1079. Implicit3DTileContent._deriveBoundingBox = deriveBoundingBox;
  1080. Implicit3DTileContent._deriveBoundingRegion = deriveBoundingRegion;
  1081. Implicit3DTileContent._deriveBoundingVolumeS2 = deriveBoundingVolumeS2;
  1082. export default Implicit3DTileContent;