DSDSWeb/static/js/Cesium-1.53/Source/Scene/Cesium3DTileset.js

2119 lines
87 KiB
JavaScript

define([
'../Core/ApproximateTerrainHeights',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/Check',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/deprecationWarning',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DoublyLinkedList',
'../Core/Ellipsoid',
'../Core/Event',
'../Core/JulianDate',
'../Core/ManagedArray',
'../Core/Math',
'../Core/Matrix4',
'../Core/Resource',
'../Core/RuntimeError',
'../Core/Transforms',
'../Renderer/ClearCommand',
'../Renderer/Pass',
'../ThirdParty/when',
'./Axis',
'./Cesium3DTile',
'./Cesium3DTileColorBlendMode',
'./Cesium3DTileContentState',
'./Cesium3DTileOptimizations',
'./Cesium3DTileRefine',
'./Cesium3DTilesetAsyncTraversal',
'./Cesium3DTilesetCache',
'./Cesium3DTilesetStatistics',
'./Cesium3DTilesetTraversal',
'./Cesium3DTileStyleEngine',
'./ClippingPlaneCollection',
'./LabelCollection',
'./PointCloudEyeDomeLighting',
'./PointCloudShading',
'./SceneMode',
'./ShadowMode',
'./TileBoundingRegion',
'./TileBoundingSphere',
'./TileOrientedBoundingBox'
], function(
ApproximateTerrainHeights,
Cartesian2,
Cartesian3,
Cartographic,
Check,
defaultValue,
defined,
defineProperties,
deprecationWarning,
destroyObject,
DeveloperError,
DoublyLinkedList,
Ellipsoid,
Event,
JulianDate,
ManagedArray,
CesiumMath,
Matrix4,
Resource,
RuntimeError,
Transforms,
ClearCommand,
Pass,
when,
Axis,
Cesium3DTile,
Cesium3DTileColorBlendMode,
Cesium3DTileContentState,
Cesium3DTileOptimizations,
Cesium3DTileRefine,
Cesium3DTilesetAsyncTraversal,
Cesium3DTilesetCache,
Cesium3DTilesetStatistics,
Cesium3DTilesetTraversal,
Cesium3DTileStyleEngine,
ClippingPlaneCollection,
LabelCollection,
PointCloudEyeDomeLighting,
PointCloudShading,
SceneMode,
ShadowMode,
TileBoundingRegion,
TileBoundingSphere,
TileOrientedBoundingBox) {
'use strict';
/**
* A {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification|3D Tiles tileset},
* used for streaming massive heterogeneous 3D geospatial datasets.
*
* @alias Cesium3DTileset
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Resource|String|Promise<Resource>|Promise<String>} options.url The url to a tileset JSON file.
* @param {Boolean} [options.show=true] Determines if the tileset will be shown.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the tileset's root tile.
* @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the tileset casts or receives shadows from each light source.
* @param {Number} [options.maximumScreenSpaceError=16] The maximum screen space error used to drive level of detail refinement.
* @param {Number} [options.maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset.
* @param {Boolean} [options.cullWithChildrenBounds=true] Optimization option. Whether to cull tiles using the union of their children bounding volumes.
* @param {Boolean} [options.dynamicScreenSpaceError=false] Optimization option. Reduce the screen space error for tiles that are further away from the camera.
* @param {Number} [options.dynamicScreenSpaceErrorDensity=0.00278] Density used to adjust the dynamic screen space error, similar to fog density.
* @param {Number} [options.dynamicScreenSpaceErrorFactor=4.0] A factor used to increase the computed dynamic screen space error.
* @param {Number} [options.dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height at which the density starts to falloff.
* @param {Boolean} [options.skipLevelOfDetail=true] Optimization option. Determines if level of detail skipping should be applied during the traversal.
* @param {Number} [options.baseScreenSpaceError=1024] When <code>skipLevelOfDetail</code> is <code>true</code>, the screen space error that must be reached before skipping levels of detail.
* @param {Number} [options.skipScreenSpaceErrorFactor=16] When <code>skipLevelOfDetail</code> is <code>true</code>, a multiplier defining the minimum screen space error to skip. Used in conjunction with <code>skipLevels</code> to determine which tiles to load.
* @param {Number} [options.skipLevels=1] When <code>skipLevelOfDetail</code> is <code>true</code>, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with <code>skipScreenSpaceErrorFactor</code> to determine which tiles to load.
* @param {Boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When <code>skipLevelOfDetail</code> is <code>true</code>, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded.
* @param {Boolean} [options.loadSiblings=false] When <code>skipLevelOfDetail</code> is <code>true</code>, determines whether siblings of visible tiles are always downloaded during traversal.
* @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset.
* @param {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this tileset. See {@link Cesium3DTileset#classificationType} for details about restrictions and limitations.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid determining the size and shape of the globe.
* @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting.
* @param {Cartesian2} [options.imageBasedLightingFactor=new Cartesian2(1.0, 1.0)] Scales the diffuse and specular image-based lighting from the earth, sky, atmosphere and star skybox.
* @param {Cartesian3} [options.lightColor] The color and intensity of the sunlight used to shade models.
* @param {Number} [options.luminanceAtZenith=0.5] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map.
* @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting.
* @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps.
* @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering.
* @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile.
* @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile.
* @param {Boolean} [options.debugShowContentBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile's content.
* @param {Boolean} [options.debugShowViewerRequestVolume=false] For debugging only. When true, renders the viewer request volume for each tile.
* @param {Boolean} [options.debugShowGeometricError=false] For debugging only. When true, draws labels to indicate the geometric error of each tile.
* @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile.
* @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile.
* @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile.
*
* @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0.
*
* @example
* var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({
* url : 'http://localhost:8002/tilesets/Seattle/tileset.json'
* }));
*
* @example
* // Common setting for the skipLevelOfDetail optimization
* var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({
* url : 'http://localhost:8002/tilesets/Seattle/tileset.json',
* skipLevelOfDetail : true,
* baseScreenSpaceError : 1024,
* skipScreenSpaceErrorFactor : 16,
* skipLevels : 1,
* immediatelyLoadDesiredLevelOfDetail : false,
* loadSiblings : false,
* cullWithChildrenBounds : true
* }));
*
* @example
* // Common settings for the dynamicScreenSpaceError optimization
* var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({
* url : 'http://localhost:8002/tilesets/Seattle/tileset.json',
* dynamicScreenSpaceError : true,
* dynamicScreenSpaceErrorDensity : 0.00278,
* dynamicScreenSpaceErrorFactor : 4.0,
* dynamicScreenSpaceErrorHeightFalloff : 0.25
* }));
*
* @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification|3D Tiles specification}
*/
function Cesium3DTileset(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//>>includeStart('debug', pragmas.debug);
Check.defined('options.url', options.url);
//>>includeEnd('debug');
this._url = undefined;
this._basePath = undefined;
this._root = undefined;
this._asset = undefined; // Metadata for the entire tileset
this._properties = undefined; // Metadata for per-model/point/etc properties
this._geometricError = undefined; // Geometric error when the tree is not rendered at all
this._extensionsUsed = undefined;
this._gltfUpAxis = undefined;
this._cache = new Cesium3DTilesetCache();
this._processingQueue = [];
this._selectedTiles = [];
this._emptyTiles = [];
this._requestedTiles = [];
this._selectedTilesToStyle = [];
this._loadTimestamp = undefined;
this._timeSinceLoad = 0.0;
this._updatedVisibilityFrame = 0;
this._extras = undefined;
this._cullWithChildrenBounds = defaultValue(options.cullWithChildrenBounds, true);
this._allTilesAdditive = true;
this._hasMixedContent = false;
this._backfaceCommands = new ManagedArray();
this._maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 16);
this._maximumMemoryUsage = defaultValue(options.maximumMemoryUsage, 512);
this._styleEngine = new Cesium3DTileStyleEngine();
this._modelMatrix = defined(options.modelMatrix) ? Matrix4.clone(options.modelMatrix) : Matrix4.clone(Matrix4.IDENTITY);
this._statistics = new Cesium3DTilesetStatistics();
this._statisticsLastRender = new Cesium3DTilesetStatistics();
this._statisticsLastPick = new Cesium3DTilesetStatistics();
this._statisticsLastAsync = new Cesium3DTilesetStatistics();
this._tilesLoaded = false;
this._initialTilesLoaded = false;
this._tileDebugLabels = undefined;
this._readyPromise = when.defer();
this._classificationType = options.classificationType;
this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
this._initialClippingPlanesOriginMatrix = Matrix4.IDENTITY; // Computed from the tileset JSON.
this._clippingPlanesOriginMatrix = undefined; // Combines the above with any run-time transforms.
this._clippingPlanesOriginMatrixDirty = true;
/**
* Optimization option. Whether the tileset should refine based on a dynamic screen space error. Tiles that are further
* away will be rendered with lower detail than closer tiles. This improves performance by rendering fewer
* tiles and making less requests, but may result in a slight drop in visual quality for tiles in the distance.
* The algorithm is biased towards "street views" where the camera is close to the ground plane of the tileset and looking
* at the horizon. In addition results are more accurate for tightly fitting bounding volumes like box and region.
*
* @type {Boolean}
* @default false
*/
this.dynamicScreenSpaceError = defaultValue(options.dynamicScreenSpaceError, false);
/**
* A scalar that determines the density used to adjust the dynamic screen space error, similar to {@link Fog}. Increasing this
* value has the effect of increasing the maximum screen space error for all tiles, but in a non-linear fashion.
* The error starts at 0.0 and increases exponentially until a midpoint is reached, and then approaches 1.0 asymptotically.
* This has the effect of keeping high detail in the closer tiles and lower detail in the further tiles, with all tiles
* beyond a certain distance all roughly having an error of 1.0.
* <p>
* The dynamic error is in the range [0.0, 1.0) and is multiplied by <code>dynamicScreenSpaceErrorFactor</code> to produce the
* final dynamic error. This dynamic error is then subtracted from the tile's actual screen space error.
* </p>
* <p>
* Increasing <code>dynamicScreenSpaceErrorDensity</code> has the effect of moving the error midpoint closer to the camera.
* It is analogous to moving fog closer to the camera.
* </p>
*
* @type {Number}
* @default 0.00278
*/
this.dynamicScreenSpaceErrorDensity = 0.00278;
/**
* A factor used to increase the screen space error of tiles for dynamic screen space error. As this value increases less tiles
* are requested for rendering and tiles in the distance will have lower detail. If set to zero, the feature will be disabled.
*
* @type {Number}
* @default 4.0
*/
this.dynamicScreenSpaceErrorFactor = 4.0;
/**
* A ratio of the tileset's height at which the density starts to falloff. If the camera is below this height the
* full computed density is applied, otherwise the density falls off. This has the effect of higher density at
* street level views.
* <p>
* Valid values are between 0.0 and 1.0.
* </p>
*
* @type {Number}
* @default 0.25
*/
this.dynamicScreenSpaceErrorHeightFalloff = 0.25;
this._dynamicScreenSpaceErrorComputedDensity = 0.0; // Updated based on the camera position and direction
/**
* Determines whether the tileset casts or receives shadows from each light source.
* <p>
* Enabling shadows has a performance impact. A tileset that casts shadows must be rendered twice, once from the camera and again from the light's point of view.
* </p>
* <p>
* Shadows are rendered only when {@link Viewer#shadows} is <code>true</code>.
* </p>
*
* @type {ShadowMode}
* @default ShadowMode.ENABLED
*/
this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED);
/**
* Determines if the tileset will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* Defines how per-feature colors set from the Cesium API or declarative styling blend with the source colors from
* the original feature, e.g. glTF material or per-point color in the tile.
*
* @type {Cesium3DTileColorBlendMode}
* @default Cesium3DTileColorBlendMode.HIGHLIGHT
*/
this.colorBlendMode = Cesium3DTileColorBlendMode.HIGHLIGHT;
/**
* Defines the value used to linearly interpolate between the source color and feature color when the {@link Cesium3DTileset#colorBlendMode} is <code>MIX</code>.
* A value of 0.0 results in the source color while a value of 1.0 results in the feature color, with any value in-between
* resulting in a mix of the source color and feature color.
*
* @type {Number}
* @default 0.5
*/
this.colorBlendAmount = 0.5;
/**
* Options for controlling point size based on geometric error and eye dome lighting.
* @type {PointCloudShading}
*/
this.pointCloudShading = new PointCloudShading(options.pointCloudShading);
this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting();
/**
* The event fired to indicate progress of loading new tiles. This event is fired when a new tile
* is requested, when a requested tile is finished downloading, and when a downloaded tile has been
* processed and is ready to render.
* <p>
* The number of pending tile requests, <code>numberOfPendingRequests</code>, and number of tiles
* processing, <code>numberOfTilesProcessing</code> are passed to the event listener.
* </p>
* <p>
* This event is fired at the end of the frame after the scene is rendered.
* </p>
*
* @type {Event}
* @default new Event()
*
* @example
* tileset.loadProgress.addEventListener(function(numberOfPendingRequests, numberOfTilesProcessing) {
* if ((numberOfPendingRequests === 0) && (numberOfTilesProcessing === 0)) {
* console.log('Stopped loading');
* return;
* }
*
* console.log('Loading: requests: ' + numberOfPendingRequests + ', processing: ' + numberOfTilesProcessing);
* });
*/
this.loadProgress = new Event();
/**
* The event fired to indicate that all tiles that meet the screen space error this frame are loaded. The tileset
* is completely loaded for this view.
* <p>
* This event is fired at the end of the frame after the scene is rendered.
* </p>
*
* @type {Event}
* @default new Event()
*
* @example
* tileset.allTilesLoaded.addEventListener(function() {
* console.log('All tiles are loaded');
* });
*
* @see Cesium3DTileset#tilesLoaded
*/
this.allTilesLoaded = new Event();
/**
* The event fired to indicate that all tiles that meet the screen space error this frame are loaded. This event
* is fired once when all tiles in the initial view are loaded.
* <p>
* This event is fired at the end of the frame after the scene is rendered.
* </p>
*
* @type {Event}
* @default new Event()
*
* @example
* tileset.initialTilesLoaded.addEventListener(function() {
* console.log('Initial tiles are loaded');
* });
*
* @see Cesium3DTileset#allTilesLoaded
*/
this.initialTilesLoaded = new Event();
/**
* The event fired to indicate that a tile's content was loaded.
* <p>
* The loaded {@link Cesium3DTile} is passed to the event listener.
* </p>
* <p>
* This event is fired during the tileset traversal while the frame is being rendered
* so that updates to the tile take effect in the same frame. Do not create or modify
* Cesium entities or primitives during the event listener.
* </p>
*
* @type {Event}
* @default new Event()
*
* @example
* tileset.tileLoad.addEventListener(function(tile) {
* console.log('A tile was loaded.');
* });
*/
this.tileLoad = new Event();
/**
* The event fired to indicate that a tile's content was unloaded.
* <p>
* The unloaded {@link Cesium3DTile} is passed to the event listener.
* </p>
* <p>
* This event is fired immediately before the tile's content is unloaded while the frame is being
* rendered so that the event listener has access to the tile's content. Do not create
* or modify Cesium entities or primitives during the event listener.
* </p>
*
* @type {Event}
* @default new Event()
*
* @example
* tileset.tileUnload.addEventListener(function(tile) {
* console.log('A tile was unloaded from the cache.');
* });
*
* @see Cesium3DTileset#maximumMemoryUsage
* @see Cesium3DTileset#trimLoadedTiles
*/
this.tileUnload = new Event();
/**
* The event fired to indicate that a tile's content failed to load.
* <p>
* If there are no event listeners, error messages will be logged to the console.
* </p>
* <p>
* The error object passed to the listener contains two properties:
* <ul>
* <li><code>url</code>: the url of the failed tile.</li>
* <li><code>message</code>: the error message.</li>
* </ul>
*
* @type {Event}
* @default new Event()
*
* @example
* tileset.tileFailed.addEventListener(function(error) {
* console.log('An error occurred loading tile: ' + error.url);
* console.log('Error: ' + error.message);
* });
*/
this.tileFailed = new Event();
/**
* This event fires once for each visible tile in a frame. This can be used to manually
* style a tileset.
* <p>
* The visible {@link Cesium3DTile} is passed to the event listener.
* </p>
* <p>
* This event is fired during the tileset traversal while the frame is being rendered
* so that updates to the tile take effect in the same frame. Do not create or modify
* Cesium entities or primitives during the event listener.
* </p>
*
* @type {Event}
* @default new Event()
*
* @example
* tileset.tileVisible.addEventListener(function(tile) {
* if (tile.content instanceof Cesium.Batched3DModel3DTileContent) {
* console.log('A Batched 3D Model tile is visible.');
* }
* });
*
* @example
* // Apply a red style and then manually set random colors for every other feature when the tile becomes visible.
* tileset.style = new Cesium.Cesium3DTileStyle({
* color : 'color("red")'
* });
* tileset.tileVisible.addEventListener(function(tile) {
* var content = tile.content;
* var featuresLength = content.featuresLength;
* for (var i = 0; i < featuresLength; i+=2) {
* content.getFeature(i).color = Cesium.Color.fromRandom();
* }
* });
*/
this.tileVisible = new Event();
/**
* Optimization option. Determines if level of detail skipping should be applied during the traversal.
* <p>
* The common strategy for replacement-refinement traversal is to store all levels of the tree in memory and require
* all children to be loaded before the parent can refine. With this optimization levels of the tree can be skipped
* entirely and children can be rendered alongside their parents. The tileset requires significantly less memory when
* using this optimization.
* </p>
*
* @type {Boolean}
* @default true
*/
this.skipLevelOfDetail = defaultValue(options.skipLevelOfDetail, true);
this._skipLevelOfDetail = this.skipLevelOfDetail;
this._disableSkipLevelOfDetail = false;
/**
* The screen space error that must be reached before skipping levels of detail.
* <p>
* Only used when {@link Cesium3DTileset#skipLevelOfDetail} is <code>true</code>.
* </p>
*
* @type {Number}
* @default 1024
*/
this.baseScreenSpaceError = defaultValue(options.baseScreenSpaceError, 1024);
/**
* Multiplier defining the minimum screen space error to skip.
* For example, if a tile has screen space error of 100, no tiles will be loaded unless they
* are leaves or have a screen space error <code><= 100 / skipScreenSpaceErrorFactor</code>.
* <p>
* Only used when {@link Cesium3DTileset#skipLevelOfDetail} is <code>true</code>.
* </p>
*
* @type {Number}
* @default 16
*/
this.skipScreenSpaceErrorFactor = defaultValue(options.skipScreenSpaceErrorFactor, 16);
/**
* Constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped.
* For example, if a tile is level 1, no tiles will be loaded unless they are at level greater than 2.
* <p>
* Only used when {@link Cesium3DTileset#skipLevelOfDetail} is <code>true</code>.
* </p>
*
* @type {Number}
* @default 1
*/
this.skipLevels = defaultValue(options.skipLevels, 1);
/**
* When true, only tiles that meet the maximum screen space error will ever be downloaded.
* Skipping factors are ignored and just the desired tiles are loaded.
* <p>
* Only used when {@link Cesium3DTileset#skipLevelOfDetail} is <code>true</code>.
* </p>
*
* @type {Boolean}
* @default false
*/
this.immediatelyLoadDesiredLevelOfDetail = defaultValue(options.immediatelyLoadDesiredLevelOfDetail, false);
/**
* Determines whether siblings of visible tiles are always downloaded during traversal.
* This may be useful for ensuring that tiles are already available when the viewer turns left/right.
* <p>
* Only used when {@link Cesium3DTileset#skipLevelOfDetail} is <code>true</code>.
* </p>
*
* @type {Boolean}
* @default false
*/
this.loadSiblings = defaultValue(options.loadSiblings, false);
this._clippingPlanes = undefined;
this.clippingPlanes = options.clippingPlanes;
this._imageBasedLightingFactor = new Cartesian2(1.0, 1.0);
Cartesian2.clone(options.imageBasedLightingFactor, this._imageBasedLightingFactor);
/**
* The color and intensity of the sunlight used to shade a model.
* <p>
* For example, disabling additional light sources by setting <code>model.imageBasedLightingFactor = new Cartesian2(0.0, 0.0)</code> will make the
* model much darker. Here, increasing the intensity of the light source will make the model brighter.
* </p>
*
* @type {Cartesian3}
* @default undefined
*/
this.lightColor = options.lightColor;
/**
* The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map.
* This is used when {@link Cesium3DTileset#specularEnvironmentMaps} and {@link Cesium3DTileset#sphericalHarmonicCoefficients} are not defined.
*
* @type Number
*
* @default 0.5
*
*/
this.luminanceAtZenith = defaultValue(options.luminanceAtZenith, 0.5);
/**
* The third order spherical harmonic coefficients used for the diffuse color of image-based lighting. When <code>undefined</code>, a diffuse irradiance
* computed from the atmosphere color is used.
* <p>
* There are nine <code>Cartesian3</code> coefficients.
* The order of the coefficients is: L<sub>00</sub>, L<sub>1-1</sub>, L<sub>10</sub>, L<sub>11</sub>, L<sub>2-2</sub>, L<sub>2-1</sub>, L<sub>20</sub>, L<sub>21</sub>, L<sub>22</sub>
* </p>
*
* These values can be obtained by preprocessing the environment map using the <code>cmgen</code> tool of
* {@link https://github.com/google/filament/releases | Google's Filament project}. This will also generate a KTX file that can be
* supplied to {@link Cesium3DTileset#specularEnvironmentMaps}.
*
* @type {Cartesian3[]}
* @demo {@link https://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Image-Based Lighting.html|Sandcastle Image Based Lighting Demo}
* @see {@link https://graphics.stanford.edu/papers/envmap/envmap.pdf|An Efficient Representation for Irradiance Environment Maps}
*/
this.sphericalHarmonicCoefficients = options.sphericalHarmonicCoefficients;
/**
* A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps.
*
* @demo {@link https://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Image-Based Lighting.html|Sandcastle Image Based Lighting Demo}
* @type {String}
* @see Cesium3DTileset#sphericalHarmonicCoefficients
*/
this.specularEnvironmentMaps = options.specularEnvironmentMaps;
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* Determines if only the tiles from last frame should be used for rendering. This
* effectively "freezes" the tileset to the previous frame so it is possible to zoom
* out and see what was rendered.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugFreezeFrame = defaultValue(options.debugFreezeFrame, false);
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, assigns a random color to each tile. This is useful for visualizing
* what features belong to what tiles, especially with additive refinement where features
* from parent tiles may be interleaved with features from child tiles.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugColorizeTiles = defaultValue(options.debugColorizeTiles, false);
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, renders each tile's content as a wireframe.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugWireframe = defaultValue(options.debugWireframe, false);
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, renders the bounding volume for each visible tile. The bounding volume is
* white if the tile has a content bounding volume or is empty; otherwise, it is red. Tiles that don't meet the
* screen space error and are still refining to their descendants are yellow.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, renders the bounding volume for each visible tile's content. The bounding volume is
* blue if the tile has a content bounding volume; otherwise it is red.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugShowContentBoundingVolume = defaultValue(options.debugShowContentBoundingVolume, false);
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, renders the viewer request volume for each tile.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugShowViewerRequestVolume = defaultValue(options.debugShowViewerRequestVolume, false);
this._tileDebugLabels = undefined;
this.debugPickedTileLabelOnly = false;
this.debugPickedTile = undefined;
this.debugPickPosition = undefined;
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, draws labels to indicate the geometric error of each tile.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugShowGeometricError = defaultValue(options.debugShowGeometricError, false);
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, draws labels to indicate the number of commands, points, triangles and features of each tile.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugShowRenderingStatistics = defaultValue(options.debugShowRenderingStatistics, false);
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, draws labels to indicate the geometry and texture memory usage of each tile.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugShowMemoryUsage = defaultValue(options.debugShowMemoryUsage, false);
/**
* This property is for debugging only; it is not optimized for production use.
* <p>
* When true, draws labels to indicate the url of each tile.
* </p>
*
* @type {Boolean}
* @default false
*/
this.debugShowUrl = defaultValue(options.debugShowUrl, false);
this._credits = undefined;
var that = this;
var resource;
when(options.url)
.then(function(url) {
var basePath;
resource = Resource.createIfNeeded(url);
// ion resources have a credits property we can use for additional attribution.
that._credits = resource.credits;
if (resource.extension === 'json') {
basePath = resource.getBaseUri(true);
} else if (resource.isDataUri) {
basePath = '';
}
that._url = resource.url;
that._basePath = basePath;
return Cesium3DTileset.loadJson(resource);
})
.then(function(tilesetJson) {
that._root = that.loadTileset(resource, tilesetJson);
var gltfUpAxis = defined(tilesetJson.asset.gltfUpAxis) ? Axis.fromName(tilesetJson.asset.gltfUpAxis) : Axis.Y;
that._asset = tilesetJson.asset;
that._properties = tilesetJson.properties;
that._geometricError = tilesetJson.geometricError;
that._extensionsUsed = tilesetJson.extensionsUsed;
that._gltfUpAxis = gltfUpAxis;
that._extras = tilesetJson.extras;
// Save the original, untransformed bounding volume position so we can apply
// the tile transform and model matrix at run time
var boundingVolume = that._root.createBoundingVolume(tilesetJson.root.boundingVolume, Matrix4.IDENTITY);
var clippingPlanesOrigin = boundingVolume.boundingSphere.center;
// If this origin is above the surface of the earth
// we want to apply an ENU orientation as our best guess of orientation.
// Otherwise, we assume it gets its position/orientation completely from the
// root tile transform and the tileset's model matrix
var originCartographic = that._ellipsoid.cartesianToCartographic(clippingPlanesOrigin);
if (defined(originCartographic) && (originCartographic.height > ApproximateTerrainHeights._defaultMinTerrainHeight)) {
that._initialClippingPlanesOriginMatrix = Transforms.eastNorthUpToFixedFrame(clippingPlanesOrigin);
}
that._clippingPlanesOriginMatrix = Matrix4.clone(that._initialClippingPlanesOriginMatrix);
that._readyPromise.resolve(that);
}).otherwise(function(error) {
that._readyPromise.reject(error);
});
}
defineProperties(Cesium3DTileset.prototype, {
/**
* Gets the tileset's asset object property, which contains metadata about the tileset.
* <p>
* See the {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification#reference-asset|asset schema reference}
* in the 3D Tiles spec for the full set of properties.
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {Object}
* @readonly
*
* @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.
*/
asset : {
get : function() {
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError('The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.');
}
//>>includeEnd('debug');
return this._asset;
}
},
/**
* The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset.
*
* @memberof Cesium3DTileset.prototype
*
* @type {ClippingPlaneCollection}
*/
clippingPlanes : {
get : function() {
return this._clippingPlanes;
},
set : function(value) {
ClippingPlaneCollection.setOwner(value, this, '_clippingPlanes');
}
},
/**
* Gets the tileset's properties dictionary object, which contains metadata about per-feature properties.
* <p>
* See the {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification#reference-properties|properties schema reference}
* in the 3D Tiles spec for the full set of properties.
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {Object}
* @readonly
*
* @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.
*
* @example
* console.log('Maximum building height: ' + tileset.properties.height.maximum);
* console.log('Minimum building height: ' + tileset.properties.height.minimum);
*
* @see Cesium3DTileFeature#getProperty
* @see Cesium3DTileFeature#setProperty
*/
properties : {
get : function() {
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError('The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.');
}
//>>includeEnd('debug');
return this._properties;
}
},
/**
* When <code>true</code>, the tileset's root tile is loaded and the tileset is ready to render.
* This is set to <code>true</code> right before {@link Cesium3DTileset#readyPromise} is resolved.
*
* @memberof Cesium3DTileset.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
ready : {
get : function() {
return defined(this._root);
}
},
/**
* Gets the promise that will be resolved when the tileset's root tile is loaded and the tileset is ready to render.
* <p>
* This promise is resolved at the end of the frame before the first frame the tileset is rendered in.
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {Promise.<Cesium3DTileset>}
* @readonly
*
* @example
* tileset.readyPromise.then(function(tileset) {
* // tile.properties is not defined until readyPromise resolves.
* var properties = tileset.properties;
* if (Cesium.defined(properties)) {
* for (var name in properties) {
* console.log(properties[name]);
* }
* }
* });
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* When <code>true</code>, all tiles that meet the screen space error this frame are loaded. The tileset is
* completely loaded for this view.
*
* @memberof Cesium3DTileset.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*
* @see Cesium3DTileset#allTilesLoaded
*/
tilesLoaded : {
get : function() {
return this._tilesLoaded;
}
},
/**
* The url to a tileset JSON file.
*
* @memberof Cesium3DTileset.prototype
*
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* The base path that non-absolute paths in tileset JSON file are relative to.
*
* @memberof Cesium3DTileset.prototype
*
* @type {String}
* @readonly
* @deprecated
*/
basePath : {
get : function() {
deprecationWarning('Cesium3DTileset.basePath', 'Cesium3DTileset.basePath has been deprecated. All tiles are relative to the url of the tileset JSON file that contains them. Use the url property instead.');
return this._basePath;
}
},
/**
* The style, defined using the
* {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification/Styling|3D Tiles Styling language},
* applied to each feature in the tileset.
* <p>
* Assign <code>undefined</code> to remove the style, which will restore the visual
* appearance of the tileset to its default when no style was applied.
* </p>
* <p>
* The style is applied to a tile before the {@link Cesium3DTileset#tileVisible}
* event is raised, so code in <code>tileVisible</code> can manually set a feature's
* properties (e.g. color and show) after the style is applied. When
* a new style is assigned any manually set properties are overwritten.
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {Cesium3DTileStyle}
*
* @default undefined
*
* @example
* tileset.style = new Cesium.Cesium3DTileStyle({
* color : {
* conditions : [
* ['${Height} >= 100', 'color("purple", 0.5)'],
* ['${Height} >= 50', 'color("red")'],
* ['true', 'color("blue")']
* ]
* },
* show : '${Height} > 0',
* meta : {
* description : '"Building id ${id} has height ${Height}."'
* }
* });
*
* @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification/Styling|3D Tiles Styling language}
*/
style : {
get : function() {
return this._styleEngine.style;
},
set : function(value) {
this._styleEngine.style = value;
}
},
/**
* The maximum screen space error used to drive level of detail refinement. This value helps determine when a tile
* refines to its descendants, and therefore plays a major role in balancing performance with visual quality.
* <p>
* A tile's screen space error is roughly equivalent to the number of pixels wide that would be drawn if a sphere with a
* radius equal to the tile's <b>geometric error</b> were rendered at the tile's position. If this value exceeds
* <code>maximumScreenSpaceError</code> the tile refines to its descendants.
* </p>
* <p>
* Depending on the tileset, <code>maximumScreenSpaceError</code> may need to be tweaked to achieve the right balance.
* Higher values provide better performance but lower visual quality.
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {Number}
* @default 16
*
* @exception {DeveloperError} <code>maximumScreenSpaceError</code> must be greater than or equal to zero.
*/
maximumScreenSpaceError : {
get : function() {
return this._maximumScreenSpaceError;
},
set : function(value) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals('maximumScreenSpaceError', value, 0);
//>>includeEnd('debug');
this._maximumScreenSpaceError = value;
}
},
/**
* The maximum amount of GPU memory (in MB) that may be used to cache tiles. This value is estimated from
* geometry, textures, and batch table textures of loaded tiles. For point clouds, this value also
* includes per-point metadata.
* <p>
* Tiles not in view are unloaded to enforce this.
* </p>
* <p>
* If decreasing this value results in unloading tiles, the tiles are unloaded the next frame.
* </p>
* <p>
* If tiles sized more than <code>maximumMemoryUsage</code> are needed
* to meet the desired screen space error, determined by {@link Cesium3DTileset#maximumScreenSpaceError},
* for the current view, then the memory usage of the tiles loaded will exceed
* <code>maximumMemoryUsage</code>. For example, if the maximum is 256 MB, but
* 300 MB of tiles are needed to meet the screen space error, then 300 MB of tiles may be loaded. When
* these tiles go out of view, they will be unloaded.
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {Number}
* @default 512
*
* @exception {DeveloperError} <code>maximumMemoryUsage</code> must be greater than or equal to zero.
* @see Cesium3DTileset#totalMemoryUsageInBytes
*/
maximumMemoryUsage : {
get : function() {
return this._maximumMemoryUsage;
},
set : function(value) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals('value', value, 0);
//>>includeEnd('debug');
this._maximumMemoryUsage = value;
}
},
/**
* The root tile.
*
* @memberOf Cesium3DTileset.prototype
*
* @type {Cesium3DTile}
* @readonly
*
* @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.
*/
root : {
get : function() {
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError('The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.');
}
//>>includeEnd('debug');
return this._root;
}
},
/**
* The tileset's bounding sphere.
*
* @memberof Cesium3DTileset.prototype
*
* @type {BoundingSphere}
* @readonly
*
* @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.
*
* @example
* var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
* url : 'http://localhost:8002/tilesets/Seattle/tileset.json'
* }));
*
* tileset.readyPromise.then(function(tileset) {
* // Set the camera to view the newly added tileset
* viewer.camera.viewBoundingSphere(tileset.boundingSphere, new Cesium.HeadingPitchRange(0, -0.5, 0));
* });
*/
boundingSphere : {
get : function() {
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError('The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.');
}
//>>includeEnd('debug');
this._root.updateTransform(this._modelMatrix);
return this._root.boundingSphere;
}
},
/**
* A 4x4 transformation matrix that transforms the entire tileset.
*
* @memberof Cesium3DTileset.prototype
*
* @type {Matrix4}
* @default Matrix4.IDENTITY
*
* @example
* // Adjust a tileset's height from the globe's surface.
* var heightOffset = 20.0;
* var boundingSphere = tileset.boundingSphere;
* var cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
* var surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0);
* var offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset);
* var translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
* tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
*/
modelMatrix : {
get : function() {
return this._modelMatrix;
},
set : function(value) {
this._modelMatrix = Matrix4.clone(value, this._modelMatrix);
}
},
/**
* Returns the time, in milliseconds, since the tileset was loaded and first updated.
*
* @memberof Cesium3DTileset.prototype
*
* @type {Number}
* @readonly
*/
timeSinceLoad : {
get : function() {
return this._timeSinceLoad;
}
},
/**
* The total amount of GPU memory in bytes used by the tileset. This value is estimated from
* geometry, texture, and batch table textures of loaded tiles. For point clouds, this value also
* includes per-point metadata.
*
* @memberof Cesium3DTileset.prototype
*
* @type {Number}
* @readonly
*
* @see Cesium3DTileset#maximumMemoryUsage
*/
totalMemoryUsageInBytes : {
get : function() {
var statistics = this._statistics;
return statistics.texturesByteLength + statistics.geometryByteLength + statistics.batchTableByteLength;
}
},
/**
* @private
*/
clippingPlanesOriginMatrix : {
get : function() {
if (!defined(this._clippingPlanesOriginMatrix)) {
return Matrix4.IDENTITY;
}
if (this._clippingPlanesOriginMatrixDirty) {
Matrix4.multiply(this.root.computedTransform, this._initialClippingPlanesOriginMatrix, this._clippingPlanesOriginMatrix);
this._clippingPlanesOriginMatrixDirty = false;
}
return this._clippingPlanesOriginMatrix;
}
},
/**
* @private
*/
styleEngine : {
get : function() {
return this._styleEngine;
}
},
/**
* @private
*/
statistics : {
get : function() {
return this._statistics;
}
},
/**
* Determines whether terrain, 3D Tiles or both will be classified by this tileset.
* <p>
* This option is only applied to tilesets containing batched 3D models, geometry data, or vector data. Even when undefined, vector data and geometry data
* must render as classifications and will default to rendering on both terrain and other 3D Tiles tilesets.
* </p>
* <p>
* When enabled for batched 3D model tilesets, there are a few requirements/limitations on the glTF:
* <ul>
* <li>POSITION and _BATCHID semantics are required.</li>
* <li>All indices with the same batch id must occupy contiguous sections of the index buffer.</li>
* <li>All shaders and techniques are ignored. The generated shader simply multiplies the position by the model-view-projection matrix.</li>
* <li>The only supported extensions are CESIUM_RTC and WEB3D_quantized_attributes.</li>
* <li>Only one node is supported.</li>
* <li>Only one mesh per node is supported.</li>
* <li>Only one primitive per mesh is supported.</li>
* </ul>
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {ClassificationType}
* @default undefined
*
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
* @readonly
*/
classificationType : {
get : function() {
return this._classificationType;
}
},
/**
* Gets an ellipsoid describing the shape of the globe.
*
* @memberof Cesium3DTileset.prototype
*
* @type {Ellipsoid}
* @readonly
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
},
/**
* Returns the <code>extras</code> property at the top-level of the tileset JSON, which contains application specific metadata.
* Returns <code>undefined</code> if <code>extras</code> does not exist.
*
* @memberof Cesium3DTileset.prototype
*
* @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.
*
* @type {*}
* @readonly
*
* @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification#specifying-extensions-and-application-specific-extras|Extras in the 3D Tiles specification.}
*/
extras : {
get : function() {
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError('The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true.');
}
//>>includeEnd('debug');
return this._extras;
}
},
/**
* Cesium adds lighting from the earth, sky, atmosphere, and star skybox. This cartesian is used to scale the final
* diffuse and specular lighting contribution from those sources to the final color. A value of 0.0 will disable those light sources.
*
* @type {Cartesian2}
* @default Cartesian2(1.0, 1.0)
*/
imageBasedLightingFactor : {
get : function() {
return this._imageBasedLightingFactor;
},
set : function(value) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object('imageBasedLightingFactor', value);
Check.typeOf.number.greaterThanOrEquals('imageBasedLightingFactor.x', value.x, 0.0);
Check.typeOf.number.lessThanOrEquals('imageBasedLightingFactor.x', value.x, 1.0);
Check.typeOf.number.greaterThanOrEquals('imageBasedLightingFactor.y', value.y, 0.0);
Check.typeOf.number.lessThanOrEquals('imageBasedLightingFactor.y', value.y, 1.0);
//>>includeEnd('debug');
Cartesian2.clone(value, this._imageBasedLightingFactor);
}
}
});
/**
* Provides a hook to override the method used to request the tileset json
* useful when fetching tilesets from remote servers
* @param {Resource|String} tilesetUrl The url of the json file to be fetched
* @returns {Promise.<Object>} A promise that resolves with the fetched json data
*/
Cesium3DTileset.loadJson = function(tilesetUrl) {
var resource = Resource.createIfNeeded(tilesetUrl);
return resource.fetchJson();
};
/**
* Marks the tileset's {@link Cesium3DTileset#style} as dirty, which forces all
* features to re-evaluate the style in the next frame each is visible.
*/
Cesium3DTileset.prototype.makeStyleDirty = function() {
this._styleEngine.makeDirty();
};
/**
* Loads the main tileset JSON file or a tileset JSON file referenced from a tile.
*
* @private
*/
Cesium3DTileset.prototype.loadTileset = function(resource, tilesetJson, parentTile) {
var asset = tilesetJson.asset;
if (!defined(asset)) {
throw new RuntimeError('Tileset must have an asset property.');
}
if (asset.version !== '0.0' && asset.version !== '1.0') {
throw new RuntimeError('The tileset must be 3D Tiles version 0.0 or 1.0.');
}
var statistics = this._statistics;
var tilesetVersion = asset.tilesetVersion;
if (defined(tilesetVersion)) {
// Append the tileset version to the resource
this._basePath += '?v=' + tilesetVersion;
resource.setQueryParameters({ v: tilesetVersion });
} else {
delete resource.queryParameters.v;
}
// A tileset JSON file referenced from a tile may exist in a different directory than the root tileset.
// Get the basePath relative to the external tileset.
var rootTile = new Cesium3DTile(this, resource, tilesetJson.root, parentTile);
// If there is a parentTile, add the root of the currently loading tileset
// to parentTile's children, and update its _depth.
if (defined(parentTile)) {
parentTile.children.push(rootTile);
rootTile._depth = parentTile._depth + 1;
}
var stack = [];
stack.push(rootTile);
while (stack.length > 0) {
var tile = stack.pop();
++statistics.numberOfTilesTotal;
this._allTilesAdditive = this._allTilesAdditive && (tile.refine === Cesium3DTileRefine.ADD);
var children = tile._header.children;
if (defined(children)) {
var length = children.length;
for (var i = 0; i < length; ++i) {
var childHeader = children[i];
var childTile = new Cesium3DTile(this, resource, childHeader, tile);
tile.children.push(childTile);
childTile._depth = tile._depth + 1;
stack.push(childTile);
}
}
if (this._cullWithChildrenBounds) {
Cesium3DTileOptimizations.checkChildrenWithinParent(tile);
}
}
return rootTile;
};
var scratchPositionNormal = new Cartesian3();
var scratchCartographic = new Cartographic();
var scratchMatrix = new Matrix4();
var scratchCenter = new Cartesian3();
var scratchPosition = new Cartesian3();
var scratchDirection = new Cartesian3();
function updateDynamicScreenSpaceError(tileset, frameState) {
var up;
var direction;
var height;
var minimumHeight;
var maximumHeight;
var camera = frameState.camera;
var root = tileset._root;
var tileBoundingVolume = root.contentBoundingVolume;
if (tileBoundingVolume instanceof TileBoundingRegion) {
up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
direction = camera.directionWC;
height = camera.positionCartographic.height;
minimumHeight = tileBoundingVolume.minimumHeight;
maximumHeight = tileBoundingVolume.maximumHeight;
} else {
// Transform camera position and direction into the local coordinate system of the tileset
var transformLocal = Matrix4.inverseTransformation(root.computedTransform, scratchMatrix);
var ellipsoid = frameState.mapProjection.ellipsoid;
var boundingVolume = tileBoundingVolume.boundingVolume;
var centerLocal = Matrix4.multiplyByPoint(transformLocal, boundingVolume.center, scratchCenter);
if (Cartesian3.magnitude(centerLocal) > ellipsoid.minimumRadius) {
// The tileset is defined in WGS84. Approximate the minimum and maximum height.
var centerCartographic = Cartographic.fromCartesian(centerLocal, ellipsoid, scratchCartographic);
up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
direction = camera.directionWC;
height = camera.positionCartographic.height;
minimumHeight = 0.0;
maximumHeight = centerCartographic.height * 2.0;
} else {
// The tileset is defined in local coordinates (z-up)
var positionLocal = Matrix4.multiplyByPoint(transformLocal, camera.positionWC, scratchPosition);
up = Cartesian3.UNIT_Z;
direction = Matrix4.multiplyByPointAsVector(transformLocal, camera.directionWC, scratchDirection);
direction = Cartesian3.normalize(direction, direction);
height = positionLocal.z;
if (tileBoundingVolume instanceof TileOrientedBoundingBox) {
// Assuming z-up, the last component stores the half-height of the box
var boxHeight = root._header.boundingVolume.box[11];
minimumHeight = centerLocal.z - boxHeight;
maximumHeight = centerLocal.z + boxHeight;
} else if (tileBoundingVolume instanceof TileBoundingSphere) {
var radius = boundingVolume.radius;
minimumHeight = centerLocal.z - radius;
maximumHeight = centerLocal.z + radius;
}
}
}
// The range where the density starts to lessen. Start at the quarter height of the tileset.
var heightFalloff = tileset.dynamicScreenSpaceErrorHeightFalloff;
var heightClose = minimumHeight + (maximumHeight - minimumHeight) * heightFalloff;
var heightFar = maximumHeight;
var t = CesiumMath.clamp((height - heightClose) / (heightFar - heightClose), 0.0, 1.0);
// Increase density as the camera tilts towards the horizon
var dot = Math.abs(Cartesian3.dot(direction, up));
var horizonFactor = 1.0 - dot;
// Weaken the horizon factor as the camera height increases, implying the camera is further away from the tileset.
// The goal is to increase density for the "street view", not when viewing the tileset from a distance.
horizonFactor = horizonFactor * (1.0 - t);
var density = tileset.dynamicScreenSpaceErrorDensity;
density *= horizonFactor;
tileset._dynamicScreenSpaceErrorComputedDensity = density;
}
///////////////////////////////////////////////////////////////////////////
function requestContent(tileset, tile) {
if (tile.hasEmptyContent) {
return;
}
var statistics = tileset._statistics;
var expired = tile.contentExpired;
var requested = tile.requestContent();
if (!requested) {
++statistics.numberOfAttemptedRequests;
return;
}
if (expired) {
if (tile.hasTilesetContent) {
destroySubtree(tileset, tile);
} else {
statistics.decrementLoadCounts(tile.content);
--statistics.numberOfTilesWithContentReady;
}
}
++statistics.numberOfPendingRequests;
tile.contentReadyToProcessPromise.then(addToProcessingQueue(tileset, tile));
tile.contentReadyPromise.then(handleTileSuccess(tileset, tile)).otherwise(handleTileFailure(tileset, tile));
}
function sortRequestByPriority(a, b) {
return a._priority - b._priority;
}
function requestTiles(tileset) {
// Sort requests by priority before making any requests.
// This makes it less likely that requests will be cancelled after being issued.
var requestedTiles = tileset._requestedTiles;
var length = requestedTiles.length;
requestedTiles.sort(sortRequestByPriority);
for (var i = 0; i < length; ++i) {
requestContent(tileset, requestedTiles[i]);
}
}
function addToProcessingQueue(tileset, tile) {
return function() {
tileset._processingQueue.push(tile);
--tileset._statistics.numberOfPendingRequests;
++tileset._statistics.numberOfTilesProcessing;
};
}
function handleTileFailure(tileset, tile) {
return function(error) {
if (tileset._processingQueue.indexOf(tile) >= 0) {
// Failed during processing
--tileset._statistics.numberOfTilesProcessing;
} else {
// Failed when making request
--tileset._statistics.numberOfPendingRequests;
}
var url = tile._contentResource.url;
var message = defined(error.message) ? error.message : error.toString();
if (tileset.tileFailed.numberOfListeners > 0) {
tileset.tileFailed.raiseEvent({
url : url,
message : message
});
} else {
console.log('A 3D tile failed to load: ' + url);
console.log('Error: ' + message);
}
};
}
function handleTileSuccess(tileset, tile) {
return function() {
--tileset._statistics.numberOfTilesProcessing;
if (!tile.hasTilesetContent) {
// RESEARCH_IDEA: ability to unload tiles (without content) for an
// external tileset when all the tiles are unloaded.
tileset._statistics.incrementLoadCounts(tile.content);
++tileset._statistics.numberOfTilesWithContentReady;
// Add to the tile cache. Previously expired tiles are already in the cache and won't get re-added.
tileset._cache.add(tile);
}
tileset.tileLoad.raiseEvent(tile);
};
}
function filterProcessingQueue(tileset) {
var tiles = tileset._processingQueue;
var length = tiles.length;
var removeCount = 0;
for (var i = 0; i < length; ++i) {
var tile = tiles[i];
if (tile._contentState !== Cesium3DTileContentState.PROCESSING) {
++removeCount;
continue;
}
if (removeCount > 0) {
tiles[i - removeCount] = tile;
}
}
tiles.length -= removeCount;
}
function processTiles(tileset, frameState) {
filterProcessingQueue(tileset);
var tiles = tileset._processingQueue;
var length = tiles.length;
// Process tiles in the PROCESSING state so they will eventually move to the READY state.
for (var i = 0; i < length; ++i) {
tiles[i].process(tileset, frameState);
}
}
///////////////////////////////////////////////////////////////////////////
var scratchCartesian = new Cartesian3();
var stringOptions = {
maximumFractionDigits : 3
};
function formatMemoryString(memorySizeInBytes) {
var memoryInMegabytes = memorySizeInBytes / 1048576;
if (memoryInMegabytes < 1.0) {
return memoryInMegabytes.toLocaleString(undefined, stringOptions);
}
return Math.round(memoryInMegabytes).toLocaleString();
}
function computeTileLabelPosition(tile) {
var boundingVolume = tile.boundingVolume.boundingVolume;
var halfAxes = boundingVolume.halfAxes;
var radius = boundingVolume.radius;
var position = Cartesian3.clone(boundingVolume.center, scratchCartesian);
if (defined(halfAxes)) {
position.x += 0.75 * (halfAxes[0] + halfAxes[3] + halfAxes[6]);
position.y += 0.75 * (halfAxes[1] + halfAxes[4] + halfAxes[7]);
position.z += 0.75 * (halfAxes[2] + halfAxes[5] + halfAxes[8]);
} else if (defined(radius)) {
var normal = Cartesian3.normalize(boundingVolume.center, scratchCartesian);
normal = Cartesian3.multiplyByScalar(normal, 0.75 * radius, scratchCartesian);
position = Cartesian3.add(normal, boundingVolume.center, scratchCartesian);
}
return position;
}
function addTileDebugLabel(tile, tileset, position) {
var labelString = '';
var attributes = 0;
if (tileset.debugShowGeometricError) {
labelString += '\nGeometric error: ' + tile.geometricError;
attributes++;
}
if (tileset.debugShowRenderingStatistics) {
labelString += '\nCommands: ' + tile.commandsLength;
attributes++;
// Don't display number of points or triangles if 0.
var numberOfPoints = tile.content.pointsLength;
if (numberOfPoints > 0) {
labelString += '\nPoints: ' + tile.content.pointsLength;
attributes++;
}
var numberOfTriangles = tile.content.trianglesLength;
if (numberOfTriangles > 0) {
labelString += '\nTriangles: ' + tile.content.trianglesLength;
attributes++;
}
labelString += '\nFeatures: ' + tile.content.featuresLength;
attributes ++;
}
if (tileset.debugShowMemoryUsage) {
labelString += '\nTexture Memory: ' + formatMemoryString(tile.content.texturesByteLength);
labelString += '\nGeometry Memory: ' + formatMemoryString(tile.content.geometryByteLength);
attributes += 2;
}
if (tileset.debugShowUrl) {
labelString += '\nUrl: ' + tile._header.content.uri;
attributes++;
}
var newLabel = {
text : labelString.substring(1),
position : position,
font : (19-attributes) + 'px sans-serif',
showBackground : true,
disableDepthTestDistance : Number.POSITIVE_INFINITY
};
return tileset._tileDebugLabels.add(newLabel);
}
function updateTileDebugLabels(tileset, frameState) {
var i;
var tile;
var selectedTiles = tileset._selectedTiles;
var selectedLength = selectedTiles.length;
var emptyTiles = tileset._emptyTiles;
var emptyLength = emptyTiles.length;
tileset._tileDebugLabels.removeAll();
if (tileset.debugPickedTileLabelOnly) {
if (defined(tileset.debugPickedTile)) {
var position = (defined(tileset.debugPickPosition)) ? tileset.debugPickPosition : computeTileLabelPosition(tileset.debugPickedTile);
var label = addTileDebugLabel(tileset.debugPickedTile, tileset, position);
label.pixelOffset = new Cartesian2(15, -15); // Offset to avoid picking the label.
}
} else {
for (i = 0; i < selectedLength; ++i) {
tile = selectedTiles[i];
addTileDebugLabel(tile, tileset, computeTileLabelPosition(tile));
}
for (i = 0; i < emptyLength; ++i) {
tile = emptyTiles[i];
if (tile.hasTilesetContent) {
addTileDebugLabel(tile, tileset, computeTileLabelPosition(tile));
}
}
}
tileset._tileDebugLabels.update(frameState);
}
var stencilClearCommand = new ClearCommand({
stencil : 0,
pass : Pass.CESIUM_3D_TILE
});
function updateTiles(tileset, frameState) {
tileset._styleEngine.applyStyle(tileset, frameState);
var statistics = tileset._statistics;
var passes = frameState.passes;
var isRender = passes.render;
var commandList = frameState.commandList;
var numberOfInitialCommands = commandList.length;
var selectedTiles = tileset._selectedTiles;
var selectedLength = selectedTiles.length;
var emptyTiles = tileset._emptyTiles;
var emptyLength = emptyTiles.length;
var tileVisible = tileset.tileVisible;
var i;
var tile;
var bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer && selectedLength > 0;
tileset._backfaceCommands.length = 0;
if (bivariateVisibilityTest) {
commandList.push(stencilClearCommand);
}
var lengthBeforeUpdate = commandList.length;
for (i = 0; i < selectedLength; ++i) {
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
if (isRender) {
tileVisible.raiseEvent(tile);
}
tile.update(tileset, frameState);
statistics.incrementSelectionCounts(tile.content);
++statistics.selected;
}
for (i = 0; i < emptyLength; ++i) {
tile = emptyTiles[i];
tile.update(tileset, frameState);
}
var addedCommandsLength = commandList.length - lengthBeforeUpdate;
tileset._backfaceCommands.trim();
if (bivariateVisibilityTest) {
/**
* Consider 'effective leaf' tiles as selected tiles that have no selected descendants. They may have children,
* but they are currently our effective leaves because they do not have selected descendants. These tiles
* are those where with tile._finalResolution === true.
* Let 'unresolved' tiles be those with tile._finalResolution === false.
*
* 1. Render just the backfaces of unresolved tiles in order to lay down z
* 2. Render all frontfaces wherever tile._selectionDepth > stencilBuffer.
* Replace stencilBuffer with tile._selectionDepth, when passing the z test.
* Because children are always drawn before ancestors {@link Cesium3DTilesetTraversal#traverseAndSelect},
* this effectively draws children first and does not draw ancestors if a descendant has already
* been drawn at that pixel.
* Step 1 prevents child tiles from appearing on top when they are truly behind ancestor content.
* If they are behind the backfaces of the ancestor, then they will not be drawn.
*
* NOTE: Step 2 sometimes causes visual artifacts when backfacing child content has some faces that
* partially face the camera and are inside of the ancestor content. Because they are inside, they will
* not be culled by the depth writes in Step 1, and because they partially face the camera, the stencil tests
* will draw them on top of the ancestor content.
*
* NOTE: Because we always render backfaces of unresolved tiles, if the camera is looking at the backfaces
* of an object, they will always be drawn while loading, even if backface culling is enabled.
*/
var backfaceCommands = tileset._backfaceCommands.values;
var backfaceCommandsLength = backfaceCommands.length;
commandList.length += backfaceCommandsLength;
// copy commands to the back of the commandList
for (i = addedCommandsLength - 1; i >= 0; --i) {
commandList[lengthBeforeUpdate + backfaceCommandsLength + i] = commandList[lengthBeforeUpdate + i];
}
// move backface commands to the front of the commandList
for (i = 0; i < backfaceCommandsLength; ++i) {
commandList[lengthBeforeUpdate + i] = backfaceCommands[i];
}
}
// Number of commands added by each update above
addedCommandsLength = commandList.length - numberOfInitialCommands;
statistics.numberOfCommands = addedCommandsLength;
// Only run EDL if simple attenuation is on
if (isRender &&
tileset.pointCloudShading.attenuation &&
tileset.pointCloudShading.eyeDomeLighting &&
(addedCommandsLength > 0)) {
tileset._pointCloudEyeDomeLighting.update(frameState, numberOfInitialCommands, tileset.pointCloudShading);
}
if (isRender) {
if (tileset.debugShowGeometricError || tileset.debugShowRenderingStatistics || tileset.debugShowMemoryUsage || tileset.debugShowUrl) {
if (!defined(tileset._tileDebugLabels)) {
tileset._tileDebugLabels = new LabelCollection();
}
updateTileDebugLabels(tileset, frameState);
} else {
tileset._tileDebugLabels = tileset._tileDebugLabels && tileset._tileDebugLabels.destroy();
}
}
}
var scratchStack = [];
function destroySubtree(tileset, tile) {
var root = tile;
var stack = scratchStack;
stack.push(tile);
while (stack.length > 0) {
tile = stack.pop();
var children = tile.children;
var length = children.length;
for (var i = 0; i < length; ++i) {
stack.push(children[i]);
}
if (tile !== root) {
destroyTile(tileset, tile);
--tileset._statistics.numberOfTilesTotal;
}
}
root.children = [];
}
function unloadTile(tileset, tile) {
tileset.tileUnload.raiseEvent(tile);
tileset._statistics.decrementLoadCounts(tile.content);
--tileset._statistics.numberOfTilesWithContentReady;
tile.unloadContent();
}
function destroyTile(tileset, tile) {
tileset._cache.unloadTile(tileset, tile, unloadTile);
tile.destroy();
}
function unloadTiles(tileset) {
tileset._cache.unloadTiles(tileset, unloadTile);
}
/**
* Unloads all tiles that weren't selected the previous frame. This can be used to
* explicitly manage the tile cache and reduce the total number of tiles loaded below
* {@link Cesium3DTileset#maximumMemoryUsage}.
* <p>
* Tile unloads occur at the next frame to keep all the WebGL delete calls
* within the render loop.
* </p>
*/
Cesium3DTileset.prototype.trimLoadedTiles = function() {
this._cache.trim();
};
///////////////////////////////////////////////////////////////////////////
function raiseLoadProgressEvent(tileset, frameState) {
var statistics = tileset._statistics;
var statisticsLast = tileset._statisticsLastRender;
var numberOfPendingRequests = statistics.numberOfPendingRequests;
var numberOfTilesProcessing = statistics.numberOfTilesProcessing;
var lastNumberOfPendingRequest = statisticsLast.numberOfPendingRequests;
var lastNumberOfTilesProcessing = statisticsLast.numberOfTilesProcessing;
var progressChanged = (numberOfPendingRequests !== lastNumberOfPendingRequest) || (numberOfTilesProcessing !== lastNumberOfTilesProcessing);
if (progressChanged) {
frameState.afterRender.push(function() {
tileset.loadProgress.raiseEvent(numberOfPendingRequests, numberOfTilesProcessing);
});
}
tileset._tilesLoaded = (statistics.numberOfPendingRequests === 0) && (statistics.numberOfTilesProcessing === 0) && (statistics.numberOfAttemptedRequests === 0);
if (progressChanged && tileset._tilesLoaded) {
frameState.afterRender.push(function() {
tileset.allTilesLoaded.raiseEvent();
});
if (!tileset._initialTilesLoaded) {
tileset._initialTilesLoaded = true;
frameState.afterRender.push(function() {
tileset.initialTilesLoaded.raiseEvent();
});
}
}
}
///////////////////////////////////////////////////////////////////////////
function update(tileset, frameState) {
if (frameState.mode === SceneMode.MORPHING) {
return false;
}
if (!tileset.show || !tileset.ready) {
return false;
}
if (!defined(tileset._loadTimestamp)) {
tileset._loadTimestamp = JulianDate.clone(frameState.time);
}
// Update clipping planes
var clippingPlanes = tileset._clippingPlanes;
tileset._clippingPlanesOriginMatrixDirty = true;
if (defined(clippingPlanes) && clippingPlanes.enabled) {
clippingPlanes.update(frameState);
}
tileset._timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, tileset._loadTimestamp) * 1000, 0.0);
tileset._skipLevelOfDetail = tileset.skipLevelOfDetail && !defined(tileset._classificationType) && !tileset._disableSkipLevelOfDetail && !tileset._allTilesAdditive;
// Do out-of-core operations (new content requests, cache removal,
// process new tiles) only during the render pass.
var passes = frameState.passes;
var isRender = passes.render;
var isPick = passes.pick;
var isAsync = passes.async;
var statistics = tileset._statistics;
statistics.clear();
if (tileset.dynamicScreenSpaceError) {
updateDynamicScreenSpaceError(tileset, frameState);
}
if (isRender) {
tileset._cache.reset();
}
++tileset._updatedVisibilityFrame;
var ready;
if (isAsync) {
ready = Cesium3DTilesetAsyncTraversal.selectTiles(tileset, frameState);
} else {
ready = Cesium3DTilesetTraversal.selectTiles(tileset, frameState);
}
if (isRender || isAsync) {
requestTiles(tileset);
}
if (isRender) {
processTiles(tileset, frameState);
}
updateTiles(tileset, frameState);
if (isRender) {
unloadTiles(tileset);
// Events are raised (added to the afterRender queue) here since promises
// may resolve outside of the update loop that then raise events, e.g.,
// model's readyPromise.
raiseLoadProgressEvent(tileset, frameState);
if (statistics.selected !== 0) {
var credits = tileset._credits;
if (defined(credits)) {
var length = credits.length;
for (var i = 0; i < length; i++) {
frameState.creditDisplay.addCredit(credits[i]);
}
}
}
}
// Update last statistics
var statisticsLast = isAsync ? tileset._statisticsLastAsync : (isPick ? tileset._statisticsLastPick : tileset._statisticsLastRender);
Cesium3DTilesetStatistics.clone(statistics, statisticsLast);
return ready;
}
/**
* @private
*/
Cesium3DTileset.prototype.update = function(frameState) {
update(this, frameState);
};
/**
* @private
*/
Cesium3DTileset.prototype.updateAsync = function(frameState) {
return update(this, frameState);
};
/**
* <code>true</code> if the tileset JSON file lists the extension in extensionsUsed; otherwise, <code>false</code>.
* @param {String} extensionName The name of the extension to check.
*
* @returns {Boolean} <code>true</code> if the tileset JSON file lists the extension in extensionsUsed; otherwise, <code>false</code>.
*/
Cesium3DTileset.prototype.hasExtension = function(extensionName) {
if (!defined(this._extensionsUsed)) {
return false;
}
return (this._extensionsUsed.indexOf(extensionName) > -1);
};
/**
* Returns true if this object was destroyed; otherwise, false.
* <br /><br />
* If this object was destroyed, it should not be used; calling any function other than
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
*
* @see Cesium3DTileset#destroy
*/
Cesium3DTileset.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
* <br /><br />
* Once an object is destroyed, it should not be used; calling any function other than
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (<code>undefined</code>) to the object as done in the example.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* tileset = tileset && tileset.destroy();
*
* @see Cesium3DTileset#isDestroyed
*/
Cesium3DTileset.prototype.destroy = function() {
this._tileDebugLabels = this._tileDebugLabels && this._tileDebugLabels.destroy();
this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy();
// Traverse the tree and destroy all tiles
if (defined(this._root)) {
var stack = scratchStack;
stack.push(this._root);
while (stack.length > 0) {
var tile = stack.pop();
tile.destroy();
var children = tile.children;
var length = children.length;
for (var i = 0; i < length; ++i) {
stack.push(children[i]);
}
}
}
this._root = undefined;
return destroyObject(this);
};
return Cesium3DTileset;
});