Skip to content

Commit

Permalink
New approach to checking if a tile is refinable
Browse files Browse the repository at this point in the history
  • Loading branch information
lilleyse committed Jan 22, 2016
1 parent a11a8d3 commit 3cf51b5
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 14 deletions.
24 changes: 22 additions & 2 deletions Source/Scene/Cesium3DTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ define([
*/
this.children = [];

/**
* Descendant tiles that need to be visible before this tile can refine. For example, if
* a child is empty (such as for accelerating culling), its descendants with content must
* be loaded first. If a tiles's children all have content, this is left undefined.
*
* @type {Array}
* @readonly
*/
this.descendantsWithContent = undefined;

/**
* Marks if the tile is selected this frame.
*
* @type {Boolean}
*/
this.selected = false;

/**
* DOC_TBA
*
Expand Down Expand Up @@ -353,8 +370,11 @@ define([
* DOC_TBA
*/
Cesium3DTile.prototype.update = function(owner, frameState) {
applyDebugSettings(this, owner, frameState);
this._content.update(owner, frameState);
if (this.selected) {
applyDebugSettings(this, owner, frameState);
this._content.update(owner, frameState);
}
this.selected = false;
};

var scratchCommandList = [];
Expand Down
86 changes: 82 additions & 4 deletions Source/Scene/Cesium3DTileset.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ define([
++parentTile.numberOfChildrenWithoutContent;
}

var refiningTiles = [];

var stack = [];
stack.push({
header : tilesetJson.root,
Expand All @@ -286,29 +288,63 @@ define([

while (stack.length > 0) {
var t = stack.pop();
var tile3D = t.cesium3DTile;
var children = t.header.children;
var hasEmptyChild = false;
if (defined(children)) {
var length = children.length;
for (var k = 0; k < length; ++k) {
var childHeader = children[k];
var childTile = new Cesium3DTile(tileset, baseUrl, childHeader, t.cesium3DTile);
t.cesium3DTile.children.push(childTile);

var childTile = new Cesium3DTile(tileset, baseUrl, childHeader, tile3D);
tile3D.children.push(childTile);
stack.push({
header : childHeader,
cesium3DTile : childTile
});
if (!childTile.hasContent) {
hasEmptyChild = true;
}
}
}
if (tile3D.hasContent && hasEmptyChild && (tile3D.refine === Cesium3DTileRefine.REPLACE)) {
refiningTiles.push(tile3D);
}
}

loadRefiningTiles(refiningTiles);

return {
tilesetJson : tilesetJson,
root : rootTile
};
});
};

function loadRefiningTiles(refiningTiles) {
// Tiles that use replacement refinement and have empty child tiles need to keep track of
// descendants with content in order to refine correctly.
var stack = [];
var length = refiningTiles.length;
for (var i = 0; i < length; ++i) {
var refiningTile = refiningTiles[i];
refiningTile.descendantsWithContent = [];
stack.push(refiningTile);
while (stack.length > 0) {
var tile = stack.pop();
var children = tile.children;
var childrenLength = children.length;
for (var k = 0; k < childrenLength; ++k) {
var childTile = children[k];
if (childTile.hasContent) {
refiningTile.descendantsWithContent.push(childTile);
} else {
stack.push(childTile);
}
}
}
}
}

function getScreenSpaceError(geometricError, tile, frameState) {
// TODO: screenSpaceError2D like QuadtreePrimitive.js
if (geometricError === 0.0) {
Expand Down Expand Up @@ -369,10 +405,12 @@ define([
// Don't select if the tile is being loaded to refine another tile
if (tile.isReady() && (fullyVisible || (tile.contentsVisibility(frameState.cullingVolume) !== Intersect.OUTSIDE))) {
selectedTiles.push(tile);
tile.selected = true;
}
}

var scratchStack = [];
var refiningTiles = [];

function selectTiles(tiles3D, frameState, outOfCore) {
if (tiles3D.debugFreezeFrame) {
Expand All @@ -385,6 +423,8 @@ define([
var selectedTiles = tiles3D._selectedTiles;
selectedTiles.length = 0;

refiningTiles.length = 0;

var root = tiles3D._root;
root.distanceToCamera = root.distanceToTile(frameState);
root.parentPlaneMask = CullingVolume.MASK_INDETERMINATE;
Expand Down Expand Up @@ -505,7 +545,7 @@ define([
child = children[k];
// TODO: we could spin a bit less CPU here and probably above by keeping separate lists for unloaded/ready children.
if (child.isContentUnloaded()) {
requestContent(tiles3D, child);
requestContent(tiles3D, child, outOfCore);
}
}
}
Expand All @@ -517,10 +557,48 @@ define([
child.parentPlaneMask = planeMask;
stack.push(child);
}

if (defined(t.descendantsWithContent)) {
refiningTiles.push(t);
}
}
}
}
}

checkRefiningTiles(refiningTiles, tiles3D, frameState);
}

function checkRefiningTiles(refiningTiles, tiles3D, frameState) {
// In the common case, a tile that uses replacement refinement is refinable once all its
// children are loaded. However if it has an empty child, refining to its children would
// show a visible gap. In this case, the empty child's children (or further descendants)
// would need to be selected before the original tile is refinable. It is hard to determine
// this easily during the traversal, so this fixes the situation retroactively.
var descendant;
var refiningTilesLength = refiningTiles.length;
for (var i = 0; i < refiningTilesLength; ++i) {
var j;
var refinable = true;
var refiningTile = refiningTiles[i];
var descendantsLength = refiningTile.descendantsWithContent.length;
for (j = 0; j < descendantsLength; ++j) {
descendant = refiningTile.descendantsWithContent[j];
if (!descendant.selected) {
// TODO: also check that its visible
refinable = false;
break;
}
}
if (!refinable) {
var fullyVisible = refiningTile.visibility(frameState.cullingVolume) === CullingVolume.MASK_INSIDE;
selectTile(tiles3D._selectedTiles, refiningTile, fullyVisible, frameState);
for (j = 0; j < descendantsLength; ++j) {
descendant = refiningTile.descendantsWithContent[j];
descendant.selected = false;
}
}
}
}

///////////////////////////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
20
]
},
"geometricError": 0,
"geometricError": 70,
"refine": "add",
"content": {
"url": "tileset3.json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
},
"geometricError": 70,
"refine": "replace",
"content": {
"url": "parent.b3dm"
},
"children": [
{
"boundingVolume": {
Expand Down
15 changes: 8 additions & 7 deletions Specs/Scene/Cesium3DTilesetSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ defineSuite([

expect(tileset._geometricError).toEqual(240.0);
expect(tileset._root).toBeDefined();
expect(tileset._root.descendantsWithContent).toBeUndefined();
expect(tileset.url).toEqual(tilesetUrl);
});
});
Expand Down Expand Up @@ -494,7 +495,6 @@ defineSuite([
scene.renderForSpecs();

var stats = tileset._statistics;
expect(root.isRefinable()).toEqual(false);
expect(stats.visited).toEqual(1);
expect(stats.numberOfCommands).toEqual(1);
expect(stats.numberOfPendingRequests).toEqual(4);
Expand All @@ -517,17 +517,18 @@ defineSuite([

var stats = tileset._statistics;
var root = tileset._root;
var root = tileset._root;
expect(root.descendantsWithContent).toBeDefined();
expect(root.descendantsWithContent.length).toEqual(4);
return when.join(root.children[0].readyPromise, root.children[1].readyPromise).then(function() {
// Even though root's children are loaded, the grandchildren need to be loaded before it becomes refinable
scene.renderForSpecs();
expect(root.isRefinable()).toEqual(false);
expect(root.numberOfChildrenWithoutContent).toEqual(0); // Children are loaded
expect(stats.numberOfCommands).toEqual(1); // Render root
expect(stats.numberOfPendingRequests).toEqual(4); // Loading grandchildren

return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
scene.renderForSpecs();
expect(root.isRefinable()).toEqual(true);
expect(stats.numberOfCommands).toEqual(4); // Render children
});
});
Expand All @@ -537,7 +538,7 @@ defineSuite([
it('replacement refinement - selects root when sse is not met and subtree is not refinable (2)', function() {
// Check that the root is refinable once its child is loaded
//
// E
// C
// E
// C E
// C (smaller geometric error)
Expand All @@ -550,17 +551,17 @@ defineSuite([

var stats = tileset._statistics;
var root = tileset._root;
expect(root.descendantsWithContent).toBeDefined();
expect(root.descendantsWithContent.length).toEqual(2);
return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
scene.renderForSpecs();
expect(root.isRefinable()).toEqual(false);
expect(stats.numberOfCommands).toEqual(0);
expect(stats.numberOfCommands).toEqual(1);

setZoom(5.0); // Zoom into the last tile, when it is ready the root is refinable
scene.renderForSpecs();

return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
scene.renderForSpecs();
expect(root.isRefinable()).toEqual(true);
expect(stats.numberOfCommands).toEqual(2); // Renders two content tiles
});
});
Expand Down

0 comments on commit 3cf51b5

Please sign in to comment.