DSDSServer2024/build/build.20240805/static/js/Cesium-1.53/Specs/Scene/QuadtreePrimitiveSpec.js

532 lines
20 KiB
JavaScript

defineSuite([
'Scene/QuadtreePrimitive',
'Core/Cartesian3',
'Core/Cartographic',
'Core/defineProperties',
'Core/EventHelper',
'Core/GeographicTilingScheme',
'Core/Visibility',
'Scene/QuadtreeTileLoadState',
'Specs/createScene',
'Specs/pollToPromise'
], function(
QuadtreePrimitive,
Cartesian3,
Cartographic,
defineProperties,
EventHelper,
GeographicTilingScheme,
Visibility,
QuadtreeTileLoadState,
createScene,
pollToPromise) {
'use strict';
var scene;
beforeAll(function() {
scene = createScene();
scene.render();
});
afterAll(function() {
scene.destroyForSpecs();
});
it('must be constructed with a tileProvider', function() {
expect(function() {
return new QuadtreePrimitive();
}).toThrowDeveloperError();
expect(function() {
return new QuadtreePrimitive({});
}).toThrowDeveloperError();
});
function createSpyTileProvider() {
var result = jasmine.createSpyObj('tileProvider', [
'getQuadtree', 'setQuadtree', 'getReady', 'getTilingScheme', 'getErrorEvent',
'initialize', 'updateImagery', 'beginUpdate', 'endUpdate', 'getLevelMaximumGeometricError', 'loadTile',
'computeTileVisibility', 'showTileThisFrame', 'computeDistanceToTile', 'isDestroyed', 'destroy']);
defineProperties(result, {
quadtree : {
get : result.getQuadtree,
set : result.setQuadtree
},
ready : {
get : result.getReady
},
tilingScheme : {
get : result.getTilingScheme
},
errorEvent : {
get : result.getErrorEvent
}
});
var tilingScheme = new GeographicTilingScheme();
result.getTilingScheme.and.returnValue(tilingScheme);
return result;
}
it('calls initialize, beginUpdate, loadTile, and endUpdate', function() {
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
// determine what tiles to load
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// load tiles
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
expect(tileProvider.initialize).toHaveBeenCalled();
expect(tileProvider.beginUpdate).toHaveBeenCalled();
expect(tileProvider.loadTile).toHaveBeenCalled();
expect(tileProvider.endUpdate).toHaveBeenCalled();
});
it('shows the root tiles when they are ready and visible', function() {
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
tileProvider.computeTileVisibility.and.returnValue(Visibility.FULL);
tileProvider.loadTile.and.callFake(function(frameState, tile) {
tile.renderable = true;
});
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
// determine what tiles to load
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// load tiles
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
expect(tileProvider.showTileThisFrame).toHaveBeenCalled();
});
it('stops loading a tile that moves to the DONE state', function() {
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
tileProvider.computeTileVisibility.and.returnValue(Visibility.FULL);
var calls = 0;
tileProvider.loadTile.and.callFake(function(frameState, tile) {
++calls;
tile.state = QuadtreeTileLoadState.DONE;
});
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
// determine what tiles to load
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// load tiles
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
expect(calls).toBe(2);
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
expect(calls).toBe(2);
});
it('tileLoadProgressEvent is raised when tile loaded and when new children discovered', function() {
var eventHelper = new EventHelper();
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
tileProvider.computeTileVisibility.and.returnValue(Visibility.FULL);
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
var progressEventSpy = jasmine.createSpy('progressEventSpy');
eventHelper.add(quadtree.tileLoadProgressEvent, progressEventSpy);
// Initial update to get the zero-level tiles set up.
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// load zero-level tiles
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
quadtree.update(scene.frameState);
scene.renderForSpecs();
// There will now be two zero-level tiles in the load queue.
expect(progressEventSpy.calls.mostRecent().args[0]).toEqual(2);
// Change one to loaded and update again
quadtree._levelZeroTiles[0].state = QuadtreeTileLoadState.DONE;
quadtree._levelZeroTiles[1].state = QuadtreeTileLoadState.LOADING;
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
quadtree.update(scene.frameState);
scene.renderForSpecs();
// Now there should only be one left in the update queue
expect(progressEventSpy.calls.mostRecent().args[0]).toEqual(1);
// Simulate the second zero-level child having loaded with two children.
quadtree._levelZeroTiles[1].state = QuadtreeTileLoadState.DONE;
quadtree._levelZeroTiles[1].renderable = true;
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
quadtree.update(scene.frameState);
scene.renderForSpecs();
// Now that tile's four children should be in the load queue.
expect(progressEventSpy.calls.mostRecent().args[0]).toEqual(4);
});
it('forEachLoadedTile does not enumerate tiles in the START state', function() {
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
tileProvider.computeTileVisibility.and.returnValue(Visibility.FULL);
tileProvider.computeDistanceToTile.and.returnValue(1e-15);
// Load the root tiles.
tileProvider.loadTile.and.callFake(function(frameState, tile) {
tile.state = QuadtreeTileLoadState.DONE;
tile.renderable = true;
});
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
// determine what tiles to load
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// load tiles
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// Don't load further tiles.
tileProvider.loadTile.and.callFake(function(frameState, tile) {
tile.state = QuadtreeTileLoadState.START;
});
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
quadtree.forEachLoadedTile(function(tile) {
expect(tile.state).not.toBe(QuadtreeTileLoadState.START);
});
});
it('add and remove callbacks to tiles', function() {
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
tileProvider.computeTileVisibility.and.returnValue(Visibility.FULL);
tileProvider.computeDistanceToTile.and.returnValue(1e-15);
// Load the root tiles.
tileProvider.loadTile.and.callFake(function(frameState, tile) {
tile.state = QuadtreeTileLoadState.DONE;
tile.renderable = true;
tile.data = {
pick : function() {
return undefined;
}
};
});
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
var removeFunc = quadtree.updateHeight(Cartographic.fromDegrees(-72.0, 40.0), function(position) {
});
// determine what tiles to load
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// load tiles
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
var addedCallback = false;
quadtree.forEachLoadedTile(function(tile) {
addedCallback = addedCallback || tile.customData.length > 0;
});
expect(addedCallback).toEqual(true);
removeFunc();
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
var removedCallback = true;
quadtree.forEachLoadedTile(function(tile) {
removedCallback = removedCallback && tile.customData.length === 0;
});
expect(removedCallback).toEqual(true);
});
it('updates heights', function() {
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
tileProvider.computeTileVisibility.and.returnValue(Visibility.FULL);
tileProvider.computeDistanceToTile.and.returnValue(1e-15);
tileProvider.terrainProvider = {
getTileDataAvailable : function() {
return true;
}
};
var position = Cartesian3.clone(Cartesian3.ZERO);
var updatedPosition = Cartesian3.clone(Cartesian3.UNIT_X);
var currentPosition = position;
// Load the root tiles.
tileProvider.loadTile.and.callFake(function(frameState, tile) {
tile.state = QuadtreeTileLoadState.DONE;
tile.renderable = true;
tile.data = {
pick : function() {
return currentPosition;
}
};
});
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
quadtree.updateHeight(Cartographic.fromDegrees(-72.0, 40.0), function(p) {
Cartesian3.clone(p, position);
});
// determine what tiles to load
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// load tiles
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
expect(position).toEqual(Cartesian3.ZERO);
currentPosition = updatedPosition;
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
expect(position).toEqual(updatedPosition);
});
it('gives correct priority to tile loads', function() {
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
tileProvider.computeTileVisibility.and.returnValue(Visibility.FULL);
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// The root tiles should be in the high priority load queue
expect(quadtree._tileLoadQueueHigh.length).toBe(2);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[1]);
expect(quadtree._tileLoadQueueMedium.length).toBe(0);
expect(quadtree._tileLoadQueueLow.length).toBe(0);
// Mark the first root tile renderable (but not done loading)
quadtree._levelZeroTiles[0].renderable = true;
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// That root tile should now load with low priority while its children should load with high.
expect(quadtree._tileLoadQueueHigh.length).toBe(5);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[1]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0].children[0]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0].children[1]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0].children[2]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0].children[3]);
expect(quadtree._tileLoadQueueMedium.length).toBe(0);
expect(quadtree._tileLoadQueueLow.length).toBe(1);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0]);
// Mark the children of that root tile renderable too, so we can refine it
quadtree._levelZeroTiles[0].children[0].renderable = true;
quadtree._levelZeroTiles[0].children[1].renderable = true;
quadtree._levelZeroTiles[0].children[2].renderable = true;
quadtree._levelZeroTiles[0].children[3].renderable = true;
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
expect(quadtree._tileLoadQueueHigh.length).toBe(17); // levelZeroTiles[1] plus levelZeroTiles[0]'s 16 grandchildren
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[1]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0].children[0].children[0]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0].children[0].children[1]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0].children[0].children[2]);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[0].children[0].children[3]);
expect(quadtree._tileLoadQueueMedium.length).toBe(0);
expect(quadtree._tileLoadQueueLow.length).toBe(5);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0]);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0].children[0]);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0].children[1]);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0].children[2]);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0].children[3]);
// Mark the children of levelZeroTiles[0] upsampled
quadtree._levelZeroTiles[0].children[0].upsampledFromParent = true;
quadtree._levelZeroTiles[0].children[1].upsampledFromParent = true;
quadtree._levelZeroTiles[0].children[2].upsampledFromParent = true;
quadtree._levelZeroTiles[0].children[3].upsampledFromParent = true;
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// levelZeroTiles[0] should move to medium priority.
// Its descendents should continue loading, so they have a chance to decide they're not upsampled later.
expect(quadtree._tileLoadQueueHigh.length).toBe(1);
expect(quadtree._tileLoadQueueHigh).toContain(quadtree._levelZeroTiles[1]);
expect(quadtree._tileLoadQueueMedium.length).toBe(1);
expect(quadtree._tileLoadQueueMedium).toContain(quadtree._levelZeroTiles[0]);
expect(quadtree._tileLoadQueueLow.length).toBe(4);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0].children[0]);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0].children[1]);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0].children[2]);
expect(quadtree._tileLoadQueueLow).toContain(quadtree._levelZeroTiles[0].children[3]);
});
it('renders tiles in approximate near-to-far order', function() {
var tileProvider = createSpyTileProvider();
tileProvider.getReady.and.returnValue(true);
tileProvider.computeTileVisibility.and.returnValue(Visibility.FULL);
var quadtree = new QuadtreePrimitive({
tileProvider : tileProvider
});
tileProvider.loadTile.and.callFake(function(frameState, tile) {
if (tile.level <= 1) {
tile.state = QuadtreeTileLoadState.DONE;
tile.renderable = true;
}
});
scene.camera.setView({
destination : Cartesian3.fromDegrees(1.0, 1.0, 15000.0)
});
scene.camera.update(scene.mode);
return pollToPromise(function() {
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
return quadtree._tilesToRender.filter(function(tile) { return tile.level === 1; }).length === 8;
}).then(function() {
quadtree.update(scene.frameState);
quadtree.beginFrame(scene.frameState);
quadtree.render(scene.frameState);
quadtree.endFrame(scene.frameState);
// Rendered tiles:
// +----+----+----+----+
// |w.nw|w.ne|e.nw|e.ne|
// +----+----+----+----+
// |w.sw|w.se|e.sw|e.se|
// +----+----+----+----+
// camera is located in e.nw (east.northwestChild)
var west = quadtree._levelZeroTiles.filter(function(tile) { return tile.x === 0; })[0];
var east = quadtree._levelZeroTiles.filter(function(tile) { return tile.x === 1; })[0];
expect(quadtree._tilesToRender[0]).toBe(east.northwestChild);
expect(quadtree._tilesToRender[1] === east.southwestChild || quadtree._tilesToRender[1] === east.northeastChild).toBe(true);
expect(quadtree._tilesToRender[2] === east.southwestChild || quadtree._tilesToRender[2] === east.northeastChild).toBe(true);
expect(quadtree._tilesToRender[3]).toBe(east.southeastChild);
expect(quadtree._tilesToRender[4]).toBe(west.northeastChild);
expect(quadtree._tilesToRender[5] === west.northwestChild || quadtree._tilesToRender[5] === west.southeastChild).toBe(true);
expect(quadtree._tilesToRender[6] === west.northwestChild || quadtree._tilesToRender[6] === west.southeastChild).toBe(true);
expect(quadtree._tilesToRender[7]).toBe(west.southwestChild);
});
});
}, 'WebGL');