| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 | import CesiumMath from "../Core/Math.js";import Check from "../Core/Check.js";import DeveloperError from "../Core/DeveloperError.js";import MortonOrder from "../Core/MortonOrder.js";import ImplicitSubdivisionScheme from "./ImplicitSubdivisionScheme.js";/** * The coordinates for a tile in an implicit tileset. The coordinates * are (level, x, y) for quadtrees or (level, x, y, z) for octrees. * <p> * Level numbers are 0-indexed and typically start at the root of the implicit * tileset (the tile with either implicitTiling in its JSON (3D Tiles 1.1) or * the <code>3DTILES_implicit_tiling</code> extension). * This object can also represent the relative offset from one set of coordinates * to another. See {@link ImplicitTileCoordinates#getOffsetCoordinates}. The term * local coordinates refers to coordinates that are relative to the root of a * subtree and the term global coordinates refers to coordinates relative to the * root of an implicit tileset. * </p> * <p> * For box bounding volumes, x, y, z increase along the +x, +y, and +z * directions defined by the half axes. * </p> * <p> * For region bounding volumes, x increases in the +longitude direction, y * increases in the +latitude direction, and z increases in the +height * direction. * </p> * <p> * Care must be taken when converting between implicit coordinates and Morton * indices because there is a 16-bit limit on {@link MortonOrder#encode2D} and * a 10-bit limit on {@link MortonOrder#encode3D}. Typically these conversions * should be done on local coordinates, not global coordinates, and the maximum * number of levels in the subtree should be 15 for quadtree and 9 for octree (to * account for the extra level needed by child subtree coordinates). * </p> * * @alias ImplicitTileCoordinates * @constructor * * @param {Object} options An object with the following properties: * @param {ImplicitSubdivisionScheme} options.subdivisionScheme Whether the coordinates are for a quadtree or octree * @param {Number} options.subtreeLevels The number of distinct levels within the coordinate's subtree * @param {Number} options.level The level of a tile relative to the tile with the extension * @param {Number} options.x The x coordinate of the tile * @param {Number} options.y The y coordinate of the tile * @param {Number} [options.z] The z coordinate of the tile. Only required when options.subdivisionScheme is ImplicitSubdivisionScheme.OCTREE * @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 ImplicitTileCoordinates(options) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("options.subdivisionScheme", options.subdivisionScheme);  Check.typeOf.number("options.subtreeLevels", options.subtreeLevels);  Check.typeOf.number("options.level", options.level);  Check.typeOf.number("options.x", options.x);  Check.typeOf.number("options.y", options.y);  if (options.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    Check.typeOf.number("options.z", options.z);  }  // Check for values that are negative  if (options.level < 0) {    throw new DeveloperError("level must be non-negative");  }  if (options.x < 0) {    throw new DeveloperError("x must be non-negative");  }  if (options.y < 0) {    throw new DeveloperError("y must be non-negative");  }  if (options.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    if (options.z < 0) {      throw new DeveloperError("z must be non-negative");    }  }  // Check for values that are too large  const dimensionAtLevel = 1 << options.level;  if (options.x >= dimensionAtLevel) {    throw new DeveloperError("x is out of range");  }  if (options.y >= dimensionAtLevel) {    throw new DeveloperError("y is out of range");  }  if (options.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    if (options.z >= dimensionAtLevel) {      throw new DeveloperError("z is out of range");    }  }  //>>includeEnd('debug');  /**   * Whether the tileset is a quadtree or octree   *   * @type {ImplicitSubdivisionScheme}   * @readonly   * @private   */  this.subdivisionScheme = options.subdivisionScheme;  /**   * The number of distinct levels within the coordinate's subtree   *   * @type {Number}   * @readonly   * @private   */  this.subtreeLevels = options.subtreeLevels;  /**   * Level of this tile, relative to the tile with implicit tiling in its JSON   * (3D Tiles 1.1) or the <code>3DTILES_implicit_tiling</code> extension.   * Level numbers start at 0.   *   * @type {Number}   * @readonly   * @private   */  this.level = options.level;  /**   * X coordinate of this tile   *   * @type {Number}   * @readonly   * @private   */  this.x = options.x;  /**   * Y coordinate of this tile   *   * @type {Number}   * @readonly   * @private   */  this.y = options.y;  /**   * Z coordinate of this tile. Only defined for octrees.   *   * @type {Number|undefined}   * @readonly   * @private   */  this.z = undefined;  if (options.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    this.z = options.z;  }}Object.defineProperties(ImplicitTileCoordinates.prototype, {  /**   * An index in the range of [0, branchingFactor) that indicates   * which child of the parent cell these coordinates correspond to.   * This can be viewed as a morton index within the parent tile.   * <p>   * This is the last 3 bits of the morton index of the tile, but it can   * be computed more directly by concatenating the bits [z0] y0 x0   * </p>   *   * @type {Number}   * @readonly   * @private   */  childIndex: {    get: function () {      let childIndex = 0;      childIndex |= this.x & 1;      childIndex |= (this.y & 1) << 1;      if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {        childIndex |= (this.z & 1) << 2;      }      return childIndex;    },  },  /**   * Get the Morton index for this tile within the current level by interleaving   * the bits of the x, y and z coordinates.   *   * @type {Number}   * @readonly   * @private   */  mortonIndex: {    get: function () {      if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {        return MortonOrder.encode3D(this.x, this.y, this.z);      }      return MortonOrder.encode2D(this.x, this.y);    },  },  /**   * Get the tile index by adding the Morton index to the level offset   *   * @type {Number}   * @readonly   * @private   */  tileIndex: {    get: function () {      const levelOffset =        this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE          ? // (8^N - 1) / (8-1)            ((1 << (3 * this.level)) - 1) / 7          : // (4^N - 1) / (4-1)            ((1 << (2 * this.level)) - 1) / 3;      const mortonIndex = this.mortonIndex;      return levelOffset + mortonIndex;    },  },});/** * Check that the two coordinates are compatible * @param {ImplicitTileCoordinates} a * @param {ImplicitTileCoordinates} b * @private */function checkMatchingSubtreeShape(a, b) {  if (a.subdivisionScheme !== b.subdivisionScheme) {    throw new DeveloperError("coordinates must have same subdivisionScheme");  }  if (a.subtreeLevels !== b.subtreeLevels) {    throw new DeveloperError("coordinates must have same subtreeLevels");  }}/** * Compute the coordinates of a tile deeper in the tree with a (level, x, y, [z]) relative offset. * * @param {ImplicitTileCoordinates} offsetCoordinates The offset from the ancestor * @returns {ImplicitTileCoordinates} The coordinates of the descendant * @private */ImplicitTileCoordinates.prototype.getDescendantCoordinates = function (  offsetCoordinates) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("offsetCoordinates", offsetCoordinates);  checkMatchingSubtreeShape(this, offsetCoordinates);  //>>includeEnd('debug');  const descendantLevel = this.level + offsetCoordinates.level;  const descendantX = (this.x << offsetCoordinates.level) + offsetCoordinates.x;  const descendantY = (this.y << offsetCoordinates.level) + offsetCoordinates.y;  if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    const descendantZ =      (this.z << offsetCoordinates.level) + offsetCoordinates.z;    return new ImplicitTileCoordinates({      subdivisionScheme: this.subdivisionScheme,      subtreeLevels: this.subtreeLevels,      level: descendantLevel,      x: descendantX,      y: descendantY,      z: descendantZ,    });  }  // Quadtree  return new ImplicitTileCoordinates({    subdivisionScheme: this.subdivisionScheme,    subtreeLevels: this.subtreeLevels,    level: descendantLevel,    x: descendantX,    y: descendantY,  });};/** * Compute the coordinates of a tile higher up in the tree by going up a number of levels. * * @param {Number} offsetLevels The number of levels to go up in the tree * @returns {ImplicitTileCoordinates} The coordinates of the ancestor * @private */ImplicitTileCoordinates.prototype.getAncestorCoordinates = function (  offsetLevels) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.number("offsetLevels", offsetLevels);  if (offsetLevels < 0) {    throw new DeveloperError("offsetLevels must be non-negative");  }  if (offsetLevels > this.level) {    throw new DeveloperError("ancestor cannot be above the tileset root");  }  //>>includeEnd('debug');  const divisor = 1 << offsetLevels;  const ancestorLevel = this.level - offsetLevels;  const ancestorX = Math.floor(this.x / divisor);  const ancestorY = Math.floor(this.y / divisor);  if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    const ancestorZ = Math.floor(this.z / divisor);    return new ImplicitTileCoordinates({      subdivisionScheme: this.subdivisionScheme,      subtreeLevels: this.subtreeLevels,      level: ancestorLevel,      x: ancestorX,      y: ancestorY,      z: ancestorZ,    });  }  // Quadtree  return new ImplicitTileCoordinates({    subdivisionScheme: this.subdivisionScheme,    subtreeLevels: this.subtreeLevels,    level: ancestorLevel,    x: ancestorX,    y: ancestorY,  });};/** * Compute the (level, x, y, [z]) offset to a descendant * * @param {ImplicitTileCoordinates} descendantCoordinates The descendant coordinates * @returns {ImplicitTileCoordinates} The offset between the ancestor and the descendant */ImplicitTileCoordinates.prototype.getOffsetCoordinates = function (  descendantCoordinates) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("descendantCoordinates", descendantCoordinates);  if (    !this.isEqual(descendantCoordinates) &&    !this.isAncestor(descendantCoordinates)  ) {    throw new DeveloperError("this is not an ancestor of descendant");  }  checkMatchingSubtreeShape(this, descendantCoordinates);  //>>includeEnd('debug');  const offsetLevel = descendantCoordinates.level - this.level;  const dimensionAtOffsetLevel = 1 << offsetLevel;  const offsetX = descendantCoordinates.x % dimensionAtOffsetLevel;  const offsetY = descendantCoordinates.y % dimensionAtOffsetLevel;  if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    const offsetZ = descendantCoordinates.z % dimensionAtOffsetLevel;    return new ImplicitTileCoordinates({      subdivisionScheme: this.subdivisionScheme,      subtreeLevels: this.subtreeLevels,      level: offsetLevel,      x: offsetX,      y: offsetY,      z: offsetZ,    });  }  // Quadtree  return new ImplicitTileCoordinates({    subdivisionScheme: this.subdivisionScheme,    subtreeLevels: this.subtreeLevels,    level: offsetLevel,    x: offsetX,    y: offsetY,  });};/** * Given the morton index of the child, compute the coordinates of the child. * This is a special case of {@link ImplicitTileCoordinates#getDescendantCoordinates}. * * @param {Number} childIndex The morton index of the child tile relative to its parent * @returns {ImplicitTileCoordinates} The tile coordinates of the child * @private */ImplicitTileCoordinates.prototype.getChildCoordinates = function (childIndex) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.number("childIndex", childIndex);  const branchingFactor = ImplicitSubdivisionScheme.getBranchingFactor(    this.subdivisionScheme  );  if (childIndex < 0 || branchingFactor <= childIndex) {    throw new DeveloperError(      `childIndex must be at least 0 and less than ${branchingFactor}`    );  }  //>>includeEnd('debug');  const level = this.level + 1;  const x = 2 * this.x + (childIndex % 2);  const y = 2 * this.y + (Math.floor(childIndex / 2) % 2);  if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    const z = 2 * this.z + (Math.floor(childIndex / 4) % 2);    return new ImplicitTileCoordinates({      subdivisionScheme: this.subdivisionScheme,      subtreeLevels: this.subtreeLevels,      level: level,      x: x,      y: y,      z: z,    });  }  // Quadtree  return new ImplicitTileCoordinates({    subdivisionScheme: this.subdivisionScheme,    subtreeLevels: this.subtreeLevels,    level: level,    x: x,    y: y,  });};/** * Get the coordinates of the subtree that contains this tile. If the tile is * the root of the subtree, the root of the subtree is returned. * * @returns {ImplicitTileCoordinates} The subtree that contains this tile * @private */ImplicitTileCoordinates.prototype.getSubtreeCoordinates = function () {  return this.getAncestorCoordinates(this.level % this.subtreeLevels);};/** * Get the coordinates of the parent subtree that contains this tile * * @returns {ImplicitTileCoordinates} The parent subtree that contains this tile * @private */ImplicitTileCoordinates.prototype.getParentSubtreeCoordinates = function () {  return this.getAncestorCoordinates(    (this.level % this.subtreeLevels) + this.subtreeLevels  );};/** * Returns whether this tile is an ancestor of another tile * * @param {ImplicitTileCoordinates} descendantCoordinates the descendant coordinates * @returns {Boolean} <code>true</code> if this tile is an ancestor of the other tile * @private */ImplicitTileCoordinates.prototype.isAncestor = function (  descendantCoordinates) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("descendantCoordinates", descendantCoordinates);  checkMatchingSubtreeShape(this, descendantCoordinates);  //>>includeEnd('debug');  const levelDifference = descendantCoordinates.level - this.level;  if (levelDifference <= 0) {    return false;  }  const ancestorX = descendantCoordinates.x >> levelDifference;  const ancestorY = descendantCoordinates.y >> levelDifference;  const isAncestorX = this.x === ancestorX;  const isAncestorY = this.y === ancestorY;  if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    const ancestorZ = descendantCoordinates.z >> levelDifference;    const isAncestorZ = this.z === ancestorZ;    return isAncestorX && isAncestorY && isAncestorZ;  }  // Quadtree  return isAncestorX && isAncestorY;};/** * Returns whether the provided coordinates are equal to this coordinate * * @param {ImplicitTileCoordinates} otherCoordinates the other coordinates * @returns {Boolean} <code>true</code> if the coordinates are equal * @private */ImplicitTileCoordinates.prototype.isEqual = function (otherCoordinates) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("otherCoordinates", otherCoordinates);  //>>includeEnd('debug');  return (    this.subdivisionScheme === otherCoordinates.subdivisionScheme &&    this.subtreeLevels === otherCoordinates.subtreeLevels &&    this.level === otherCoordinates.level &&    this.x === otherCoordinates.x &&    this.y === otherCoordinates.y &&    (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE      ? this.z === otherCoordinates.z      : true)  );};/** * Returns whether this tile is the root of the implicit tileset * * @returns {Boolean} <code>true</code> if this tile is the root * @private */ImplicitTileCoordinates.prototype.isImplicitTilesetRoot = function () {  return this.level === 0;};/** * Returns whether this tile is the root of the subtree * * @returns {Boolean} <code>true</code> if this tile is the root of the subtree * @private */ImplicitTileCoordinates.prototype.isSubtreeRoot = function () {  return this.level % this.subtreeLevels === 0;};/** * Returns whether this tile is on the last row of tiles in the subtree * * @returns {Boolean} <code>true</code> if this tile is on the last row of tiles in the subtree * @private */ImplicitTileCoordinates.prototype.isBottomOfSubtree = function () {  return this.level % this.subtreeLevels === this.subtreeLevels - 1;};/** * Get a dictionary of values for templating into an implicit template URI. * * @returns {Object} An object suitable for use with {@link Resource#getDerivedResource} * @private */ImplicitTileCoordinates.prototype.getTemplateValues = function () {  const values = {    level: this.level,    x: this.x,    y: this.y,  };  if (this.subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    values.z = this.z;  }  return values;};const scratchCoordinatesArray = [0, 0, 0];/** * Given a level number, morton index, and whether the tileset is an * octree/quadtree, compute the (level, x, y, [z]) coordinates * * @param {ImplicitSubdivisionScheme} subdivisionScheme Whether the coordinates are for a quadtree or octree * @param {Number} subtreeLevels The number of distinct levels within the coordinate's subtree * @param {Number} level The level of the tree * @param {Number} mortonIndex The morton index of the tile. * @returns {ImplicitTileCoordinates} The coordinates of the tile with the given Morton index * @private */ImplicitTileCoordinates.fromMortonIndex = function (  subdivisionScheme,  subtreeLevels,  level,  mortonIndex) {  let coordinatesArray;  if (subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    coordinatesArray = MortonOrder.decode3D(      mortonIndex,      scratchCoordinatesArray    );    return new ImplicitTileCoordinates({      subdivisionScheme: subdivisionScheme,      subtreeLevels: subtreeLevels,      level: level,      x: coordinatesArray[0],      y: coordinatesArray[1],      z: coordinatesArray[2],    });  }  coordinatesArray = MortonOrder.decode2D(mortonIndex, scratchCoordinatesArray);  return new ImplicitTileCoordinates({    subdivisionScheme: subdivisionScheme,    subtreeLevels: subtreeLevels,    level: level,    x: coordinatesArray[0],    y: coordinatesArray[1],  });};/** * Given a tile index and whether the tileset is an octree/quadtree, compute * the (level, x, y, [z]) coordinates * * @param {ImplicitSubdivisionScheme} subdivisionScheme Whether the coordinates are for a quadtree or octree * @param {Number} subtreeLevels The number of distinct levels within the coordinate's subtree * @param {Number} tileIndex The tile's index * @returns {ImplicitTileCoordinates} The coordinates of the tile with the given tile index * @private */ImplicitTileCoordinates.fromTileIndex = function (  subdivisionScheme,  subtreeLevels,  tileIndex) {  let level;  let levelOffset;  let mortonIndex;  if (subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {    // Node count up to octree level: (8^L - 1) / (8-1)    // (8^L - 1) / (8-1) <= X < (8^(L+1) - 1) / (8-1)    // 8^L <= (7x + 1) < 8^(L+1)    // L <= log8(7x + 1) < L + 1    // L = floor(log8(7x + 1))    // L = floor(log2(7x + 1) / log2(8))    // L = floor(log2(7x + 1) / 3)    level = Math.floor(CesiumMath.log2(7 * tileIndex + 1) / 3);    levelOffset = ((1 << (3 * level)) - 1) / 7;    mortonIndex = tileIndex - levelOffset;  } else {    // Node count up to quadtree level: (4^L - 1) / (4-1)    // (4^L - 1) / (4-1) <= X < (4^(L+1) - 1) / (4-1)    // 4^L <= (3x + 1) < 4^(L+1)    // L <= log4(3x + 1) < L + 1    // L = floor(log4(3x + 1))    // L = floor(log2(3x + 1) / log2(4))    // L = floor(log2(3x + 1) / 2)    level = Math.floor(CesiumMath.log2(3 * tileIndex + 1) / 2);    levelOffset = ((1 << (2 * level)) - 1) / 3;    mortonIndex = tileIndex - levelOffset;  }  return ImplicitTileCoordinates.fromMortonIndex(    subdivisionScheme,    subtreeLevels,    level,    mortonIndex  );};
 |