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');