UNPKG

cesium

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

1,234 lines (1,087 loc) 62.3 kB
define([ '../Core/BoundingSphere', '../Core/BoxOutlineGeometry', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Color', '../Core/ColorGeometryInstanceAttribute', '../Core/combine', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', '../Core/Event', '../Core/GeometryInstance', '../Core/GeometryPipeline', '../Core/IndexDatatype', '../Core/Intersect', '../Core/Math', '../Core/Matrix4', '../Core/OrientedBoundingBox', '../Core/OrthographicFrustum', '../Core/PrimitiveType', '../Core/Rectangle', '../Core/SphereOutlineGeometry', '../Core/TerrainQuantization', '../Core/Visibility', '../Core/WebMercatorProjection', '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/ContextLimits', '../Renderer/DrawCommand', '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/VertexArray', '../Scene/BlendingState', '../Scene/DepthFunction', '../Scene/PerInstanceColorAppearance', '../Scene/Primitive', './ClippingPlaneCollection', './GlobeSurfaceTile', './ImageryLayer', './QuadtreeTileLoadState', './SceneMode', './ShadowMode' ], function( BoundingSphere, BoxOutlineGeometry, Cartesian2, Cartesian3, Cartesian4, Color, ColorGeometryInstanceAttribute, combine, defaultValue, defined, defineProperties, destroyObject, DeveloperError, Event, GeometryInstance, GeometryPipeline, IndexDatatype, Intersect, CesiumMath, Matrix4, OrientedBoundingBox, OrthographicFrustum, PrimitiveType, Rectangle, SphereOutlineGeometry, TerrainQuantization, Visibility, WebMercatorProjection, Buffer, BufferUsage, ContextLimits, DrawCommand, Pass, RenderState, VertexArray, BlendingState, DepthFunction, PerInstanceColorAppearance, Primitive, ClippingPlaneCollection, GlobeSurfaceTile, ImageryLayer, QuadtreeTileLoadState, SceneMode, ShadowMode) { 'use strict'; /** * Provides quadtree tiles representing the surface of the globe. This type is intended to be used * with {@link QuadtreePrimitive}. * * @alias GlobeSurfaceTileProvider * @constructor * * @param {TerrainProvider} options.terrainProvider The terrain provider that describes the surface geometry. * @param {ImageryLayerCollection} option.imageryLayers The collection of imagery layers describing the shading of the surface. * @param {GlobeSurfaceShaderSet} options.surfaceShaderSet The set of shaders used to render the surface. * * @private */ function GlobeSurfaceTileProvider(options) { //>>includeStart('debug', pragmas.debug); if (!defined(options)) { throw new DeveloperError('options is required.'); } if (!defined(options.terrainProvider)) { throw new DeveloperError('options.terrainProvider is required.'); } else if (!defined(options.imageryLayers)) { throw new DeveloperError('options.imageryLayers is required.'); } else if (!defined(options.surfaceShaderSet)) { throw new DeveloperError('options.surfaceShaderSet is required.'); } //>>includeEnd('debug'); this.lightingFadeOutDistance = 6500000.0; this.lightingFadeInDistance = 9000000.0; this.hasWaterMask = false; this.oceanNormalMap = undefined; this.zoomedOutOceanSpecularIntensity = 0.5; this.enableLighting = false; this.shadows = ShadowMode.RECEIVE_ONLY; this._quadtree = undefined; this._terrainProvider = options.terrainProvider; this._imageryLayers = options.imageryLayers; this._surfaceShaderSet = options.surfaceShaderSet; this._renderState = undefined; this._blendRenderState = undefined; this._pickRenderState = undefined; this._errorEvent = new Event(); this._imageryLayers.layerAdded.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerAdded, this); this._imageryLayers.layerRemoved.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerRemoved, this); this._imageryLayers.layerMoved.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerMoved, this); this._imageryLayers.layerShownOrHidden.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden, this); this._tileLoadedEvent = new Event(); this._imageryLayersUpdatedEvent = new Event(); this._layerOrderChanged = false; this._tilesToRenderByTextureCount = []; this._drawCommands = []; this._uniformMaps = []; this._pickCommands = []; this._usedDrawCommands = 0; this._usedPickCommands = 0; this._vertexArraysToDestroy = []; this._debug = { wireframe : false, boundingSphereTile : undefined }; this._baseColor = undefined; this._firstPassInitialColor = undefined; this.baseColor = new Color(0.0, 0.0, 0.5, 1.0); /** * A property specifying a {@link ClippingPlaneCollection} used to selectively disable rendering on the outside of each plane. * @type {ClippingPlaneCollection} * @private */ this._clippingPlanes = undefined; } defineProperties(GlobeSurfaceTileProvider.prototype, { /** * Gets or sets the color of the globe when no imagery is available. * @memberof GlobeSurfaceTileProvider.prototype * @type {Color} */ baseColor : { get : function() { return this._baseColor; }, set : function(value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError('value is required.'); } //>>includeEnd('debug'); this._baseColor = value; this._firstPassInitialColor = Cartesian4.fromColor(value, this._firstPassInitialColor); } }, /** * Gets or sets the {@link QuadtreePrimitive} for which this provider is * providing tiles. This property may be undefined if the provider is not yet associated * with a {@link QuadtreePrimitive}. * @memberof GlobeSurfaceTileProvider.prototype * @type {QuadtreePrimitive} */ quadtree : { get : function() { return this._quadtree; }, set : function(value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError('value is required.'); } //>>includeEnd('debug'); this._quadtree = value; } }, /** * Gets a value indicating whether or not the provider is ready for use. * @memberof GlobeSurfaceTileProvider.prototype * @type {Boolean} */ ready : { get : function() { return this._terrainProvider.ready && (this._imageryLayers.length === 0 || this._imageryLayers.get(0).imageryProvider.ready); } }, /** * Gets the tiling scheme used by the provider. This property should * not be accessed before {@link GlobeSurfaceTileProvider#ready} returns true. * @memberof GlobeSurfaceTileProvider.prototype * @type {TilingScheme} */ tilingScheme : { get : function() { return this._terrainProvider.tilingScheme; } }, /** * Gets an event that is raised when the geometry provider encounters an asynchronous error. By subscribing * to the event, you will be notified of the error and can potentially recover from it. Event listeners * are passed an instance of {@link TileProviderError}. * @memberof GlobeSurfaceTileProvider.prototype * @type {Event} */ errorEvent : { get : function() { return this._errorEvent; } }, /** * Gets an event that is raised when an globe surface tile is loaded and ready to be rendered. * @memberof GlobeSurfaceTileProvider.prototype * @type {Event} */ tileLoadedEvent : { get : function() { return this._tileLoadedEvent; } }, /** * Gets an event that is raised when an imagery layer is added, shown, hidden, moved, or removed. * @memberof GlobeSurfaceTileProvider.prototype * @type {Event} */ imageryLayersUpdatedEvent : { get : function() { return this._imageryLayersUpdatedEvent; } }, /** * Gets or sets the terrain provider that describes the surface geometry. * @memberof GlobeSurfaceTileProvider.prototype * @type {TerrainProvider} */ terrainProvider : { get : function() { return this._terrainProvider; }, set : function(terrainProvider) { if (this._terrainProvider === terrainProvider) { return; } //>>includeStart('debug', pragmas.debug); if (!defined(terrainProvider)) { throw new DeveloperError('terrainProvider is required.'); } //>>includeEnd('debug'); this._terrainProvider = terrainProvider; if (defined(this._quadtree)) { this._quadtree.invalidateAllTiles(); } } }, /** * The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset. * * @type {ClippingPlaneCollection} * * @private */ clippingPlanes : { get : function() { return this._clippingPlanes; }, set : function(value) { ClippingPlaneCollection.setOwner(value, this, '_clippingPlanes'); } } }); function sortTileImageryByLayerIndex(a, b) { var aImagery = a.loadingImagery; if (!defined(aImagery)) { aImagery = a.readyImagery; } var bImagery = b.loadingImagery; if (!defined(bImagery)) { bImagery = b.readyImagery; } return aImagery.imageryLayer._layerIndex - bImagery.imageryLayer._layerIndex; } /** * Make updates to the tile provider that are not involved in rendering. Called before the render update cycle. */ GlobeSurfaceTileProvider.prototype.update = function(frameState) { // update collection: imagery indices, base layers, raise layer show/hide event this._imageryLayers._update(); }; function freeVertexArray(vertexArray) { var indexBuffer = vertexArray.indexBuffer; vertexArray.destroy(); if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) { --indexBuffer.referenceCount; if (indexBuffer.referenceCount === 0) { indexBuffer.destroy(); } } } function updateCredits(surface, frameState) { var creditDisplay = frameState.creditDisplay; if (surface._terrainProvider.ready && defined(surface._terrainProvider.credit)) { creditDisplay.addCredit(surface._terrainProvider.credit); } var imageryLayers = surface._imageryLayers; for (var i = 0, len = imageryLayers.length; i < len; ++i) { var imageryProvider = imageryLayers.get(i).imageryProvider; if (imageryProvider.ready && defined(imageryProvider.credit)) { creditDisplay.addCredit(imageryProvider.credit); } } } /** * Called at the beginning of each render frame, before {@link QuadtreeTileProvider#showTileThisFrame} * @param {FrameState} frameState The frame state. */ GlobeSurfaceTileProvider.prototype.initialize = function(frameState) { // update each layer for texture reprojection. this._imageryLayers.queueReprojectionCommands(frameState); if (this._layerOrderChanged) { this._layerOrderChanged = false; // Sort the TileImagery instances in each tile by the layer index. this._quadtree.forEachLoadedTile(function(tile) { tile.data.imagery.sort(sortTileImageryByLayerIndex); }); } // Add credits for terrain and imagery providers. updateCredits(this, frameState); var vertexArraysToDestroy = this._vertexArraysToDestroy; var length = vertexArraysToDestroy.length; for (var j = 0; j < length; ++j) { freeVertexArray(vertexArraysToDestroy[j]); } vertexArraysToDestroy.length = 0; }; /** * Called at the beginning of the update cycle for each render frame, before {@link QuadtreeTileProvider#showTileThisFrame} * or any other functions. * * @param {FrameState} frameState The frame state. */ GlobeSurfaceTileProvider.prototype.beginUpdate = function(frameState) { var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount; for (var i = 0, len = tilesToRenderByTextureCount.length; i < len; ++i) { var tiles = tilesToRenderByTextureCount[i]; if (defined(tiles)) { tiles.length = 0; } } // update clipping planes var clippingPlanes = this._clippingPlanes; if (defined(clippingPlanes) && clippingPlanes.enabled) { clippingPlanes.update(frameState); } this._usedDrawCommands = 0; }; /** * Called at the end of the update cycle for each render frame, after {@link QuadtreeTileProvider#showTileThisFrame} * and any other functions. * * @param {FrameState} frameState The frame state. */ GlobeSurfaceTileProvider.prototype.endUpdate = function(frameState) { if (!defined(this._renderState)) { this._renderState = RenderState.fromCache({ // Write color and depth cull : { enabled : true }, depthTest : { enabled : true, func : DepthFunction.LESS } }); this._blendRenderState = RenderState.fromCache({ // Write color and depth cull : { enabled : true }, depthTest : { enabled : true, func : DepthFunction.LESS_OR_EQUAL }, blending : BlendingState.ALPHA_BLEND }); } // Add the tile render commands to the command list, sorted by texture count. var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount; for (var textureCountIndex = 0, textureCountLength = tilesToRenderByTextureCount.length; textureCountIndex < textureCountLength; ++textureCountIndex) { var tilesToRender = tilesToRenderByTextureCount[textureCountIndex]; if (!defined(tilesToRender)) { continue; } for (var tileIndex = 0, tileLength = tilesToRender.length; tileIndex < tileLength; ++tileIndex) { addDrawCommandsForTile(this, tilesToRender[tileIndex], frameState); } } }; /** * Adds draw commands for tiles rendered in the previous frame for a pick pass. * * @param {FrameState} frameState The frame state. */ GlobeSurfaceTileProvider.prototype.updateForPick = function(frameState) { if (!defined(this._pickRenderState)) { this._pickRenderState = RenderState.fromCache({ colorMask : { red : false, green : false, blue : false, alpha : false }, depthTest : { enabled : true } }); } this._usedPickCommands = 0; var drawCommands = this._drawCommands; // Add the tile pick commands from the tiles drawn last frame. for (var i = 0, length = this._usedDrawCommands; i < length; ++i) { addPickCommandsForTile(this, drawCommands[i], frameState); } }; /** * Cancels any imagery re-projections in the queue. */ GlobeSurfaceTileProvider.prototype.cancelReprojections = function() { this._imageryLayers.cancelReprojections(); }; /** * Gets the maximum geometric error allowed in a tile at a given level, in meters. This function should not be * called before {@link GlobeSurfaceTileProvider#ready} returns true. * * @param {Number} level The tile level for which to get the maximum geometric error. * @returns {Number} The maximum geometric error in meters. */ GlobeSurfaceTileProvider.prototype.getLevelMaximumGeometricError = function(level) { return this._terrainProvider.getLevelMaximumGeometricError(level); }; /** * Loads, or continues loading, a given tile. This function will continue to be called * until {@link QuadtreeTile#state} is no longer {@link QuadtreeTileLoadState#LOADING}. This function should * not be called before {@link GlobeSurfaceTileProvider#ready} returns true. * * @param {FrameState} frameState The frame state. * @param {QuadtreeTile} tile The tile to load. * * @exception {DeveloperError} <code>loadTile</code> must not be called before the tile provider is ready. */ GlobeSurfaceTileProvider.prototype.loadTile = function(frameState, tile) { GlobeSurfaceTile.processStateMachine(tile, frameState, this._terrainProvider, this._imageryLayers, this._vertexArraysToDestroy); var tileLoadedEvent = this._tileLoadedEvent; tile._loadedCallbacks['tileLoadedEvent'] = function (tile) { tileLoadedEvent.raiseEvent(); return true; }; }; var boundingSphereScratch = new BoundingSphere(); /** * Determines the visibility of a given tile. The tile may be fully visible, partially visible, or not * visible at all. Tiles that are renderable and are at least partially visible will be shown by a call * to {@link GlobeSurfaceTileProvider#showTileThisFrame}. * * @param {QuadtreeTile} tile The tile instance. * @param {FrameState} frameState The state information about the current frame. * @param {QuadtreeOccluders} occluders The objects that may occlude this tile. * * @returns {Visibility} The visibility of the tile. */ GlobeSurfaceTileProvider.prototype.computeTileVisibility = function(tile, frameState, occluders) { var distance = this.computeDistanceToTile(tile, frameState); tile._distance = distance; if (frameState.fog.enabled) { if (CesiumMath.fog(distance, frameState.fog.density) >= 1.0) { // Tile is completely in fog so return that it is not visible. return Visibility.NONE; } } var surfaceTile = tile.data; var cullingVolume = frameState.cullingVolume; var boundingVolume = defaultValue(surfaceTile.orientedBoundingBox, surfaceTile.boundingSphere3D); if (frameState.mode !== SceneMode.SCENE3D) { boundingVolume = boundingSphereScratch; BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume); Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center); if (frameState.mode === SceneMode.MORPHING) { boundingVolume = BoundingSphere.union(surfaceTile.boundingSphere3D, boundingVolume, boundingVolume); } } var clippingPlanes = this._clippingPlanes; if (defined(clippingPlanes) && clippingPlanes.enabled) { var planeIntersection = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); tile.isClipped = (planeIntersection !== Intersect.INSIDE); if (planeIntersection === Intersect.OUTSIDE) { return Visibility.NONE; } } var intersection = cullingVolume.computeVisibility(boundingVolume); if (intersection === Intersect.OUTSIDE) { return Visibility.NONE; } var ortho3D = frameState.mode === SceneMode.SCENE3D && frameState.camera.frustum instanceof OrthographicFrustum; if (frameState.mode === SceneMode.SCENE3D && !ortho3D && defined(occluders)) { var occludeePointInScaledSpace = surfaceTile.occludeePointInScaledSpace; if (!defined(occludeePointInScaledSpace)) { return intersection; } if (occluders.ellipsoid.isScaledSpacePointVisible(occludeePointInScaledSpace)) { return intersection; } return Visibility.NONE; } return intersection; }; var modifiedModelViewScratch = new Matrix4(); var modifiedModelViewProjectionScratch = new Matrix4(); var tileRectangleScratch = new Cartesian4(); var rtcScratch = new Cartesian3(); var centerEyeScratch = new Cartesian3(); var southwestScratch = new Cartesian3(); var northeastScratch = new Cartesian3(); /** * Shows a specified tile in this frame. The provider can cause the tile to be shown by adding * render commands to the commandList, or use any other method as appropriate. The tile is not * expected to be visible next frame as well, unless this method is called next frame, too. * * @param {Object} tile The tile instance. * @param {FrameState} frameState The state information of the current rendering frame. */ GlobeSurfaceTileProvider.prototype.showTileThisFrame = function(tile, frameState) { var readyTextureCount = 0; var tileImageryCollection = tile.data.imagery; for (var i = 0, len = tileImageryCollection.length; i < len; ++i) { var tileImagery = tileImageryCollection[i]; if (defined(tileImagery.readyImagery) && tileImagery.readyImagery.imageryLayer.alpha !== 0.0) { ++readyTextureCount; } } var tileSet = this._tilesToRenderByTextureCount[readyTextureCount]; if (!defined(tileSet)) { tileSet = []; this._tilesToRenderByTextureCount[readyTextureCount] = tileSet; } tileSet.push(tile); var debug = this._debug; ++debug.tilesRendered; debug.texturesRendered += readyTextureCount; }; /** * Gets the distance from the camera to the closest point on the tile. This is used for level-of-detail selection. * * @param {QuadtreeTile} tile The tile instance. * @param {FrameState} frameState The state information of the current rendering frame. * * @returns {Number} The distance from the camera to the closest point on the tile, in meters. */ GlobeSurfaceTileProvider.prototype.computeDistanceToTile = function(tile, frameState) { var surfaceTile = tile.data; var tileBoundingRegion = surfaceTile.tileBoundingRegion; return tileBoundingRegion.distanceToCamera(frameState); }; /** * 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} True if this object was destroyed; otherwise, false. * * @see GlobeSurfaceTileProvider#destroy */ GlobeSurfaceTileProvider.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 * provider = provider && provider(); * * @see GlobeSurfaceTileProvider#isDestroyed */ GlobeSurfaceTileProvider.prototype.destroy = function() { this._tileProvider = this._tileProvider && this._tileProvider.destroy(); this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy(); return destroyObject(this); }; function getTileReadyCallback(tileImageriesToFree, layer, terrainProvider) { return function(tile) { var tileImagery; var imagery; var startIndex = -1; var tileImageryCollection = tile.data.imagery; var length = tileImageryCollection.length; var i; for (i = 0; i < length; ++i) { tileImagery = tileImageryCollection[i]; imagery = defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery); if (imagery.imageryLayer === layer) { startIndex = i; break; } } if (startIndex !== -1) { var endIndex = startIndex + tileImageriesToFree; tileImagery = tileImageryCollection[endIndex]; imagery = defined(tileImagery) ? defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery) : undefined; if (!defined(imagery) || imagery.imageryLayer !== layer) { // Return false to keep the callback if we have to wait on the skeletons // Return true to remove the callback if something went wrong return !(layer._createTileImagerySkeletons(tile, terrainProvider, endIndex)); } for (i = startIndex; i < endIndex; ++i) { tileImageryCollection[i].freeResources(); } tileImageryCollection.splice(startIndex, tileImageriesToFree); } return true; // Everything is done, so remove the callback }; } GlobeSurfaceTileProvider.prototype._onLayerAdded = function(layer, index) { if (layer.show) { var terrainProvider = this._terrainProvider; var that = this; var imageryProvider = layer.imageryProvider; var tileImageryUpdatedEvent = this._imageryLayersUpdatedEvent; imageryProvider._reload = function() { // Clear the layer's cache layer._imageryCache = {}; that._quadtree.forEachLoadedTile(function(tile) { // If this layer is still waiting to for the loaded callback, just return if (defined(tile._loadedCallbacks[layer._layerIndex])) { return; } var i; // Figure out how many TileImageries we will need to remove and where to insert new ones var tileImageryCollection = tile.data.imagery; var length = tileImageryCollection.length; var startIndex = -1; var tileImageriesToFree = 0; for (i = 0; i < length; ++i) { var tileImagery = tileImageryCollection[i]; var imagery = defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery); if (imagery.imageryLayer === layer) { if (startIndex === -1) { startIndex = i; } ++tileImageriesToFree; } else if (startIndex !== -1) { // iterated past the section of TileImageries belonging to this layer, no need to continue. break; } } if (startIndex === -1) { return; } // Insert immediately after existing TileImageries var insertionPoint = startIndex + tileImageriesToFree; // Create new TileImageries for all loaded tiles if (layer._createTileImagerySkeletons(tile, terrainProvider, insertionPoint)) { // Add callback to remove old TileImageries when the new TileImageries are ready tile._loadedCallbacks[layer._layerIndex] = getTileReadyCallback(tileImageriesToFree, layer, terrainProvider); tile.state = QuadtreeTileLoadState.LOADING; } }); }; // create TileImageries for this layer for all previously loaded tiles this._quadtree.forEachLoadedTile(function(tile) { if (layer._createTileImagerySkeletons(tile, terrainProvider)) { tile.state = QuadtreeTileLoadState.LOADING; } }); this._layerOrderChanged = true; tileImageryUpdatedEvent.raiseEvent(); } }; GlobeSurfaceTileProvider.prototype._onLayerRemoved = function(layer, index) { // destroy TileImagerys for this layer for all previously loaded tiles this._quadtree.forEachLoadedTile(function(tile) { var tileImageryCollection = tile.data.imagery; var startIndex = -1; var numDestroyed = 0; for (var i = 0, len = tileImageryCollection.length; i < len; ++i) { var tileImagery = tileImageryCollection[i]; var imagery = tileImagery.loadingImagery; if (!defined(imagery)) { imagery = tileImagery.readyImagery; } if (imagery.imageryLayer === layer) { if (startIndex === -1) { startIndex = i; } tileImagery.freeResources(); ++numDestroyed; } else if (startIndex !== -1) { // iterated past the section of TileImagerys belonging to this layer, no need to continue. break; } } if (startIndex !== -1) { tileImageryCollection.splice(startIndex, numDestroyed); } }); if (defined(layer.imageryProvider)) { layer.imageryProvider._reload = undefined; } this._imageryLayersUpdatedEvent.raiseEvent(); }; GlobeSurfaceTileProvider.prototype._onLayerMoved = function(layer, newIndex, oldIndex) { this._layerOrderChanged = true; this._imageryLayersUpdatedEvent.raiseEvent(); }; GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden = function(layer, index, show) { if (show) { this._onLayerAdded(layer, index); } else { this._onLayerRemoved(layer, index); } }; var scratchClippingPlaneMatrix = new Matrix4(); function createTileUniformMap(frameState, globeSurfaceTileProvider) { var uniformMap = { u_initialColor : function() { return this.properties.initialColor; }, u_zoomedOutOceanSpecularIntensity : function() { return this.properties.zoomedOutOceanSpecularIntensity; }, u_oceanNormalMap : function() { return this.properties.oceanNormalMap; }, u_lightingFadeDistance : function() { return this.properties.lightingFadeDistance; }, u_center3D : function() { return this.properties.center3D; }, u_tileRectangle : function() { return this.properties.tileRectangle; }, u_modifiedModelView : function() { var viewMatrix = frameState.context.uniformState.view; var centerEye = Matrix4.multiplyByPoint(viewMatrix, this.properties.rtc, centerEyeScratch); Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewScratch); return modifiedModelViewScratch; }, u_modifiedModelViewProjection : function() { var viewMatrix = frameState.context.uniformState.view; var projectionMatrix = frameState.context.uniformState.projection; var centerEye = Matrix4.multiplyByPoint(viewMatrix, this.properties.rtc, centerEyeScratch); Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewProjectionScratch); Matrix4.multiply(projectionMatrix, modifiedModelViewProjectionScratch, modifiedModelViewProjectionScratch); return modifiedModelViewProjectionScratch; }, u_dayTextures : function() { return this.properties.dayTextures; }, u_dayTextureTranslationAndScale : function() { return this.properties.dayTextureTranslationAndScale; }, u_dayTextureTexCoordsRectangle : function() { return this.properties.dayTextureTexCoordsRectangle; }, u_dayTextureUseWebMercatorT : function() { return this.properties.dayTextureUseWebMercatorT; }, u_dayTextureAlpha : function() { return this.properties.dayTextureAlpha; }, u_dayTextureBrightness : function() { return this.properties.dayTextureBrightness; }, u_dayTextureContrast : function() { return this.properties.dayTextureContrast; }, u_dayTextureHue : function() { return this.properties.dayTextureHue; }, u_dayTextureSaturation : function() { return this.properties.dayTextureSaturation; }, u_dayTextureOneOverGamma : function() { return this.properties.dayTextureOneOverGamma; }, u_dayIntensity : function() { return this.properties.dayIntensity; }, u_southAndNorthLatitude : function() { return this.properties.southAndNorthLatitude; }, u_southMercatorYAndOneOverHeight : function() { return this.properties.southMercatorYAndOneOverHeight; }, u_waterMask : function() { return this.properties.waterMask; }, u_waterMaskTranslationAndScale : function() { return this.properties.waterMaskTranslationAndScale; }, u_minMaxHeight : function() { return this.properties.minMaxHeight; }, u_scaleAndBias : function() { return this.properties.scaleAndBias; }, u_dayTextureSplit : function() { return this.properties.dayTextureSplit; }, u_clippingPlanes : function() { var clippingPlanes = globeSurfaceTileProvider._clippingPlanes; if (defined(clippingPlanes) && defined(clippingPlanes.texture)) { // Check in case clippingPlanes hasn't been updated yet. return clippingPlanes.texture; } return frameState.context.defaultTexture; }, u_clippingPlanesMatrix : function() { var clippingPlanes = globeSurfaceTileProvider._clippingPlanes; return defined(clippingPlanes) ? Matrix4.multiply(frameState.context.uniformState.view, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix) : Matrix4.IDENTITY; }, u_clippingPlanesEdgeStyle : function() { var style = this.properties.clippingPlanesEdgeColor; style.alpha = this.properties.clippingPlanesEdgeWidth; return style; }, u_minimumBrightness : function() { return frameState.fog.minimumBrightness; }, // make a separate object so that changes to the properties are seen on // derived commands that combine another uniform map with this one. properties : { initialColor : new Cartesian4(0.0, 0.0, 0.5, 1.0), zoomedOutOceanSpecularIntensity : 0.5, oceanNormalMap : undefined, lightingFadeDistance : new Cartesian2(6500000.0, 9000000.0), center3D : undefined, rtc : new Cartesian3(), modifiedModelView : new Matrix4(), tileRectangle : new Cartesian4(), dayTextures : [], dayTextureTranslationAndScale : [], dayTextureTexCoordsRectangle : [], dayTextureUseWebMercatorT : [], dayTextureAlpha : [], dayTextureBrightness : [], dayTextureContrast : [], dayTextureHue : [], dayTextureSaturation : [], dayTextureOneOverGamma : [], dayTextureSplit : [], dayIntensity : 0.0, southAndNorthLatitude : new Cartesian2(), southMercatorYAndOneOverHeight : new Cartesian2(), waterMask : undefined, waterMaskTranslationAndScale : new Cartesian4(), minMaxHeight : new Cartesian2(), scaleAndBias : new Matrix4(), clippingPlanesEdgeColor : Color.clone(Color.WHITE), clippingPlanesEdgeWidth : 0.0 } }; return uniformMap; } function createWireframeVertexArrayIfNecessary(context, provider, tile) { var surfaceTile = tile.data; if (defined(surfaceTile.wireframeVertexArray)) { return; } if (!defined(surfaceTile.terrainData) || !defined(surfaceTile.terrainData._mesh)) { return; } surfaceTile.wireframeVertexArray = createWireframeVertexArray(context, surfaceTile.vertexArray, surfaceTile.terrainData._mesh); } /** * Creates a vertex array for wireframe rendering of a terrain tile. * * @private * * @param {Context} context The context in which to create the vertex array. * @param {VertexArray} vertexArray The existing, non-wireframe vertex array. The new vertex array * will share vertex buffers with this existing one. * @param {TerrainMesh} terrainMesh The terrain mesh containing non-wireframe indices. * @returns {VertexArray} The vertex array for wireframe rendering. */ function createWireframeVertexArray(context, vertexArray, terrainMesh) { var geometry = { indices : terrainMesh.indices, primitiveType : PrimitiveType.TRIANGLES }; GeometryPipeline.toWireframe(geometry); var wireframeIndices = geometry.indices; var wireframeIndexBuffer = Buffer.createIndexBuffer({ context : context, typedArray : wireframeIndices, usage : BufferUsage.STATIC_DRAW, indexDatatype : IndexDatatype.UNSIGNED_SHORT }); return new VertexArray({ context : context, attributes : vertexArray._attributes, indexBuffer : wireframeIndexBuffer }); } var getDebugOrientedBoundingBox; var getDebugBoundingSphere; var debugDestroyPrimitive; (function() { var instanceOBB = new GeometryInstance({ geometry : BoxOutlineGeometry.fromDimensions({dimensions : new Cartesian3(2.0, 2.0, 2.0)}) }); var instanceSphere = new GeometryInstance({ geometry : new SphereOutlineGeometry({radius : 1.0}) }); var modelMatrix = new Matrix4(); var previousVolume; var primitive; function createDebugPrimitive(instance) { return new Primitive({ geometryInstances : instance, appearance : new PerInstanceColorAppearance({ translucent : false, flat : true }), asynchronous : false }); } getDebugOrientedBoundingBox = function(obb, color) { if (obb === previousVolume) { return primitive; } debugDestroyPrimitive(); previousVolume = obb; modelMatrix = Matrix4.fromRotationTranslation(obb.halfAxes, obb.center, modelMatrix); instanceOBB.modelMatrix = modelMatrix; instanceOBB.attributes.color = ColorGeometryInstanceAttribute.fromColor(color); primitive = createDebugPrimitive(instanceOBB); return primitive; }; getDebugBoundingSphere = function(sphere, color) { if (sphere === previousVolume) { return primitive; } debugDestroyPrimitive(); previousVolume = sphere; modelMatrix = Matrix4.fromTranslation(sphere.center, modelMatrix); modelMatrix = Matrix4.multiplyByUniformScale(modelMatrix, sphere.radius, modelMatrix); instanceSphere.modelMatrix = modelMatrix; instanceSphere.attributes.color = ColorGeometryInstanceAttribute.fromColor(color); primitive = createDebugPrimitive(instanceSphere); return primitive; }; debugDestroyPrimitive = function() { if (defined(primitive)) { primitive.destroy(); primitive = undefined; previousVolume = undefined; } }; })(); var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); function addDrawCommandsForTile(tileProvider, tile, frameState) { var surfaceTile = tile.data; var creditDisplay = frameState.creditDisplay; var terrainData = surfaceTile.terrainData; if (defined(terrainData) && defined(terrainData.credits)) { var tileCredits = terrainData.credits; for (var tileCreditIndex = 0, tileCreditLength = tileCredits.length; tileCreditIndex < tileCreditLength; ++tileCreditIndex) { creditDisplay.addCredit(tileCredits[tileCreditIndex]); } } var maxTextures = ContextLimits.maximumTextureImageUnits; var waterMaskTexture = surfaceTile.waterMaskTexture; var showReflectiveOcean = tileProvider.hasWaterMask && defined(waterMaskTexture); var oceanNormalMap = tileProvider.oceanNormalMap; var showOceanWaves = showReflectiveOcean && defined(oceanNormalMap); var hasVertexNormals = tileProvider.terrainProvider.ready && tileProvider.terrainProvider.hasVertexNormals; var enableFog = frameState.fog.enabled; var castShadows = ShadowMode.castShadows(tileProvider.shadows); var receiveShadows = ShadowMode.receiveShadows(tileProvider.shadows); if (showReflectiveOcean) { --maxTextures; } if (showOceanWaves) { --maxTextures; } var rtc = surfaceTile.center; var encoding = surfaceTile.pickTerrain.mesh.encoding; // Not used in 3D. var tileRectangle = tileRectangleScratch; // Only used for Mercator projections. var southLatitude = 0.0; var northLatitude = 0.0; var southMercatorY = 0.0; var oneOverMercatorHeight = 0.0; var useWebMercatorProjection = false; if (frameState.mode !== SceneMode.SCENE3D) { var projection = frameState.mapProjection; var southwest = projection.project(Rectangle.southwest(tile.rectangle), southwestScratch); var northeast = projection.project(Rectangle.northeast(tile.rectangle), northeastScratch); tileRectangle.x = southwest.x; tileRectangle.y = southwest.y; tileRectangle.z = northeast.x; tileRectangle.w = northeast.y; // In 2D and Columbus View, use the center of the tile for RTC rendering. if (frameState.mode !== SceneMode.MORPHING) { rtc = rtcScratch; rtc.x = 0.0; rtc.y = (tileRectangle.z + tileRectangle.x) * 0.5; rtc.z = (tileRectangle.w + tileRectangle.y) * 0.5; tileRectangle.x -= rtc.y; tileRectangle.y -= rtc.z; tileRectangle.z -= rtc.y; tileRectangle.w -= rtc.z; } if (frameState.mode === SceneMode.SCENE2D && encoding.quantization === TerrainQuantization.BITS12) { // In 2D, the texture coordinates of the tile are interpolated over the rectangle to get the position in the vertex shader. // When the texture coordinates are quantized, error is introduced. This can be seen through the 1px wide cracking // between the quantized tiles in 2D. To compensate for the error, move the expand the rectangle in each direction by // half the error amount. var epsilon = (1.0 / (Math.pow(2.0, 12.0) - 1.0)) * 0.5; var widthEpsilon = (tileRectangle.z - tileRectangle.x) * epsilon; var heightEpsilon = (tileRectangle.w - tileRectangle.y) * epsilon; tileRectangle.x -= widthEpsilon; tileRectangle.y -= heightEpsilon; tileRectangle.z += widthEpsilon; tileRectangle.w += heightEpsilon; } if (projection instanceof WebMercatorProjection) { southLatitude = tile.rectangle.south; northLatitude = tile.rectangle.north; southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(southLatitude); oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(northLatitude) - southMercatorY); useWebMercatorProjection = true; } } var tileImageryCollection = surfaceTile.imagery; var imageryIndex = 0; var imageryLen = tileImageryCollection.length; var firstPassRenderState = tileProvider._renderState; var otherPassesRenderState = tileProvider._blendRenderState; var renderState = firstPassRenderState; var initialColor = tileProvider._firstPassInitialColor; var context = frameState.context; if (!defined(tileProvider._debug.boundingSphereTile)) { debugDestroyPrimitive(); } do { var numberOfDayTextures = 0; var command; var uniformMap; if (tileProvider._drawCommands.length <= tileProvider._usedDrawCommands) { command = new DrawCommand(); command.owner = tile; command.cull = false; command.boundingVolume = new Bou