123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773 |
- import defined from "../Core/defined.js";
- import Intersect from "../Core/Intersect.js";
- import ManagedArray from "../Core/ManagedArray.js";
- import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
- import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
- /**
- * @private
- */
- function Cesium3DTilesetTraversal() {}
- function isVisible(tile) {
- return tile._visible && tile._inRequestVolume;
- }
- const traversal = {
- stack: new ManagedArray(),
- stackMaximumLength: 0,
- };
- const emptyTraversal = {
- 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;
- Cesium3DTilesetTraversal.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;
- updateTile(tileset, root, frameState);
- // The root tile is not visible
- if (!isVisible(root)) {
- return;
- }
- // The tileset doesn't meet the SSE requirement, therefore the tree does not need to be rendered
- if (
- root.getScreenSpaceError(frameState, true) <=
- tileset._maximumScreenSpaceError
- ) {
- return;
- }
- if (!skipLevelOfDetail(tileset)) {
- executeBaseTraversal(tileset, root, frameState);
- } else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
- executeSkipTraversal(tileset, root, frameState);
- } else {
- executeBaseAndSkipTraversal(tileset, root, frameState);
- }
- traversal.stack.trim(traversal.stackMaximumLength);
- emptyTraversal.stack.trim(emptyTraversal.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;
- const length = requestedTiles.length;
- for (let i = 0; i < length; ++i) {
- requestedTiles[i].updatePriority();
- }
- };
- function executeBaseTraversal(tileset, root, frameState) {
- const baseScreenSpaceError = tileset._maximumScreenSpaceError;
- const maximumScreenSpaceError = tileset._maximumScreenSpaceError;
- executeTraversal(
- tileset,
- root,
- baseScreenSpaceError,
- maximumScreenSpaceError,
- frameState
- );
- }
- function executeSkipTraversal(tileset, root, frameState) {
- const baseScreenSpaceError = Number.MAX_VALUE;
- const maximumScreenSpaceError = tileset._maximumScreenSpaceError;
- executeTraversal(
- tileset,
- root,
- baseScreenSpaceError,
- maximumScreenSpaceError,
- frameState
- );
- traverseAndSelect(tileset, root, frameState);
- }
- function executeBaseAndSkipTraversal(tileset, root, frameState) {
- const baseScreenSpaceError = Math.max(
- tileset.baseScreenSpaceError,
- tileset.maximumScreenSpaceError
- );
- const maximumScreenSpaceError = tileset.maximumScreenSpaceError;
- executeTraversal(
- tileset,
- root,
- baseScreenSpaceError,
- maximumScreenSpaceError,
- frameState
- );
- traverseAndSelect(tileset, root, frameState);
- }
- function skipLevelOfDetail(tileset) {
- return tileset._skipLevelOfDetail;
- }
- function addEmptyTile(tileset, tile) {
- tileset._emptyTiles.push(tile);
- }
- function selectTile(tileset, tile, frameState) {
- if (tile.contentVisibility(frameState) !== Intersect.OUTSIDE) {
- const tileContent = tile.content;
- if (tileContent.featurePropertiesDirty) {
- // A feature's property in this tile changed, the tile needs to be re-styled.
- tileContent.featurePropertiesDirty = false;
- tile.lastStyleTime = 0; // Force applying the style to this tile
- tileset._selectedTilesToStyle.push(tile);
- } else if (tile._selectedFrame < frameState.frameNumber - 1) {
- // Tile is newly selected; it is selected this frame, but was not selected last frame.
- tileset._selectedTilesToStyle.push(tile);
- }
- tile._selectedFrame = frameState.frameNumber;
- tileset._selectedTiles.push(tile);
- }
- }
- function selectDescendants(tileset, root, frameState) {
- 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;
- const childrenLength = children.length;
- for (let i = 0; i < childrenLength; ++i) {
- const child = children[i];
- if (isVisible(child)) {
- if (child.contentAvailable) {
- updateTile(tileset, child, frameState);
- touchTile(tileset, child, frameState);
- selectTile(tileset, child, frameState);
- } else if (child._depth - root._depth < descendantSelectionDepth) {
- // Continue traversing, but not too far
- stack.push(child);
- }
- }
- }
- }
- }
- function selectDesiredTile(tileset, tile, frameState) {
- if (!skipLevelOfDetail(tileset)) {
- if (tile.contentAvailable) {
- // The tile can be selected right away and does not require traverseAndSelect
- selectTile(tileset, tile, frameState);
- }
- return;
- }
- // 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(tileset, tile, frameState);
- }
- }
- function visitTile(tileset, tile, frameState) {
- ++tileset._statistics.visited;
- tile._visitedFrame = frameState.frameNumber;
- }
- function touchTile(tileset, tile, frameState) {
- if (tile._touchedFrame === frameState.frameNumber) {
- // Prevents another pass from touching the frame again
- return;
- }
- tileset._cache.touch(tile);
- tile._touchedFrame = frameState.frameNumber;
- }
- function updateMinimumMaximumPriority(tileset, tile) {
- tileset._maximumPriority.distance = Math.max(
- tile._priorityHolder._distanceToCamera,
- tileset._maximumPriority.distance
- );
- tileset._minimumPriority.distance = Math.min(
- tile._priorityHolder._distanceToCamera,
- tileset._minimumPriority.distance
- );
- tileset._maximumPriority.depth = Math.max(
- tile._depth,
- tileset._maximumPriority.depth
- );
- tileset._minimumPriority.depth = Math.min(
- tile._depth,
- tileset._minimumPriority.depth
- );
- tileset._maximumPriority.foveatedFactor = Math.max(
- tile._priorityHolder._foveatedFactor,
- tileset._maximumPriority.foveatedFactor
- );
- tileset._minimumPriority.foveatedFactor = Math.min(
- tile._priorityHolder._foveatedFactor,
- tileset._minimumPriority.foveatedFactor
- );
- tileset._maximumPriority.reverseScreenSpaceError = Math.max(
- tile._priorityReverseScreenSpaceError,
- tileset._maximumPriority.reverseScreenSpaceError
- );
- tileset._minimumPriority.reverseScreenSpaceError = Math.min(
- tile._priorityReverseScreenSpaceError,
- tileset._minimumPriority.reverseScreenSpaceError
- );
- }
- function isOnScreenLongEnough(tileset, tile, frameState) {
- // Prevent unnecessary loads while camera is moving by getting the ratio of travel distance to tile size.
- if (!tileset._cullRequestsWhileMoving) {
- return true;
- }
- const sphere = tile.boundingSphere;
- const diameter = Math.max(sphere.radius * 2.0, 1.0);
- const camera = frameState.camera;
- const deltaMagnitude =
- camera.positionWCDeltaMagnitude !== 0.0
- ? camera.positionWCDeltaMagnitude
- : camera.positionWCDeltaMagnitudeLastFrame;
- const movementRatio =
- (tileset.cullRequestsWhileMovingMultiplier * deltaMagnitude) / diameter; // How do n frames of this movement compare to the tile's physical size.
- return movementRatio < 1.0;
- }
- function loadTile(tileset, tile, frameState) {
- if (
- tile._requestedFrame === frameState.frameNumber ||
- (!hasUnloadedContent(tile) && !tile.contentExpired)
- ) {
- return;
- }
- if (!isOnScreenLongEnough(tileset, tile, frameState)) {
- return;
- }
- const cameraHasNotStoppedMovingLongEnough =
- frameState.camera.timeSinceMoved < tileset.foveatedTimeDelay;
- if (tile.priorityDeferred && cameraHasNotStoppedMovingLongEnough) {
- return;
- }
- tile._requestedFrame = frameState.frameNumber;
- tileset._requestedTiles.push(tile);
- }
- function updateVisibility(tileset, tile, frameState) {
- if (tile._updatedVisibilityFrame === tileset._updatedVisibilityFrame) {
- // Return early if visibility has already been checked during the traversal.
- // The visibility may have already been checked if the cullWithChildrenBounds optimization is used.
- return;
- }
- tile.updateVisibility(frameState);
- tile._updatedVisibilityFrame = tileset._updatedVisibilityFrame;
- }
- function anyChildrenVisible(tileset, tile, frameState) {
- let anyVisible = false;
- const children = tile.children;
- const length = children.length;
- for (let i = 0; i < length; ++i) {
- const child = children[i];
- updateVisibility(tileset, child, frameState);
- anyVisible = anyVisible || isVisible(child);
- }
- return anyVisible;
- }
- function meetsScreenSpaceErrorEarly(tileset, tile, frameState) {
- const parent = tile.parent;
- if (
- !defined(parent) ||
- parent.hasTilesetContent ||
- parent.hasImplicitContent ||
- parent.refine !== Cesium3DTileRefine.ADD
- ) {
- return false;
- }
- // Use parent's geometric error with child's box to see if the tile already meet the SSE
- return (
- tile.getScreenSpaceError(frameState, true) <=
- tileset._maximumScreenSpaceError
- );
- }
- function updateTileVisibility(tileset, tile, frameState) {
- updateVisibility(tileset, tile, frameState);
- if (!isVisible(tile)) {
- return;
- }
- const hasChildren = tile.children.length > 0;
- if ((tile.hasTilesetContent || tile.hasImplicitContent) && hasChildren) {
- // Use the root tile's visibility instead of this tile's visibility.
- // The root tile may be culled by the children bounds optimization in which
- // case this tile should also be culled.
- const child = tile.children[0];
- updateTileVisibility(tileset, child, frameState);
- tile._visible = child._visible;
- return;
- }
- if (meetsScreenSpaceErrorEarly(tileset, tile, frameState)) {
- tile._visible = false;
- return;
- }
- // Optimization - if none of the tile's children are visible then this tile isn't visible
- const replace = tile.refine === Cesium3DTileRefine.REPLACE;
- const useOptimization =
- tile._optimChildrenWithinParent ===
- Cesium3DTileOptimizationHint.USE_OPTIMIZATION;
- if (replace && useOptimization && hasChildren) {
- if (!anyChildrenVisible(tileset, tile, frameState)) {
- ++tileset._statistics.numberOfTilesCulledWithChildrenUnion;
- tile._visible = false;
- return;
- }
- }
- }
- function updateTile(tileset, tile, frameState) {
- // Reset some of the tile's flags and re-evaluate visibility
- updateTileVisibility(tileset, tile, frameState);
- tile.updateExpiration();
- // Request priority
- tile._wasMinPriorityChild = false;
- tile._priorityHolder = tile;
- updateMinimumMaximumPriority(tileset, tile);
- // SkipLOD
- tile._shouldSelect = false;
- tile._finalResolution = true;
- }
- function updateTileAncestorContentLinks(tile, frameState) {
- tile._ancestorWithContent = undefined;
- tile._ancestorWithContentAvailable = undefined;
- const parent = tile.parent;
- if (defined(parent)) {
- // 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.
- // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded.
- const hasContent =
- !hasUnloadedContent(parent) ||
- parent._requestedFrame === frameState.frameNumber;
- tile._ancestorWithContent = hasContent
- ? parent
- : parent._ancestorWithContent;
- tile._ancestorWithContentAvailable = parent.contentAvailable
- ? parent
- : parent._ancestorWithContentAvailable; // Links a descendant up to its contentAvailable ancestor as the traversal progresses.
- }
- }
- function hasEmptyContent(tile) {
- return (
- tile.hasEmptyContent || tile.hasTilesetContent || tile.hasImplicitContent
- );
- }
- function hasUnloadedContent(tile) {
- return !hasEmptyContent(tile) && tile.contentUnloaded;
- }
- 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))
- );
- }
- function sortChildrenByDistanceToCamera(a, b) {
- // Sort by farthest child first since this is going on a stack
- if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
- return b._centerZDepth - a._centerZDepth;
- }
- return b._distanceToCamera - a._distanceToCamera;
- }
- function updateAndPushChildren(tileset, tile, stack, frameState) {
- let i;
- const replace = tile.refine === Cesium3DTileRefine.REPLACE;
- const children = tile.children;
- const length = children.length;
- for (i = 0; i < length; ++i) {
- updateTile(tileset, children[i], frameState);
- }
- // Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
- children.sort(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 =
- !skipLevelOfDetail(tileset) && replace && !hasEmptyContent(tile);
- let refines = true;
- let anyChildrenVisible = false;
- // Determining min child
- let minIndex = -1;
- let minimumPriority = Number.MAX_VALUE;
- let child;
- for (i = 0; i < length; ++i) {
- child = children[i];
- if (isVisible(child)) {
- 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(tileset, child, frameState);
- touchTile(tileset, child, frameState);
- }
- if (checkRefines) {
- let childRefines;
- if (!child._inRequestVolume) {
- childRefines = false;
- } else if (hasEmptyContent(child)) {
- childRefines = executeEmptyTraversal(tileset, child, frameState);
- } else {
- childRefines = child.contentAvailable;
- }
- refines = refines && childRefines;
- }
- }
- if (!anyChildrenVisible) {
- refines = false;
- }
- if (minIndex !== -1 && !skipLevelOfDetail(tileset) && 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 (i = 0; i < length; ++i) {
- child = children[i];
- child._priorityHolder = priorityHolder;
- }
- }
- return refines;
- }
- function inBaseTraversal(tileset, tile, baseScreenSpaceError) {
- if (!skipLevelOfDetail(tileset)) {
- return true;
- }
- 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;
- }
- function canTraverse(tileset, tile) {
- if (tile.children.length === 0) {
- return false;
- }
- if (tile.hasTilesetContent || tile.hasImplicitContent) {
- // Traverse external tileset to visit its root tile
- // Don't traverse if the subtree is expired because it will be destroyed
- return !tile.contentExpired;
- }
- return tile._screenSpaceError > tileset._maximumScreenSpaceError;
- }
- function executeTraversal(
- tileset,
- root,
- baseScreenSpaceError,
- maximumScreenSpaceError,
- frameState
- ) {
- // Depth-first traversal that traverses all visible tiles and marks tiles for selection.
- // If skipLevelOfDetail is off then a tile does not refine until all children are loaded.
- // This is the traditional replacement refinement approach and is called the base traversal.
- // 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.
- 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 baseTraversal = inBaseTraversal(tileset, tile, baseScreenSpaceError);
- const add = tile.refine === Cesium3DTileRefine.ADD;
- const replace = tile.refine === Cesium3DTileRefine.REPLACE;
- const parent = tile.parent;
- const parentRefines = !defined(parent) || parent._refines;
- let refines = false;
- if (canTraverse(tileset, tile)) {
- refines =
- updateAndPushChildren(tileset, tile, stack, frameState) &&
- parentRefines;
- }
- const stoppedRefining = !refines && parentRefines;
- if (hasEmptyContent(tile)) {
- // 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
- addEmptyTile(tileset, tile, frameState);
- loadTile(tileset, tile, frameState);
- if (stoppedRefining) {
- selectDesiredTile(tileset, tile, frameState);
- }
- } else if (add) {
- // Additive tiles are always loaded and selected
- selectDesiredTile(tileset, tile, frameState);
- loadTile(tileset, tile, frameState);
- } else if (replace) {
- if (baseTraversal) {
- // Always load tiles in the base traversal
- // Select tiles that can't refine further
- loadTile(tileset, tile, frameState);
- if (stoppedRefining) {
- selectDesiredTile(tileset, tile, frameState);
- }
- } else if (stoppedRefining) {
- // In skip traversal, load and select tiles that can't refine further
- selectDesiredTile(tileset, tile, frameState);
- loadTile(tileset, tile, frameState);
- } else if (reachedSkippingThreshold(tileset, tile)) {
- // In skip traversal, load tiles that aren't skipped. In practice roughly half the tiles stay unloaded.
- loadTile(tileset, tile, frameState);
- }
- }
- visitTile(tileset, tile, frameState);
- touchTile(tileset, tile, frameState);
- tile._refines = refines;
- }
- }
- function executeEmptyTraversal(tileset, root, frameState) {
- // Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility.
- 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 stop at descendants with content
- const emptyContent = hasEmptyContent(tile);
- const traverse = emptyContent && canTraverse(tileset, tile);
- const emptyLeaf = emptyContent && 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(tileset, tile, frameState);
- if (!isVisible(tile)) {
- // Load tiles that aren't visible since they are still needed for the parent to refine
- loadTile(tileset, tile, frameState);
- touchTile(tileset, tile, frameState);
- }
- if (traverse) {
- for (let i = 0; i < childrenLength; ++i) {
- const child = children[i];
- stack.push(child);
- }
- }
- }
- return allDescendantsLoaded;
- }
- /**
- * 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
- */
- function traverseAndSelect(tileset, root, frameState) {
- const stack = selectionTraversal.stack;
- const ancestorStack = selectionTraversal.ancestorStack;
- 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(tileset, waitingTile, frameState);
- continue;
- }
- }
- const tile = stack.pop();
- if (!defined(tile)) {
- // stack is empty but ancestorStack isn't
- continue;
- }
- const add = tile.refine === Cesium3DTileRefine.ADD;
- const shouldSelect = tile._shouldSelect;
- const children = tile.children;
- const childrenLength = children.length;
- const traverse = canTraverse(tileset, tile);
- if (shouldSelect) {
- if (add) {
- selectTile(tileset, tile, frameState);
- } else {
- tile._selectionDepth = ancestorStack.length;
- if (tile._selectionDepth > 0) {
- tileset._hasMixedContent = true;
- }
- lastAncestor = tile;
- if (!traverse) {
- selectTile(tileset, tile, frameState);
- continue;
- }
- ancestorStack.push(tile);
- tile._stackLength = stack.length;
- }
- }
- if (traverse) {
- for (let i = 0; i < childrenLength; ++i) {
- const child = children[i];
- if (isVisible(child)) {
- stack.push(child);
- }
- }
- }
- }
- }
- export default Cesium3DTilesetTraversal;
|