/*global define*/ define([ '../Core/BoundingSphere', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Color', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', '../Core/Event', '../Core/FeatureDetection', '../Core/GeometryPipeline', '../Core/IndexDatatype', '../Core/Intersect', '../Core/Matrix4', '../Core/PrimitiveType', '../Core/Rectangle', '../Core/Visibility', '../Core/WebMercatorProjection', '../Renderer/BufferUsage', '../Renderer/DrawCommand', '../Scene/BlendingState', '../Scene/DepthFunction', '../Scene/Pass', '../ThirdParty/when', './GlobeSurfaceTile', './ImageryLayer', './ImageryState', './QuadtreeTileLoadState', './SceneMode' ], function( BoundingSphere, Cartesian2, Cartesian3, Cartesian4, Color, defined, defineProperties, destroyObject, DeveloperError, Event, FeatureDetection, GeometryPipeline, IndexDatatype, Intersect, Matrix4, PrimitiveType, Rectangle, Visibility, WebMercatorProjection, BufferUsage, DrawCommand, BlendingState, DepthFunction, Pass, when, GlobeSurfaceTile, ImageryLayer, ImageryState, QuadtreeTileLoadState, SceneMode) { "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 */ var GlobeSurfaceTileProvider = 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._quadtree = undefined; this._terrainProvider = options.terrainProvider; this._imageryLayers = options.imageryLayers; this._surfaceShaderSet = options.surfaceShaderSet; this._renderState = undefined; this._blendRenderState = 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._layerOrderChanged = false; this._tilesToRenderByTextureCount = []; this._drawCommands = []; this._uniformMaps = []; this._usedDrawCommands = 0; this._debug = { wireframe : false, boundingSphereTile : undefined }; this._baseColor = undefined; this._firstPassInitialColor = undefined; this.baseColor = new Color(0.0, 0.0, 0.5, 1.0); }; 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 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(); } } } }); 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; } /** * Called at the beginning of the update cycle for each render frame, before {@link QuadtreeTileProvider#showTileThisFrame} * or any other functions. * * @param {Context} context The rendering context. * @param {FrameState} frameState The frame state. * @param {DrawCommand[]} commandList An array of rendering commands. This method may push * commands into this array. */ GlobeSurfaceTileProvider.prototype.beginUpdate = function(context, frameState, commandList) { this._imageryLayers._update(); 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); }); } var i; var len; var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount; for (i = 0, len = tilesToRenderByTextureCount.length; i < len; ++i) { var tiles = tilesToRenderByTextureCount[i]; if (defined(tiles)) { tiles.length = 0; } } this._usedDrawCommands = 0; // Add credits for terrain and imagery providers. var creditDisplay = frameState.creditDisplay; if (this._terrainProvider.ready && defined(this._terrainProvider.credit)) { creditDisplay.addCredit(this._terrainProvider.credit); } var imageryLayers = this._imageryLayers; for (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 end of the update cycle for each render frame, after {@link QuadtreeTileProvider#showTileThisFrame} * and any other functions. * * @param {Context} context The rendering context. * @param {FrameState} frameState The frame state. * @param {DrawCommand[]} commandList An array of rendering commands. This method may push * commands into this array. */ GlobeSurfaceTileProvider.prototype.endUpdate = function(context, frameState, commandList) { if (!defined(this._renderState)) { this._renderState = context.createRenderState({ // Write color and depth cull : { enabled : true }, depthTest : { enabled : true } }); } if (!defined(this._blendRenderState)) { this._blendRenderState = context.createRenderState({ // Write color and depth cull : { enabled : true }, depthTest : { enabled : true, func : DepthFunction.LESS_OR_EQUAL }, blending : BlendingState.ALPHA_BLEND }); } this._renderState.depthTest.enabled = frameState.mode === SceneMode.SCENE3D || frameState.mode === SceneMode.COLUMBUS_VIEW; this._blendRenderState.depthTest.enabled = this._renderState.depthTest.enabled; // And 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], context, frameState, commandList); } } }; /** * 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 {Context} context The rendering context. * @param {FrameState} frameState The frame state. * @param {QuadtreeTile} tile The tile to load. * * @exception {DeveloperError} loadTile must not be called before the tile provider is ready. */ GlobeSurfaceTileProvider.prototype.loadTile = function(context, frameState, tile) { GlobeSurfaceTile.processStateMachine(tile, context, this._terrainProvider, this._imageryLayers); }; 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 surfaceTile = tile.data; var cullingVolume = frameState.cullingVolume; var boundingVolume = 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 intersection = cullingVolume.computeVisibility(boundingVolume); if (intersection === Intersect.OUTSIDE) { return Visibility.NONE; } if (frameState.mode === SceneMode.SCENE3D) { var occludeePointInScaledSpace = surfaceTile.occludeePointInScaledSpace; if (!defined(occludeePointInScaledSpace)) { return intersection; } if (occluders.ellipsoid.isScaledSpacePointVisible(occludeePointInScaledSpace)) { return intersection; } return Visibility.NONE; } return intersection; }; var float32ArrayScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(1) : undefined; var modifiedModelViewScratch = new Matrix4(); var tileRectangleScratch = new Cartesian4(); var rtcScratch = new Cartesian3(); var centerEyeScratch = new Cartesian4(); 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 {Context} context The rendering context. * @param {FrameState} frameState The state information of the current rendering frame. * @param {DrawCommand[]} commandList The list of rendering commands. This method may add additional commands to this list. */ GlobeSurfaceTileProvider.prototype.showTileThisFrame = function(tile, context, frameState, commandList) { 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; }; var southwestCornerScratch = new Cartesian3(); var northeastCornerScratch = new Cartesian3(); var negativeUnitY = new Cartesian3(0.0, -1.0, 0.0); var negativeUnitZ = new Cartesian3(0.0, 0.0, -1.0); var vectorScratch = new Cartesian3(); /** * 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. * @param {Cartesian3} cameraCartesianPosition The position of the camera in world coordinates. * @param {Cartographic} cameraCartographicPosition The position of the camera in cartographic / geodetic coordinates. * * @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 southwestCornerCartesian = surfaceTile.southwestCornerCartesian; var northeastCornerCartesian = surfaceTile.northeastCornerCartesian; var westNormal = surfaceTile.westNormal; var southNormal = surfaceTile.southNormal; var eastNormal = surfaceTile.eastNormal; var northNormal = surfaceTile.northNormal; var maximumHeight = surfaceTile.maximumHeight; if (frameState.mode !== SceneMode.SCENE3D) { southwestCornerCartesian = frameState.mapProjection.project(Rectangle.southwest(tile.rectangle), southwestCornerScratch); southwestCornerCartesian.z = southwestCornerCartesian.y; southwestCornerCartesian.y = southwestCornerCartesian.x; southwestCornerCartesian.x = 0.0; northeastCornerCartesian = frameState.mapProjection.project(Rectangle.northeast(tile.rectangle), northeastCornerScratch); northeastCornerCartesian.z = northeastCornerCartesian.y; northeastCornerCartesian.y = northeastCornerCartesian.x; northeastCornerCartesian.x = 0.0; westNormal = negativeUnitY; eastNormal = Cartesian3.UNIT_Y; southNormal = negativeUnitZ; northNormal = Cartesian3.UNIT_Z; maximumHeight = 0.0; } var cameraCartesianPosition = frameState.camera.positionWC; var cameraCartographicPosition = frameState.camera.positionCartographic; var vectorFromSouthwestCorner = Cartesian3.subtract(cameraCartesianPosition, southwestCornerCartesian, vectorScratch); var distanceToWestPlane = Cartesian3.dot(vectorFromSouthwestCorner, westNormal); var distanceToSouthPlane = Cartesian3.dot(vectorFromSouthwestCorner, southNormal); var vectorFromNortheastCorner = Cartesian3.subtract(cameraCartesianPosition, northeastCornerCartesian, vectorScratch); var distanceToEastPlane = Cartesian3.dot(vectorFromNortheastCorner, eastNormal); var distanceToNorthPlane = Cartesian3.dot(vectorFromNortheastCorner, northNormal); var cameraHeight; if (frameState.mode === SceneMode.SCENE3D) { cameraHeight = cameraCartographicPosition.height; } else { cameraHeight = cameraCartesianPosition.x; } var distanceFromTop = cameraHeight - maximumHeight; var result = 0.0; if (distanceToWestPlane > 0.0) { result += distanceToWestPlane * distanceToWestPlane; } else if (distanceToEastPlane > 0.0) { result += distanceToEastPlane * distanceToEastPlane; } if (distanceToSouthPlane > 0.0) { result += distanceToSouthPlane * distanceToSouthPlane; } else if (distanceToNorthPlane > 0.0) { result += distanceToNorthPlane * distanceToNorthPlane; } if (distanceFromTop > 0.0) { result += distanceFromTop * distanceFromTop; } return Math.sqrt(result); }; /** * Returns true if this object was destroyed; otherwise, false. *

* If this object was destroyed, it should not be used; calling any function other than * isDestroyed 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. *

* Once an object is destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. Therefore, * assign the return value (undefined) to the object as done in the example. * * @returns {undefined} * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * @see GlobeSurfaceTileProvider#isDestroyed * * @example * provider = provider && provider(); */ GlobeSurfaceTileProvider.prototype.destroy = function() { this._tileProvider = this._tileProvider && this._tileProvider.destroy(); return destroyObject(this); }; GlobeSurfaceTileProvider.prototype._onLayerAdded = function(layer, index) { if (layer.show) { var terrainProvider = this._terrainProvider; // create TileImagerys 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; } }; 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); } }); }; GlobeSurfaceTileProvider.prototype._onLayerMoved = function(layer, newIndex, oldIndex) { this._layerOrderChanged = true; }; GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden = function(layer, index, show) { if (show) { this._onLayerAdded(layer, index); } else { this._onLayerRemoved(layer, index); } }; function createTileUniformMap() { var uniformMap = { u_initialColor : function() { return this.initialColor; }, u_zoomedOutOceanSpecularIntensity : function() { return this.zoomedOutOceanSpecularIntensity; }, u_oceanNormalMap : function() { return this.oceanNormalMap; }, u_lightingFadeDistance : function() { return this.lightingFadeDistance; }, u_center3D : function() { return this.center3D; }, u_tileRectangle : function() { return this.tileRectangle; }, u_modifiedModelView : function() { return this.modifiedModelView; }, u_dayTextures : function() { return this.dayTextures; }, u_dayTextureTranslationAndScale : function() { return this.dayTextureTranslationAndScale; }, u_dayTextureTexCoordsRectangle : function() { return this.dayTextureTexCoordsRectangle; }, u_dayTextureAlpha : function() { return this.dayTextureAlpha; }, u_dayTextureBrightness : function() { return this.dayTextureBrightness; }, u_dayTextureContrast : function() { return this.dayTextureContrast; }, u_dayTextureHue : function() { return this.dayTextureHue; }, u_dayTextureSaturation : function() { return this.dayTextureSaturation; }, u_dayTextureOneOverGamma : function() { return this.dayTextureOneOverGamma; }, u_dayIntensity : function() { return this.dayIntensity; }, u_southAndNorthLatitude : function() { return this.southAndNorthLatitude; }, u_southMercatorYLowAndHighAndOneOverHeight : function() { return this.southMercatorYLowAndHighAndOneOverHeight; }, u_waterMask : function() { return this.waterMask; }, u_waterMaskTranslationAndScale : function() { return this.waterMaskTranslationAndScale; }, 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, modifiedModelView : new Matrix4(), tileRectangle : new Cartesian4(), dayTextures : [], dayTextureTranslationAndScale : [], dayTextureTexCoordsRectangle : [], dayTextureAlpha : [], dayTextureBrightness : [], dayTextureContrast : [], dayTextureHue : [], dayTextureSaturation : [], dayTextureOneOverGamma : [], dayIntensity : 0.0, southAndNorthLatitude : new Cartesian2(), southMercatorYLowAndHighAndOneOverHeight : new Cartesian3(), waterMask : undefined, waterMaskTranslationAndScale : new Cartesian4() }; return uniformMap; } function createWireframeVertexArrayIfNecessary(context, provider, tile) { var surfaceTile = tile.data; if (defined(surfaceTile.wireframeVertexArray)) { return; } if (defined(surfaceTile.meshForWireframePromise)) { return; } surfaceTile.meshForWireframePromise = surfaceTile.terrainData.createMesh(provider._terrainProvider.tilingScheme, tile.x, tile.y, tile.level); if (!defined(surfaceTile.meshForWireframePromise)) { // deferrred return; } var vertexArray = surfaceTile.vertexArray; when(surfaceTile.meshForWireframePromise, function(mesh) { if (surfaceTile.vertexArray === vertexArray) { surfaceTile.wireframeVertexArray = createWireframeVertexArray(context, surfaceTile.vertexArray, mesh); } surfaceTile.meshForWireframePromise = undefined; }); } /** * 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 = context.createIndexBuffer(wireframeIndices, BufferUsage.STATIC_DRAW, IndexDatatype.UNSIGNED_SHORT); return context.createVertexArray(vertexArray._attributes, wireframeIndexBuffer); } var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); function addDrawCommandsForTile(tileProvider, tile, context, frameState, commandList) { var surfaceTile = tile.data; var viewMatrix = frameState.camera.viewMatrix; var maxTextures = context.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; if (showReflectiveOcean) { --maxTextures; } if (showOceanWaves) { --maxTextures; } var rtc = surfaceTile.center; // Not used in 3D. var tileRectangle = tileRectangleScratch; // Only used for Mercator projections. var southLatitude = 0.0; var northLatitude = 0.0; var southMercatorYHigh = 0.0; var southMercatorYLow = 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 (projection instanceof WebMercatorProjection) { southLatitude = tile.rectangle.south; northLatitude = tile.rectangle.north; var southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(southLatitude); var northMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(northLatitude); float32ArrayScratch[0] = southMercatorY; southMercatorYHigh = float32ArrayScratch[0]; southMercatorYLow = southMercatorY - float32ArrayScratch[0]; oneOverMercatorHeight = 1.0 / (northMercatorY - southMercatorY); useWebMercatorProjection = true; } } var centerEye = centerEyeScratch; centerEye.x = rtc.x; centerEye.y = rtc.y; centerEye.z = rtc.z; centerEye.w = 1.0; Matrix4.multiplyByVector(viewMatrix, centerEye, centerEye); Matrix4.setColumn(viewMatrix, 3, centerEye, modifiedModelViewScratch); 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; 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 BoundingSphere(); uniformMap = createTileUniformMap(); tileProvider._drawCommands.push(command); tileProvider._uniformMaps.push(uniformMap); } else { command = tileProvider._drawCommands[tileProvider._usedDrawCommands]; uniformMap = tileProvider._uniformMaps[tileProvider._usedDrawCommands]; } command.owner = tile; ++tileProvider._usedDrawCommands; command.debugShowBoundingVolume = (tile === tileProvider._debug.boundingSphereTile); Cartesian4.clone(initialColor, uniformMap.initialColor); uniformMap.oceanNormalMap = oceanNormalMap; uniformMap.lightingFadeDistance.x = tileProvider.lightingFadeOutDistance; uniformMap.lightingFadeDistance.y = tileProvider.lightingFadeInDistance; uniformMap.zoomedOutOceanSpecularIntensity = tileProvider.zoomedOutOceanSpecularIntensity; uniformMap.center3D = surfaceTile.center; Cartesian4.clone(tileRectangle, uniformMap.tileRectangle); uniformMap.southAndNorthLatitude.x = southLatitude; uniformMap.southAndNorthLatitude.y = northLatitude; uniformMap.southMercatorYLowAndHighAndOneOverHeight.x = southMercatorYLow; uniformMap.southMercatorYLowAndHighAndOneOverHeight.y = southMercatorYHigh; uniformMap.southMercatorYLowAndHighAndOneOverHeight.z = oneOverMercatorHeight; Matrix4.clone(modifiedModelViewScratch, uniformMap.modifiedModelView); var applyBrightness = false; var applyContrast = false; var applyHue = false; var applySaturation = false; var applyGamma = false; var applyAlpha = false; while (numberOfDayTextures < maxTextures && imageryIndex < imageryLen) { var tileImagery = tileImageryCollection[imageryIndex]; var imagery = tileImagery.readyImagery; ++imageryIndex; if (!defined(imagery) || imagery.state !== ImageryState.READY || imagery.imageryLayer.alpha === 0.0) { continue; } var imageryLayer = imagery.imageryLayer; if (!defined(tileImagery.textureTranslationAndScale)) { tileImagery.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, tileImagery); } uniformMap.dayTextures[numberOfDayTextures] = imagery.texture; uniformMap.dayTextureTranslationAndScale[numberOfDayTextures] = tileImagery.textureTranslationAndScale; uniformMap.dayTextureTexCoordsRectangle[numberOfDayTextures] = tileImagery.textureCoordinateRectangle; uniformMap.dayTextureAlpha[numberOfDayTextures] = imageryLayer.alpha; applyAlpha = applyAlpha || uniformMap.dayTextureAlpha[numberOfDayTextures] !== 1.0; uniformMap.dayTextureBrightness[numberOfDayTextures] = imageryLayer.brightness; applyBrightness = applyBrightness || uniformMap.dayTextureBrightness[numberOfDayTextures] !== ImageryLayer.DEFAULT_BRIGHTNESS; uniformMap.dayTextureContrast[numberOfDayTextures] = imageryLayer.contrast; applyContrast = applyContrast || uniformMap.dayTextureContrast[numberOfDayTextures] !== ImageryLayer.DEFAULT_CONTRAST; uniformMap.dayTextureHue[numberOfDayTextures] = imageryLayer.hue; applyHue = applyHue || uniformMap.dayTextureHue[numberOfDayTextures] !== ImageryLayer.DEFAULT_HUE; uniformMap.dayTextureSaturation[numberOfDayTextures] = imageryLayer.saturation; applySaturation = applySaturation || uniformMap.dayTextureSaturation[numberOfDayTextures] !== ImageryLayer.DEFAULT_SATURATION; uniformMap.dayTextureOneOverGamma[numberOfDayTextures] = 1.0 / imageryLayer.gamma; applyGamma = applyGamma || uniformMap.dayTextureOneOverGamma[numberOfDayTextures] !== 1.0 / ImageryLayer.DEFAULT_GAMMA; if (defined(imagery.credits)) { var creditDisplay = frameState.creditDisplay; var credits = imagery.credits; for (var creditIndex = 0, creditLength = credits.length; creditIndex < creditLength; ++creditIndex) { creditDisplay.addCredit(credits[creditIndex]); } } ++numberOfDayTextures; } // trim texture array to the used length so we don't end up using old textures // which might get destroyed eventually uniformMap.dayTextures.length = numberOfDayTextures; uniformMap.waterMask = waterMaskTexture; Cartesian4.clone(surfaceTile.waterMaskTranslationAndScale, uniformMap.waterMaskTranslationAndScale); command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(context, frameState.mode, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection); command.renderState = renderState; command.primitiveType = PrimitiveType.TRIANGLES; command.vertexArray = surfaceTile.vertexArray; command.uniformMap = uniformMap; command.pass = Pass.OPAQUE; if (tileProvider._debug.wireframe) { createWireframeVertexArrayIfNecessary(context, tileProvider, tile); if (defined(surfaceTile.wireframeVertexArray)) { command.vertexArray = surfaceTile.wireframeVertexArray; command.primitiveType = PrimitiveType.LINES; } } var boundingVolume = command.boundingVolume; if (frameState.mode !== SceneMode.SCENE3D) { 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); } } else { BoundingSphere.clone(surfaceTile.boundingSphere3D, boundingVolume); } commandList.push(command); renderState = otherPassesRenderState; initialColor = otherPassesInitialColor; } while (imageryIndex < imageryLen); } return GlobeSurfaceTileProvider; });