| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 | import defined from "../Core/defined.js";import ManagedArray from "../Core/ManagedArray.js";import Cesium3DTileRefine from "./Cesium3DTileRefine.js";import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";/** * Depth-first traversal that traverses all visible tiles and marks tiles for selection. * Allows for skipping levels of the tree and rendering children and parent tiles simultaneously. * * @alias Cesium3DTilesetSkipTraversal * @constructor * * @private */function Cesium3DTilesetSkipTraversal() {}const traversal = {  stack: new ManagedArray(),  stackMaximumLength: 0,};const descendantTraversal = {  stack: new ManagedArray(),  stackMaximumLength: 0,};const selectionTraversal = {  stack: new ManagedArray(),  stackMaximumLength: 0,  ancestorStack: new ManagedArray(),  ancestorStackMaximumLength: 0,};const descendantSelectionDepth = 2;/** * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render. * * @private * @param {Cesium3DTileset} tileset * @param {FrameState} frameState */Cesium3DTilesetSkipTraversal.selectTiles = function (tileset, frameState) {  tileset._requestedTiles.length = 0;  if (tileset.debugFreezeFrame) {    return;  }  tileset._selectedTiles.length = 0;  tileset._selectedTilesToStyle.length = 0;  tileset._emptyTiles.length = 0;  tileset.hasMixedContent = false;  const root = tileset.root;  Cesium3DTilesetTraversal.updateTile(root, frameState);  if (!root.isVisible) {    return;  }  if (    root.getScreenSpaceError(frameState, true) <=    tileset._maximumScreenSpaceError  ) {    return;  }  executeTraversal(root, frameState);  traverseAndSelect(root, frameState);  traversal.stack.trim(traversal.stackMaximumLength);  descendantTraversal.stack.trim(descendantTraversal.stackMaximumLength);  selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength);  selectionTraversal.ancestorStack.trim(    selectionTraversal.ancestorStackMaximumLength  );  // Update the priority for any requests found during traversal  // Update after traversal so that min and max values can be used to normalize priority values  const requestedTiles = tileset._requestedTiles;  for (let i = 0; i < requestedTiles.length; ++i) {    requestedTiles[i].updatePriority();  }};/** * Mark descendant tiles for rendering, and update as needed * * @private * @param {Cesium3DTile} root * @param {FrameState} frameState */function selectDescendants(root, frameState) {  const { updateTile, touchTile, selectTile } = Cesium3DTilesetTraversal;  const stack = descendantTraversal.stack;  stack.push(root);  while (stack.length > 0) {    descendantTraversal.stackMaximumLength = Math.max(      descendantTraversal.stackMaximumLength,      stack.length    );    const tile = stack.pop();    const children = tile.children;    for (let i = 0; i < children.length; ++i) {      const child = children[i];      if (child.isVisible) {        if (child.contentAvailable) {          updateTile(child, frameState);          touchTile(child, frameState);          selectTile(child, frameState);        } else if (child._depth - root._depth < descendantSelectionDepth) {          // Continue traversing, but not too far          stack.push(child);        }      }    }  }}/** * Mark a tile as selected if it has content available. * If its content is not available, and we are skipping levels of detail, * select an ancestor or descendant tile instead * * @private * @param {Cesium3DTile} tile * @param {FrameState} frameState */function selectDesiredTile(tile, frameState) {  // If this tile is not loaded attempt to select its ancestor instead  const loadedTile = tile.contentAvailable    ? tile    : tile._ancestorWithContentAvailable;  if (defined(loadedTile)) {    // Tiles will actually be selected in traverseAndSelect    loadedTile._shouldSelect = true;  } else {    // If no ancestors are ready traverse down and select tiles to minimize empty regions.    // This happens often for immediatelyLoadDesiredLevelOfDetail where parent tiles are not necessarily loaded before zooming out.    selectDescendants(tile, frameState);  }}/** * Update links to the ancestor tiles that have content * * @private * @param {Cesium3DTile} tile * @param {FrameState} frameState */function updateTileAncestorContentLinks(tile, frameState) {  tile._ancestorWithContent = undefined;  tile._ancestorWithContentAvailable = undefined;  const { parent } = tile;  if (!defined(parent)) {    return;  }  const parentHasContent =    !parent.hasUnloadedRenderableContent ||    parent._requestedFrame === frameState.frameNumber;  // ancestorWithContent is an ancestor that has content or has the potential to have  // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.  tile._ancestorWithContent = parentHasContent    ? parent    : parent._ancestorWithContent;  // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded  tile._ancestorWithContentAvailable = parent.contentAvailable    ? parent    : parent._ancestorWithContentAvailable;}/** * Determine if a tile has reached the limit of level of detail skipping. * If so, it should _not_ be skipped: it should be loaded and rendered * * @private * @param {Cesium3DTileset} tileset * @param {Cesium3DTile} tile * @returns {boolean} true if this tile should not be skipped */function reachedSkippingThreshold(tileset, tile) {  const ancestor = tile._ancestorWithContent;  return (    !tileset.immediatelyLoadDesiredLevelOfDetail &&    (tile._priorityProgressiveResolutionScreenSpaceErrorLeaf ||      (defined(ancestor) &&        tile._screenSpaceError <          ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor &&        tile._depth > ancestor._depth + tileset.skipLevels))  );}/** * @private * @param {Cesium3DTile} tile * @param {ManagedArray} stack * @param {FrameState} frameState * @returns {boolean} */function updateAndPushChildren(tile, stack, frameState) {  const { tileset, children } = tile;  const { updateTile, loadTile, touchTile } = Cesium3DTilesetTraversal;  for (let i = 0; i < children.length; ++i) {    updateTile(children[i], frameState);  }  // Sort by distance to take advantage of early Z and reduce artifacts  children.sort(Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera);  let anyChildrenVisible = false;  for (let i = 0; i < children.length; ++i) {    const child = children[i];    if (child.isVisible) {      stack.push(child);      anyChildrenVisible = true;    } else if (tileset.loadSiblings) {      loadTile(child, frameState);      touchTile(child, frameState);    }  }  return anyChildrenVisible;}/** * Determine if a tile is part of the base traversal. * If not, this tile could be considered for level of detail skipping * * @private * @param {Cesium3DTile} tile * @param {number} baseScreenSpaceError * @returns {boolean} */function inBaseTraversal(tile, baseScreenSpaceError) {  const { tileset } = tile;  if (tileset.immediatelyLoadDesiredLevelOfDetail) {    return false;  }  if (!defined(tile._ancestorWithContent)) {    // Include root or near-root tiles in the base traversal so there is something to select up to    return true;  }  if (tile._screenSpaceError === 0.0) {    // If a leaf, use parent's SSE    return tile.parent._screenSpaceError > baseScreenSpaceError;  }  return tile._screenSpaceError > baseScreenSpaceError;}/** * Depth-first traversal that traverses all visible tiles and marks tiles for selection. * Tiles that have a greater screen space error than the base screen space error are part of the base traversal, * all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree * and rendering children and parent tiles simultaneously. * * @private * @param {Cesium3DTile} root * @param {FrameState} frameState */function executeTraversal(root, frameState) {  const { tileset } = root;  const baseScreenSpaceError = tileset.immediatelyLoadDesiredLevelOfDetail    ? Number.MAX_VALUE    : Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError);  const {    canTraverse,    loadTile,    visitTile,    touchTile,  } = Cesium3DTilesetTraversal;  const stack = traversal.stack;  stack.push(root);  while (stack.length > 0) {    traversal.stackMaximumLength = Math.max(      traversal.stackMaximumLength,      stack.length    );    const tile = stack.pop();    updateTileAncestorContentLinks(tile, frameState);    const parent = tile.parent;    const parentRefines = !defined(parent) || parent._refines;    tile._refines = canTraverse(tile)      ? updateAndPushChildren(tile, stack, frameState) && parentRefines      : false;    const stoppedRefining = !tile._refines && parentRefines;    if (!tile.hasRenderableContent) {      // Add empty tile just to show its debug bounding volume      // If the tile has tileset content load the external tileset      // If the tile cannot refine further select its nearest loaded ancestor      tileset._emptyTiles.push(tile);      loadTile(tile, frameState);      if (stoppedRefining) {        selectDesiredTile(tile, frameState);      }    } else if (tile.refine === Cesium3DTileRefine.ADD) {      // Additive tiles are always loaded and selected      selectDesiredTile(tile, frameState);      loadTile(tile, frameState);    } else if (tile.refine === Cesium3DTileRefine.REPLACE) {      if (inBaseTraversal(tile, baseScreenSpaceError)) {        // Always load tiles in the base traversal        // Select tiles that can't refine further        loadTile(tile, frameState);        if (stoppedRefining) {          selectDesiredTile(tile, frameState);        }      } else if (stoppedRefining) {        // In skip traversal, load and select tiles that can't refine further        selectDesiredTile(tile, frameState);        loadTile(tile, frameState);      } else if (reachedSkippingThreshold(tileset, tile)) {        // In skip traversal, load tiles that aren't skipped        loadTile(tile, frameState);      }    }    visitTile(tile, frameState);    touchTile(tile, frameState);  }}/** * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue. * This is a preorder traversal so children tiles are selected before ancestor tiles. * * The reason for the preorder traversal is so that tiles can easily be marked with their * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed. * This property is important for use in the stencil test because we want to render deeper tiles on top of their * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer. * * We want to select children before their ancestors because there is no guarantee on the relationship between * the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top * of ancestor regardless of true depth. The stencil tests used require children to be drawn first. * * NOTE: 3D Tiles uses 3 bits from the stencil buffer meaning this will not work when there is a chain of * selected tiles that is deeper than 7. This is not very likely. * * @private * @param {Cesium3DTile} root * @param {FrameState} frameState */function traverseAndSelect(root, frameState) {  const { selectTile, canTraverse } = Cesium3DTilesetTraversal;  const { stack, ancestorStack } = selectionTraversal;  let lastAncestor;  stack.push(root);  while (stack.length > 0 || ancestorStack.length > 0) {    selectionTraversal.stackMaximumLength = Math.max(      selectionTraversal.stackMaximumLength,      stack.length    );    selectionTraversal.ancestorStackMaximumLength = Math.max(      selectionTraversal.ancestorStackMaximumLength,      ancestorStack.length    );    if (ancestorStack.length > 0) {      const waitingTile = ancestorStack.peek();      if (waitingTile._stackLength === stack.length) {        ancestorStack.pop();        if (waitingTile !== lastAncestor) {          waitingTile._finalResolution = false;        }        selectTile(waitingTile, frameState);        continue;      }    }    const tile = stack.pop();    if (!defined(tile)) {      // stack is empty but ancestorStack isn't      continue;    }    const traverse = canTraverse(tile);    if (tile._shouldSelect) {      if (tile.refine === Cesium3DTileRefine.ADD) {        selectTile(tile, frameState);      } else {        tile._selectionDepth = ancestorStack.length;        if (tile._selectionDepth > 0) {          tile.tileset.hasMixedContent = true;        }        lastAncestor = tile;        if (!traverse) {          selectTile(tile, frameState);          continue;        }        ancestorStack.push(tile);        tile._stackLength = stack.length;      }    }    if (traverse) {      const children = tile.children;      for (let i = 0; i < children.length; ++i) {        const child = children[i];        if (child.isVisible) {          stack.push(child);        }      }    }  }}export default Cesium3DTilesetSkipTraversal;
 |