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;
|