123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962 |
- /*global define*/
- define([
- '../Core/BoundingRectangle',
- '../Core/Cartesian2',
- '../Core/Cartesian4',
- '../Core/Color',
- '../Core/ComponentDatatype',
- '../Core/defaultValue',
- '../Core/defined',
- '../Core/defineProperties',
- '../Core/destroyObject',
- '../Core/FeatureDetection',
- '../Core/GeographicTilingScheme',
- '../Core/IndexDatatype',
- '../Core/Math',
- '../Core/PixelFormat',
- '../Core/PrimitiveType',
- '../Core/Rectangle',
- '../Core/TerrainProvider',
- '../Core/TileProviderError',
- '../Renderer/BufferUsage',
- '../Renderer/ClearCommand',
- '../Renderer/DrawCommand',
- '../Renderer/MipmapHint',
- '../Renderer/ShaderSource',
- '../Renderer/TextureMagnificationFilter',
- '../Renderer/TextureMinificationFilter',
- '../Renderer/TextureWrap',
- '../Shaders/ReprojectWebMercatorFS',
- '../Shaders/ReprojectWebMercatorVS',
- '../ThirdParty/when',
- './Imagery',
- './ImageryState',
- './TileImagery'
- ], function(
- BoundingRectangle,
- Cartesian2,
- Cartesian4,
- Color,
- ComponentDatatype,
- defaultValue,
- defined,
- defineProperties,
- destroyObject,
- FeatureDetection,
- GeographicTilingScheme,
- IndexDatatype,
- CesiumMath,
- PixelFormat,
- PrimitiveType,
- Rectangle,
- TerrainProvider,
- TileProviderError,
- BufferUsage,
- ClearCommand,
- DrawCommand,
- MipmapHint,
- ShaderSource,
- TextureMagnificationFilter,
- TextureMinificationFilter,
- TextureWrap,
- ReprojectWebMercatorFS,
- ReprojectWebMercatorVS,
- when,
- Imagery,
- ImageryState,
- TileImagery) {
- "use strict";
- /**
- * An imagery layer that displays tiled image data from a single imagery provider
- * on a {@link Globe}.
- *
- * @alias ImageryLayer
- * @constructor
- *
- * @param {ImageryProvider} imageryProvider The imagery provider to use.
- * @param {Object} [options] Object with the following properties:
- * @param {Rectangle} [options.rectangle=imageryProvider.rectangle] The rectangle of the layer. This rectangle
- * can limit the visible portion of the imagery provider.
- * @param {Number|Function} [options.alpha=1.0] The alpha blending value of this layer, from 0.0 to 1.0.
- * This can either be a simple number or a function with the signature
- * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
- * current frame state, this layer, and the x, y, and level coordinates of the
- * imagery tile for which the alpha is required, and it is expected to return
- * the alpha value to use for the tile.
- * @param {Number|Function} [options.brightness=1.0] The brightness of this layer. 1.0 uses the unmodified imagery
- * color. Less than 1.0 makes the imagery darker while greater than 1.0 makes it brighter.
- * This can either be a simple number or a function with the signature
- * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
- * current frame state, this layer, and the x, y, and level coordinates of the
- * imagery tile for which the brightness is required, and it is expected to return
- * the brightness value to use for the tile. The function is executed for every
- * frame and for every tile, so it must be fast.
- * @param {Number|Function} [options.contrast=1.0] The contrast of this layer. 1.0 uses the unmodified imagery color.
- * Less than 1.0 reduces the contrast while greater than 1.0 increases it.
- * This can either be a simple number or a function with the signature
- * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
- * current frame state, this layer, and the x, y, and level coordinates of the
- * imagery tile for which the contrast is required, and it is expected to return
- * the contrast value to use for the tile. The function is executed for every
- * frame and for every tile, so it must be fast.
- * @param {Number|Function} [options.hue=0.0] The hue of this layer. 0.0 uses the unmodified imagery color.
- * This can either be a simple number or a function with the signature
- * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
- * current frame state, this layer, and the x, y, and level coordinates
- * of the imagery tile for which the hue is required, and it is expected to return
- * the contrast value to use for the tile. The function is executed for every
- * frame and for every tile, so it must be fast.
- * @param {Number|Function} [options.saturation=1.0] The saturation of this layer. 1.0 uses the unmodified imagery color.
- * Less than 1.0 reduces the saturation while greater than 1.0 increases it.
- * This can either be a simple number or a function with the signature
- * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
- * current frame state, this layer, and the x, y, and level coordinates
- * of the imagery tile for which the saturation is required, and it is expected to return
- * the contrast value to use for the tile. The function is executed for every
- * frame and for every tile, so it must be fast.
- * @param {Number|Function} [options.gamma=1.0] The gamma correction to apply to this layer. 1.0 uses the unmodified imagery color.
- * This can either be a simple number or a function with the signature
- * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
- * current frame state, this layer, and the x, y, and level coordinates of the
- * imagery tile for which the gamma is required, and it is expected to return
- * the gamma value to use for the tile. The function is executed for every
- * frame and for every tile, so it must be fast.
- * @param {Boolean} [options.show=true] True if the layer is shown; otherwise, false.
- * @param {Number} [options.maximumAnisotropy=maximum supported] The maximum anisotropy level to use
- * for texture filtering. If this parameter is not specified, the maximum anisotropy supported
- * by the WebGL stack will be used. Larger values make the imagery look better in horizon
- * views.
- * @param {Number} [options.minimumTerrainLevel] The minimum terrain level-of-detail at which to show this imagery layer,
- * or undefined to show it at all levels. Level zero is the least-detailed level.
- * @param {Number} [options.maximumTerrainLevel] The maximum terrain level-of-detail at which to show this imagery layer,
- * or undefined to show it at all levels. Level zero is the least-detailed level.
- */
- var ImageryLayer = function ImageryLayer(imageryProvider, options) {
- this._imageryProvider = imageryProvider;
- options = defaultValue(options, {});
- /**
- * The alpha blending value of this layer, with 0.0 representing fully transparent and
- * 1.0 representing fully opaque.
- *
- * @type {Number}
- * @default 1.0
- */
- this.alpha = defaultValue(options.alpha, defaultValue(imageryProvider.defaultAlpha, 1.0));
- /**
- * The brightness of this layer. 1.0 uses the unmodified imagery color. Less than 1.0
- * makes the imagery darker while greater than 1.0 makes it brighter.
- *
- * @type {Number}
- * @default {@link ImageryLayer.DEFAULT_BRIGHTNESS}
- */
- this.brightness = defaultValue(options.brightness, defaultValue(imageryProvider.defaultBrightness, ImageryLayer.DEFAULT_BRIGHTNESS));
- /**
- * The contrast of this layer. 1.0 uses the unmodified imagery color. Less than 1.0 reduces
- * the contrast while greater than 1.0 increases it.
- *
- * @type {Number}
- * @default {@link ImageryLayer.DEFAULT_CONTRAST}
- */
- this.contrast = defaultValue(options.contrast, defaultValue(imageryProvider.defaultContrast, ImageryLayer.DEFAULT_CONTRAST));
- /**
- * The hue of this layer in radians. 0.0 uses the unmodified imagery color.
- *
- * @type {Number}
- * @default {@link ImageryLayer.DEFAULT_HUE}
- */
- this.hue = defaultValue(options.hue, defaultValue(imageryProvider.defaultHue, ImageryLayer.DEFAULT_HUE));
- /**
- * The saturation of this layer. 1.0 uses the unmodified imagery color. Less than 1.0 reduces the
- * saturation while greater than 1.0 increases it.
- *
- * @type {Number}
- * @default {@link ImageryLayer.DEFAULT_SATURATION}
- */
- this.saturation = defaultValue(options.saturation, defaultValue(imageryProvider.defaultSaturation, ImageryLayer.DEFAULT_SATURATION));
- /**
- * The gamma correction to apply to this layer. 1.0 uses the unmodified imagery color.
- *
- * @type {Number}
- * @default {@link ImageryLayer.DEFAULT_GAMMA}
- */
- this.gamma = defaultValue(options.gamma, defaultValue(imageryProvider.defaultGamma, ImageryLayer.DEFAULT_GAMMA));
- /**
- * Determines if this layer is shown.
- *
- * @type {Boolean}
- * @default true
- */
- this.show = defaultValue(options.show, true);
- this._minimumTerrainLevel = options.minimumTerrainLevel;
- this._maximumTerrainLevel = options.maximumTerrainLevel;
- this._rectangle = defaultValue(options.rectangle, Rectangle.MAX_VALUE);
- this._maximumAnisotropy = options.maximumAnisotropy;
- this._imageryCache = {};
- this._skeletonPlaceholder = new TileImagery(Imagery.createPlaceholder(this));
- // The value of the show property on the last update.
- this._show = true;
- // The index of this layer in the ImageryLayerCollection.
- this._layerIndex = -1;
- // true if this is the base (lowest shown) layer.
- this._isBaseLayer = false;
- this._requestImageError = undefined;
- };
- defineProperties(ImageryLayer.prototype, {
- /**
- * Gets the imagery provider for this layer.
- * @memberof ImageryLayer.prototype
- * @type {ImageryProvider}
- * @readonly
- */
- imageryProvider : {
- get: function() {
- return this._imageryProvider;
- }
- },
- /**
- * Gets the rectangle of this layer. If this rectangle is smaller than the rectangle of the
- * {@link ImageryProvider}, only a portion of the imagery provider is shown.
- * @memberof ImageryLayer.prototype
- * @type {Rectangle}
- * @readonly
- */
- rectangle: {
- get: function() {
- return this._rectangle;
- }
- }
- });
- /**
- * This value is used as the default brightness for the imagery layer if one is not provided during construction
- * or by the imagery provider. This value does not modify the brightness of the imagery.
- * @type {Number}
- * @default 1.0
- */
- ImageryLayer.DEFAULT_BRIGHTNESS = 1.0;
- /**
- * This value is used as the default contrast for the imagery layer if one is not provided during construction
- * or by the imagery provider. This value does not modify the contrast of the imagery.
- * @type {Number}
- * @default 1.0
- */
- ImageryLayer.DEFAULT_CONTRAST = 1.0;
- /**
- * This value is used as the default hue for the imagery layer if one is not provided during construction
- * or by the imagery provider. This value does not modify the hue of the imagery.
- * @type {Number}
- * @default 0.0
- */
- ImageryLayer.DEFAULT_HUE = 0.0;
- /**
- * This value is used as the default saturation for the imagery layer if one is not provided during construction
- * or by the imagery provider. This value does not modify the saturation of the imagery.
- * @type {Number}
- * @default 1.0
- */
- ImageryLayer.DEFAULT_SATURATION = 1.0;
- /**
- * This value is used as the default gamma for the imagery layer if one is not provided during construction
- * or by the imagery provider. This value does not modify the gamma of the imagery.
- * @type {Number}
- * @default 1.0
- */
- ImageryLayer.DEFAULT_GAMMA = 1.0;
- /**
- * Gets a value indicating whether this layer is the base layer in the
- * {@link ImageryLayerCollection}. The base layer is the one that underlies all
- * others. It is special in that it is treated as if it has global rectangle, even if
- * it actually does not, by stretching the texels at the edges over the entire
- * globe.
- *
- * @returns {Boolean} true if this is the base layer; otherwise, false.
- */
- ImageryLayer.prototype.isBaseLayer = function() {
- return this._isBaseLayer;
- };
- /**
- * 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 ImageryLayer#destroy
- */
- ImageryLayer.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.
- *
- * @returns {undefined}
- *
- * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
- *
- * @see ImageryLayer#isDestroyed
- *
- * @example
- * imageryLayer = imageryLayer && imageryLayer.destroy();
- */
- ImageryLayer.prototype.destroy = function() {
- return destroyObject(this);
- };
- var imageryBoundsScratch = new Rectangle();
- var tileImageryBoundsScratch = new Rectangle();
- var clippedRectangleScratch = new Rectangle();
- /**
- * Create skeletons for the imagery tiles that partially or completely overlap a given terrain
- * tile.
- *
- * @private
- *
- * @param {Tile} tile The terrain tile.
- * @param {TerrainProvider} terrainProvider The terrain provider associated with the terrain tile.
- * @param {Number} insertionPoint The position to insert new skeletons before in the tile's imagery lsit.
- * @returns {Boolean} true if this layer overlaps any portion of the terrain tile; otherwise, false.
- */
- ImageryLayer.prototype._createTileImagerySkeletons = function(tile, terrainProvider, insertionPoint) {
- var surfaceTile = tile.data;
- if (defined(this._minimumTerrainLevel) && tile.level < this._minimumTerrainLevel) {
- return false;
- }
- if (defined(this._maximumTerrainLevel) && tile.level > this._maximumTerrainLevel) {
- return false;
- }
- var imageryProvider = this._imageryProvider;
- if (!defined(insertionPoint)) {
- insertionPoint = surfaceTile.imagery.length;
- }
- if (!imageryProvider.ready) {
- // The imagery provider is not ready, so we can't create skeletons, yet.
- // Instead, add a placeholder so that we'll know to create
- // the skeletons once the provider is ready.
- this._skeletonPlaceholder.loadingImagery.addReference();
- surfaceTile.imagery.splice(insertionPoint, 0, this._skeletonPlaceholder);
- return true;
- }
- // Compute the rectangle of the imagery from this imageryProvider that overlaps
- // the geometry tile. The ImageryProvider and ImageryLayer both have the
- // opportunity to constrain the rectangle. The imagery TilingScheme's rectangle
- // always fully contains the ImageryProvider's rectangle.
- var imageryBounds = Rectangle.intersection(imageryProvider.rectangle, this._rectangle, imageryBoundsScratch);
- var rectangle = Rectangle.intersection(tile.rectangle, imageryBounds, tileImageryBoundsScratch);
- if (!defined(rectangle)) {
- // There is no overlap between this terrain tile and this imagery
- // provider. Unless this is the base layer, no skeletons need to be created.
- // We stretch texels at the edge of the base layer over the entire globe.
- if (!this.isBaseLayer()) {
- return false;
- }
- var baseImageryRectangle = imageryBounds;
- var baseTerrainRectangle = tile.rectangle;
- rectangle = tileImageryBoundsScratch;
- if (baseTerrainRectangle.south >= baseImageryRectangle.north) {
- rectangle.north = rectangle.south = baseImageryRectangle.north;
- } else if (baseTerrainRectangle.north <= baseImageryRectangle.south) {
- rectangle.north = rectangle.south = baseImageryRectangle.south;
- }
- if (baseTerrainRectangle.west >= baseImageryRectangle.east) {
- rectangle.west = rectangle.east = baseImageryRectangle.east;
- } else if (baseTerrainRectangle.east <= baseImageryRectangle.west) {
- rectangle.west = rectangle.east = baseImageryRectangle.west;
- }
- }
- var latitudeClosestToEquator = 0.0;
- if (rectangle.south > 0.0) {
- latitudeClosestToEquator = rectangle.south;
- } else if (rectangle.north < 0.0) {
- latitudeClosestToEquator = rectangle.north;
- }
- // Compute the required level in the imagery tiling scheme.
- // The errorRatio should really be imagerySSE / terrainSSE rather than this hard-coded value.
- // But first we need configurable imagery SSE and we need the rendering to be able to handle more
- // images attached to a terrain tile than there are available texture units. So that's for the future.
- var errorRatio = 1.0;
- var targetGeometricError = errorRatio * terrainProvider.getLevelMaximumGeometricError(tile.level);
- var imageryLevel = getLevelWithMaximumTexelSpacing(this, targetGeometricError, latitudeClosestToEquator);
- imageryLevel = Math.max(0, imageryLevel);
- var maximumLevel = imageryProvider.maximumLevel;
- if (imageryLevel > maximumLevel) {
- imageryLevel = maximumLevel;
- }
- if (defined(imageryProvider.minimumLevel)) {
- var minimumLevel = imageryProvider.minimumLevel;
- if (imageryLevel < minimumLevel) {
- imageryLevel = minimumLevel;
- }
- }
- var imageryTilingScheme = imageryProvider.tilingScheme;
- var northwestTileCoordinates = imageryTilingScheme.positionToTileXY(Rectangle.northwest(rectangle), imageryLevel);
- var southeastTileCoordinates = imageryTilingScheme.positionToTileXY(Rectangle.southeast(rectangle), imageryLevel);
- // If the southeast corner of the rectangle lies very close to the north or west side
- // of the southeast tile, we don't actually need the southernmost or easternmost
- // tiles.
- // Similarly, if the northwest corner of the rectangle lies very close to the south or east side
- // of the northwest tile, we don't actually need the northernmost or westernmost tiles.
- // We define "very close" as being within 1/512 of the width of the tile.
- var veryCloseX = tile.rectangle.height / 512.0;
- var veryCloseY = tile.rectangle.width / 512.0;
- var northwestTileRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel);
- if (Math.abs(northwestTileRectangle.south - tile.rectangle.north) < veryCloseY && northwestTileCoordinates.y < southeastTileCoordinates.y) {
- ++northwestTileCoordinates.y;
- }
- if (Math.abs(northwestTileRectangle.east - tile.rectangle.west) < veryCloseX && northwestTileCoordinates.x < southeastTileCoordinates.x) {
- ++northwestTileCoordinates.x;
- }
- var southeastTileRectangle = imageryTilingScheme.tileXYToRectangle(southeastTileCoordinates.x, southeastTileCoordinates.y, imageryLevel);
- if (Math.abs(southeastTileRectangle.north - tile.rectangle.south) < veryCloseY && southeastTileCoordinates.y > northwestTileCoordinates.y) {
- --southeastTileCoordinates.y;
- }
- if (Math.abs(southeastTileRectangle.west - tile.rectangle.east) < veryCloseX && southeastTileCoordinates.x > northwestTileCoordinates.x) {
- --southeastTileCoordinates.x;
- }
- // Create TileImagery instances for each imagery tile overlapping this terrain tile.
- // We need to do all texture coordinate computations in the imagery tile's tiling scheme.
- var terrainRectangle = tile.rectangle;
- var imageryRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel);
- var clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
- var minU;
- var maxU = 0.0;
- var minV = 1.0;
- var maxV;
- // If this is the northern-most or western-most tile in the imagery tiling scheme,
- // it may not start at the northern or western edge of the terrain tile.
- // Calculate where it does start.
- if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.west - tile.rectangle.west) >= veryCloseX) {
- maxU = Math.min(1.0, (clippedImageryRectangle.west - terrainRectangle.west) / terrainRectangle.width);
- }
- if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.north - tile.rectangle.north) >= veryCloseY) {
- minV = Math.max(0.0, (clippedImageryRectangle.north - terrainRectangle.south) / terrainRectangle.height);
- }
- var initialMinV = minV;
- for ( var i = northwestTileCoordinates.x; i <= southeastTileCoordinates.x; i++) {
- minU = maxU;
- imageryRectangle = imageryTilingScheme.tileXYToRectangle(i, northwestTileCoordinates.y, imageryLevel);
- clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
- maxU = Math.min(1.0, (clippedImageryRectangle.east - terrainRectangle.west) / terrainRectangle.width);
- // If this is the eastern-most imagery tile mapped to this terrain tile,
- // and there are more imagery tiles to the east of this one, the maxU
- // should be 1.0 to make sure rounding errors don't make the last
- // image fall shy of the edge of the terrain tile.
- if (i === southeastTileCoordinates.x && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.east - tile.rectangle.east) < veryCloseX)) {
- maxU = 1.0;
- }
- minV = initialMinV;
- for ( var j = northwestTileCoordinates.y; j <= southeastTileCoordinates.y; j++) {
- maxV = minV;
- imageryRectangle = imageryTilingScheme.tileXYToRectangle(i, j, imageryLevel);
- clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
- minV = Math.max(0.0, (clippedImageryRectangle.south - terrainRectangle.south) / terrainRectangle.height);
- // If this is the southern-most imagery tile mapped to this terrain tile,
- // and there are more imagery tiles to the south of this one, the minV
- // should be 0.0 to make sure rounding errors don't make the last
- // image fall shy of the edge of the terrain tile.
- if (j === southeastTileCoordinates.y && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.south - tile.rectangle.south) < veryCloseY)) {
- minV = 0.0;
- }
- var texCoordsRectangle = new Cartesian4(minU, minV, maxU, maxV);
- var imagery = this.getImageryFromCache(i, j, imageryLevel, imageryRectangle);
- surfaceTile.imagery.splice(insertionPoint, 0, new TileImagery(imagery, texCoordsRectangle));
- ++insertionPoint;
- }
- }
- return true;
- };
- /**
- * Calculate the translation and scale for a particular {@link TileImagery} attached to a
- * particular terrain tile.
- *
- * @private
- *
- * @param {Tile} tile The terrain tile.
- * @param {TileImagery} tileImagery The imagery tile mapping.
- * @returns {Cartesian4} The translation and scale where X and Y are the translation and Z and W
- * are the scale.
- */
- ImageryLayer.prototype._calculateTextureTranslationAndScale = function(tile, tileImagery) {
- var imageryRectangle = tileImagery.readyImagery.rectangle;
- var terrainRectangle = tile.rectangle;
- var terrainWidth = terrainRectangle.width;
- var terrainHeight = terrainRectangle.height;
- var scaleX = terrainWidth / imageryRectangle.width;
- var scaleY = terrainHeight / imageryRectangle.height;
- return new Cartesian4(
- scaleX * (terrainRectangle.west - imageryRectangle.west) / terrainWidth,
- scaleY * (terrainRectangle.south - imageryRectangle.south) / terrainHeight,
- scaleX,
- scaleY);
- };
- /**
- * Request a particular piece of imagery from the imagery provider. This method handles raising an
- * error event if the request fails, and retrying the request if necessary.
- *
- * @private
- *
- * @param {Imagery} imagery The imagery to request.
- */
- ImageryLayer.prototype._requestImagery = function(imagery) {
- var imageryProvider = this._imageryProvider;
- var that = this;
- function success(image) {
- if (!defined(image)) {
- return failure();
- }
- imagery.image = image;
- imagery.state = ImageryState.RECEIVED;
- TileProviderError.handleSuccess(that._requestImageError);
- }
- function failure(e) {
- // Initially assume failure. handleError may retry, in which case the state will
- // change to TRANSITIONING.
- imagery.state = ImageryState.FAILED;
- var message = 'Failed to obtain image tile X: ' + imagery.x + ' Y: ' + imagery.y + ' Level: ' + imagery.level + '.';
- that._requestImageError = TileProviderError.handleError(
- that._requestImageError,
- imageryProvider,
- imageryProvider.errorEvent,
- message,
- imagery.x, imagery.y, imagery.level,
- doRequest);
- }
- function doRequest() {
- imagery.state = ImageryState.TRANSITIONING;
- var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level);
- if (!defined(imagePromise)) {
- // Too many parallel requests, so postpone loading tile.
- imagery.state = ImageryState.UNLOADED;
- return;
- }
- if (defined(imageryProvider.getTileCredits)) {
- imagery.credits = imageryProvider.getTileCredits(imagery.x, imagery.y, imagery.level);
- }
- when(imagePromise, success, failure);
- }
- doRequest();
- };
- /**
- * Create a WebGL texture for a given {@link Imagery} instance.
- *
- * @private
- *
- * @param {Context} context The rendered context to use to create textures.
- * @param {Imagery} imagery The imagery for which to create a texture.
- */
- ImageryLayer.prototype._createTexture = function(context, imagery) {
- var imageryProvider = this._imageryProvider;
- // If this imagery provider has a discard policy, use it to check if this
- // image should be discarded.
- if (defined(imageryProvider.tileDiscardPolicy)) {
- var discardPolicy = imageryProvider.tileDiscardPolicy;
- if (defined(discardPolicy)) {
- // If the discard policy is not ready yet, transition back to the
- // RECEIVED state and we'll try again next time.
- if (!discardPolicy.isReady()) {
- imagery.state = ImageryState.RECEIVED;
- return;
- }
- // Mark discarded imagery tiles invalid. Parent imagery will be used instead.
- if (discardPolicy.shouldDiscardImage(imagery.image)) {
- imagery.state = ImageryState.INVALID;
- return;
- }
- }
- }
- // Imagery does not need to be discarded, so upload it to WebGL.
- var texture = context.createTexture2D({
- source : imagery.image,
- pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB
- });
- imagery.texture = texture;
- imagery.image = undefined;
- imagery.state = ImageryState.TEXTURE_LOADED;
- };
- /**
- * Reproject a texture to a {@link GeographicProjection}, if necessary, and generate
- * mipmaps for the geographic texture.
- *
- * @private
- *
- * @param {Context} context The rendered context to use.
- * @param {Imagery} imagery The imagery instance to reproject.
- */
- ImageryLayer.prototype._reprojectTexture = function(context, imagery) {
- var texture = imagery.texture;
- var rectangle = imagery.rectangle;
- // Reproject this texture if it is not already in a geographic projection and
- // the pixels are more than 1e-5 radians apart. The pixel spacing cutoff
- // avoids precision problems in the reprojection transformation while making
- // no noticeable difference in the georeferencing of the image.
- if (!(this._imageryProvider.tilingScheme instanceof GeographicTilingScheme) &&
- rectangle.width / texture.width > 1e-5) {
- var reprojectedTexture = reprojectToGeographic(this, context, texture, imagery.rectangle);
- texture.destroy();
- imagery.texture = texture = reprojectedTexture;
- }
- // Use mipmaps if this texture has power-of-two dimensions.
- if (CesiumMath.isPowerOfTwo(texture.width) && CesiumMath.isPowerOfTwo(texture.height)) {
- var mipmapSampler = context.cache.imageryLayer_mipmapSampler;
- if (!defined(mipmapSampler)) {
- var maximumSupportedAnisotropy = context.maximumTextureFilterAnisotropy;
- mipmapSampler = context.cache.imageryLayer_mipmapSampler = context.createSampler({
- wrapS : TextureWrap.CLAMP_TO_EDGE,
- wrapT : TextureWrap.CLAMP_TO_EDGE,
- minificationFilter : TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
- magnificationFilter : TextureMagnificationFilter.LINEAR,
- maximumAnisotropy : Math.min(maximumSupportedAnisotropy, defaultValue(this._maximumAnisotropy, maximumSupportedAnisotropy))
- });
- }
- texture.generateMipmap(MipmapHint.NICEST);
- texture.sampler = mipmapSampler;
- } else {
- var nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler;
- if (!defined(nonMipmapSampler)) {
- nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler = context.createSampler({
- wrapS : TextureWrap.CLAMP_TO_EDGE,
- wrapT : TextureWrap.CLAMP_TO_EDGE,
- minificationFilter : TextureMinificationFilter.LINEAR,
- magnificationFilter : TextureMagnificationFilter.LINEAR
- });
- }
- texture.sampler = nonMipmapSampler;
- }
- imagery.state = ImageryState.READY;
- };
- ImageryLayer.prototype.getImageryFromCache = function(x, y, level, imageryRectangle) {
- var cacheKey = getImageryCacheKey(x, y, level);
- var imagery = this._imageryCache[cacheKey];
- if (!defined(imagery)) {
- imagery = new Imagery(this, x, y, level, imageryRectangle);
- this._imageryCache[cacheKey] = imagery;
- }
- imagery.addReference();
- return imagery;
- };
- ImageryLayer.prototype.removeImageryFromCache = function(imagery) {
- var cacheKey = getImageryCacheKey(imagery.x, imagery.y, imagery.level);
- delete this._imageryCache[cacheKey];
- };
- function getImageryCacheKey(x, y, level) {
- return JSON.stringify([x, y, level]);
- }
- var uniformMap = {
- u_textureDimensions : function() {
- return this.textureDimensions;
- },
- u_texture : function() {
- return this.texture;
- },
- textureDimensions : new Cartesian2(),
- texture : undefined
- };
- var float32ArrayScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(2 * 64) : undefined;
- function reprojectToGeographic(imageryLayer, context, texture, rectangle) {
- // This function has gone through a number of iterations, because GPUs are awesome.
- //
- // Originally, we had a very simple vertex shader and computed the Web Mercator texture coordinates
- // per-fragment in the fragment shader. That worked well, except on mobile devices, because
- // fragment shaders have limited precision on many mobile devices. The result was smearing artifacts
- // at medium zoom levels because different geographic texture coordinates would be reprojected to Web
- // Mercator as the same value.
- //
- // Our solution was to reproject to Web Mercator in the vertex shader instead of the fragment shader.
- // This required far more vertex data. With fragment shader reprojection, we only needed a single quad.
- // But to achieve the same precision with vertex shader reprojection, we needed a vertex for each
- // output pixel. So we used a grid of 256x256 vertices, because most of our imagery
- // tiles are 256x256. Fortunately the grid could be created and uploaded to the GPU just once and
- // re-used for all reprojections, so the performance was virtually unchanged from our original fragment
- // shader approach. See https://github.com/AnalyticalGraphicsInc/cesium/pull/714.
- //
- // Over a year later, we noticed (https://github.com/AnalyticalGraphicsInc/cesium/issues/2110)
- // that our reprojection code was creating a rare but severe artifact on some GPUs (Intel HD 4600
- // for one). The problem was that the GLSL sin function on these GPUs had a discontinuity at fine scales in
- // a few places.
- //
- // We solved this by implementing a more reliable sin function based on the CORDIC algorithm
- // (https://github.com/AnalyticalGraphicsInc/cesium/pull/2111). Even though this was a fair
- // amount of code to be executing per vertex, the performance seemed to be pretty good on most GPUs.
- // Unfortunately, on some GPUs, the performance was absolutely terrible
- // (https://github.com/AnalyticalGraphicsInc/cesium/issues/2258).
- //
- // So that brings us to our current solution, the one you see here. Effectively, we compute the Web
- // Mercator texture coordinates on the CPU and store the T coordinate with each vertex (the S coordinate
- // is the same in Geographic and Web Mercator). To make this faster, we reduced our reprojection mesh
- // to be only 2 vertices wide and 64 vertices high. We should have reduced the width to 2 sooner,
- // because the extra vertices weren't buying us anything. The height of 64 means we are technically
- // doing a slightly less accurate reprojection than we were before, but we can't see the difference
- // so it's worth the 4x speedup.
- var reproject = context.cache.imageryLayer_reproject;
- if (!defined(reproject)) {
- reproject = context.cache.imageryLayer_reproject = {
- framebuffer : undefined,
- vertexArray : undefined,
- shaderProgram : undefined,
- renderState : undefined,
- sampler : undefined,
- destroy : function() {
- if (defined(this.framebuffer)) {
- this.framebuffer.destroy();
- }
- if (defined(this.vertexArray)) {
- this.vertexArray.destroy();
- }
- if (defined(this.shaderProgram)) {
- this.shaderProgram.destroy();
- }
- }
- };
- var positions = new Float32Array(2 * 64 * 2);
- var index = 0;
- for (var j = 0; j < 64; ++j) {
- var y = j / 63.0;
- positions[index++] = 0.0;
- positions[index++] = y;
- positions[index++] = 1.0;
- positions[index++] = y;
- }
- var reprojectAttributeIndices = {
- position : 0,
- webMercatorT : 1
- };
- var indices = TerrainProvider.getRegularGridIndices(2, 64);
- var indexBuffer = context.createIndexBuffer(indices, BufferUsage.STATIC_DRAW, IndexDatatype.UNSIGNED_SHORT);
- reproject.vertexArray = context.createVertexArray([
- {
- index : reprojectAttributeIndices.position,
- vertexBuffer : context.createVertexBuffer(positions, BufferUsage.STATIC_DRAW),
- componentsPerAttribute : 2
- },
- {
- index : reprojectAttributeIndices.webMercatorT,
- vertexBuffer : context.createVertexBuffer(64 * 2 * 4, BufferUsage.STREAM_DRAW),
- componentsPerAttribute : 1
- }
- ], indexBuffer);
- var vs = new ShaderSource({
- sources : [ReprojectWebMercatorVS]
- });
- reproject.shaderProgram = context.createShaderProgram(vs, ReprojectWebMercatorFS, reprojectAttributeIndices);
- reproject.sampler = context.createSampler({
- wrapS : TextureWrap.CLAMP_TO_EDGE,
- wrapT : TextureWrap.CLAMP_TO_EDGE,
- minificationFilter : TextureMinificationFilter.LINEAR,
- magnificationFilter : TextureMagnificationFilter.LINEAR
- });
- }
- texture.sampler = reproject.sampler;
- var width = texture.width;
- var height = texture.height;
- uniformMap.textureDimensions.x = width;
- uniformMap.textureDimensions.y = height;
- uniformMap.texture = texture;
- var sinLatitude = Math.sin(rectangle.south);
- var southMercatorY = 0.5 * Math.log((1 + sinLatitude) / (1 - sinLatitude));
- sinLatitude = Math.sin(rectangle.north);
- var northMercatorY = 0.5 * Math.log((1 + sinLatitude) / (1 - sinLatitude));
- var oneOverMercatorHeight = 1.0 / (northMercatorY - southMercatorY);
- var outputTexture = context.createTexture2D({
- width : width,
- height : height,
- pixelFormat : texture.pixelFormat,
- pixelDatatype : texture.pixelDatatype,
- preMultiplyAlpha : texture.preMultiplyAlpha
- });
- // Allocate memory for the mipmaps. Failure to do this before rendering
- // to the texture via the FBO, and calling generateMipmap later,
- // will result in the texture appearing blank. I can't pretend to
- // understand exactly why this is.
- outputTexture.generateMipmap(MipmapHint.NICEST);
- if (defined(reproject.framebuffer)) {
- reproject.framebuffer.destroy();
- }
- reproject.framebuffer = context.createFramebuffer({
- colorTextures : [outputTexture]
- });
- reproject.framebuffer.destroyAttachments = false;
- var south = rectangle.south;
- var north = rectangle.north;
- var webMercatorT = float32ArrayScratch;
- var outputIndex = 0;
- for (var webMercatorTIndex = 0; webMercatorTIndex < 64; ++webMercatorTIndex) {
- var fraction = webMercatorTIndex / 63.0;
- var latitude = CesiumMath.lerp(south, north, fraction);
- sinLatitude = Math.sin(latitude);
- var mercatorY = 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude));
- var mercatorFraction = (mercatorY - southMercatorY) * oneOverMercatorHeight;
- webMercatorT[outputIndex++] = mercatorFraction;
- webMercatorT[outputIndex++] = mercatorFraction;
- }
- reproject.vertexArray.getAttribute(1).vertexBuffer.copyFromArrayView(webMercatorT);
- var command = new ClearCommand({
- color : Color.BLACK,
- framebuffer : reproject.framebuffer
- });
- command.execute(context);
- if ((!defined(reproject.renderState)) ||
- (reproject.renderState.viewport.width !== width) ||
- (reproject.renderState.viewport.height !== height)) {
- reproject.renderState = context.createRenderState({
- viewport : new BoundingRectangle(0, 0, width, height)
- });
- }
- var drawCommand = new DrawCommand({
- framebuffer : reproject.framebuffer,
- shaderProgram : reproject.shaderProgram,
- renderState : reproject.renderState,
- primitiveType : PrimitiveType.TRIANGLES,
- vertexArray : reproject.vertexArray,
- uniformMap : uniformMap
- });
- drawCommand.execute(context);
- return outputTexture;
- }
- /**
- * Gets the level with the specified world coordinate spacing between texels, or less.
- *
- * @param {Number} texelSpacing The texel spacing for which to find a corresponding level.
- * @param {Number} latitudeClosestToEquator The latitude closest to the equator that we're concerned with.
- * @returns {Number} The level with the specified texel spacing or less.
- */
- function getLevelWithMaximumTexelSpacing(layer, texelSpacing, latitudeClosestToEquator) {
- // PERFORMANCE_IDEA: factor out the stuff that doesn't change.
- var imageryProvider = layer._imageryProvider;
- var tilingScheme = imageryProvider.tilingScheme;
- var ellipsoid = tilingScheme.ellipsoid;
- var latitudeFactor = !(layer._imageryProvider.tilingScheme instanceof GeographicTilingScheme) ? Math.cos(latitudeClosestToEquator) : 1.0;
- var tilingSchemeRectangle = tilingScheme.rectangle;
- var levelZeroMaximumTexelSpacing = ellipsoid.maximumRadius * tilingSchemeRectangle.width * latitudeFactor / (imageryProvider.tileWidth * tilingScheme.getNumberOfXTilesAtLevel(0));
- var twoToTheLevelPower = levelZeroMaximumTexelSpacing / texelSpacing;
- var level = Math.log(twoToTheLevelPower) / Math.log(2);
- var rounded = Math.round(level);
- return rounded | 0;
- }
- return ImageryLayer;
- });
|