diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js
index 3237d5ef8be3..ea6b571be395 100644
--- a/Apps/CesiumViewer/CesiumViewer.js
+++ b/Apps/CesiumViewer/CesiumViewer.js
@@ -1,6 +1,7 @@
-if (window.CESIUM_BASE_URL === undefined) {
- window.CESIUM_BASE_URL = "../../Build/CesiumUnminified/";
-}
+// eslint-disable-next-line no-undef
+window.CESIUM_BASE_URL = window.CESIUM_BASE_URL
+ ? window.CESIUM_BASE_URL
+ : "../../Build/CesiumUnminified/";
import {
Cartesian3,
diff --git a/Apps/CesiumViewer/index.js b/Apps/CesiumViewer/index.js
deleted file mode 100644
index f4e3e3d56f26..000000000000
--- a/Apps/CesiumViewer/index.js
+++ /dev/null
@@ -1 +0,0 @@
-window.CESIUM_BASE_URL = ".";
diff --git a/CHANGES.md b/CHANGES.md
index 3798045bc86f..06888e445cda 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -25,6 +25,8 @@ try {
- Fixed issue where passing `children` in the Entity constructor options will override children. [#11101](https://github.com/CesiumGS/cesium/issues/11101)
- Fixed error type to be `RequestErrorEvent` in `Resource.retryCallback`. [#11177](https://github.com/CesiumGS/cesium/pull/11177)
+- Fixed issue when render `OrthographicFrustum` geometry by `DebugCameraPrimitive`. [#11159](https://github.com/CesiumGS/cesium/issues/11159)
+- Fixed ion URL in `RequestScheduler` throttling overrides. [#11193](https://github.com/CesiumGS/cesium/pull/11193)
- Fixed `SingleTileImageryProvider` fetching image when `show` is `false` by allowing lazy-loading for `SingleTileImageryProvider` if `tileWidth` and `tileHeight` are provided to the constructor. [#9529](https://github.com/CesiumGS/cesium/issues/9529)
- Fixed various race conditions from async operations. [#10909](https://github.com/CesiumGS/cesium/issues/10909)
diff --git a/Documentation/CustomShaderGuide/README.md b/Documentation/CustomShaderGuide/README.md
index 714af9604308..bec25d80b127 100644
--- a/Documentation/CustomShaderGuide/README.md
+++ b/Documentation/CustomShaderGuide/README.md
@@ -144,12 +144,12 @@ The user is responsible for assigning a value to this varying in
const customShader = new Cesium.CustomShader({
// Varying is declared here
varyings: {
- v_selectedColor: VaryingType.VEC3,
+ v_selectedColor: Cesium.VaryingType.VEC4,
},
// User assigns the varying in the vertex shader
vertexShaderText: `
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
- float positiveX = step(0.0, positionMC.x);
+ float positiveX = step(0.0, vsOutput.positionMC.x);
v_selectedColor = mix(
vsInput.attributes.color_0,
vsInput.attributes.color_1,
@@ -160,7 +160,7 @@ const customShader = new Cesium.CustomShader({
// User uses the varying in the fragment shader
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
- material.diffuse = v_selectedColor;
+ material.diffuse = v_selectedColor.rgb;
}
`,
});
@@ -838,7 +838,7 @@ For `ENUM` type metadata, the statistics struct for that property should contain
## `czm_modelVertexOutput` struct
-This struct is built-in, see the [documentation comment](../../../Shaders/Builtin/Structs/modelVertexOutput.glsl).
+This struct is built-in, see the [documentation comment](../../packages/engine/Source/Shaders/Builtin/Structs/modelVertexOutput.glsl).
This struct contains the output of the custom vertex shader. This includes:
@@ -856,7 +856,7 @@ This struct contains the output of the custom vertex shader. This includes:
## `czm_modelMaterial` struct
-This struct is a built-in, see the [documentation comment](../../Source/Shaders/Builtin/Structs/modelMaterial.glsl). This is similar to `czm_material` from the old Fabric system, but has slightly different fields as this one supports PBR lighting.
+This struct is a built-in, see the [documentation comment](../../packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl). This is similar to `czm_material` from the old Fabric system, but has slightly different fields as this one supports PBR lighting.
This struct serves as the basic input/output of the fragment shader pipeline stages. For example:
diff --git a/gulpfile.js b/gulpfile.js
index ef228912e4f9..51a36176f101 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -2230,7 +2230,8 @@ async function buildCesiumViewer() {
".png": "text",
};
config.format = "iife";
- config.inject = ["Apps/CesiumViewer/index.js"];
+ // Configure Cesium base path to use built
+ config.define = { CESIUM_BASE_URL: `"."` };
config.external = ["https", "http", "url", "zlib"];
config.outdir = cesiumViewerOutputDirectory;
config.outbase = "Apps/CesiumViewer";
@@ -2247,7 +2248,7 @@ async function buildCesiumViewer() {
".gif": "text",
".png": "text",
},
- outdir: cesiumViewerOutputDirectory,
+ outdir: join(cesiumViewerOutputDirectory, "Widgets"),
outbase: "packages/widgets/Source/",
});
diff --git a/package.json b/package.json
index f755bdb9a61f..5122b730fd44 100644
--- a/package.json
+++ b/package.json
@@ -97,14 +97,14 @@
"karma-jasmine": "^5.1.0",
"karma-longest-reporter": "^1.1.0",
"karma-safari-launcher": "^1.0.0",
- "karma-sourcemap-loader": "^0.3.8",
+ "karma-sourcemap-loader": "^0.4.0",
"karma-spec-reporter": "^0.0.36",
"markdownlint-cli": "^0.33.0",
"merge-stream": "^2.0.0",
"mime": "^3.0.0",
"mkdirp": "^2.1.3",
"node-fetch": "^3.2.10",
- "open": "^8.2.1",
+ "open": "^9.1.0",
"p-limit": "^4.0.0",
"prettier": "2.1.2",
"prismjs": "^1.28.0",
@@ -114,7 +114,7 @@
"rollup-plugin-strip-pragma": "^1.0.0",
"stream-to-promise": "^3.0.0",
"tsd-jsdoc": "^2.5.0",
- "typescript": "^4.9.4",
+ "typescript": "^5.0.2",
"yargs": "^17.0.1"
},
"scripts": {
@@ -167,4 +167,4 @@
"packages/engine",
"packages/widgets"
]
-}
\ No newline at end of file
+}
diff --git a/packages/engine/Source/Core/FrustumGeometry.js b/packages/engine/Source/Core/FrustumGeometry.js
index bece354b733c..0446378d3261 100644
--- a/packages/engine/Source/Core/FrustumGeometry.js
+++ b/packages/engine/Source/Core/FrustumGeometry.js
@@ -295,8 +295,8 @@ FrustumGeometry._computeNearFarPlanes = function (
let inverseView;
let inverseViewProjection;
+ const projection = frustum.projectionMatrix;
if (frustumType === PERSPECTIVE) {
- const projection = frustum.projectionMatrix;
const viewProjection = Matrix4.multiply(
projection,
view,
diff --git a/packages/engine/Source/Core/RequestScheduler.js b/packages/engine/Source/Core/RequestScheduler.js
index a0e4b63c8e4e..43542ad80cc1 100644
--- a/packages/engine/Source/Core/RequestScheduler.js
+++ b/packages/engine/Source/Core/RequestScheduler.js
@@ -76,7 +76,7 @@ RequestScheduler.maximumRequestsPerServer = 6;
*/
RequestScheduler.requestsByServer = {
"api.cesium.com:443": 18,
- "assets.cesium.com:443": 18,
+ "assets.ion.cesium.com:443": 18,
};
/**
diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js
index eb056f4d7062..713472c0ee47 100644
--- a/packages/engine/Source/Scene/Cesium3DTile.js
+++ b/packages/engine/Source/Scene/Cesium3DTile.js
@@ -576,6 +576,22 @@ Object.defineProperties(Cesium3DTile.prototype, {
},
},
+ /**
+ * Determines if the tile is visible within the current field of view
+ *
+ * @memberof Cesium3DTile.prototype
+ *
+ * @type {boolean}
+ * @readonly
+ *
+ * @private
+ */
+ isVisible: {
+ get: function () {
+ return this._visible && this._inRequestVolume;
+ },
+ },
+
/**
* Returns the extras
property in the tileset JSON for this tile, which contains application specific metadata.
* Returns undefined
if extras
does not exist.
@@ -616,6 +632,27 @@ Object.defineProperties(Cesium3DTile.prototype, {
},
},
+ /**
+ * Determines if the tile's content is renderable. false
if the
+ * tile has empty content or if it points to an external tileset or implicit content
+ *
+ * @memberof Cesium3DTile.prototype
+ *
+ * @type {boolean}
+ * @readonly
+ *
+ * @private
+ */
+ hasRenderableContent: {
+ get: function () {
+ return (
+ !this.hasEmptyContent &&
+ !this.hasTilesetContent &&
+ !this.hasImplicitContent
+ );
+ },
+ },
+
/**
* Determines if the tile has available content to render. true
if the tile's
* content is ready or if it has expired content that renders while new content loads; otherwise,
@@ -631,10 +668,7 @@ Object.defineProperties(Cesium3DTile.prototype, {
contentAvailable: {
get: function () {
return (
- (this.contentReady &&
- !this.hasEmptyContent &&
- !this.hasTilesetContent &&
- !this.hasImplicitContent) ||
+ (this.contentReady && this.hasRenderableContent) ||
(defined(this._expiredContent) && !this.contentFailed)
);
},
@@ -674,6 +708,22 @@ Object.defineProperties(Cesium3DTile.prototype, {
},
},
+ /**
+ * Determines if the tile has renderable content which is unloaded
+ *
+ * @memberof Cesium3DTile.prototype
+ *
+ * @type {boolean}
+ * @readonly
+ *
+ * @private
+ */
+ hasUnloadedRenderableContent: {
+ get: function () {
+ return this.hasRenderableContent && this.contentUnloaded;
+ },
+ },
+
/**
* Determines if the tile's content is expired. true
if tile's
* content is expired; otherwise, false
.
@@ -791,7 +841,7 @@ function isPriorityDeferred(tile, frameState) {
// Skip this feature if: non-skipLevelOfDetail and replace refine, if the foveated settings are turned off, if tile is progressive resolution and replace refine and skipLevelOfDetail (will help get rid of ancestor artifacts faster)
// Or if the tile is a preload of any kind
const replace = tile.refine === Cesium3DTileRefine.REPLACE;
- const skipLevelOfDetail = tileset._skipLevelOfDetail;
+ const skipLevelOfDetail = tileset.isSkippingLevelOfDetail;
if (
(replace && !skipLevelOfDetail) ||
!tileset.foveatedScreenSpaceError ||
@@ -935,7 +985,7 @@ function getPriorityReverseScreenSpaceError(tileset, tile) {
const parent = tile.parent;
const useParentScreenSpaceError =
defined(parent) &&
- (!tileset._skipLevelOfDetail ||
+ (!tileset.isSkippingLevelOfDetail ||
tile._screenSpaceError === 0.0 ||
parent.hasTilesetContent ||
parent.hasImplicitContent);
@@ -953,6 +1003,11 @@ function getPriorityReverseScreenSpaceError(tileset, tile) {
*/
Cesium3DTile.prototype.updateVisibility = function (frameState) {
const { parent, tileset } = this;
+ if (this._updatedVisibilityFrame === tileset._updatedVisibilityFrame) {
+ // The tile has already been updated for this frame
+ return;
+ }
+
const parentTransform = defined(parent)
? parent.computedTransform
: tileset.modelMatrix;
@@ -983,6 +1038,8 @@ Cesium3DTile.prototype.updateVisibility = function (frameState) {
this
);
this.priorityDeferred = isPriorityDeferred(this, frameState);
+
+ this._updatedVisibilityFrame = tileset._updatedVisibilityFrame;
};
/**
@@ -1342,11 +1399,7 @@ Cesium3DTile.prototype.cancelRequests = function () {
* @private
*/
Cesium3DTile.prototype.unloadContent = function () {
- if (
- this.hasEmptyContent ||
- this.hasTilesetContent ||
- this.hasImplicitContent
- ) {
+ if (!this.hasRenderableContent) {
return;
}
@@ -1720,19 +1773,15 @@ Cesium3DTile.prototype.createBoundingVolume = function (
);
}
- if (defined(boundingVolumeHeader.box)) {
- return createBox(boundingVolumeHeader.box, transform, result);
+ const { box, region, sphere } = boundingVolumeHeader;
+ if (defined(box)) {
+ return createBox(box, transform, result);
}
- if (defined(boundingVolumeHeader.region)) {
- return createRegion(
- boundingVolumeHeader.region,
- transform,
- this._initialTransform,
- result
- );
+ if (defined(region)) {
+ return createRegion(region, transform, this._initialTransform, result);
}
- if (defined(boundingVolumeHeader.sphere)) {
- return createSphere(boundingVolumeHeader.sphere, transform, result);
+ if (defined(sphere)) {
+ return createSphere(sphere, transform, result);
}
throw new RuntimeError(
"boundingVolume must contain a sphere, region, or box"
@@ -1824,8 +1873,6 @@ function applyDebugSettings(tile, tileset, frameState, passOptions) {
const hasContentBoundingVolume =
defined(tile._contentHeader) && defined(tile._contentHeader.boundingVolume);
- const empty =
- tile.hasEmptyContent || tile.hasTilesetContent || tile.hasImplicitContent;
const showVolume =
tileset.debugShowBoundingVolume ||
@@ -1834,7 +1881,7 @@ function applyDebugSettings(tile, tileset, frameState, passOptions) {
let color;
if (!tile._finalResolution) {
color = Color.YELLOW;
- } else if (empty) {
+ } else if (!tile.hasRenderableContent) {
color = Color.DARKGRAY;
} else {
color = Color.WHITE;
@@ -1981,18 +2028,18 @@ function updateClippingPlanes(tile, tileset) {
* @param {object} passOptions
*/
Cesium3DTile.prototype.update = function (tileset, frameState, passOptions) {
- const commandStart = frameState.commandList.length;
+ const { commandList } = frameState;
+ const commandStart = commandList.length;
updateClippingPlanes(this, tileset);
applyDebugSettings(this, tileset, frameState, passOptions);
updateContent(this, tileset, frameState);
- const commandEnd = frameState.commandList.length;
- const commandsLength = commandEnd - commandStart;
- this._commandsLength = commandsLength;
+ const commandEnd = commandList.length;
+ this._commandsLength = commandEnd - commandStart;
- for (let i = 0; i < commandsLength; ++i) {
- const command = frameState.commandList[commandStart + i];
+ for (let i = commandStart; i < commandEnd; ++i) {
+ const command = commandList[i];
const translucent = command.pass === Pass.TRANSLUCENT;
command.depthForTranslucentClassification = translucent;
}
@@ -2132,7 +2179,8 @@ Cesium3DTile.prototype.updatePriority = function () {
// Map 0-1 then convert to digit. Include a distance sort when doing non-skipLOD and replacement refinement, helps things like non-skipLOD photogrammetry
const useDistance =
- !tileset._skipLevelOfDetail && this.refine === Cesium3DTileRefine.REPLACE;
+ !tileset.isSkippingLevelOfDetail &&
+ this.refine === Cesium3DTileRefine.REPLACE;
const normalizedPreferredSorting = useDistance
? priorityNormalizeAndClamp(
this._priorityHolder._distanceToCamera,
diff --git a/packages/engine/Source/Scene/Cesium3DTileBatchTable.js b/packages/engine/Source/Scene/Cesium3DTileBatchTable.js
index a892b0f94945..6631d6c19622 100644
--- a/packages/engine/Source/Scene/Cesium3DTileBatchTable.js
+++ b/packages/engine/Source/Scene/Cesium3DTileBatchTable.js
@@ -896,8 +896,8 @@ Cesium3DTileBatchTable.prototype.addDerivedCommands = function (
const finalResolution = tile._finalResolution;
const tileset = tile.tileset;
const bivariateVisibilityTest =
- tileset._skipLevelOfDetail &&
- tileset._hasMixedContent &&
+ tileset.isSkippingLevelOfDetail &&
+ tileset.hasMixedContent &&
frameState.context.stencilBuffer;
const styleCommandsNeeded = getStyleCommandsNeeded(this);
diff --git a/packages/engine/Source/Scene/Cesium3DTilePass.js b/packages/engine/Source/Scene/Cesium3DTilePass.js
index c210f917734f..4d3017d30556 100644
--- a/packages/engine/Source/Scene/Cesium3DTilePass.js
+++ b/packages/engine/Source/Scene/Cesium3DTilePass.js
@@ -1,6 +1,3 @@
-import Cesium3DTilesetMostDetailedTraversal from "./Cesium3DTilesetMostDetailedTraversal.js";
-import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";
-
/**
* The pass in which a 3D Tileset is updated.
*
@@ -21,56 +18,56 @@ const Cesium3DTilePass = {
const passOptions = new Array(Cesium3DTilePass.NUMBER_OF_PASSES);
passOptions[Cesium3DTilePass.RENDER] = Object.freeze({
- traversal: Cesium3DTilesetTraversal,
+ pass: Cesium3DTilePass.RENDER,
isRender: true,
requestTiles: true,
ignoreCommands: false,
});
passOptions[Cesium3DTilePass.PICK] = Object.freeze({
- traversal: Cesium3DTilesetTraversal,
+ pass: Cesium3DTilePass.PICK,
isRender: false,
requestTiles: false,
ignoreCommands: false,
});
passOptions[Cesium3DTilePass.SHADOW] = Object.freeze({
- traversal: Cesium3DTilesetTraversal,
+ pass: Cesium3DTilePass.SHADOW,
isRender: false,
requestTiles: true,
ignoreCommands: false,
});
passOptions[Cesium3DTilePass.PRELOAD] = Object.freeze({
- traversal: Cesium3DTilesetTraversal,
+ pass: Cesium3DTilePass.SHADOW,
isRender: false,
requestTiles: true,
ignoreCommands: true,
});
passOptions[Cesium3DTilePass.PRELOAD_FLIGHT] = Object.freeze({
- traversal: Cesium3DTilesetTraversal,
+ pass: Cesium3DTilePass.PRELOAD_FLIGHT,
isRender: false,
requestTiles: true,
ignoreCommands: true,
});
passOptions[Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK] = Object.freeze({
- traversal: Cesium3DTilesetTraversal,
+ pass: Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK,
isRender: false,
requestTiles: true,
ignoreCommands: true,
});
passOptions[Cesium3DTilePass.MOST_DETAILED_PRELOAD] = Object.freeze({
- traversal: Cesium3DTilesetMostDetailedTraversal,
+ pass: Cesium3DTilePass.MOST_DETAILED_PRELOAD,
isRender: false,
requestTiles: true,
ignoreCommands: true,
});
passOptions[Cesium3DTilePass.MOST_DETAILED_PICK] = Object.freeze({
- traversal: Cesium3DTilesetMostDetailedTraversal,
+ pass: Cesium3DTilePass.MOST_DETAILED_PICK,
isRender: false,
requestTiles: false,
ignoreCommands: false,
diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js
index 7ddd6a3da9a2..b36f2c91a06a 100644
--- a/packages/engine/Source/Scene/Cesium3DTileset.js
+++ b/packages/engine/Source/Scene/Cesium3DTileset.js
@@ -52,6 +52,9 @@ import StencilConstants from "./StencilConstants.js";
import TileBoundingRegion from "./TileBoundingRegion.js";
import TileBoundingSphere from "./TileBoundingSphere.js";
import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js";
+import Cesium3DTilesetMostDetailedTraversal from "./Cesium3DTilesetMostDetailedTraversal.js";
+import Cesium3DTilesetBaseTraversal from "./Cesium3DTilesetBaseTraversal.js";
+import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js";
/**
* @typedef {Object} Cesium3DTileset.ConstructorOptions
@@ -668,7 +671,7 @@ function Cesium3DTileset(options) {
* @default false
*/
this.skipLevelOfDetail = defaultValue(options.skipLevelOfDetail, false);
- this._skipLevelOfDetail = this.skipLevelOfDetail;
+
this._disableSkipLevelOfDetail = false;
/**
@@ -1374,6 +1377,50 @@ Object.defineProperties(Cesium3DTileset.prototype, {
},
},
+ /**
+ * Whether the tileset is rendering different levels of detail in the same view.
+ * Only relevant if {@link Cesium3DTileset.isSkippingLevelOfDetail} is true.
+ *
+ * @memberof Cesium3DTileset.prototype
+ *
+ * @type {boolean}
+ * @private
+ */
+ hasMixedContent: {
+ get: function () {
+ return this._hasMixedContent;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("value", value);
+ //>>includeEnd('debug');
+
+ this._hasMixedContent = value;
+ },
+ },
+
+ /**
+ * Whether this tileset is actually skipping levels of detail.
+ * The user option may have been disabled if all tiles are using additive refinement,
+ * or if some tiles have a content type for which rendering does not support skipping
+ *
+ * @memberof Cesium3DTileset.prototype
+ *
+ * @type {boolean}
+ * @private
+ * @readonly
+ */
+ isSkippingLevelOfDetail: {
+ get: function () {
+ return (
+ this.skipLevelOfDetail &&
+ !defined(this._classificationType) &&
+ !this._disableSkipLevelOfDetail &&
+ !this._allTilesAdditive
+ );
+ },
+ },
+
/**
* The tileset's schema, groups, tileset metadata and other details from the
* 3DTILES_metadata extension or a 3D Tiles 1.1 tileset JSON. This getter is
@@ -2531,12 +2578,6 @@ Cesium3DTileset.prototype.prePassesUpdate = function (frameState) {
0.0
);
- this._skipLevelOfDetail =
- this.skipLevelOfDetail &&
- !defined(this._classificationType) &&
- !this._disableSkipLevelOfDetail &&
- !this._allTilesAdditive;
-
if (this.dynamicScreenSpaceError) {
updateDynamicScreenSpaceError(this, frameState);
}
@@ -2836,18 +2877,15 @@ function updateTiles(tileset, frameState, passOptions) {
tileset._styleEngine.applyStyle(tileset);
tileset._styleApplied = true;
- const isRender = passOptions.isRender;
- const { statistics, tileVisible } = tileset;
- const commandList = frameState.commandList;
+ const { commandList, context } = frameState;
const numberOfInitialCommands = commandList.length;
const selectedTiles = tileset._selectedTiles;
- const selectedLength = selectedTiles.length;
const bivariateVisibilityTest =
- tileset._skipLevelOfDetail &&
+ tileset.isSkippingLevelOfDetail &&
tileset._hasMixedContent &&
- frameState.context.stencilBuffer &&
- selectedLength > 0;
+ context.stencilBuffer &&
+ selectedTiles.length > 0;
tileset._backfaceCommands.length = 0;
@@ -2864,8 +2902,11 @@ function updateTiles(tileset, frameState, passOptions) {
commandList.push(tileset._stencilClearCommand);
}
+ const { statistics, tileVisible } = tileset;
+ const isRender = passOptions.isRender;
const lengthBeforeUpdate = commandList.length;
- for (let i = 0; i < selectedLength; ++i) {
+
+ for (let i = 0; i < selectedTiles.length; ++i) {
const tile = selectedTiles[i];
// Raise the tileVisible event before update in case the tileVisible event
// handler makes changes that update needs to apply to WebGL resources
@@ -3142,8 +3183,6 @@ function update(tileset, frameState, passStatistics, passOptions) {
const statistics = tileset._statistics;
statistics.clear();
- const isRender = passOptions.isRender;
-
// Resets the visibility check for each pass
++tileset._updatedVisibilityFrame;
@@ -3154,7 +3193,9 @@ function update(tileset, frameState, passStatistics, passOptions) {
tileset._cullRequestsWhileMoving =
tileset.cullRequestsWhileMoving && !tileset._modelMatrixChanged;
- const ready = passOptions.traversal.selectTiles(tileset, frameState);
+ const ready = tileset
+ .getTraversal(passOptions)
+ .selectTiles(tileset, frameState);
if (passOptions.requestTiles) {
requestTiles(tileset);
@@ -3165,7 +3206,7 @@ function update(tileset, frameState, passStatistics, passOptions) {
// Update pass statistics
Cesium3DTilesetStatistics.clone(statistics, passStatistics);
- if (isRender) {
+ if (passOptions.isRender) {
const credits = tileset._credits;
if (defined(credits) && statistics.selected !== 0) {
for (let i = 0; i < credits.length; ++i) {
@@ -3179,6 +3220,24 @@ function update(tileset, frameState, passStatistics, passOptions) {
return ready;
}
+/**
+ * @private
+ * @param {object} passOptions
+ * @returns {Cesium3DTilesetTraversal}
+ */
+Cesium3DTileset.prototype.getTraversal = function (passOptions) {
+ const { pass } = passOptions;
+ if (
+ pass === Cesium3DTilePass.MOST_DETAILED_PRELOAD ||
+ pass === Cesium3DTilePass.MOST_DETAILED_PICK
+ ) {
+ return Cesium3DTilesetMostDetailedTraversal;
+ }
+ return this.isSkippingLevelOfDetail
+ ? Cesium3DTilesetSkipTraversal
+ : Cesium3DTilesetBaseTraversal;
+};
+
/**
* @private
* @param {FrameState} frameState
diff --git a/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js
new file mode 100644
index 000000000000..a85ddcc4013c
--- /dev/null
+++ b/packages/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js
@@ -0,0 +1,301 @@
+import defined from "../Core/defined.js";
+import ManagedArray from "../Core/ManagedArray.js";
+import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
+import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";
+
+/**
+ * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
+ * A tile does not refine until all children are loaded.
+ * This is the traditional replacement refinement approach and is called the base traversal.
+ *
+ * @alias Cesium3DTilesetBaseTraversal
+ * @constructor
+ *
+ * @private
+ */
+function Cesium3DTilesetBaseTraversal() {}
+
+const traversal = {
+ stack: new ManagedArray(),
+ stackMaximumLength: 0,
+};
+
+const emptyTraversal = {
+ stack: new ManagedArray(),
+ stackMaximumLength: 0,
+};
+
+/**
+ * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
+ *
+ * @private
+ * @param {Cesium3DTileset} tileset
+ * @param {FrameState} frameState
+ */
+Cesium3DTilesetBaseTraversal.selectTiles = function (tileset, frameState) {
+ tileset._requestedTiles.length = 0;
+
+ if (tileset.debugFreezeFrame) {
+ return;
+ }
+
+ tileset._selectedTiles.length = 0;
+ tileset._selectedTilesToStyle.length = 0;
+ tileset._emptyTiles.length = 0;
+ tileset.hasMixedContent = false;
+
+ const root = tileset.root;
+ Cesium3DTilesetTraversal.updateTile(root, frameState);
+
+ if (!root.isVisible) {
+ return;
+ }
+
+ if (
+ root.getScreenSpaceError(frameState, true) <=
+ tileset._maximumScreenSpaceError
+ ) {
+ return;
+ }
+
+ executeTraversal(root, frameState);
+
+ traversal.stack.trim(traversal.stackMaximumLength);
+ emptyTraversal.stack.trim(emptyTraversal.stackMaximumLength);
+
+ // Update the priority for any requests found during traversal
+ // Update after traversal so that min and max values can be used to normalize priority values
+ const requestedTiles = tileset._requestedTiles;
+ for (let i = 0; i < requestedTiles.length; ++i) {
+ requestedTiles[i].updatePriority();
+ }
+};
+
+/**
+ * Mark a tile as selected if it has content available.
+ *
+ * @private
+ * @param {Cesium3DTile} tile
+ * @param {FrameState} frameState
+ */
+function selectDesiredTile(tile, frameState) {
+ if (tile.contentAvailable) {
+ Cesium3DTilesetTraversal.selectTile(tile, frameState);
+ }
+}
+
+/**
+ * @private
+ * @param {Cesium3DTile} tile
+ * @param {ManagedArray} stack
+ * @param {FrameState} frameState
+ * @returns {boolean}
+ */
+function updateAndPushChildren(tile, stack, frameState) {
+ const replace = tile.refine === Cesium3DTileRefine.REPLACE;
+ const { tileset, children } = tile;
+ const { updateTile, loadTile, touchTile } = Cesium3DTilesetTraversal;
+
+ for (let i = 0; i < children.length; ++i) {
+ updateTile(children[i], frameState);
+ }
+
+ // Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
+ children.sort(Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera);
+
+ // For traditional replacement refinement only refine if all children are loaded.
+ // Empty tiles are exempt since it looks better if children stream in as they are loaded to fill the empty space.
+ const checkRefines = replace && tile.hasRenderableContent;
+ let refines = true;
+
+ let anyChildrenVisible = false;
+
+ // Determining min child
+ let minIndex = -1;
+ let minimumPriority = Number.MAX_VALUE;
+
+ for (let i = 0; i < children.length; ++i) {
+ const child = children[i];
+ if (child.isVisible) {
+ stack.push(child);
+ if (child._foveatedFactor < minimumPriority) {
+ minIndex = i;
+ minimumPriority = child._foveatedFactor;
+ }
+ anyChildrenVisible = true;
+ } else if (checkRefines || tileset.loadSiblings) {
+ // Keep non-visible children loaded since they are still needed before the parent can refine.
+ // Or loadSiblings is true so always load tiles regardless of visibility.
+ if (child._foveatedFactor < minimumPriority) {
+ minIndex = i;
+ minimumPriority = child._foveatedFactor;
+ }
+ loadTile(child, frameState);
+ touchTile(child, frameState);
+ }
+ if (checkRefines) {
+ let childRefines;
+ if (!child._inRequestVolume) {
+ childRefines = false;
+ } else if (!child.hasRenderableContent) {
+ childRefines = executeEmptyTraversal(child, frameState);
+ } else {
+ childRefines = child.contentAvailable;
+ }
+ refines = refines && childRefines;
+ }
+ }
+
+ if (!anyChildrenVisible) {
+ refines = false;
+ }
+
+ if (minIndex !== -1 && replace) {
+ // An ancestor will hold the _foveatedFactor and _distanceToCamera for descendants between itself and its highest priority descendant. Siblings of a min children along the way use this ancestor as their priority holder as well.
+ // Priority of all tiles that refer to the _foveatedFactor and _distanceToCamera stored in the common ancestor will be differentiated based on their _depth.
+ const minPriorityChild = children[minIndex];
+ minPriorityChild._wasMinPriorityChild = true;
+ const priorityHolder =
+ (tile._wasMinPriorityChild || tile === tileset.root) &&
+ minimumPriority <= tile._priorityHolder._foveatedFactor
+ ? tile._priorityHolder
+ : tile; // This is where priority dependency chains are wired up or started anew.
+ priorityHolder._foveatedFactor = Math.min(
+ minPriorityChild._foveatedFactor,
+ priorityHolder._foveatedFactor
+ );
+ priorityHolder._distanceToCamera = Math.min(
+ minPriorityChild._distanceToCamera,
+ priorityHolder._distanceToCamera
+ );
+
+ for (let i = 0; i < children.length; ++i) {
+ children[i]._priorityHolder = priorityHolder;
+ }
+ }
+
+ return refines;
+}
+
+/**
+ * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
+ * A tile does not refine until all children are loaded.
+ * This is the traditional replacement refinement approach and is called the base traversal.
+ *
+ * @private
+ * @param {Cesium3DTile} root
+ * @param {FrameState} frameState
+ */
+function executeTraversal(root, frameState) {
+ const { tileset } = root;
+
+ const {
+ canTraverse,
+ loadTile,
+ visitTile,
+ touchTile,
+ } = Cesium3DTilesetTraversal;
+ const stack = traversal.stack;
+ stack.push(root);
+
+ while (stack.length > 0) {
+ traversal.stackMaximumLength = Math.max(
+ traversal.stackMaximumLength,
+ stack.length
+ );
+
+ const tile = stack.pop();
+
+ const parent = tile.parent;
+ const parentRefines = !defined(parent) || parent._refines;
+
+ tile._refines = canTraverse(tile)
+ ? updateAndPushChildren(tile, stack, frameState) && parentRefines
+ : false;
+
+ const stoppedRefining = !tile._refines && parentRefines;
+
+ if (!tile.hasRenderableContent) {
+ // Add empty tile just to show its debug bounding volume
+ // If the tile has tileset content load the external tileset
+ tileset._emptyTiles.push(tile);
+ loadTile(tile, frameState);
+ if (stoppedRefining) {
+ selectDesiredTile(tile, frameState);
+ }
+ } else if (tile.refine === Cesium3DTileRefine.ADD) {
+ // Additive tiles are always loaded and selected
+ selectDesiredTile(tile, frameState);
+ loadTile(tile, frameState);
+ } else if (tile.refine === Cesium3DTileRefine.REPLACE) {
+ loadTile(tile, frameState);
+ if (stoppedRefining) {
+ selectDesiredTile(tile, frameState);
+ }
+ }
+
+ visitTile(tile, frameState);
+ touchTile(tile, frameState);
+ }
+}
+
+/**
+ * Depth-first traversal that checks if all nearest descendants with content are loaded.
+ * Ignores visibility.
+ *
+ * @private
+ * @param {Cesium3DTile} root
+ * @param {FrameState} frameState
+ * @returns {boolean}
+ */
+function executeEmptyTraversal(root, frameState) {
+ const {
+ canTraverse,
+ updateTile,
+ loadTile,
+ touchTile,
+ } = Cesium3DTilesetTraversal;
+ let allDescendantsLoaded = true;
+ const stack = emptyTraversal.stack;
+ stack.push(root);
+
+ while (stack.length > 0) {
+ emptyTraversal.stackMaximumLength = Math.max(
+ emptyTraversal.stackMaximumLength,
+ stack.length
+ );
+
+ const tile = stack.pop();
+ const children = tile.children;
+ const childrenLength = children.length;
+
+ // Only traverse if the tile is empty - traversal stops at descendants with content
+ const traverse = !tile.hasRenderableContent && canTraverse(tile);
+ const emptyLeaf = !tile.hasRenderableContent && tile.children.length === 0;
+
+ // Traversal stops but the tile does not have content yet
+ // There will be holes if the parent tries to refine to its children, so don't refine
+ // One exception: a parent may refine even if one of its descendants is an empty leaf
+ if (!traverse && !tile.contentAvailable && !emptyLeaf) {
+ allDescendantsLoaded = false;
+ }
+
+ updateTile(tile, frameState);
+ if (!tile.isVisible) {
+ // Load tiles that aren't visible since they are still needed for the parent to refine
+ loadTile(tile, frameState);
+ touchTile(tile, frameState);
+ }
+
+ if (traverse) {
+ for (let i = 0; i < childrenLength; ++i) {
+ const child = children[i];
+ stack.push(child);
+ }
+ }
+ }
+
+ return allDescendantsLoaded;
+}
+
+export default Cesium3DTilesetBaseTraversal;
diff --git a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js
index 7fe1e10ba204..8645642a852f 100644
--- a/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js
+++ b/packages/engine/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js
@@ -1,11 +1,15 @@
import Intersect from "../Core/Intersect.js";
import ManagedArray from "../Core/ManagedArray.js";
import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
+import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";
/**
* Traversal that loads all leaves that intersect the camera frustum.
* Used to determine ray-tileset intersections during a pickFromRayMostDetailed call.
*
+ * @alias Cesium3DTilesetMostDetailedTraversal
+ * @constructor
+ *
* @private
*/
function Cesium3DTilesetMostDetailedTraversal() {}
@@ -15,25 +19,35 @@ const traversal = {
stackMaximumLength: 0,
};
+/**
+ * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
+ *
+ * @private
+ * @param {Cesium3DTileset} tileset
+ * @param {FrameState} frameState
+ * @returns {boolean} Whether the appropriate tile is ready for picking
+ */
Cesium3DTilesetMostDetailedTraversal.selectTiles = function (
tileset,
frameState
) {
tileset._selectedTiles.length = 0;
tileset._requestedTiles.length = 0;
- tileset._hasMixedContent = false;
+ tileset.hasMixedContent = false;
let ready = true;
const root = tileset.root;
root.updateVisibility(frameState);
- if (!isVisible(root)) {
+ if (!root.isVisible) {
return ready;
}
+ const { touchTile, visitTile } = Cesium3DTilesetTraversal;
+
const stack = traversal.stack;
- stack.push(tileset.root);
+ stack.push(root);
while (stack.length > 0) {
traversal.stackMaximumLength = Math.max(
@@ -44,23 +58,23 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function (
const tile = stack.pop();
const add = tile.refine === Cesium3DTileRefine.ADD;
const replace = tile.refine === Cesium3DTileRefine.REPLACE;
- const traverse = canTraverse(tileset, tile);
+ const traverse = canTraverse(tile);
if (traverse) {
- updateAndPushChildren(tileset, tile, stack, frameState);
+ updateAndPushChildren(tile, stack, frameState);
}
if (add || (replace && !traverse)) {
loadTile(tileset, tile);
- touchTile(tileset, tile, frameState);
- selectDesiredTile(tileset, tile, frameState);
+ touchTile(tile, frameState);
+ selectDesiredTile(tile, frameState);
- if (!hasEmptyContent(tile) && !tile.contentAvailable) {
+ if (tile.hasRenderableContent && !tile.contentAvailable) {
ready = false;
}
}
- visitTile(tileset);
+ visitTile(tile, frameState);
}
traversal.stack.trim(traversal.stackMaximumLength);
@@ -68,21 +82,7 @@ Cesium3DTilesetMostDetailedTraversal.selectTiles = function (
return ready;
};
-function isVisible(tile) {
- return tile._visible && tile._inRequestVolume;
-}
-
-function hasEmptyContent(tile) {
- return (
- tile.hasEmptyContent || tile.hasTilesetContent || tile.hasImplicitContent
- );
-}
-
-function hasUnloadedContent(tile) {
- return !hasEmptyContent(tile) && tile.contentUnloaded;
-}
-
-function canTraverse(tileset, tile) {
+function canTraverse(tile) {
if (tile.children.length === 0) {
return false;
}
@@ -97,48 +97,35 @@ function canTraverse(tileset, tile) {
return true;
}
- return true; // Keep traversing until a leave is hit
+ return true; // Keep traversing until a leaf is hit
}
-function updateAndPushChildren(tileset, tile, stack, frameState) {
- const children = tile.children;
- const length = children.length;
+function updateAndPushChildren(tile, stack, frameState) {
+ const { children } = tile;
- for (let i = 0; i < length; ++i) {
+ for (let i = 0; i < children.length; ++i) {
const child = children[i];
child.updateVisibility(frameState);
- if (isVisible(child)) {
+ if (child.isVisible) {
stack.push(child);
}
}
}
function loadTile(tileset, tile) {
- if (hasUnloadedContent(tile) || tile.contentExpired) {
+ if (tile.hasUnloadedRenderableContent || tile.contentExpired) {
tile._priority = 0.0; // Highest priority
tileset._requestedTiles.push(tile);
}
}
-function touchTile(tileset, tile, frameState) {
- if (tile._touchedFrame === frameState.frameNumber) {
- // Prevents another pass from touching the frame again
- return;
- }
- tileset._cache.touch(tile);
- tile._touchedFrame = frameState.frameNumber;
-}
-
-function visitTile(tileset) {
- ++tileset.statistics.visited;
-}
-
-function selectDesiredTile(tileset, tile, frameState) {
+function selectDesiredTile(tile, frameState) {
if (
tile.contentAvailable &&
tile.contentVisibility(frameState) !== Intersect.OUTSIDE
) {
- tileset._selectedTiles.push(tile);
+ tile.tileset._selectedTiles.push(tile);
}
}
+
export default Cesium3DTilesetMostDetailedTraversal;
diff --git a/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js
new file mode 100644
index 000000000000..aea34eee3fcb
--- /dev/null
+++ b/packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js
@@ -0,0 +1,421 @@
+import defined from "../Core/defined.js";
+import ManagedArray from "../Core/ManagedArray.js";
+import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
+import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";
+
+/**
+ * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
+ * Allows for skipping levels of the tree and rendering children and parent tiles simultaneously.
+ *
+ * @alias Cesium3DTilesetSkipTraversal
+ * @constructor
+ *
+ * @private
+ */
+function Cesium3DTilesetSkipTraversal() {}
+
+const traversal = {
+ stack: new ManagedArray(),
+ stackMaximumLength: 0,
+};
+
+const descendantTraversal = {
+ stack: new ManagedArray(),
+ stackMaximumLength: 0,
+};
+
+const selectionTraversal = {
+ stack: new ManagedArray(),
+ stackMaximumLength: 0,
+ ancestorStack: new ManagedArray(),
+ ancestorStackMaximumLength: 0,
+};
+
+const descendantSelectionDepth = 2;
+
+/**
+ * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
+ *
+ * @private
+ * @param {Cesium3DTileset} tileset
+ * @param {FrameState} frameState
+ */
+Cesium3DTilesetSkipTraversal.selectTiles = function (tileset, frameState) {
+ tileset._requestedTiles.length = 0;
+
+ if (tileset.debugFreezeFrame) {
+ return;
+ }
+
+ tileset._selectedTiles.length = 0;
+ tileset._selectedTilesToStyle.length = 0;
+ tileset._emptyTiles.length = 0;
+ tileset.hasMixedContent = false;
+
+ const root = tileset.root;
+ Cesium3DTilesetTraversal.updateTile(root, frameState);
+
+ if (!root.isVisible) {
+ return;
+ }
+
+ if (
+ root.getScreenSpaceError(frameState, true) <=
+ tileset._maximumScreenSpaceError
+ ) {
+ return;
+ }
+
+ executeTraversal(root, frameState);
+ traverseAndSelect(root, frameState);
+
+ traversal.stack.trim(traversal.stackMaximumLength);
+ descendantTraversal.stack.trim(descendantTraversal.stackMaximumLength);
+ selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength);
+ selectionTraversal.ancestorStack.trim(
+ selectionTraversal.ancestorStackMaximumLength
+ );
+
+ // Update the priority for any requests found during traversal
+ // Update after traversal so that min and max values can be used to normalize priority values
+ const requestedTiles = tileset._requestedTiles;
+ for (let i = 0; i < requestedTiles.length; ++i) {
+ requestedTiles[i].updatePriority();
+ }
+};
+
+/**
+ * Mark descendant tiles for rendering, and update as needed
+ *
+ * @private
+ * @param {Cesium3DTile} root
+ * @param {FrameState} frameState
+ */
+function selectDescendants(root, frameState) {
+ const { updateTile, touchTile, selectTile } = Cesium3DTilesetTraversal;
+ const stack = descendantTraversal.stack;
+ stack.push(root);
+ while (stack.length > 0) {
+ descendantTraversal.stackMaximumLength = Math.max(
+ descendantTraversal.stackMaximumLength,
+ stack.length
+ );
+ const tile = stack.pop();
+ const children = tile.children;
+ for (let i = 0; i < children.length; ++i) {
+ const child = children[i];
+ if (child.isVisible) {
+ if (child.contentAvailable) {
+ updateTile(child, frameState);
+ touchTile(child, frameState);
+ selectTile(child, frameState);
+ } else if (child._depth - root._depth < descendantSelectionDepth) {
+ // Continue traversing, but not too far
+ stack.push(child);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Mark a tile as selected if it has content available.
+ * If its content is not available, and we are skipping levels of detail,
+ * select an ancestor or descendant tile instead
+ *
+ * @private
+ * @param {Cesium3DTile} tile
+ * @param {FrameState} frameState
+ */
+function selectDesiredTile(tile, frameState) {
+ // If this tile is not loaded attempt to select its ancestor instead
+ const loadedTile = tile.contentAvailable
+ ? tile
+ : tile._ancestorWithContentAvailable;
+ if (defined(loadedTile)) {
+ // Tiles will actually be selected in traverseAndSelect
+ loadedTile._shouldSelect = true;
+ } else {
+ // If no ancestors are ready traverse down and select tiles to minimize empty regions.
+ // This happens often for immediatelyLoadDesiredLevelOfDetail where parent tiles are not necessarily loaded before zooming out.
+ selectDescendants(tile, frameState);
+ }
+}
+
+/**
+ * Update links to the ancestor tiles that have content
+ *
+ * @private
+ * @param {Cesium3DTile} tile
+ * @param {FrameState} frameState
+ */
+function updateTileAncestorContentLinks(tile, frameState) {
+ tile._ancestorWithContent = undefined;
+ tile._ancestorWithContentAvailable = undefined;
+
+ const { parent } = tile;
+ if (!defined(parent)) {
+ return;
+ }
+ const parentHasContent =
+ !parent.hasUnloadedRenderableContent ||
+ parent._requestedFrame === frameState.frameNumber;
+
+ // ancestorWithContent is an ancestor that has content or has the potential to have
+ // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
+ tile._ancestorWithContent = parentHasContent
+ ? parent
+ : parent._ancestorWithContent;
+
+ // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded
+ tile._ancestorWithContentAvailable = parent.contentAvailable
+ ? parent
+ : parent._ancestorWithContentAvailable;
+}
+
+/**
+ * Determine if a tile has reached the limit of level of detail skipping.
+ * If so, it should _not_ be skipped: it should be loaded and rendered
+ *
+ * @private
+ * @param {Cesium3DTileset} tileset
+ * @param {Cesium3DTile} tile
+ * @returns {boolean} true if this tile should not be skipped
+ */
+function reachedSkippingThreshold(tileset, tile) {
+ const ancestor = tile._ancestorWithContent;
+ return (
+ !tileset.immediatelyLoadDesiredLevelOfDetail &&
+ (tile._priorityProgressiveResolutionScreenSpaceErrorLeaf ||
+ (defined(ancestor) &&
+ tile._screenSpaceError <
+ ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor &&
+ tile._depth > ancestor._depth + tileset.skipLevels))
+ );
+}
+
+/**
+ * @private
+ * @param {Cesium3DTile} tile
+ * @param {ManagedArray} stack
+ * @param {FrameState} frameState
+ * @returns {boolean}
+ */
+function updateAndPushChildren(tile, stack, frameState) {
+ const { tileset, children } = tile;
+ const { updateTile, loadTile, touchTile } = Cesium3DTilesetTraversal;
+
+ for (let i = 0; i < children.length; ++i) {
+ updateTile(children[i], frameState);
+ }
+
+ // Sort by distance to take advantage of early Z and reduce artifacts
+ children.sort(Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera);
+
+ let anyChildrenVisible = false;
+
+ for (let i = 0; i < children.length; ++i) {
+ const child = children[i];
+ if (child.isVisible) {
+ stack.push(child);
+ anyChildrenVisible = true;
+ } else if (tileset.loadSiblings) {
+ loadTile(child, frameState);
+ touchTile(child, frameState);
+ }
+ }
+
+ return anyChildrenVisible;
+}
+
+/**
+ * Determine if a tile is part of the base traversal.
+ * If not, this tile could be considered for level of detail skipping
+ *
+ * @private
+ * @param {Cesium3DTile} tile
+ * @param {number} baseScreenSpaceError
+ * @returns {boolean}
+ */
+function inBaseTraversal(tile, baseScreenSpaceError) {
+ const { tileset } = tile;
+ if (tileset.immediatelyLoadDesiredLevelOfDetail) {
+ return false;
+ }
+ if (!defined(tile._ancestorWithContent)) {
+ // Include root or near-root tiles in the base traversal so there is something to select up to
+ return true;
+ }
+ if (tile._screenSpaceError === 0.0) {
+ // If a leaf, use parent's SSE
+ return tile.parent._screenSpaceError > baseScreenSpaceError;
+ }
+ return tile._screenSpaceError > baseScreenSpaceError;
+}
+
+/**
+ * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
+ * Tiles that have a greater screen space error than the base screen space error are part of the base traversal,
+ * all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
+ * and rendering children and parent tiles simultaneously.
+ *
+ * @private
+ * @param {Cesium3DTile} root
+ * @param {FrameState} frameState
+ */
+function executeTraversal(root, frameState) {
+ const { tileset } = root;
+ const baseScreenSpaceError = tileset.immediatelyLoadDesiredLevelOfDetail
+ ? Number.MAX_VALUE
+ : Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError);
+ const {
+ canTraverse,
+ loadTile,
+ visitTile,
+ touchTile,
+ } = Cesium3DTilesetTraversal;
+ const stack = traversal.stack;
+ stack.push(root);
+
+ while (stack.length > 0) {
+ traversal.stackMaximumLength = Math.max(
+ traversal.stackMaximumLength,
+ stack.length
+ );
+
+ const tile = stack.pop();
+
+ updateTileAncestorContentLinks(tile, frameState);
+ const parent = tile.parent;
+ const parentRefines = !defined(parent) || parent._refines;
+
+ tile._refines = canTraverse(tile)
+ ? updateAndPushChildren(tile, stack, frameState) && parentRefines
+ : false;
+
+ const stoppedRefining = !tile._refines && parentRefines;
+
+ if (!tile.hasRenderableContent) {
+ // Add empty tile just to show its debug bounding volume
+ // If the tile has tileset content load the external tileset
+ // If the tile cannot refine further select its nearest loaded ancestor
+ tileset._emptyTiles.push(tile);
+ loadTile(tile, frameState);
+ if (stoppedRefining) {
+ selectDesiredTile(tile, frameState);
+ }
+ } else if (tile.refine === Cesium3DTileRefine.ADD) {
+ // Additive tiles are always loaded and selected
+ selectDesiredTile(tile, frameState);
+ loadTile(tile, frameState);
+ } else if (tile.refine === Cesium3DTileRefine.REPLACE) {
+ if (inBaseTraversal(tile, baseScreenSpaceError)) {
+ // Always load tiles in the base traversal
+ // Select tiles that can't refine further
+ loadTile(tile, frameState);
+ if (stoppedRefining) {
+ selectDesiredTile(tile, frameState);
+ }
+ } else if (stoppedRefining) {
+ // In skip traversal, load and select tiles that can't refine further
+ selectDesiredTile(tile, frameState);
+ loadTile(tile, frameState);
+ } else if (reachedSkippingThreshold(tileset, tile)) {
+ // In skip traversal, load tiles that aren't skipped
+ loadTile(tile, frameState);
+ }
+ }
+
+ visitTile(tile, frameState);
+ touchTile(tile, frameState);
+ }
+}
+
+/**
+ * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
+ * This is a preorder traversal so children tiles are selected before ancestor tiles.
+ *
+ * The reason for the preorder traversal is so that tiles can easily be marked with their
+ * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
+ * This property is important for use in the stencil test because we want to render deeper tiles on top of their
+ * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer.
+ *
+ * We want to select children before their ancestors because there is no guarantee on the relationship between
+ * the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top
+ * of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
+ *
+ * NOTE: 3D Tiles uses 3 bits from the stencil buffer meaning this will not work when there is a chain of
+ * selected tiles that is deeper than 7. This is not very likely.
+ *
+ * @private
+ * @param {Cesium3DTile} root
+ * @param {FrameState} frameState
+ */
+function traverseAndSelect(root, frameState) {
+ const { selectTile, canTraverse } = Cesium3DTilesetTraversal;
+ const { stack, ancestorStack } = selectionTraversal;
+ let lastAncestor;
+
+ stack.push(root);
+
+ while (stack.length > 0 || ancestorStack.length > 0) {
+ selectionTraversal.stackMaximumLength = Math.max(
+ selectionTraversal.stackMaximumLength,
+ stack.length
+ );
+ selectionTraversal.ancestorStackMaximumLength = Math.max(
+ selectionTraversal.ancestorStackMaximumLength,
+ ancestorStack.length
+ );
+
+ if (ancestorStack.length > 0) {
+ const waitingTile = ancestorStack.peek();
+ if (waitingTile._stackLength === stack.length) {
+ ancestorStack.pop();
+ if (waitingTile !== lastAncestor) {
+ waitingTile._finalResolution = false;
+ }
+ selectTile(waitingTile, frameState);
+ continue;
+ }
+ }
+
+ const tile = stack.pop();
+ if (!defined(tile)) {
+ // stack is empty but ancestorStack isn't
+ continue;
+ }
+
+ const traverse = canTraverse(tile);
+
+ if (tile._shouldSelect) {
+ if (tile.refine === Cesium3DTileRefine.ADD) {
+ selectTile(tile, frameState);
+ } else {
+ tile._selectionDepth = ancestorStack.length;
+ if (tile._selectionDepth > 0) {
+ tile.tileset.hasMixedContent = true;
+ }
+ lastAncestor = tile;
+ if (!traverse) {
+ selectTile(tile, frameState);
+ continue;
+ }
+ ancestorStack.push(tile);
+ tile._stackLength = stack.length;
+ }
+ }
+
+ if (traverse) {
+ const children = tile.children;
+ for (let i = 0; i < children.length; ++i) {
+ const child = children[i];
+ if (child.isVisible) {
+ stack.push(child);
+ }
+ }
+ }
+ }
+}
+
+export default Cesium3DTilesetSkipTraversal;
diff --git a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js
index 7da76155fed3..fc30a1eca829 100644
--- a/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/packages/engine/Source/Scene/Cesium3DTilesetTraversal.js
@@ -1,118 +1,71 @@
import defined from "../Core/defined.js";
+import DeveloperError from "../Core/DeveloperError.js";
import Intersect from "../Core/Intersect.js";
-import ManagedArray from "../Core/ManagedArray.js";
import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
/**
+ * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
+ * This type describes an interface and is not intended to be instantiated directly.
+ *
+ * @alias Cesium3DTilesetTraversal
+ * @constructor
+ * @abstract
+ *
+ * @see Cesium3DTilesetBaseTraversal
+ * @see Cesium3DTilesetSkipTraversal
+ * @see Cesium3DTilesetMostDetailedTraversal
+ *
* @private
*/
function Cesium3DTilesetTraversal() {}
-const traversal = {
- stack: new ManagedArray(),
- stackMaximumLength: 0,
-};
-
-const emptyTraversal = {
- stack: new ManagedArray(),
- stackMaximumLength: 0,
-};
-
-const descendantTraversal = {
- stack: new ManagedArray(),
- stackMaximumLength: 0,
-};
-
-const selectionTraversal = {
- stack: new ManagedArray(),
- stackMaximumLength: 0,
- ancestorStack: new ManagedArray(),
- ancestorStackMaximumLength: 0,
-};
-
-const descendantSelectionDepth = 2;
-
/**
+ * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
+ *
* @private
* @param {Cesium3DTileset} tileset
* @param {FrameState} frameState
*/
Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) {
- tileset._requestedTiles.length = 0;
-
- if (tileset.debugFreezeFrame) {
- return;
- }
-
- tileset._selectedTiles.length = 0;
- tileset._selectedTilesToStyle.length = 0;
- tileset._emptyTiles.length = 0;
- tileset._hasMixedContent = false;
-
- const root = tileset.root;
- updateTile(root, frameState);
-
- if (!isVisible(root)) {
- return;
- }
-
- if (
- root.getScreenSpaceError(frameState, true) <=
- tileset._maximumScreenSpaceError
- ) {
- return;
- }
-
- const baseScreenSpaceError = !skipLevelOfDetail(tileset)
- ? tileset._maximumScreenSpaceError
- : tileset.immediatelyLoadDesiredLevelOfDetail
- ? Number.MAX_VALUE
- : Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError);
-
- executeTraversal(root, baseScreenSpaceError, frameState);
-
- if (skipLevelOfDetail(tileset)) {
- traverseAndSelect(root, frameState);
- }
-
- traversal.stack.trim(traversal.stackMaximumLength);
- emptyTraversal.stack.trim(emptyTraversal.stackMaximumLength);
- descendantTraversal.stack.trim(descendantTraversal.stackMaximumLength);
- selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength);
- selectionTraversal.ancestorStack.trim(
- selectionTraversal.ancestorStackMaximumLength
- );
-
- // Update the priority for any requests found during traversal
- // Update after traversal so that min and max values can be used to normalize priority values
- const requestedTiles = tileset._requestedTiles;
- for (let i = 0; i < requestedTiles.length; ++i) {
- requestedTiles[i].updatePriority();
- }
+ DeveloperError.throwInstantiationError();
};
/**
+ * Sort by farthest child first since this is going on a stack
+ *
* @private
- * @param {Cesium3DTile} tile
- * @returns {boolean} Whether the tile is within the current field of view
+ * @param {Cesium3DTile} a
+ * @param {Cesium3DTile} b
+ * @returns {number}
*/
-function isVisible(tile) {
- return tile._visible && tile._inRequestVolume;
-}
+Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera = function (a, b) {
+ if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
+ return b._centerZDepth - a._centerZDepth;
+ }
+
+ return b._distanceToCamera - a._distanceToCamera;
+};
/**
- * The private ._skipLevelOfDetail flag on a Cesium3DTileset is updated in
- * Cesium3DTileset.prototype.prePassesUpdate to confirm if skipping is actually
- * possible and allowed, even when the public .skipLevelOfDetail flag is true
+ * Determine if a tile can and should be traversed for children tiles that
+ * would contribute to rendering the current view
*
* @private
- * @param {Cesium3DTileset} tileset
- * @returns {boolean} Whether to do LOD skipping
+ * @param {Cesium3DTile} tile
+ * @returns {boolean}
*/
-function skipLevelOfDetail(tileset) {
- return tileset._skipLevelOfDetail;
-}
+Cesium3DTilesetTraversal.canTraverse = function (tile) {
+ if (tile.children.length === 0) {
+ return false;
+ }
+ if (tile.hasTilesetContent || tile.hasImplicitContent) {
+ // Traverse external tileset to visit its root tile
+ // Don't traverse if the subtree is expired because it will be destroyed
+ return !tile.contentExpired;
+ }
+ return tile._screenSpaceError > tile.tileset._maximumScreenSpaceError;
+};
/**
* Mark a tile as selected, and add it to the tileset's list of selected tiles
@@ -121,7 +74,7 @@ function skipLevelOfDetail(tileset) {
* @param {Cesium3DTile} tile
* @param {FrameState} frameState
*/
-function selectTile(tile, frameState) {
+Cesium3DTilesetTraversal.selectTile = function (tile, frameState) {
if (tile.contentVisibility(frameState) === Intersect.OUTSIDE) {
return;
}
@@ -138,135 +91,61 @@ function selectTile(tile, frameState) {
}
tile._selectedFrame = frameState.frameNumber;
tileset._selectedTiles.push(tile);
-}
-
-/**
- * Mark descendant tiles for rendering, and update as needed
- *
- * @private
- * @param {Cesium3DTile} root
- * @param {FrameState} frameState
- */
-function selectDescendants(root, frameState) {
- const stack = descendantTraversal.stack;
- stack.push(root);
- while (stack.length > 0) {
- descendantTraversal.stackMaximumLength = Math.max(
- descendantTraversal.stackMaximumLength,
- stack.length
- );
- const tile = stack.pop();
- const children = tile.children;
- for (let i = 0; i < children.length; ++i) {
- const child = children[i];
- if (isVisible(child)) {
- if (child.contentAvailable) {
- updateTile(child, frameState);
- touchTile(child, frameState);
- selectTile(child, frameState);
- } else if (child._depth - root._depth < descendantSelectionDepth) {
- // Continue traversing, but not too far
- stack.push(child);
- }
- }
- }
- }
-}
-
-/**
- * Mark a tile as selected if it has content available.
- * If its content is not available, and we are skipping levels of detail,
- * select an ancestor or descendant tile instead
- *
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- */
-function selectDesiredTile(tile, frameState) {
- if (!skipLevelOfDetail(tile.tileset)) {
- if (tile.contentAvailable) {
- // The tile can be selected right away and does not require traverseAndSelect
- selectTile(tile, frameState);
- }
- return;
- }
-
- // If this tile is not loaded attempt to select its ancestor instead
- const loadedTile = tile.contentAvailable
- ? tile
- : tile._ancestorWithContentAvailable;
- if (defined(loadedTile)) {
- // Tiles will actually be selected in traverseAndSelect
- loadedTile._shouldSelect = true;
- } else {
- // If no ancestors are ready traverse down and select tiles to minimize empty regions.
- // This happens often for immediatelyLoadDesiredLevelOfDetail where parent tiles are not necessarily loaded before zooming out.
- selectDescendants(tile, frameState);
- }
-}
+};
/**
* @private
* @param {Cesium3DTile} tile
* @param {FrameState} frameState
*/
-function visitTile(tile, frameState) {
+Cesium3DTilesetTraversal.visitTile = function (tile, frameState) {
++tile.tileset._statistics.visited;
tile._visitedFrame = frameState.frameNumber;
-}
+};
/**
* @private
* @param {Cesium3DTile} tile
* @param {FrameState} frameState
*/
-function touchTile(tile, frameState) {
+Cesium3DTilesetTraversal.touchTile = function (tile, frameState) {
if (tile._touchedFrame === frameState.frameNumber) {
// Prevents another pass from touching the frame again
return;
}
tile.tileset._cache.touch(tile);
tile._touchedFrame = frameState.frameNumber;
-}
+};
/**
+ * Add a tile to the list of requested tiles, if appropriate
+ *
* @private
* @param {Cesium3DTile} tile
+ * @param {FrameState} frameState
*/
-function updateMinimumMaximumPriority(tile) {
- const {
- _maximumPriority: maximumPriority,
- _minimumPriority: minimumPriority,
- } = tile.tileset;
- const priorityHolder = tile._priorityHolder;
+Cesium3DTilesetTraversal.loadTile = function (tile, frameState) {
+ const { tileset } = tile;
+ if (
+ tile._requestedFrame === frameState.frameNumber ||
+ (!tile.hasUnloadedRenderableContent && !tile.contentExpired)
+ ) {
+ return;
+ }
- maximumPriority.distance = Math.max(
- priorityHolder._distanceToCamera,
- maximumPriority.distance
- );
- minimumPriority.distance = Math.min(
- priorityHolder._distanceToCamera,
- minimumPriority.distance
- );
- maximumPriority.depth = Math.max(tile._depth, maximumPriority.depth);
- minimumPriority.depth = Math.min(tile._depth, minimumPriority.depth);
- maximumPriority.foveatedFactor = Math.max(
- priorityHolder._foveatedFactor,
- maximumPriority.foveatedFactor
- );
- minimumPriority.foveatedFactor = Math.min(
- priorityHolder._foveatedFactor,
- minimumPriority.foveatedFactor
- );
- maximumPriority.reverseScreenSpaceError = Math.max(
- tile._priorityReverseScreenSpaceError,
- maximumPriority.reverseScreenSpaceError
- );
- minimumPriority.reverseScreenSpaceError = Math.min(
- tile._priorityReverseScreenSpaceError,
- minimumPriority.reverseScreenSpaceError
- );
-}
+ if (!isOnScreenLongEnough(tile, frameState)) {
+ return;
+ }
+
+ const cameraHasNotStoppedMovingLongEnough =
+ frameState.camera.timeSinceMoved < tileset.foveatedTimeDelay;
+ if (tile.priorityDeferred && cameraHasNotStoppedMovingLongEnough) {
+ return;
+ }
+
+ tile._requestedFrame = frameState.frameNumber;
+ tileset._requestedTiles.push(tile);
+};
/**
* Prevent unnecessary loads while camera is moving by getting the ratio of travel distance to tile size.
@@ -300,94 +179,24 @@ function isOnScreenLongEnough(tile, frameState) {
}
/**
- * Add a tile to the list of requested tiles, if appropriate
- *
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- */
-function loadTile(tile, frameState) {
- const { tileset } = tile;
- if (
- tile._requestedFrame === frameState.frameNumber ||
- (!hasUnloadedContent(tile) && !tile.contentExpired)
- ) {
- return;
- }
-
- if (!isOnScreenLongEnough(tile, frameState)) {
- return;
- }
-
- const cameraHasNotStoppedMovingLongEnough =
- frameState.camera.timeSinceMoved < tileset.foveatedTimeDelay;
- if (tile.priorityDeferred && cameraHasNotStoppedMovingLongEnough) {
- return;
- }
-
- tile._requestedFrame = frameState.frameNumber;
- tileset._requestedTiles.push(tile);
-}
-
-/**
- * Wrap Cesium3DTile.prototype.updateVisibility to avoid repeated updates
+ * Reset some of the tile's flags and re-evaluate visibility and priority
*
* @private
* @param {Cesium3DTile} tile
* @param {FrameState} frameState
*/
-function updateVisibility(tile, frameState) {
- const updatedVisibilityFrame = tile.tileset._updatedVisibilityFrame;
- if (tile._updatedVisibilityFrame === updatedVisibilityFrame) {
- // Return early if visibility has already been checked during the traversal.
- // The visibility may have already been checked if the cullWithChildrenBounds optimization is used.
- return;
- }
-
- tile.updateVisibility(frameState);
- tile._updatedVisibilityFrame = updatedVisibilityFrame;
-}
-
-/**
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- * @returns {boolean}
- */
-function anyChildrenVisible(tile, frameState) {
- let anyVisible = false;
- const children = tile.children;
- for (let i = 0; i < children.length; ++i) {
- const child = children[i];
- updateVisibility(child, frameState);
- anyVisible = anyVisible || isVisible(child);
- }
- return anyVisible;
-}
+Cesium3DTilesetTraversal.updateTile = function (tile, frameState) {
+ updateTileVisibility(tile, frameState);
+ tile.updateExpiration();
-/**
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- * @returns {boolean}
- */
-function meetsScreenSpaceErrorEarly(tile, frameState) {
- const { parent, tileset } = tile;
- if (
- !defined(parent) ||
- parent.hasTilesetContent ||
- parent.hasImplicitContent ||
- parent.refine !== Cesium3DTileRefine.ADD
- ) {
- return false;
- }
+ tile._wasMinPriorityChild = false;
+ tile._priorityHolder = tile;
+ updateMinimumMaximumPriority(tile);
- // Use parent's geometric error with child's box to see if the tile already meet the SSE
- return (
- tile.getScreenSpaceError(frameState, true) <=
- tileset._maximumScreenSpaceError
- );
-}
+ // SkipLOD
+ tile._shouldSelect = false;
+ tile._finalResolution = true;
+};
/**
* @private
@@ -395,9 +204,9 @@ function meetsScreenSpaceErrorEarly(tile, frameState) {
* @param {FrameState} frameState
*/
function updateTileVisibility(tile, frameState) {
- updateVisibility(tile, frameState);
+ tile.updateVisibility(frameState);
- if (!isVisible(tile)) {
+ if (!tile.isVisible) {
return;
}
@@ -432,464 +241,81 @@ function updateTileVisibility(tile, frameState) {
}
/**
- * Reset some of the tile's flags and re-evaluate visibility and priority
- *
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- */
-function updateTile(tile, frameState) {
- updateTileVisibility(tile, frameState);
- tile.updateExpiration();
-
- tile._wasMinPriorityChild = false;
- tile._priorityHolder = tile;
- updateMinimumMaximumPriority(tile);
-
- // SkipLOD
- tile._shouldSelect = false;
- tile._finalResolution = true;
-}
-
-/**
- * Update links to the ancestor tiles that have content
- *
* @private
* @param {Cesium3DTile} tile
* @param {FrameState} frameState
- */
-function updateTileAncestorContentLinks(tile, frameState) {
- tile._ancestorWithContent = undefined;
- tile._ancestorWithContentAvailable = undefined;
-
- const { parent } = tile;
- if (!defined(parent)) {
- return;
- }
- const parentHasContent =
- !hasUnloadedContent(parent) ||
- parent._requestedFrame === frameState.frameNumber;
-
- // ancestorWithContent is an ancestor that has content or has the potential to have
- // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
- tile._ancestorWithContent = parentHasContent
- ? parent
- : parent._ancestorWithContent;
-
- // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded
- tile._ancestorWithContentAvailable = parent.contentAvailable
- ? parent
- : parent._ancestorWithContentAvailable;
-}
-
-/**
- * @private
- * @param {Cesium3DTile} tile
* @returns {boolean}
*/
-function hasEmptyContent(tile) {
- return (
- tile.hasEmptyContent || tile.hasTilesetContent || tile.hasImplicitContent
- );
-}
-
-/**
- * @private
- * @param {Cesium3DTile} tile
- * @returns {boolean}
- */
-function hasUnloadedContent(tile) {
- return !hasEmptyContent(tile) && tile.contentUnloaded;
-}
+function meetsScreenSpaceErrorEarly(tile, frameState) {
+ const { parent, tileset } = tile;
+ if (
+ !defined(parent) ||
+ parent.hasTilesetContent ||
+ parent.hasImplicitContent ||
+ parent.refine !== Cesium3DTileRefine.ADD
+ ) {
+ return false;
+ }
-/**
- * Determine if a tile has reached the limit of level of detail skipping.
- * If so, it should _not_ be skipped: it should be loaded and rendered
- *
- * @private
- * @param {Cesium3DTileset} tileset
- * @param {Cesium3DTile} tile
- * @returns {boolean} true if this tile should not be skipped
- */
-function reachedSkippingThreshold(tileset, tile) {
- const ancestor = tile._ancestorWithContent;
+ // Use parent's geometric error with child's box to see if the tile already meet the SSE
return (
- !tileset.immediatelyLoadDesiredLevelOfDetail &&
- (tile._priorityProgressiveResolutionScreenSpaceErrorLeaf ||
- (defined(ancestor) &&
- tile._screenSpaceError <
- ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor &&
- tile._depth > ancestor._depth + tileset.skipLevels))
+ tile.getScreenSpaceError(frameState, true) <=
+ tileset._maximumScreenSpaceError
);
}
-/**
- * Sort by farthest child first since this is going on a stack
- *
- * @private
- * @param {Cesium3DTile} a
- * @param {Cesium3DTile} b
- * @returns {number}
- */
-function sortChildrenByDistanceToCamera(a, b) {
- if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
- return b._centerZDepth - a._centerZDepth;
- }
-
- return b._distanceToCamera - a._distanceToCamera;
-}
-
/**
* @private
* @param {Cesium3DTile} tile
- * @param {ManagedArray} stack
* @param {FrameState} frameState
* @returns {boolean}
*/
-function updateAndPushChildren(tile, stack, frameState) {
- const replace = tile.refine === Cesium3DTileRefine.REPLACE;
- const { tileset, children } = tile;
-
- for (let i = 0; i < children.length; ++i) {
- updateTile(children[i], frameState);
- }
-
- // Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
- children.sort(sortChildrenByDistanceToCamera);
-
- // For traditional replacement refinement only refine if all children are loaded.
- // Empty tiles are exempt since it looks better if children stream in as they are loaded to fill the empty space.
- const checkRefines =
- !skipLevelOfDetail(tileset) && replace && !hasEmptyContent(tile);
- let refines = true;
-
- let anyChildrenVisible = false;
-
- // Determining min child
- let minIndex = -1;
- let minimumPriority = Number.MAX_VALUE;
-
+function anyChildrenVisible(tile, frameState) {
+ let anyVisible = false;
+ const children = tile.children;
for (let i = 0; i < children.length; ++i) {
const child = children[i];
- if (isVisible(child)) {
- stack.push(child);
- if (child._foveatedFactor < minimumPriority) {
- minIndex = i;
- minimumPriority = child._foveatedFactor;
- }
- anyChildrenVisible = true;
- } else if (checkRefines || tileset.loadSiblings) {
- // Keep non-visible children loaded since they are still needed before the parent can refine.
- // Or loadSiblings is true so always load tiles regardless of visibility.
- if (child._foveatedFactor < minimumPriority) {
- minIndex = i;
- minimumPriority = child._foveatedFactor;
- }
- loadTile(child, frameState);
- touchTile(child, frameState);
- }
- if (checkRefines) {
- let childRefines;
- if (!child._inRequestVolume) {
- childRefines = false;
- } else if (hasEmptyContent(child)) {
- childRefines = executeEmptyTraversal(child, frameState);
- } else {
- childRefines = child.contentAvailable;
- }
- refines = refines && childRefines;
- }
+ child.updateVisibility(frameState);
+ anyVisible = anyVisible || child.isVisible;
}
-
- if (!anyChildrenVisible) {
- refines = false;
- }
-
- if (minIndex !== -1 && !skipLevelOfDetail(tileset) && replace) {
- // An ancestor will hold the _foveatedFactor and _distanceToCamera for descendants between itself and its highest priority descendant. Siblings of a min children along the way use this ancestor as their priority holder as well.
- // Priority of all tiles that refer to the _foveatedFactor and _distanceToCamera stored in the common ancestor will be differentiated based on their _depth.
- const minPriorityChild = children[minIndex];
- minPriorityChild._wasMinPriorityChild = true;
- const priorityHolder =
- (tile._wasMinPriorityChild || tile === tileset.root) &&
- minimumPriority <= tile._priorityHolder._foveatedFactor
- ? tile._priorityHolder
- : tile; // This is where priority dependency chains are wired up or started anew.
- priorityHolder._foveatedFactor = Math.min(
- minPriorityChild._foveatedFactor,
- priorityHolder._foveatedFactor
- );
- priorityHolder._distanceToCamera = Math.min(
- minPriorityChild._distanceToCamera,
- priorityHolder._distanceToCamera
- );
-
- for (let i = 0; i < children.length; ++i) {
- children[i]._priorityHolder = priorityHolder;
- }
- }
-
- return refines;
-}
-
-/**
- * Determine if a tile is part of the base traversal.
- * If not, this tile could be considered for level of detail skipping
- *
- * @private
- * @param {Cesium3DTile} tile
- * @param {number} baseScreenSpaceError
- * @returns {boolean}
- */
-function inBaseTraversal(tile, baseScreenSpaceError) {
- const { tileset } = tile;
- if (!skipLevelOfDetail(tileset)) {
- return true;
- }
- if (tileset.immediatelyLoadDesiredLevelOfDetail) {
- return false;
- }
- if (!defined(tile._ancestorWithContent)) {
- // Include root or near-root tiles in the base traversal so there is something to select up to
- return true;
- }
- if (tile._screenSpaceError === 0.0) {
- // If a leaf, use parent's SSE
- return tile.parent._screenSpaceError > baseScreenSpaceError;
- }
- return tile._screenSpaceError > baseScreenSpaceError;
+ return anyVisible;
}
/**
- * Determine if a tile can and should be traversed for children tiles that
- * would contribute to rendering the current view
- *
* @private
* @param {Cesium3DTile} tile
- * @returns {boolean}
- */
-function canTraverse(tile) {
- if (tile.children.length === 0) {
- return false;
- }
- if (tile.hasTilesetContent || tile.hasImplicitContent) {
- // Traverse external tileset to visit its root tile
- // Don't traverse if the subtree is expired because it will be destroyed
- return !tile.contentExpired;
- }
- return tile._screenSpaceError > tile.tileset._maximumScreenSpaceError;
-}
-
-/**
- * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
- * If skipLevelOfDetail is off then a tile does not refine until all children are loaded.
- * This is the traditional replacement refinement approach and is called the base traversal.
- * Tiles that have a greater screen space error than the base screen space error are part of the base traversal,
- * all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
- * and rendering children and parent tiles simultaneously.
- *
- * @private
- * @param {Cesium3DTile} root
- * @param {number} baseScreenSpaceError
- * @param {FrameState} frameState
- */
-function executeTraversal(root, baseScreenSpaceError, frameState) {
- const { tileset } = root;
- const stack = traversal.stack;
- stack.push(root);
-
- while (stack.length > 0) {
- traversal.stackMaximumLength = Math.max(
- traversal.stackMaximumLength,
- stack.length
- );
-
- const tile = stack.pop();
-
- updateTileAncestorContentLinks(tile, frameState);
- const parent = tile.parent;
- const parentRefines = !defined(parent) || parent._refines;
-
- tile._refines = canTraverse(tile)
- ? updateAndPushChildren(tile, stack, frameState) && parentRefines
- : false;
-
- const stoppedRefining = !tile._refines && parentRefines;
-
- if (hasEmptyContent(tile)) {
- // Add empty tile just to show its debug bounding volume
- // If the tile has tileset content load the external tileset
- // If the tile cannot refine further select its nearest loaded ancestor
- tileset._emptyTiles.push(tile);
- loadTile(tile, frameState);
- if (stoppedRefining) {
- selectDesiredTile(tile, frameState);
- }
- } else if (tile.refine === Cesium3DTileRefine.ADD) {
- // Additive tiles are always loaded and selected
- selectDesiredTile(tile, frameState);
- loadTile(tile, frameState);
- } else if (tile.refine === Cesium3DTileRefine.REPLACE) {
- if (inBaseTraversal(tile, baseScreenSpaceError)) {
- // Always load tiles in the base traversal
- // Select tiles that can't refine further
- loadTile(tile, frameState);
- if (stoppedRefining) {
- selectDesiredTile(tile, frameState);
- }
- } else if (stoppedRefining) {
- // In skip traversal, load and select tiles that can't refine further
- selectDesiredTile(tile, frameState);
- loadTile(tile, frameState);
- } else if (reachedSkippingThreshold(tileset, tile)) {
- // In skip traversal, load tiles that aren't skipped
- loadTile(tile, frameState);
- }
- }
-
- visitTile(tile, frameState);
- touchTile(tile, frameState);
- }
-}
-
-/**
- * Depth-first traversal that checks if all nearest descendants with content are loaded.
- * Ignores visibility.
- *
- * @private
- * @param {Cesium3DTile} root
- * @param {FrameState} frameState
- * @returns {boolean}
- */
-function executeEmptyTraversal(root, frameState) {
- let allDescendantsLoaded = true;
- const stack = emptyTraversal.stack;
- stack.push(root);
-
- while (stack.length > 0) {
- emptyTraversal.stackMaximumLength = Math.max(
- emptyTraversal.stackMaximumLength,
- stack.length
- );
-
- const tile = stack.pop();
- const children = tile.children;
- const childrenLength = children.length;
-
- // Only traverse if the tile is empty - traversal stop at descendants with content
- const emptyContent = hasEmptyContent(tile);
- const traverse = emptyContent && canTraverse(tile);
- const emptyLeaf = emptyContent && tile.children.length === 0;
-
- // Traversal stops but the tile does not have content yet
- // There will be holes if the parent tries to refine to its children, so don't refine
- // One exception: a parent may refine even if one of its descendants is an empty leaf
- if (!traverse && !tile.contentAvailable && !emptyLeaf) {
- allDescendantsLoaded = false;
- }
-
- updateTile(tile, frameState);
- if (!isVisible(tile)) {
- // Load tiles that aren't visible since they are still needed for the parent to refine
- loadTile(tile, frameState);
- touchTile(tile, frameState);
- }
-
- if (traverse) {
- for (let i = 0; i < childrenLength; ++i) {
- const child = children[i];
- stack.push(child);
- }
- }
- }
-
- return allDescendantsLoaded;
-}
-
-/**
- * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
- * This is a preorder traversal so children tiles are selected before ancestor tiles.
- *
- * The reason for the preorder traversal is so that tiles can easily be marked with their
- * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
- * This property is important for use in the stencil test because we want to render deeper tiles on top of their
- * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer.
- *
- * We want to select children before their ancestors because there is no guarantee on the relationship between
- * the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top
- * of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
- *
- * NOTE: 3D Tiles uses 3 bits from the stencil buffer meaning this will not work when there is a chain of
- * selected tiles that is deeper than 7. This is not very likely.
- *
- * @private
- * @param {Cesium3DTile} root
- * @param {FrameState} frameState
*/
-function traverseAndSelect(root, frameState) {
- const { stack, ancestorStack } = selectionTraversal;
- let lastAncestor;
-
- stack.push(root);
-
- while (stack.length > 0 || ancestorStack.length > 0) {
- selectionTraversal.stackMaximumLength = Math.max(
- selectionTraversal.stackMaximumLength,
- stack.length
- );
- selectionTraversal.ancestorStackMaximumLength = Math.max(
- selectionTraversal.ancestorStackMaximumLength,
- ancestorStack.length
- );
-
- if (ancestorStack.length > 0) {
- const waitingTile = ancestorStack.peek();
- if (waitingTile._stackLength === stack.length) {
- ancestorStack.pop();
- if (waitingTile !== lastAncestor) {
- waitingTile._finalResolution = false;
- }
- selectTile(waitingTile, frameState);
- continue;
- }
- }
-
- const tile = stack.pop();
- if (!defined(tile)) {
- // stack is empty but ancestorStack isn't
- continue;
- }
-
- const traverse = canTraverse(tile);
-
- if (tile._shouldSelect) {
- if (tile.refine === Cesium3DTileRefine.ADD) {
- selectTile(tile, frameState);
- } else {
- tile._selectionDepth = ancestorStack.length;
- if (tile._selectionDepth > 0) {
- tile.tileset._hasMixedContent = true;
- }
- lastAncestor = tile;
- if (!traverse) {
- selectTile(tile, frameState);
- continue;
- }
- ancestorStack.push(tile);
- tile._stackLength = stack.length;
- }
- }
+function updateMinimumMaximumPriority(tile) {
+ const minimumPriority = tile.tileset._minimumPriority;
+ const maximumPriority = tile.tileset._maximumPriority;
+ const priorityHolder = tile._priorityHolder;
- if (traverse) {
- const children = tile.children;
- for (let i = 0; i < children.length; ++i) {
- const child = children[i];
- if (isVisible(child)) {
- stack.push(child);
- }
- }
- }
- }
+ maximumPriority.distance = Math.max(
+ priorityHolder._distanceToCamera,
+ maximumPriority.distance
+ );
+ minimumPriority.distance = Math.min(
+ priorityHolder._distanceToCamera,
+ minimumPriority.distance
+ );
+ maximumPriority.depth = Math.max(tile._depth, maximumPriority.depth);
+ minimumPriority.depth = Math.min(tile._depth, minimumPriority.depth);
+ maximumPriority.foveatedFactor = Math.max(
+ priorityHolder._foveatedFactor,
+ maximumPriority.foveatedFactor
+ );
+ minimumPriority.foveatedFactor = Math.min(
+ priorityHolder._foveatedFactor,
+ minimumPriority.foveatedFactor
+ );
+ maximumPriority.reverseScreenSpaceError = Math.max(
+ tile._priorityReverseScreenSpaceError,
+ maximumPriority.reverseScreenSpaceError
+ );
+ minimumPriority.reverseScreenSpaceError = Math.min(
+ tile._priorityReverseScreenSpaceError,
+ minimumPriority.reverseScreenSpaceError
+ );
}
export default Cesium3DTilesetTraversal;
diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js
index d7fbad7f790e..4b53df8e3740 100644
--- a/packages/engine/Source/Scene/Model/Model.js
+++ b/packages/engine/Source/Scene/Model/Model.js
@@ -2574,10 +2574,6 @@ Model.prototype.hasSilhouette = function (frameState) {
);
};
-function supportsSkipLevelOfDetail(frameState) {
- return frameState.context.stencilBuffer;
-}
-
/**
* Gets whether or not the model is part of a tileset that uses the
* skipLevelOfDetail optimization. This accounts for whether skipLevelOfDetail
@@ -2588,13 +2584,13 @@ function supportsSkipLevelOfDetail(frameState) {
* @private
*/
Model.prototype.hasSkipLevelOfDetail = function (frameState) {
- const is3DTiles = ModelType.is3DTiles(this.type);
- if (!is3DTiles) {
+ if (!ModelType.is3DTiles(this.type)) {
return false;
}
+ const supportsSkipLevelOfDetail = frameState.context.stencilBuffer;
const tileset = this._content.tileset;
- return supportsSkipLevelOfDetail(frameState) && tileset.skipLevelOfDetail;
+ return supportsSkipLevelOfDetail && tileset.isSkippingLevelOfDetail;
};
/**
diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommand.js b/packages/engine/Source/Scene/Model/ModelDrawCommand.js
index a82b75fc4b52..83323fc1ec7d 100644
--- a/packages/engine/Source/Scene/Model/ModelDrawCommand.js
+++ b/packages/engine/Source/Scene/Model/ModelDrawCommand.js
@@ -437,9 +437,8 @@ function updateShadows(drawCommand) {
const receiveShadows = ShadowMode.receiveShadows(shadows);
const derivedCommands = drawCommand._derivedCommands;
- const length = derivedCommands.length;
- for (let i = 0; i < length; ++i) {
+ for (let i = 0; i < derivedCommands.length; ++i) {
const derivedCommand = derivedCommands[i];
if (derivedCommand.updateShadows) {
const command = derivedCommand.command;
@@ -451,11 +450,9 @@ function updateShadows(drawCommand) {
function updateBackFaceCulling(drawCommand) {
const backFaceCulling = drawCommand.backFaceCulling;
-
const derivedCommands = drawCommand._derivedCommands;
- const length = derivedCommands.length;
- for (let i = 0; i < length; ++i) {
+ for (let i = 0; i < derivedCommands.length; ++i) {
const derivedCommand = derivedCommands[i];
if (derivedCommand.updateBackFaceCulling) {
const command = derivedCommand.command;
@@ -468,11 +465,9 @@ function updateBackFaceCulling(drawCommand) {
function updateCullFace(drawCommand) {
const cullFace = drawCommand.cullFace;
-
const derivedCommands = drawCommand._derivedCommands;
- const length = derivedCommands.length;
- for (let i = 0; i < length; ++i) {
+ for (let i = 0; i < derivedCommands.length; ++i) {
const derivedCommand = derivedCommands[i];
if (derivedCommand.updateCullFace) {
const command = derivedCommand.command;
@@ -485,11 +480,9 @@ function updateCullFace(drawCommand) {
function updateDebugShowBoundingVolume(drawCommand) {
const debugShowBoundingVolume = drawCommand.debugShowBoundingVolume;
-
const derivedCommands = drawCommand._derivedCommands;
- const length = derivedCommands.length;
- for (let i = 0; i < length; ++i) {
+ for (let i = 0; i < derivedCommands.length; ++i) {
const derivedCommand = derivedCommands[i];
if (derivedCommand.updateDebugShowBoundingVolume) {
const command = derivedCommand.command;
@@ -537,15 +530,10 @@ ModelDrawCommand.prototype.pushCommands = function (frameState, result) {
}
if (this._needsSkipLevelOfDetailCommands) {
- const content = this._model.content;
- const tileset = content.tileset;
- const tile = content.tile;
-
- const hasMixedContent = tileset._hasMixedContent;
- const finalResolution = tile._finalResolution;
+ const { tileset, tile } = this._model.content;
- if (hasMixedContent) {
- if (!finalResolution) {
+ if (tileset.hasMixedContent) {
+ if (!tile._finalResolution) {
pushCommand(
tileset._backfaceCommands,
this._skipLodBackfaceCommand,
@@ -846,13 +834,14 @@ function deriveSkipLodStencilCommand(command) {
const stencilCommand = DrawCommand.shallowClone(command);
const renderState = clone(command.renderState, true);
// The stencil reference is updated dynamically; see updateSkipLodStencilCommand().
- renderState.stencilTest.enabled = true;
- renderState.stencilTest.mask = StencilConstants.SKIP_LOD_MASK;
- renderState.stencilTest.reference = StencilConstants.CESIUM_3D_TILE_MASK;
- renderState.stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL;
- renderState.stencilTest.frontOperation.zPass = StencilOperation.REPLACE;
- renderState.stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL;
- renderState.stencilTest.backOperation.zPass = StencilOperation.REPLACE;
+ const { stencilTest } = renderState;
+ stencilTest.enabled = true;
+ stencilTest.mask = StencilConstants.SKIP_LOD_MASK;
+ stencilTest.reference = StencilConstants.CESIUM_3D_TILE_MASK;
+ stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL;
+ stencilTest.frontOperation.zPass = StencilOperation.REPLACE;
+ stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL;
+ stencilTest.backOperation.zPass = StencilOperation.REPLACE;
renderState.stencilMask =
StencilConstants.CESIUM_3D_TILE_MASK | StencilConstants.SKIP_LOD_MASK;
diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetBaseTraversalSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetBaseTraversalSpec.js
new file mode 100644
index 000000000000..3c4782ee83c0
--- /dev/null
+++ b/packages/engine/Specs/Scene/Cesium3DTilesetBaseTraversalSpec.js
@@ -0,0 +1,12 @@
+import {
+ Cesium3DTilesetTraversal,
+ Cesium3DTilesetBaseTraversal,
+} from "../../index.js";
+
+describe("Scene/Cesium3DTilesetBaseTraversal", function () {
+ it("conforms to Cesium3DTilesetTraversal interface", function () {
+ expect(Cesium3DTilesetBaseTraversal).toConformToInterface(
+ Cesium3DTilesetTraversal
+ );
+ });
+});
diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetMostDetailedTraversalSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetMostDetailedTraversalSpec.js
new file mode 100644
index 000000000000..364c9369df1a
--- /dev/null
+++ b/packages/engine/Specs/Scene/Cesium3DTilesetMostDetailedTraversalSpec.js
@@ -0,0 +1,12 @@
+import {
+ Cesium3DTilesetTraversal,
+ Cesium3DTilesetMostDetailedTraversal,
+} from "../../index.js";
+
+describe("Scene/Cesium3DTilesetMostDetailedTraversal", function () {
+ it("conforms to Cesium3DTilesetTraversal interface", function () {
+ expect(Cesium3DTilesetMostDetailedTraversal).toConformToInterface(
+ Cesium3DTilesetTraversal
+ );
+ });
+});
diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSkipTraversalSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSkipTraversalSpec.js
new file mode 100644
index 000000000000..bb1418cebfcf
--- /dev/null
+++ b/packages/engine/Specs/Scene/Cesium3DTilesetSkipTraversalSpec.js
@@ -0,0 +1,12 @@
+import {
+ Cesium3DTilesetTraversal,
+ Cesium3DTilesetSkipTraversal,
+} from "../../index.js";
+
+describe("Scene/Cesium3DTilesetSkipTraversal", function () {
+ it("conforms to Cesium3DTilesetTraversal interface", function () {
+ expect(Cesium3DTilesetSkipTraversal).toConformToInterface(
+ Cesium3DTilesetTraversal
+ );
+ });
+});
diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js
index e93bdc4c7f20..d0535302cc31 100644
--- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js
@@ -3802,12 +3802,12 @@ describe(
const statistics = tileset._statistics;
expect(statistics.numberOfTilesWithContentReady).toEqual(1);
expect(tileset._selectedTiles[0]._selectionDepth).toEqual(0);
- expect(tileset._hasMixedContent).toBe(false);
+ expect(tileset.hasMixedContent).toBe(false);
return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(
function (tileset) {
expect(statistics.numberOfTilesWithContentReady).toEqual(5);
- expect(tileset._hasMixedContent).toBe(false);
+ expect(tileset.hasMixedContent).toBe(false);
}
);
});
@@ -3828,7 +3828,7 @@ describe(
scene.renderForSpecs();
- expect(tileset._hasMixedContent).toBe(true);
+ expect(tileset.hasMixedContent).toBe(true);
expect(statistics.numberOfTilesWithContentReady).toEqual(2);
expect(
tileset.root.children[0].children[0].children[3]._selectionDepth
@@ -3838,7 +3838,7 @@ describe(
return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(
function (tileset) {
expect(statistics.numberOfTilesWithContentReady).toEqual(5);
- expect(tileset._hasMixedContent).toBe(false);
+ expect(tileset.hasMixedContent).toBe(false);
}
);
});
@@ -3889,7 +3889,7 @@ describe(
expect(root.children[0].children[0].children[3]._finalResolution).toBe(
true
);
- expect(tileset._hasMixedContent).toBe(true);
+ expect(tileset.hasMixedContent).toBe(true);
const commandList = scene.frameState.commandList;
const rs = commandList[1].renderState;
@@ -3933,7 +3933,7 @@ describe(
expect(
isSelected(tileset, root.children[0].children[0].children[3])
).toBe(false);
- expect(tileset._hasMixedContent).toBe(false);
+ expect(tileset.hasMixedContent).toBe(false);
return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset);
});
diff --git a/packages/engine/Specs/Scene/Model/ModelDrawCommandSpec.js b/packages/engine/Specs/Scene/Model/ModelDrawCommandSpec.js
index 99a5fb9a7fe3..6e86f1b0f1b6 100644
--- a/packages/engine/Specs/Scene/Model/ModelDrawCommandSpec.js
+++ b/packages/engine/Specs/Scene/Model/ModelDrawCommandSpec.js
@@ -88,7 +88,7 @@ describe(
_projectTo2D: false,
content: {
tileset: {
- _hasMixedContent: true,
+ hasMixedContent: true,
_backfaceCommands: [],
},
tile: {
diff --git a/travis/coverage.sh b/travis/coverage.sh
index d718e9a5d6b4..f4c929ec6cf9 100755
--- a/travis/coverage.sh
+++ b/travis/coverage.sh
@@ -4,7 +4,7 @@ if [ $TRAVIS_BRANCH != "cesium.com" ]; then
npm --silent run build
npm --silent run coverage -- --browsers FirefoxHeadless --webgl-stub --failTaskOnError --suppressPassed
- if [ $TRAVIS_REPO_SLUG == "CesiumGS/cesium" ]; then
+ if [ $TRAVIS_SECURE_ENV_VARS ]; then
aws s3 sync ./Build/Coverage s3://cesium-dev/cesium/$TRAVIS_BRANCH/Build/Coverage --delete --color on
fi
fi
diff --git a/travis/deploy.sh b/travis/deploy.sh
index b1ec01abbaf3..26cab879a9f4 100755
--- a/travis/deploy.sh
+++ b/travis/deploy.sh
@@ -1,6 +1,6 @@
#!/bin/bash
set -ev
-if [ $TRAVIS_REPO_SLUG == "CesiumGS/cesium" ]; then
+if [ $TRAVIS_SECURE_ENV_VARS ]; then
# Files deployed to cesium.com are "production", and should be cached at edge locations for
# better performance. Otherwise, this is a development deploy and nothing should be cached
if [ $TRAVIS_BRANCH == "cesium.com" ]; then