From 46a40bddc49caca9e0e4d3510a7ed83dcff1c93a Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 8 Mar 2023 14:00:38 -0500 Subject: [PATCH 01/21] Minor edits for clarity and consistency with Coding Guide --- packages/engine/Source/Scene/Cesium3DTile.js | 30 ++++++-------- .../engine/Source/Scene/Cesium3DTileset.js | 18 ++++---- packages/engine/Source/Scene/Model/Model.js | 3 +- .../Source/Scene/Model/ModelDrawCommand.js | 41 +++++++------------ 4 files changed, 37 insertions(+), 55 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index 1fb9138aa66f..db922b9a637b 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -1796,19 +1796,15 @@ Cesium3DTile.prototype.createBoundingVolume = function ( ); } - if (defined(boundingVolumeHeader.box)) { - return createBox(boundingVolumeHeader.box, transform, result); + const { box, region, sphere } = boundingVolumeHeader; + if (defined(box)) { + return createBox(box, transform, result); } - if (defined(boundingVolumeHeader.region)) { - return createRegion( - boundingVolumeHeader.region, - transform, - this._initialTransform, - result - ); + if (defined(region)) { + return createRegion(region, transform, this._initialTransform, result); } - if (defined(boundingVolumeHeader.sphere)) { - return createSphere(boundingVolumeHeader.sphere, transform, result); + if (defined(sphere)) { + return createSphere(sphere, transform, result); } throw new RuntimeError( "boundingVolume must contain a sphere, region, or box" @@ -2043,18 +2039,18 @@ function updateClippingPlanes(tile, tileset) { * @param {object} passOptions */ Cesium3DTile.prototype.update = function (tileset, frameState, passOptions) { - const commandStart = frameState.commandList.length; + const { commandList } = frameState; + const commandStart = commandList.length; updateClippingPlanes(this, tileset); applyDebugSettings(this, tileset, frameState, passOptions); updateContent(this, tileset, frameState); - const commandEnd = frameState.commandList.length; - const commandsLength = commandEnd - commandStart; - this._commandsLength = commandsLength; + const commandEnd = commandList.length; + this._commandsLength = commandEnd - commandStart; - for (let i = 0; i < commandsLength; ++i) { - const command = frameState.commandList[commandStart + i]; + for (let i = commandStart; i < commandEnd; ++i) { + const command = commandList[i]; const translucent = command.pass === Pass.TRANSLUCENT; command.depthForTranslucentClassification = translucent; } diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index a5042eabefeb..5511a21ef52b 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -2713,18 +2713,15 @@ function updateTiles(tileset, frameState, passOptions) { tileset._styleEngine.applyStyle(tileset); tileset._styleApplied = true; - const isRender = passOptions.isRender; - const { statistics, tileVisible } = tileset; - const commandList = frameState.commandList; + const { commandList, context } = frameState; const numberOfInitialCommands = commandList.length; const selectedTiles = tileset._selectedTiles; - const selectedLength = selectedTiles.length; const bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && - frameState.context.stencilBuffer && - selectedLength > 0; + context.stencilBuffer && + selectedTiles.length > 0; tileset._backfaceCommands.length = 0; @@ -2741,8 +2738,11 @@ function updateTiles(tileset, frameState, passOptions) { commandList.push(tileset._stencilClearCommand); } + const { statistics, tileVisible } = tileset; + const isRender = passOptions.isRender; const lengthBeforeUpdate = commandList.length; - for (let i = 0; i < selectedLength; ++i) { + + for (let i = 0; i < selectedTiles.length; ++i) { const tile = selectedTiles[i]; // Raise the tileVisible event before update in case the tileVisible event // handler makes changes that update needs to apply to WebGL resources @@ -3019,8 +3019,6 @@ function update(tileset, frameState, passStatistics, passOptions) { const statistics = tileset._statistics; statistics.clear(); - const isRender = passOptions.isRender; - // Resets the visibility check for each pass ++tileset._updatedVisibilityFrame; @@ -3042,7 +3040,7 @@ function update(tileset, frameState, passStatistics, passOptions) { // Update pass statistics Cesium3DTilesetStatistics.clone(statistics, passStatistics); - if (isRender) { + if (passOptions.isRender) { const credits = tileset._credits; if (defined(credits) && statistics.selected !== 0) { for (let i = 0; i < credits.length; ++i) { diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 59980de218e2..a25ea3b0806c 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2444,8 +2444,7 @@ function supportsSkipLevelOfDetail(frameState) { * @private */ Model.prototype.hasSkipLevelOfDetail = function (frameState) { - const is3DTiles = ModelType.is3DTiles(this.type); - if (!is3DTiles) { + if (!ModelType.is3DTiles(this.type)) { return false; } diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommand.js b/packages/engine/Source/Scene/Model/ModelDrawCommand.js index a82b75fc4b52..d24adea9cf1e 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommand.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommand.js @@ -437,9 +437,8 @@ function updateShadows(drawCommand) { const receiveShadows = ShadowMode.receiveShadows(shadows); const derivedCommands = drawCommand._derivedCommands; - const length = derivedCommands.length; - for (let i = 0; i < length; ++i) { + for (let i = 0; i < derivedCommands.length; ++i) { const derivedCommand = derivedCommands[i]; if (derivedCommand.updateShadows) { const command = derivedCommand.command; @@ -451,11 +450,9 @@ function updateShadows(drawCommand) { function updateBackFaceCulling(drawCommand) { const backFaceCulling = drawCommand.backFaceCulling; - const derivedCommands = drawCommand._derivedCommands; - const length = derivedCommands.length; - for (let i = 0; i < length; ++i) { + for (let i = 0; i < derivedCommands.length; ++i) { const derivedCommand = derivedCommands[i]; if (derivedCommand.updateBackFaceCulling) { const command = derivedCommand.command; @@ -468,11 +465,9 @@ function updateBackFaceCulling(drawCommand) { function updateCullFace(drawCommand) { const cullFace = drawCommand.cullFace; - const derivedCommands = drawCommand._derivedCommands; - const length = derivedCommands.length; - for (let i = 0; i < length; ++i) { + for (let i = 0; i < derivedCommands.length; ++i) { const derivedCommand = derivedCommands[i]; if (derivedCommand.updateCullFace) { const command = derivedCommand.command; @@ -485,11 +480,9 @@ function updateCullFace(drawCommand) { function updateDebugShowBoundingVolume(drawCommand) { const debugShowBoundingVolume = drawCommand.debugShowBoundingVolume; - const derivedCommands = drawCommand._derivedCommands; - const length = derivedCommands.length; - for (let i = 0; i < length; ++i) { + for (let i = 0; i < derivedCommands.length; ++i) { const derivedCommand = derivedCommands[i]; if (derivedCommand.updateDebugShowBoundingVolume) { const command = derivedCommand.command; @@ -537,15 +530,10 @@ ModelDrawCommand.prototype.pushCommands = function (frameState, result) { } if (this._needsSkipLevelOfDetailCommands) { - const content = this._model.content; - const tileset = content.tileset; - const tile = content.tile; - - const hasMixedContent = tileset._hasMixedContent; - const finalResolution = tile._finalResolution; + const { tileset, tile } = this._model.content; - if (hasMixedContent) { - if (!finalResolution) { + if (tileset._hasMixedContent) { + if (!tile._finalResolution) { pushCommand( tileset._backfaceCommands, this._skipLodBackfaceCommand, @@ -846,13 +834,14 @@ function deriveSkipLodStencilCommand(command) { const stencilCommand = DrawCommand.shallowClone(command); const renderState = clone(command.renderState, true); // The stencil reference is updated dynamically; see updateSkipLodStencilCommand(). - renderState.stencilTest.enabled = true; - renderState.stencilTest.mask = StencilConstants.SKIP_LOD_MASK; - renderState.stencilTest.reference = StencilConstants.CESIUM_3D_TILE_MASK; - renderState.stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL; - renderState.stencilTest.frontOperation.zPass = StencilOperation.REPLACE; - renderState.stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL; - renderState.stencilTest.backOperation.zPass = StencilOperation.REPLACE; + const { stencilTest } = renderState; + stencilTest.enabled = true; + stencilTest.mask = StencilConstants.SKIP_LOD_MASK; + stencilTest.reference = StencilConstants.CESIUM_3D_TILE_MASK; + stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL; + stencilTest.frontOperation.zPass = StencilOperation.REPLACE; + stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL; + stencilTest.backOperation.zPass = StencilOperation.REPLACE; renderState.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK | StencilConstants.SKIP_LOD_MASK; From 8f8a869ef2a126cd33383c3dcf46684ccb47bfc6 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 9 Mar 2023 14:24:13 -0500 Subject: [PATCH 02/21] Move basic tile checks from traversals to Cesium3DTile --- packages/engine/Source/Scene/Cesium3DTile.js | 59 +++++++++++---- .../Cesium3DTilesetMostDetailedTraversal.js | 18 ++--- .../Source/Scene/Cesium3DTilesetTraversal.js | 72 +++++-------------- 3 files changed, 67 insertions(+), 82 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index db922b9a637b..ef846759eeef 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -578,6 +578,22 @@ Object.defineProperties(Cesium3DTile.prototype, { }, }, + /** + * Determines if the tile is visible within the current field of view + * + * @memberof Cesium3DTile.prototype + * + * @type {boolean} + * @readonly + * + * @private + */ + isVisible: { + get: function () { + return this._visible && this._inRequestVolume; + }, + }, + /** * Returns the extras property in the tileset JSON for this tile, which contains application specific metadata. * Returns undefined if extras does not exist. @@ -618,6 +634,27 @@ Object.defineProperties(Cesium3DTile.prototype, { }, }, + /** + * Determines if the tile's content is renderable. false if the + * tile has empty content or if it points to an external tileset or implicit content + * + * @memberof Cesium3DTile.prototype + * + * @type {boolean} + * @readonly + * + * @private + */ + hasRenderableContent: { + get: function () { + return ( + !this.hasEmptyContent && + !this.hasTilesetContent && + !this.hasImplicitContent + ); + }, + }, + /** * Determines if the tile has available content to render. true if the tile's * content is ready or if it has expired content that renders while new content loads; otherwise, @@ -633,10 +670,7 @@ Object.defineProperties(Cesium3DTile.prototype, { contentAvailable: { get: function () { return ( - (this.contentReady && - !this.hasEmptyContent && - !this.hasTilesetContent && - !this.hasImplicitContent) || + (this.contentReady && this.hasRenderableContent) || (defined(this._expiredContent) && !this.contentFailed) ); }, @@ -991,6 +1025,11 @@ function getPriorityReverseScreenSpaceError(tileset, tile) { */ Cesium3DTile.prototype.updateVisibility = function (frameState) { const { parent, tileset } = this; + if (this._updatedVisibilityFrame === tileset._updatedVisibilityFrame) { + // The tile has already been updated for this frame + return; + } + const parentTransform = defined(parent) ? parent.computedTransform : tileset.modelMatrix; @@ -1021,6 +1060,8 @@ Cesium3DTile.prototype.updateVisibility = function (frameState) { this ); this.priorityDeferred = isPriorityDeferred(this, frameState); + + this._updatedVisibilityFrame = tileset._updatedVisibilityFrame; }; /** @@ -1416,11 +1457,7 @@ Cesium3DTile.prototype.cancelRequests = function () { * @private */ Cesium3DTile.prototype.unloadContent = function () { - if ( - this.hasEmptyContent || - this.hasTilesetContent || - this.hasImplicitContent - ) { + if (!this.hasRenderableContent) { return; } @@ -1896,8 +1933,6 @@ function applyDebugSettings(tile, tileset, frameState, passOptions) { const hasContentBoundingVolume = defined(tile._contentHeader) && defined(tile._contentHeader.boundingVolume); - const empty = - tile.hasEmptyContent || tile.hasTilesetContent || tile.hasImplicitContent; const showVolume = tileset.debugShowBoundingVolume || @@ -1906,7 +1941,7 @@ function applyDebugSettings(tile, tileset, frameState, passOptions) { let color; if (!tile._finalResolution) { color = Color.YELLOW; - } else if (empty) { + } else if (!tile.hasRenderableContent) { color = Color.DARKGRAY; } else { color = Color.WHITE; diff --git a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js index 7fe1e10ba204..4b7f0a3858ef 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js @@ -28,7 +28,7 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( const root = tileset.root; root.updateVisibility(frameState); - if (!isVisible(root)) { + if (!root.isVisible) { return ready; } @@ -55,7 +55,7 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( touchTile(tileset, tile, frameState); selectDesiredTile(tileset, tile, frameState); - if (!hasEmptyContent(tile) && !tile.contentAvailable) { + if (tile.hasRenderableContent && !tile.contentAvailable) { ready = false; } } @@ -68,18 +68,8 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( return ready; }; -function isVisible(tile) { - return tile._visible && tile._inRequestVolume; -} - -function hasEmptyContent(tile) { - return ( - tile.hasEmptyContent || tile.hasTilesetContent || tile.hasImplicitContent - ); -} - function hasUnloadedContent(tile) { - return !hasEmptyContent(tile) && tile.contentUnloaded; + return tile.hasRenderableContent && tile.contentUnloaded; } function canTraverse(tileset, tile) { @@ -107,7 +97,7 @@ function updateAndPushChildren(tileset, tile, stack, frameState) { for (let i = 0; i < length; ++i) { const child = children[i]; child.updateVisibility(frameState); - if (isVisible(child)) { + if (child.isVisible) { stack.push(child); } } diff --git a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js index 7da76155fed3..e04794e71f26 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js @@ -53,7 +53,7 @@ Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) { const root = tileset.root; updateTile(root, frameState); - if (!isVisible(root)) { + if (!root.isVisible) { return; } @@ -92,15 +92,6 @@ Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) { } }; -/** - * @private - * @param {Cesium3DTile} tile - * @returns {boolean} Whether the tile is within the current field of view - */ -function isVisible(tile) { - return tile._visible && tile._inRequestVolume; -} - /** * The private ._skipLevelOfDetail flag on a Cesium3DTileset is updated in * Cesium3DTileset.prototype.prePassesUpdate to confirm if skipping is actually @@ -159,7 +150,7 @@ function selectDescendants(root, frameState) { const children = tile.children; for (let i = 0; i < children.length; ++i) { const child = children[i]; - if (isVisible(child)) { + if (child.isVisible) { if (child.contentAvailable) { updateTile(child, frameState); touchTile(child, frameState); @@ -329,25 +320,6 @@ function loadTile(tile, frameState) { tileset._requestedTiles.push(tile); } -/** - * Wrap Cesium3DTile.prototype.updateVisibility to avoid repeated updates - * - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -function updateVisibility(tile, frameState) { - const updatedVisibilityFrame = tile.tileset._updatedVisibilityFrame; - if (tile._updatedVisibilityFrame === 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 = updatedVisibilityFrame; -} - /** * @private * @param {Cesium3DTile} tile @@ -359,8 +331,8 @@ function anyChildrenVisible(tile, frameState) { const children = tile.children; for (let i = 0; i < children.length; ++i) { const child = children[i]; - updateVisibility(child, frameState); - anyVisible = anyVisible || isVisible(child); + child.updateVisibility(frameState); + anyVisible = anyVisible || child.isVisible; } return anyVisible; } @@ -395,9 +367,9 @@ function meetsScreenSpaceErrorEarly(tile, frameState) { * @param {FrameState} frameState */ function updateTileVisibility(tile, frameState) { - updateVisibility(tile, frameState); + tile.updateVisibility(frameState); - if (!isVisible(tile)) { + if (!tile.isVisible) { return; } @@ -482,24 +454,13 @@ function updateTileAncestorContentLinks(tile, frameState) { : parent._ancestorWithContentAvailable; } -/** - * @private - * @param {Cesium3DTile} tile - * @returns {boolean} - */ -function hasEmptyContent(tile) { - return ( - tile.hasEmptyContent || tile.hasTilesetContent || tile.hasImplicitContent - ); -} - /** * @private * @param {Cesium3DTile} tile * @returns {boolean} */ function hasUnloadedContent(tile) { - return !hasEmptyContent(tile) && tile.contentUnloaded; + return tile.hasRenderableContent && tile.contentUnloaded; } /** @@ -560,7 +521,7 @@ function updateAndPushChildren(tile, stack, frameState) { // 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); + !skipLevelOfDetail(tileset) && replace && tile.hasRenderableContent; let refines = true; let anyChildrenVisible = false; @@ -571,7 +532,7 @@ function updateAndPushChildren(tile, stack, frameState) { for (let i = 0; i < children.length; ++i) { const child = children[i]; - if (isVisible(child)) { + if (child.isVisible) { stack.push(child); if (child._foveatedFactor < minimumPriority) { minIndex = i; @@ -592,7 +553,7 @@ function updateAndPushChildren(tile, stack, frameState) { let childRefines; if (!child._inRequestVolume) { childRefines = false; - } else if (hasEmptyContent(child)) { + } else if (!child.hasRenderableContent) { childRefines = executeEmptyTraversal(child, frameState); } else { childRefines = child.contentAvailable; @@ -716,7 +677,7 @@ function executeTraversal(root, baseScreenSpaceError, frameState) { const stoppedRefining = !tile._refines && parentRefines; - if (hasEmptyContent(tile)) { + 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 @@ -776,10 +737,9 @@ function executeEmptyTraversal(root, frameState) { 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(tile); - const emptyLeaf = emptyContent && tile.children.length === 0; + // 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 @@ -789,7 +749,7 @@ function executeEmptyTraversal(root, frameState) { } updateTile(tile, frameState); - if (!isVisible(tile)) { + 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); @@ -884,7 +844,7 @@ function traverseAndSelect(root, frameState) { const children = tile.children; for (let i = 0; i < children.length; ++i) { const child = children[i]; - if (isVisible(child)) { + if (child.isVisible) { stack.push(child); } } From 73bf2f32113bbe92b87401e9d460b70e6927edea Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 9 Mar 2023 18:57:50 -0500 Subject: [PATCH 03/21] Use private skipLevelOfDetail flag more consistently --- .../engine/Source/Scene/Cesium3DTileset.js | 9 +++++++ .../Source/Scene/Cesium3DTilesetTraversal.js | 25 +++++-------------- packages/engine/Source/Scene/Model/Model.js | 2 +- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 5511a21ef52b..85efe729096a 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -654,6 +654,15 @@ function Cesium3DTileset(options) { * @default false */ this.skipLevelOfDetail = defaultValue(options.skipLevelOfDetail, false); + + /** + * Whether this tileset is actually skipping levels of detail. + * The user option may have been disabled if all tiles are using additive refinement, + * or if some tiles have a content type for which rendering does not support skipping + * + * @private + * @type {boolean} + */ this._skipLevelOfDetail = this.skipLevelOfDetail; this._disableSkipLevelOfDetail = false; diff --git a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js index e04794e71f26..a58ca1c0a465 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js @@ -64,7 +64,7 @@ Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) { return; } - const baseScreenSpaceError = !skipLevelOfDetail(tileset) + const baseScreenSpaceError = !tileset._skipLevelOfDetail ? tileset._maximumScreenSpaceError : tileset.immediatelyLoadDesiredLevelOfDetail ? Number.MAX_VALUE @@ -72,7 +72,7 @@ Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) { executeTraversal(root, baseScreenSpaceError, frameState); - if (skipLevelOfDetail(tileset)) { + if (tileset._skipLevelOfDetail) { traverseAndSelect(root, frameState); } @@ -92,19 +92,6 @@ Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) { } }; -/** - * The private ._skipLevelOfDetail flag on a Cesium3DTileset is updated in - * Cesium3DTileset.prototype.prePassesUpdate to confirm if skipping is actually - * possible and allowed, even when the public .skipLevelOfDetail flag is true - * - * @private - * @param {Cesium3DTileset} tileset - * @returns {boolean} Whether to do LOD skipping - */ -function skipLevelOfDetail(tileset) { - return tileset._skipLevelOfDetail; -} - /** * Mark a tile as selected, and add it to the tileset's list of selected tiles * @@ -174,7 +161,7 @@ function selectDescendants(root, frameState) { * @param {FrameState} frameState */ function selectDesiredTile(tile, frameState) { - if (!skipLevelOfDetail(tile.tileset)) { + if (!tile.tileset._skipLevelOfDetail) { if (tile.contentAvailable) { // The tile can be selected right away and does not require traverseAndSelect selectTile(tile, frameState); @@ -521,7 +508,7 @@ function updateAndPushChildren(tile, stack, frameState) { // 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 && tile.hasRenderableContent; + !tileset._skipLevelOfDetail && replace && tile.hasRenderableContent; let refines = true; let anyChildrenVisible = false; @@ -566,7 +553,7 @@ function updateAndPushChildren(tile, stack, frameState) { refines = false; } - if (minIndex !== -1 && !skipLevelOfDetail(tileset) && replace) { + if (minIndex !== -1 && !tileset.skipLevelOfDetail && 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]; @@ -604,7 +591,7 @@ function updateAndPushChildren(tile, stack, frameState) { */ function inBaseTraversal(tile, baseScreenSpaceError) { const { tileset } = tile; - if (!skipLevelOfDetail(tileset)) { + if (!tileset._skipLevelOfDetail) { return true; } if (tileset.immediatelyLoadDesiredLevelOfDetail) { diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index a25ea3b0806c..da958787bf2d 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2449,7 +2449,7 @@ Model.prototype.hasSkipLevelOfDetail = function (frameState) { } const tileset = this._content.tileset; - return supportsSkipLevelOfDetail(frameState) && tileset.skipLevelOfDetail; + return supportsSkipLevelOfDetail(frameState) && tileset._skipLevelOfDetail; }; /** From b5962b097a8dd94febc6bccf1444e36fdf4755f3 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 9 Mar 2023 20:37:35 -0500 Subject: [PATCH 04/21] Pull TraversalUtility functions out of Cesium3DTilesetTraversal --- .../Source/Scene/Cesium3DTilesetTraversal.js | 303 +---------------- .../engine/Source/Scene/TraversalUtility.js | 311 ++++++++++++++++++ 2 files changed, 320 insertions(+), 294 deletions(-) create mode 100644 packages/engine/Source/Scene/TraversalUtility.js diff --git a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js index a58ca1c0a465..afd1637ea0c7 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js @@ -1,8 +1,7 @@ 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"; +import TraversalUtility from "./TraversalUtility.js"; /** * @private @@ -51,7 +50,7 @@ Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) { tileset._hasMixedContent = false; const root = tileset.root; - updateTile(root, frameState); + TraversalUtility.updateTile(root, frameState); if (!root.isVisible) { return; @@ -92,32 +91,6 @@ Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) { } }; -/** - * Mark a tile as selected, and add it to the tileset's list of selected tiles - * - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -function selectTile(tile, frameState) { - if (tile.contentVisibility(frameState) === Intersect.OUTSIDE) { - return; - } - - 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._selectedFrame = frameState.frameNumber; - tileset._selectedTiles.push(tile); -} - /** * Mark descendant tiles for rendering, and update as needed * @@ -126,6 +99,7 @@ function selectTile(tile, frameState) { * @param {FrameState} frameState */ function selectDescendants(root, frameState) { + const { updateTile, touchTile, selectTile } = TraversalUtility; const stack = descendantTraversal.stack; stack.push(root); while (stack.length > 0) { @@ -164,7 +138,7 @@ function selectDesiredTile(tile, frameState) { if (!tile.tileset._skipLevelOfDetail) { if (tile.contentAvailable) { // The tile can be selected right away and does not require traverseAndSelect - selectTile(tile, frameState); + TraversalUtility.selectTile(tile, frameState); } return; } @@ -183,233 +157,6 @@ function selectDesiredTile(tile, frameState) { } } -/** - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -function visitTile(tile, frameState) { - ++tile.tileset._statistics.visited; - tile._visitedFrame = frameState.frameNumber; -} - -/** - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -function touchTile(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; -} - -/** - * @private - * @param {Cesium3DTile} tile - */ -function updateMinimumMaximumPriority(tile) { - const { - _maximumPriority: maximumPriority, - _minimumPriority: minimumPriority, - } = tile.tileset; - 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 - ); -} - -/** - * 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; -} - -/** - * Add a tile to the list of requested tiles, if appropriate - * - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -function loadTile(tile, frameState) { - const { tileset } = tile; - if ( - tile._requestedFrame === frameState.frameNumber || - (!hasUnloadedContent(tile) && !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); -} - -/** - * @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 - * @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._maximumScreenSpaceError - ); -} - -/** - * @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; - } - } -} - -/** - * Reset some of the tile's flags and re-evaluate visibility and priority - * - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -function updateTile(tile, frameState) { - updateTileVisibility(tile, frameState); - tile.updateExpiration(); - - tile._wasMinPriorityChild = false; - tile._priorityHolder = tile; - updateMinimumMaximumPriority(tile); - - // SkipLOD - tile._shouldSelect = false; - tile._finalResolution = true; -} - /** * Update links to the ancestor tiles that have content * @@ -471,22 +218,6 @@ function reachedSkippingThreshold(tileset, tile) { ); } -/** - * Sort by farthest child first since this is going on a stack - * - * @private - * @param {Cesium3DTile} a - * @param {Cesium3DTile} b - * @returns {number} - */ -function sortChildrenByDistanceToCamera(a, b) { - if (b._distanceToCamera === 0 && a._distanceToCamera === 0) { - return b._centerZDepth - a._centerZDepth; - } - - return b._distanceToCamera - a._distanceToCamera; -} - /** * @private * @param {Cesium3DTile} tile @@ -497,13 +228,14 @@ function sortChildrenByDistanceToCamera(a, b) { function updateAndPushChildren(tile, stack, frameState) { const replace = tile.refine === Cesium3DTileRefine.REPLACE; const { tileset, children } = tile; + const { updateTile, loadTile, touchTile } = TraversalUtility; 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(sortChildrenByDistanceToCamera); + children.sort(TraversalUtility.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. @@ -608,26 +340,6 @@ function inBaseTraversal(tile, baseScreenSpaceError) { return tile._screenSpaceError > baseScreenSpaceError; } -/** - * 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} - */ -function canTraverse(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._maximumScreenSpaceError; -} - /** * 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. @@ -643,6 +355,7 @@ function canTraverse(tile) { */ function executeTraversal(root, baseScreenSpaceError, frameState) { const { tileset } = root; + const { canTraverse, loadTile, visitTile, touchTile } = TraversalUtility; const stack = traversal.stack; stack.push(root); @@ -710,6 +423,7 @@ function executeTraversal(root, baseScreenSpaceError, frameState) { * @returns {boolean} */ function executeEmptyTraversal(root, frameState) { + const { canTraverse, updateTile, loadTile, touchTile } = TraversalUtility; let allDescendantsLoaded = true; const stack = emptyTraversal.stack; stack.push(root); @@ -774,6 +488,7 @@ function executeEmptyTraversal(root, frameState) { * @param {FrameState} frameState */ function traverseAndSelect(root, frameState) { + const { selectTile, canTraverse } = TraversalUtility; const { stack, ancestorStack } = selectionTraversal; let lastAncestor; diff --git a/packages/engine/Source/Scene/TraversalUtility.js b/packages/engine/Source/Scene/TraversalUtility.js new file mode 100644 index 000000000000..c02e5c050eed --- /dev/null +++ b/packages/engine/Source/Scene/TraversalUtility.js @@ -0,0 +1,311 @@ +import defined from "../Core/defined.js"; +import Intersect from "../Core/Intersect.js"; +import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js"; +import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; + +/** + * Utility functions for {@link Cesium3DTilesetTraversal}. + * + * @private + */ +function TraversalUtility() {} + +/** + * Sort by farthest child first since this is going on a stack + * + * @private + * @param {Cesium3DTile} a + * @param {Cesium3DTile} b + * @returns {number} + */ +TraversalUtility.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} + */ +TraversalUtility.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._maximumScreenSpaceError; +}; + +/** + * Mark a tile as selected, and add it to the tileset's list of selected tiles + * + * @private + * @param {Cesium3DTile} tile + * @param {FrameState} frameState + */ +TraversalUtility.selectTile = function (tile, frameState) { + if (tile.contentVisibility(frameState) === Intersect.OUTSIDE) { + return; + } + + 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._selectedFrame = frameState.frameNumber; + tileset._selectedTiles.push(tile); +}; + +/** + * @private + * @param {Cesium3DTile} tile + * @param {FrameState} frameState + */ +TraversalUtility.visitTile = function (tile, frameState) { + ++tile.tileset._statistics.visited; + tile._visitedFrame = frameState.frameNumber; +}; + +/** + * @private + * @param {Cesium3DTile} tile + * @param {FrameState} frameState + */ +TraversalUtility.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 + */ +TraversalUtility.loadTile = function (tile, frameState) { + const { tileset } = tile; + if ( + tile._requestedFrame === frameState.frameNumber || + (!hasUnloadedContent(tile) && !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); +}; + +/** + * @private + * @param {Cesium3DTile} tile + * @returns {boolean} + */ +function hasUnloadedContent(tile) { + return tile.hasRenderableContent && tile.contentUnloaded; +} + +/** + * 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 + */ +TraversalUtility.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._maximumScreenSpaceError + ); +} + +/** + * @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 { + _maximumPriority: maximumPriority, + _minimumPriority: minimumPriority, + } = tile.tileset; + 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 TraversalUtility; From a9942be24a2dac5b1cce2a174a143cd26b068468 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 10 Mar 2023 13:10:04 -0500 Subject: [PATCH 05/21] Continue separating traversal logic --- packages/engine/Source/Scene/Cesium3DTile.js | 16 +++++++ .../Cesium3DTilesetMostDetailedTraversal.js | 44 +++++++------------ .../Source/Scene/Cesium3DTilesetTraversal.js | 11 +---- .../engine/Source/Scene/TraversalUtility.js | 11 +---- 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index ef846759eeef..f3de7ee3fb06 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -710,6 +710,22 @@ Object.defineProperties(Cesium3DTile.prototype, { }, }, + /** + * Determines if the tile has renderable content which is unloaded + * + * @memberof Cesium3DTile.prototype + * + * @type {boolean} + * @readonly + * + * @private + */ + hasUnloadedRenderableContent: { + get: function () { + return this.hasRenderableContent && this.contentUnloaded; + }, + }, + /** * Determines if the tile's content is expired. true if tile's * content is expired; otherwise, false. diff --git a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js index 4b7f0a3858ef..af99a6a05956 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js @@ -1,6 +1,7 @@ import Intersect from "../Core/Intersect.js"; import ManagedArray from "../Core/ManagedArray.js"; import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; +import TraversalUtility from "./TraversalUtility.js"; /** * Traversal that loads all leaves that intersect the camera frustum. @@ -32,8 +33,10 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( return ready; } + const { touchTile, visitTile } = TraversalUtility; + const stack = traversal.stack; - stack.push(tileset.root); + stack.push(root); while (stack.length > 0) { traversal.stackMaximumLength = Math.max( @@ -44,23 +47,23 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( const tile = stack.pop(); const add = tile.refine === Cesium3DTileRefine.ADD; const replace = tile.refine === Cesium3DTileRefine.REPLACE; - const traverse = canTraverse(tileset, tile); + const traverse = canTraverse(tile); if (traverse) { - updateAndPushChildren(tileset, tile, stack, frameState); + updateAndPushChildren(tile, stack, frameState); } if (add || (replace && !traverse)) { loadTile(tileset, tile); - touchTile(tileset, tile, frameState); - selectDesiredTile(tileset, tile, frameState); + touchTile(tile, frameState); + selectDesiredTile(tile, frameState); if (tile.hasRenderableContent && !tile.contentAvailable) { ready = false; } } - visitTile(tileset); + visitTile(tile, frameState); } traversal.stack.trim(traversal.stackMaximumLength); @@ -72,7 +75,7 @@ function hasUnloadedContent(tile) { return tile.hasRenderableContent && tile.contentUnloaded; } -function canTraverse(tileset, tile) { +function canTraverse(tile) { if (tile.children.length === 0) { return false; } @@ -87,14 +90,13 @@ function canTraverse(tileset, tile) { return true; } - return true; // Keep traversing until a leave is hit + return true; // Keep traversing until a leaf is hit } -function updateAndPushChildren(tileset, tile, stack, frameState) { - const children = tile.children; - const length = children.length; +function updateAndPushChildren(tile, stack, frameState) { + const { children } = tile; - for (let i = 0; i < length; ++i) { + for (let i = 0; i < children.length; ++i) { const child = children[i]; child.updateVisibility(frameState); if (child.isVisible) { @@ -110,25 +112,13 @@ function loadTile(tileset, tile) { } } -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 visitTile(tileset) { - ++tileset.statistics.visited; -} - -function selectDesiredTile(tileset, tile, frameState) { +function selectDesiredTile(tile, frameState) { if ( tile.contentAvailable && tile.contentVisibility(frameState) !== Intersect.OUTSIDE ) { - tileset._selectedTiles.push(tile); + tile.tileset._selectedTiles.push(tile); } } + export default Cesium3DTilesetMostDetailedTraversal; diff --git a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js index afd1637ea0c7..a0423856dd41 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js @@ -173,7 +173,7 @@ function updateTileAncestorContentLinks(tile, frameState) { return; } const parentHasContent = - !hasUnloadedContent(parent) || + !parent.hasUnloadedRenderableContent || parent._requestedFrame === frameState.frameNumber; // ancestorWithContent is an ancestor that has content or has the potential to have @@ -188,15 +188,6 @@ function updateTileAncestorContentLinks(tile, frameState) { : parent._ancestorWithContentAvailable; } -/** - * @private - * @param {Cesium3DTile} tile - * @returns {boolean} - */ -function hasUnloadedContent(tile) { - return tile.hasRenderableContent && tile.contentUnloaded; -} - /** * 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 diff --git a/packages/engine/Source/Scene/TraversalUtility.js b/packages/engine/Source/Scene/TraversalUtility.js index c02e5c050eed..ffd90c70663d 100644 --- a/packages/engine/Source/Scene/TraversalUtility.js +++ b/packages/engine/Source/Scene/TraversalUtility.js @@ -107,7 +107,7 @@ TraversalUtility.loadTile = function (tile, frameState) { const { tileset } = tile; if ( tile._requestedFrame === frameState.frameNumber || - (!hasUnloadedContent(tile) && !tile.contentExpired) + (!tile.hasUnloadedRenderableContent && !tile.contentExpired) ) { return; } @@ -126,15 +126,6 @@ TraversalUtility.loadTile = function (tile, frameState) { tileset._requestedTiles.push(tile); }; -/** - * @private - * @param {Cesium3DTile} tile - * @returns {boolean} - */ -function hasUnloadedContent(tile) { - return tile.hasRenderableContent && tile.contentUnloaded; -} - /** * Prevent unnecessary loads while camera is moving by getting the ratio of travel distance to tile size. * From fd2d32dc758f571213e1044044c5a0bb2d4d6e72 Mon Sep 17 00:00:00 2001 From: jiangheng Date: Wed, 15 Mar 2023 15:52:30 +0800 Subject: [PATCH 06/21] Fix DebugCameraPrimitive render error --- CHANGES.md | 1 + packages/engine/Source/Core/OrthographicFrustum.js | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b2e896226f2a..cf721dcd0bb4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ ##### Fixes :wrench: - Fixed issue where passing `children` in the Entity constructor options will override children. [#11101](https://github.com/CesiumGS/cesium/issues/11101) +- Fixed issue when render `OrthographicFrustum` geometry by `DebugCameraPrimitive`. [#11159](https://github.com/CesiumGS/cesium/issues/11159) #### @cesium/widgets diff --git a/packages/engine/Source/Core/OrthographicFrustum.js b/packages/engine/Source/Core/OrthographicFrustum.js index 6e45f6b1b199..ff629fa1e259 100644 --- a/packages/engine/Source/Core/OrthographicFrustum.js +++ b/packages/engine/Source/Core/OrthographicFrustum.js @@ -63,6 +63,15 @@ function OrthographicFrustum(options) { */ this.far = defaultValue(options.far, 500000000.0); this._far = this.far; + + if ( + defined(this.width) && + defined(this.aspectRatio) && + defined(this.near) && + defined(this.far) + ) { + update(this); + } } /** From 141032fd204ae5d06fe5deb21d348057da0b85d4 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 16 Mar 2023 14:25:40 -0400 Subject: [PATCH 07/21] Split base and skip traversal into separate classes --- .../engine/Source/Scene/Cesium3DTilePass.js | 19 +- .../engine/Source/Scene/Cesium3DTileset.js | 25 +- .../Scene/Cesium3DTilesetBaseTraversal.js | 294 ++++++++ .../Cesium3DTilesetMostDetailedTraversal.js | 10 +- .../Scene/Cesium3DTilesetSkipTraversal.js | 413 +++++++++++ .../Source/Scene/Cesium3DTilesetTraversal.js | 664 ++++++------------ .../engine/Source/Scene/TraversalUtility.js | 302 -------- 7 files changed, 960 insertions(+), 767 deletions(-) create mode 100644 packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js create mode 100644 packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js delete mode 100644 packages/engine/Source/Scene/TraversalUtility.js diff --git a/packages/engine/Source/Scene/Cesium3DTilePass.js b/packages/engine/Source/Scene/Cesium3DTilePass.js index c210f917734f..4d3017d30556 100644 --- a/packages/engine/Source/Scene/Cesium3DTilePass.js +++ b/packages/engine/Source/Scene/Cesium3DTilePass.js @@ -1,6 +1,3 @@ -import Cesium3DTilesetMostDetailedTraversal from "./Cesium3DTilesetMostDetailedTraversal.js"; -import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js"; - /** * The pass in which a 3D Tileset is updated. * @@ -21,56 +18,56 @@ const Cesium3DTilePass = { const passOptions = new Array(Cesium3DTilePass.NUMBER_OF_PASSES); passOptions[Cesium3DTilePass.RENDER] = Object.freeze({ - traversal: Cesium3DTilesetTraversal, + pass: Cesium3DTilePass.RENDER, isRender: true, requestTiles: true, ignoreCommands: false, }); passOptions[Cesium3DTilePass.PICK] = Object.freeze({ - traversal: Cesium3DTilesetTraversal, + pass: Cesium3DTilePass.PICK, isRender: false, requestTiles: false, ignoreCommands: false, }); passOptions[Cesium3DTilePass.SHADOW] = Object.freeze({ - traversal: Cesium3DTilesetTraversal, + pass: Cesium3DTilePass.SHADOW, isRender: false, requestTiles: true, ignoreCommands: false, }); passOptions[Cesium3DTilePass.PRELOAD] = Object.freeze({ - traversal: Cesium3DTilesetTraversal, + pass: Cesium3DTilePass.SHADOW, isRender: false, requestTiles: true, ignoreCommands: true, }); passOptions[Cesium3DTilePass.PRELOAD_FLIGHT] = Object.freeze({ - traversal: Cesium3DTilesetTraversal, + pass: Cesium3DTilePass.PRELOAD_FLIGHT, isRender: false, requestTiles: true, ignoreCommands: true, }); passOptions[Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK] = Object.freeze({ - traversal: Cesium3DTilesetTraversal, + pass: Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK, isRender: false, requestTiles: true, ignoreCommands: true, }); passOptions[Cesium3DTilePass.MOST_DETAILED_PRELOAD] = Object.freeze({ - traversal: Cesium3DTilesetMostDetailedTraversal, + pass: Cesium3DTilePass.MOST_DETAILED_PRELOAD, isRender: false, requestTiles: true, ignoreCommands: true, }); passOptions[Cesium3DTilePass.MOST_DETAILED_PICK] = Object.freeze({ - traversal: Cesium3DTilesetMostDetailedTraversal, + pass: Cesium3DTilePass.MOST_DETAILED_PICK, isRender: false, requestTiles: false, ignoreCommands: false, diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 85efe729096a..181f63ec81fb 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -52,6 +52,9 @@ import StencilConstants from "./StencilConstants.js"; import TileBoundingRegion from "./TileBoundingRegion.js"; import TileBoundingSphere from "./TileBoundingSphere.js"; import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; +import Cesium3DTilesetMostDetailedTraversal from "./Cesium3DTilesetMostDetailedTraversal.js"; +import Cesium3DTilesetBaseTraversal from "./Cesium3DTilesetBaseTraversal.js"; +import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js"; /** * A {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles tileset}, @@ -3038,7 +3041,9 @@ function update(tileset, frameState, passStatistics, passOptions) { tileset._cullRequestsWhileMoving = tileset.cullRequestsWhileMoving && !tileset._modelMatrixChanged; - const ready = passOptions.traversal.selectTiles(tileset, frameState); + const ready = tileset + .getTraversal(passOptions) + .selectTiles(tileset, frameState); if (passOptions.requestTiles) { requestTiles(tileset); @@ -3063,6 +3068,24 @@ function update(tileset, frameState, passStatistics, passOptions) { return ready; } +/** + * @private + * @param {object} passOptions + * @returns {Cesium3DTilesetTraversal} + */ +Cesium3DTileset.prototype.getTraversal = function (passOptions) { + const { pass } = passOptions; + if ( + pass === Cesium3DTilePass.MOST_DETAILED_PRELOAD || + pass === Cesium3DTilePass.MOST_DETAILED_PICK + ) { + return Cesium3DTilesetMostDetailedTraversal; + } + return this._skipLevelOfDetail + ? Cesium3DTilesetSkipTraversal + : Cesium3DTilesetBaseTraversal; +}; + /** * @private * @param {FrameState} frameState diff --git a/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js new file mode 100644 index 000000000000..fbb56f56cb87 --- /dev/null +++ b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js @@ -0,0 +1,294 @@ +import defined from "../Core/defined.js"; +import ManagedArray from "../Core/ManagedArray.js"; +import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; +import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js"; + +/** + * @private + */ +function Cesium3DTilesetBaseTraversal() {} + +const traversal = { + stack: new ManagedArray(), + stackMaximumLength: 0, +}; + +const emptyTraversal = { + stack: new ManagedArray(), + stackMaximumLength: 0, +}; + +/** + * @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) { + // The tile can be selected right away and does not require traverseAndSelect + Cesium3DTilesetTraversal.selectTile(tile, frameState); + } + return; +} + +/** + * @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; diff --git a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js index af99a6a05956..f8a66b2f551c 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js @@ -1,7 +1,7 @@ import Intersect from "../Core/Intersect.js"; import ManagedArray from "../Core/ManagedArray.js"; import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; -import TraversalUtility from "./TraversalUtility.js"; +import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js"; /** * Traversal that loads all leaves that intersect the camera frustum. @@ -33,7 +33,7 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( return ready; } - const { touchTile, visitTile } = TraversalUtility; + const { touchTile, visitTile } = Cesium3DTilesetTraversal; const stack = traversal.stack; stack.push(root); @@ -71,10 +71,6 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( return ready; }; -function hasUnloadedContent(tile) { - return tile.hasRenderableContent && tile.contentUnloaded; -} - function canTraverse(tile) { if (tile.children.length === 0) { return false; @@ -106,7 +102,7 @@ function updateAndPushChildren(tile, stack, frameState) { } function loadTile(tileset, tile) { - if (hasUnloadedContent(tile) || tile.contentExpired) { + if (tile.hasUnloadedRenderableContent || tile.contentExpired) { tile._priority = 0.0; // Highest priority tileset._requestedTiles.push(tile); } diff --git a/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js new file mode 100644 index 000000000000..a74e04481dee --- /dev/null +++ b/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js @@ -0,0 +1,413 @@ +import defined from "../Core/defined.js"; +import ManagedArray from "../Core/ManagedArray.js"; +import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; +import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js"; + +/** + * @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; + +/** + * @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; diff --git a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js index a0423856dd41..2c32ced77219 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js @@ -1,548 +1,320 @@ import defined from "../Core/defined.js"; -import ManagedArray from "../Core/ManagedArray.js"; +import DeveloperError from "../Core/DeveloperError.js"; +import Intersect from "../Core/Intersect.js"; +import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js"; import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; -import TraversalUtility from "./TraversalUtility.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 + * + * @see Cesium3DTilesetBaseTraversal + * @see Cesium3DTilesetSkipTraversal + * @see Cesium3DTilesetMostDetailedTraversal + * * @private */ function Cesium3DTilesetTraversal() {} -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; - /** * @private * @param {Cesium3DTileset} tileset * @param {FrameState} frameState */ 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; - TraversalUtility.updateTile(root, frameState); - - if (!root.isVisible) { - return; - } - - if ( - root.getScreenSpaceError(frameState, true) <= - tileset._maximumScreenSpaceError - ) { - return; - } - - const baseScreenSpaceError = !tileset._skipLevelOfDetail - ? tileset._maximumScreenSpaceError - : tileset.immediatelyLoadDesiredLevelOfDetail - ? Number.MAX_VALUE - : Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError); - - executeTraversal(root, baseScreenSpaceError, frameState); - - if (tileset._skipLevelOfDetail) { - traverseAndSelect(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; - for (let i = 0; i < requestedTiles.length; ++i) { - requestedTiles[i].updatePriority(); - } + DeveloperError.throwInstantiationError(); }; /** - * Mark descendant tiles for rendering, and update as needed + * Sort by farthest child first since this is going on a stack * * @private - * @param {Cesium3DTile} root - * @param {FrameState} frameState + * @param {Cesium3DTile} a + * @param {Cesium3DTile} b + * @returns {number} */ -function selectDescendants(root, frameState) { - const { updateTile, touchTile, selectTile } = TraversalUtility; - 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); - } - } - } +Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera = function (a, b) { + if (b._distanceToCamera === 0 && a._distanceToCamera === 0) { + return b._centerZDepth - a._centerZDepth; } -} + + return b._distanceToCamera - a._distanceToCamera; +}; /** - * 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 + * Determine if a tile can and should be traversed for children tiles that + * would contribute to rendering the current view * * @private * @param {Cesium3DTile} tile - * @param {FrameState} frameState + * @returns {boolean} */ -function selectDesiredTile(tile, frameState) { - if (!tile.tileset._skipLevelOfDetail) { - if (tile.contentAvailable) { - // The tile can be selected right away and does not require traverseAndSelect - TraversalUtility.selectTile(tile, frameState); - } - return; +Cesium3DTilesetTraversal.canTraverse = function (tile) { + if (tile.children.length === 0) { + return false; } - - // 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); + 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._maximumScreenSpaceError; +}; /** - * Update links to the ancestor tiles that have content + * Mark a tile as selected, and add it to the tileset's list of selected tiles * * @private * @param {Cesium3DTile} tile * @param {FrameState} frameState */ -function updateTileAncestorContentLinks(tile, frameState) { - tile._ancestorWithContent = undefined; - tile._ancestorWithContentAvailable = undefined; - - const { parent } = tile; - if (!defined(parent)) { +Cesium3DTilesetTraversal.selectTile = function (tile, frameState) { + if (tile.contentVisibility(frameState) === Intersect.OUTSIDE) { 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; -} + 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._selectedFrame = frameState.frameNumber; + tileset._selectedTiles.push(tile); +}; /** - * 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 + * @param {FrameState} frameState */ -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)) - ); -} +Cesium3DTilesetTraversal.visitTile = function (tile, frameState) { + ++tile.tileset._statistics.visited; + tile._visitedFrame = frameState.frameNumber; +}; /** * @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 } = TraversalUtility; - - for (let i = 0; i < children.length; ++i) { - updateTile(children[i], 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; +}; - // Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail - children.sort(TraversalUtility.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 = - !tileset._skipLevelOfDetail && 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; - } +/** + * 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 (!anyChildrenVisible) { - refines = false; + if (!isOnScreenLongEnough(tile, frameState)) { + return; } - if (minIndex !== -1 && !tileset.skipLevelOfDetail && 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; - } + const cameraHasNotStoppedMovingLongEnough = + frameState.camera.timeSinceMoved < tileset.foveatedTimeDelay; + if (tile.priorityDeferred && cameraHasNotStoppedMovingLongEnough) { + return; } - return refines; -} + tile._requestedFrame = frameState.frameNumber; + tileset._requestedTiles.push(tile); +}; /** - * Determine if a tile is part of the base traversal. - * If not, this tile could be considered for level of detail skipping + * Prevent unnecessary loads while camera is moving by getting the ratio of travel distance to tile size. * * @private * @param {Cesium3DTile} tile - * @param {number} baseScreenSpaceError + * @param {FrameState} frameState * @returns {boolean} */ -function inBaseTraversal(tile, baseScreenSpaceError) { +function isOnScreenLongEnough(tile, frameState) { const { tileset } = tile; - if (!tileset._skipLevelOfDetail) { + if (!tileset._cullRequestsWhileMoving) { 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; + + 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; } /** - * 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. + * Reset some of the tile's flags and re-evaluate visibility and priority * * @private - * @param {Cesium3DTile} root - * @param {number} baseScreenSpaceError + * @param {Cesium3DTile} tile * @param {FrameState} frameState */ -function executeTraversal(root, baseScreenSpaceError, frameState) { - const { tileset } = root; - const { canTraverse, loadTile, visitTile, touchTile } = TraversalUtility; - const stack = traversal.stack; - stack.push(root); +Cesium3DTilesetTraversal.updateTile = function (tile, frameState) { + updateTileVisibility(tile, frameState); + tile.updateExpiration(); - while (stack.length > 0) { - traversal.stackMaximumLength = Math.max( - traversal.stackMaximumLength, - stack.length - ); + tile._wasMinPriorityChild = false; + tile._priorityHolder = tile; + updateMinimumMaximumPriority(tile); - const tile = stack.pop(); + // SkipLOD + tile._shouldSelect = false; + tile._finalResolution = true; +}; + +/** + * @private + * @param {Cesium3DTile} tile + * @param {FrameState} frameState + */ +function updateTileVisibility(tile, frameState) { + tile.updateVisibility(frameState); - updateTileAncestorContentLinks(tile, frameState); - const parent = tile.parent; - const parentRefines = !defined(parent) || parent._refines; + if (!tile.isVisible) { + return; + } - tile._refines = canTraverse(tile) - ? updateAndPushChildren(tile, stack, frameState) && parentRefines - : false; + 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; + } - const stoppedRefining = !tile._refines && parentRefines; + if (meetsScreenSpaceErrorEarly(tile, frameState)) { + tile._visible = false; + return; + } - 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); - } + // 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; } - - 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 {Cesium3DTile} tile * @param {FrameState} frameState * @returns {boolean} */ -function executeEmptyTraversal(root, frameState) { - const { canTraverse, updateTile, loadTile, touchTile } = TraversalUtility; - 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); - } - } +function meetsScreenSpaceErrorEarly(tile, frameState) { + const { parent, tileset } = tile; + if ( + !defined(parent) || + parent.hasTilesetContent || + parent.hasImplicitContent || + parent.refine !== Cesium3DTileRefine.ADD + ) { + return false; } - return allDescendantsLoaded; + // 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 + ); } /** - * 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 {Cesium3DTile} tile * @param {FrameState} frameState + * @returns {boolean} */ -function traverseAndSelect(root, frameState) { - const { selectTile, canTraverse } = TraversalUtility; - 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); - } - } - } +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 { + _maximumPriority: maximumPriority, + _minimumPriority: minimumPriority, + } = tile.tileset; + 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; diff --git a/packages/engine/Source/Scene/TraversalUtility.js b/packages/engine/Source/Scene/TraversalUtility.js deleted file mode 100644 index ffd90c70663d..000000000000 --- a/packages/engine/Source/Scene/TraversalUtility.js +++ /dev/null @@ -1,302 +0,0 @@ -import defined from "../Core/defined.js"; -import Intersect from "../Core/Intersect.js"; -import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js"; -import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; - -/** - * Utility functions for {@link Cesium3DTilesetTraversal}. - * - * @private - */ -function TraversalUtility() {} - -/** - * Sort by farthest child first since this is going on a stack - * - * @private - * @param {Cesium3DTile} a - * @param {Cesium3DTile} b - * @returns {number} - */ -TraversalUtility.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} - */ -TraversalUtility.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._maximumScreenSpaceError; -}; - -/** - * Mark a tile as selected, and add it to the tileset's list of selected tiles - * - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -TraversalUtility.selectTile = function (tile, frameState) { - if (tile.contentVisibility(frameState) === Intersect.OUTSIDE) { - return; - } - - 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._selectedFrame = frameState.frameNumber; - tileset._selectedTiles.push(tile); -}; - -/** - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -TraversalUtility.visitTile = function (tile, frameState) { - ++tile.tileset._statistics.visited; - tile._visitedFrame = frameState.frameNumber; -}; - -/** - * @private - * @param {Cesium3DTile} tile - * @param {FrameState} frameState - */ -TraversalUtility.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 - */ -TraversalUtility.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 - */ -TraversalUtility.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._maximumScreenSpaceError - ); -} - -/** - * @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 { - _maximumPriority: maximumPriority, - _minimumPriority: minimumPriority, - } = tile.tileset; - 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 TraversalUtility; From 556ff8823738dfc035ca518fc2e30a9cd65afc9b Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 16 Mar 2023 18:12:02 -0400 Subject: [PATCH 08/21] Add specs to check Cesium3DTilesetTraversal interfaces --- .../Source/Scene/Cesium3DTilesetBaseTraversal.js | 2 -- .../Specs/Scene/Cesium3DTilesetBaseTraversalSpec.js | 12 ++++++++++++ .../Cesium3DTilesetMostDetailedTraversalSpec.js | 12 ++++++++++++ .../Specs/Scene/Cesium3DTilesetSkipTraversalSpec.js | 12 ++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 packages/engine/Specs/Scene/Cesium3DTilesetBaseTraversalSpec.js create mode 100644 packages/engine/Specs/Scene/Cesium3DTilesetMostDetailedTraversalSpec.js create mode 100644 packages/engine/Specs/Scene/Cesium3DTilesetSkipTraversalSpec.js diff --git a/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js index fbb56f56cb87..57b776b6b082 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js @@ -71,10 +71,8 @@ Cesium3DTilesetBaseTraversal.selectTiles = function (tileset, frameState) { */ function selectDesiredTile(tile, frameState) { if (tile.contentAvailable) { - // The tile can be selected right away and does not require traverseAndSelect Cesium3DTilesetTraversal.selectTile(tile, frameState); } - return; } /** diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetBaseTraversalSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetBaseTraversalSpec.js new file mode 100644 index 000000000000..3c4782ee83c0 --- /dev/null +++ b/packages/engine/Specs/Scene/Cesium3DTilesetBaseTraversalSpec.js @@ -0,0 +1,12 @@ +import { + Cesium3DTilesetTraversal, + Cesium3DTilesetBaseTraversal, +} from "../../index.js"; + +describe("Scene/Cesium3DTilesetBaseTraversal", function () { + it("conforms to Cesium3DTilesetTraversal interface", function () { + expect(Cesium3DTilesetBaseTraversal).toConformToInterface( + Cesium3DTilesetTraversal + ); + }); +}); diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetMostDetailedTraversalSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetMostDetailedTraversalSpec.js new file mode 100644 index 000000000000..364c9369df1a --- /dev/null +++ b/packages/engine/Specs/Scene/Cesium3DTilesetMostDetailedTraversalSpec.js @@ -0,0 +1,12 @@ +import { + Cesium3DTilesetTraversal, + Cesium3DTilesetMostDetailedTraversal, +} from "../../index.js"; + +describe("Scene/Cesium3DTilesetMostDetailedTraversal", function () { + it("conforms to Cesium3DTilesetTraversal interface", function () { + expect(Cesium3DTilesetMostDetailedTraversal).toConformToInterface( + Cesium3DTilesetTraversal + ); + }); +}); diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSkipTraversalSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSkipTraversalSpec.js new file mode 100644 index 000000000000..bb1418cebfcf --- /dev/null +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSkipTraversalSpec.js @@ -0,0 +1,12 @@ +import { + Cesium3DTilesetTraversal, + Cesium3DTilesetSkipTraversal, +} from "../../index.js"; + +describe("Scene/Cesium3DTilesetSkipTraversal", function () { + it("conforms to Cesium3DTilesetTraversal interface", function () { + expect(Cesium3DTilesetSkipTraversal).toConformToInterface( + Cesium3DTilesetTraversal + ); + }); +}); From def23b0932663e5bfee09b7bc0e5192092179275 Mon Sep 17 00:00:00 2001 From: jiangheng Date: Thu, 23 Mar 2023 09:25:06 +0800 Subject: [PATCH 09/21] Make both projectionMatrix called --- packages/engine/Source/Core/FrustumGeometry.js | 2 +- packages/engine/Source/Core/OrthographicFrustum.js | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/engine/Source/Core/FrustumGeometry.js b/packages/engine/Source/Core/FrustumGeometry.js index bece354b733c..0446378d3261 100644 --- a/packages/engine/Source/Core/FrustumGeometry.js +++ b/packages/engine/Source/Core/FrustumGeometry.js @@ -295,8 +295,8 @@ FrustumGeometry._computeNearFarPlanes = function ( let inverseView; let inverseViewProjection; + const projection = frustum.projectionMatrix; if (frustumType === PERSPECTIVE) { - const projection = frustum.projectionMatrix; const viewProjection = Matrix4.multiply( projection, view, diff --git a/packages/engine/Source/Core/OrthographicFrustum.js b/packages/engine/Source/Core/OrthographicFrustum.js index ff629fa1e259..6e45f6b1b199 100644 --- a/packages/engine/Source/Core/OrthographicFrustum.js +++ b/packages/engine/Source/Core/OrthographicFrustum.js @@ -63,15 +63,6 @@ function OrthographicFrustum(options) { */ this.far = defaultValue(options.far, 500000000.0); this._far = this.far; - - if ( - defined(this.width) && - defined(this.aspectRatio) && - defined(this.near) && - defined(this.far) - ) { - update(this); - } } /** From 4a74622a0972f2694e3134715473e88d7b7a4cb9 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 24 Mar 2023 12:26:23 -0400 Subject: [PATCH 10/21] Fix example in CustomShaderGuide --- Documentation/CustomShaderGuide/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/CustomShaderGuide/README.md b/Documentation/CustomShaderGuide/README.md index 7a95e34cac73..814749755184 100644 --- a/Documentation/CustomShaderGuide/README.md +++ b/Documentation/CustomShaderGuide/README.md @@ -143,12 +143,12 @@ The user is responsible for assigning a value to this varying in const customShader = new Cesium.CustomShader({ // Varying is declared here varyings: { - v_selectedColor: VaryingType.VEC3, + v_selectedColor: Cesium.VaryingType.VEC4, }, // User assigns the varying in the vertex shader vertexShaderText: ` void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) { - float positiveX = step(0.0, positionMC.x); + float positiveX = step(0.0, vsOutput.positionMC.x); v_selectedColor = mix( vsInput.attributes.color_0, vsInput.attributes.color_1, @@ -159,7 +159,7 @@ const customShader = new Cesium.CustomShader({ // User uses the varying in the fragment shader fragmentShaderText: ` void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) { - material.diffuse = v_selectedColor; + material.diffuse = v_selectedColor.rgb; } `, }); From dbe0d457ef88329e5d290595234ba83e7bd1842a Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 27 Mar 2023 11:04:18 -0400 Subject: [PATCH 11/21] Fix broken links in CustomShaderGuide --- Documentation/CustomShaderGuide/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/CustomShaderGuide/README.md b/Documentation/CustomShaderGuide/README.md index 814749755184..dcd4bc1186ad 100644 --- a/Documentation/CustomShaderGuide/README.md +++ b/Documentation/CustomShaderGuide/README.md @@ -837,7 +837,7 @@ For `ENUM` type metadata, the statistics struct for that property should contain ## `czm_modelVertexOutput` struct -This struct is built-in, see the [documentation comment](../../../Shaders/Builtin/Structs/modelVertexOutput.glsl). +This struct is built-in, see the [documentation comment](../../packages/engine/Source/Shaders/Builtin/Structs/modelVertexOutput.glsl). This struct contains the output of the custom vertex shader. This includes: @@ -855,7 +855,7 @@ This struct contains the output of the custom vertex shader. This includes: ## `czm_modelMaterial` struct -This struct is a built-in, see the [documentation comment](../../Source/Shaders/Builtin/Structs/modelMaterial.glsl). This is similar to `czm_material` from the old Fabric system, but has slightly different fields as this one supports PBR lighting. +This struct is a built-in, see the [documentation comment](../../packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl). This is similar to `czm_material` from the old Fabric system, but has slightly different fields as this one supports PBR lighting. This struct serves as the basic input/output of the fragment shader pipeline stages. For example: From 63f720d45e2964cfa79a0aaebd5ed5bb0cd03feb Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 27 Mar 2023 14:33:22 -0400 Subject: [PATCH 12/21] PR feedback phase 1 --- .../Source/Scene/Cesium3DTilesetBaseTraversal.js | 9 +++++++++ .../Scene/Cesium3DTilesetMostDetailedTraversal.js | 11 +++++++++++ .../Source/Scene/Cesium3DTilesetSkipTraversal.js | 8 ++++++++ .../engine/Source/Scene/Cesium3DTilesetTraversal.js | 9 +++++---- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js index 57b776b6b082..8f5227e0106c 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js @@ -4,6 +4,13 @@ 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() {} @@ -19,6 +26,8 @@ const emptyTraversal = { }; /** + * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render. + * * @private * @param {Cesium3DTileset} tileset * @param {FrameState} frameState diff --git a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js index f8a66b2f551c..98b950096114 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js @@ -7,6 +7,9 @@ import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js"; * Traversal that loads all leaves that intersect the camera frustum. * Used to determine ray-tileset intersections during a pickFromRayMostDetailed call. * + * @alias Cesium3DTilesetMostDetailedTraversal + * @constructor + * * @private */ function Cesium3DTilesetMostDetailedTraversal() {} @@ -16,6 +19,14 @@ const traversal = { stackMaximumLength: 0, }; +/** + * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render. + * + * @private + * @param {Cesium3DTileset} tileset + * @param {FrameState} frameState + * @returns {boolean} Whether the appropriate tile is ready for picking + */ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( tileset, frameState diff --git a/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js index a74e04481dee..b4dbdc6b3ac1 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js @@ -4,6 +4,12 @@ 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() {} @@ -28,6 +34,8 @@ const selectionTraversal = { const descendantSelectionDepth = 2; /** + * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render. + * * @private * @param {Cesium3DTileset} tileset * @param {FrameState} frameState diff --git a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js index 2c32ced77219..fc30a1eca829 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js @@ -10,6 +10,7 @@ import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; * * @alias Cesium3DTilesetTraversal * @constructor + * @abstract * * @see Cesium3DTilesetBaseTraversal * @see Cesium3DTilesetSkipTraversal @@ -20,6 +21,8 @@ import Cesium3DTileRefine from "./Cesium3DTileRefine.js"; function Cesium3DTilesetTraversal() {} /** + * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render. + * * @private * @param {Cesium3DTileset} tileset * @param {FrameState} frameState @@ -283,10 +286,8 @@ function anyChildrenVisible(tile, frameState) { * @param {Cesium3DTile} tile */ function updateMinimumMaximumPriority(tile) { - const { - _maximumPriority: maximumPriority, - _minimumPriority: minimumPriority, - } = tile.tileset; + const minimumPriority = tile.tileset._minimumPriority; + const maximumPriority = tile.tileset._maximumPriority; const priorityHolder = tile._priorityHolder; maximumPriority.distance = Math.max( From 83afbc4e2ef8782d319988649a173165f93f53d6 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 27 Mar 2023 17:15:35 -0400 Subject: [PATCH 13/21] Add getter for tileset.isSkippingLevelOfDetail --- packages/engine/Source/Scene/Cesium3DTile.js | 7 ++-- .../Source/Scene/Cesium3DTileBatchTable.js | 2 +- .../engine/Source/Scene/Cesium3DTileset.js | 41 +++++++++++-------- packages/engine/Source/Scene/Model/Model.js | 7 +--- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index f3de7ee3fb06..acdc5c83db27 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -879,7 +879,7 @@ function isPriorityDeferred(tile, frameState) { // Skip this feature if: non-skipLevelOfDetail and replace refine, if the foveated settings are turned off, if tile is progressive resolution and replace refine and skipLevelOfDetail (will help get rid of ancestor artifacts faster) // Or if the tile is a preload of any kind const replace = tile.refine === Cesium3DTileRefine.REPLACE; - const skipLevelOfDetail = tileset._skipLevelOfDetail; + const skipLevelOfDetail = tileset.isSkippingLevelOfDetail; if ( (replace && !skipLevelOfDetail) || !tileset.foveatedScreenSpaceError || @@ -1023,7 +1023,7 @@ function getPriorityReverseScreenSpaceError(tileset, tile) { const parent = tile.parent; const useParentScreenSpaceError = defined(parent) && - (!tileset._skipLevelOfDetail || + (!tileset.isSkippingLevelOfDetail || tile._screenSpaceError === 0.0 || parent.hasTilesetContent || parent.hasImplicitContent); @@ -2214,7 +2214,8 @@ Cesium3DTile.prototype.updatePriority = function () { // Map 0-1 then convert to digit. Include a distance sort when doing non-skipLOD and replacement refinement, helps things like non-skipLOD photogrammetry const useDistance = - !tileset._skipLevelOfDetail && this.refine === Cesium3DTileRefine.REPLACE; + !tileset.isSkippingLevelOfDetail && + this.refine === Cesium3DTileRefine.REPLACE; const normalizedPreferredSorting = useDistance ? priorityNormalizeAndClamp( this._priorityHolder._distanceToCamera, diff --git a/packages/engine/Source/Scene/Cesium3DTileBatchTable.js b/packages/engine/Source/Scene/Cesium3DTileBatchTable.js index a892b0f94945..29517ea70a1e 100644 --- a/packages/engine/Source/Scene/Cesium3DTileBatchTable.js +++ b/packages/engine/Source/Scene/Cesium3DTileBatchTable.js @@ -896,7 +896,7 @@ Cesium3DTileBatchTable.prototype.addDerivedCommands = function ( const finalResolution = tile._finalResolution; const tileset = tile.tileset; const bivariateVisibilityTest = - tileset._skipLevelOfDetail && + tileset.isSkippingLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer; const styleCommandsNeeded = getStyleCommandsNeeded(this); diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 181f63ec81fb..dd3697e9e731 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -658,15 +658,6 @@ function Cesium3DTileset(options) { */ this.skipLevelOfDetail = defaultValue(options.skipLevelOfDetail, false); - /** - * Whether this tileset is actually skipping levels of detail. - * The user option may have been disabled if all tiles are using additive refinement, - * or if some tiles have a content type for which rendering does not support skipping - * - * @private - * @type {boolean} - */ - this._skipLevelOfDetail = this.skipLevelOfDetail; this._disableSkipLevelOfDetail = false; /** @@ -1385,6 +1376,28 @@ Object.defineProperties(Cesium3DTileset.prototype, { }, }, + /** + * Whether this tileset is actually skipping levels of detail. + * The user option may have been disabled if all tiles are using additive refinement, + * or if some tiles have a content type for which rendering does not support skipping + * + * @memberof Cesium3DTileset.prototype + * + * @type {boolean} + * @private + * @readonly + */ + isSkippingLevelOfDetail: { + get: function () { + return ( + this.skipLevelOfDetail && + !defined(this._classificationType) && + !this._disableSkipLevelOfDetail && + !this._allTilesAdditive + ); + }, + }, + /** * The tileset's schema, groups, tileset metadata and other details from the * 3DTILES_metadata extension or a 3D Tiles 1.1 tileset JSON. This getter is @@ -2393,12 +2406,6 @@ Cesium3DTileset.prototype.prePassesUpdate = function (frameState) { 0.0 ); - this._skipLevelOfDetail = - this.skipLevelOfDetail && - !defined(this._classificationType) && - !this._disableSkipLevelOfDetail && - !this._allTilesAdditive; - if (this.dynamicScreenSpaceError) { updateDynamicScreenSpaceError(this, frameState); } @@ -2730,7 +2737,7 @@ function updateTiles(tileset, frameState, passOptions) { const selectedTiles = tileset._selectedTiles; const bivariateVisibilityTest = - tileset._skipLevelOfDetail && + tileset.isSkippingLevelOfDetail && tileset._hasMixedContent && context.stencilBuffer && selectedTiles.length > 0; @@ -3081,7 +3088,7 @@ Cesium3DTileset.prototype.getTraversal = function (passOptions) { ) { return Cesium3DTilesetMostDetailedTraversal; } - return this._skipLevelOfDetail + return this.isSkippingLevelOfDetail ? Cesium3DTilesetSkipTraversal : Cesium3DTilesetBaseTraversal; }; diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index da958787bf2d..538363a15805 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2430,10 +2430,6 @@ Model.prototype.hasSilhouette = function (frameState) { ); }; -function supportsSkipLevelOfDetail(frameState) { - return frameState.context.stencilBuffer; -} - /** * Gets whether or not the model is part of a tileset that uses the * skipLevelOfDetail optimization. This accounts for whether skipLevelOfDetail @@ -2448,8 +2444,9 @@ Model.prototype.hasSkipLevelOfDetail = function (frameState) { return false; } + const supportsSkipLevelOfDetail = frameState.context.stencilBuffer; const tileset = this._content.tileset; - return supportsSkipLevelOfDetail(frameState) && tileset._skipLevelOfDetail; + return supportsSkipLevelOfDetail && tileset.isSkippingLevelOfDetail; }; /** From 841b9612846d476a9c3001b2b94326a2f8f9fced Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 27 Mar 2023 17:49:09 -0400 Subject: [PATCH 14/21] Expose and document private property Cesium3DTileset.prototype.hasMixedContent --- .../Source/Scene/Cesium3DTileBatchTable.js | 2 +- .../engine/Source/Scene/Cesium3DTileset.js | 22 +++++++++++++++++++ .../Scene/Cesium3DTilesetBaseTraversal.js | 2 +- .../Cesium3DTilesetMostDetailedTraversal.js | 2 +- .../Scene/Cesium3DTilesetSkipTraversal.js | 4 ++-- .../Source/Scene/Model/ModelDrawCommand.js | 2 +- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileBatchTable.js b/packages/engine/Source/Scene/Cesium3DTileBatchTable.js index 29517ea70a1e..6631d6c19622 100644 --- a/packages/engine/Source/Scene/Cesium3DTileBatchTable.js +++ b/packages/engine/Source/Scene/Cesium3DTileBatchTable.js @@ -897,7 +897,7 @@ Cesium3DTileBatchTable.prototype.addDerivedCommands = function ( const tileset = tile.tileset; const bivariateVisibilityTest = tileset.isSkippingLevelOfDetail && - tileset._hasMixedContent && + tileset.hasMixedContent && frameState.context.stencilBuffer; const styleCommandsNeeded = getStyleCommandsNeeded(this); diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index dd3697e9e731..5a7c916fa4f8 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -1376,6 +1376,28 @@ Object.defineProperties(Cesium3DTileset.prototype, { }, }, + /** + * Whether the tileset is rendering different levels of detail in the same view. + * Only relevant if {@link Cesium3DTileset.isSkippingLevelOfDetail} is true. + * + * @memberof Cesium3DTileset.prototype + * + * @type {boolean} + * @private + */ + hasMixedContent: { + get: function () { + return this._hasMixedContent; + }, + set: function (value) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.bool(value); + //>>includeEnd('debug'); + + this._hasMixedContent = value; + }, + }, + /** * Whether this tileset is actually skipping levels of detail. * The user option may have been disabled if all tiles are using additive refinement, diff --git a/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js index 8f5227e0106c..a85ddcc4013c 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js @@ -42,7 +42,7 @@ Cesium3DTilesetBaseTraversal.selectTiles = function (tileset, frameState) { tileset._selectedTiles.length = 0; tileset._selectedTilesToStyle.length = 0; tileset._emptyTiles.length = 0; - tileset._hasMixedContent = false; + tileset.hasMixedContent = false; const root = tileset.root; Cesium3DTilesetTraversal.updateTile(root, frameState); diff --git a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js index 98b950096114..8645642a852f 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js @@ -33,7 +33,7 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function ( ) { tileset._selectedTiles.length = 0; tileset._requestedTiles.length = 0; - tileset._hasMixedContent = false; + tileset.hasMixedContent = false; let ready = true; diff --git a/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js index b4dbdc6b3ac1..aea34eee3fcb 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js +++ b/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js @@ -50,7 +50,7 @@ Cesium3DTilesetSkipTraversal.selectTiles = function (tileset, frameState) { tileset._selectedTiles.length = 0; tileset._selectedTilesToStyle.length = 0; tileset._emptyTiles.length = 0; - tileset._hasMixedContent = false; + tileset.hasMixedContent = false; const root = tileset.root; Cesium3DTilesetTraversal.updateTile(root, frameState); @@ -394,7 +394,7 @@ function traverseAndSelect(root, frameState) { } else { tile._selectionDepth = ancestorStack.length; if (tile._selectionDepth > 0) { - tile.tileset._hasMixedContent = true; + tile.tileset.hasMixedContent = true; } lastAncestor = tile; if (!traverse) { diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommand.js b/packages/engine/Source/Scene/Model/ModelDrawCommand.js index d24adea9cf1e..83323fc1ec7d 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommand.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommand.js @@ -532,7 +532,7 @@ ModelDrawCommand.prototype.pushCommands = function (frameState, result) { if (this._needsSkipLevelOfDetailCommands) { const { tileset, tile } = this._model.content; - if (tileset._hasMixedContent) { + if (tileset.hasMixedContent) { if (!tile._finalResolution) { pushCommand( tileset._backfaceCommands, From 9f59a400ebfc2f93bb9d6669899f93d356672bee Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 28 Mar 2023 12:59:28 -0400 Subject: [PATCH 15/21] Fix specs to use tileset.hasMixedContent accessor --- packages/engine/Specs/Scene/Cesium3DTilesetSpec.js | 12 ++++++------ .../engine/Specs/Scene/Model/ModelDrawCommandSpec.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index 624d3b7a2f03..d0c1807dd030 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -3827,12 +3827,12 @@ describe( const statistics = tileset._statistics; expect(statistics.numberOfTilesWithContentReady).toEqual(1); expect(tileset._selectedTiles[0]._selectionDepth).toEqual(0); - expect(tileset._hasMixedContent).toBe(false); + expect(tileset.hasMixedContent).toBe(false); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect(statistics.numberOfTilesWithContentReady).toEqual(5); - expect(tileset._hasMixedContent).toBe(false); + expect(tileset.hasMixedContent).toBe(false); } ); }); @@ -3853,7 +3853,7 @@ describe( scene.renderForSpecs(); - expect(tileset._hasMixedContent).toBe(true); + expect(tileset.hasMixedContent).toBe(true); expect(statistics.numberOfTilesWithContentReady).toEqual(2); expect( tileset.root.children[0].children[0].children[3]._selectionDepth @@ -3863,7 +3863,7 @@ describe( return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect(statistics.numberOfTilesWithContentReady).toEqual(5); - expect(tileset._hasMixedContent).toBe(false); + expect(tileset.hasMixedContent).toBe(false); } ); }); @@ -3914,7 +3914,7 @@ describe( expect(root.children[0].children[0].children[3]._finalResolution).toBe( true ); - expect(tileset._hasMixedContent).toBe(true); + expect(tileset.hasMixedContent).toBe(true); const commandList = scene.frameState.commandList; const rs = commandList[1].renderState; @@ -3958,7 +3958,7 @@ describe( expect( isSelected(tileset, root.children[0].children[0].children[3]) ).toBe(false); - expect(tileset._hasMixedContent).toBe(false); + expect(tileset.hasMixedContent).toBe(false); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); }); diff --git a/packages/engine/Specs/Scene/Model/ModelDrawCommandSpec.js b/packages/engine/Specs/Scene/Model/ModelDrawCommandSpec.js index 99a5fb9a7fe3..6e86f1b0f1b6 100644 --- a/packages/engine/Specs/Scene/Model/ModelDrawCommandSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelDrawCommandSpec.js @@ -88,7 +88,7 @@ describe( _projectTo2D: false, content: { tileset: { - _hasMixedContent: true, + hasMixedContent: true, _backfaceCommands: [], }, tile: { From 66d76da116354710120a16556bd1efb263d843f9 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 28 Mar 2023 13:18:52 -0400 Subject: [PATCH 16/21] Fix Check.typeOf signature in tileset.hasMixedContent --- packages/engine/Source/Scene/Cesium3DTileset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 5a7c916fa4f8..601bfb2393a3 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -1391,7 +1391,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { }, set: function (value) { //>>includeStart('debug', pragmas.debug); - Check.typeOf.bool(value); + Check.typeOf.bool("value", value); //>>includeEnd('debug'); this._hasMixedContent = value; From a121a0a1459d35ea4b6313873cd8a7bd5b4e4b10 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Wed, 29 Mar 2023 10:21:19 -0400 Subject: [PATCH 17/21] Fix built viewer --- Apps/CesiumViewer/CesiumViewer.js | 7 ++++--- Apps/CesiumViewer/index.js | 1 - gulpfile.js | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 Apps/CesiumViewer/index.js diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js index 2226c85e8fa2..f2d303300b6e 100644 --- a/Apps/CesiumViewer/CesiumViewer.js +++ b/Apps/CesiumViewer/CesiumViewer.js @@ -1,6 +1,7 @@ -if (window.CESIUM_BASE_URL === undefined) { - window.CESIUM_BASE_URL = "../../Build/CesiumUnminified/"; -} +// eslint-disable-next-line no-undef +window.CESIUM_BASE_URL = window.CESIUM_BASE_URL + ? window.CESIUM_BASE_URL + : "../../Build/CesiumUnminified/"; import { Cartesian3, diff --git a/Apps/CesiumViewer/index.js b/Apps/CesiumViewer/index.js deleted file mode 100644 index f4e3e3d56f26..000000000000 --- a/Apps/CesiumViewer/index.js +++ /dev/null @@ -1 +0,0 @@ -window.CESIUM_BASE_URL = "."; diff --git a/gulpfile.js b/gulpfile.js index ef228912e4f9..51a36176f101 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2230,7 +2230,8 @@ async function buildCesiumViewer() { ".png": "text", }; config.format = "iife"; - config.inject = ["Apps/CesiumViewer/index.js"]; + // Configure Cesium base path to use built + config.define = { CESIUM_BASE_URL: `"."` }; config.external = ["https", "http", "url", "zlib"]; config.outdir = cesiumViewerOutputDirectory; config.outbase = "Apps/CesiumViewer"; @@ -2247,7 +2248,7 @@ async function buildCesiumViewer() { ".gif": "text", ".png": "text", }, - outdir: cesiumViewerOutputDirectory, + outdir: join(cesiumViewerOutputDirectory, "Widgets"), outbase: "packages/widgets/Source/", }); From 12a2f2899e15969895a59ff9ae34eed46d2ec255 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 29 Mar 2023 11:15:05 -0400 Subject: [PATCH 18/21] Update ion URL in RequestScheduler --- packages/engine/Source/Core/RequestScheduler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Core/RequestScheduler.js b/packages/engine/Source/Core/RequestScheduler.js index a0e4b63c8e4e..43542ad80cc1 100644 --- a/packages/engine/Source/Core/RequestScheduler.js +++ b/packages/engine/Source/Core/RequestScheduler.js @@ -76,7 +76,7 @@ RequestScheduler.maximumRequestsPerServer = 6; */ RequestScheduler.requestsByServer = { "api.cesium.com:443": 18, - "assets.cesium.com:443": 18, + "assets.ion.cesium.com:443": 18, }; /** From f4bc9c40df37e6ad5679212e09c2b933ec32693b Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Wed, 29 Mar 2023 10:46:25 -0400 Subject: [PATCH 19/21] Update dev dependencies --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f755bdb9a61f..5122b730fd44 100644 --- a/package.json +++ b/package.json @@ -97,14 +97,14 @@ "karma-jasmine": "^5.1.0", "karma-longest-reporter": "^1.1.0", "karma-safari-launcher": "^1.0.0", - "karma-sourcemap-loader": "^0.3.8", + "karma-sourcemap-loader": "^0.4.0", "karma-spec-reporter": "^0.0.36", "markdownlint-cli": "^0.33.0", "merge-stream": "^2.0.0", "mime": "^3.0.0", "mkdirp": "^2.1.3", "node-fetch": "^3.2.10", - "open": "^8.2.1", + "open": "^9.1.0", "p-limit": "^4.0.0", "prettier": "2.1.2", "prismjs": "^1.28.0", @@ -114,7 +114,7 @@ "rollup-plugin-strip-pragma": "^1.0.0", "stream-to-promise": "^3.0.0", "tsd-jsdoc": "^2.5.0", - "typescript": "^4.9.4", + "typescript": "^5.0.2", "yargs": "^17.0.1" }, "scripts": { @@ -167,4 +167,4 @@ "packages/engine", "packages/widgets" ] -} \ No newline at end of file +} From 14bfce8648a412cd4272a379443c3bcb49423c38 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Wed, 29 Mar 2023 12:27:04 -0400 Subject: [PATCH 20/21] Tweaked travis scripts to ensure encrypted variables are available for internal tasks --- travis/coverage.sh | 2 +- travis/deploy.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/travis/coverage.sh b/travis/coverage.sh index d718e9a5d6b4..f4c929ec6cf9 100755 --- a/travis/coverage.sh +++ b/travis/coverage.sh @@ -4,7 +4,7 @@ if [ $TRAVIS_BRANCH != "cesium.com" ]; then npm --silent run build npm --silent run coverage -- --browsers FirefoxHeadless --webgl-stub --failTaskOnError --suppressPassed - if [ $TRAVIS_REPO_SLUG == "CesiumGS/cesium" ]; then + if [ $TRAVIS_SECURE_ENV_VARS ]; then aws s3 sync ./Build/Coverage s3://cesium-dev/cesium/$TRAVIS_BRANCH/Build/Coverage --delete --color on fi fi diff --git a/travis/deploy.sh b/travis/deploy.sh index b1ec01abbaf3..26cab879a9f4 100755 --- a/travis/deploy.sh +++ b/travis/deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash set -ev -if [ $TRAVIS_REPO_SLUG == "CesiumGS/cesium" ]; then +if [ $TRAVIS_SECURE_ENV_VARS ]; then # Files deployed to cesium.com are "production", and should be cached at edge locations for # better performance. Otherwise, this is a development deploy and nothing should be cached if [ $TRAVIS_BRANCH == "cesium.com" ]; then From f593afa79b03994833e8f4ea4c46bd6b068a546c Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 29 Mar 2023 12:37:23 -0400 Subject: [PATCH 21/21] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 65b98282d578..c01414809428 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Fixed issue where passing `children` in the Entity constructor options will override children. [#11101](https://github.com/CesiumGS/cesium/issues/11101) - Fixed error type to be `RequestErrorEvent` in `Resource.retryCallback`. [#11177](https://github.com/CesiumGS/cesium/pull/11177) +- Fixed ion URL in `RequestScheduler` throttling overrides. [#11193](https://github.com/CesiumGS/cesium/pull/11193) #### @cesium/widgets