diff --git a/CHANGES.md b/CHANGES.md index cf12d0c7272c..2e4f1d4b8820 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,8 @@ Change Log * Fixed a bug that could lead to incorrect terrain heights when using `HeightmapTerrainData` with an encoding in which actual heights were equal to the minimum representable height. * Fixed a bug in `AttributeCompression.compressTextureCoordinates` and `decompressTextureCoordinates` that could cause a small inaccuracy in the encoded texture coordinates. * Added `DebugCameraPrimitive` to visualize the view frustum of a camera. +* Added `Rectangle.simpleIntersection`. +* Removed an unnecessary reprojection of Web Mercator imagery tiles to the Geographic projection on load. This should improve both visual quality and load performance slightly. ### 1.25 - 2016-09-01 diff --git a/Source/Core/HeightmapTerrainData.js b/Source/Core/HeightmapTerrainData.js index 1dad3a8a2632..3a402797ba1d 100644 --- a/Source/Core/HeightmapTerrainData.js +++ b/Source/Core/HeightmapTerrainData.js @@ -206,6 +206,7 @@ define([ var verticesPromise = taskProcessor.scheduleTask({ heightmap : this._buffer, structure : structure, + includeWebMercatorT : true, width : this._width, height : this._height, nativeRectangle : nativeRectangle, @@ -232,7 +233,7 @@ define([ result.maximumHeight, result.boundingSphere3D, result.occludeePointInScaledSpace, - 6, + result.numberOfAttributes, result.orientedBoundingBox, TerrainEncoding.clone(result.encoding), exaggeration); diff --git a/Source/Core/HeightmapTessellator.js b/Source/Core/HeightmapTessellator.js index fc478f62a2b8..2b607744d693 100644 --- a/Source/Core/HeightmapTessellator.js +++ b/Source/Core/HeightmapTessellator.js @@ -15,7 +15,8 @@ define([ './OrientedBoundingBox', './Rectangle', './TerrainEncoding', - './Transforms' + './Transforms', + './WebMercatorProjection', ], function( AxisAlignedBoundingBox, BoundingSphere, @@ -32,7 +33,8 @@ define([ OrientedBoundingBox, Rectangle, TerrainEncoding, - Transforms) { + Transforms, + WebMercatorProjection) { 'use strict'; /** @@ -200,6 +202,7 @@ define([ var relativeToCenter = defaultValue(options.relativeToCenter, Cartesian3.ZERO); var exaggeration = defaultValue(options.exaggeration, 1.0); + var includeWebMercatorT = defaultValue(options.includeWebMercatorT, false); var structure = defaultValue(options.structure, HeightmapTessellator.DEFAULT_STRUCTURE); var heightScale = defaultValue(structure.heightScale, HeightmapTessellator.DEFAULT_STRUCTURE.heightScale); @@ -223,6 +226,13 @@ define([ var fromENU = Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid); var toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch); + var southMercatorY; + var oneOverMercatorHeight; + if (includeWebMercatorT) { + southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicSouth); + oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicNorth) - southMercatorY); + } + var minimum = minimumScratch; minimum.x = Number.POSITIVE_INFINITY; minimum.y = Number.POSITIVE_INFINITY; @@ -241,6 +251,7 @@ define([ var positions = new Array(size); var heights = new Array(size); var uvs = new Array(size); + var webMercatorTs = includeWebMercatorT ? new Array(size) : []; var startRow = 0; var endRow = height; @@ -280,6 +291,11 @@ define([ var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth); v = CesiumMath.clamp(v, 0.0, 1.0); + var webMercatorT; + if (includeWebMercatorT) { + webMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight; + } + for (var colIndex = startCol; colIndex < endCol; ++colIndex) { var col = colIndex; if (col < 0) { @@ -351,6 +367,10 @@ define([ u = CesiumMath.clamp(u, 0.0, 1.0); uvs[index] = new Cartesian2(u, v); + if (includeWebMercatorT) { + webMercatorTs[index] = webMercatorT; + } + index++; Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch); @@ -377,12 +397,12 @@ define([ } var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter); - var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, false); + var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, false, includeWebMercatorT); var vertices = new Float32Array(size * encoding.getStride()); var bufferIndex = 0; for (var j = 0; j < size; ++j) { - bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j]); + bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j], undefined, webMercatorTs[j]); } return { diff --git a/Source/Core/QuantizedMeshTerrainData.js b/Source/Core/QuantizedMeshTerrainData.js index 43c85f7c2a8c..d1b8323d96e6 100644 --- a/Source/Core/QuantizedMeshTerrainData.js +++ b/Source/Core/QuantizedMeshTerrainData.js @@ -104,7 +104,7 @@ define([ * eastSkirtHeight : 1.0, * northSkirtHeight : 1.0 * }); - * + * * @see TerrainData * @see HeightmapTerrainData */ @@ -273,6 +273,7 @@ define([ maximumHeight : this._maximumHeight, quantizedVertices : this._quantizedVertices, octEncodedNormals : this._encodedNormals, + includeWebMercatorT : true, indices : this._indices, westIndices : this._westIndices, southIndices : this._southIndices, diff --git a/Source/Core/Rectangle.js b/Source/Core/Rectangle.js index b3f1743ccc1a..025d7f9d7742 100644 --- a/Source/Core/Rectangle.js +++ b/Source/Core/Rectangle.js @@ -597,7 +597,11 @@ define([ }; /** - * Computes the intersection of two rectangles + * Computes the intersection of two rectangles. This function assumes that the rectangle's coordinates are + * latitude and longitude in radians and produces a correct intersection, taking into account the fact that + * the same angle can be represented with multiple values as well as the wrapping of longitude at the + * anti-meridian. For a simple intersection that ignores these factors and can be used with projected + * coordinates, see {@link Rectangle.simpleIntersection}. * * @param {Rectangle} rectangle On rectangle to find an intersection * @param {Rectangle} otherRectangle Another rectangle to find an intersection @@ -656,6 +660,47 @@ define([ return result; }; + /** + * Computes a simple intersection of two rectangles. Unlike {@link Rectangle.intersection}, this function + * does not attempt to put the angular coordinates into a consistent range or to account for crossing the + * anti-meridian. As such, it can be used for rectangles where the coordinates are not simply latitude + * and longitude (i.e. projected coordinates). + * + * @param {Rectangle} rectangle On rectangle to find an intersection + * @param {Rectangle} otherRectangle Another rectangle to find an intersection + * @param {Rectangle} [result] The object onto which to store the result. + * @returns {Rectangle|undefined} The modified result parameter, a new Rectangle instance if none was provided or undefined if there is no intersection. + */ + Rectangle.simpleIntersection = function(rectangle, otherRectangle, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(rectangle)) { + throw new DeveloperError('rectangle is required'); + } + if (!defined(otherRectangle)) { + throw new DeveloperError('otherRectangle is required.'); + } + //>>includeEnd('debug'); + + var west = Math.max(rectangle.west, otherRectangle.west); + var south = Math.max(rectangle.south, otherRectangle.south); + var east = Math.min(rectangle.east, otherRectangle.east); + var north = Math.min(rectangle.north, otherRectangle.north); + + if (south >= north || west >= east) { + return undefined; + } + + if (!defined(result)) { + return new Rectangle(west, south, east, north); + } + + result.west = west; + result.south = south; + result.east = east; + result.north = north; + return result; + }; + /** * Computes a rectangle that is the union of two rectangles. * diff --git a/Source/Core/TerrainEncoding.js b/Source/Core/TerrainEncoding.js index e29cf70a3dad..9241995d44e0 100644 --- a/Source/Core/TerrainEncoding.js +++ b/Source/Core/TerrainEncoding.js @@ -4,6 +4,7 @@ define([ './Cartesian2', './Cartesian3', './ComponentDatatype', + './defaultValue', './defined', './Math', './Matrix3', @@ -14,6 +15,7 @@ define([ Cartesian2, Cartesian3, ComponentDatatype, + defaultValue, defined, CesiumMath, Matrix3, @@ -41,10 +43,11 @@ define([ * @param {Number} maximumHeight The maximum height. * @param {Matrix4} fromENU The east-north-up to fixed frame matrix at the center of the terrain mesh. * @param {Boolean} hasVertexNormals If the mesh has vertex normals. + * @param {Boolean} [hasWebMercatorT=false] true if the terrain data includes a Web Mercator texture coordinate; otherwise, false. * * @private */ - function TerrainEncoding(axisAlignedBoundingBox, minimumHeight, maximumHeight, fromENU, hasVertexNormals) { + function TerrainEncoding(axisAlignedBoundingBox, minimumHeight, maximumHeight, fromENU, hasVertexNormals, hasWebMercatorT) { var quantization; var center; var toENU; @@ -137,9 +140,15 @@ define([ * @type {Boolean} */ this.hasVertexNormals = hasVertexNormals; + + /** + * The terrain mesh contains a vertical texture coordinate following the Web Mercator projection. + * @type {Boolean} + */ + this.hasWebMercatorT = defaultValue(hasWebMercatorT, false); } - TerrainEncoding.prototype.encode = function(vertexBuffer, bufferIndex, position, uv, height, normalToPack) { + TerrainEncoding.prototype.encode = function(vertexBuffer, bufferIndex, position, uv, height, normalToPack, webMercatorT) { var u = uv.x; var v = uv.y; @@ -165,6 +174,12 @@ define([ vertexBuffer[bufferIndex++] = compressed0; vertexBuffer[bufferIndex++] = compressed1; vertexBuffer[bufferIndex++] = compressed2; + + if (this.hasWebMercatorT) { + Cartesian2.fromElements(webMercatorT, 0.0, cartesian2Scratch); + var compressed3 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch); + vertexBuffer[bufferIndex++] = compressed3; + } } else { Cartesian3.subtract(position, this.center, cartesian3Scratch); @@ -174,6 +189,10 @@ define([ vertexBuffer[bufferIndex++] = height; vertexBuffer[bufferIndex++] = u; vertexBuffer[bufferIndex++] = v; + + if (this.hasWebMercatorT) { + vertexBuffer[bufferIndex++] = webMercatorT; + } } if (this.hasVertexNormals) { @@ -254,6 +273,10 @@ define([ vertexStride = 6; } + if (this.hasWebMercatorT) { + ++vertexStride; + } + if (this.hasVertexNormals) { ++vertexStride; } @@ -266,17 +289,29 @@ define([ textureCoordAndEncodedNormals : 1 }; var attributes = { - compressed : 0 + compressed0 : 0, + compressed1 : 1 }; TerrainEncoding.prototype.getAttributes = function(buffer) { var datatype = ComponentDatatype.FLOAT; + var sizeInBytes = ComponentDatatype.getSizeInBytes(datatype); + var stride; if (this.quantization === TerrainQuantization.NONE) { - var sizeInBytes = ComponentDatatype.getSizeInBytes(datatype); var position3DAndHeightLength = 4; - var numTexCoordComponents = this.hasVertexNormals ? 3 : 2; - var stride = (this.hasVertexNormals ? 7 : 6) * sizeInBytes; + var numTexCoordComponents = 2; + + if (this.hasWebMercatorT) { + ++numTexCoordComponents; + } + + if (this.hasVertexNormals) { + ++numTexCoordComponents; + } + + stride = (position3DAndHeightLength + numTexCoordComponents) * sizeInBytes; + return [{ index : attributesNone.position3DAndHeight, vertexBuffer : buffer, @@ -294,14 +329,41 @@ define([ }]; } - var numComponents = 3; - numComponents += this.hasVertexNormals ? 1 : 0; - return [{ - index : attributes.compressed, - vertexBuffer : buffer, - componentDatatype : datatype, - componentsPerAttribute : numComponents - }]; + var numCompressed0 = 3; + var numCompressed1 = 0; + + if (this.hasWebMercatorT || this.hasVertexNormals) { + ++numCompressed0; + } + + if (this.hasWebMercatorT && this.hasVertexNormals) { + ++numCompressed1; + + stride = (numCompressed0 + numCompressed1) * sizeInBytes; + + return [{ + index : attributes.compressed0, + vertexBuffer : buffer, + componentDatatype : datatype, + componentsPerAttribute : numCompressed0, + offsetInBytes : 0, + strideInBytes : stride + }, { + index : attributes.compressed1, + vertexBuffer : buffer, + componentDatatype : datatype, + componentsPerAttribute : numCompressed1, + offsetInBytes : numCompressed0 * sizeInBytes, + strideInBytes : stride + }]; + } else { + return [{ + index : attributes.compressed0, + vertexBuffer : buffer, + componentDatatype : datatype, + componentsPerAttribute : numCompressed0 + }]; + } }; TerrainEncoding.prototype.getAttributeLocations = function() { @@ -325,6 +387,7 @@ define([ result.fromScaledENU = Matrix4.clone(encoding.fromScaledENU); result.matrix = Matrix4.clone(encoding.matrix); result.hasVertexNormals = encoding.hasVertexNormals; + result.hasWebMercatorT = encoding.hasWebMercatorT; return result; }; diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index 58dc795e919e..2a96d85b1222 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -148,13 +148,16 @@ define([ } } + vs.defines.push('INCLUDE_WEB_MERCATOR_Y'); + fs.defines.push('INCLUDE_WEB_MERCATOR_Y'); + if (enableFog) { vs.defines.push('FOG'); fs.defines.push('FOG'); } var computeDayColor = '\ - vec4 computeDayColor(vec4 initialColor, vec2 textureCoordinates)\n\ + vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates)\n\ {\n\ vec4 color = initialColor;\n'; @@ -163,7 +166,7 @@ define([ color = sampleAndBlend(\n\ color,\n\ u_dayTextures[' + i + '],\n\ - textureCoordinates,\n\ + u_dayTextureUseWebMercatorT[' + i + '] ? textureCoordinates.xz : textureCoordinates.xy,\n\ u_dayTextureTexCoordsRectangle[' + i + '],\n\ u_dayTextureTranslationAndScale[' + i + '],\n\ ' + (applyAlpha ? 'u_dayTextureAlpha[' + i + ']' : '1.0') + ',\n\ diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 9886ea4ec8be..3f96321df2f5 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -716,6 +716,9 @@ define([ u_dayTextureTexCoordsRectangle : function() { return this.properties.dayTextureTexCoordsRectangle; }, + u_dayTextureUseWebMercatorT : function() { + return this.properties.dayTextureUseWebMercatorT; + }, u_dayTextureAlpha : function() { return this.properties.dayTextureAlpha; }, @@ -772,6 +775,7 @@ define([ dayTextures : [], dayTextureTranslationAndScale : [], dayTextureTexCoordsRectangle : [], + dayTextureUseWebMercatorT : [], dayTextureAlpha : [], dayTextureBrightness : [], dayTextureContrast : [], @@ -924,7 +928,7 @@ define([ var enableFog = frameState.fog.enabled; var castShadows = ShadowMode.castShadows(tileProvider.shadows); var receiveShadows = ShadowMode.receiveShadows(tileProvider.shadows); - + if (showReflectiveOcean) { --maxTextures; } @@ -1078,19 +1082,38 @@ define([ var imagery = tileImagery.readyImagery; ++imageryIndex; - if (!defined(imagery) || imagery.state !== ImageryState.READY || imagery.imageryLayer.alpha === 0.0) { + if (!defined(imagery) || imagery.imageryLayer.alpha === 0.0) { continue; } + var texture = tileImagery.useWebMercatorT ? imagery.textureWebMercator : imagery.texture; + + //>>includeStart('debug', pragmas.debug); + if (!defined(texture)) { + // Our "ready" texture isn't actually ready. This should never happen. + // + // Side note: It IS possible for it to not be in the READY ImageryState, though. + // This can happen when a single imagery tile is shared by two terrain tiles (common) + // and one of them (A) needs a geographic version of the tile because it is near the poles, + // and the other (B) does not. B can and will transition the imagery tile to the READY state + // without reprojecting to geographic. Then, later, A will deem that same tile not-ready-yet + // because it only has the Web Mercator texture, and flip it back to the TRANSITIONING state. + // The imagery tile won't be in the READY state anymore, but it's still READY enough for B's + // purposes. + throw new DeveloperError('readyImagery is not actually ready!'); + } + //>>includeEnd('debug'); + var imageryLayer = imagery.imageryLayer; if (!defined(tileImagery.textureTranslationAndScale)) { tileImagery.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, tileImagery); } - uniformMapProperties.dayTextures[numberOfDayTextures] = imagery.texture; + uniformMapProperties.dayTextures[numberOfDayTextures] = texture; uniformMapProperties.dayTextureTranslationAndScale[numberOfDayTextures] = tileImagery.textureTranslationAndScale; uniformMapProperties.dayTextureTexCoordsRectangle[numberOfDayTextures] = tileImagery.textureCoordinateRectangle; + uniformMapProperties.dayTextureUseWebMercatorT[numberOfDayTextures] = tileImagery.useWebMercatorT; uniformMapProperties.dayTextureAlpha[numberOfDayTextures] = imageryLayer.alpha; applyAlpha = applyAlpha || uniformMapProperties.dayTextureAlpha[numberOfDayTextures] !== 1.0; diff --git a/Source/Scene/Imagery.js b/Source/Scene/Imagery.js index 2eb7ceb47db6..497248f0eb81 100644 --- a/Source/Scene/Imagery.js +++ b/Source/Scene/Imagery.js @@ -32,6 +32,7 @@ define([ this.imageUrl = undefined; this.image = undefined; this.texture = undefined; + this.textureWebMercator = undefined; this.credits = undefined; this.referenceCount = 0; @@ -71,6 +72,10 @@ define([ this.texture.destroy(); } + if (defined(this.textureWebMercator)) { + this.textureWebMercator.destroy(); + } + destroyObject(this); return 0; @@ -79,7 +84,7 @@ define([ return this.referenceCount; }; - Imagery.prototype.processStateMachine = function(frameState) { + Imagery.prototype.processStateMachine = function(frameState, needGeographicProjection) { if (this.state === ImageryState.UNLOADED) { this.state = ImageryState.TRANSITIONING; this.imageryLayer._requestImagery(this); @@ -90,9 +95,14 @@ define([ this.imageryLayer._createTexture(frameState.context, this); } - if (this.state === ImageryState.TEXTURE_LOADED) { + // If the imagery is already ready, but we need a geographic version and don't have it yet, + // we still need to do the reprojection step. This can happen if the Web Mercator version + // is fine initially, but the geographic one is needed later. + var needsReprojection = this.state === ImageryState.READY && needGeographicProjection && !this.texture; + + if (this.state === ImageryState.TEXTURE_LOADED || needsReprojection) { this.state = ImageryState.TRANSITIONING; - this.imageryLayer._reprojectTexture(frameState, this); + this.imageryLayer._reprojectTexture(frameState, this, needGeographicProjection); } }; diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index 7b68a411bdb4..1aa9d3c1aed6 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -18,6 +18,8 @@ define([ '../Core/Rectangle', '../Core/TerrainProvider', '../Core/TileProviderError', + '../Core/WebMercatorProjection', + '../Core/WebMercatorTilingScheme', '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/ClearCommand', @@ -60,6 +62,8 @@ define([ Rectangle, TerrainProvider, TileProviderError, + WebMercatorProjection, + WebMercatorTilingScheme, Buffer, BufferUsage, ClearCommand, @@ -356,6 +360,7 @@ define([ var imageryBoundsScratch = new Rectangle(); var tileImageryBoundsScratch = new Rectangle(); var clippedRectangleScratch = new Rectangle(); + var terrainRectangleScratch = new Rectangle(); /** * Computes the intersection of this layer's rectangle with the imagery provider's availability rectangle, @@ -415,6 +420,13 @@ define([ return true; } + // Use Web Mercator for our texture coordinate computations if this imagery layer uses + // that projection and the terrain tile falls entirely inside the valid bounds of the + // projection. + var useWebMercatorT = imageryProvider.tilingScheme instanceof WebMercatorTilingScheme && + tile.rectangle.north < WebMercatorProjection.MaximumLatitude && + tile.rectangle.south > -WebMercatorProjection.MaximumLatitude; + // 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 @@ -491,8 +503,8 @@ define([ // 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 veryCloseX = tile.rectangle.width / 512.0; + var veryCloseY = tile.rectangle.height / 512.0; var northwestTileRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel); if (Math.abs(northwestTileRectangle.south - tile.rectangle.north) < veryCloseY && northwestTileCoordinates.y < southeastTileCoordinates.y) { @@ -513,10 +525,23 @@ define([ // 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 terrainRectangle = Rectangle.clone(tile.rectangle, terrainRectangleScratch); var imageryRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel); var clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch); + var imageryTileXYToRectangle; + if (useWebMercatorT) { + imageryTilingScheme.rectangleToNativeRectangle(terrainRectangle, terrainRectangle); + imageryTilingScheme.rectangleToNativeRectangle(imageryRectangle, imageryRectangle); + imageryTilingScheme.rectangleToNativeRectangle(clippedImageryRectangle, clippedImageryRectangle); + imageryTilingScheme.rectangleToNativeRectangle(imageryBounds, imageryBounds); + imageryTileXYToRectangle = imageryTilingScheme.tileXYToNativeRectangle.bind(imageryTilingScheme); + veryCloseX = terrainRectangle.width / 512.0; + veryCloseY = terrainRectangle.height / 512.0; + } else { + imageryTileXYToRectangle = imageryTilingScheme.tileXYToRectangle.bind(imageryTilingScheme); + } + var minU; var maxU = 0.0; @@ -526,11 +551,11 @@ define([ // 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) { + if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.west - terrainRectangle.west) >= veryCloseX) { maxU = Math.min(1.0, (clippedImageryRectangle.west - terrainRectangle.west) / terrainRectangle.width); } - if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.north - tile.rectangle.north) >= veryCloseY) { + if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.north - terrainRectangle.north) >= veryCloseY) { minV = Math.max(0.0, (clippedImageryRectangle.north - terrainRectangle.south) / terrainRectangle.height); } @@ -539,8 +564,8 @@ define([ for ( var i = northwestTileCoordinates.x; i <= southeastTileCoordinates.x; i++) { minU = maxU; - imageryRectangle = imageryTilingScheme.tileXYToRectangle(i, northwestTileCoordinates.y, imageryLevel); - clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch); + imageryRectangle = imageryTileXYToRectangle(i, northwestTileCoordinates.y, imageryLevel); + clippedImageryRectangle = Rectangle.simpleIntersection(imageryRectangle, imageryBounds, clippedRectangleScratch); maxU = Math.min(1.0, (clippedImageryRectangle.east - terrainRectangle.west) / terrainRectangle.width); @@ -548,7 +573,7 @@ define([ // 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)) { + if (i === southeastTileCoordinates.x && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.east - terrainRectangle.east) < veryCloseX)) { maxU = 1.0; } @@ -557,21 +582,21 @@ define([ for ( var j = northwestTileCoordinates.y; j <= southeastTileCoordinates.y; j++) { maxV = minV; - imageryRectangle = imageryTilingScheme.tileXYToRectangle(i, j, imageryLevel); - clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch); + imageryRectangle = imageryTileXYToRectangle(i, j, imageryLevel); + clippedImageryRectangle = Rectangle.simpleIntersection(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)) { + if (j === southeastTileCoordinates.y && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.south - terrainRectangle.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)); + var imagery = this.getImageryFromCache(i, j, imageryLevel); + surfaceTile.imagery.splice(insertionPoint, 0, new TileImagery(imagery, texCoordsRectangle, useWebMercatorT)); ++insertionPoint; } } @@ -593,6 +618,13 @@ define([ ImageryLayer.prototype._calculateTextureTranslationAndScale = function(tile, tileImagery) { var imageryRectangle = tileImagery.readyImagery.rectangle; var terrainRectangle = tile.rectangle; + + if (tileImagery.useWebMercatorT) { + var tilingScheme = tileImagery.readyImagery.imageryLayer.imageryProvider.tilingScheme; + imageryRectangle = tilingScheme.rectangleToNativeRectangle(imageryRectangle, imageryBoundsScratch); + terrainRectangle = tilingScheme.rectangleToNativeRectangle(terrainRectangle, terrainRectangleScratch); + } + var terrainWidth = terrainRectangle.width; var terrainHeight = terrainRectangle.height; @@ -703,7 +735,11 @@ define([ pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB }); - imagery.texture = texture; + if (imageryProvider.tilingScheme instanceof WebMercatorTilingScheme) { + imagery.textureWebMercator = texture; + } else { + imagery.texture = texture; + } imagery.image = undefined; imagery.state = ImageryState.TEXTURE_LOADED; }; @@ -748,17 +784,21 @@ define([ * * @param {FrameState} frameState The frameState. * @param {Imagery} imagery The imagery instance to reproject. + * @param {Boolean} [needGeographicProjection=true] True to reproject to geographic, or false if Web Mercator is fine. */ - ImageryLayer.prototype._reprojectTexture = function(frameState, imagery) { - var texture = imagery.texture; + ImageryLayer.prototype._reprojectTexture = function(frameState, imagery, needGeographicProjection) { + var texture = imagery.textureWebMercator || imagery.texture; var rectangle = imagery.rectangle; var context = frameState.context; + needGeographicProjection = defaultValue(needGeographicProjection, true); + // 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) && + if (needGeographicProjection && + !(this._imageryProvider.tilingScheme instanceof GeographicTilingScheme) && rectangle.width / texture.width > 1e-5) { var that = this; imagery.addReference(); @@ -771,7 +811,6 @@ define([ reprojectToGeographic(command, context, texture, imagery.rectangle); }, postExecute : function(outputTexture) { - texture.destroy(); imagery.texture = outputTexture; finalizeReprojectTexture(that, context, imagery, outputTexture); imagery.releaseReference(); diff --git a/Source/Scene/TileImagery.js b/Source/Scene/TileImagery.js index 3a59c3031748..d7ef752a7191 100644 --- a/Source/Scene/TileImagery.js +++ b/Source/Scene/TileImagery.js @@ -16,12 +16,14 @@ define([ * @param {Imagery} imagery The imagery tile. * @param {Cartesian4} textureCoordinateRectangle The texture rectangle of the tile that is covered * by the imagery, where X=west, Y=south, Z=east, W=north. + * @param {Boolean} useWebMercatorT true to use the Web Mercator texture coordinates for this imagery tile. */ - function TileImagery(imagery, textureCoordinateRectangle) { + function TileImagery(imagery, textureCoordinateRectangle, useWebMercatorT) { this.readyImagery = undefined; this.loadingImagery = imagery; this.textureCoordinateRectangle = textureCoordinateRectangle; this.textureTranslationAndScale = undefined; + this.useWebMercatorT = useWebMercatorT; } /** @@ -48,7 +50,7 @@ define([ var loadingImagery = this.loadingImagery; var imageryLayer = loadingImagery.imageryLayer; - loadingImagery.processStateMachine(frameState); + loadingImagery.processStateMachine(frameState, !this.useWebMercatorT); if (loadingImagery.state === ImageryState.READY) { if (defined(this.readyImagery)) { @@ -90,7 +92,7 @@ define([ // Push the ancestor's load process along a bit. This is necessary because some ancestor imagery // tiles may not be attached directly to a terrain tile. Such tiles will never load if // we don't do it here. - closestAncestorThatNeedsLoading.processStateMachine(frameState); + closestAncestorThatNeedsLoading.processStateMachine(frameState, !this.useWebMercatorT); return false; // not done loading } else { // This imagery tile is failed or invalid, and we have the "best available" substitute. diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index f7a4efb66420..2ba201f9b050 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -5,6 +5,7 @@ uniform vec4 u_initialColor; #if TEXTURE_UNITS > 0 uniform sampler2D u_dayTextures[TEXTURE_UNITS]; uniform vec4 u_dayTextureTranslationAndScale[TEXTURE_UNITS]; +uniform bool u_dayTextureUseWebMercatorT[TEXTURE_UNITS]; #ifdef APPLY_ALPHA uniform float u_dayTextureAlpha[TEXTURE_UNITS]; @@ -49,7 +50,7 @@ uniform vec2 u_lightingFadeDistance; varying vec3 v_positionMC; varying vec3 v_positionEC; -varying vec2 v_textureCoordinates; +varying vec3 v_textureCoordinates; varying vec3 v_normalMC; varying vec3 v_normalEC; @@ -79,19 +80,19 @@ vec4 sampleAndBlend( // tileTextureCoordinates.t > textureCoordinateRectangle.q // In other words, the alpha is zero if the fragment is outside the rectangle // covered by this texture. Would an actual 'if' yield better performance? - vec2 alphaMultiplier = step(textureCoordinateRectangle.st, tileTextureCoordinates); + vec2 alphaMultiplier = step(textureCoordinateRectangle.st, tileTextureCoordinates); textureAlpha = textureAlpha * alphaMultiplier.x * alphaMultiplier.y; - + alphaMultiplier = step(vec2(0.0), textureCoordinateRectangle.pq - tileTextureCoordinates); textureAlpha = textureAlpha * alphaMultiplier.x * alphaMultiplier.y; - + vec2 translation = textureCoordinateTranslationAndScale.xy; vec2 scale = textureCoordinateTranslationAndScale.zw; vec2 textureCoordinates = tileTextureCoordinates * scale + translation; vec4 value = texture2D(texture, textureCoordinates); vec3 color = value.rgb; float alpha = value.a; - + #ifdef APPLY_BRIGHTNESS color = mix(vec3(0.0), color, textureBrightness); #endif @@ -118,7 +119,7 @@ vec4 sampleAndBlend( return vec4(outColor, outAlpha); } -vec4 computeDayColor(vec4 initialColor, vec2 textureCoordinates); +vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates); vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat3 enuToEye, vec4 imageryColor, float specularMapValue); void main() @@ -145,14 +146,14 @@ void main() #ifdef SHOW_REFLECTIVE_OCEAN vec2 waterMaskTranslation = u_waterMaskTranslationAndScale.xy; vec2 waterMaskScale = u_waterMaskTranslationAndScale.zw; - vec2 waterMaskTextureCoordinates = v_textureCoordinates * waterMaskScale + waterMaskTranslation; + vec2 waterMaskTextureCoordinates = v_textureCoordinates.xy * waterMaskScale + waterMaskTranslation; float mask = texture2D(u_waterMask, waterMaskTextureCoordinates).r; if (mask > 0.0) { mat3 enuToEye = czm_eastNorthUpToEyeCoordinates(v_positionMC, normalEC); - + vec2 ellipsoidTextureCoordinates = czm_ellipsoidWgs84TextureCoordinates(normalMC); vec2 ellipsoidFlippedTextureCoordinates = czm_ellipsoidWgs84TextureCoordinates(normalMC.zyx); @@ -182,7 +183,7 @@ void main() const float fExposure = 2.0; vec3 fogColor = v_mieColor + finalColor.rgb * v_rayleighColor; fogColor = vec3(1.0) - exp(-fExposure * fogColor); - + gl_FragColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor), finalColor.a); #else gl_FragColor = finalColor; @@ -210,7 +211,7 @@ const float oceanFrequencyLowAltitude = 825000.0; const float oceanAnimationSpeedLowAltitude = 0.004; const float oceanOneOverAmplitudeLowAltitude = 1.0 / 2.0; const float oceanSpecularIntensity = 0.5; - + // high altitude wave settings const float oceanFrequencyHighAltitude = 125000.0; const float oceanAnimationSpeedHighAltitude = 0.008; @@ -223,7 +224,7 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat // The double normalize below works around a bug in Firefox on Android devices. vec3 normalizedpositionToEyeEC = normalize(normalize(positionToEyeEC)); - + // Fade out the waves as the camera moves far from the surface. float waveIntensity = waveFade(70000.0, 1000000.0, positionToEyeECLength); @@ -232,20 +233,20 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat float time = czm_frameNumber * oceanAnimationSpeedHighAltitude; vec4 noise = czm_getWaterNoise(u_oceanNormalMap, textureCoordinates * oceanFrequencyHighAltitude, time, 0.0); vec3 normalTangentSpaceHighAltitude = vec3(noise.xy, noise.z * oceanOneOverAmplitudeHighAltitude); - + // low altitude waves time = czm_frameNumber * oceanAnimationSpeedLowAltitude; noise = czm_getWaterNoise(u_oceanNormalMap, textureCoordinates * oceanFrequencyLowAltitude, time, 0.0); vec3 normalTangentSpaceLowAltitude = vec3(noise.xy, noise.z * oceanOneOverAmplitudeLowAltitude); - + // blend the 2 wave layers based on distance to surface float highAltitudeFade = linearFade(0.0, 60000.0, positionToEyeECLength); float lowAltitudeFade = 1.0 - linearFade(20000.0, 60000.0, positionToEyeECLength); - vec3 normalTangentSpace = - (highAltitudeFade * normalTangentSpaceHighAltitude) + + vec3 normalTangentSpace = + (highAltitudeFade * normalTangentSpaceHighAltitude) + (lowAltitudeFade * normalTangentSpaceLowAltitude); normalTangentSpace = normalize(normalTangentSpace); - + // fade out the normal perturbation as we move farther from the water surface normalTangentSpace.xy *= waveIntensity; normalTangentSpace = normalize(normalTangentSpace); @@ -254,13 +255,13 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat #endif vec3 normalEC = enuToEye * normalTangentSpace; - + const vec3 waveHighlightColor = vec3(0.3, 0.45, 0.6); - + // Use diffuse light to highlight the waves float diffuseIntensity = czm_getLambertDiffuse(czm_sunDirectionEC, normalEC) * maskValue; vec3 diffuseHighlight = waveHighlightColor * diffuseIntensity; - + #ifdef SHOW_OCEAN_WAVES // Where diffuse light is low or non-existent, use wave highlights based solely on // the wave bumpiness and no particular light direction. @@ -274,8 +275,8 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat float specularIntensity = czm_getSpecular(czm_sunDirectionEC, normalizedpositionToEyeEC, normalEC, 10.0) + 0.25 * czm_getSpecular(czm_moonDirectionEC, normalizedpositionToEyeEC, normalEC, 10.0); float surfaceReflectance = mix(0.0, mix(u_zoomedOutOceanSpecularIntensity, oceanSpecularIntensity, waveIntensity), maskValue); float specular = specularIntensity * surfaceReflectance; - - return vec4(imageryColor.rgb + diffuseHighlight + nonDiffuseHighlight + specular, imageryColor.a); + + return vec4(imageryColor.rgb + diffuseHighlight + nonDiffuseHighlight + specular, imageryColor.a); } #endif // #ifdef SHOW_REFLECTIVE_OCEAN diff --git a/Source/Shaders/GlobeVS.glsl b/Source/Shaders/GlobeVS.glsl index 49cef12b40cd..aff0480f3a51 100644 --- a/Source/Shaders/GlobeVS.glsl +++ b/Source/Shaders/GlobeVS.glsl @@ -1,8 +1,9 @@ #ifdef QUANTIZATION_BITS12 -attribute vec4 compressed; +attribute vec4 compressed0; +attribute float compressed1; #else attribute vec4 position3DAndHeight; -attribute vec3 textureCoordAndEncodedNormals; +attribute vec4 textureCoordAndEncodedNormals; #endif uniform vec3 u_center3D; @@ -17,7 +18,7 @@ uniform vec2 u_southMercatorYAndOneOverHeight; varying vec3 v_positionMC; varying vec3 v_positionEC; -varying vec2 v_textureCoordinates; +varying vec3 v_textureCoordinates; varying vec3 v_normalMC; varying vec3 v_normalEC; @@ -55,7 +56,7 @@ float get2DMercatorYPositionFraction(vec2 textureCoordinates) float currentLatitude = mix(southLatitude, northLatitude, textureCoordinates.y); currentLatitude = clamp(currentLatitude, -czm_webMercatorMaxLatitude, czm_webMercatorMaxLatitude); positionFraction = czm_latitudeToWebMercatorFraction(currentLatitude, southMercatorY, oneOverMercatorHeight); - } + } return positionFraction; } @@ -97,29 +98,58 @@ uniform vec2 u_minMaxHeight; uniform mat4 u_scaleAndBias; #endif -void main() +void main() { #ifdef QUANTIZATION_BITS12 - vec2 xy = czm_decompressTextureCoordinates(compressed.x); - vec2 zh = czm_decompressTextureCoordinates(compressed.y); + vec2 xy = czm_decompressTextureCoordinates(compressed0.x); + vec2 zh = czm_decompressTextureCoordinates(compressed0.y); vec3 position = vec3(xy, zh.x); float height = zh.y; - vec2 textureCoordinates = czm_decompressTextureCoordinates(compressed.z); - float encodedNormal = compressed.w; + vec2 textureCoordinates = czm_decompressTextureCoordinates(compressed0.z); height = height * (u_minMaxHeight.y - u_minMaxHeight.x) + u_minMaxHeight.x; position = (u_scaleAndBias * vec4(position, 1.0)).xyz; + +#if defined(ENABLE_VERTEX_LIGHTING) && defined(INCLUDE_WEB_MERCATOR_Y) + float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x; + float encodedNormal = compressed1; +#elif defined(INCLUDE_WEB_MERCATOR_Y) + float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x; + float encodedNormal = 0.0; +#elif defined(ENABLE_VERTEX_LIGHTING) + float webMercatorT = textureCoordinates.y; + float encodedNormal = compressed0.w; #else + float webMercatorT = textureCoordinates.y; + float encodedNormal = 0.0; +#endif + +#else + // A single float per element vec3 position = position3DAndHeight.xyz; float height = position3DAndHeight.w; vec2 textureCoordinates = textureCoordAndEncodedNormals.xy; + +#if defined(ENABLE_VERTEX_LIGHTING) && defined(INCLUDE_WEB_MERCATOR_Y) + float webMercatorT = textureCoordAndEncodedNormals.z; + float encodedNormal = textureCoordAndEncodedNormals.w; +#elif defined(ENABLE_VERTEX_LIGHTING) + float webMercatorT = textureCoordinates.y; float encodedNormal = textureCoordAndEncodedNormals.z; +#elif defined(INCLUDE_WEB_MERCATOR_Y) + float webMercatorT = textureCoordAndEncodedNormals.z; + float encodedNormal = 0.0; +#else + float webMercatorT = textureCoordinates.y; + float encodedNormal = 0.0; +#endif + #endif vec3 position3DWC = position + u_center3D; gl_Position = getPosition(position, height, textureCoordinates); - v_textureCoordinates = textureCoordinates; + v_textureCoordinates = vec3(textureCoordinates, webMercatorT); #if defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL) v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz; @@ -130,7 +160,7 @@ void main() v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz; v_positionMC = position3DWC; // position in model coordinates #endif - + #ifdef FOG AtmosphereColor atmosColor = computeGroundAtmosphereFromSpace(position3DWC); v_mieColor = atmosColor.mie; diff --git a/Source/Workers/createVerticesFromQuantizedTerrainMesh.js b/Source/Workers/createVerticesFromQuantizedTerrainMesh.js index b32ed7d2521a..2dcd5d9d5cb5 100644 --- a/Source/Workers/createVerticesFromQuantizedTerrainMesh.js +++ b/Source/Workers/createVerticesFromQuantizedTerrainMesh.js @@ -15,6 +15,7 @@ define([ '../Core/OrientedBoundingBox', '../Core/TerrainEncoding', '../Core/Transforms', + '../Core/WebMercatorProjection', './createTaskProcessorWorker' ], function( AttributeCompression, @@ -32,6 +33,7 @@ define([ OrientedBoundingBox, TerrainEncoding, Transforms, + WebMercatorProjection, createTaskProcessorWorker) { 'use strict'; @@ -52,6 +54,7 @@ define([ var octEncodedNormals = parameters.octEncodedNormals; var edgeVertexCount = parameters.westIndices.length + parameters.eastIndices.length + parameters.southIndices.length + parameters.northIndices.length; + var includeWebMercatorT = parameters.includeWebMercatorT; var rectangle = parameters.rectangle; var west = rectangle.west; @@ -69,6 +72,13 @@ define([ var fromENU = Transforms.eastNorthUpToFixedFrame(center, ellipsoid); var toENU = Matrix4.inverseTransformation(fromENU, new Matrix4()); + var southMercatorY; + var oneOverMercatorHeight; + if (includeWebMercatorT) { + southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(south); + oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(north) - southMercatorY); + } + var uBuffer = quantizedVertices.subarray(0, quantizedVertexCount); var vBuffer = quantizedVertices.subarray(quantizedVertexCount, 2 * quantizedVertexCount); var heightBuffer = quantizedVertices.subarray(quantizedVertexCount * 2, 3 * quantizedVertexCount); @@ -77,6 +87,7 @@ define([ var uvs = new Array(quantizedVertexCount); var heights = new Array(quantizedVertexCount); var positions = new Array(quantizedVertexCount); + var webMercatorTs = includeWebMercatorT ? new Array(quantizedVertexCount) : []; var minimum = scratchMinimum; minimum.x = Number.POSITIVE_INFINITY; @@ -103,6 +114,10 @@ define([ heights[i] = height; positions[i] = position; + if (includeWebMercatorT) { + webMercatorTs[i] = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(cartographicScratch.latitude) - southMercatorY) * oneOverMercatorHeight; + } + Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch); Cartesian3.minimumByComponent(cartesian3Scratch, minimum, minimum); @@ -123,9 +138,9 @@ define([ hMin = Math.min(hMin, findMinMaxSkirts(parameters.southIndices, parameters.southSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum)); hMin = Math.min(hMin, findMinMaxSkirts(parameters.eastIndices, parameters.eastSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum)); hMin = Math.min(hMin, findMinMaxSkirts(parameters.northIndices, parameters.northSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum)); - + var aaBox = new AxisAlignedBoundingBox(minimum, maximum, center); - var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, hasVertexNormals); + var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, hasVertexNormals, includeWebMercatorT); var vertexStride = encoding.getStride(); var size = quantizedVertexCount * vertexStride + edgeVertexCount * vertexStride; var vertexBuffer = new Float32Array(size); @@ -153,7 +168,7 @@ define([ } } - bufferIndex = encoding.encode(vertexBuffer, bufferIndex, positions[j], uvs[j], heights[j], toPack); + bufferIndex = encoding.encode(vertexBuffer, bufferIndex, positions[j], uvs[j], heights[j], toPack, webMercatorTs[j]); } var edgeTriangleCount = Math.max(0, (edgeVertexCount - 4) * 2); @@ -164,13 +179,13 @@ define([ // Add skirts. var vertexBufferIndex = quantizedVertexCount * vertexStride; var indexBufferIndex = parameters.indices.length; - indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.westIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.westSkirtHeight, true, exaggeration); + indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.westIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.westSkirtHeight, true, exaggeration, southMercatorY, oneOverMercatorHeight); vertexBufferIndex += parameters.westIndices.length * vertexStride; - indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.southIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.southSkirtHeight, false, exaggeration); + indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.southIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.southSkirtHeight, false, exaggeration, southMercatorY, oneOverMercatorHeight); vertexBufferIndex += parameters.southIndices.length * vertexStride; - indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.eastIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.eastSkirtHeight, false, exaggeration); + indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.eastIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.eastSkirtHeight, false, exaggeration, southMercatorY, oneOverMercatorHeight); vertexBufferIndex += parameters.eastIndices.length * vertexStride; - addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.northIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.northSkirtHeight, true, exaggeration); + addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.northIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.northSkirtHeight, true, exaggeration, southMercatorY, oneOverMercatorHeight); transferableObjects.push(vertexBuffer.buffer, indexBuffer.buffer); @@ -221,7 +236,7 @@ define([ return hMin; } - function addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, edgeVertices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, skirtLength, isWestOrNorthEdge, exaggeration) { + function addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, edgeVertices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, skirtLength, isWestOrNorthEdge, exaggeration, southMercatorY, oneOverMercatorHeight) { var start, end, increment; if (isWestOrNorthEdge) { start = edgeVertices.length - 1; @@ -280,7 +295,12 @@ define([ } } - vertexBufferIndex = encoding.encode(vertexBuffer, vertexBufferIndex, position, uv, cartographicScratch.height, toPack); + var webMercatorT; + if (encoding.hasWebMercatorT) { + webMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(cartographicScratch.latitude) - southMercatorY) * oneOverMercatorHeight; + } + + vertexBufferIndex = encoding.encode(vertexBuffer, vertexBufferIndex, position, uv, cartographicScratch.height, toPack, webMercatorT); if (previousIndex !== -1) { indexBuffer[indexBufferIndex++] = previousIndex; diff --git a/Specs/Scene/ImageryLayerSpec.js b/Specs/Scene/ImageryLayerSpec.js index 4a4bb02249a3..94ddc52ac0a2 100644 --- a/Specs/Scene/ImageryLayerSpec.js +++ b/Specs/Scene/ImageryLayerSpec.js @@ -156,7 +156,7 @@ defineSuite([ }); } - it('reprojects web mercator images', function() { + it('reprojects web mercator images when necessary', function() { var provider = createWebMercatorProvider(); var layer = new ImageryLayer(provider); @@ -175,14 +175,15 @@ defineSuite([ return pollToPromise(function() { return imagery.state === ImageryState.TEXTURE_LOADED; }).then(function() { - var textureBeforeReprojection = imagery.texture; - layer._reprojectTexture(scene.frameState, imagery); + var textureBeforeReprojection = imagery.textureWebMercator; + layer._reprojectTexture(scene.frameState, imagery, true); layer.queueReprojectionCommands(scene.frameState); scene.frameState.commandList[0].execute(computeEngine); return pollToPromise(function() { return imagery.state === ImageryState.READY; }).then(function() { + expect(imagery.texture).toBeDefined(); expect(textureBeforeReprojection).not.toEqual(imagery.texture); imagery.releaseReference(); }); @@ -191,6 +192,87 @@ defineSuite([ }); }); + it('does not reproject web mercator images when not necessary', function() { + var provider = createWebMercatorProvider(); + var layer = new ImageryLayer(provider); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + var imagery = new Imagery(layer, 0, 1, 3); + imagery.addReference(); + layer._requestImagery(imagery); + + return pollToPromise(function() { + return imagery.state === ImageryState.RECEIVED; + }).then(function() { + layer._createTexture(scene.context, imagery); + + return pollToPromise(function() { + return imagery.state === ImageryState.TEXTURE_LOADED; + }).then(function() { + expect(imagery.textureWebMercator).toBeDefined(); + layer._reprojectTexture(scene.frameState, imagery, false); + layer.queueReprojectionCommands(scene.frameState); + expect(scene.frameState.commandList.length).toBe(0); + + return pollToPromise(function() { + return imagery.state === ImageryState.READY; + }).then(function() { + expect(imagery.texture).not.toBeDefined(); + imagery.releaseReference(); + }); + }); + }); + }); + }); + + it('reprojects web mercator images later if it becomes necessary later', function() { + var provider = createWebMercatorProvider(); + var layer = new ImageryLayer(provider); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + var imagery = new Imagery(layer, 0, 1, 3); + imagery.addReference(); + layer._requestImagery(imagery); + + return pollToPromise(function() { + return imagery.state === ImageryState.RECEIVED; + }).then(function() { + layer._createTexture(scene.context, imagery); + + return pollToPromise(function() { + return imagery.state === ImageryState.TEXTURE_LOADED; + }).then(function() { + var textureBeforeReprojection = imagery.textureWebMercator; + layer._reprojectTexture(scene.frameState, imagery, false); + layer.queueReprojectionCommands(scene.frameState); + expect(scene.frameState.commandList.length).toBe(0); + + return pollToPromise(function() { + return imagery.state === ImageryState.READY; + }).then(function() { + expect(imagery.texture).not.toBeDefined(); + + layer._reprojectTexture(scene.frameState, imagery, true); + layer.queueReprojectionCommands(scene.frameState); + scene.frameState.commandList[0].execute(computeEngine); + + return pollToPromise(function() { + return imagery.state === ImageryState.READY; + }).then(function() { + expect(imagery.texture).toBeDefined(); + expect(textureBeforeReprojection).not.toEqual(imagery.texture); + imagery.releaseReference(); + }); + }); + }); + }); + }); + }); + it('cancels reprojection', function() { var provider = createWebMercatorProvider(); var layer = new ImageryLayer(provider);