Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Improved performance and reduced memory usage of `Event` class. [#12896](https://github.com/CesiumGS/cesium/pull/12896)
- Fixes vertical misalignment of glyphs in labels with small fonts [#8474](https://github.com/CesiumGS/cesium/issues/8474)
- Prevent runtime errors for certain forms of invalid PNTS files [#12872](https://github.com/CesiumGS/cesium/issues/12872)
- Improved performance of terrain Quadtree handling of custom data [#12907](https://github.com/CesiumGS/cesium/pull/12907)

#### Additions :tada:

Expand Down
4 changes: 1 addition & 3 deletions packages/engine/Source/Scene/GlobeSurfaceTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
60 changes: 34 additions & 26 deletions packages/engine/Source/Scene/QuadtreePrimitive.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand All @@ -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();
}
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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);
Expand All @@ -596,7 +601,7 @@ function selectTilesForRendering(primitive, frameState) {
}
}

primitive._lastSelectionFrameNumber = frameNumber;
primitive._lastSelectionFrameNumber = frameState.frameNumber;
}

function queueTileLoad(primitive, queue, tile, frameState) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}
Expand Down
100 changes: 56 additions & 44 deletions packages/engine/Source/Scene/QuadtreeTile.js
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -107,8 +108,9 @@ function QuadtreeTile(options) {
this._distance = 0.0;
this._loadPriority = 0.0;

this._customData = [];
this._frameUpdated = undefined;
this._customData = new Set();
this._addedCustomData = [];
this._removedCustomData = [];
this._lastSelectionResult = TileSelectionResult.NONE;
this._lastSelectionResultFrame = undefined;
this._loadedCallbacks = {};
Expand Down Expand Up @@ -311,52 +313,62 @@ 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);
}
}
QuadtreeTile.prototype._updateCustomData = function () {
const added = this._addedCustomData;
const removed = this._removedCustomData;
if (added.length === 0 && removed.length === 0) {
return;
}

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);
}
}
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;

this._frameUpdated = parent._frameUpdated;
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 centerScratch = 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) {
const center = Rectangle.center(tile.rectangle, centerScratch);
const x = positionCartographic.longitude >= center.longitude ? 1 : 0;
const y = positionCartographic.latitude < center.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;
}
}

Comment on lines +355 to +371
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two comments / questions on this:

  1. Is this reliable? I don't think it works if the tile crosses the IDL, so is that a bad assumption to make here?
  2. Instead of a switch statement, we could define an array out of this function that contains the names of the children and index into that. But that means if the variable name changes, so must the array. I wasn't sure if that was a good tradeoff for a minimal speed boost (if there's any), but input is welcome.

Object.defineProperties(QuadtreeTile.prototype, {
/**
* Gets the tiling scheme used to tile the surface.
Expand Down Expand Up @@ -522,9 +534,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 () {
Expand Down
4 changes: 2 additions & 2 deletions packages/engine/Specs/Scene/QuadtreePrimitiveSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down