From 27a168372ac928045d8d548603e4e1ccb0ffd740 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 11 Sep 2018 19:59:17 -0400 Subject: [PATCH 1/4] Force traversal on external tileset to always get its root --- Source/Scene/Cesium3DTilesetTraversal.js | 35 ++++++++++++++---------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js index d01be2a539de..eb58780142f1 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) @@ -307,7 +307,7 @@ define([ // 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 (defined(parent) && !parent.hasTilesetContent && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= tileset._maximumScreenSpaceError) { tile._visible = false; return; } @@ -438,6 +438,21 @@ 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; + } + if (tile.hasTilesetContent) { + return true; + } + 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 +470,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 +521,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 +532,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 +579,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 +611,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) { From 3ff4d23f7cf0ad3db3006e52bcdc833fa6d7adb2 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 11 Sep 2018 20:40:27 -0400 Subject: [PATCH 2/4] Add test --- CHANGES.md | 1 + Source/Scene/Cesium3DTilesetTraversal.js | 5 +---- .../Tilesets/TilesetOfTilesets/tileset.json | 2 +- .../Tilesets/TilesetOfTilesets/tileset2.json | 2 +- Specs/Scene/Cesium3DTilesetSpec.js | 12 ++++++++++-- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bf2ba5572d65..2ec8b3570589 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Change Log * Added `cartographicLimitRectangle` to `Globe`. Use this to limit terrain and imagery to a specific `Rectangle` area. [#6987](https://github.com/AnalyticalGraphicsInc/cesium/pull/6987) ##### 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) ### 1.49 - 2018-09-04 diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js index eb58780142f1..0aff0d6fa1e1 100644 --- a/Source/Scene/Cesium3DTilesetTraversal.js +++ b/Source/Scene/Cesium3DTilesetTraversal.js @@ -443,13 +443,10 @@ define([ return false; } if (tile.hasTilesetContent) { - // Traverse external tileset to visit its root tile. + // Traverse external tileset to visit its root tile // Don't traverse if the subtree is expired because it will be destroyed return !tile.contentExpired; } - if (tile.hasTilesetContent) { - return true; - } return tile._screenSpaceError > tileset._maximumScreenSpaceError; } 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..5f27bc510ad1 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -1079,8 +1079,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 +1363,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 From cd1245f7e2650e35d40aeb54ebaefec93d6eb7b4 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 12 Sep 2018 15:25:26 -0400 Subject: [PATCH 3/4] Move sse check to own function --- Source/Scene/Cesium3DTilesetTraversal.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js index 0aff0d6fa1e1..882d82aef852 100644 --- a/Source/Scene/Cesium3DTilesetTraversal.js +++ b/Source/Scene/Cesium3DTilesetTraversal.js @@ -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.hasTilesetContent && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= tileset._maximumScreenSpaceError) { + if (meetsScreenSpaceErrorEarly(tileset, tile, frameState)) { tile._visible = false; return; } From ab8c8952509caf7dc7ab66a953fbf43f1c5efa10 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 12 Sep 2018 18:56:04 -0400 Subject: [PATCH 4/4] Fix unrelated test that was intermittently failing --- Specs/Scene/Cesium3DTilesetSpec.js | 58 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 5f27bc510ad1..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); @@ -2773,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)); }); }); });