You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1186 lines
40 KiB
JavaScript
1186 lines
40 KiB
JavaScript
import MockTerrainProvider from "../MockTerrainProvider.js";
|
|
import TerrainTileProcessor from "../TerrainTileProcessor.js";
|
|
import { Cartesian3 } from "../../Source/Cesium.js";
|
|
import { Cartographic } from "../../Source/Cesium.js";
|
|
import { defined } from "../../Source/Cesium.js";
|
|
import { Ellipsoid } from "../../Source/Cesium.js";
|
|
import { EventHelper } from "../../Source/Cesium.js";
|
|
import { GeographicProjection } from "../../Source/Cesium.js";
|
|
import { GeographicTilingScheme } from "../../Source/Cesium.js";
|
|
import { Intersect } from "../../Source/Cesium.js";
|
|
import { Rectangle } from "../../Source/Cesium.js";
|
|
import { Visibility } from "../../Source/Cesium.js";
|
|
import { Camera } from "../../Source/Cesium.js";
|
|
import { GlobeSurfaceTileProvider } from "../../Source/Cesium.js";
|
|
import { GlobeTranslucencyState } from "../../Source/Cesium.js";
|
|
import { ImageryLayerCollection } from "../../Source/Cesium.js";
|
|
import { QuadtreePrimitive } from "../../Source/Cesium.js";
|
|
import { QuadtreeTileLoadState } from "../../Source/Cesium.js";
|
|
import { SceneMode } from "../../Source/Cesium.js";
|
|
import createScene from "../createScene.js";
|
|
import pollToPromise from "../pollToPromise.js";
|
|
import { when } from "../../Source/Cesium.js";
|
|
|
|
describe("Scene/QuadtreePrimitive", function () {
|
|
describe("selectTilesForRendering", function () {
|
|
var scene;
|
|
var camera;
|
|
var frameState;
|
|
var quadtree;
|
|
var mockTerrain;
|
|
var tileProvider;
|
|
var imageryLayerCollection;
|
|
var surfaceShaderSet;
|
|
var processor;
|
|
var rootTiles;
|
|
|
|
beforeEach(function () {
|
|
scene = {
|
|
mapProjection: new GeographicProjection(),
|
|
drawingBufferWidth: 1000,
|
|
drawingBufferHeight: 1000,
|
|
};
|
|
|
|
camera = new Camera(scene);
|
|
|
|
frameState = {
|
|
frameNumber: 0,
|
|
passes: {
|
|
render: true,
|
|
},
|
|
camera: camera,
|
|
fog: {
|
|
enabled: false,
|
|
},
|
|
context: {
|
|
drawingBufferWidth: scene.drawingBufferWidth,
|
|
drawingBufferHeight: scene.drawingBufferHeight,
|
|
},
|
|
mode: SceneMode.SCENE3D,
|
|
commandList: [],
|
|
cullingVolume: jasmine.createSpyObj("CullingVolume", [
|
|
"computeVisibility",
|
|
]),
|
|
afterRender: [],
|
|
pixelRatio: 1.0,
|
|
globeTranslucencyState: new GlobeTranslucencyState(),
|
|
};
|
|
|
|
frameState.cullingVolume.computeVisibility.and.returnValue(
|
|
Intersect.INTERSECTING
|
|
);
|
|
|
|
imageryLayerCollection = new ImageryLayerCollection();
|
|
surfaceShaderSet = jasmine.createSpyObj("SurfaceShaderSet", [
|
|
"getShaderProgram",
|
|
]);
|
|
mockTerrain = new MockTerrainProvider();
|
|
tileProvider = new GlobeSurfaceTileProvider({
|
|
terrainProvider: mockTerrain,
|
|
imageryLayers: imageryLayerCollection,
|
|
surfaceShaderSet: surfaceShaderSet,
|
|
});
|
|
quadtree = new QuadtreePrimitive({
|
|
tileProvider: tileProvider,
|
|
});
|
|
|
|
processor = new TerrainTileProcessor(
|
|
frameState,
|
|
mockTerrain,
|
|
imageryLayerCollection
|
|
);
|
|
|
|
quadtree.render(frameState);
|
|
rootTiles = quadtree._levelZeroTiles;
|
|
|
|
processor.mockWebGL();
|
|
});
|
|
|
|
function process(quadtreePrimitive, callback) {
|
|
var deferred = when.defer();
|
|
|
|
function next() {
|
|
++frameState.frameNumber;
|
|
quadtree.beginFrame(frameState);
|
|
quadtree.render(frameState);
|
|
quadtree.endFrame(frameState);
|
|
|
|
if (callback()) {
|
|
setTimeout(next, 0);
|
|
} else {
|
|
deferred.resolve();
|
|
}
|
|
}
|
|
|
|
next();
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
it("must be constructed with a tileProvider", function () {
|
|
expect(function () {
|
|
return new QuadtreePrimitive();
|
|
}).toThrowDeveloperError();
|
|
|
|
expect(function () {
|
|
return new QuadtreePrimitive({});
|
|
}).toThrowDeveloperError();
|
|
});
|
|
|
|
it("selects nothing when the root tiles are not yet ready", function () {
|
|
quadtree.render(frameState);
|
|
expect(quadtree._tilesToRender.length).toBe(0);
|
|
});
|
|
|
|
it("selects root tiles once they are ready", function () {
|
|
mockTerrain
|
|
.requestTileGeometryWillSucceed(rootTiles[0])
|
|
.requestTileGeometryWillSucceed(rootTiles[1])
|
|
.createMeshWillSucceed(rootTiles[0])
|
|
.createMeshWillSucceed(rootTiles[1]);
|
|
|
|
return processor.process(rootTiles).then(function () {
|
|
quadtree.render(frameState);
|
|
|
|
// There should be at least one selected tile.
|
|
expect(quadtree._tilesToRender.length).toBeGreaterThan(0);
|
|
|
|
// All selected tiles should be root tiles.
|
|
expect(
|
|
quadtree._tilesToRender.filter(function (tile) {
|
|
return tile.level === 0;
|
|
}).length
|
|
).toBe(quadtree._tilesToRender.length);
|
|
});
|
|
});
|
|
|
|
it("selects deeper tiles once they are renderable", function () {
|
|
mockTerrain
|
|
.requestTileGeometryWillSucceed(rootTiles[0])
|
|
.requestTileGeometryWillSucceed(rootTiles[1])
|
|
.createMeshWillSucceed(rootTiles[0])
|
|
.createMeshWillSucceed(rootTiles[1]);
|
|
|
|
rootTiles[0].children.forEach(function (tile) {
|
|
mockTerrain
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
expect(tile.renderable).toBe(false);
|
|
});
|
|
|
|
return processor
|
|
.process(rootTiles)
|
|
.then(function () {
|
|
quadtree.render(frameState);
|
|
|
|
// All selected tiles should be root tiles.
|
|
expect(quadtree._tilesToRender.length).toBeGreaterThan(0);
|
|
expect(
|
|
quadtree._tilesToRender.filter(function (tile) {
|
|
return tile.level === 0;
|
|
}).length
|
|
).toBe(quadtree._tilesToRender.length);
|
|
|
|
// Allow the child tiles to load.
|
|
return processor.process(rootTiles[0].children);
|
|
})
|
|
.then(function () {
|
|
quadtree.render(frameState);
|
|
|
|
// Now child tiles should be rendered too.
|
|
expect(quadtree._tilesToRender).toContain(
|
|
rootTiles[0].southwestChild
|
|
);
|
|
expect(quadtree._tilesToRender).toContain(
|
|
rootTiles[0].southeastChild
|
|
);
|
|
expect(quadtree._tilesToRender).toContain(
|
|
rootTiles[0].northwestChild
|
|
);
|
|
expect(quadtree._tilesToRender).toContain(
|
|
rootTiles[0].northeastChild
|
|
);
|
|
});
|
|
});
|
|
|
|
it("skips loading levels when tiles are known to be available", function () {
|
|
// Mark all tiles through level 2 as available.
|
|
rootTiles.forEach(function (tile) {
|
|
// level 0 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 1 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 2 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
});
|
|
});
|
|
});
|
|
|
|
quadtree.preloadAncestors = false;
|
|
|
|
// Look down at the center of a level 2 tile from a distance that will refine to it.
|
|
var lookAtTile = rootTiles[0].southwestChild.northeastChild;
|
|
setCameraPosition(
|
|
quadtree,
|
|
frameState,
|
|
Rectangle.center(lookAtTile.rectangle),
|
|
lookAtTile.level
|
|
);
|
|
|
|
spyOn(mockTerrain, "requestTileGeometry").and.callThrough();
|
|
|
|
return process(quadtree, function () {
|
|
// Process until the lookAtTile is rendered. That tile's parent (level 1)
|
|
// should not be rendered along the way.
|
|
expect(quadtree._tilesToRender).not.toContain(lookAtTile.parent);
|
|
var lookAtTileRendered =
|
|
quadtree._tilesToRender.indexOf(lookAtTile) >= 0;
|
|
var continueProcessing = !lookAtTileRendered;
|
|
return continueProcessing;
|
|
}).then(function () {
|
|
// The lookAtTile should be a real tile, not a fill.
|
|
expect(quadtree._tilesToRender).toContain(lookAtTile);
|
|
expect(lookAtTile.data.fill).toBeUndefined();
|
|
expect(lookAtTile.data.vertexArray).toBeDefined();
|
|
|
|
// The parent of the lookAtTile should not have been requested.
|
|
var parent = lookAtTile.parent;
|
|
mockTerrain.requestTileGeometry.calls
|
|
.allArgs()
|
|
.forEach(function (call) {
|
|
expect(call.slice(0, 3)).not.toEqual([
|
|
parent.x,
|
|
parent.y,
|
|
parent.level,
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
|
|
it("does not skip loading levels if availability is unknown", function () {
|
|
// Mark all tiles through level 2 as available.
|
|
rootTiles.forEach(function (tile) {
|
|
// level 0 tile
|
|
mockTerrain
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 1 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 2 tile
|
|
mockTerrain
|
|
.willBeUnknownAvailability(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
});
|
|
});
|
|
});
|
|
|
|
quadtree.preloadAncestors = false;
|
|
|
|
// Look down at the center of a level 2 tile from a distance that will refine to it.
|
|
var lookAtTile = rootTiles[0].southwestChild.northeastChild;
|
|
setCameraPosition(
|
|
quadtree,
|
|
frameState,
|
|
Rectangle.center(lookAtTile.rectangle),
|
|
lookAtTile.level
|
|
);
|
|
|
|
spyOn(mockTerrain, "requestTileGeometry").and.callThrough();
|
|
|
|
return process(quadtree, function () {
|
|
// Process until the lookAtTile is rendered. That tile's parent (level 1)
|
|
// should not be rendered along the way, but it will be loaded.
|
|
expect(quadtree._tilesToRender).not.toContain(lookAtTile.parent);
|
|
var lookAtTileRendered =
|
|
quadtree._tilesToRender.indexOf(lookAtTile) >= 0;
|
|
var continueProcessing = !lookAtTileRendered;
|
|
return continueProcessing;
|
|
}).then(function () {
|
|
// The lookAtTile should be a real tile, not a fill.
|
|
expect(quadtree._tilesToRender).toContain(lookAtTile);
|
|
expect(lookAtTile.data.fill).toBeUndefined();
|
|
expect(lookAtTile.data.vertexArray).toBeDefined();
|
|
|
|
// The parent of the lookAtTile should have been requested before the lookAtTile itself.
|
|
var parent = lookAtTile.parent;
|
|
var allArgs = mockTerrain.requestTileGeometry.calls.allArgs();
|
|
var parentArgsIndex = allArgs.indexOf(
|
|
allArgs.filter(function (call) {
|
|
return (
|
|
call[0] === parent.x &&
|
|
call[1] === parent.y &&
|
|
call[2] === parent.level
|
|
);
|
|
})[0]
|
|
);
|
|
var lookAtArgsIndex = allArgs.indexOf(
|
|
allArgs.filter(function (call) {
|
|
return (
|
|
call[0] === lookAtTile.x &&
|
|
call[1] === lookAtTile.y &&
|
|
call[2] === lookAtTile.level
|
|
);
|
|
})[0]
|
|
);
|
|
expect(parentArgsIndex).toBeLessThan(lookAtArgsIndex);
|
|
});
|
|
});
|
|
|
|
it("loads and renders intermediate tiles according to loadingDescendantLimit", function () {
|
|
// Mark all tiles through level 2 as available.
|
|
rootTiles.forEach(function (tile) {
|
|
// level 0 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 1 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 2 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
});
|
|
});
|
|
});
|
|
|
|
quadtree.preloadAncestors = false;
|
|
quadtree.loadingDescendantLimit = 1;
|
|
|
|
// Look down at the center of a level 2 tile from a distance that will refine to it.
|
|
var lookAtTile = rootTiles[0].southwestChild.northeastChild;
|
|
setCameraPosition(
|
|
quadtree,
|
|
frameState,
|
|
Rectangle.center(lookAtTile.rectangle),
|
|
lookAtTile.level
|
|
);
|
|
|
|
spyOn(mockTerrain, "requestTileGeometry").and.callThrough();
|
|
|
|
return process(quadtree, function () {
|
|
// First the lookAtTile's parent should be rendered.
|
|
var lookAtTileParentRendered =
|
|
quadtree._tilesToRender.indexOf(lookAtTile.parent) >= 0;
|
|
var continueProcessing = !lookAtTileParentRendered;
|
|
return continueProcessing;
|
|
})
|
|
.then(function () {
|
|
// The lookAtTile's parent should be a real tile, not a fill.
|
|
expect(quadtree._tilesToRender).toContain(lookAtTile.parent);
|
|
expect(lookAtTile.parent.data.fill).toBeUndefined();
|
|
expect(lookAtTile.parent.data.vertexArray).toBeDefined();
|
|
|
|
return process(quadtree, function () {
|
|
// Then the lookAtTile should be rendered.
|
|
var lookAtTileRendered =
|
|
quadtree._tilesToRender.indexOf(lookAtTile) >= 0;
|
|
var continueProcessing = !lookAtTileRendered;
|
|
return continueProcessing;
|
|
});
|
|
})
|
|
.then(function () {
|
|
// The lookAtTile should be a real tile, not a fill.
|
|
expect(quadtree._tilesToRender).toContain(lookAtTile);
|
|
expect(lookAtTile.data.fill).toBeUndefined();
|
|
expect(lookAtTile.data.vertexArray).toBeDefined();
|
|
});
|
|
});
|
|
|
|
it("continues rendering more detailed tiles when camera zooms out and an appropriate ancestor is not yet renderable", function () {
|
|
// Mark all tiles through level 2 as available.
|
|
rootTiles.forEach(function (tile) {
|
|
// level 0 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 1 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 2 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
});
|
|
});
|
|
});
|
|
|
|
quadtree.preloadAncestors = false;
|
|
|
|
// Look down at the center of a level 2 tile from a distance that will refine to it.
|
|
var lookAtTile = rootTiles[0].southwestChild.northeastChild;
|
|
setCameraPosition(
|
|
quadtree,
|
|
frameState,
|
|
Rectangle.center(lookAtTile.rectangle),
|
|
lookAtTile.level
|
|
);
|
|
|
|
spyOn(mockTerrain, "requestTileGeometry").and.callThrough();
|
|
|
|
return process(quadtree, function () {
|
|
// Process until the lookAtTile is rendered. That tile's parent (level 1)
|
|
// should not be rendered along the way.
|
|
expect(quadtree._tilesToRender).not.toContain(lookAtTile.parent);
|
|
var lookAtTileRendered =
|
|
quadtree._tilesToRender.indexOf(lookAtTile) >= 0;
|
|
var continueProcessing = !lookAtTileRendered;
|
|
return continueProcessing;
|
|
})
|
|
.then(function () {
|
|
// Zoom out so the parent tile no longer needs to refine to meet SSE.
|
|
setCameraPosition(
|
|
quadtree,
|
|
frameState,
|
|
Rectangle.center(lookAtTile.rectangle),
|
|
lookAtTile.parent.level
|
|
);
|
|
|
|
// Select new tiles
|
|
quadtree.beginFrame(frameState);
|
|
quadtree.render(frameState);
|
|
quadtree.endFrame(frameState);
|
|
|
|
// The lookAtTile should still be rendered, not it's parent.
|
|
expect(quadtree._tilesToRender).toContain(lookAtTile);
|
|
expect(quadtree._tilesToRender).not.toContain(lookAtTile.parent);
|
|
|
|
return process(quadtree, function () {
|
|
// Eventually the parent should be rendered instead.
|
|
var parentRendered =
|
|
quadtree._tilesToRender.indexOf(lookAtTile.parent) >= 0;
|
|
var continueProcessing = !parentRendered;
|
|
return continueProcessing;
|
|
});
|
|
})
|
|
.then(function () {
|
|
expect(quadtree._tilesToRender).not.toContain(lookAtTile);
|
|
expect(quadtree._tilesToRender).toContain(lookAtTile.parent);
|
|
});
|
|
});
|
|
|
|
it("renders a fill for a newly-visible tile", function () {
|
|
// Mark all tiles through level 2 as available.
|
|
rootTiles.forEach(function (tile) {
|
|
// level 0 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 1 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
|
|
tile.children.forEach(function (tile) {
|
|
// level 2 tile
|
|
mockTerrain
|
|
.willBeAvailable(tile)
|
|
.requestTileGeometryWillSucceed(tile)
|
|
.createMeshWillSucceed(tile);
|
|
});
|
|
});
|
|
});
|
|
|
|
quadtree.preloadAncestors = false;
|
|
|
|
var visibleTile = rootTiles[0].southwestChild.northeastChild;
|
|
var notVisibleTile = rootTiles[0].southwestChild.northwestChild;
|
|
|
|
frameState.cullingVolume.computeVisibility.and.callFake(function (
|
|
boundingVolume
|
|
) {
|
|
if (!defined(visibleTile.data)) {
|
|
return Intersect.INTERSECTING;
|
|
}
|
|
|
|
if (boundingVolume === visibleTile.data.orientedBoundingBox) {
|
|
return Intersect.INTERSECTING;
|
|
} else if (boundingVolume === notVisibleTile.data.orientedBoundingBox) {
|
|
return Intersect.OUTSIDE;
|
|
}
|
|
return Intersect.INTERSECTING;
|
|
});
|
|
|
|
// Look down at the center of the visible tile.
|
|
setCameraPosition(
|
|
quadtree,
|
|
frameState,
|
|
Rectangle.center(visibleTile.rectangle),
|
|
visibleTile.level
|
|
);
|
|
|
|
spyOn(mockTerrain, "requestTileGeometry").and.callThrough();
|
|
|
|
return process(quadtree, function () {
|
|
// Process until the visibleTile is rendered.
|
|
var visibleTileRendered =
|
|
quadtree._tilesToRender.indexOf(visibleTile) >= 0;
|
|
var continueProcessing = !visibleTileRendered;
|
|
return continueProcessing;
|
|
}).then(function () {
|
|
expect(quadtree._tilesToRender).not.toContain(notVisibleTile);
|
|
|
|
// Now treat the not-visible-tile as visible.
|
|
frameState.cullingVolume.computeVisibility.and.returnValue(
|
|
Intersect.INTERSECTING
|
|
);
|
|
|
|
// Select new tiles
|
|
quadtree.beginFrame(frameState);
|
|
quadtree.render(frameState);
|
|
quadtree.endFrame(frameState);
|
|
|
|
// The notVisibleTile should be rendered as a fill.
|
|
expect(quadtree._tilesToRender).toContain(visibleTile);
|
|
expect(quadtree._tilesToRender).toContain(notVisibleTile);
|
|
expect(notVisibleTile.data.fill).toBeDefined();
|
|
expect(notVisibleTile.data.vertexArray).toBeUndefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe(
|
|
"with mock tile provider",
|
|
function () {
|
|
var scene;
|
|
|
|
beforeAll(function () {
|
|
scene = createScene();
|
|
scene.render();
|
|
});
|
|
|
|
afterAll(function () {
|
|
scene.destroyForSpecs();
|
|
});
|
|
|
|
function createSpyTileProvider() {
|
|
var result = jasmine.createSpyObj("tileProvider", [
|
|
"getQuadtree",
|
|
"setQuadtree",
|
|
"getReady",
|
|
"getTilingScheme",
|
|
"getErrorEvent",
|
|
"initialize",
|
|
"updateImagery",
|
|
"beginUpdate",
|
|
"endUpdate",
|
|
"getLevelMaximumGeometricError",
|
|
"loadTile",
|
|
"computeTileVisibility",
|
|
"showTileThisFrame",
|
|
"computeDistanceToTile",
|
|
"canRefine",
|
|
"isDestroyed",
|
|
"destroy",
|
|
]);
|
|
|
|
Object.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);
|
|
|
|
result.canRefine.and.callFake(function (tile) {
|
|
return tile.renderable;
|
|
});
|
|
|
|
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);
|
|
|
|
++scene.frameState.frameNumber;
|
|
|
|
// 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();
|
|
|
|
++scene.frameState.frameNumber;
|
|
|
|
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;
|
|
},
|
|
mesh: {},
|
|
};
|
|
});
|
|
|
|
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.
|
|
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(0);
|
|
});
|
|
|
|
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"
|
|
);
|
|
|
|
// Sets the camera to look at a given cartographic position from a distance
|
|
// that will produce a screen-space error at that position that will refine to
|
|
// a given tile level and no further.
|
|
function setCameraPosition(quadtree, frameState, position, level) {
|
|
var camera = frameState.camera;
|
|
var geometricError = quadtree.tileProvider.getLevelMaximumGeometricError(
|
|
level
|
|
);
|
|
var sse = quadtree.maximumScreenSpaceError * 0.8;
|
|
var sseDenominator = camera.frustum.sseDenominator;
|
|
var height = frameState.context.drawingBufferHeight;
|
|
|
|
var distance = (geometricError * height) / (sse * sseDenominator);
|
|
var cartesian = Ellipsoid.WGS84.cartographicToCartesian(position);
|
|
camera.lookAt(cartesian, new Cartesian3(0.0, 0.0, distance));
|
|
}
|
|
});
|