diff --git a/CHANGES.md b/CHANGES.md index 6509bb852f15..3530d3a07bb4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Change Log * Added `OpenCageGeocoderService`, which provides geocoding via [OpenCage](https://opencagedata.com/). [#7015](https://github.com/AnalyticalGraphicsInc/cesium/pull/7015) ##### Fixes :wrench: +* Fixed an issue in the 3D Tiles traversal where external tilesets would not always traverse to their root tile. [#7035](https://github.com/AnalyticalGraphicsInc/cesium/pull/7035) * Fixed an issue in the 3D Tiles traversal where empty tiles would be selected instead of their nearest loaded ancestors. [#7011](https://github.com/AnalyticalGraphicsInc/cesium/pull/7011) * Fixed bug where credits weren't displaying correctly if more than one viewer was initialized [#6965](expect(https://github.com/AnalyticalGraphicsInc/cesium/issues/6965) diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js index d01be2a539de..882d82aef852 100644 --- a/Source/Scene/Cesium3DTilesetTraversal.js +++ b/Source/Scene/Cesium3DTilesetTraversal.js @@ -215,7 +215,7 @@ define([ return tile._distanceToCamera; } var parent = tile.parent; - var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0)); + var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0) || parent.hasTilesetContent); var screenSpaceError = useParentScreenSpaceError ? parent._screenSpaceError : tile._screenSpaceError; var rootScreenSpaceError = tileset.root._screenSpaceError; return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower values (e.g. root tile is highest priority) @@ -298,6 +298,17 @@ define([ return anyVisible; } + function meetsScreenSpaceErrorEarly(tileset, tile, frameState) { + var parent = tile.parent; + if (!defined(parent) || parent.hasTilesetContent || (parent.refine !== Cesium3DTileRefine.ADD)) { + return false; + } + + // Use parent's geometric error with child's box to see if the tile already meet the SSE + var sse = getScreenSpaceError(tileset, parent.geometricError, tile, frameState); + return sse <= tileset._maximumScreenSpaceError; + } + function updateTileVisibility(tileset, tile, frameState) { updateVisibility(tileset, tile, frameState); @@ -305,9 +316,7 @@ define([ return; } - // Use parent's geometric error with child's box to see if the tile already meet the SSE - var parent = tile.parent; - if (defined(parent) && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= tileset._maximumScreenSpaceError) { + if (meetsScreenSpaceErrorEarly(tileset, tile, frameState)) { tile._visible = false; return; } @@ -438,6 +447,18 @@ define([ return tile._screenSpaceError > baseScreenSpaceError; } + function canTraverse(tileset, tile) { + if (tile.children.length === 0) { + return false; + } + if (tile.hasTilesetContent) { + // Traverse external tileset to visit its root tile + // Don't traverse if the subtree is expired because it will be destroyed + return !tile.contentExpired; + } + return tile._screenSpaceError > tileset._maximumScreenSpaceError; + } + function executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) { // Depth-first traversal that traverses all visible tiles and marks tiles for selection. // If skipLevelOfDetail is off then a tile does not refine until all children are loaded. @@ -455,19 +476,11 @@ define([ var baseTraversal = inBaseTraversal(tileset, tile, baseScreenSpaceError); var add = tile.refine === Cesium3DTileRefine.ADD; var replace = tile.refine === Cesium3DTileRefine.REPLACE; - var children = tile.children; - var childrenLength = children.length; var parent = tile.parent; var parentRefines = !defined(parent) || parent._refines; - var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError); var refines = false; - if (tile.hasTilesetContent && tile.contentExpired) { - // Don't traverse expired subtree because it will be destroyed - traverse = false; - } - - if (traverse) { + if (canTraverse(tileset, tile)) { refines = updateAndPushChildren(tileset, tile, stack, frameState) && parentRefines; } @@ -514,7 +527,6 @@ define([ function executeEmptyTraversal(tileset, root, frameState) { // Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility. var allDescendantsLoaded = true; - var maximumScreenSpaceError = tileset._maximumScreenSpaceError; var stack = emptyTraversal.stack; stack.push(root); @@ -526,7 +538,7 @@ define([ var childrenLength = children.length; // Only traverse if the tile is empty - traversal stop at descendants with content - var traverse = hasEmptyContent(tile) && (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError); + var traverse = hasEmptyContent(tile) && canTraverse(tileset, tile); // 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. @@ -573,7 +585,6 @@ define([ * selected tiles must be no deeper than 15. This is still very unlikely. */ function traverseAndSelect(tileset, root, frameState) { - var maximumScreenSpaceError = tileset._maximumScreenSpaceError; var stack = selectionTraversal.stack; var ancestorStack = selectionTraversal.ancestorStack; var lastAncestor; @@ -606,7 +617,7 @@ define([ var shouldSelect = tile._shouldSelect; var children = tile.children; var childrenLength = children.length; - var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError); + var traverse = canTraverse(tileset, tile); if (shouldSelect) { if (add) { diff --git a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json index ec20e19e4225..fffe09adbcf0 100644 --- a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json +++ b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json @@ -33,7 +33,7 @@ 88 ] }, - "geometricError": 240, + "geometricError": 70, "refine": "ADD", "content": { "uri": "tileset2.json" diff --git a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json index dc862f884d68..53f5aeb9f759 100644 --- a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json +++ b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json @@ -31,7 +31,7 @@ 20 ] }, - "geometricError": 70, + "geometricError": 0, "content": { "uri": "tileset3/tileset3.json" } diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 204ae8911bc9..ae5c15351c72 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -854,11 +854,15 @@ defineSuite([ }); }); - function findTileByUrl(tiles, url) { + function findTileByUri(tiles, uri) { var length = tiles.length; for (var i = 0; i < length; ++i) { - if (tiles[i].content._resource.url.indexOf(url) >= 0) { - return tiles[i]; + var tile = tiles[i]; + var contentHeader = tile._header.content; + if (defined(contentHeader)) { + if (contentHeader.uri.indexOf(uri) >= 0) { + return tile; + } } } return undefined; @@ -877,10 +881,10 @@ defineSuite([ scene.renderForSpecs(); var root = tileset.root; - var llTile = findTileByUrl(root.children, 'll.b3dm'); - var lrTile = findTileByUrl(root.children, 'lr.b3dm'); - var urTile = findTileByUrl(root.children, 'ur.b3dm'); - var ulTile = findTileByUrl(root.children, 'ul.b3dm'); + var llTile = findTileByUri(root.children, 'll.b3dm'); + var lrTile = findTileByUri(root.children, 'lr.b3dm'); + var urTile = findTileByUri(root.children, 'ur.b3dm'); + var ulTile = findTileByUri(root.children, 'ul.b3dm'); var selectedTiles = tileset._selectedTiles; expect(selectedTiles[0]).toBe(root); @@ -1079,8 +1083,7 @@ defineSuite([ // return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement1Url).then(function(tileset) { tileset.root.geometricError = 90; - viewRootOnly(); - scene.camera.zoomIn(20); + setZoom(80); scene.renderForSpecs(); var statistics = tileset._statistics; @@ -1364,6 +1367,15 @@ defineSuite([ }); }); + it('always visits external tileset root', function() { + viewRootOnly(); + return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then(function(tileset) { + var statistics = tileset._statistics; + expect(statistics.visited).toEqual(2); // Visits external tileset tile, and external tileset root + expect(statistics.numberOfCommands).toEqual(1); // Renders external tileset root + }); + }); + it('set tile color', function() { return Cesium3DTilesTester.loadTileset(scene, noBatchIdsUrl).then(function(tileset) { // Get initial color @@ -2765,29 +2777,27 @@ defineSuite([ }); it('immediatelyLoadDesiredLevelOfDetail', function() { - viewBottomLeft(); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetOfTilesetsUrl, + viewNothing(); + return Cesium3DTilesTester.loadTileset(scene, tilesetUrl, { immediatelyLoadDesiredLevelOfDetail : true - })); - return Cesium3DTilesTester.waitForReady(scene, tileset).then(function(tileset) { - scene.renderForSpecs(); - return tileset.root.contentReadyPromise.then(function() { - tileset.root.refine = Cesium3DTileRefine.REPLACE; - tileset.root.children[0].refine = Cesium3DTileRefine.REPLACE; - tileset._allTilesAdditive = false; + }).then(function(tileset) { + var root = tileset.root; + var child = findTileByUri(root.children, 'll.b3dm'); + tileset.root.refine = Cesium3DTileRefine.REPLACE; + tileset._allTilesAdditive = false; + viewBottomLeft(); + return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function(tileset) { + expect(isSelected(tileset, child)); + expect(!isSelected(tileset, root)); + expect(root.contentUnloaded).toBe(true); + // Renders child while parent loads + viewRootOnly(); + scene.renderForSpecs(); + expect(isSelected(tileset, child)); + expect(!isSelected(tileset, root)); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function(tileset) { - var statistics = tileset._statistics; - expect(statistics.numberOfTilesWithContentReady).toBe(1); - // Renders child while parent loads - viewRootOnly(); - scene.renderForSpecs(); - expect(isSelected(tileset, tileset.root.children[0])); - expect(!isSelected(tileset, tileset.root)); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function(tileset) { - expect(!isSelected(tileset, tileset.root.children[0])); - expect(isSelected(tileset, tileset.root)); - }); + expect(!isSelected(tileset, child)); + expect(isSelected(tileset, root)); }); }); });