mirror of https://github.com/CesiumGS/cesium.git
Merge pull request #12907 from CesiumGS/quadtree-performance
Quadtree performance
This commit is contained in:
commit
3418fa936e
|
|
@ -14,6 +14,7 @@
|
|||
- Fix issues with label background when updating properties while `label.show` is `false`. [#12138](https://github.com/CesiumGS/cesium/issues/12138)
|
||||
- Improved performance of `scene.drillPick`. [#12916](https://github.com/CesiumGS/cesium/pull/12916)
|
||||
- Improved performance when removing primitives. [#3018](https://github.com/CesiumGS/cesium/pull/3018)
|
||||
- Improved performance of terrain Quadtree handling of custom data [#12907](https://github.com/CesiumGS/cesium/pull/12907)
|
||||
|
||||
## 1.134.1 - 2025-10-10
|
||||
|
||||
|
|
|
|||
|
|
@ -493,10 +493,8 @@ GlobeSurfaceTile.prototype.updateExaggeration = function (
|
|||
if (quadtree !== undefined) {
|
||||
quadtree._tileToUpdateHeights.push(tile);
|
||||
const customData = tile.customData;
|
||||
const customDataLength = customData.length;
|
||||
for (let i = 0; i < customDataLength; i++) {
|
||||
for (const data of customData) {
|
||||
// Restart the level so that a height update is triggered
|
||||
const data = customData[i];
|
||||
data.level = -1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ function QuadtreePrimitive(options) {
|
|||
this._removeHeightCallbacks = [];
|
||||
|
||||
this._tileToUpdateHeights = [];
|
||||
this._lastTileIndex = 0;
|
||||
this._updateHeightsTimeSlice = 2.0;
|
||||
|
||||
// If a culled tile contains _cameraPositionCartographic or _cameraReferenceFrameOriginCartographic, it will be marked
|
||||
|
|
@ -217,10 +216,8 @@ function invalidateAllTiles(primitive) {
|
|||
for (let i = 0; i < levelZeroTiles.length; ++i) {
|
||||
const tile = levelZeroTiles[i];
|
||||
const customData = tile.customData;
|
||||
const customDataLength = customData.length;
|
||||
|
||||
for (let j = 0; j < customDataLength; ++j) {
|
||||
const data = customData[j];
|
||||
for (const data of customData) {
|
||||
data.level = 0;
|
||||
primitive._addHeightCallbacks.push(data);
|
||||
}
|
||||
|
|
@ -513,7 +510,6 @@ function selectTilesForRendering(primitive, frameState) {
|
|||
tilesToRender.length = 0;
|
||||
|
||||
// We can't render anything before the level zero tiles exist.
|
||||
let i;
|
||||
const tileProvider = primitive._tileProvider;
|
||||
if (!defined(primitive._levelZeroTiles)) {
|
||||
const tilingScheme = tileProvider.tilingScheme;
|
||||
|
|
@ -524,7 +520,7 @@ function selectTilesForRendering(primitive, frameState) {
|
|||
const numberOfRootTiles = primitive._levelZeroTiles.length;
|
||||
if (rootTraversalDetails.length < numberOfRootTiles) {
|
||||
rootTraversalDetails = new Array(numberOfRootTiles);
|
||||
for (i = 0; i < numberOfRootTiles; ++i) {
|
||||
for (let i = 0; i < numberOfRootTiles; ++i) {
|
||||
if (rootTraversalDetails[i] === undefined) {
|
||||
rootTraversalDetails[i] = new TraversalDetails();
|
||||
}
|
||||
|
|
@ -537,7 +533,6 @@ function selectTilesForRendering(primitive, frameState) {
|
|||
|
||||
primitive._occluders.ellipsoid.cameraPosition = frameState.camera.positionWC;
|
||||
|
||||
let tile;
|
||||
const levelZeroTiles = primitive._levelZeroTiles;
|
||||
const occluders =
|
||||
levelZeroTiles.length > 1 ? primitive._occluders : undefined;
|
||||
|
|
@ -550,18 +545,28 @@ function selectTilesForRendering(primitive, frameState) {
|
|||
|
||||
const customDataAdded = primitive._addHeightCallbacks;
|
||||
const customDataRemoved = primitive._removeHeightCallbacks;
|
||||
const frameNumber = frameState.frameNumber;
|
||||
|
||||
let len;
|
||||
if (customDataAdded.length > 0 || customDataRemoved.length > 0) {
|
||||
for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
|
||||
tile = levelZeroTiles[i];
|
||||
tile._updateCustomData(frameNumber, customDataAdded, customDataRemoved);
|
||||
customDataAdded.forEach((data) => {
|
||||
const tile = levelZeroTiles.find((tile) =>
|
||||
Rectangle.contains(tile.rectangle, data.positionCartographic),
|
||||
);
|
||||
if (tile) {
|
||||
tile._addedCustomData.push(data);
|
||||
}
|
||||
});
|
||||
|
||||
customDataAdded.length = 0;
|
||||
customDataRemoved.length = 0;
|
||||
}
|
||||
customDataRemoved.forEach((data) => {
|
||||
const tile = levelZeroTiles.find((tile) =>
|
||||
Rectangle.contains(tile.rectangle, data.positionCartographic),
|
||||
);
|
||||
if (tile) {
|
||||
tile._removedCustomData.push(data);
|
||||
}
|
||||
});
|
||||
|
||||
levelZeroTiles.forEach((tile) => tile.updateCustomData());
|
||||
customDataAdded.length = 0;
|
||||
customDataRemoved.length = 0;
|
||||
|
||||
const camera = frameState.camera;
|
||||
|
||||
|
|
@ -577,8 +582,8 @@ function selectTilesForRendering(primitive, frameState) {
|
|||
);
|
||||
|
||||
// Traverse in depth-first, near-to-far order.
|
||||
for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
|
||||
tile = levelZeroTiles[i];
|
||||
for (let i = 0; i < levelZeroTiles.length; ++i) {
|
||||
const tile = levelZeroTiles[i];
|
||||
primitive._tileReplacementQueue.markTileRendered(tile);
|
||||
if (!tile.renderable) {
|
||||
queueTileLoad(primitive, primitive._tileLoadQueueHigh, tile, frameState);
|
||||
|
|
@ -596,7 +601,7 @@ function selectTilesForRendering(primitive, frameState) {
|
|||
}
|
||||
}
|
||||
|
||||
primitive._lastSelectionFrameNumber = frameNumber;
|
||||
primitive._lastSelectionFrameNumber = frameState.frameNumber;
|
||||
}
|
||||
|
||||
function queueTileLoad(primitive, queue, tile, frameState) {
|
||||
|
|
@ -716,7 +721,7 @@ function visitTile(
|
|||
++debug.tilesVisited;
|
||||
|
||||
primitive._tileReplacementQueue.markTileRendered(tile);
|
||||
tile._updateCustomData(frameState.frameNumber);
|
||||
tile.updateCustomData();
|
||||
|
||||
if (tile.level > debug.maxDepthVisited) {
|
||||
debug.maxDepthVisited = tile.level;
|
||||
|
|
@ -1417,15 +1422,18 @@ function updateHeights(primitive, frameState) {
|
|||
// Ensure stale position cache is cleared
|
||||
tile.clearPositionCache();
|
||||
tilesToUpdateHeights.shift();
|
||||
primitive._lastTileIndex = 0;
|
||||
continue;
|
||||
}
|
||||
const customData = tile.customData;
|
||||
const customDataLength = customData.length;
|
||||
if (!defined(tile._customDataIterator)) {
|
||||
tile._customDataIterator = customData.values();
|
||||
}
|
||||
const customDataIterator = tile._customDataIterator;
|
||||
|
||||
let timeSliceMax = false;
|
||||
for (i = primitive._lastTileIndex; i < customDataLength; ++i) {
|
||||
const data = customData[i];
|
||||
let nextData;
|
||||
while (!(nextData = customDataIterator.next()).done) {
|
||||
const data = nextData.value;
|
||||
|
||||
// No need to run this code when the tile is upsampled, because the height will be the same as its parent.
|
||||
const terrainData = tile.data.terrainData;
|
||||
|
|
@ -1543,10 +1551,10 @@ function updateHeights(primitive, frameState) {
|
|||
}
|
||||
|
||||
if (timeSliceMax) {
|
||||
primitive._lastTileIndex = i;
|
||||
tile._customDataIterator = customDataIterator;
|
||||
break;
|
||||
} else {
|
||||
primitive._lastTileIndex = 0;
|
||||
tile._customDataIterator = undefined;
|
||||
tilesToUpdateHeights.shift();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import defined from "../Core/defined.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import Rectangle from "../Core/Rectangle.js";
|
||||
import Cartographic from "../Core/Cartographic.js";
|
||||
import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js";
|
||||
import TileSelectionResult from "./TileSelectionResult.js";
|
||||
|
||||
|
|
@ -107,8 +108,10 @@ function QuadtreeTile(options) {
|
|||
this._distance = 0.0;
|
||||
this._loadPriority = 0.0;
|
||||
|
||||
this._customData = [];
|
||||
this._frameUpdated = undefined;
|
||||
this._customData = new Set();
|
||||
this._customDataIterator = undefined;
|
||||
this._addedCustomData = [];
|
||||
this._removedCustomData = [];
|
||||
this._lastSelectionResult = TileSelectionResult.NONE;
|
||||
this._lastSelectionResultFrame = undefined;
|
||||
this._loadedCallbacks = {};
|
||||
|
|
@ -311,52 +314,69 @@ QuadtreeTile.prototype.clearPositionCache = function () {
|
|||
}
|
||||
};
|
||||
|
||||
QuadtreeTile.prototype._updateCustomData = function (
|
||||
frameNumber,
|
||||
added,
|
||||
removed,
|
||||
) {
|
||||
let customData = this.customData;
|
||||
|
||||
let i;
|
||||
let data;
|
||||
let rectangle;
|
||||
|
||||
if (defined(added) && defined(removed)) {
|
||||
customData = customData.filter(function (value) {
|
||||
return removed.indexOf(value) === -1;
|
||||
});
|
||||
this._customData = customData;
|
||||
|
||||
rectangle = this._rectangle;
|
||||
for (i = 0; i < added.length; ++i) {
|
||||
data = added[i];
|
||||
if (Rectangle.contains(rectangle, data.positionCartographic)) {
|
||||
customData.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
this._frameUpdated = frameNumber;
|
||||
} else {
|
||||
// interior or leaf tile, update from parent
|
||||
const parent = this._parent;
|
||||
if (defined(parent) && this._frameUpdated !== parent._frameUpdated) {
|
||||
customData.length = 0;
|
||||
|
||||
rectangle = this._rectangle;
|
||||
const parentCustomData = parent.customData;
|
||||
for (i = 0; i < parentCustomData.length; ++i) {
|
||||
data = parentCustomData[i];
|
||||
if (Rectangle.contains(rectangle, data.positionCartographic)) {
|
||||
customData.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
this._frameUpdated = parent._frameUpdated;
|
||||
}
|
||||
QuadtreeTile.prototype.updateCustomData = function () {
|
||||
const added = this._addedCustomData;
|
||||
const removed = this._removedCustomData;
|
||||
if (added.length === 0 && removed.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const customData = this.customData;
|
||||
for (let i = 0; i < added.length; ++i) {
|
||||
const data = added[i];
|
||||
customData.add(data);
|
||||
|
||||
const child = childTileAtPosition(this, data.positionCartographic);
|
||||
child._addedCustomData.push(data);
|
||||
}
|
||||
this._addedCustomData.length = 0;
|
||||
|
||||
for (let i = 0; i < removed.length; ++i) {
|
||||
const data = removed[i];
|
||||
if (customData.has(data)) {
|
||||
customData.delete(data);
|
||||
}
|
||||
|
||||
const child = childTileAtPosition(this, data.positionCartographic);
|
||||
child._removedCustomData.push(data);
|
||||
}
|
||||
this._removedCustomData.length = 0;
|
||||
};
|
||||
|
||||
const splitPointScratch = new Cartographic();
|
||||
|
||||
/**
|
||||
* Determines which child tile that contains the specified position. Assumes the position is within
|
||||
* the bounds of the parent tile.
|
||||
* @private
|
||||
* @param {QuadtreeTile} tile - The parent tile.
|
||||
* @param {Cartographic} positionCartographic - The cartographic position.
|
||||
* @returns {QuadtreeTile} The child tile that contains the position.
|
||||
*/
|
||||
function childTileAtPosition(tile, positionCartographic) {
|
||||
// Can't assume that a given tiling scheme divides a parent into four tiles at its rectangle's center.
|
||||
// But we can safely take any child tile's rectangle and take its center-facing corner as the parent's split point.
|
||||
const nwChildRectangle = tile.northwestChild.rectangle;
|
||||
const tileSplitPoint = Rectangle.southeast(
|
||||
nwChildRectangle,
|
||||
splitPointScratch,
|
||||
);
|
||||
|
||||
const x = positionCartographic.longitude >= tileSplitPoint.longitude ? 1 : 0;
|
||||
const y = positionCartographic.latitude < tileSplitPoint.latitude ? 1 : 0;
|
||||
|
||||
switch (y * 2 + x) {
|
||||
case 0:
|
||||
return tile.northwestChild;
|
||||
case 1:
|
||||
return tile.northeastChild;
|
||||
case 2:
|
||||
return tile.southwestChild;
|
||||
default:
|
||||
return tile.southeastChild;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(QuadtreeTile.prototype, {
|
||||
/**
|
||||
* Gets the tiling scheme used to tile the surface.
|
||||
|
|
@ -522,9 +542,9 @@ Object.defineProperties(QuadtreeTile.prototype, {
|
|||
},
|
||||
|
||||
/**
|
||||
* An array of objects associated with this tile.
|
||||
* A set of objects associated with this tile.
|
||||
* @memberof QuadtreeTile.prototype
|
||||
* @type {Array}
|
||||
* @type {Set}
|
||||
*/
|
||||
customData: {
|
||||
get: function () {
|
||||
|
|
|
|||
|
|
@ -899,7 +899,7 @@ describe("Scene/QuadtreePrimitive", function () {
|
|||
|
||||
let addedCallback = false;
|
||||
quadtree.forEachLoadedTile(function (tile) {
|
||||
addedCallback = addedCallback || tile.customData.length > 0;
|
||||
addedCallback = addedCallback || tile.customData.size > 0;
|
||||
});
|
||||
|
||||
expect(addedCallback).toEqual(true);
|
||||
|
|
@ -915,7 +915,7 @@ describe("Scene/QuadtreePrimitive", function () {
|
|||
|
||||
let removedCallback = true;
|
||||
quadtree.forEachLoadedTile(function (tile) {
|
||||
removedCallback = removedCallback && tile.customData.length === 0;
|
||||
removedCallback = removedCallback && tile.customData.size === 0;
|
||||
});
|
||||
|
||||
expect(removedCallback).toEqual(true);
|
||||
|
|
|
|||
|
|
@ -458,4 +458,53 @@ describe("Scene/QuadtreeTile", function () {
|
|||
expect(cachedData).toEqual(dummyPosition);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateCustomData", function () {
|
||||
function addAndRemoveCustomData(tilingScheme) {
|
||||
const tile = new QuadtreeTile({
|
||||
level: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
tilingScheme: tilingScheme,
|
||||
});
|
||||
|
||||
const child = tile.northwestChild;
|
||||
const centerCartographic = Rectangle.center(child.rectangle);
|
||||
|
||||
const data = {
|
||||
positionCartographic: centerCartographic,
|
||||
};
|
||||
|
||||
tile._addedCustomData.push(data);
|
||||
tile.updateCustomData();
|
||||
|
||||
expect(tile.customData.has(data)).toBe(true);
|
||||
expect(tile._addedCustomData.length).toBe(0);
|
||||
expect(child._addedCustomData.length).toBe(1);
|
||||
expect(child._addedCustomData[0]).toBe(data);
|
||||
|
||||
child.updateCustomData();
|
||||
expect(child.customData.has(data)).toBe(true);
|
||||
|
||||
// Now remove the data from the parent tile.
|
||||
tile._removedCustomData.push(data);
|
||||
tile.updateCustomData();
|
||||
|
||||
expect(tile.customData.has(data)).toBe(false);
|
||||
expect(tile._removedCustomData.length).toBe(0);
|
||||
expect(child._removedCustomData.length).toBe(1);
|
||||
expect(child._removedCustomData[0]).toBe(data);
|
||||
|
||||
child.updateCustomData();
|
||||
expect(child.customData.has(data)).toBe(false);
|
||||
}
|
||||
|
||||
it("can add and remove custom data when tiling scheme is GeographicTilingScheme", function () {
|
||||
addAndRemoveCustomData(new GeographicTilingScheme());
|
||||
});
|
||||
|
||||
it("can add and remove custom data when tiling scheme is WebMercatorTilingScheme", function () {
|
||||
addAndRemoveCustomData(new WebMercatorTilingScheme());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue