Compare commits

...

16 Commits

Author SHA1 Message Date
Marco Hutter bdcd200d47
Merge 1a8764265f into 41a0f18ba9 2025-11-25 18:28:48 -05:00
Don McCurdy 41a0f18ba9
Merge pull request #13018 from CesiumGS/billboard-depth-regression-fix
Fixes model-billboard depth interactions
2025-11-25 18:03:41 +00:00
Don McCurdy 8bd8ae7f63
Merge branch 'main' into billboard-depth-regression-fix 2025-11-25 13:02:57 -05:00
Marco Hutter 1a8764265f Squashed commit with cleanups and additional tests
deploy / deploy (push) Waiting to run Details
2025-11-25 18:24:35 +01:00
Marco Hutter 48bbf6fd4f Squashed commits for spec experiments 2025-11-16 16:58:01 +01:00
Marco Hutter 59b9f79c4a Draft for RequestHandle spec 2025-11-06 14:28:55 +01:00
Marco Hutter 6a07d54781 Squashed commits for spec experiments 2025-11-06 13:54:58 +01:00
Matt Schwartz 020749de32 Add to CHANGES.md 2025-11-05 09:27:19 -05:00
Matt Schwartz f4e49d7b64 Fixes model-billboard depth interactions 2025-11-05 09:12:11 -05:00
Marco Hutter e2024b33bb Merge branch 'dynamic-3d-tiles-staging' of https://github.com/CesiumGS/cesium into dynamic-3d-tiles-staging 2025-11-03 18:48:29 +01:00
Marco Hutter 165d426689 Cleanups and drafts for specs and statistics 2025-11-03 18:47:07 +01:00
Marco Hutter 0daebfe832 Clanups and drafts for specs and statistics 2025-11-03 18:45:38 +01:00
Marco Hutter 9bd92a616f Minor cleanups and comments. Statistics drafts. 2025-10-29 20:08:30 +01:00
Marco Hutter df75727e02 Switch from extension to content type 2025-10-16 17:10:54 +02:00
Marco Hutter f79f19f6fc Dynamic 3D Tiles drafts (squashed) 2025-10-15 19:27:22 +02:00
Marco Hutter 9311afbe3d Placeholder for dynamic content handling 2025-10-11 15:47:10 +02:00
14 changed files with 4839 additions and 225 deletions

View File

@ -1,11 +1,12 @@
# Change Log
## 1.136
## 1.136 - 2025-12-01
### @cesium/engine
#### Fixes :wrench:
- Fixed depth testing bug with billboards and labels clipping through models [#13012](https://github.com/CesiumGS/cesium/issues/13012)
- Billboards using `imageSubRegion` now render as expected. [#12585](https://github.com/CesiumGS/cesium/issues/12585)
- Improved scaling of SVGs in billboards [#13020](https://github.com/CesiumGS/cesium/issues/13020)
- Fixed unexpected outline artifacts around billboards [#13038](https://github.com/CesiumGS/cesium/issues/13038)

View File

@ -790,7 +790,17 @@ Resource.prototype.appendForwardSlash = function () {
* @returns {Promise<ArrayBuffer>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
*
* @example
* // load a single URL asynchronously
* // load a single URL asynchronously.
* // Note that fetchArrayBuffer may return 'undefined', and this will cause
* // an error here. There is no way to know when it will return 'undefined'
* // or an actual promise. If it returns 'undefined', it is necessary to
* // call it again, until it returns the actual promise. But it may not be
* // called again after it returned a promise, because then there will be
* // multiple promises. Also note that the returned promise may be
* // rejected and receive 'undefined' as the error, so it's impossible to
* // know WHY it was rejected. So you can either ignore that, or just try
* // it again, hoping that it will not be rejected next time.
* // If you are reading this: GOOD LUCK!
* resource.fetchArrayBuffer().then(function(arrayBuffer) {
* // use the data
* }).catch(function(error) {

View File

@ -34,6 +34,7 @@ import SDFSettings from "./SDFSettings.js";
import TextureAtlas from "../Renderer/TextureAtlas.js";
import VerticalOrigin from "./VerticalOrigin.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import WebGLConstants from "../Core/WebGLConstants.js";
const SHOW_INDEX = Billboard.SHOW_INDEX;
const POSITION_INDEX = Billboard.POSITION_INDEX;
@ -2039,7 +2040,8 @@ BillboardCollection.prototype.update = function (frameState) {
) {
this._rsOpaque = RenderState.fromCache({
depthTest: {
enabled: false,
enabled: true,
func: WebGLConstants.LESS,
},
depthMask: true,
});
@ -2059,7 +2061,10 @@ BillboardCollection.prototype.update = function (frameState) {
) {
this._rsTranslucent = RenderState.fromCache({
depthTest: {
enabled: false,
enabled: true,
func: useTranslucentDepthMask
? WebGLConstants.LEQUAL
: WebGLConstants.LESS,
},
depthMask: useTranslucentDepthMask,
blending: BlendingState.ALPHA_BLEND,

View File

@ -21,16 +21,12 @@ import RequestState from "../Core/RequestState.js";
import RequestType from "../Core/RequestType.js";
import Resource from "../Core/Resource.js";
import RuntimeError from "../Core/RuntimeError.js";
import Cesium3DContentGroup from "./Cesium3DContentGroup.js";
import Cesium3DTileContentFactory from "./Cesium3DTileContentFactory.js";
import Cesium3DTileContentState from "./Cesium3DTileContentState.js";
import Cesium3DTileContentType from "./Cesium3DTileContentType.js";
import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
import Cesium3DTilePass from "./Cesium3DTilePass.js";
import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
import Empty3DTileContent from "./Empty3DTileContent.js";
import findContentMetadata from "./findContentMetadata.js";
import findGroupMetadata from "./findGroupMetadata.js";
import findTileMetadata from "./findTileMetadata.js";
import hasExtension from "./hasExtension.js";
import Multiple3DTileContent from "./Multiple3DTileContent.js";
@ -43,6 +39,8 @@ import TileBoundingSphere from "./TileBoundingSphere.js";
import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js";
import Pass from "../Renderer/Pass.js";
import VerticalExaggeration from "../Core/VerticalExaggeration.js";
import finishContent from "./finishContent.js";
import Dynamic3DTileContent from "./Dynamic3DTileContent.js";
/**
* A tile in a {@link Cesium3DTileset}. When a tile is first created, its content is not loaded;
@ -62,19 +60,6 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
this._tileset = tileset;
this._header = header;
const hasContentsArray = defined(header.contents);
const hasMultipleContents =
(hasContentsArray && header.contents.length > 1) ||
hasExtension(header, "3DTILES_multiple_contents");
// In the 1.0 schema, content is stored in tile.content instead of tile.contents
const contentHeader =
hasContentsArray && !hasMultipleContents
? header.contents[0]
: header.content;
this._contentHeader = contentHeader;
/**
* The local transform of this tile.
* @type {Matrix4}
@ -131,22 +116,6 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
);
this._boundingVolume2D = undefined;
let contentBoundingVolume;
if (defined(contentHeader) && defined(contentHeader.boundingVolume)) {
// Non-leaf tiles may have a content bounding-volume, which is a tight-fit bounding volume
// around only the features in the tile. This box is useful for culling for rendering,
// but not for culling for traversing the tree since it does not guarantee spatial coherence, i.e.,
// since it only bounds features in the tile, not the entire tile, children may be
// outside of this box.
contentBoundingVolume = this.createBoundingVolume(
contentHeader.boundingVolume,
computedTransform,
);
}
this._contentBoundingVolume = contentBoundingVolume;
this._contentBoundingVolume2D = undefined;
let viewerRequestVolume;
if (defined(header.viewerRequestVolume)) {
viewerRequestVolume = this.createBoundingVolume(
@ -178,27 +147,6 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
this.updateGeometricErrorScale();
let refine;
if (defined(header.refine)) {
if (header.refine === "replace" || header.refine === "add") {
Cesium3DTile._deprecationWarning(
"lowercase-refine",
`This tile uses a lowercase refine "${
header.refine
}". Instead use "${header.refine.toUpperCase()}".`,
);
}
refine =
header.refine.toUpperCase() === "REPLACE"
? Cesium3DTileRefine.REPLACE
: Cesium3DTileRefine.ADD;
} else if (defined(parent)) {
// Inherit from parent tile if omitted.
refine = parent.refine;
} else {
refine = Cesium3DTileRefine.REPLACE;
}
/**
* Specifies the type of refinement that is used when traversing this tile for rendering.
*
@ -206,7 +154,7 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
* @readonly
* @private
*/
this.refine = refine;
this.refine = determineRefine(header.refine, parent);
/**
* Gets the tile's children.
@ -229,58 +177,6 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
*/
this.parent = parent;
let content;
let hasEmptyContent = false;
let contentState;
let contentResource;
let serverKey;
baseResource = Resource.createIfNeeded(baseResource);
if (hasMultipleContents) {
contentState = Cesium3DTileContentState.UNLOADED;
// Each content may have its own URI, but they all need to be resolved
// relative to the tileset, so the base resource is used.
contentResource = baseResource.clone();
} else if (defined(contentHeader)) {
let contentHeaderUri = contentHeader.uri;
if (defined(contentHeader.url)) {
Cesium3DTile._deprecationWarning(
"contentUrl",
'This tileset JSON uses the "content.url" property which has been deprecated. Use "content.uri" instead.',
);
contentHeaderUri = contentHeader.url;
}
if (contentHeaderUri === "") {
Cesium3DTile._deprecationWarning(
"contentUriEmpty",
"content.uri property is an empty string, which creates a circular dependency, making this tileset invalid. Omit the content property instead",
);
content = new Empty3DTileContent(tileset, this);
hasEmptyContent = true;
contentState = Cesium3DTileContentState.READY;
} else {
contentState = Cesium3DTileContentState.UNLOADED;
contentResource = baseResource.getDerivedResource({
url: contentHeaderUri,
});
serverKey = RequestScheduler.getServerKey(
contentResource.getUrlComponent(),
);
}
} else {
content = new Empty3DTileContent(tileset, this);
hasEmptyContent = true;
contentState = Cesium3DTileContentState.READY;
}
this._content = content;
this._contentResource = contentResource;
this._contentState = contentState;
this._expiredContent = undefined;
this._serverKey = serverKey;
/**
* When <code>true</code>, the tile has no content.
*
@ -289,7 +185,7 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
*
* @private
*/
this.hasEmptyContent = hasEmptyContent;
this.hasEmptyContent = false;
/**
* When <code>true</code>, the tile's content points to an external tileset.
@ -334,7 +230,7 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
*
* @private
*/
this.hasRenderableContent = !hasEmptyContent;
this.hasRenderableContent = false;
/**
* When <code>true</code>, the tile contains content metadata from implicit tiling. This flag is set
@ -362,7 +258,17 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
*
* @private
*/
this.hasMultipleContents = hasMultipleContents;
this.hasMultipleContents = false;
// Initialize the content-related properties
this._contentBoundingVolume = undefined;
this._contentBoundingVolume2D = undefined;
this._content = undefined;
this._contentResource = undefined;
this._contentState = undefined;
this._expiredContent = undefined;
this._serverKey = undefined;
initializeContent(this, baseResource, header);
/**
* The node in the tileset's LRU cache, used to determine when to unload a tile's content.
@ -483,7 +389,7 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
*/
this.implicitSubtree = undefined;
// Members that are updated every frame for tree traversal and rendering optimizations:
// Members that are updated every frame for tree traversal and rendering optimizations.
this._distanceToCamera = 0.0;
this._centerZDepth = 0.0;
this._screenSpaceError = 0.0;
@ -535,6 +441,161 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
this._request = undefined;
}
/**
* Initialize the content-related properties of the given tile.
*
* This assumes that the <code>_tileset</code> and the
* <code>computedTransform</code> of the given tile have
* already been set.
*
* It will initialize the following properties of the tile, based
* on the given resource and header information:
*
* - _contentHeader
* - _contentBoundingVolume
* - _contentBoundingVolume2D
* - _content
* - _contentResource
* - _contentState
* - _expiredContent
* - _serverKey
* - hasEmptyContent
* - hasRenderableContent
* - hasMultipleContents
*
* The exact meaning of these properties has to be derived from
* the code. This function was just introduced as first cleanup.
*
* @param {Cesium3DTile} tile The tile
* @param {Resource} baseResource The base resource for the tileset
* @param {object} header The JSON header for the tile
*/
function initializeContent(tile, baseResource, header) {
const hasContentsArray = defined(header.contents);
const hasMultipleContents =
(hasContentsArray && header.contents.length > 1) ||
hasExtension(header, "3DTILES_multiple_contents");
// In the 1.0 schema, content is stored in tile.content instead of tile.contents
const contentHeader =
hasContentsArray && !hasMultipleContents
? header.contents[0]
: header.content;
let contentBoundingVolume;
if (defined(contentHeader) && defined(contentHeader.boundingVolume)) {
// Non-leaf tiles may have a content bounding-volume, which is a tight-fit bounding volume
// around only the features in the tile. This box is useful for culling for rendering,
// but not for culling for traversing the tree since it does not guarantee spatial coherence, i.e.,
// since it only bounds features in the tile, not the entire tile, children may be
// outside of this box.
contentBoundingVolume = tile.createBoundingVolume(
contentHeader.boundingVolume,
tile.computedTransform,
);
}
let content;
let contentState;
let contentResource;
let serverKey;
let hasEmptyContent = false;
baseResource = Resource.createIfNeeded(baseResource);
if (hasMultipleContents) {
contentState = Cesium3DTileContentState.UNLOADED;
// Each content may have its own URI, but they all need to be resolved
// relative to the tileset, so the base resource is used.
contentResource = baseResource.clone();
} else if (defined(contentHeader)) {
let contentHeaderUri = contentHeader.uri;
if (defined(contentHeader.url)) {
Cesium3DTile._deprecationWarning(
"contentUrl",
'This tileset JSON uses the "content.url" property which has been deprecated. Use "content.uri" instead.',
);
contentHeaderUri = contentHeader.url;
}
if (contentHeaderUri === "") {
Cesium3DTile._deprecationWarning(
"contentUriEmpty",
"content.uri property is an empty string, which creates a circular dependency, making this tileset invalid. Omit the content property instead",
);
content = new Empty3DTileContent(tile._tileset, tile);
hasEmptyContent = true;
contentState = Cesium3DTileContentState.READY;
} else {
contentState = Cesium3DTileContentState.UNLOADED;
contentResource = baseResource.getDerivedResource({
url: contentHeaderUri,
});
serverKey = RequestScheduler.getServerKey(
contentResource.getUrlComponent(),
);
}
} else {
content = new Empty3DTileContent(tile._tileset, tile);
hasEmptyContent = true;
contentState = Cesium3DTileContentState.READY;
}
tile._contentHeader = contentHeader;
tile._contentBoundingVolume = contentBoundingVolume;
tile._contentBoundingVolume2D = undefined;
tile._content = content;
tile._contentResource = contentResource;
tile._contentState = contentState;
tile._expiredContent = undefined;
tile._serverKey = serverKey;
tile.hasEmptyContent = hasEmptyContent;
tile.hasRenderableContent = !hasEmptyContent;
tile.hasMultipleContents = hasMultipleContents;
}
/**
* Returns the value for the 'refine' property of a tile.
*
* If the given value from the header is one of the known, deprecated
* lowercase values ("add" or "remove"), then a deprecation warning
* will be printed, and the corresponding constant will be returned.
*
* If the value is <code>undefined</code>, and the parent is not
* <code>undefined</code>, then the value from the parent will
* be inherited and returned.
*
* Otherwise, <code>REPLACE</code> is returned as the default.
*
* @param {string|undefined} headerRefine The refine value from the JSON
* @param {Cesium3DTile|undefined} parent The parent tile
* @returns {number} The <code>Cesium3DTileRefine</code> value
*/
function determineRefine(headerRefine, parent) {
// Note: This will not create a warning for strings like "RePlAcE",
// but still handle them by uppercasing them.
if (defined(headerRefine)) {
if (headerRefine === "replace" || headerRefine === "add") {
Cesium3DTile._deprecationWarning(
"lowercase-refine",
`This tile uses a lowercase refine "${
headerRefine
}". Instead use "${headerRefine.toUpperCase()}".`,
);
}
const refine =
headerRefine.toUpperCase() === "REPLACE"
? Cesium3DTileRefine.REPLACE
: Cesium3DTileRefine.ADD;
return refine;
}
if (defined(parent)) {
// Inherit from parent tile if omitted.
return parent.refine;
}
return Cesium3DTileRefine.REPLACE;
}
// This can be overridden for testing purposes
Cesium3DTile._deprecationWarning = deprecationWarning;
@ -1128,11 +1189,9 @@ Cesium3DTile.prototype.requestContent = function () {
if (this.hasEmptyContent) {
return;
}
if (this.hasMultipleContents) {
return requestMultipleContents(this);
}
return requestSingleContent(this);
};
@ -1350,52 +1409,12 @@ async function makeContent(tile, arrayBuffer) {
tile.hasRenderableContent = false;
}
let content;
const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType];
if (tile.isDestroyed()) {
return;
}
if (defined(preprocessed.binaryPayload)) {
content = await Promise.resolve(
contentFactory(
tileset,
tile,
tile._contentResource,
preprocessed.binaryPayload.buffer,
0,
),
);
} else {
// JSON formats
content = await Promise.resolve(
contentFactory(
tileset,
tile,
tile._contentResource,
preprocessed.jsonPayload,
),
);
}
const resource = tile._contentResource;
const contentHeader = tile._contentHeader;
if (tile.hasImplicitContentMetadata) {
const subtree = tile.implicitSubtree;
const coordinates = tile.implicitCoordinates;
content.metadata = subtree.getContentMetadataView(coordinates, 0);
} else if (!tile.hasImplicitContent) {
content.metadata = findContentMetadata(tileset, contentHeader);
}
const groupMetadata = findGroupMetadata(tileset, contentHeader);
if (defined(groupMetadata)) {
content.group = new Cesium3DContentGroup({
metadata: groupMetadata,
});
}
return content;
return finishContent(tile, resource, preprocessed, contentHeader, 0);
}
/**
@ -1405,8 +1424,13 @@ async function makeContent(tile, arrayBuffer) {
* @private
*/
Cesium3DTile.prototype.cancelRequests = function () {
// XXX_DYNAMIC: This actually happens sometimes, but only when the tile is
// in the "LOADING" state. Now... what do do with dynamic tiles?
console.log("Cesium3DTile.cancelRequests is called");
if (this.hasMultipleContents) {
this._content.cancelRequests();
} else if (this._content instanceof Dynamic3DTileContent) {
this._content.cancelRequests();
} else {
this._request.cancel();
}

View File

@ -302,17 +302,17 @@ Cesium3DTileContent.prototype.getFeature = function (batchId) {
};
/**
* Called when {@link Cesium3DTileset#debugColorizeTiles} changes.
* <p>
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
* not part of the public Cesium API.
* </p>
*
* @param {boolean} enabled Whether to enable or disable debug settings.
* @returns {Cesium3DTileFeature} The corresponding {@link Cesium3DTileFeature} object.
* @private
*/
* Called when {@link Cesium3DTileset#debugColorizeTiles} changes.
* <p>
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
* not part of the public Cesium API.
* </p>
*
* @param {boolean} enabled Whether to enable or disable debug settings.
* @param {Color|undefined} color The color to apply
*
* @private
*/
Cesium3DTileContent.prototype.applyDebugSettings = function (enabled, color) {
DeveloperError.throwInstantiationError();
};

View File

@ -6,6 +6,7 @@ import Tileset3DTileContent from "./Tileset3DTileContent.js";
import Vector3DTileContent from "./Vector3DTileContent.js";
import GaussianSplat3DTileContent from "./GaussianSplat3DTileContent.js";
import RuntimeError from "../Core/RuntimeError.js";
import Dynamic3DTileContent from "./Dynamic3DTileContent.js";
/**
* Maps a tile's magic field in its header to a new content object for the tile's payload.
@ -54,6 +55,9 @@ const Cesium3DTileContentFactory = {
externalTileset: function (tileset, tile, resource, json) {
return Tileset3DTileContent.fromJson(tileset, tile, resource, json);
},
dynamicContents: function (tileset, tile, resource, json) {
return Dynamic3DTileContent.fromJson(tileset, tile, resource, json);
},
geom: function (tileset, tile, resource, arrayBuffer, byteOffset) {
return new Geometry3DTileContent(
tileset,

View File

@ -113,6 +113,16 @@ const Cesium3DTileContentType = {
* @private
*/
EXTERNAL_TILESET: "externalTileset",
/**
* The content is a dynamic content, which contains an array of
* content objects with 'keys' that identify which content is
* active at a certain point in time.
*
* @type {string}
* @constant
* @private
*/
DYNAMIC_CONTENTS: "dynamicContents",
/**
* Multiple contents are handled separately from the other content types
* due to differences in request scheduling.

View File

@ -216,10 +216,82 @@ function Cesium3DTileset(options) {
this._modelUpAxis = undefined;
this._modelForwardAxis = undefined;
this._cache = new Cesium3DTilesetCache();
this._processingQueue = [];
this._selectedTiles = [];
this._emptyTiles = [];
/**
* The tiles that are 'selected' by the traversal.
*
* During the 'Cesium3DTileset.update' call, the tile traversal is
* executed. This includes the execution of the 'selectTiles'
* function of the traversal (which exists in different forms,
* depending on the traversal - but it's not really an interface,
* just different functions).
*
* The 'selectTiles' function will first clear this list of
* selected tiles, and then fill it with the tiles that are
* 'selected'.
*
* (This usually/roughly means that they are in the view frustum
* and have the right level of detail, but the details may vary)
*
* Some of these tiles may also be moved into the '_requestedTiles'
* as part of the traversal.
*/
this._selectedTiles = [];
/**
* Tiles that are 'requested' according to the traversal.
*
* This is usually a subset of the '_selectedTiles': The list
* of requested tiles is cleared at the beginning of the traversal,
* and then some tiles that are 'selected' will also be added to
* these 'requested' tiles.
*
* There is no clear definition of what a 'requested' tile is.
* It roughly means that ~"their content has to be loaded".
* The tiles are added to this list, usually in a function
* called 'loadTile', which is literally saying that the tile
* is added to this list "if appropriate".
*
* The important point is that AFTER the traversal, the
* contents of these tiles will be loaded, meaning that
* 'Cesium3DTile.requestContent' will be called for them,
* and they will be added to the '_requestedTilesInFlight'.
*
* (Once the content is loaded, the tiles will be added to
* the '_processingQueue');
*/
this._requestedTiles = [];
/**
* The tiles for which a content request is currently "in flight".
*
* This list is filled with tiles from the '_requestedTiles'
* in each frame. Tiles are removed from this list after each
* frame (when 'cancelOutOfViewRequests' is called), if their
* '_contentState' is no longer 'LOADING'.
*
* So a tile being in this list roughly means that its content
* is currently being loaded.
*/
this._requestedTilesInFlight = [];
/**
* The tiles that are currently being processed.
*
* These are the tiles that have been 'selected' and 'requested'
* and whose content was eventually obtained. Before the next
* rendering pass, these tiles will be "processed", meaning that
* their 'Cesium3DTile.process' method will be called.
*
* This mainly means that the 'Cesium3DTileContent.update' function
* of their content is called, loading data and creating WebGL
* resources and doing other random stuff, which eventually leads
* to the tile moving from the 'PROCESSING' state into the 'READY' state.
*/
this._processingQueue = [];
this._selectedTilesToStyle = [];
this._loadTimestamp = undefined;
this._timeSinceLoad = 0.0;
@ -276,8 +348,6 @@ function Cesium3DTileset(options) {
this._statisticsPerPass[i] = new Cesium3DTilesetStatistics();
}
this._requestedTilesInFlight = [];
this._maximumPriority = {
foveatedFactor: -Number.MAX_VALUE,
depth: -Number.MAX_VALUE,
@ -1077,6 +1147,17 @@ function Cesium3DTileset(options) {
instanceFeatureIdLabel = `instanceFeatureId_${instanceFeatureIdLabel}`;
}
this._instanceFeatureIdLabel = instanceFeatureIdLabel;
/**
* The function that determines which inner contents of a dynamic
* contents object are currently active.
*
* See the setter of this property for details.
*
* @type {Function|undefined}
* @private
*/
this._dynamicContentPropertyProvider = undefined;
}
Object.defineProperties(Cesium3DTileset.prototype, {
@ -2103,6 +2184,34 @@ Object.defineProperties(Cesium3DTileset.prototype, {
this._instanceFeatureIdLabel = value;
},
},
/**
* The function that provides the properties based on which inner
* contents of a dynamic content should be active.
*
* This is a function that returns a JSON plain object. This object corresponds
* to one 'key' of a dynamic content definition. It will cause the content
* with this key to be the currently active content, namely, when the
* "update" function of that content is called.
*
* @memberof Cesium3DTileset.prototype
* @readonly
* @type {Function|undefined}
* @private
*/
dynamicContentPropertyProvider: {
get: function () {
return this._dynamicContentPropertyProvider;
},
set: function (value) {
if (defined(value) && !defined(this._dynamicContentsDimensions)) {
console.log(
"This tileset does not contain the 3DTILES_dynamic extension. The given function will not have an effect.",
);
}
this._dynamicContentPropertyProvider = value;
},
},
});
/**
@ -2261,6 +2370,18 @@ Cesium3DTileset.fromUrl = async function (url, options) {
tileset._initialClippingPlanesOriginMatrix,
);
// Extract the information about the "dimensions" of the dynamic contents,
// if present.
// XXX_DYNAMIC This should probably not be done here, but ...
// maybe in the constructor or so...? The lifecycle, though...
const hasDynamicContents = hasExtension(tilesetJson, "3DTILES_dynamic");
if (hasDynamicContents) {
const dynamicContentsExtension = tilesetJson.extensions["3DTILES_dynamic"];
tileset._dynamicContentsDimensions = dynamicContentsExtension.dimensions;
} else {
tileset._dynamicContentsDimensions = undefined;
}
return tileset;
};
@ -2362,6 +2483,54 @@ Cesium3DTileset.prototype.loadTileset = function (
return rootTile;
};
/**
* XXX_DYNAMIC A draft for a convenience function for the dynamic content
* properties provider. Whether or not this should be offered depends on
* how much we want to specialize all this for single ISO8601 date strings.
* We could even omit the "timeDimensionName" if this was a fixed, specified
* string like "isoTimeStamp" or so.
*
* ---
*
* Set the function that determines which dynamic content is currently active,
* based on the ISO8601 string representation of the current time of the given
* clock.
*
* @param {string} timeDimensionName The name of the property that will
* contain the ISO8601 date string of the current time of the clock
* @param {Clock} clock The clock that provides the current time
*/
Cesium3DTileset.prototype.setDefaultTimeDynamicContentPropertyProvider =
function (timeDimensionName, clock) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("timeDimensionName", timeDimensionName);
Check.typeOf.object("clock", clock);
//>>includeEnd('debug');
const dimensions = this._dynamicContentsDimensions;
if (defined(dimensions)) {
const dimensionNames = dimensions.map((d) => d.name);
if (!dimensionNames.includes(timeDimensionName)) {
console.log(
`The time dimension name ${timeDimensionName} is not a valid dimension name. Valid dimension names are`,
dimensionNames,
);
}
}
const dynamicContentPropertyProvider = () => {
const currentTime = clock.currentTime;
if (!defined(currentTime)) {
return undefined;
}
const currentTimeString = JulianDate.toIso8601(currentTime);
return {
[timeDimensionName]: currentTimeString,
};
};
this.dynamicContentPropertyProvider = dynamicContentPropertyProvider;
};
/**
* Make a {@link Cesium3DTile} for a specific tile. If the tile's header has implicit
* tiling (3D Tiles 1.1) or uses the <code>3DTILES_implicit_tiling</code> extension,

File diff suppressed because it is too large Load Diff

View File

@ -6,12 +6,9 @@ import Request from "../Core/Request.js";
import RequestScheduler from "../Core/RequestScheduler.js";
import RequestState from "../Core/RequestState.js";
import RequestType from "../Core/RequestType.js";
import Cesium3DContentGroup from "./Cesium3DContentGroup.js";
import Cesium3DTileContentType from "./Cesium3DTileContentType.js";
import Cesium3DTileContentFactory from "./Cesium3DTileContentFactory.js";
import findContentMetadata from "./findContentMetadata.js";
import findGroupMetadata from "./findGroupMetadata.js";
import preprocess3DTileContent from "./preprocess3DTileContent.js";
import finishContent from "./finishContent.js";
/**
* A collection of contents for tiles that have multiple contents, either via the tile JSON (3D Tiles 1.1) or the <code>3DTILES_multiple_contents</code> extension.
@ -35,7 +32,11 @@ import preprocess3DTileContent from "./preprocess3DTileContent.js";
function Multiple3DTileContent(tileset, tile, tilesetResource, contentsJson) {
this._tileset = tileset;
this._tile = tile;
this._tilesetResource = tilesetResource;
// XXX_DYNAMIC Was unused... ?!
// This could be avoided by writing a COMMENT!!!
//this._tilesetResource = tilesetResource;
this._contents = [];
this._contentsCreated = false;
@ -532,8 +533,8 @@ async function createInnerContent(multipleContents, arrayBuffer, index) {
try {
const preprocessed = preprocess3DTileContent(arrayBuffer);
const tileset = multipleContents._tileset;
const resource = multipleContents._innerContentResources[index];
const contentHeader = multipleContents._innerContentHeaders[index];
const tile = multipleContents._tile;
if (preprocessed.contentType === Cesium3DTileContentType.EXTERNAL_TILESET) {
@ -546,42 +547,7 @@ async function createInnerContent(multipleContents, arrayBuffer, index) {
preprocessed.contentType === Cesium3DTileContentType.GEOMETRY ||
preprocessed.contentType === Cesium3DTileContentType.VECTOR;
let content;
const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType];
if (defined(preprocessed.binaryPayload)) {
content = await Promise.resolve(
contentFactory(
tileset,
tile,
resource,
preprocessed.binaryPayload.buffer,
0,
),
);
} else {
// JSON formats
content = await Promise.resolve(
contentFactory(tileset, tile, resource, preprocessed.jsonPayload),
);
}
const contentHeader = multipleContents._innerContentHeaders[index];
if (tile.hasImplicitContentMetadata) {
const subtree = tile.implicitSubtree;
const coordinates = tile.implicitCoordinates;
content.metadata = subtree.getContentMetadataView(coordinates, index);
} else if (!tile.hasImplicitContent) {
content.metadata = findContentMetadata(tileset, contentHeader);
}
const groupMetadata = findGroupMetadata(tileset, contentHeader);
if (defined(groupMetadata)) {
content.group = new Cesium3DContentGroup({
metadata: groupMetadata,
});
}
return content;
return finishContent(tile, resource, preprocessed, contentHeader, index);
} catch (error) {
handleInnerContentFailed(multipleContents, index, error);
}

View File

@ -0,0 +1,68 @@
import defined from "../Core/defined.js";
import Cesium3DTileContentFactory from "./Cesium3DTileContentFactory.js";
import findContentMetadata from "./findContentMetadata.js";
import findGroupMetadata from "./findGroupMetadata.js";
import Cesium3DContentGroup from "./Cesium3DContentGroup.js";
/**
* Finalize the creation of a <code>Cesium3DTileContent</code> object.
*
* This takes the information from the tile and the preprocessed content
* data that was fetched from the resource, creates the proper
* <code>Cesium3DTileContent</code> instance, and assigns the
* content- and group metadata to it.
*
* @function
*
* @param {Cesium3DTile} tile The tile that contained the content
* @param {Resource} resource The resource
* @param {PreprocessedContent} preprocessed The return value of <code>preprocess3DTileContent</code>
* @param {object} contentHeader the JSON header for a {@link Cesium3DTileContent}
* @param {number} index The content index. This is 0 for a single content, or the index of the inner content for multiple contents.
* @return {Cesium3DTileContent} The finished <code>Cesium3DTileContent</code>
* @private
*/
async function finishContent(
tile,
resource,
preprocessed,
contentHeader,
index,
) {
const tileset = tile._tileset;
const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType];
let content;
if (defined(preprocessed.binaryPayload)) {
content = await Promise.resolve(
contentFactory(
tileset,
tile,
resource,
preprocessed.binaryPayload.buffer,
0,
),
);
} else {
// JSON formats
content = await Promise.resolve(
contentFactory(tileset, tile, resource, preprocessed.jsonPayload),
);
}
if (tile.hasImplicitContentMetadata) {
const subtree = tile.implicitSubtree;
const coordinates = tile.implicitCoordinates;
content.metadata = subtree.getContentMetadataView(coordinates, index);
} else if (!tile.hasImplicitContent) {
content.metadata = findContentMetadata(tileset, contentHeader);
}
const groupMetadata = findGroupMetadata(tileset, contentHeader);
if (defined(groupMetadata)) {
content.group = new Cesium3DContentGroup({
metadata: groupMetadata,
});
}
return content;
}
export default finishContent;

View File

@ -84,6 +84,15 @@ function preprocess3DTileContent(arrayBuffer) {
};
}
if (defined(json.dynamicContents)) {
// If this is not dynamic content, someone must have
// added that 'dynamicContents' property maliciously.
return {
contentType: Cesium3DTileContentType.DYNAMIC_CONTENTS,
jsonPayload: json,
};
}
throw new RuntimeError("Invalid tile content.");
}

View File

@ -131,7 +131,9 @@ void doThreePointDepthTest(float eyeDepth, bool applyTranslate) {
}
#endif
void doDepthTest() {
// Extra manual depth testing is done to allow more control over how a billboard is occluded
// by the globe when near and far from the camera.
void doDepthTest(float globeDepth) {
float temp = v_compressed.y;
temp = temp * SHIFT_RIGHT1;
float temp2 = (temp - floor(temp)) * SHIFT_LEFT1;
@ -156,14 +158,10 @@ void doDepthTest() {
}
#endif
// Automatic depth testing of billboards is disabled (@see BillboardCollection#update).
// Instead, we do one of two types of manual depth tests (potentially in addition to the test above), depending on the camera's distance to the billboard fragment.
// If we're far away, we just compare against a flat, camera-facing depth-plane at the ellipsoid's center.
// If we're close, we compare against the globe depth texture (which includes depth from the 3D tile pass).
vec2 fragSt = gl_FragCoord.xy / czm_viewport.zw;
float globeDepth = getGlobeDepthAtCoords(fragSt);
if (globeDepth == 0.0) return; // Not on globe
if (globeDepth == 0.0) return; // Not on globe
float distanceToEllipsoidCenter = -length(czm_viewerPositionWC); // depth is negative by convention
float testDistance = (eyeDepth > -u_coarseDepthTestDistance) ? globeDepth : distanceToEllipsoidCenter;
if (eyeDepth < testDistance) {
@ -175,7 +173,10 @@ void main()
{
if (v_splitDirection < 0.0 && gl_FragCoord.x > czm_splitPosition) discard;
if (v_splitDirection > 0.0 && gl_FragCoord.x < czm_splitPosition) discard;
doDepthTest();
vec2 fragSt = gl_FragCoord.xy / czm_viewport.zw;
float globeDepth = getGlobeDepthAtCoords(fragSt);
doDepthTest(globeDepth);
vec4 color = texture(u_atlas, v_textureCoordinates);
@ -243,6 +244,18 @@ void main()
out_FragColor = color;
#ifdef LOG_DEPTH
czm_writeLogDepth();
// If we've made it here, we passed our manual depth test, above. But the automatic depth test will
// still run, and some fragments of the billboard may clip against the globe. To prevent that,
// ensure the depth value we write out is in front of the globe depth.
float depthArg = v_depthFromNearPlusOne;
if (globeDepth != 0.0) { // On the globe
float globeDepthFromNearPlusOne = (-globeDepth - czm_currentFrustum.x) + 1.0;
float nudge = max(globeDepthFromNearPlusOne * 5e-6, czm_epsilon7);
float globeOnTop = max(1.0, globeDepthFromNearPlusOne - nudge);
depthArg = min(depthArg, globeOnTop);
}
czm_writeLogDepth(depthArg);
#endif
}

File diff suppressed because it is too large Load Diff