diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js index 71abb17fc627..1f3880728728 100644 --- a/Apps/CesiumViewer/CesiumViewer.js +++ b/Apps/CesiumViewer/CesiumViewer.js @@ -104,7 +104,10 @@ define([ } else if (/\.geojson$/i.test(source) || /\.json$/i.test(source) || /\.topojson$/i.test(source)) { loadPromise = GeoJsonDataSource.load(source); } else if (/\.kml$/i.test(source) || /\.kmz$/i.test(source)) { - loadPromise = KmlDataSource.load(source); + loadPromise = KmlDataSource.load(source, { + camera: scene.camera, + canvas: scene.canvas + }); } else { showLoadError(source, 'Unknown format.'); } diff --git a/Apps/Sandcastle/gallery/Atmosphere Color.html b/Apps/Sandcastle/gallery/Atmosphere Color.html new file mode 100644 index 000000000000..f58f0517dec1 --- /dev/null +++ b/Apps/Sandcastle/gallery/Atmosphere Color.html @@ -0,0 +1,124 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + + + + + + + + + + + +
hueShift + + +
saturationShift + + +
brightnessShift + + +
+
+
+
+ + + diff --git a/Apps/Sandcastle/gallery/Atmosphere Color.jpg b/Apps/Sandcastle/gallery/Atmosphere Color.jpg new file mode 100644 index 000000000000..fc09d290ce4a Binary files /dev/null and b/Apps/Sandcastle/gallery/Atmosphere Color.jpg differ diff --git a/Apps/Sandcastle/gallery/CZML Billboard and Label.html b/Apps/Sandcastle/gallery/CZML Billboard and Label.html index aeb9db551321..dc6e1521d663 100644 --- a/Apps/Sandcastle/gallery/CZML Billboard and Label.html +++ b/Apps/Sandcastle/gallery/CZML Billboard and Label.html @@ -26,54 +26,52 @@ diff --git a/Apps/Sandcastle/gallery/CZML Circles and Ellipses.html b/Apps/Sandcastle/gallery/CZML Circles and Ellipses.html index 44869d48ee57..e9fd0a46f533 100644 --- a/Apps/Sandcastle/gallery/CZML Circles and Ellipses.html +++ b/Apps/Sandcastle/gallery/CZML Circles and Ellipses.html @@ -1,20 +1,20 @@ - - - - - - Cesium Demo - - - + + + + + + Cesium Demo + + + +
+

Loading...

+
+ + + \ No newline at end of file diff --git a/Apps/Sandcastle/gallery/Rotatable 2D Map.jpg b/Apps/Sandcastle/gallery/Rotatable 2D Map.jpg new file mode 100644 index 000000000000..3d3d8f591522 Binary files /dev/null and b/Apps/Sandcastle/gallery/Rotatable 2D Map.jpg differ diff --git a/CHANGES.md b/CHANGES.md index 106cda0bc0a5..7f5bc7b50672 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,17 @@ Change Log ========== +### 1.23 - 2016-07-01 + +* Add a `rotatable2D` option to to `Scene`, `CesiumWidget` and `Viewer` to enable a rotatable map in 2D. [#3897](https://github.com/AnalyticalGraphicsInc/cesium/issues/3897) +* `Camera.setView` and `Camera.flyTo` will now use the `orientation.heading` parameter in 2D if the map is rotatable. + ### 1.22 - 2016-06-01 +* Breaking changes + * `KmlDataSource` now requires `options.camera` and `options.canvas`. * Added shadows + * See the Sandcastle demo: [Shadows](http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Shadows.html&label=Showcases). * Added `Viewer.shadows` and `Viewer.terrainShadows`. Both are off by default. * Added `Viewer.shadowMap` and `Scene.shadowMap` for accessing the scene's shadow map. * Added `castShadows` and `receiveShadows` properties to `Model` and `Entity.model`, and options to the `Model` constructor and `Model.fromGltf`. @@ -16,6 +24,7 @@ Change Log * Added `VelocityVectorProperty` so billboard's aligned axis can follow the velocity vector. [#3908](https://github.com/AnalyticalGraphicsInc/cesium/issues/3908) * Improve memory management for entity billboard/label/point/path visualization. * Added `terrainProviderChanged` event to `Scene` and `Globe` +* Added support for hue, saturation, and brightness color shifts in the atmosphere in `SkyAtmosphere`. See the new Sandcastle example: [Atmosphere Color](http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Atmosphere%20Color.html&label=Showcases). [#3439](https://github.com/AnalyticalGraphicsInc/cesium/issues/3439) * Fixed exaggerated terrain tiles disappearing. [#3676](https://github.com/AnalyticalGraphicsInc/cesium/issues/3676) * Fixed a bug that could cause incorrect normals to be computed for exaggerated terrain, especially for low-detail tiles. [#3904](https://github.com/AnalyticalGraphicsInc/cesium/pull/3904) * Fixed a bug that was causing errors to be thrown when picking and terrain was enabled. [#3779](https://github.com/AnalyticalGraphicsInc/cesium/issues/3779) @@ -31,6 +40,7 @@ Change Log * Added `Matrix4.computeView`. * Added `CullingVolume.fromBoundingSphere`. * Added `debugShowShadowVolume` to `GroundPrimitive`. +* Fix issue with disappearing tiles on Linux. [#3889](https://github.com/AnalyticalGraphicsInc/cesium/issues/3889) ### 1.21 - 2016-05-02 diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index ed9ab60ae96d..15c6b332ee08 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -51,7 +51,7 @@ define([ * var halfAxes = Cesium.Matrix3.fromScale(new Cesium.Cartesian3(1.0, 3.0, 2.0), new Cesium.Matrix3()); * * var obb = new Cesium.OrientedBoundingBox(center, halfAxes); - * + * * @see BoundingSphere * @see BoundingRectangle */ @@ -75,6 +75,7 @@ define([ var scratchCartesian3 = new Cartesian3(); var scratchCartesian4 = new Cartesian3(); var scratchCartesian5 = new Cartesian3(); + var scratchCartesian6 = new Cartesian3(); var scratchCovarianceResult = new Matrix3(); var scratchEigenResult = { unitary : new Matrix3(), @@ -152,26 +153,41 @@ define([ covarianceMatrix[8] = ezz; var eigenDecomposition = Matrix3.computeEigenDecomposition(covarianceMatrix, scratchEigenResult); - var rotation = Matrix3.transpose(eigenDecomposition.unitary, result.halfAxes); + var rotation = Matrix3.clone(eigenDecomposition.unitary, result.halfAxes); - p = Cartesian3.subtract(positions[0], meanPoint, scratchCartesian2); - var tempPoint = Matrix3.multiplyByVector(rotation, p, scratchCartesian3); - var maxPoint = Cartesian3.clone(tempPoint, scratchCartesian4); - var minPoint = Cartesian3.clone(tempPoint, scratchCartesian5); + var v1 = Matrix3.getColumn(rotation, 0, scratchCartesian4); + var v2 = Matrix3.getColumn(rotation, 1, scratchCartesian5); + var v3 = Matrix3.getColumn(rotation, 2, scratchCartesian6); - for (i = 1; i < length; i++) { - p = Cartesian3.subtract(positions[i], meanPoint, p); - Matrix3.multiplyByVector(rotation, p, tempPoint); - Cartesian3.minimumByComponent(minPoint, tempPoint, minPoint); - Cartesian3.maximumByComponent(maxPoint, tempPoint, maxPoint); + var u1 = -Number.MAX_VALUE; + var u2 = -Number.MAX_VALUE; + var u3 = -Number.MAX_VALUE; + var l1 = Number.MAX_VALUE; + var l2 = Number.MAX_VALUE; + var l3 = Number.MAX_VALUE; + + for (i = 0; i < length; i++) { + p = positions[i]; + u1 = Math.max(Cartesian3.dot(v1, p), u1); + u2 = Math.max(Cartesian3.dot(v2, p), u2); + u3 = Math.max(Cartesian3.dot(v3, p), u3); + + l1 = Math.min(Cartesian3.dot(v1, p), l1); + l2 = Math.min(Cartesian3.dot(v2, p), l2); + l3 = Math.min(Cartesian3.dot(v3, p), l3); } - var center = Cartesian3.add(minPoint, maxPoint, scratchCartesian3); - Cartesian3.multiplyByScalar(center, 0.5, center); - Matrix3.multiplyByVector(rotation, center, center); - Cartesian3.add(meanPoint, center, result.center); + v1 = Cartesian3.multiplyByScalar(v1, 0.5 * (l1 + u1), v1); + v2 = Cartesian3.multiplyByScalar(v2, 0.5 * (l2 + u2), v2); + v3 = Cartesian3.multiplyByScalar(v3, 0.5 * (l3 + u3), v3); + + var center = Cartesian3.add(v1, v2, result.center); + center = Cartesian3.add(center, v3, center); - var scale = Cartesian3.subtract(maxPoint, minPoint, scratchCartesian3); + var scale = scratchCartesian3; + scale.x = u1 - l1; + scale.y = u2 - l2; + scale.z = u3 - l3; Cartesian3.multiplyByScalar(scale, 0.5, scale); Matrix3.multiplyByScale(result.halfAxes, scale, result.halfAxes); diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index 31ae7448e2a9..7a2b15ebdb97 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -12,7 +12,6 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/deprecationWarning', '../Core/DeveloperError', '../Core/Ellipsoid', '../Core/Event', @@ -70,7 +69,6 @@ define([ defaultValue, defined, defineProperties, - deprecationWarning, DeveloperError, Ellipsoid, Event, @@ -2129,21 +2127,18 @@ define([ * }); */ function KmlDataSource(options) { - var showWarning = false; options = defaultValue(options, {}); - if (defined(options.getURL)) { - showWarning = true; - var proxy = options; - options = { - proxy: proxy - }; - } else if (!defined(options.camera) || !defined(options.canvas)) { - showWarning = true; - } + var camera = options.camera; + var canvas = options.canvas; - if (showWarning) { - deprecationWarning('KmlDataSource', 'KmlDataSource now longer takes a proxy object. It takes an options object with camera and canvas as required properties. This will throw in Cesium 1.22.'); + //>>includeStart('debug', pragmas.debug); + if (!defined(camera)) { + throw new DeveloperError('options.camera is required.'); + } + if (!defined(canvas)) { + throw new DeveloperError('options.canvas is required.'); } + //>>includeEnd('debug'); this._changed = new Event(); this._error = new Event(); @@ -2158,9 +2153,6 @@ define([ this._promises = []; this._networkLinks = new AssociativeArray(); - var camera = options.camera; - var canvas = options.canvas; - this._canvas = canvas; this._camera = camera; this._lastCameraView = { diff --git a/Source/Scene/Camera.js b/Source/Scene/Camera.js index e3ce37673142..ce2afd082abe 100644 --- a/Source/Scene/Camera.js +++ b/Source/Scene/Camera.js @@ -24,6 +24,7 @@ define([ '../Core/Rectangle', '../Core/Transforms', './CameraFlightPath', + './MapMode2D', './PerspectiveFrustum', './SceneMode' ], function( @@ -51,6 +52,7 @@ define([ Rectangle, Transforms, CameraFlightPath, + MapMode2D, PerspectiveFrustum, SceneMode) { 'use strict'; @@ -186,18 +188,11 @@ define([ this.constrainedAxis = undefined; /** * The factor multiplied by the the map size used to determine where to clamp the camera position - * when translating across the surface. The default is 1.5. Only valid for 2D and Columbus view. + * when zooming out from the surface. The default is 1.5. Only valid for 2D and the map is rotatable. * @type {Number} * @default 1.5 */ - this.maximumTranslateFactor = 1.5; - /** - * The factor multiplied by the the map size used to determine where to clamp the camera position - * when zooming out from the surface. The default is 2.5. Only valid for 2D. - * @type {Number} - * @default 2.5 - */ - this.maximumZoomFactor = 2.5; + this.maximumZoomFactor = 1.5; this._moveStart = new Event(); this._moveEnd = new Event(); @@ -624,7 +619,7 @@ define([ * @readonly */ heading : { - get : function () { + get : function() { if (this._mode !== SceneMode.MORPHING) { var ellipsoid = this._projection.ellipsoid; @@ -743,8 +738,7 @@ define([ var frustum = this._max2Dfrustum = this.frustum.clone(); //>>includeStart('debug', pragmas.debug); - if (!defined(frustum.left) || !defined(frustum.right) || - !defined(frustum.top) || !defined(frustum.bottom)) { + if (!defined(frustum.left) || !defined(frustum.right) || !defined(frustum.top) || !defined(frustum.bottom)) { throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.'); } //>>includeEnd('debug'); @@ -831,7 +825,10 @@ define([ camera._setTransform(currentTransform); } - function setView2D(camera, position, convert) { + function setView2D(camera, position, heading, convert) { + var pitch = -CesiumMath.PI_OVER_TWO; + var roll = 0.0; + var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1); camera._setTransform(Matrix4.IDENTITY); @@ -857,6 +854,14 @@ define([ } } + if (camera._scene.mapMode2D === MapMode2D.ROTATE) { + var rotQuat = Quaternion.fromHeadingPitchRoll(heading - CesiumMath.PI_OVER_TWO, pitch, roll, scratchSetViewQuaternion); + var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3); + + Matrix3.getColumn(rotMat, 2, camera.up); + Cartesian3.cross(camera.direction, camera.up, camera.right); + } + camera._setTransform(currentTransform); } @@ -895,7 +900,7 @@ define([ pitch : undefined, roll : undefined }, - convert: undefined, + convert : undefined, endTransform : undefined }; @@ -906,7 +911,7 @@ define([ * @param {Cartesian3|Rectangle} [options.destination] The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view. * @param {Object} [options.orientation] An object that contains either direction and up properties or heading, pith and roll properties. By default, the direction will point * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive - * y direction in Columbus view. Orientation is not used in 2D. + * y direction in Columbus view. Orientation is not used in 2D when in infinite scrolling mode. * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame of the camera. * * @example @@ -980,7 +985,7 @@ define([ if (mode === SceneMode.SCENE3D) { setView3D(this, destination, heading, pitch, roll); } else if (mode === SceneMode.SCENE2D) { - setView2D(this, destination, convert); + setView2D(this, destination, heading, convert); } else { setViewCV(this, destination, heading, pitch, roll, convert); } @@ -1052,7 +1057,7 @@ define([ } //>>includeEnd('debug'); - if (!defined(result)){ + if (!defined(result)) { result = new Cartesian4(); } updateMembers(this); @@ -1073,7 +1078,7 @@ define([ } //>>includeEnd('debug'); - if (!defined(result)){ + if (!defined(result)) { result = new Cartesian3(); } updateMembers(this); @@ -1094,7 +1099,7 @@ define([ } //>>includeEnd('debug'); - if (!defined(result)){ + if (!defined(result)) { result = new Cartesian3(); } updateMembers(this); @@ -1115,7 +1120,7 @@ define([ } //>>includeEnd('debug'); - if (!defined(result)){ + if (!defined(result)) { result = new Cartesian4(); } updateMembers(this); @@ -1136,7 +1141,7 @@ define([ } //>>includeEnd('debug'); - if (!defined(result)){ + if (!defined(result)) { result = new Cartesian3(); } updateMembers(this); @@ -1157,7 +1162,7 @@ define([ } //>>includeEnd('debug'); - if (!defined(result)){ + if (!defined(result)) { result = new Cartesian3(); } updateMembers(this); @@ -1165,20 +1170,32 @@ define([ }; function clampMove2D(camera, position) { - var maxX = camera._maxCoord.x; - if (position.x > maxX) { - position.x = position.x - maxX * 2.0; + var rotatable2D = camera._scene.mapMode2D === MapMode2D.ROTATE; + var maxProjectedX = camera._maxCoord.x; + var maxProjectedY = camera._maxCoord.y; + + var minX; + var maxX; + if (rotatable2D) { + maxX = maxProjectedX; + minX = -maxX; + } else { + maxX = position.x - maxProjectedX * 2.0; + minX = position.x + maxProjectedX * 2.0; } - if (position.x < -maxX) { - position.x = position.x + maxX * 2.0; + + if (position.x > maxProjectedX) { + position.x = maxX; + } + if (position.x < -maxProjectedX) { + position.x = minX; } - var maxY = camera._maxCoord.y; - if (position.y > maxY) { - position.y = maxY; + if (position.y > maxProjectedY) { + position.y = maxProjectedY; } - if (position.y < -maxY) { - position.y = -maxY; + if (position.y < -maxProjectedY) { + position.y = -maxProjectedY; } } @@ -1536,6 +1553,10 @@ define([ var newLeft = frustum.left + amount; var maxRight = camera._maxCoord.x; + if (camera._scene.mapMode2D === MapMode2D.ROTATE) { + maxRight *= camera.maximumZoomFactor; + } + if (newRight > maxRight) { newRight = maxRight; newLeft = -maxRight; @@ -1602,7 +1623,7 @@ define([ } else if (this._mode === SceneMode.COLUMBUS_VIEW) { return Math.abs(this.position.z); } else if (this._mode === SceneMode.SCENE2D) { - return Math.max(this.frustum.right - this.frustum.left, this.frustum.top - this.frustum.bottom); + return Math.max(this.frustum.right - this.frustum.left, this.frustum.top - this.frustum.bottom); } }; @@ -1779,7 +1800,11 @@ define([ var viewRectangle3DSouthCenter = new Cartesian3(); var viewRectangle3DCenter = new Cartesian3(); var viewRectangle3DEquator = new Cartesian3(); - var defaultRF = {direction: new Cartesian3(), right: new Cartesian3(), up: new Cartesian3()}; + var defaultRF = { + direction : new Cartesian3(), + right : new Cartesian3(), + up : new Cartesian3() + }; var viewRectangle3DEllipsoidGeodesic; function computeD(direction, upOrRight, corner, tanThetaOrPhi) { @@ -1787,7 +1812,7 @@ define([ return opposite / tanThetaOrPhi - Cartesian3.dot(direction, corner); } - function rectangleCameraPosition3D (camera, rectangle, result, updateCamera) { + function rectangleCameraPosition3D(camera, rectangle, result, updateCamera) { var ellipsoid = camera._projection.ellipsoid; var cameraRF = updateCamera ? camera : defaultRF; @@ -1944,7 +1969,7 @@ define([ var viewRectangle2DCartographic = new Cartographic(); var viewRectangle2DNorthEast = new Cartesian3(); var viewRectangle2DSouthWest = new Cartesian3(); - function rectangleCameraPosition2D (camera, rectangle, result) { + function rectangleCameraPosition2D(camera, rectangle, result) { var projection = camera._projection; if (rectangle.west > rectangle.east) { rectangle = Rectangle.MAX_VALUE; @@ -2050,7 +2075,7 @@ define([ var cart = projection.unproject(new Cartesian3(result.y, result.z, 0.0)); if (cart.latitude < -CesiumMath.PI_OVER_TWO || cart.latitude > CesiumMath.PI_OVER_TWO || - cart.longitude < - Math.PI || cart.longitude > Math.PI) { + cart.longitude < -Math.PI || cart.longitude > Math.PI) { return undefined; } @@ -2316,7 +2341,6 @@ define([ return undefined; }; - var scratchFlyToDestination = new Cartesian3(); var scratchFlyToCarto = new Cartographic(); var newOptions = { @@ -2339,7 +2363,7 @@ define([ * @param {Cartesian3|Rectangle} options.destination The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view. * @param {Object} [options.orientation] An object that contains either direction and up properties or heading, pith and roll properties. By default, the direction will point * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive - * y direction in Columbus view. Orientation is not used in 2D. + * y direction in Columbus view. Orientation is not used in 2D when in infinite scrolling mode. * @param {Number} [options.duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight. * @param {Camera~FlightCompleteCallback} [options.complete] The function to execute when the flight is complete. * @param {Camera~FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled. @@ -2407,7 +2431,7 @@ define([ setViewOptions.convert = options.convert; setViewOptions.endTransform = options.endTransform; this.setView(setViewOptions); - if (typeof options.complete === 'function'){ + if (typeof options.complete === 'function') { options.complete(); } return; diff --git a/Source/Scene/CameraFlightPath.js b/Source/Scene/CameraFlightPath.js index 570f8547dfb8..4b7b28df3e94 100644 --- a/Source/Scene/CameraFlightPath.js +++ b/Source/Scene/CameraFlightPath.js @@ -192,6 +192,7 @@ define([ var camera = scene.camera; var start = Cartesian3.clone(camera.position, scratchStart); + var startHeading = adjustAngleForLERP(camera.heading, heading); var startHeight = camera.frustum.right - camera.frustum.left; var heightFunction = createHeightFunction(camera, destination, startHeight, destination.z, optionAltitude); @@ -199,6 +200,12 @@ define([ function update(value) { var time = value.time / duration; + camera.setView({ + orientation: { + heading : CesiumMath.lerp(startHeading, heading, time) + } + }); + Cartesian2.lerp(start, destination, time, camera.position); var zoom = heightFunction(time); diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index 72670f66b14e..58dc795e919e 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -35,8 +35,7 @@ define([ function getPositionMode(sceneMode) { var getPosition3DMode = 'vec4 getPosition(vec3 position, float height, vec2 textureCoordinates) { return getPosition3DMode(position, height, textureCoordinates); }'; - var getPosition2DMode = 'vec4 getPosition(vec3 position, float height, vec2 textureCoordinates) { return getPosition2DMode(position, height, textureCoordinates); }'; - var getPositionColumbusViewMode = 'vec4 getPosition(vec3 position, float height, vec2 textureCoordinates) { return getPositionColumbusViewMode(position, height, textureCoordinates); }'; + var getPositionColumbusViewAnd2DMode = 'vec4 getPosition(vec3 position, float height, vec2 textureCoordinates) { return getPositionColumbusViewMode(position, height, textureCoordinates); }'; var getPositionMorphingMode = 'vec4 getPosition(vec3 position, float height, vec2 textureCoordinates) { return getPositionMorphingMode(position, height, textureCoordinates); }'; var positionMode; @@ -46,10 +45,8 @@ define([ positionMode = getPosition3DMode; break; case SceneMode.SCENE2D: - positionMode = getPosition2DMode; - break; case SceneMode.COLUMBUS_VIEW: - positionMode = getPositionColumbusViewMode; + positionMode = getPositionColumbusViewAnd2DMode; break; case SceneMode.MORPHING: positionMode = getPositionMorphingMode; diff --git a/Source/Scene/MapMode2D.js b/Source/Scene/MapMode2D.js new file mode 100644 index 000000000000..235f2aa274a6 --- /dev/null +++ b/Source/Scene/MapMode2D.js @@ -0,0 +1,32 @@ +/*global define*/ +define([ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * Describes how the map will operate in 2D. + * + * @exports MapMode2D + */ + var MapMode2D = { + /** + * The 2D map can be rotated about the z axis. + * + * @type {Number} + * @constant + */ + ROTATE : 0, + + /** + * The 2D map can be scrolled infinitely in the horizontal direction. + * + * @type {Number} + * @constant + */ + INFINITE_SCROLL : 1 + }; + + return freezeObject(MapMode2D); +}); diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 4634226de1d9..16ec0524ce1a 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -1493,11 +1493,25 @@ define([ } } - function createAttributeLocations(attributes) { + function createAttributeLocations(model, attributes) { var attributeLocations = {}; var length = attributes.length; + var i; - for (var i = 0; i < length; ++i) { + // Set the position attribute to the 0th index. In some WebGL implementations the shader + // will not work correctly if the 0th attribute is not active. For example, some glTF models + // list the normal attribute first but derived shaders like the cast-shadows shader do not use + // the normal attribute. + for (i = 1; i < length; ++i) { + var attribute = attributes[i]; + if (/position/i.test(attribute)) { + attributes[i] = attributes[0]; + attributes[0] = attribute; + break; + } + } + + for (i = 0; i < length; ++i) { attributeLocations[attributes[i]] = i; } @@ -1528,7 +1542,7 @@ define([ var shaders = model._loadResources.shaders; var program = programs[id]; - var attributeLocations = createAttributeLocations(program.attributes); + var attributeLocations = createAttributeLocations(model, program.attributes); var vs = getShaderSource(model, shaders[program.vertexShader]); var fs = getShaderSource(model, shaders[program.fragmentShader]); diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index f7f648fb8be6..e2098f111754 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -48,6 +48,7 @@ define([ './FrustumCommands', './FXAA', './GlobeDepth', + './MapMode2D', './OIT', './OrthographicFrustum', './Pass', @@ -114,6 +115,7 @@ define([ FrustumCommands, FXAA, GlobeDepth, + MapMode2D, OIT, OrthographicFrustum, Pass, @@ -185,6 +187,7 @@ define([ * @param {Boolean} [options.scene3DOnly=false] If true, optimizes memory use and performance for 3D mode but disables the ability to use 2D or Columbus View. * @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid. * @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun. + * @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction. * * @see CesiumWidget * @see {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes} @@ -573,6 +576,7 @@ define([ this._camera = camera; this._cameraClone = Camera.clone(camera); this._screenSpaceCameraController = new ScreenSpaceCameraController(this); + this._mapMode2D = defaultValue(options.mapMode2D, MapMode2D.INFINITE_SCROLL); // Keeps track of the state of a frame. FrameState is the state across // the primitives of the scene. This state is for internally keeping track @@ -1062,6 +1066,17 @@ define([ this._camera.frustum.xOffset = 0.0; } } + }, + + /** + * Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction. + * @memberof Scene.prototype + * @type {Boolean} + */ + mapMode2D : { + get : function() { + return this._mapMode2D; + } } }); @@ -1173,7 +1188,7 @@ define([ curNear = Math.max(near, Math.pow(farToNearRatio, m) * near); curFar = Math.min(far, farToNearRatio * curNear); } else { - curNear = near + m * nearToFarDistance2D; + curNear = Math.min(far - nearToFarDistance2D, near + m * nearToFarDistance2D); curFar = Math.min(far, curNear + nearToFarDistance2D); } @@ -1687,10 +1702,20 @@ define([ var index = numFrustums - i - 1; var frustumCommands = frustumCommandsList[index]; - // Avoid tearing artifacts between adjacent frustums in the opaque passes - frustum.near = index !== 0 ? frustumCommands.near * OPAQUE_FRUSTUM_NEAR_OFFSET : frustumCommands.near; - frustum.far = frustumCommands.far; - us.updateFrustum(frustum); + if (scene.mode === SceneMode.SCENE2D) { + // To avoid z-fighting in 2D, move the camera to just before the frustum + // and scale the frustum depth to be in [1.0, nearToFarDistance2D]. + camera.position.z = height2D - frustumCommands.near + 1.0; + frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near); + frustum.near = 1.0; + us.update(scene.frameState); + us.updateFrustum(frustum); + } else { + // Avoid tearing artifacts between adjacent frustums in the opaque passes + frustum.near = index !== 0 ? frustumCommands.near * OPAQUE_FRUSTUM_NEAR_OFFSET : frustumCommands.near; + frustum.far = frustumCommands.far; + us.updateFrustum(frustum); + } var globeDepth = scene.debugShowGlobeDepth ? getDebugGlobeDepth(scene, index) : scene._globeDepth; @@ -1732,16 +1757,6 @@ define([ } } - if (scene.mode === SceneMode.SCENE2D) { - // To avoid z-fighting in 2D, move the camera to just before the frustum - // and scale the frustum depth to be in [1.0, nearToFarDistance2D]. - camera.position.z = height2D - frustumCommands.near + 1.0; - frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near); - frustum.near = 1.0; - us.update(scene.frameState); - us.updateFrustum(frustum); - } - // Execute commands in order by pass up to the translucent pass. // Translucent geometry needs special handling (sorting/OIT). var startPass = Pass.GROUND + 1; @@ -1940,7 +1955,7 @@ define([ viewport.width = context.drawingBufferWidth; viewport.height = context.drawingBufferHeight; - if (mode !== SceneMode.SCENE2D) { + if (mode !== SceneMode.SCENE2D || scene._mapMode2D === MapMode2D.ROTATE) { executeCommandsInViewport(true, scene, passState, backgroundColor, picking); } else { execute2DViewportCommands(scene, passState, backgroundColor, picking); diff --git a/Source/Scene/ScreenSpaceCameraController.js b/Source/Scene/ScreenSpaceCameraController.js index dd9d98a0d1c9..07516af37da1 100644 --- a/Source/Scene/ScreenSpaceCameraController.js +++ b/Source/Scene/ScreenSpaceCameraController.js @@ -21,6 +21,7 @@ define([ '../Core/Transforms', './CameraEventAggregator', './CameraEventType', + './MapMode2D', './SceneMode', './SceneTransforms', './TweenCollection' @@ -46,6 +47,7 @@ define([ Transforms, CameraEventAggregator, CameraEventType, + MapMode2D, SceneMode, SceneTransforms, TweenCollection) { @@ -594,12 +596,80 @@ define([ handleZoom(controller, startPosition, movement, controller._zoomFactor, camera.getMagnitude()); } + var twist2DStart = new Cartesian2(); + var twist2DEnd = new Cartesian2(); + + function twist2D(controller, startPosition, movement) { + if (defined(movement.angleAndHeight)) { + singleAxisTwist2D(controller, startPosition, movement.angleAndHeight); + return; + } + + var scene = controller._scene; + var camera = scene.camera; + var canvas = scene.canvas; + var width = canvas.clientWidth; + var height = canvas.clientHeight; + + var start = twist2DStart; + start.x = (2.0 / width) * movement.startPosition.x - 1.0; + start.y = (2.0 / height) * (height - movement.startPosition.y) - 1.0; + start = Cartesian2.normalize(start, start); + + var end = twist2DEnd; + end.x = (2.0 / width) * movement.endPosition.x - 1.0; + end.y = (2.0 / height) * (height - movement.endPosition.y) - 1.0; + end = Cartesian2.normalize(end, end); + + var startTheta = CesiumMath.acosClamped(start.x); + if (start.y < 0) { + startTheta = CesiumMath.TWO_PI - startTheta; + } + var endTheta = CesiumMath.acosClamped(end.x); + if (end.y < 0) { + endTheta = CesiumMath.TWO_PI - endTheta; + } + var theta = endTheta - startTheta; + + camera.twistRight(theta); + } + + function singleAxisTwist2D(controller, startPosition, movement) { + var rotateRate = controller._rotateFactor * controller._rotateRateRangeAdjustment; + + if (rotateRate > controller._maximumRotateRate) { + rotateRate = controller._maximumRotateRate; + } + + if (rotateRate < controller._minimumRotateRate) { + rotateRate = controller._minimumRotateRate; + } + + var scene = controller._scene; + var camera = scene.camera; + var canvas = scene.canvas; + + var phiWindowRatio = (movement.endPosition.x - movement.startPosition.x) / canvas.clientWidth; + phiWindowRatio = Math.min(phiWindowRatio, controller.maximumMovementRatio); + + var deltaPhi = rotateRate * phiWindowRatio * Math.PI * 4.0; + + camera.twistRight(deltaPhi); + } + function update2D(controller) { + var rotatable2D = controller._scene.mapMode2D === MapMode2D.ROTATE; if (!Matrix4.equals(Matrix4.IDENTITY, controller._scene.camera.transform)) { reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom2D, controller.inertiaZoom, '_lastInertiaZoomMovement'); + if (rotatable2D) { + reactToInput(controller, controller.enableRotate, controller.translateEventTypes, twist2D, controller.inertiaSpin, '_lastInertiaSpinMovement'); + } } else { reactToInput(controller, controller.enableTranslate, controller.translateEventTypes, translate2D, controller.inertiaTranslate, '_lastInertiaTranslateMovement'); reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom2D, controller.inertiaZoom, '_lastInertiaZoomMovement'); + if (rotatable2D) { + reactToInput(controller, controller.enableRotate, controller.tiltEventTypes, twist2D, controller.inertiaSpin, '_lastInertiaTiltMovement'); + } } } @@ -1821,7 +1891,7 @@ define([ * * @example * controller = controller && controller.destroy(); - * + * * @see ScreenSpaceCameraController#isDestroyed */ ScreenSpaceCameraController.prototype.destroy = function() { diff --git a/Source/Scene/SkyAtmosphere.js b/Source/Scene/SkyAtmosphere.js index 6c28879bd86d..bf0a1274ef2d 100644 --- a/Source/Scene/SkyAtmosphere.js +++ b/Source/Scene/SkyAtmosphere.js @@ -9,6 +9,7 @@ define([ '../Core/Ellipsoid', '../Core/EllipsoidGeometry', '../Core/GeometryPipeline', + '../Core/Math', '../Core/VertexFormat', '../Renderer/BufferUsage', '../Renderer/DrawCommand', @@ -31,6 +32,7 @@ define([ Ellipsoid, EllipsoidGeometry, GeometryPipeline, + CesiumMath, VertexFormat, BufferUsage, DrawCommand, @@ -81,6 +83,35 @@ define([ this._spSkyFromSpace = undefined; this._spSkyFromAtmosphere = undefined; + this._spSkyFromSpaceColorCorrect = undefined; + this._spSkyFromAtmosphereColorCorrect = undefined; + + /** + * The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift). + * A hue shift of 1.0 indicates a complete rotation of the hues available. + * @type {Number} + * @default 0.0 + */ + this.hueShift = 0.0; + + /** + * The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift). + * A saturation shift of -1.0 is monochrome. + * @type {Number} + * @default 0.0 + */ + this.saturationShift = 0.0; + + /** + * The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift). + * A brightness shift of -1.0 is complete darkness, which will let space show through. + * @type {Number} + * @default 0.0 + */ + this.brightnessShift = 0.0; + + this._hueSaturationBrightness = new Cartesian3(); + // camera height, outer radius, inner radius, dynamic atmosphere color flag var cameraAndRadiiAndDynamicAtmosphereColor = new Cartesian4(); @@ -94,8 +125,14 @@ define([ var that = this; this._command.uniformMap = { - cameraAndRadiiAndDynamicAtmosphereColor : function() { + u_cameraAndRadiiAndDynamicAtmosphereColor : function() { return that._cameraAndRadiiAndDynamicAtmosphereColor; + }, + u_hsbShift : function() { + that._hueSaturationBrightness.x = that.hueShift; + that._hueSaturationBrightness.y = that.saturationShift; + that._hueSaturationBrightness.z = that.brightnessShift; + return that._hueSaturationBrightness; } }; } @@ -169,6 +206,7 @@ define([ defines : ['SKY_FROM_SPACE'], sources : [SkyAtmosphereVS] }); + this._spSkyFromSpace = ShaderProgram.fromCache({ context : context, vertexShaderSource : vs, @@ -186,6 +224,36 @@ define([ }); } + // Compile the color correcting versions of the shader on demand + var useColorCorrect = colorCorrect(this); + if (useColorCorrect && (!defined(this._spSkyFromSpaceColorCorrect) || !defined(this._spSkyFromAtmosphereColorCorrect))) { + var contextColorCorrect = frameState.context; + + var vsColorCorrect = new ShaderSource({ + defines : ['SKY_FROM_SPACE'], + sources : [SkyAtmosphereVS] + }); + var fsColorCorrect = new ShaderSource({ + defines : ['COLOR_CORRECT'], + sources : [SkyAtmosphereFS] + }); + + this._spSkyFromSpaceColorCorrect = ShaderProgram.fromCache({ + context : contextColorCorrect, + vertexShaderSource : vsColorCorrect, + fragmentShaderSource : fsColorCorrect + }); + vsColorCorrect = new ShaderSource({ + defines : ['SKY_FROM_ATMOSPHERE'], + sources : [SkyAtmosphereVS] + }); + this._spSkyFromAtmosphereColorCorrect = ShaderProgram.fromCache({ + context : contextColorCorrect, + vertexShaderSource : vsColorCorrect, + fragmentShaderSource : fsColorCorrect + }); + } + var cameraPosition = frameState.camera.positionWC; var cameraHeight = Cartesian3.magnitude(cameraPosition); @@ -193,15 +261,21 @@ define([ if (cameraHeight > this._cameraAndRadiiAndDynamicAtmosphereColor.y) { // Camera in space - command.shaderProgram = this._spSkyFromSpace; + command.shaderProgram = useColorCorrect ? this._spSkyFromSpaceColorCorrect : this._spSkyFromSpace; } else { // Camera in atmosphere - command.shaderProgram = this._spSkyFromAtmosphere; + command.shaderProgram = useColorCorrect ? this._spSkyFromAtmosphereColorCorrect : this._spSkyFromAtmosphere; } return command; }; + function colorCorrect(skyAtmosphere) { + return !(CesiumMath.equalsEpsilon(skyAtmosphere.hueShift, 0.0, CesiumMath.EPSILON7) && + CesiumMath.equalsEpsilon(skyAtmosphere.saturationShift, 0.0, CesiumMath.EPSILON7) && + CesiumMath.equalsEpsilon(skyAtmosphere.brightnessShift, 0.0, CesiumMath.EPSILON7)); + } + /** * Returns true if this object was destroyed; otherwise, false. *

@@ -239,6 +313,8 @@ define([ command.vertexArray = command.vertexArray && command.vertexArray.destroy(); this._spSkyFromSpace = this._spSkyFromSpace && this._spSkyFromSpace.destroy(); this._spSkyFromAtmosphere = this._spSkyFromAtmosphere && this._spSkyFromAtmosphere.destroy(); + this._spSkyFromSpaceColorCorrect = this._spSkyFromSpaceColorCorrect && this._spSkyFromSpaceColorCorrect.destroy(); + this._spSkyFromAtmosphereColorCorrect = this._spSkyFromAtmosphereColorCorrect && this._spSkyFromAtmosphereColorCorrect.destroy(); return destroyObject(this); }; diff --git a/Source/Shaders/SkyAtmosphereFS.glsl b/Source/Shaders/SkyAtmosphereFS.glsl index 5e9b75eab92c..9fd4000bb26e 100644 --- a/Source/Shaders/SkyAtmosphereFS.glsl +++ b/Source/Shaders/SkyAtmosphereFS.glsl @@ -32,15 +32,39 @@ // Code: http://sponeil.net/ // GPU Gems 2 Article: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html - + // HSV/HSB <-> RGB conversion with minimal branching: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl + +#ifdef COLOR_CORRECT +uniform vec3 u_hsbShift; // Hue, saturation, brightness +#endif + const float g = -0.95; const float g2 = g * g; +const vec4 K_RGB2HSB = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); +const vec4 K_HSB2RGB = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); varying vec3 v_rayleighColor; varying vec3 v_mieColor; varying vec3 v_toCamera; varying vec3 v_positionEC; +#ifdef COLOR_CORRECT +vec3 rgb2hsb(vec3 rgbColor) +{ + vec4 p = mix(vec4(rgbColor.bg, K_RGB2HSB.wz), vec4(rgbColor.gb, K_RGB2HSB.xy), step(rgbColor.b, rgbColor.g)); + vec4 q = mix(vec4(p.xyw, rgbColor.r), vec4(rgbColor.r, p.yzx), step(p.x, rgbColor.r)); + + float d = q.x - min(q.w, q.y); + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + czm_epsilon7)), d / (q.x + czm_epsilon7), q.x); +} + +vec3 hsb2rgb(vec3 hsbColor) +{ + vec3 p = abs(fract(hsbColor.xxx + K_HSB2RGB.xyz) * 6.0 - K_HSB2RGB.www); + return hsbColor.z * mix(K_HSB2RGB.xxx, clamp(p - K_HSB2RGB.xxx, 0.0, 1.0), hsbColor.y); +} +#endif + void main (void) { // Extra normalize added for Android @@ -52,6 +76,22 @@ void main (void) vec3 rgb = rayleighPhase * v_rayleighColor + miePhase * v_mieColor; rgb = vec3(1.0) - exp(-exposure * rgb); + // Compute luminance before color correction to avoid strangely gray night skies float l = czm_luminance(rgb); + +#ifdef COLOR_CORRECT + // Convert rgb color to hsb + vec3 hsb = rgb2hsb(rgb); + // Perform hsb shift + hsb.x += u_hsbShift.x; // hue + hsb.y = clamp(hsb.y + u_hsbShift.y, 0.0, 1.0); // saturation + hsb.z = hsb.z > czm_epsilon7 ? hsb.z + u_hsbShift.z : 0.0; // brightness + // Convert shifted hsb back to rgb + rgb = hsb2rgb(hsb); + + // Check if correction decreased the luminance to 0 + l = min(l, czm_luminance(rgb)); +#endif + gl_FragColor = vec4(rgb, min(smoothstep(0.0, 0.1, l), 1.0) * smoothstep(0.0, 1.0, czm_morphTime)); } diff --git a/Source/Shaders/SkyAtmosphereVS.glsl b/Source/Shaders/SkyAtmosphereVS.glsl index 58a93a20f875..1e826848fb0e 100644 --- a/Source/Shaders/SkyAtmosphereVS.glsl +++ b/Source/Shaders/SkyAtmosphereVS.glsl @@ -35,7 +35,7 @@ attribute vec4 position; -uniform vec4 cameraAndRadiiAndDynamicAtmosphereColor; // camera height, outer radius, inner radius, dynamic atmosphere color flag +uniform vec4 u_cameraAndRadiiAndDynamicAtmosphereColor; // Camera height, outer radius, inner radius, dynamic atmosphere color flag const float Kr = 0.0025; const float Kr4PI = Kr * 4.0 * czm_pi; @@ -65,10 +65,10 @@ float scale(float cosAngle) void main(void) { - // unpack attributes - float cameraHeight = cameraAndRadiiAndDynamicAtmosphereColor.x; - float outerRadius = cameraAndRadiiAndDynamicAtmosphereColor.y; - float innerRadius = cameraAndRadiiAndDynamicAtmosphereColor.z; + // Unpack attributes + float cameraHeight = u_cameraAndRadiiAndDynamicAtmosphereColor.x; + float outerRadius = u_cameraAndRadiiAndDynamicAtmosphereColor.y; + float innerRadius = u_cameraAndRadiiAndDynamicAtmosphereColor.z; // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) vec3 positionV3 = position.xyz; @@ -107,7 +107,7 @@ void main(void) // Now loop through the sample rays vec3 frontColor = vec3(0.0, 0.0, 0.0); - vec3 lightDir = (cameraAndRadiiAndDynamicAtmosphereColor.w > 0.0) ? czm_sunPositionWC - czm_viewerPositionWC : czm_viewerPositionWC; + vec3 lightDir = (u_cameraAndRadiiAndDynamicAtmosphereColor.w > 0.0) ? czm_sunPositionWC - czm_viewerPositionWC : czm_viewerPositionWC; lightDir = normalize(lightDir); for(int i=0; i