| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151 | import Cartesian3 from "../Core/Cartesian3.js";import Check from "../Core/Check.js";import clone from "../Core/clone.js";import combine from "../Core/combine.js";import defaultValue from "../Core/defaultValue.js";import defer from "../Core/defer.js";import defined from "../Core/defined.js";import destroyObject from "../Core/destroyObject.js";import DeveloperError from "../Core/DeveloperError.js";import CesiumMath from "../Core/Math.js";import HilbertOrder from "../Core/HilbertOrder.js";import Matrix3 from "../Core/Matrix3.js";import Rectangle from "../Core/Rectangle.js";import S2Cell from "../Core/S2Cell.js";import ImplicitSubtree from "./ImplicitSubtree.js";import hasExtension from "./hasExtension.js";import MetadataSemantic from "./MetadataSemantic.js";import parseBoundingVolumeSemantics from "./parseBoundingVolumeSemantics.js";/** * A specialized {@link Cesium3DTileContent} that lazily evaluates an implicit * tileset. It is somewhat similar in operation to a * {@link Tileset3DTileContent} in that once the content is constructed, it * updates the tileset tree with more tiles. However, unlike external tilesets, * child subtrees are represented as additional placeholder nodes with * Implicit3DTileContent. * <p> * Implements the {@link Cesium3DTileContent} interface. * </p> * * @alias Implicit3DTileContent * @constructor * * @param {Cesium3DTileset} tileset The tileset this content belongs to * @param {Cesium3DTile} tile The tile this content belongs to. * @param {Resource} resource The resource for the tileset * @param {Object} [json] The JSON object containing the subtree. Mutually exclusive with arrayBuffer. * @param {ArrayBuffer} [arrayBuffer] The array buffer that stores the content payload. Mutually exclusive with json. * @param {Number} [byteOffset=0] The offset into the array buffer, if one was provided * * @exception {DeveloperError} One of json and arrayBuffer must be defined. * * @private * @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. */export default function Implicit3DTileContent(  tileset,  tile,  resource,  json,  arrayBuffer,  byteOffset) {  //>>includeStart('debug', pragmas.debug);  Check.defined("tile.implicitTileset", tile.implicitTileset);  Check.defined("tile.implicitCoordinates", tile.implicitCoordinates);  if (defined(json) === defined(arrayBuffer)) {    throw new DeveloperError("One of json and arrayBuffer must be defined.");  }  //>>includeEnd('debug');  const implicitTileset = tile.implicitTileset;  const implicitCoordinates = tile.implicitCoordinates;  this._implicitTileset = implicitTileset;  this._implicitCoordinates = implicitCoordinates;  this._implicitSubtree = undefined;  this._tileset = tileset;  this._tile = tile;  this._resource = resource;  this._readyPromise = defer();  this._metadata = undefined;  this.featurePropertiesDirty = false;  this._group = undefined;  const templateValues = implicitCoordinates.getTemplateValues();  const subtreeResource = implicitTileset.subtreeUriTemplate.getDerivedResource(    {      templateValues: templateValues,    }  );  this._url = subtreeResource.getUrlComponent(true);  initialize(this, json, arrayBuffer, byteOffset);}Object.defineProperties(Implicit3DTileContent.prototype, {  featuresLength: {    get: function () {      return 0;    },  },  pointsLength: {    get: function () {      return 0;    },  },  trianglesLength: {    get: function () {      return 0;    },  },  geometryByteLength: {    get: function () {      return 0;    },  },  texturesByteLength: {    get: function () {      return 0;    },  },  batchTableByteLength: {    get: function () {      return 0;    },  },  innerContents: {    get: function () {      return undefined;    },  },  readyPromise: {    get: function () {      return this._readyPromise.promise;    },  },  tileset: {    get: function () {      return this._tileset;    },  },  tile: {    get: function () {      return this._tile;    },  },  url: {    get: function () {      return this._url;    },  },  /**   * Part of the {@link Cesium3DTileContent} interface. <code>Implicit3DTileContent</code>   * always returns <code>undefined</code>. Only transcoded tiles have content metadata.   * @memberof Implicit3DTileContent.prototype   * @private   */  metadata: {    get: function () {      return undefined;    },    set: function () {      //>>includeStart('debug', pragmas.debug);      throw new DeveloperError("Implicit3DTileContent cannot have metadata");      //>>includeEnd('debug');    },  },  batchTable: {    get: function () {      return undefined;    },  },  group: {    get: function () {      return this._group;    },    set: function (value) {      this._group = value;    },  },});/** * Initialize the implicit content by parsing the subtree resource and setting * up a promise chain to expand the immediate subtree. * * @param {Implicit3DTileContent} content The implicit content * @param {Object} [json] The JSON containing the subtree. Mutually exclusive with arrayBuffer. * @param {ArrayBuffer} [arrayBuffer] The ArrayBuffer containing a subtree binary. Mutually exclusive with json. * @param {Number} [byteOffset=0] The byte offset into the arrayBuffer * @private */function initialize(content, json, arrayBuffer, byteOffset) {  byteOffset = defaultValue(byteOffset, 0);  let uint8Array;  if (defined(arrayBuffer)) {    uint8Array = new Uint8Array(arrayBuffer, byteOffset);  }  const subtree = new ImplicitSubtree(    content._resource,    json,    uint8Array,    content._implicitTileset,    content._implicitCoordinates  );  content._implicitSubtree = subtree;  subtree.readyPromise    .then(function () {      expandSubtree(content, subtree);      content._readyPromise.resolve();    })    .catch(function (error) {      content._readyPromise.reject(error);    });}/** * Expand a single subtree placeholder tile. This transcodes the subtree into * a tree of {@link Cesium3DTile}. The root of this tree is stored in * the placeholder tile's children array. This method also creates placeholder * tiles for the child subtrees to be lazily expanded as needed. * * @param {Implicit3DTileContent} content The content * @param {ImplicitSubtree} subtree The parsed subtree * @private */function expandSubtree(content, subtree) {  const placeholderTile = content._tile;  // Parse the tiles inside this immediate subtree  const childIndex = content._implicitCoordinates.childIndex;  const results = transcodeSubtreeTiles(    content,    subtree,    placeholderTile,    childIndex  );  // Link the new subtree to the existing placeholder tile.  placeholderTile.children.push(results.rootTile);  // for each child subtree, make new placeholder tiles  const childSubtrees = listChildSubtrees(content, subtree, results.bottomRow);  for (let i = 0; i < childSubtrees.length; i++) {    const subtreeLocator = childSubtrees[i];    const leafTile = subtreeLocator.tile;    const implicitChildTile = makePlaceholderChildSubtree(      content,      leafTile,      subtreeLocator.childIndex    );    leafTile.children.push(implicitChildTile);  }}/** * A pair of (tile, childIndex) used for finding child subtrees. * * @typedef {Object} ChildSubtreeLocator * @property {Cesium3DTile} tile One of the tiles in the bottommost row of the subtree. * @property {Number} childIndex The morton index of the child tile relative to its parent * @private *//** * Determine what child subtrees exist and return a list of information * * @param {Implicit3DTileContent} content The implicit content * @param {ImplicitSubtree} subtree The subtree for looking up availability * @param {Array<Cesium3DTile|undefined>} bottomRow The bottom row of tiles in a transcoded subtree * @returns {ChildSubtreeLocator[]} A list of identifiers for the child subtrees. * @private */function listChildSubtrees(content, subtree, bottomRow) {  const results = [];  const branchingFactor = content._implicitTileset.branchingFactor;  for (let i = 0; i < bottomRow.length; i++) {    const leafTile = bottomRow[i];    if (!defined(leafTile)) {      continue;    }    for (let j = 0; j < branchingFactor; j++) {      const index = i * branchingFactor + j;      if (subtree.childSubtreeIsAvailableAtIndex(index)) {        results.push({          tile: leafTile,          childIndex: j,        });      }    }  }  return results;}/** * Results of transcodeSubtreeTiles, containing the root tile of the * subtree and the bottom row of nodes for further processing. * * @typedef {Object} TranscodedSubtree * @property {Cesium3DTile} rootTile The transcoded root tile of the subtree * @property {Array<Cesium3DTile|undefined>} bottomRow The bottom row of transcoded tiles. This is helpful for processing child subtrees * @private *//** * Transcode the implicitly-defined tiles within this subtree and generate * explicit {@link Cesium3DTile} objects. This function only transcode tiles, * child subtrees are handled separately. * * @param {Implicit3DTileContent} content The implicit content * @param {ImplicitSubtree} subtree The subtree to get availability information * @param {Cesium3DTile} placeholderTile The placeholder tile, used for constructing the subtree root tile * @param {Number} childIndex The Morton index of the root tile relative to parentOfRootTile * @returns {TranscodedSubtree} The newly created subtree of tiles * @private */function transcodeSubtreeTiles(content, subtree, placeholderTile, childIndex) {  const rootBitIndex = 0;  const rootParentIsPlaceholder = true;  const rootTile = deriveChildTile(    content,    subtree,    placeholderTile,    childIndex,    rootBitIndex,    rootParentIsPlaceholder  );  // Sliding window over the levels of the tree.  // Each row is branchingFactor * length of previous row  // Tiles within a row are ordered by Morton index.  let parentRow = [rootTile];  let currentRow = [];  const implicitTileset = content._implicitTileset;  for (let level = 1; level < implicitTileset.subtreeLevels; level++) {    const levelOffset = subtree.getLevelOffset(level);    const numberOfChildren = implicitTileset.branchingFactor * parentRow.length;    for (      let childMortonIndex = 0;      childMortonIndex < numberOfChildren;      childMortonIndex++    ) {      const childBitIndex = levelOffset + childMortonIndex;      if (!subtree.tileIsAvailableAtIndex(childBitIndex)) {        currentRow.push(undefined);        continue;      }      const parentMortonIndex = subtree.getParentMortonIndex(childMortonIndex);      const parentTile = parentRow[parentMortonIndex];      const childChildIndex =        childMortonIndex % implicitTileset.branchingFactor;      const childTile = deriveChildTile(        content,        subtree,        parentTile,        childChildIndex,        childBitIndex      );      parentTile.children.push(childTile);      currentRow.push(childTile);    }    parentRow = currentRow;    currentRow = [];  }  return {    rootTile: rootTile,    // At the end of the last loop, bottomRow was moved to parentRow    bottomRow: parentRow,  };}function getGeometricError(tileMetadata, implicitTileset, implicitCoordinates) {  const semantic = MetadataSemantic.TILE_GEOMETRIC_ERROR;  if (defined(tileMetadata) && tileMetadata.hasPropertyBySemantic(semantic)) {    return tileMetadata.getPropertyBySemantic(semantic);  }  return (    implicitTileset.geometricError / Math.pow(2, implicitCoordinates.level)  );}/** * Given a parent tile and information about which child to create, derive * the properties of the child tile implicitly. * <p> * This creates a real tile for rendering, not a placeholder tile like some of * the other methods of ImplicitTileset. * </p> * * @param {Implicit3DTileContent} implicitContent The implicit content * @param {ImplicitSubtree} subtree The subtree the child tile belongs to * @param {Cesium3DTile} parentTile The parent of the new child tile * @param {Number} childIndex The morton index of the child tile relative to its parent * @param {Number} childBitIndex The index of the child tile within the tile's availability information. * @param {Boolean} [parentIsPlaceholderTile=false] True if parentTile is a placeholder tile. This is true for the root of each subtree. * @returns {Cesium3DTile} The new child tile. * @private */function deriveChildTile(  implicitContent,  subtree,  parentTile,  childIndex,  childBitIndex,  parentIsPlaceholderTile) {  const implicitTileset = implicitContent._implicitTileset;  let implicitCoordinates;  if (defaultValue(parentIsPlaceholderTile, false)) {    implicitCoordinates = parentTile.implicitCoordinates;  } else {    implicitCoordinates = parentTile.implicitCoordinates.getChildCoordinates(      childIndex    );  }  // Parse metadata and bounding volume semantics at the beginning  // as the bounding volumes are needed below.  let tileMetadata;  let tileBounds;  let contentBounds;  if (defined(subtree.tilePropertyTableJson)) {    tileMetadata = subtree.getTileMetadataView(implicitCoordinates);    const boundingVolumeSemantics = parseBoundingVolumeSemantics(tileMetadata);    tileBounds = boundingVolumeSemantics.tile;    contentBounds = boundingVolumeSemantics.content;  }  // Content is not loaded at this point, so this flag is set for future reference.  const contentPropertyTableJsons = subtree.contentPropertyTableJsons;  const length = contentPropertyTableJsons.length;  let hasImplicitContentMetadata = false;  for (let i = 0; i < length; i++) {    if (subtree.contentIsAvailableAtCoordinates(implicitCoordinates, i)) {      hasImplicitContentMetadata = true;      break;    }  }  const boundingVolume = getTileBoundingVolume(    implicitTileset,    implicitCoordinates,    childIndex,    parentIsPlaceholderTile,    parentTile,    tileBounds  );  const contentJsons = [];  for (let i = 0; i < implicitTileset.contentCount; i++) {    if (!subtree.contentIsAvailableAtIndex(childBitIndex, i)) {      continue;    }    const childContentTemplate = implicitTileset.contentUriTemplates[i];    const childContentUri = childContentTemplate.getDerivedResource({      templateValues: implicitCoordinates.getTemplateValues(),    }).url;    const contentJson = {      uri: childContentUri,    };    const contentBoundingVolume = getContentBoundingVolume(      boundingVolume,      contentBounds    );    if (defined(contentBoundingVolume)) {      contentJson.boundingVolume = contentBoundingVolume;    }    // combine() is used to pass through any additional properties the    // user specified such as extras or extensions    contentJsons.push(combine(contentJson, implicitTileset.contentHeaders[i]));  }  const childGeometricError = getGeometricError(    tileMetadata,    implicitTileset,    implicitCoordinates  );  const tileJson = {    boundingVolume: boundingVolume,    geometricError: childGeometricError,    refine: implicitTileset.refine,    contents: contentJsons,  };  // combine() is used to pass through any additional properties the  // user specified such as extras or extensions.  const deep = true;  const rootHeader = clone(implicitTileset.tileHeader, deep);  delete rootHeader.boundingVolume;  delete rootHeader.transform;  const combinedTileJson = combine(tileJson, rootHeader, deep);  const childTile = makeTile(    implicitContent,    implicitTileset.baseResource,    combinedTileJson,    parentTile  );  childTile.implicitCoordinates = implicitCoordinates;  childTile.implicitSubtree = subtree;  childTile.metadata = tileMetadata;  childTile.hasImplicitContentMetadata = hasImplicitContentMetadata;  return childTile;}/** * Checks whether the bounding volume's heights can be updated. * Returns true if the minimumHeight/maximumHeight parameter * is defined and the bounding volume is a region or S2 cell. * * @param {Object} [boundingVolume] The bounding volume * @param {Object} [tileBounds] The tile bounds * @param {Number} [tileBounds.minimumHeight] The minimum height * @param {Number} [tileBounds.maximumHeight] The maximum height * @returns {Boolean} Whether the bounding volume's heights can be updated * @private */function canUpdateHeights(boundingVolume, tileBounds) {  return (    defined(boundingVolume) &&    defined(tileBounds) &&    (defined(tileBounds.minimumHeight) || defined(tileBounds.maximumHeight)) &&    (hasExtension(boundingVolume, "3DTILES_bounding_volume_S2") ||      defined(boundingVolume.region))  );}/** * Update the minimum and maximum height of the bounding volume. * This is typically used to tighten a bounding volume using the * <code>TILE_MINIMUM_HEIGHT</code> and <code>TILE_MAXIMUM_HEIGHT</code> * semantics. Heights are only updated if the respective * minimumHeight/maximumHeight parameter is defined and the * bounding volume is a region or S2 cell. * * @param {Object} boundingVolume The bounding volume * @param {Object} [tileBounds] The tile bounds * @param {Number} [tileBounds.minimumHeight] The new minimum height * @param {Number} [tileBounds.maximumHeight] The new maximum height * @private */function updateHeights(boundingVolume, tileBounds) {  if (!defined(tileBounds)) {    return;  }  if (hasExtension(boundingVolume, "3DTILES_bounding_volume_S2")) {    updateS2CellHeights(      boundingVolume.extensions["3DTILES_bounding_volume_S2"],      tileBounds.minimumHeight,      tileBounds.maximumHeight    );  } else if (defined(boundingVolume.region)) {    updateRegionHeights(      boundingVolume.region,      tileBounds.minimumHeight,      tileBounds.maximumHeight    );  }}/** * For a bounding region, update the minimum and maximum height. This * is typically used to tighten a bounding volume using the * <code>TILE_MINIMUM_HEIGHT</code> and <code>TILE_MAXIMUM_HEIGHT</code> * semantics. Heights are only updated if the respective * minimumHeight/maximumHeight parameter is defined. * * @param {Array} region A 6-element array describing the bounding region * @param {Number} [minimumHeight] The new minimum height * @param {Number} [maximumHeight] The new maximum height * @private */function updateRegionHeights(region, minimumHeight, maximumHeight) {  if (defined(minimumHeight)) {    region[4] = minimumHeight;  }  if (defined(maximumHeight)) {    region[5] = maximumHeight;  }}/** * For a bounding S2 cell, update the minimum and maximum height. This * is typically used to tighten a bounding volume using the * <code>TILE_MINIMUM_HEIGHT</code> and <code>TILE_MAXIMUM_HEIGHT</code> * semantics. Heights are only updated if the respective * minimumHeight/maximumHeight parameter is defined. * * @param {Object} s2CellVolume An object describing the S2 cell * @param {Number} [minimumHeight] The new minimum height * @param {Number} [maximumHeight] The new maximum height * @private */function updateS2CellHeights(s2CellVolume, minimumHeight, maximumHeight) {  if (defined(minimumHeight)) {    s2CellVolume.minimumHeight = minimumHeight;  }  if (defined(maximumHeight)) {    s2CellVolume.maximumHeight = maximumHeight;  }}/** * Gets the tile's bounding volume, which may be specified via * metadata semantics such as TILE_BOUNDING_BOX or implicitly * derived from the implicit root tile's bounding volume. * <p> * Priority of bounding volume types: * <ol> * <li>Explicit min/max height *   <ol> *     <li>With explicit region</li> *     <li>With implicit S2</li> *     <li>With implicit region</li> *   </ol> * </li> * <li>Explicit box</li> * <li>Explicit region</li> * <li>Explicit sphere</li> * <li>Implicit S2</li> * <li>Implicit box</li> * <li>Implicit region</li> * </ol> * </p> * * @param {ImplicitTileset} implicitTileset The implicit tileset struct which holds the root bounding volume * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the child tile * @param {Number} childIndex The morton index of the child tile relative to its parent * @param {Boolean} parentIsPlaceholderTile True if parentTile is a placeholder tile. This is true for the root of each subtree. * @param {Cesium3DTile} parentTile The parent of the new child tile * @param {Object} [tileBounds] The tile bounds * @returns {Object} An object containing the JSON for a bounding volume * @private */function getTileBoundingVolume(  implicitTileset,  implicitCoordinates,  childIndex,  parentIsPlaceholderTile,  parentTile,  tileBounds) {  let boundingVolume;  if (    !defined(tileBounds) ||    !defined(tileBounds.boundingVolume) ||    (!canUpdateHeights(tileBounds.boundingVolume, tileBounds) &&      canUpdateHeights(implicitTileset.boundingVolume, tileBounds))  ) {    boundingVolume = deriveBoundingVolume(      implicitTileset,      implicitCoordinates,      childIndex,      defaultValue(parentIsPlaceholderTile, false),      parentTile    );  } else {    boundingVolume = tileBounds.boundingVolume;  }  // The TILE_MINIMUM_HEIGHT and TILE_MAXIMUM_HEIGHT metadata semantics  // can be used to tighten the bounding volume  updateHeights(boundingVolume, tileBounds);  return boundingVolume;}/** * Gets the content bounding volume, which may be specified via * metadata semantics such as CONTENT_BOUNDING_BOX. * <p> * Priority of bounding volume types: * <ol> * <li>Explicit min/max height *   <ol> *     <li>With explicit region</li> *     <li>With tile bounding volume (S2 or region)</li> *   </ol> * </li> * <li>Explicit box</li> * <li>Explicit region</li> * <li>Explicit sphere</li> * <li>Tile bounding volume (when content.boundingVolume is undefined)</li> * </ol> * </p> * * @param {Object} tileBoundingVolume An object containing the JSON for the tile's bounding volume * @param {Object} [contentBounds] The content bounds * @returns {Object|undefined} An object containing the JSON for a bounding volume, or <code>undefined</code> if there is no bounding volume * @private */function getContentBoundingVolume(tileBoundingVolume, contentBounds) {  // content bounding volumes can only be specified via  // metadata semantics such as CONTENT_BOUNDING_BOX  let contentBoundingVolume;  if (defined(contentBounds)) {    contentBoundingVolume = contentBounds.boundingVolume;  }  // The CONTENT_MINIMUM_HEIGHT and CONTENT_MAXIMUM_HEIGHT metadata semantics  // can be used to tighten the bounding volume  if (canUpdateHeights(contentBoundingVolume, contentBounds)) {    updateHeights(contentBoundingVolume, contentBounds);  } else if (canUpdateHeights(tileBoundingVolume, contentBounds)) {    contentBoundingVolume = clone(tileBoundingVolume, true);    updateHeights(contentBoundingVolume, contentBounds);  }  return contentBoundingVolume;}/** * Given the coordinates of a tile, derive its bounding volume from the root. * * @param {ImplicitTileset} implicitTileset The implicit tileset struct which holds the root bounding volume * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the child tile * @param {Number} childIndex The morton index of the child tile relative to its parent * @param {Boolean} parentIsPlaceholderTile True if parentTile is a placeholder tile. This is true for the root of each subtree. * @param {Cesium3DTile} parentTile The parent of the new child tile * @returns {Object} An object containing the JSON for a bounding volume * @private */function deriveBoundingVolume(  implicitTileset,  implicitCoordinates,  childIndex,  parentIsPlaceholderTile,  parentTile) {  const rootBoundingVolume = implicitTileset.boundingVolume;  if (hasExtension(rootBoundingVolume, "3DTILES_bounding_volume_S2")) {    return deriveBoundingVolumeS2(      parentIsPlaceholderTile,      parentTile,      childIndex,      implicitCoordinates.level,      implicitCoordinates.x,      implicitCoordinates.y,      implicitCoordinates.z    );  }  if (defined(rootBoundingVolume.region)) {    const childRegion = deriveBoundingRegion(      rootBoundingVolume.region,      implicitCoordinates.level,      implicitCoordinates.x,      implicitCoordinates.y,      implicitCoordinates.z    );    return {      region: childRegion,    };  }  const childBox = deriveBoundingBox(    rootBoundingVolume.box,    implicitCoordinates.level,    implicitCoordinates.x,    implicitCoordinates.y,    implicitCoordinates.z  );  return {    box: childBox,  };}/** * Derive a bounding volume for a descendant tile (child, grandchild, etc.), * assuming a quadtree or octree implicit tiling scheme. The (level, x, y, [z]) * coordinates are given to select the descendant tile and compute its position * and dimensions. * <p> * If z is present, octree subdivision is used. Otherwise, quadtree subdivision * is used. Quadtrees are always divided at the midpoint of the the horizontal * dimensions, i.e. (x, y), leaving the z axis unchanged. * </p> * * @param {Boolean} parentIsPlaceholderTile True if parentTile is a placeholder tile. This is true for the root of each subtree. * @param {Cesium3DTile} parentTile The parent of the new child tile * @param {Number} childIndex The morton index of the child tile relative to its parent * @param {Number} level The level of the descendant tile relative to the root implicit tile * @param {Number} x The x coordinate of the descendant tile * @param {Number} y The y coordinate of the descendant tile * @param {Number} [z] The z coordinate of the descendant tile (octree only) * @returns {Object} An object with the 3DTILES_bounding_volume_S2 extension. * @private */function deriveBoundingVolumeS2(  parentIsPlaceholderTile,  parentTile,  childIndex,  level,  x,  y,  z) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.bool("parentIsPlaceholderTile", parentIsPlaceholderTile);  Check.typeOf.object("parentTile", parentTile);  Check.typeOf.number("childIndex", childIndex);  Check.typeOf.number("level", level);  Check.typeOf.number("x", x);  Check.typeOf.number("y", y);  if (defined(z)) {    Check.typeOf.number("z", z);  }  //>>includeEnd('debug');  const boundingVolumeS2 = parentTile._boundingVolume;  // Handle the placeholder tile case, where we just duplicate the placeholder's bounding volume.  if (parentIsPlaceholderTile) {    return {      extensions: {        "3DTILES_bounding_volume_S2": {          token: S2Cell.getTokenFromId(boundingVolumeS2.s2Cell._cellId),          minimumHeight: boundingVolumeS2.minimumHeight,          maximumHeight: boundingVolumeS2.maximumHeight,        },      },    };  }  // Extract the first 3 face bits from the 64-bit S2 cell ID.  // eslint-disable-next-line no-undef  const face = Number(parentTile._boundingVolume.s2Cell._cellId >> BigInt(61));  // The Hilbert curve is rotated for the "odd" faces on the S2 Earthcube.  // See http://s2geometry.io/devguide/img/s2cell_global.jpg  const position =    face % 2 === 0      ? HilbertOrder.encode2D(level, x, y)      : HilbertOrder.encode2D(level, y, x);  // eslint-disable-next-line no-undef  const cell = S2Cell.fromFacePositionLevel(face, BigInt(position), level);  let minHeight, maxHeight;  if (defined(z)) {    const midpointHeight =      (boundingVolumeS2.maximumHeight + boundingVolumeS2.minimumHeight) / 2;    minHeight =      childIndex < 4 ? boundingVolumeS2.minimumHeight : midpointHeight;    maxHeight =      childIndex < 4 ? midpointHeight : boundingVolumeS2.maximumHeight;  } else {    minHeight = boundingVolumeS2.minimumHeight;    maxHeight = boundingVolumeS2.maximumHeight;  }  return {    extensions: {      "3DTILES_bounding_volume_S2": {        token: S2Cell.getTokenFromId(cell._cellId),        minimumHeight: minHeight,        maximumHeight: maxHeight,      },    },  };}const scratchScaleFactors = new Cartesian3();const scratchRootCenter = new Cartesian3();const scratchCenter = new Cartesian3();const scratchHalfAxes = new Matrix3();/** * Derive a bounding volume for a descendant tile (child, grandchild, etc.), * assuming a quadtree or octree implicit tiling scheme. The (level, x, y, [z]) * coordinates are given to select the descendant tile and compute its position * and dimensions. * <p> * If z is present, octree subdivision is used. Otherwise, quadtree subdivision * is used. Quadtrees are always divided at the midpoint of the the horizontal * dimensions, i.e. (x, y), leaving the z axis unchanged. * </p> * <p> * This computes the child volume directly from the root bounding volume rather * than recursively subdividing to minimize floating point error. * </p> * * @param {Number[]} rootBox An array of 12 numbers representing the bounding box of the root tile * @param {Number} level The level of the descendant tile relative to the root implicit tile * @param {Number} x The x coordinate of the descendant tile * @param {Number} y The y coordinate of the descendant tile * @param {Number} [z] The z coordinate of the descendant tile (octree only) * @returns {Number[]} An array of 12 numbers representing the bounding box of the descendant tile. * @private */function deriveBoundingBox(rootBox, level, x, y, z) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("rootBox", rootBox);  Check.typeOf.number("level", level);  Check.typeOf.number("x", x);  Check.typeOf.number("y", y);  if (defined(z)) {    Check.typeOf.number("z", z);  }  //>>includeEnd('debug');  if (level === 0) {    return rootBox;  }  const rootCenter = Cartesian3.unpack(rootBox, 0, scratchRootCenter);  const rootHalfAxes = Matrix3.unpack(rootBox, 3, scratchHalfAxes);  const tileScale = Math.pow(2, -level);  const modelSpaceX = -1 + (2 * x + 1) * tileScale;  const modelSpaceY = -1 + (2 * y + 1) * tileScale;  let modelSpaceZ = 0;  const scaleFactors = Cartesian3.fromElements(    tileScale,    tileScale,    1,    scratchScaleFactors  );  if (defined(z)) {    modelSpaceZ = -1 + (2 * z + 1) * tileScale;    scaleFactors.z = tileScale;  }  let center = Cartesian3.fromElements(    modelSpaceX,    modelSpaceY,    modelSpaceZ,    scratchCenter  );  center = Matrix3.multiplyByVector(rootHalfAxes, center, scratchCenter);  center = Cartesian3.add(center, rootCenter, scratchCenter);  let halfAxes = Matrix3.clone(rootHalfAxes);  halfAxes = Matrix3.multiplyByScale(halfAxes, scaleFactors, halfAxes);  const childBox = new Array(12);  Cartesian3.pack(center, childBox);  Matrix3.pack(halfAxes, childBox, 3);  return childBox;}const scratchRectangle = new Rectangle();/** * Derive a bounding volume for a descendant tile (child, grandchild, etc.), * assuming a quadtree or octree implicit tiling scheme. The (level, x, y, [z]) * coordinates are given to select the descendant tile and compute its position * and dimensions. * <p> * If z is present, octree subdivision is used. Otherwise, quadtree subdivision * is used. Quadtrees are always divided at the midpoint of the the horizontal * dimensions, i.e. (mid_longitude, mid_latitude), leaving the height values * unchanged. * </p> * <p> * This computes the child volume directly from the root bounding volume rather * than recursively subdividing to minimize floating point error. * </p> * @param {Number[]} rootRegion An array of 6 numbers representing the root implicit tile * @param {Number} level The level of the descendant tile relative to the root implicit tile * @param {Number} x The x coordinate of the descendant tile * @param {Number} y The x coordinate of the descendant tile * @param {Number} [z] The z coordinate of the descendant tile (octree only) * @returns {Number[]} An array of 6 numbers representing the bounding region of the descendant tile * @private */function deriveBoundingRegion(rootRegion, level, x, y, z) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("rootRegion", rootRegion);  Check.typeOf.number("level", level);  Check.typeOf.number("x", x);  Check.typeOf.number("y", y);  if (defined(z)) {    Check.typeOf.number("z", z);  }  //>>includeEnd('debug');  if (level === 0) {    return rootRegion.slice();  }  const rectangle = Rectangle.unpack(rootRegion, 0, scratchRectangle);  const rootMinimumHeight = rootRegion[4];  const rootMaximumHeight = rootRegion[5];  const tileScale = Math.pow(2, -level);  const childWidth = tileScale * rectangle.width;  const west = CesiumMath.negativePiToPi(rectangle.west + x * childWidth);  const east = CesiumMath.negativePiToPi(west + childWidth);  const childHeight = tileScale * rectangle.height;  const south = CesiumMath.negativePiToPi(rectangle.south + y * childHeight);  const north = CesiumMath.negativePiToPi(south + childHeight);  // Height is only subdivided for octrees; It remains constant for quadtrees.  let minimumHeight = rootMinimumHeight;  let maximumHeight = rootMaximumHeight;  if (defined(z)) {    const childThickness = tileScale * (rootMaximumHeight - rootMinimumHeight);    minimumHeight += z * childThickness;    maximumHeight = minimumHeight + childThickness;  }  return [west, south, east, north, minimumHeight, maximumHeight];}/** * Create a placeholder 3D Tile whose content will be an Implicit3DTileContent * for lazy evaluation of a child subtree. * * @param {Implicit3DTileContent} content The content object. * @param {Cesium3DTile} parentTile The parent of the new child subtree. * @param {Number} childIndex The morton index of the child tile relative to its parent * @returns {Cesium3DTile} The new placeholder tile * @private */function makePlaceholderChildSubtree(content, parentTile, childIndex) {  const implicitTileset = content._implicitTileset;  const implicitCoordinates = parentTile.implicitCoordinates.getChildCoordinates(    childIndex  );  const childBoundingVolume = deriveBoundingVolume(    implicitTileset,    implicitCoordinates,    childIndex,    false,    parentTile  );  // Ignore tile metadata when computing geometric error for the placeholder tile  // since the child subtree's metadata hasn't been loaded yet.  // The actual geometric error will be computed in deriveChildTile.  const childGeometricError = getGeometricError(    undefined,    implicitTileset,    implicitCoordinates  );  const childContentUri = implicitTileset.subtreeUriTemplate.getDerivedResource(    {      templateValues: implicitCoordinates.getTemplateValues(),    }  ).url;  const tileJson = {    boundingVolume: childBoundingVolume,    geometricError: childGeometricError,    refine: implicitTileset.refine,    contents: [      {        uri: childContentUri,      },    ],  };  const tile = makeTile(    content,    implicitTileset.baseResource,    tileJson,    parentTile  );  tile.implicitTileset = implicitTileset;  tile.implicitCoordinates = implicitCoordinates;  return tile;}/** * Make a {@link Cesium3DTile}. This uses the content's tile's constructor instead * of importing Cesium3DTile. This is to avoid a circular dependency between * this file and Cesium3DTile.js * @param {Implicit3DTileContent} content The implicit content * @param {Resource} baseResource The base resource for the tileset * @param {Object} tileJson The JSON header for the tile * @param {Cesium3DTile} parentTile The parent of the new tile * @returns {Cesium3DTile} The newly created tile. * @private */function makeTile(content, baseResource, tileJson, parentTile) {  const Cesium3DTile = content._tile.constructor;  return new Cesium3DTile(content._tileset, baseResource, tileJson, parentTile);}/** * Part of the {@link Cesium3DTileContent} interface.  <code>Implicit3DTileContent</code> * always returns <code>false</code> since a tile of this type does not have any features. * @private */Implicit3DTileContent.prototype.hasProperty = function (batchId, name) {  return false;};/** * Part of the {@link Cesium3DTileContent} interface.  <code>Implicit3DTileContent</code> * always returns <code>undefined</code> since a tile of this type does not have any features. * @private */Implicit3DTileContent.prototype.getFeature = function (batchId) {  return undefined;};Implicit3DTileContent.prototype.applyDebugSettings = function (  enabled,  color) {};Implicit3DTileContent.prototype.applyStyle = function (style) {};Implicit3DTileContent.prototype.update = function (tileset, frameState) {};Implicit3DTileContent.prototype.isDestroyed = function () {  return false;};Implicit3DTileContent.prototype.destroy = function () {  this._implicitSubtree =    this._implicitSubtree && this._implicitSubtree.destroy();  return destroyObject(this);};// Exposed for testingImplicit3DTileContent._deriveBoundingBox = deriveBoundingBox;Implicit3DTileContent._deriveBoundingRegion = deriveBoundingRegion;Implicit3DTileContent._deriveBoundingVolumeS2 = deriveBoundingVolumeS2;
 |