326 lines
9.0 KiB
JavaScript
326 lines
9.0 KiB
JavaScript
import defined from "../Core/defined.js";
|
|
import DeveloperError from "../Core/DeveloperError.js";
|
|
import Intersect from "../Core/Intersect.js";
|
|
import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
|
|
import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
|
|
|
|
/**
|
|
* Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
|
|
* This type describes an interface and is not intended to be instantiated directly.
|
|
*
|
|
* @alias Cesium3DTilesetTraversal
|
|
* @constructor
|
|
* @abstract
|
|
*
|
|
* @see Cesium3DTilesetBaseTraversal
|
|
* @see Cesium3DTilesetSkipTraversal
|
|
* @see Cesium3DTilesetMostDetailedTraversal
|
|
*
|
|
* @private
|
|
*/
|
|
function Cesium3DTilesetTraversal() {}
|
|
|
|
/**
|
|
* Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
|
|
*
|
|
* @private
|
|
* @param {Cesium3DTileset} tileset
|
|
* @param {FrameState} frameState
|
|
*/
|
|
Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) {
|
|
DeveloperError.throwInstantiationError();
|
|
};
|
|
|
|
/**
|
|
* Sort by farthest child first since this is going on a stack
|
|
*
|
|
* @private
|
|
* @param {Cesium3DTile} a
|
|
* @param {Cesium3DTile} b
|
|
* @returns {number}
|
|
*/
|
|
Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera = function (a, b) {
|
|
if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
|
|
return b._centerZDepth - a._centerZDepth;
|
|
}
|
|
|
|
return b._distanceToCamera - a._distanceToCamera;
|
|
};
|
|
|
|
/**
|
|
* Determine if a tile can and should be traversed for children tiles that
|
|
* would contribute to rendering the current view
|
|
*
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @returns {boolean}
|
|
*/
|
|
Cesium3DTilesetTraversal.canTraverse = function (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 > tile.tileset.memoryAdjustedScreenSpaceError;
|
|
};
|
|
|
|
/**
|
|
* Mark a tile as selected, and add it to the tileset's list of selected tiles
|
|
*
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
*/
|
|
Cesium3DTilesetTraversal.selectTile = function (tile, frameState) {
|
|
if (tile.contentVisibility(frameState) === Intersect.OUTSIDE) {
|
|
return;
|
|
}
|
|
|
|
tile._wasSelectedLastFrame = true;
|
|
|
|
const { content, tileset } = tile;
|
|
if (content.featurePropertiesDirty) {
|
|
// A feature's property in this tile changed, the tile needs to be re-styled.
|
|
content.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._wasSelectedLastFrame = false;
|
|
}
|
|
|
|
tile._selectedFrame = frameState.frameNumber;
|
|
tileset._selectedTiles.push(tile);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
*/
|
|
Cesium3DTilesetTraversal.visitTile = function (tile, frameState) {
|
|
++tile.tileset._statistics.visited;
|
|
tile._visitedFrame = frameState.frameNumber;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
*/
|
|
Cesium3DTilesetTraversal.touchTile = function (tile, frameState) {
|
|
if (tile._touchedFrame === frameState.frameNumber) {
|
|
// Prevents another pass from touching the frame again
|
|
return;
|
|
}
|
|
tile.tileset._cache.touch(tile);
|
|
tile._touchedFrame = frameState.frameNumber;
|
|
};
|
|
|
|
/**
|
|
* Add a tile to the list of requested tiles, if appropriate
|
|
*
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
*/
|
|
Cesium3DTilesetTraversal.loadTile = function (tile, frameState) {
|
|
const { tileset } = tile;
|
|
if (
|
|
tile._requestedFrame === frameState.frameNumber ||
|
|
(!tile.hasUnloadedRenderableContent && !tile.contentExpired)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (!isOnScreenLongEnough(tile, frameState)) {
|
|
return;
|
|
}
|
|
|
|
const cameraHasNotStoppedMovingLongEnough =
|
|
frameState.camera.timeSinceMoved < tileset.foveatedTimeDelay;
|
|
if (tile.priorityDeferred && cameraHasNotStoppedMovingLongEnough) {
|
|
return;
|
|
}
|
|
|
|
tile._requestedFrame = frameState.frameNumber;
|
|
tileset._requestedTiles.push(tile);
|
|
};
|
|
|
|
/**
|
|
* Prevent unnecessary loads while camera is moving by getting the ratio of travel distance to tile size.
|
|
*
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
* @returns {boolean}
|
|
*/
|
|
function isOnScreenLongEnough(tile, frameState) {
|
|
const { tileset } = tile;
|
|
if (!tileset._cullRequestsWhileMoving) {
|
|
return true;
|
|
}
|
|
|
|
const {
|
|
positionWCDeltaMagnitude,
|
|
positionWCDeltaMagnitudeLastFrame,
|
|
} = frameState.camera;
|
|
const deltaMagnitude =
|
|
positionWCDeltaMagnitude !== 0.0
|
|
? positionWCDeltaMagnitude
|
|
: positionWCDeltaMagnitudeLastFrame;
|
|
|
|
// How do n frames of this movement compare to the tile's physical size.
|
|
const diameter = Math.max(tile.boundingSphere.radius * 2.0, 1.0);
|
|
const movementRatio =
|
|
(tileset.cullRequestsWhileMovingMultiplier * deltaMagnitude) / diameter;
|
|
|
|
return movementRatio < 1.0;
|
|
}
|
|
|
|
/**
|
|
* Reset some of the tile's flags and re-evaluate visibility and priority
|
|
*
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
*/
|
|
Cesium3DTilesetTraversal.updateTile = function (tile, frameState) {
|
|
updateTileVisibility(tile, frameState);
|
|
tile.updateExpiration();
|
|
|
|
tile._wasMinPriorityChild = false;
|
|
tile._priorityHolder = tile;
|
|
updateMinimumMaximumPriority(tile);
|
|
|
|
// SkipLOD
|
|
tile._shouldSelect = false;
|
|
tile._finalResolution = true;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
*/
|
|
function updateTileVisibility(tile, frameState) {
|
|
tile.updateVisibility(frameState);
|
|
|
|
if (!tile.isVisible) {
|
|
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(child, frameState);
|
|
tile._visible = child._visible;
|
|
return;
|
|
}
|
|
|
|
if (meetsScreenSpaceErrorEarly(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(tile, frameState)) {
|
|
++tile.tileset._statistics.numberOfTilesCulledWithChildrenUnion;
|
|
tile._visible = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
* @returns {boolean}
|
|
*/
|
|
function meetsScreenSpaceErrorEarly(tile, frameState) {
|
|
const { parent, tileset } = tile;
|
|
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.memoryAdjustedScreenSpaceError
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
* @param {FrameState} frameState
|
|
* @returns {boolean}
|
|
*/
|
|
function anyChildrenVisible(tile, frameState) {
|
|
let anyVisible = false;
|
|
const children = tile.children;
|
|
for (let i = 0; i < children.length; ++i) {
|
|
const child = children[i];
|
|
child.updateVisibility(frameState);
|
|
anyVisible = anyVisible || child.isVisible;
|
|
}
|
|
return anyVisible;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @param {Cesium3DTile} tile
|
|
*/
|
|
function updateMinimumMaximumPriority(tile) {
|
|
const minimumPriority = tile.tileset._minimumPriority;
|
|
const maximumPriority = tile.tileset._maximumPriority;
|
|
const priorityHolder = tile._priorityHolder;
|
|
|
|
maximumPriority.distance = Math.max(
|
|
priorityHolder._distanceToCamera,
|
|
maximumPriority.distance
|
|
);
|
|
minimumPriority.distance = Math.min(
|
|
priorityHolder._distanceToCamera,
|
|
minimumPriority.distance
|
|
);
|
|
maximumPriority.depth = Math.max(tile._depth, maximumPriority.depth);
|
|
minimumPriority.depth = Math.min(tile._depth, minimumPriority.depth);
|
|
maximumPriority.foveatedFactor = Math.max(
|
|
priorityHolder._foveatedFactor,
|
|
maximumPriority.foveatedFactor
|
|
);
|
|
minimumPriority.foveatedFactor = Math.min(
|
|
priorityHolder._foveatedFactor,
|
|
minimumPriority.foveatedFactor
|
|
);
|
|
maximumPriority.reverseScreenSpaceError = Math.max(
|
|
tile._priorityReverseScreenSpaceError,
|
|
maximumPriority.reverseScreenSpaceError
|
|
);
|
|
minimumPriority.reverseScreenSpaceError = Math.min(
|
|
tile._priorityReverseScreenSpaceError,
|
|
minimumPriority.reverseScreenSpaceError
|
|
);
|
|
}
|
|
|
|
export default Cesium3DTilesetTraversal;
|