Implicit3DTileContent.js 36 KB

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