diff --git a/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html new file mode 100644 index 000000000000..d2a18ce874a7 --- /dev/null +++ b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html @@ -0,0 +1,87 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.jpg b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.jpg new file mode 100644 index 000000000000..db87f75e3b4c Binary files /dev/null and b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.jpg differ diff --git a/CHANGES.md b/CHANGES.md index d1fb853b1431..3865aa72db61 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Change Log ========== +### 1.50 - 2018-10-01 + +##### Additions :tada: +* Added `cartographicLimitRectangle` to `Globe`. Use this to limit terrain and imagery to a specific `Rectangle` area. [#6987](https://github.com/AnalyticalGraphicsInc/cesium/pull/6987) + ### 1.49 - 2018-09-04 ##### Breaking Changes :mega: diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 5acc74e2413f..cfc30e5adbf6 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -277,6 +277,14 @@ define([ this._surface.tileProvider.clippingPlanes = value; } }, + cartographicLimitRectangle : { + get : function() { + return this._surface.tileProvider.cartographicLimitRectangle; + }, + set : function(value) { + this._surface.tileProvider.cartographicLimitRectangle = value; + } + }, /** * The normal map to use for rendering waves in the ocean. Setting this property will * only have an effect if the configured terrain provider includes a water mask. diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index e80826269f36..2a8b5b24df08 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -66,7 +66,7 @@ define([ return useWebMercatorProjection ? get2DYPositionFractionMercatorProjection : get2DYPositionFractionGeographicProjection; } - GlobeSurfaceShaderSet.prototype.getShaderProgram = function(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog, enableClippingPlanes, clippingPlanes) { + GlobeSurfaceShaderSet.prototype.getShaderProgram = function(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog, enableClippingPlanes, clippingPlanes, clippedByBoundaries) { var quantization = 0; var quantizationDefine = ''; @@ -84,6 +84,13 @@ define([ vertexLogDepthDefine = 'DISABLE_GL_POSITION_LOG_DEPTH'; } + var cartographicLimitRectangleFlag = 0; + var cartographicLimitRectangleDefine = ''; + if (clippedByBoundaries) { + cartographicLimitRectangleFlag = 1; + cartographicLimitRectangleDefine = 'TILE_LIMIT_RECTANGLE'; + } + var sceneMode = frameState.mode; var flags = sceneMode | (applyBrightness << 2) | @@ -101,7 +108,8 @@ define([ (quantization << 14) | (applySplit << 15) | (enableClippingPlanes << 16) | - (vertexLogDepth << 17); + (vertexLogDepth << 17) | + (cartographicLimitRectangleFlag << 18); var currentClippingShaderState = 0; if (defined(clippingPlanes)) { @@ -134,7 +142,7 @@ define([ } vs.defines.push(quantizationDefine, vertexLogDepthDefine); - fs.defines.push('TEXTURE_UNITS ' + numberOfDayTextures); + fs.defines.push('TEXTURE_UNITS ' + numberOfDayTextures, cartographicLimitRectangleDefine); if (applyBrightness) { fs.defines.push('APPLY_BRIGHTNESS'); diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js index 46bba1be5100..579d73a1bf22 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -81,6 +81,8 @@ define([ this.surfaceShader = undefined; this.isClipped = true; + + this.clippedByBoundaries = false; } defineProperties(GlobeSurfaceTile.prototype, { diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 0d194c545727..6823dd8bd2e6 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -4,6 +4,7 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', + '../Core/Cartographic', '../Core/Color', '../Core/ColorGeometryInstanceAttribute', '../Core/combine', @@ -50,6 +51,7 @@ define([ Cartesian2, Cartesian3, Cartesian4, + Cartographic, Color, ColorGeometryInstanceAttribute, combine, @@ -168,6 +170,12 @@ define([ * @private */ this._clippingPlanes = undefined; + + /** + * A property specifying a {@link Rectangle} used to selectively limit terrain and imagery rendering. + * @type {Rectangle} + */ + this.cartographicLimitRectangle = Rectangle.clone(Rectangle.MAX_VALUE); } defineProperties(GlobeSurfaceTileProvider.prototype, { @@ -509,6 +517,24 @@ define([ }; var boundingSphereScratch = new BoundingSphere(); + var rectangleIntersectionScratch = new Rectangle(); + var splitCartographicLimitRectangleScratch = new Rectangle(); + var rectangleCenterScratch = new Cartographic(); + + // cartographicLimitRectangle may span the IDL, but tiles never will. + function clipRectangleAntimeridian(tileRectangle, cartographicLimitRectangle) { + if (cartographicLimitRectangle.west < cartographicLimitRectangle.east) { + return cartographicLimitRectangle; + } + var splitRectangle = Rectangle.clone(cartographicLimitRectangle, splitCartographicLimitRectangleScratch); + var tileCenter = Rectangle.center(tileRectangle, rectangleCenterScratch); + if (tileCenter.longitude > 0.0) { + splitRectangle.east = CesiumMath.PI; + } else { + splitRectangle.west = -CesiumMath.PI; + } + return splitRectangle; + } /** * Determines the visibility of a given tile. The tile may be fully visible, partially visible, or not @@ -536,6 +562,17 @@ define([ var cullingVolume = frameState.cullingVolume; var boundingVolume = defaultValue(surfaceTile.orientedBoundingBox, surfaceTile.boundingSphere3D); + // Check if the tile is outside the limit area in cartographic space + surfaceTile.clippedByBoundaries = false; + var clippedCartographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, this.cartographicLimitRectangle); + var areaLimitIntersection = Rectangle.simpleIntersection(clippedCartographicLimitRectangle, tile.rectangle, rectangleIntersectionScratch); + if (!defined(areaLimitIntersection)) { + return Visibility.NONE; + } + if (!Rectangle.equals(areaLimitIntersection, tile.rectangle)) { + surfaceTile.clippedByBoundaries = true; + } + if (frameState.mode !== SceneMode.SCENE3D) { boundingVolume = boundingSphereScratch; BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume); @@ -580,6 +617,7 @@ define([ var modifiedModelViewScratch = new Matrix4(); var modifiedModelViewProjectionScratch = new Matrix4(); var tileRectangleScratch = new Cartesian4(); + var localizedCartographicLimitRectangleScratch = new Cartesian4(); var rtcScratch = new Cartesian3(); var centerEyeScratch = new Cartesian3(); var southwestScratch = new Cartesian3(); @@ -921,6 +959,9 @@ define([ } return frameState.context.defaultTexture; }, + u_cartographicLimitRectangle : function() { + return this.properties.localizedCartographicLimitRectangle; + }, u_clippingPlanesMatrix : function() { var clippingPlanes = globeSurfaceTileProvider._clippingPlanes; return defined(clippingPlanes) ? Matrix4.multiply(frameState.context.uniformState.view, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix) : Matrix4.IDENTITY; @@ -969,7 +1010,9 @@ define([ minMaxHeight : new Cartesian2(), scaleAndBias : new Matrix4(), clippingPlanesEdgeColor : Color.clone(Color.WHITE), - clippingPlanesEdgeWidth : 0.0 + clippingPlanesEdgeWidth : 0.0, + + localizedCartographicLimitRectangle : new Cartesian4() } }; @@ -1255,6 +1298,20 @@ define([ uniformMapProperties.southMercatorYAndOneOverHeight.x = southMercatorY; uniformMapProperties.southMercatorYAndOneOverHeight.y = oneOverMercatorHeight; + // Convert tile limiter rectangle from cartographic to texture space using the tileRectangle. + var localizedCartographicLimitRectangle = localizedCartographicLimitRectangleScratch; + var cartographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, tileProvider.cartographicLimitRectangle); + + var cartographicTileRectangle = tile.rectangle; + var inverseTileWidth = 1.0 / cartographicTileRectangle.width; + var inverseTileHeight = 1.0 / cartographicTileRectangle.height; + localizedCartographicLimitRectangle.x = (cartographicLimitRectangle.west - cartographicTileRectangle.west) * inverseTileWidth; + localizedCartographicLimitRectangle.y = (cartographicLimitRectangle.south - cartographicTileRectangle.south) * inverseTileHeight; + localizedCartographicLimitRectangle.z = (cartographicLimitRectangle.east - cartographicTileRectangle.west) * inverseTileWidth; + localizedCartographicLimitRectangle.w = (cartographicLimitRectangle.north - cartographicTileRectangle.south) * inverseTileHeight; + + Cartesian4.clone(localizedCartographicLimitRectangle, uniformMapProperties.localizedCartographicLimitRectangle); + // For performance, use fog in the shader only when the tile is in fog. var applyFog = enableFog && CesiumMath.fog(tile._distance, frameState.fog.density) > CesiumMath.EPSILON3; @@ -1357,7 +1414,7 @@ define([ uniformMap = combine(uniformMap, tileProvider.uniformMap); } - command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled, clippingPlanes); + command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled, clippingPlanes, surfaceTile.clippedByBoundaries); command.castShadows = castShadows; command.receiveShadows = receiveShadows; command.renderState = renderState; diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index cb7eb85310c4..fcae7dcb54c5 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -51,6 +51,10 @@ uniform sampler2D u_oceanNormalMap; uniform vec2 u_lightingFadeDistance; #endif +#ifdef TILE_LIMIT_RECTANGLE +uniform vec4 u_cartographicLimitRectangle; +#endif + #ifdef ENABLE_CLIPPING_PLANES uniform sampler2D u_clippingPlanes; uniform mat4 u_clippingPlanesMatrix; @@ -155,6 +159,15 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat void main() { + +#ifdef TILE_LIMIT_RECTANGLE + if (v_textureCoordinates.x < u_cartographicLimitRectangle.x || u_cartographicLimitRectangle.z < v_textureCoordinates.x || + v_textureCoordinates.y < u_cartographicLimitRectangle.y || u_cartographicLimitRectangle.w < v_textureCoordinates.y) + { + discard; + } +#endif + #ifdef ENABLE_CLIPPING_PLANES float clipDistance = clip(gl_FragCoord, u_clippingPlanes, u_clippingPlanesMatrix); #endif diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 00919c6f7c80..4a1b25930027 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -8,7 +8,6 @@ defineSuite([ 'Core/Ellipsoid', 'Core/EllipsoidTerrainProvider', 'Core/GeographicProjection', - 'Core/Intersect', 'Core/Rectangle', 'Core/WebMercatorProjection', 'Renderer/ContextLimits', @@ -39,7 +38,6 @@ defineSuite([ Ellipsoid, EllipsoidTerrainProvider, GeographicProjection, - Intersect, Rectangle, WebMercatorProjection, ContextLimits, @@ -116,6 +114,7 @@ defineSuite([ afterEach(function() { scene.imageryLayers.removeAll(); + scene.primitives.removeAll(); }); it('conforms to QuadtreeTileProvider interface', function() { @@ -939,4 +938,52 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('cartographicLimitRectangle selectively enables rendering globe surface', function() { + expect(scene).toRender([0, 0, 0, 255]); + switchViewMode(SceneMode.COLUMBUS_VIEW, new GeographicProjection(Ellipsoid.WGS84)); + var result; + return updateUntilDone(scene.globe).then(function() { + expect(scene).notToRender([0, 0, 0, 255]); + expect(scene).toRenderAndCall(function(rgba) { + result = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + }); + scene.globe.cartographicLimitRectangle = Rectangle.fromDegrees(-2, -2, -1, -1); + expect(scene).notToRender(result); + scene.camera.setView({ + destination : scene.globe.cartographicLimitRectangle + }); + return updateUntilDone(scene.globe); + }) + .then(function() { + expect(scene).toRender(result); + }); + }); + + it('cartographicLimitRectangle culls tiles outside the region', function() { + switchViewMode(SceneMode.COLUMBUS_VIEW, new GeographicProjection(Ellipsoid.WGS84)); + var unculledCommandCount; + return updateUntilDone(scene.globe).then(function() { + unculledCommandCount = scene.frameState.commandList.length; + scene.globe.cartographicLimitRectangle = Rectangle.fromDegrees(-2, -2, -1, -1); + return updateUntilDone(scene.globe); + }) + .then(function() { + expect(unculledCommandCount).toBeGreaterThan(scene.frameState.commandList.length); + }); + }); + + it('cartographicLimitRectangle may cross the antimeridian', function() { + switchViewMode(SceneMode.SCENE2D, new GeographicProjection(Ellipsoid.WGS84)); + var unculledCommandCount; + return updateUntilDone(scene.globe).then(function() { + unculledCommandCount = scene.frameState.commandList.length; + scene.globe.cartographicLimitRectangle = Rectangle.fromDegrees(179, -2, -179, -1); + return updateUntilDone(scene.globe); + }) + .then(function() { + expect(unculledCommandCount).toBeGreaterThan(scene.frameState.commandList.length); + }); + }); + }, 'WebGL');