123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- 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.
- * A tile does not refine until all children are loaded.
- * This is the traditional replacement refinement approach and is called the base traversal.
- *
- * @alias Cesium3DTilesetBaseTraversal
- * @constructor
- *
- * @private
- */
- function Cesium3DTilesetBaseTraversal() {}
- const traversal = {
- stack: new ManagedArray(),
- stackMaximumLength: 0,
- };
- const emptyTraversal = {
- stack: new ManagedArray(),
- stackMaximumLength: 0,
- };
- /**
- * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
- *
- * @private
- * @param {Cesium3DTileset} tileset
- * @param {FrameState} frameState
- */
- Cesium3DTilesetBaseTraversal.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);
- traversal.stack.trim(traversal.stackMaximumLength);
- emptyTraversal.stack.trim(emptyTraversal.stackMaximumLength);
- // 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 a tile as selected if it has content available.
- *
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- */
- function selectDesiredTile(tile, frameState) {
- if (tile.contentAvailable) {
- Cesium3DTilesetTraversal.selectTile(tile, frameState);
- }
- }
- /**
- * @private
- * @param {Cesium3DTile} tile
- * @param {ManagedArray} stack
- * @param {FrameState} frameState
- * @returns {boolean}
- */
- function updateAndPushChildren(tile, stack, frameState) {
- const replace = tile.refine === Cesium3DTileRefine.REPLACE;
- 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 for skipLevelOfDetail
- children.sort(Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera);
- // For traditional replacement refinement only refine if all children are loaded.
- // Empty tiles are exempt since it looks better if children stream in as they are loaded to fill the empty space.
- const checkRefines = replace && tile.hasRenderableContent;
- let refines = true;
- let anyChildrenVisible = false;
- // Determining min child
- let minIndex = -1;
- let minimumPriority = Number.MAX_VALUE;
- for (let i = 0; i < children.length; ++i) {
- const child = children[i];
- if (child.isVisible) {
- stack.push(child);
- if (child._foveatedFactor < minimumPriority) {
- minIndex = i;
- minimumPriority = child._foveatedFactor;
- }
- anyChildrenVisible = true;
- } else if (checkRefines || tileset.loadSiblings) {
- // Keep non-visible children loaded since they are still needed before the parent can refine.
- // Or loadSiblings is true so always load tiles regardless of visibility.
- if (child._foveatedFactor < minimumPriority) {
- minIndex = i;
- minimumPriority = child._foveatedFactor;
- }
- loadTile(child, frameState);
- touchTile(child, frameState);
- }
- if (checkRefines) {
- let childRefines;
- if (!child._inRequestVolume) {
- childRefines = false;
- } else if (!child.hasRenderableContent) {
- childRefines = executeEmptyTraversal(child, frameState);
- } else {
- childRefines = child.contentAvailable;
- }
- refines = refines && childRefines;
- }
- }
- if (!anyChildrenVisible) {
- refines = false;
- }
- if (minIndex !== -1 && replace) {
- // An ancestor will hold the _foveatedFactor and _distanceToCamera for descendants between itself and its highest priority descendant. Siblings of a min children along the way use this ancestor as their priority holder as well.
- // Priority of all tiles that refer to the _foveatedFactor and _distanceToCamera stored in the common ancestor will be differentiated based on their _depth.
- const minPriorityChild = children[minIndex];
- minPriorityChild._wasMinPriorityChild = true;
- const priorityHolder =
- (tile._wasMinPriorityChild || tile === tileset.root) &&
- minimumPriority <= tile._priorityHolder._foveatedFactor
- ? tile._priorityHolder
- : tile; // This is where priority dependency chains are wired up or started anew.
- priorityHolder._foveatedFactor = Math.min(
- minPriorityChild._foveatedFactor,
- priorityHolder._foveatedFactor
- );
- priorityHolder._distanceToCamera = Math.min(
- minPriorityChild._distanceToCamera,
- priorityHolder._distanceToCamera
- );
- for (let i = 0; i < children.length; ++i) {
- children[i]._priorityHolder = priorityHolder;
- }
- }
- return refines;
- }
- /**
- * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
- * A tile does not refine until all children are loaded.
- * This is the traditional replacement refinement approach and is called the base traversal.
- *
- * @private
- * @param {Cesium3DTile} root
- * @param {FrameState} frameState
- */
- function executeTraversal(root, frameState) {
- const { tileset } = root;
- 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();
- 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
- 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) {
- loadTile(tile, frameState);
- if (stoppedRefining) {
- selectDesiredTile(tile, frameState);
- }
- }
- visitTile(tile, frameState);
- touchTile(tile, frameState);
- }
- }
- /**
- * Depth-first traversal that checks if all nearest descendants with content are loaded.
- * Ignores visibility.
- *
- * @private
- * @param {Cesium3DTile} root
- * @param {FrameState} frameState
- * @returns {boolean}
- */
- function executeEmptyTraversal(root, frameState) {
- const {
- canTraverse,
- updateTile,
- loadTile,
- touchTile,
- } = Cesium3DTilesetTraversal;
- let allDescendantsLoaded = true;
- const stack = emptyTraversal.stack;
- stack.push(root);
- while (stack.length > 0) {
- emptyTraversal.stackMaximumLength = Math.max(
- emptyTraversal.stackMaximumLength,
- stack.length
- );
- const tile = stack.pop();
- const children = tile.children;
- const childrenLength = children.length;
- // Only traverse if the tile is empty - traversal stops at descendants with content
- const traverse = !tile.hasRenderableContent && canTraverse(tile);
- const emptyLeaf = !tile.hasRenderableContent && tile.children.length === 0;
- // Traversal stops but the tile does not have content yet
- // There will be holes if the parent tries to refine to its children, so don't refine
- // One exception: a parent may refine even if one of its descendants is an empty leaf
- if (!traverse && !tile.contentAvailable && !emptyLeaf) {
- allDescendantsLoaded = false;
- }
- updateTile(tile, frameState);
- if (!tile.isVisible) {
- // Load tiles that aren't visible since they are still needed for the parent to refine
- loadTile(tile, frameState);
- touchTile(tile, frameState);
- }
- if (traverse) {
- for (let i = 0; i < childrenLength; ++i) {
- const child = children[i];
- stack.push(child);
- }
- }
- }
- return allDescendantsLoaded;
- }
- export default Cesium3DTilesetBaseTraversal;
|