diff --git a/Apps/Sandcastle/gallery/development/Shadows.html b/Apps/Sandcastle/gallery/development/Shadows.html index 1f907c7ce13b..2138e5fa77e3 100644 --- a/Apps/Sandcastle/gallery/development/Shadows.html +++ b/Apps/Sandcastle/gallery/development/Shadows.html @@ -525,7 +525,7 @@ var frustumSize = 55.0; var frustumNear = 1.0; var frustumFar = 400.0; - var frustum = new Cesium.OrthographicFrustum(); + var frustum = new Cesium.OrthographicOffCenterFrustum(); frustum.left = -frustumSize; frustum.right = frustumSize; frustum.bottom = -frustumSize; diff --git a/CHANGES.md b/CHANGES.md index 3443c7973224..e4306e63cb68 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ Change Log ### 1.32 - 2017-04-03 +* Deprecated + * The `left`, `right`, `bottom`, and `top` properties of `OrthographicFrustum` are deprecated and will be removed in 1.33. Use `OrthographicOffCenterFrustum` instead. * Added `Camera.flyTo` and `Camera.flyToBoundingSphere` options [#5070](https://github.com/AnalyticalGraphicsInc/cesium/pull/5070) * `flyOverLongitude` to select one of two possible on Globe paths which camera should fly. * `flyOverLongitudeWeight` to set a threshold: how many times the `flyOverLongitude` way can be than shortest path. @@ -15,6 +17,7 @@ Change Log * Fixed an issue with `PinBuilder` where inset images could have low-alpha fringes against an opaque background. [#5099](https://github.com/AnalyticalGraphicsInc/cesium/pull/5099) * Fixed a bug in `ModelAnimationCache` causing different animations to reference the same animation. [#5064](https://github.com/AnalyticalGraphicsInc/cesium/pull/5064) * Fixed a bug that caused an exception in `CesiumInspectorViewModel` when using the NW / NE / SW / SE / Parent buttons to navigate to a terrain tile that is not yet loaded. +* Added support for an orthographic projection in 3D and Columbus view. Set `projectionPicker` to `true` in the options when creating a `Viewer` to add a widget that will switch projections. [#5021](https://github.com/AnalyticalGraphicsInc/cesium/pull/5021) ### 1.31 - 2017-03-01 diff --git a/Source/Core/HeightmapTessellator.js b/Source/Core/HeightmapTessellator.js index fa222bf44dde..2195f819a985 100644 --- a/Source/Core/HeightmapTessellator.js +++ b/Source/Core/HeightmapTessellator.js @@ -214,8 +214,11 @@ define([ var elementMultiplier = defaultValue(structure.elementMultiplier, HeightmapTessellator.DEFAULT_STRUCTURE.elementMultiplier); var isBigEndian = defaultValue(structure.isBigEndian, HeightmapTessellator.DEFAULT_STRUCTURE.isBigEndian); - var granularityX = Rectangle.computeWidth(nativeRectangle) / (width - 1); - var granularityY = Rectangle.computeHeight(nativeRectangle) / (height - 1); + var rectangleWidth = Rectangle.computeWidth(nativeRectangle); + var rectangleHeight = Rectangle.computeHeight(nativeRectangle); + + var granularityX = rectangleWidth / (width - 1); + var granularityY = rectangleHeight / (height - 1); var radiiSquared = ellipsoid.radiiSquared; var radiiSquaredX = radiiSquared.x; @@ -337,10 +340,29 @@ define([ heightSample = (heightSample * heightScale + heightOffset) * exaggeration; + var u = (longitude - geographicWest) / (geographicEast - geographicWest); + u = CesiumMath.clamp(u, 0.0, 1.0); + uvs[index] = new Cartesian2(u, v); + maximumHeight = Math.max(maximumHeight, heightSample); minimumHeight = Math.min(minimumHeight, heightSample); if (colIndex !== col || rowIndex !== row) { + var percentage = 0.00001; + if (colIndex < 0) { + longitude -= percentage * rectangleWidth; + } else { + longitude += percentage * rectangleWidth; + } + if (rowIndex < 0) { + latitude += percentage * rectangleHeight; + } else { + latitude -= percentage * rectangleHeight; + } + + cosLatitude = cos(latitude); + nZ = sin(latitude); + kZ = radiiSquaredZ * nZ; heightSample -= skirtHeight; } @@ -365,10 +387,6 @@ define([ positions[index] = position; heights[index] = heightSample; - var u = (longitude - geographicWest) / (geographicEast - geographicWest); - u = CesiumMath.clamp(u, 0.0, 1.0); - uvs[index] = new Cartesian2(u, v); - if (includeWebMercatorT) { webMercatorTs[index] = webMercatorT; } diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js index f326d01d7ac6..6f6512789715 100644 --- a/Source/Renderer/AutomaticUniforms.js +++ b/Source/Renderer/AutomaticUniforms.js @@ -530,17 +530,6 @@ define([ } }), - /** - * @private - */ - czm_inverseProjectionOIT : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseProjectionOIT; - } - }), - /** * An automatic GLSL uniform representing a 4x4 projection transformation matrix with the far plane at infinity, * that transforms eye coordinates to clip coordinates. Clip coordinates is the diff --git a/Source/Renderer/UniformState.js b/Source/Renderer/UniformState.js index 759659750639..89eacb030303 100644 --- a/Source/Renderer/UniformState.js +++ b/Source/Renderer/UniformState.js @@ -13,6 +13,7 @@ define([ '../Core/Matrix4', '../Core/Simon1994PlanetaryPositions', '../Core/Transforms', + '../Scene/OrthographicFrustum', '../Scene/SceneMode' ], function( BoundingRectangle, @@ -28,6 +29,7 @@ define([ Matrix4, Simon1994PlanetaryPositions, Transforms, + OrthographicFrustum, SceneMode) { 'use strict'; @@ -80,9 +82,6 @@ define([ this._inverseProjectionDirty = true; this._inverseProjection = new Matrix4(); - this._inverseProjectionOITDirty = true; - this._inverseProjectionOIT = new Matrix4(); - this._modelViewDirty = true; this._modelView = new Matrix4(); @@ -147,6 +146,7 @@ define([ this._frustum2DWidth = 0.0; this._eyeHeight2D = new Cartesian2(); this._resolutionScale = 1.0; + this._orthographicIn3D = false; this._fogDensity = undefined; @@ -393,17 +393,6 @@ define([ } }, - /** - * @memberof UniformState.prototype - * @private - */ - inverseProjectionOIT : { - get : function() { - cleanInverseProjectionOIT(this); - return this._inverseProjectionOIT; - } - }, - /** * @memberof UniformState.prototype * @type {Matrix4} @@ -834,7 +823,6 @@ define([ Matrix4.clone(matrix, uniformState._projection); uniformState._inverseProjectionDirty = true; - uniformState._inverseProjectionOITDirty = true; uniformState._viewProjectionDirty = true; uniformState._inverseViewProjectionDirty = true; uniformState._modelViewProjectionDirty = true; @@ -896,6 +884,8 @@ define([ this._entireFrustum.x = camera.frustum.near; this._entireFrustum.y = camera.frustum.far; this.updateFrustum(camera.frustum); + + this._orthographicIn3D = this._mode !== SceneMode.SCENE2D && camera.frustum instanceof OrthographicFrustum; }; /** @@ -913,7 +903,7 @@ define([ this._currentFrustum.x = frustum.near; this._currentFrustum.y = frustum.far; - if (!defined(frustum.top)) { + if (defined(frustum._offCenterFrustum)) { frustum = frustum._offCenterFrustum; } @@ -987,18 +977,10 @@ define([ if (uniformState._inverseProjectionDirty) { uniformState._inverseProjectionDirty = false; - Matrix4.inverse(uniformState._projection, uniformState._inverseProjection); - } - } - - function cleanInverseProjectionOIT(uniformState) { - if (uniformState._inverseProjectionOITDirty) { - uniformState._inverseProjectionOITDirty = false; - - if (uniformState._mode !== SceneMode.SCENE2D && uniformState._mode !== SceneMode.MORPHING) { - Matrix4.inverse(uniformState._projection, uniformState._inverseProjectionOIT); + if (uniformState._mode !== SceneMode.SCENE2D && uniformState._mode !== SceneMode.MORPHING && !uniformState._orthographicIn3D) { + Matrix4.inverse(uniformState._projection, uniformState._inverseProjection); } else { - Matrix4.clone(Matrix4.IDENTITY, uniformState._inverseProjectionOIT); + Matrix4.clone(Matrix4.ZERO, uniformState._inverseProjection); } } } diff --git a/Source/Scene/Camera.js b/Source/Scene/Camera.js index d482b7ce700c..7589178a8b61 100644 --- a/Source/Scene/Camera.js +++ b/Source/Scene/Camera.js @@ -26,6 +26,8 @@ define([ '../Core/Transforms', './CameraFlightPath', './MapMode2D', + './OrthographicFrustum', + './OrthographicOffCenterFrustum', './PerspectiveFrustum', './SceneMode' ], function( @@ -55,6 +57,8 @@ define([ Transforms, CameraFlightPath, MapMode2D, + OrthographicFrustum, + OrthographicOffCenterFrustum, PerspectiveFrustum, SceneMode) { 'use strict'; @@ -335,7 +339,13 @@ define([ } var dirAngle = CesiumMath.acosClamped(Cartesian3.dot(camera.directionWC, camera._changedDirection)); - var dirPercentage = dirAngle / (camera.frustum.fovy * 0.5); + + var dirPercentage; + if (defined(camera.frustum.fovy)) { + dirPercentage = dirAngle / (camera.frustum.fovy * 0.5); + } else { + dirPercentage = dirAngle; + } var distance = Cartesian3.distance(camera.positionWC, camera._changedPosition); var heightPercentage = distance / camera.positionCartographic.height; @@ -904,6 +914,13 @@ define([ if (!defined(mode)) { throw new DeveloperError('mode is required.'); } + if (mode === SceneMode.SCENE2D && !(this.frustum instanceof OrthographicOffCenterFrustum)) { + throw new DeveloperError('An OrthographicOffCenterFrustum is required in 2D.'); + } + if ((mode === SceneMode.SCENE3D || mode === SceneMode.COLUMBUS_VIEW) && + (!(this.frustum instanceof PerspectiveFrustum) && !(this.frustum instanceof OrthographicFrustum))) { + throw new DeveloperError('A PerspectiveFrustum or OrthographicFrustum is required in 3D and Columbus view'); + } //>>includeEnd('debug'); var updateFrustum = false; @@ -917,7 +934,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 (!(frustum instanceof OrthographicOffCenterFrustum)) { throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.'); } //>>includeEnd('debug'); @@ -964,6 +981,46 @@ define([ updateMembers(this); }; + var scratchAdjustOrtghographicFrustumMousePosition = new Cartesian2(); + var pickGlobeScratchRay = new Ray(); + var scratchRayIntersection = new Cartesian3(); + + Camera.prototype._adjustOrthographicFrustum = function(zooming) { + if (!(this.frustum instanceof OrthographicFrustum)) { + return; + } + + if (!zooming && this._positionCartographic.height < 150000.0) { + return; + } + + if (!Matrix4.equals(Matrix4.IDENTITY, this.transform)) { + this.frustum.width = Cartesian3.magnitude(this.position); + return; + } + + var scene = this._scene; + var globe = scene._globe; + var rayIntersection; + + if (defined(globe)) { + var mousePosition = scratchAdjustOrtghographicFrustumMousePosition; + mousePosition.x = scene.drawingBufferWidth / 2.0; + mousePosition.y = scene.drawingBufferHeight / 2.0; + + var ray = this.getPickRay(mousePosition, pickGlobeScratchRay); + rayIntersection = globe.pick(ray, scene, scratchRayIntersection); + if (defined(rayIntersection)) { + this.frustum.width = Cartesian3.distance(rayIntersection, this.positionWC); + } + } + + if (!defined(globe) || (!defined(rayIntersection))) { + var distance = Math.max(this.positionCartographic.height, 0.0); + this.frustum.width = distance; + } + }; + var scratchSetViewCartesian = new Cartesian3(); var scratchSetViewTransform1 = new Matrix4(); var scratchSetViewTransform2 = new Matrix4(); @@ -987,6 +1044,8 @@ define([ Cartesian3.cross(camera.direction, camera.up, camera.right); camera._setTransform(currentTransform); + + camera._adjustOrthographicFrustum(true); } function setViewCV(camera, position,hpr, convert) { @@ -1011,6 +1070,8 @@ define([ Cartesian3.cross(camera.direction, camera.up, camera.right); camera._setTransform(currentTransform); + + camera._adjustOrthographicFrustum(true); } function setView2D(camera, position, hpr, convert) { @@ -1418,6 +1479,7 @@ define([ if (this._mode === SceneMode.SCENE2D) { clampMove2D(this, cameraPosition); } + this._adjustOrthographicFrustum(true); }; /** @@ -1633,6 +1695,8 @@ define([ Matrix3.multiplyByVector(rotation, this.up, this.up); Cartesian3.cross(this.direction, this.up, this.right); Cartesian3.cross(this.right, this.direction, this.up); + + this._adjustOrthographicFrustum(false); }; /** @@ -1734,7 +1798,8 @@ define([ var frustum = camera.frustum; //>>includeStart('debug', pragmas.debug); - if (!defined(frustum.left) || !defined(frustum.right) || !defined(frustum.top) || !defined(frustum.bottom)) { + if (!(frustum instanceof OrthographicOffCenterFrustum) || !defined(frustum.left) || !defined(frustum.right) || + !defined(frustum.bottom) || !defined(frustum.top)) { throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.'); } //>>includeEnd('debug'); @@ -1979,6 +2044,8 @@ define([ Cartesian3.normalize(this.right, this.right); Cartesian3.cross(this.right, this.direction, this.up); Cartesian3.normalize(this.up, this.up); + + this._adjustOrthographicFrustum(true); }; var viewRectangle3DCartographic1 = new Cartographic(); @@ -2086,38 +2153,58 @@ define([ Cartesian3.normalize(right, right); var up = Cartesian3.cross(right, direction, cameraRF.up); - var tanPhi = Math.tan(camera.frustum.fovy * 0.5); - var tanTheta = camera.frustum.aspectRatio * tanPhi; + var d; + if (camera.frustum instanceof OrthographicFrustum) { + var width = Math.max(Cartesian3.distance(northEast, northWest), Cartesian3.distance(southEast, southWest)); + var height = Math.max(Cartesian3.distance(northEast, southEast), Cartesian3.distance(northWest, southWest)); + + var rightScalar; + var topScalar; + var ratio = camera.frustum._offCenterFrustum.right / camera.frustum._offCenterFrustum.top; + var heightRatio = height * ratio; + if (width > heightRatio) { + rightScalar = width; + topScalar = rightScalar / ratio; + } else { + topScalar = height; + rightScalar = heightRatio; + } - var d = Math.max( - computeD(direction, up, northWest, tanPhi), - computeD(direction, up, southEast, tanPhi), - computeD(direction, up, northEast, tanPhi), - computeD(direction, up, southWest, tanPhi), - computeD(direction, up, northCenter, tanPhi), - computeD(direction, up, southCenter, tanPhi), - computeD(direction, right, northWest, tanTheta), - computeD(direction, right, southEast, tanTheta), - computeD(direction, right, northEast, tanTheta), - computeD(direction, right, southWest, tanTheta), - computeD(direction, right, northCenter, tanTheta), - computeD(direction, right, southCenter, tanTheta)); - - // If the rectangle crosses the equator, compute D at the equator, too, because that's the - // widest part of the rectangle when projected onto the globe. - if (south < 0 && north > 0) { - var equatorCartographic = viewRectangle3DCartographic1; - equatorCartographic.longitude = west; - equatorCartographic.latitude = 0.0; - equatorCartographic.height = 0.0; - var equatorPosition = ellipsoid.cartographicToCartesian(equatorCartographic, viewRectangle3DEquator); - Cartesian3.subtract(equatorPosition, center, equatorPosition); - d = Math.max(d, computeD(direction, up, equatorPosition, tanPhi), computeD(direction, right, equatorPosition, tanTheta)); - - equatorCartographic.longitude = east; - equatorPosition = ellipsoid.cartographicToCartesian(equatorCartographic, viewRectangle3DEquator); - Cartesian3.subtract(equatorPosition, center, equatorPosition); - d = Math.max(d, computeD(direction, up, equatorPosition, tanPhi), computeD(direction, right, equatorPosition, tanTheta)); + d = Math.max(rightScalar, topScalar); + } else { + var tanPhi = Math.tan(camera.frustum.fovy * 0.5); + var tanTheta = camera.frustum.aspectRatio * tanPhi; + + d = Math.max( + computeD(direction, up, northWest, tanPhi), + computeD(direction, up, southEast, tanPhi), + computeD(direction, up, northEast, tanPhi), + computeD(direction, up, southWest, tanPhi), + computeD(direction, up, northCenter, tanPhi), + computeD(direction, up, southCenter, tanPhi), + computeD(direction, right, northWest, tanTheta), + computeD(direction, right, southEast, tanTheta), + computeD(direction, right, northEast, tanTheta), + computeD(direction, right, southWest, tanTheta), + computeD(direction, right, northCenter, tanTheta), + computeD(direction, right, southCenter, tanTheta)); + + // If the rectangle crosses the equator, compute D at the equator, too, because that's the + // widest part of the rectangle when projected onto the globe. + if (south < 0 && north > 0) { + var equatorCartographic = viewRectangle3DCartographic1; + equatorCartographic.longitude = west; + equatorCartographic.latitude = 0.0; + equatorCartographic.height = 0.0; + var equatorPosition = ellipsoid.cartographicToCartesian(equatorCartographic, viewRectangle3DEquator); + Cartesian3.subtract(equatorPosition, center, equatorPosition); + d = Math.max(d, computeD(direction, up, equatorPosition, tanPhi), computeD(direction, right, equatorPosition, tanTheta)); + + equatorCartographic.longitude = east; + equatorPosition = ellipsoid.cartographicToCartesian(equatorCartographic, viewRectangle3DEquator); + Cartesian3.subtract(equatorPosition, center, equatorPosition); + d = Math.max(d, computeD(direction, up, equatorPosition, tanPhi), computeD(direction, right, equatorPosition, tanTheta)); + } } return Cartesian3.add(center, Cartesian3.multiplyByScalar(direction, -d, viewRectangle3DEquator), result); @@ -2147,12 +2234,18 @@ define([ Matrix4.multiplyByPoint(transform, southWest, southWest); Matrix4.multiplyByPoint(invTransform, southWest, southWest); - var tanPhi = Math.tan(camera.frustum.fovy * 0.5); - var tanTheta = camera.frustum.aspectRatio * tanPhi; - result.x = (northEast.x - southWest.x) * 0.5 + southWest.x; result.y = (northEast.y - southWest.y) * 0.5 + southWest.y; - result.z = Math.max((northEast.x - southWest.x) / tanTheta, (northEast.y - southWest.y) / tanPhi) * 0.5; + + if (defined(camera.frustum.fovy)) { + var tanPhi = Math.tan(camera.frustum.fovy * 0.5); + var tanTheta = camera.frustum.aspectRatio * tanPhi; + result.z = Math.max((northEast.x - southWest.x) / tanTheta, (northEast.y - southWest.y) / tanPhi) * 0.5; + } else { + var width = northEast.x - southWest.x; + var height = northEast.y - southWest.y; + result.z = Math.max(width, height); + } return result; } @@ -2345,10 +2438,14 @@ define([ var width = canvas.clientWidth; var height = canvas.clientHeight; + var frustum = camera.frustum; + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } var x = (2.0 / width) * windowPosition.x - 1.0; - x *= (camera.frustum.right - camera.frustum.left) * 0.5; + x *= (frustum.right - frustum.left) * 0.5; var y = (2.0 / height) * (height - windowPosition.y) - 1.0; - y *= (camera.frustum.top - camera.frustum.bottom) * 0.5; + y *= (frustum.top - frustum.bottom) * 0.5; var origin = result.origin; Cartesian3.clone(camera.position, origin); @@ -2360,6 +2457,10 @@ define([ Cartesian3.clone(camera.directionWC, result.direction); + if (camera._mode === SceneMode.COLUMBUS_VIEW) { + Cartesian3.fromElements(result.origin.z, result.origin.x, result.origin.y, result.origin); + } + return result; } @@ -2687,6 +2788,9 @@ define([ function distanceToBoundingSphere2D(camera, radius) { var frustum = camera.frustum; + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } var right, top; var ratio = frustum.right / frustum.top; @@ -2714,8 +2818,10 @@ define([ var radius = boundingSphere.radius; if (radius === 0.0) { offset.range = MINIMUM_ZOOM; + } else if (camera.frustum instanceof OrthographicFrustum || camera._mode === SceneMode.SCENE2D) { + offset.range = distanceToBoundingSphere2D(camera, radius); } else { - offset.range = camera._mode === SceneMode.SCENE2D ? distanceToBoundingSphere2D(camera, radius) : distanceToBoundingSphere3D(camera, radius); + offset.range = distanceToBoundingSphere3D(camera, radius); } } diff --git a/Source/Scene/DebugCameraPrimitive.js b/Source/Scene/DebugCameraPrimitive.js index c282852684cd..90030e73a2f9 100644 --- a/Source/Scene/DebugCameraPrimitive.js +++ b/Source/Scene/DebugCameraPrimitive.js @@ -121,10 +121,8 @@ define([ } if (!defined(this._outlinePrimitive)) { - var view = this._camera.viewMatrix; - var projection = this._camera.frustum.projectionMatrix; - var viewProjection = Matrix4.multiply(projection, view, scratchMatrix); - var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix); + var camera = this._camera; + var frustum = camera.frustum; var frustumSplits = frameState.frustumSplits; var numFrustums = frustumSplits.length - 1; @@ -135,20 +133,59 @@ define([ numFrustums = 1; } + var view = this._camera.viewMatrix; + var inverseView; + var inverseViewProjection; + if (defined(camera.frustum.fovy)) { + var projection = this._camera.frustum.projectionMatrix; + var viewProjection = Matrix4.multiply(projection, view, scratchMatrix); + inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix); + } else { + inverseView = Matrix4.inverseTransformation(view, scratchMatrix); + } + + var positions = new Float64Array(3 * 4 * (numFrustums + 1)); var f; for (f = 0; f < numFrustums + 1; ++f) { for (var i = 0; i < 4; ++i) { var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]); - Matrix4.multiplyByVector(inverseViewProjection, corner, corner); - Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide - Cartesian3.subtract(corner, this._camera.positionWC, corner); - Cartesian3.normalize(corner, corner); - - var fac = Cartesian3.dot(this._camera.directionWC, corner); - Cartesian3.multiplyByScalar(corner, frustumSplits[f] / fac, corner); - Cartesian3.add(corner, this._camera.positionWC, corner); + var worldCoords; + if (!defined(inverseViewProjection)) { + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } + + var near; + var far; + if (f === numFrustums) { + near = frustumSplits[f - 1]; + far = frustumSplits[f]; + } else { + near = frustumSplits[f]; + far = frustumSplits[f + 1]; + } + corner.x = (corner.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; + corner.y = (corner.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5; + corner.z = (corner.z * (near - far) - near - far) * 0.5; + corner.w = 1.0; + + worldCoords = Matrix4.multiplyByVector(inverseView, corner, corner); + } else { + corner = Matrix4.multiplyByVector(inverseViewProjection, corner, corner); + + // Reverse perspective divide + var w = 1.0 / corner.w; + Cartesian3.multiplyByScalar(corner, w, corner); + + Cartesian3.subtract(corner, this._camera.positionWC, corner); + Cartesian3.normalize(corner, corner); + + var fac = Cartesian3.dot(this._camera.directionWC, corner); + Cartesian3.multiplyByScalar(corner, frustumSplits[f] / fac, corner); + Cartesian3.add(corner, this._camera.positionWC, corner); + } positions[12 * f + i * 3] = corner.x; positions[12 * f + i * 3 + 1] = corner.y; diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 7bec9532d203..15b234d32be3 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -39,6 +39,7 @@ define([ '../Scene/Primitive', './GlobeSurfaceTile', './ImageryLayer', + './OrthographicFrustum', './QuadtreeTileLoadState', './SceneMode', './ShadowMode' @@ -82,6 +83,7 @@ define([ Primitive, GlobeSurfaceTile, ImageryLayer, + OrthographicFrustum, QuadtreeTileLoadState, SceneMode, ShadowMode) { @@ -502,7 +504,8 @@ define([ return Visibility.NONE; } - if (frameState.mode === SceneMode.SCENE3D) { + var ortho3D = frameState.mode === SceneMode.SCENE3D && frameState.camera.frustum instanceof OrthographicFrustum; + if (frameState.mode === SceneMode.SCENE3D && !ortho3D) { var occludeePointInScaledSpace = surfaceTile.occludeePointInScaledSpace; if (!defined(occludeePointInScaledSpace)) { return intersection; diff --git a/Source/Scene/OrthographicFrustum.js b/Source/Scene/OrthographicFrustum.js index 9c709b191809..5271d1daa5dd 100644 --- a/Source/Scene/OrthographicFrustum.js +++ b/Source/Scene/OrthographicFrustum.js @@ -1,20 +1,16 @@ /*global define*/ define([ - '../Core/Cartesian3', - '../Core/Cartesian4', '../Core/defined', '../Core/defineProperties', + '../Core/deprecationWarning', '../Core/DeveloperError', - '../Core/Matrix4', - './CullingVolume' + './OrthographicOffCenterFrustum' ], function( - Cartesian3, - Cartesian4, defined, defineProperties, + deprecationWarning, DeveloperError, - Matrix4, - CullingVolume) { + OrthographicOffCenterFrustum) { 'use strict'; /** @@ -23,13 +19,13 @@ define([ * define the unit vector normal to the plane, and the w component is the distance of the * plane from the origin/camera position. * - * @alias OrthographicFrustum + * @alias OrthographicOffCenterFrustum * @constructor * * @example * var maxRadii = ellipsoid.maximumRadius; * - * var frustum = new Cesium.OrthographicFrustum(); + * var frustum = new Cesium.OrthographicOffCenterFrustum(); * frustum.right = maxRadii * Cesium.Math.PI; * frustum.left = -c.frustum.right; * frustum.top = c.frustum.right * (canvas.clientHeight / canvas.clientWidth); @@ -38,37 +34,13 @@ define([ * frustum.far = 50.0 * maxRadii; */ function OrthographicFrustum() { - /** - * The left clipping plane. - * @type {Number} - * @default undefined - */ - this.left = undefined; - this._left = undefined; + this._offCenterFrustum = new OrthographicOffCenterFrustum(); - /** - * The right clipping plane. - * @type {Number} - * @default undefined - */ - this.right = undefined; - this._right = undefined; - - /** - * The top clipping plane. - * @type {Number} - * @default undefined - */ - this.top = undefined; - this._top = undefined; + this.width = undefined; + this._width = undefined; - /** - * The bottom clipping plane. - * @type {Number} - * @default undefined - */ - this.bottom = undefined; - this._bottom = undefined; + this.aspectRatio = undefined; + this._aspectRatio = undefined; /** * The distance of the near plane. @@ -86,42 +58,47 @@ define([ this.far = 500000000.0; this._far = this.far; - this._cullingVolume = new CullingVolume(); - this._orthographicMatrix = new Matrix4(); + this._useDeprecated = false; } function update(frustum) { //>>includeStart('debug', pragmas.debug); - if (!defined(frustum.right) || !defined(frustum.left) || - !defined(frustum.top) || !defined(frustum.bottom) || - !defined(frustum.near) || !defined(frustum.far)) { - throw new DeveloperError('right, left, top, bottom, near, or far parameters are not set.'); + if (!defined(frustum.width) || !defined(frustum.aspectRatio) || !defined(frustum.near) || !defined(frustum.far)) { + throw new DeveloperError('width, aspectRatio, near, or far parameters are not set.'); } //>>includeEnd('debug'); - if (frustum.top !== frustum._top || frustum.bottom !== frustum._bottom || - frustum.left !== frustum._left || frustum.right !== frustum._right || - frustum.near !== frustum._near || frustum.far !== frustum._far) { + var f = frustum._offCenterFrustum; + if (frustum.width !== frustum._width || frustum.aspectRatio !== frustum._aspectRatio || + frustum.near !== frustum._near || frustum.far !== frustum._far) { //>>includeStart('debug', pragmas.debug); - if (frustum.left > frustum.right) { - throw new DeveloperError('right must be greater than left.'); - } - if (frustum.bottom > frustum.top) { - throw new DeveloperError('top must be greater than bottom.'); + if (frustum.aspectRatio < 0) { + throw new DeveloperError('aspectRatio must be positive.'); } - if (frustum.near <= 0 || frustum.near > frustum.far) { + if (frustum.near < 0 || frustum.near > frustum.far) { throw new DeveloperError('near must be greater than zero and less than far.'); } //>>includeEnd('debug'); - frustum._left = frustum.left; - frustum._right = frustum.right; - frustum._top = frustum.top; - frustum._bottom = frustum.bottom; + frustum._aspectRatio = frustum.aspectRatio; + frustum._width = frustum.width; frustum._near = frustum.near; frustum._far = frustum.far; - frustum._orthographicMatrix = Matrix4.computeOrthographicOffCenter(frustum.left, frustum.right, frustum.bottom, frustum.top, frustum.near, frustum.far, frustum._orthographicMatrix); + + if (!frustum._useDeprecated) { + var ratio = frustum.aspectRatio; + if (ratio > 1.0) { + ratio = 1.0 / frustum.aspectRatio; + } + + f.right = frustum.width * 0.5; + f.left = -f.right; + f.top = ratio * f.right; + f.bottom = -f.top; + f.near = frustum.near; + f.far = frustum.far; + } } } @@ -135,16 +112,79 @@ define([ projectionMatrix : { get : function() { update(this); - return this._orthographicMatrix; + return this._offCenterFrustum.projectionMatrix; + } + }, + + /** + * The left clipping plane. + * @type {Number} + * @default undefined + */ + left : { + get : function() { + deprecationWarning('OrthographicFrustum', 'OrthographicFrustum left, right, bottom and top properties were deprecated in 1.32 and will be removed in 1.33.'); + return this._offCenterFrustum.left; + }, + set : function(value) { + deprecationWarning('OrthographicFrustum', 'OrthographicFrustum left, right, bottom and top properties were deprecated in 1.32 and will be removed in 1.33.'); + this._useDeprecated = true; + this._offCenterFrustum.left = value; + } + }, + + /** + * The right clipping plane. + * @type {Number} + * @default undefined + */ + right : { + get : function() { + deprecationWarning('OrthographicFrustum', 'OrthographicFrustum left, right, bottom and top properties were deprecated in 1.32 and will be removed in 1.33.'); + return this._offCenterFrustum.right; + }, + set : function(value) { + deprecationWarning('OrthographicFrustum', 'OrthographicFrustum left, right, bottom and top properties were deprecated in 1.32 and will be removed in 1.33.'); + this._useDeprecated = true; + this._offCenterFrustum.right = value; + } + }, + + /** + * The top clipping plane. + * @type {Number} + * @default undefined + */ + top : { + get : function() { + deprecationWarning('OrthographicFrustum', 'OrthographicFrustum left, right, bottom and top properties were deprecated in 1.32 and will be removed in 1.33.'); + return this._offCenterFrustum.top; + }, + set : function(value) { + deprecationWarning('OrthographicFrustum', 'OrthographicFrustum left, right, bottom and top properties were deprecated in 1.32 and will be removed in 1.33.'); + this._useDeprecated = true; + this._offCenterFrustum.top = value; + } + }, + + /** + * The bottom clipping plane. + * @type {Number} + * @default undefined + */ + bottom : { + get : function() { + deprecationWarning('OrthographicFrustum', 'OrthographicFrustum left, right, bottom and top properties were deprecated in 1.32 and will be removed in 1.33.'); + return this._offCenterFrustum.bottom; + }, + set : function(value) { + deprecationWarning('OrthographicFrustum', 'OrthographicFrustum left, right, bottom and top properties were deprecated in 1.32 and will be removed in 1.33.'); + this._useDeprecated = true; + this._offCenterFrustum.bottom = value; } } }); - var getPlanesRight = new Cartesian3(); - var getPlanesNearCenter = new Cartesian3(); - var getPlanesPoint = new Cartesian3(); - var negateScratch = new Cartesian3(); - /** * Creates a culling volume for this frustum. * @@ -159,109 +199,8 @@ define([ * var intersect = cullingVolume.computeVisibility(boundingVolume); */ OrthographicFrustum.prototype.computeCullingVolume = function(position, direction, up) { - //>>includeStart('debug', pragmas.debug); - if (!defined(position)) { - throw new DeveloperError('position is required.'); - } - if (!defined(direction)) { - throw new DeveloperError('direction is required.'); - } - if (!defined(up)) { - throw new DeveloperError('up is required.'); - } - //>>includeEnd('debug'); - - var planes = this._cullingVolume.planes; - var t = this.top; - var b = this.bottom; - var r = this.right; - var l = this.left; - var n = this.near; - var f = this.far; - - var right = Cartesian3.cross(direction, up, getPlanesRight); - var nearCenter = getPlanesNearCenter; - Cartesian3.multiplyByScalar(direction, n, nearCenter); - Cartesian3.add(position, nearCenter, nearCenter); - - var point = getPlanesPoint; - - // Left plane - Cartesian3.multiplyByScalar(right, l, point); - Cartesian3.add(nearCenter, point, point); - - var plane = planes[0]; - if (!defined(plane)) { - plane = planes[0] = new Cartesian4(); - } - plane.x = right.x; - plane.y = right.y; - plane.z = right.z; - plane.w = -Cartesian3.dot(right, point); - - // Right plane - Cartesian3.multiplyByScalar(right, r, point); - Cartesian3.add(nearCenter, point, point); - - plane = planes[1]; - if (!defined(plane)) { - plane = planes[1] = new Cartesian4(); - } - plane.x = -right.x; - plane.y = -right.y; - plane.z = -right.z; - plane.w = -Cartesian3.dot(Cartesian3.negate(right, negateScratch), point); - - // Bottom plane - Cartesian3.multiplyByScalar(up, b, point); - Cartesian3.add(nearCenter, point, point); - - plane = planes[2]; - if (!defined(plane)) { - plane = planes[2] = new Cartesian4(); - } - plane.x = up.x; - plane.y = up.y; - plane.z = up.z; - plane.w = -Cartesian3.dot(up, point); - - // Top plane - Cartesian3.multiplyByScalar(up, t, point); - Cartesian3.add(nearCenter, point, point); - - plane = planes[3]; - if (!defined(plane)) { - plane = planes[3] = new Cartesian4(); - } - plane.x = -up.x; - plane.y = -up.y; - plane.z = -up.z; - plane.w = -Cartesian3.dot(Cartesian3.negate(up, negateScratch), point); - - // Near plane - plane = planes[4]; - if (!defined(plane)) { - plane = planes[4] = new Cartesian4(); - } - plane.x = direction.x; - plane.y = direction.y; - plane.z = direction.z; - plane.w = -Cartesian3.dot(direction, nearCenter); - - // Far plane - Cartesian3.multiplyByScalar(direction, f, point); - Cartesian3.add(position, point, point); - - plane = planes[5]; - if (!defined(plane)) { - plane = planes[5] = new Cartesian4(); - } - plane.x = -direction.x; - plane.y = -direction.y; - plane.z = -direction.z; - plane.w = -Cartesian3.dot(Cartesian3.negate(direction, negateScratch), point); - - return this._cullingVolume; + update(this); + return this._offCenterFrustum.computeCullingVolume(position, direction, up); }; /** @@ -283,61 +222,33 @@ define([ */ OrthographicFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) { update(this); - - //>>includeStart('debug', pragmas.debug); - if (!defined(drawingBufferWidth) || !defined(drawingBufferHeight)) { - throw new DeveloperError('Both drawingBufferWidth and drawingBufferHeight are required.'); - } - if (drawingBufferWidth <= 0) { - throw new DeveloperError('drawingBufferWidth must be greater than zero.'); - } - if (drawingBufferHeight <= 0) { - throw new DeveloperError('drawingBufferHeight must be greater than zero.'); - } - if (!defined(distance)) { - throw new DeveloperError('distance is required.'); - } - if (!defined(result)) { - throw new DeveloperError('A result object is required.'); - } - //>>includeEnd('debug'); - - var frustumWidth = this.right - this.left; - var frustumHeight = this.top - this.bottom; - var pixelWidth = frustumWidth / drawingBufferWidth; - var pixelHeight = frustumHeight / drawingBufferHeight; - - result.x = pixelWidth; - result.y = pixelHeight; - return result; + return this._offCenterFrustum.getPixelDimensions(drawingBufferWidth, drawingBufferHeight, distance, result); }; /** * Returns a duplicate of a OrthographicFrustum instance. * * @param {OrthographicFrustum} [result] The object onto which to store the result. - * @returns {OrthographicFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided. + * @returns {OrthographicFrustum} The modified result parameter or a new OrthographicFrustum instance if one was not provided. */ OrthographicFrustum.prototype.clone = function(result) { if (!defined(result)) { result = new OrthographicFrustum(); } - result.left = this.left; - result.right = this.right; - result.top = this.top; - result.bottom = this.bottom; + result.aspectRatio = this.aspectRatio; + result.width = this.width; result.near = this.near; result.far = this.far; // force update of clone to compute matrices - result._left = undefined; - result._right = undefined; - result._top = undefined; - result._bottom = undefined; + result._aspectRatio = undefined; + result._width = undefined; result._near = undefined; result._far = undefined; + this._offCenterFrustum.clone(result._offCenterFrustum); + return result; }; @@ -349,13 +260,18 @@ define([ * @returns {Boolean} true if they are equal, false otherwise. */ OrthographicFrustum.prototype.equals = function(other) { - return (defined(other) && - this.right === other.right && - this.left === other.left && - this.top === other.top && - this.bottom === other.bottom && + if (!defined(other)) { + return false; + } + + update(this); + update(other); + + return (this.width === other.width && + this.aspectRatio === other.aspectRatio && this.near === other.near && - this.far === other.far); + this.far === other.far && + this._offCenterFrustum.equals(other._offCenterFrustum)); }; return OrthographicFrustum; diff --git a/Source/Scene/OrthographicOffCenterFrustum.js b/Source/Scene/OrthographicOffCenterFrustum.js new file mode 100644 index 000000000000..d14053b6ec51 --- /dev/null +++ b/Source/Scene/OrthographicOffCenterFrustum.js @@ -0,0 +1,362 @@ +/*global define*/ +define([ + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Matrix4', + './CullingVolume' + ], function( + Cartesian3, + Cartesian4, + defined, + defineProperties, + DeveloperError, + Matrix4, + CullingVolume) { + 'use strict'; + + /** + * The viewing frustum is defined by 6 planes. + * Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components + * define the unit vector normal to the plane, and the w component is the distance of the + * plane from the origin/camera position. + * + * @alias OrthographicOffCenterFrustum + * @constructor + * + * @example + * var maxRadii = ellipsoid.maximumRadius; + * + * var frustum = new Cesium.OrthographicOffCenterFrustum(); + * frustum.right = maxRadii * Cesium.Math.PI; + * frustum.left = -c.frustum.right; + * frustum.top = c.frustum.right * (canvas.clientHeight / canvas.clientWidth); + * frustum.bottom = -c.frustum.top; + * frustum.near = 0.01 * maxRadii; + * frustum.far = 50.0 * maxRadii; + */ + function OrthographicOffCenterFrustum() { + /** + * The left clipping plane. + * @type {Number} + * @default undefined + */ + this.left = undefined; + this._left = undefined; + + /** + * The right clipping plane. + * @type {Number} + * @default undefined + */ + this.right = undefined; + this._right = undefined; + + /** + * The top clipping plane. + * @type {Number} + * @default undefined + */ + this.top = undefined; + this._top = undefined; + + /** + * The bottom clipping plane. + * @type {Number} + * @default undefined + */ + this.bottom = undefined; + this._bottom = undefined; + + /** + * The distance of the near plane. + * @type {Number} + * @default 1.0 + */ + this.near = 1.0; + this._near = this.near; + + /** + * The distance of the far plane. + * @type {Number} + * @default 500000000.0; + */ + this.far = 500000000.0; + this._far = this.far; + + this._cullingVolume = new CullingVolume(); + this._orthographicMatrix = new Matrix4(); + } + + function update(frustum) { + //>>includeStart('debug', pragmas.debug); + if (!defined(frustum.right) || !defined(frustum.left) || + !defined(frustum.top) || !defined(frustum.bottom) || + !defined(frustum.near) || !defined(frustum.far)) { + throw new DeveloperError('right, left, top, bottom, near, or far parameters are not set.'); + } + //>>includeEnd('debug'); + + if (frustum.top !== frustum._top || frustum.bottom !== frustum._bottom || + frustum.left !== frustum._left || frustum.right !== frustum._right || + frustum.near !== frustum._near || frustum.far !== frustum._far) { + + //>>includeStart('debug', pragmas.debug); + if (frustum.left > frustum.right) { + throw new DeveloperError('right must be greater than left.'); + } + if (frustum.bottom > frustum.top) { + throw new DeveloperError('top must be greater than bottom.'); + } + if (frustum.near <= 0 || frustum.near > frustum.far) { + throw new DeveloperError('near must be greater than zero and less than far.'); + } + //>>includeEnd('debug'); + + frustum._left = frustum.left; + frustum._right = frustum.right; + frustum._top = frustum.top; + frustum._bottom = frustum.bottom; + frustum._near = frustum.near; + frustum._far = frustum.far; + frustum._orthographicMatrix = Matrix4.computeOrthographicOffCenter(frustum.left, frustum.right, frustum.bottom, frustum.top, frustum.near, frustum.far, frustum._orthographicMatrix); + } + } + + defineProperties(OrthographicOffCenterFrustum.prototype, { + /** + * Gets the orthographic projection matrix computed from the view frustum. + * @memberof OrthographicOffCenterFrustum.prototype + * @type {Matrix4} + * @readonly + */ + projectionMatrix : { + get : function() { + update(this); + return this._orthographicMatrix; + } + } + }); + + var getPlanesRight = new Cartesian3(); + var getPlanesNearCenter = new Cartesian3(); + var getPlanesPoint = new Cartesian3(); + var negateScratch = new Cartesian3(); + + /** + * Creates a culling volume for this frustum. + * + * @param {Cartesian3} position The eye position. + * @param {Cartesian3} direction The view direction. + * @param {Cartesian3} up The up direction. + * @returns {CullingVolume} A culling volume at the given position and orientation. + * + * @example + * // Check if a bounding volume intersects the frustum. + * var cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp); + * var intersect = cullingVolume.computeVisibility(boundingVolume); + */ + OrthographicOffCenterFrustum.prototype.computeCullingVolume = function(position, direction, up) { + //>>includeStart('debug', pragmas.debug); + if (!defined(position)) { + throw new DeveloperError('position is required.'); + } + if (!defined(direction)) { + throw new DeveloperError('direction is required.'); + } + if (!defined(up)) { + throw new DeveloperError('up is required.'); + } + //>>includeEnd('debug'); + + var planes = this._cullingVolume.planes; + var t = this.top; + var b = this.bottom; + var r = this.right; + var l = this.left; + var n = this.near; + var f = this.far; + + var right = Cartesian3.cross(direction, up, getPlanesRight); + var nearCenter = getPlanesNearCenter; + Cartesian3.multiplyByScalar(direction, n, nearCenter); + Cartesian3.add(position, nearCenter, nearCenter); + + var point = getPlanesPoint; + + // Left plane + Cartesian3.multiplyByScalar(right, l, point); + Cartesian3.add(nearCenter, point, point); + + var plane = planes[0]; + if (!defined(plane)) { + plane = planes[0] = new Cartesian4(); + } + plane.x = right.x; + plane.y = right.y; + plane.z = right.z; + plane.w = -Cartesian3.dot(right, point); + + // Right plane + Cartesian3.multiplyByScalar(right, r, point); + Cartesian3.add(nearCenter, point, point); + + plane = planes[1]; + if (!defined(plane)) { + plane = planes[1] = new Cartesian4(); + } + plane.x = -right.x; + plane.y = -right.y; + plane.z = -right.z; + plane.w = -Cartesian3.dot(Cartesian3.negate(right, negateScratch), point); + + // Bottom plane + Cartesian3.multiplyByScalar(up, b, point); + Cartesian3.add(nearCenter, point, point); + + plane = planes[2]; + if (!defined(plane)) { + plane = planes[2] = new Cartesian4(); + } + plane.x = up.x; + plane.y = up.y; + plane.z = up.z; + plane.w = -Cartesian3.dot(up, point); + + // Top plane + Cartesian3.multiplyByScalar(up, t, point); + Cartesian3.add(nearCenter, point, point); + + plane = planes[3]; + if (!defined(plane)) { + plane = planes[3] = new Cartesian4(); + } + plane.x = -up.x; + plane.y = -up.y; + plane.z = -up.z; + plane.w = -Cartesian3.dot(Cartesian3.negate(up, negateScratch), point); + + // Near plane + plane = planes[4]; + if (!defined(plane)) { + plane = planes[4] = new Cartesian4(); + } + plane.x = direction.x; + plane.y = direction.y; + plane.z = direction.z; + plane.w = -Cartesian3.dot(direction, nearCenter); + + // Far plane + Cartesian3.multiplyByScalar(direction, f, point); + Cartesian3.add(position, point, point); + + plane = planes[5]; + if (!defined(plane)) { + plane = planes[5] = new Cartesian4(); + } + plane.x = -direction.x; + plane.y = -direction.y; + plane.z = -direction.z; + plane.w = -Cartesian3.dot(Cartesian3.negate(direction, negateScratch), point); + + return this._cullingVolume; + }; + + /** + * Returns the pixel's width and height in meters. + * + * @param {Number} drawingBufferWidth The width of the drawing buffer. + * @param {Number} drawingBufferHeight The height of the drawing buffer. + * @param {Number} distance The distance to the near plane in meters. + * @param {Cartesian2} result The object onto which to store the result. + * @returns {Cartesian2} The modified result parameter or a new instance of {@link Cartesian2} with the pixel's width and height in the x and y properties, respectively. + * + * @exception {DeveloperError} drawingBufferWidth must be greater than zero. + * @exception {DeveloperError} drawingBufferHeight must be greater than zero. + * + * @example + * // Example 1 + * // Get the width and height of a pixel. + * var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 0.0, new Cesium.Cartesian2()); + */ + OrthographicOffCenterFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) { + update(this); + + //>>includeStart('debug', pragmas.debug); + if (!defined(drawingBufferWidth) || !defined(drawingBufferHeight)) { + throw new DeveloperError('Both drawingBufferWidth and drawingBufferHeight are required.'); + } + if (drawingBufferWidth <= 0) { + throw new DeveloperError('drawingBufferWidth must be greater than zero.'); + } + if (drawingBufferHeight <= 0) { + throw new DeveloperError('drawingBufferHeight must be greater than zero.'); + } + if (!defined(distance)) { + throw new DeveloperError('distance is required.'); + } + if (!defined(result)) { + throw new DeveloperError('A result object is required.'); + } + //>>includeEnd('debug'); + + var frustumWidth = this.right - this.left; + var frustumHeight = this.top - this.bottom; + var pixelWidth = frustumWidth / drawingBufferWidth; + var pixelHeight = frustumHeight / drawingBufferHeight; + + result.x = pixelWidth; + result.y = pixelHeight; + return result; + }; + + /** + * Returns a duplicate of a OrthographicOffCenterFrustum instance. + * + * @param {OrthographicOffCenterFrustum} [result] The object onto which to store the result. + * @returns {OrthographicOffCenterFrustum} The modified result parameter or a new OrthographicOffCenterFrustum instance if one was not provided. + */ + OrthographicOffCenterFrustum.prototype.clone = function(result) { + if (!defined(result)) { + result = new OrthographicOffCenterFrustum(); + } + + result.left = this.left; + result.right = this.right; + result.top = this.top; + result.bottom = this.bottom; + result.near = this.near; + result.far = this.far; + + // force update of clone to compute matrices + result._left = undefined; + result._right = undefined; + result._top = undefined; + result._bottom = undefined; + result._near = undefined; + result._far = undefined; + + return result; + }; + + /** + * Compares the provided OrthographicOffCenterFrustum componentwise and returns + * true if they are equal, false otherwise. + * + * @param {OrthographicOffCenterFrustum} [other] The right hand side OrthographicOffCenterFrustum. + * @returns {Boolean} true if they are equal, false otherwise. + */ + OrthographicOffCenterFrustum.prototype.equals = function(other) { + return (defined(other) && + this.right === other.right && + this.left === other.left && + this.top === other.top && + this.bottom === other.bottom && + this.near === other.near && + this.far === other.far); + }; + + return OrthographicOffCenterFrustum; +}); diff --git a/Source/Scene/QuadtreePrimitive.js b/Source/Scene/QuadtreePrimitive.js index 78c09c7be8a4..634e98218253 100644 --- a/Source/Scene/QuadtreePrimitive.js +++ b/Source/Scene/QuadtreePrimitive.js @@ -12,6 +12,7 @@ define([ '../Core/Ray', '../Core/Rectangle', '../Core/Visibility', + './OrthographicFrustum', './QuadtreeOccluders', './QuadtreeTile', './QuadtreeTileLoadState', @@ -30,6 +31,7 @@ define([ Ray, Rectangle, Visibility, + OrthographicFrustum, QuadtreeOccluders, QuadtreeTile, QuadtreeTileLoadState, @@ -667,7 +669,7 @@ define([ } function screenSpaceError(primitive, frameState, tile) { - if (frameState.mode === SceneMode.SCENE2D) { + if (frameState.mode === SceneMode.SCENE2D || frameState.camera.frustum instanceof OrthographicFrustum) { return screenSpaceError2D(primitive, frameState, tile); } @@ -689,6 +691,9 @@ define([ function screenSpaceError2D(primitive, frameState, tile) { var camera = frameState.camera; var frustum = camera.frustum; + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } var context = frameState.context; var width = context.drawingBufferWidth; @@ -696,7 +701,13 @@ define([ var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level); var pixelSize = Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) / Math.max(width, height); - return maxGeometricError / pixelSize; + var error = maxGeometricError / pixelSize; + + if (frameState.fog.enabled && frameState.mode !== SceneMode.SCENE2D) { + error = error - CesiumMath.fog(tile._distance, frameState.fog.density) * frameState.fog.sse; + } + + return error; } function addTileToRenderList(primitive, tile) { diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index e32dcc4afe31..29197780a636 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -58,6 +58,7 @@ define([ './MapMode2D', './OIT', './OrthographicFrustum', + './OrthographicOffCenterFrustum', './PerformanceDisplay', './PerInstanceColorAppearance', './PerspectiveFrustum', @@ -131,6 +132,7 @@ define([ MapMode2D, OIT, OrthographicFrustum, + OrthographicOffCenterFrustum, PerformanceDisplay, PerInstanceColorAppearance, PerspectiveFrustum, @@ -1093,6 +1095,11 @@ define([ return this._useWebVR; }, set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (this.camera.frustum instanceof OrthographicFrustum) { + throw new DeveloperError('VR is unsupported with an orthographic projection.'); + } + //>>includeEnd('debug'); this._useWebVR = value; if (this._useWebVR) { this._frameState.creditDisplay.container.style.visibility = 'hidden'; @@ -1735,6 +1742,7 @@ define([ var scratchPerspectiveFrustum = new PerspectiveFrustum(); var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum(); var scratchOrthographicFrustum = new OrthographicFrustum(); + var scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum(); function executeCommands(scene, passState) { var camera = scene._camera; @@ -1749,8 +1757,10 @@ define([ frustum = camera.frustum.clone(scratchPerspectiveFrustum); } else if (defined(camera.frustum.infiniteProjectionMatrix)){ frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum); - } else { + } else if (defined(camera.frustum.width)) { frustum = camera.frustum.clone(scratchOrthographicFrustum); + } else { + frustum = camera.frustum.clone(scratchOrthographicOffCenterFrustum); } // Ideally, we would render the sky box and atmosphere last for @@ -2254,18 +2264,27 @@ define([ // Update celestial and terrestrial environment effects. var environmentState = scene._environmentState; var renderPass = frameState.passes.render; - environmentState.skyBoxCommand = (renderPass && defined(scene.skyBox)) ? scene.skyBox.update(frameState) : undefined; var skyAtmosphere = scene.skyAtmosphere; var globe = scene.globe; - if (defined(skyAtmosphere) && defined(globe)) { - skyAtmosphere.setDynamicAtmosphereColor(globe.enableLighting); - environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || globe._surface._tilesToRender.length > 0; + + if (!renderPass || (scene._mode !== SceneMode.SCENE2D && frameState.camera.frustum instanceof OrthographicFrustum)) { + environmentState.skyAtmosphereCommand = undefined; + environmentState.skyBoxCommand = undefined; + environmentState.sunDrawCommand = undefined; + environmentState.sunComputeCommand = undefined; + environmentState.moonCommand = undefined; + } else { + if (defined(skyAtmosphere) && defined(globe)) { + skyAtmosphere.setDynamicAtmosphereColor(globe.enableLighting); + environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || globe._surface._tilesToRender.length > 0; + } + environmentState.skyAtmosphereCommand = defined(skyAtmosphere) ? skyAtmosphere.update(frameState) : undefined; + environmentState.skyBoxCommand = defined(scene.skyBox) ? scene.skyBox.update(frameState) : undefined; + var sunCommands = defined(scene.sun) ? scene.sun.update(scene) : undefined; + environmentState.sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined; + environmentState.sunComputeCommand = defined(sunCommands) ? sunCommands.computeCommand : undefined; + environmentState.moonCommand = defined(scene.moon) ? scene.moon.update(frameState) : undefined; } - environmentState.skyAtmosphereCommand = (renderPass && defined(skyAtmosphere)) ? skyAtmosphere.update(frameState) : undefined; - var sunCommands = (renderPass && defined(scene.sun)) ? scene.sun.update(scene) : undefined; - environmentState.sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined; - environmentState.sunComputeCommand = defined(sunCommands) ? sunCommands.computeCommand : undefined; - environmentState.moonCommand = (renderPass && defined(scene.moon)) ? scene.moon.update(frameState) : undefined; var clearGlobeDepth = environmentState.clearGlobeDepth = defined(globe) && (!globe.depthTestAgainstTerrain || scene.mode === SceneMode.SCENE2D); var useDepthPlane = environmentState.useDepthPlane = clearGlobeDepth && scene.mode === SceneMode.SCENE3D; @@ -2611,7 +2630,7 @@ define([ return Math.max(ContextLimits.minimumAliasedLineWidth, Math.min(width, ContextLimits.maximumAliasedLineWidth)); }; - var orthoPickingFrustum = new OrthographicFrustum(); + var orthoPickingFrustum = new OrthographicOffCenterFrustum(); var scratchOrigin = new Cartesian3(); var scratchDirection = new Cartesian3(); var scratchPixelSize = new Cartesian2(); @@ -2620,6 +2639,9 @@ define([ function getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height) { var camera = scene._camera; var frustum = camera.frustum; + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } var viewport = scene._passState.viewport; var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0; @@ -2638,7 +2660,9 @@ define([ camera._setTransform(transform); - Cartesian3.fromElements(origin.z, origin.x, origin.y, origin); + if (scene.mode === SceneMode.SCENE2D) { + Cartesian3.fromElements(origin.z, origin.x, origin.y, origin); + } var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, scratchPixelSize); @@ -2686,7 +2710,8 @@ define([ } function getPickCullingVolume(scene, drawingBufferPosition, width, height) { - if (scene._mode === SceneMode.SCENE2D) { + var frustum = scene.camera.frustum; + if (frustum instanceof OrthographicFrustum || frustum instanceof OrthographicOffCenterFrustum) { return getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height); } @@ -2942,8 +2967,10 @@ define([ frustum = camera.frustum.clone(scratchPerspectiveFrustum); } else if (defined(camera.frustum.infiniteProjectionMatrix)){ frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum); - } else { + } else if (defined(camera.frustum.width)) { frustum = camera.frustum.clone(scratchOrthographicFrustum); + } else { + frustum = camera.frustum.clone(scratchOrthographicOffCenterFrustum); } var numFrustums = this.numberOfFrustums; diff --git a/Source/Scene/SceneTransforms.js b/Source/Scene/SceneTransforms.js index afe452a05399..9156130c26f8 100644 --- a/Source/Scene/SceneTransforms.js +++ b/Source/Scene/SceneTransforms.js @@ -10,6 +10,8 @@ define([ '../Core/Math', '../Core/Matrix4', '../Core/Transforms', + './OrthographicFrustum', + './OrthographicOffCenterFrustum', './SceneMode' ], function( BoundingRectangle, @@ -22,6 +24,8 @@ define([ CesiumMath, Matrix4, Transforms, + OrthographicFrustum, + OrthographicOffCenterFrustum, SceneMode) { 'use strict'; @@ -184,7 +188,7 @@ define([ if (frameState.mode !== SceneMode.SCENE2D || cameraCentered) { // View-projection matrix to transform from world coordinates to clip coordinates positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); - if (positionCC.z < 0 && frameState.mode !== SceneMode.SCENE2D) { + if (positionCC.z < 0 && !(camera.frustum instanceof OrthographicFrustum) && !(camera.frustum instanceof OrthographicOffCenterFrustum)) { return undefined; } @@ -279,20 +283,6 @@ define([ return Cartesian2.fromCartesian3(positionWC, result); }; - /** - * @private - */ - SceneTransforms.clipToDrawingBufferCoordinates = function(viewport, position, result) { - // Perspective divide to transform from clip coordinates to normalized device coordinates - Cartesian3.divideByScalar(position, position.w, positionNDC); - - // Viewport transform to transform from clip coordinates to drawing buffer coordinates - Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform); - Matrix4.multiplyByPoint(viewportTransform, positionNDC, positionWC); - - return Cartesian2.fromCartesian3(positionWC, result); - }; - /** * @private */ @@ -323,6 +313,9 @@ define([ var worldCoords; var frustum = scene.camera.frustum; if (!defined(frustum.fovy)) { + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } var currentFrustum = uniformState.currentFrustum; worldCoords = scratchWorldCoords; worldCoords.x = (ndc.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; diff --git a/Source/Scene/SceneTransitioner.js b/Source/Scene/SceneTransitioner.js index 76c0989a6786..7c0f2fb6fe85 100644 --- a/Source/Scene/SceneTransitioner.js +++ b/Source/Scene/SceneTransitioner.js @@ -14,6 +14,7 @@ define([ '../Core/Transforms', './Camera', './OrthographicFrustum', + './OrthographicOffCenterFrustum', './PerspectiveFrustum', './SceneMode' ], function( @@ -31,6 +32,7 @@ define([ Transforms, Camera, OrthographicFrustum, + OrthographicOffCenterFrustum, PerspectiveFrustum, SceneMode) { 'use strict'; @@ -50,6 +52,7 @@ define([ this._morphHandler = undefined; this._morphCancelled = false; this._completeMorph = undefined; + this._morphToOrthographic = false; } SceneTransitioner.prototype.completeMorph = function() { @@ -65,6 +68,7 @@ define([ var scene = this._scene; this._previousMode = scene.mode; + this._morphToOrthographic = scene.camera.frustum instanceof OrthographicFrustum; if (this._previousMode === SceneMode.SCENE2D || this._previousMode === SceneMode.MORPHING) { return; @@ -94,7 +98,8 @@ define([ var scratchToCVSurfacePosition = new Cartesian3(); var scratchToCVCartographic = new Cartographic(); var scratchToCVToENU = new Matrix4(); - var scratchToCVFrustum = new PerspectiveFrustum(); + var scratchToCVFrustumPerspective = new PerspectiveFrustum(); + var scratchToCVFrustumOrthographic = new OrthographicFrustum(); var scratchToCVCamera = { position : undefined, direction : undefined, @@ -154,9 +159,16 @@ define([ } } - var frustum = scratchToCVFrustum; - frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; - frustum.fov = CesiumMath.toRadians(60.0); + var frustum; + if (this._morphToOrthographic) { + frustum = scratchToCVFrustumOrthographic; + frustum.width = scene.camera.frustum.right - scene.camera.frustum.left; + frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + } else { + frustum = scratchToCVFrustumPerspective; + frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + frustum.fov = CesiumMath.toRadians(60.0); + } var cameraCV = scratchToCVCamera; cameraCV.position = position; @@ -352,16 +364,14 @@ define([ transitioner._currentTweens.push(tween); } - var scratch2DTo3DFrustum = new PerspectiveFrustum(); + var scratch2DTo3DFrustumPersp = new PerspectiveFrustum(); + var scratch2DTo3DFrustumOrtho = new OrthographicFrustum(); function morphFrom2DTo3D(transitioner, duration, ellipsoid) { duration /= 3.0; var scene = transitioner._scene; var camera = scene.camera; - var frustum = scratch2DTo3DFrustum; - frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; - frustum.fov = CesiumMath.toRadians(60.0); var camera3D; if (duration > 0.0) { @@ -376,8 +386,6 @@ define([ camera3D = getColumbusViewTo3DCamera(transitioner, ellipsoid); } - camera3D.frustum = frustum; - var complete = complete3DCallback(camera3D); createMorphHandler(transitioner, complete); @@ -408,6 +416,28 @@ define([ camera.position.z = 2.0 * scene.mapProjection.ellipsoid.maximumRadius; } + var morph; + var frustum; + if (transitioner._morphToOrthographic) { + morph = function() { + frustum = scratch2DTo3DFrustumOrtho; + frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + frustum.width = camera.frustum.right - camera.frustum.left; + camera.frustum = frustum; + morphFromColumbusViewTo3D(transitioner, duration, camera3D, complete); + }; + } else { + morph = function() { + frustum = scratch2DTo3DFrustumPersp; + frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + frustum.fov = CesiumMath.toRadians(60.0); + camera3D.frustum = frustum; + morphOrthographicToPerspective(transitioner, duration, camera3D, function() { + morphFromColumbusViewTo3D(transitioner, duration, camera3D, complete); + }); + }; + } + if (duration > 0.0) { var tween = scene.tweens.add({ duration : duration, @@ -421,16 +451,12 @@ define([ update : update, complete : function() { scene._mode = SceneMode.MORPHING; - morphOrthographicToPerspective(transitioner, duration, camera3D, function() { - morphFromColumbusViewTo3D(transitioner, duration, camera3D, complete); - }); + morph(); } }); transitioner._currentTweens.push(tween); } else { - morphOrthographicToPerspective(transitioner, duration, camera3D, function() { - morphFromColumbusViewTo3D(transitioner, duration, camera3D, complete); - }); + morph(); } } @@ -443,6 +469,10 @@ define([ var scene = transitioner._scene; var camera = scene.camera; + if (camera.frustum instanceof OrthographicFrustum) { + return; + } + var startFOV = camera.frustum.fov; var endFOV = CesiumMath.RADIANS_PER_DEGREE * 0.5; var d = endCamera.position.z * Math.tan(startFOV * 0.5); @@ -477,7 +507,7 @@ define([ var scratchCVTo2DEndPos = new Cartesian3(); var scratchCVTo2DEndDir = new Cartesian3(); var scratchCVTo2DEndUp = new Cartesian3(); - var scratchCVTo2DFrustum = new OrthographicFrustum(); + var scratchCVTo2DFrustum = new OrthographicOffCenterFrustum(); var scratchCVTo2DRay = new Ray(); var scratchCVTo2DPickPos = new Cartesian3(); var scratchCVTo2DCamera = { @@ -543,6 +573,7 @@ define([ columbusViewMorph(startUp, endUp, value.time, camera.up); Cartesian3.cross(camera.direction, camera.up, camera.right); Cartesian3.normalize(camera.right, camera.right); + camera._adjustOrthographicFrustum(true); } function updateHeight(camera, height) { @@ -574,7 +605,7 @@ define([ position2D : new Cartesian3(), direction2D : new Cartesian3(), up2D : new Cartesian3(), - frustum : new OrthographicFrustum() + frustum : new OrthographicOffCenterFrustum() }; var scratch3DTo2DEndCamera = { position : new Cartesian3(), @@ -700,7 +731,8 @@ define([ var endUp = Cartesian3.clone(cameraCV.up, scratch3DToCVEndUp); scene._mode = SceneMode.MORPHING; - morphOrthographicToPerspective(transitioner, 0.0, cameraCV, function() { + + function morph() { camera.frustum = cameraCV.frustum.clone(); var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos); @@ -730,7 +762,13 @@ define([ } }); transitioner._currentTweens.push(tween); - }); + } + + if (transitioner._morphToOrthographic) { + morph(); + } else { + morphOrthographicToPerspective(transitioner, 0.0, cameraCV, morph); + } } var scratch3DToCVStartPos = new Cartesian3(); @@ -758,6 +796,7 @@ define([ columbusViewMorph(startUp, endUp, value.time, camera.up); Cartesian3.cross(camera.direction, camera.up, camera.right); Cartesian3.normalize(camera.right, camera.right); + camera._adjustOrthographicFrustum(true); } var tween = scene.tweens.add({ duration : duration, @@ -814,10 +853,6 @@ define([ Cartesian3.clone(camera3D.up, camera.up); Cartesian3.cross(camera.direction, camera.up, camera.right); Cartesian3.normalize(camera.right, camera.right); - - if (defined(camera3D.frustum)) { - camera.frustum = camera3D.frustum.clone(); - } } var wasMorphing = defined(transitioner._completeMorph); @@ -859,7 +894,6 @@ define([ scene.morphTime = SceneMode.getMorphTime(SceneMode.COLUMBUS_VIEW); destroyMorphHandler(transitioner); - scene.camera.frustum = cameraCV.frustum.clone(); if (transitioner._previousModeMode !== SceneMode.MORPHING || transitioner._morphCancelled) { transitioner._morphCancelled = false; diff --git a/Source/Scene/ScreenSpaceCameraController.js b/Source/Scene/ScreenSpaceCameraController.js index 3d6e974b3ebc..6d07334507e4 100644 --- a/Source/Scene/ScreenSpaceCameraController.js +++ b/Source/Scene/ScreenSpaceCameraController.js @@ -22,6 +22,7 @@ define([ './CameraEventAggregator', './CameraEventType', './MapMode2D', + './OrthographicFrustum', './SceneMode', './SceneTransforms', './TweenCollection' @@ -48,6 +49,7 @@ define([ CameraEventAggregator, CameraEventType, MapMode2D, + OrthographicFrustum, SceneMode, SceneTransforms, TweenCollection) { @@ -484,6 +486,14 @@ define([ var camera = scene.camera; var mode = scene.mode; + if (camera.frustum instanceof OrthographicFrustum) { + if (Math.abs(distance) > 0.0) { + camera.zoomIn(distance); + camera._adjustOrthographicFrustum(); + } + return; + } + var sameStartPosition = Cartesian2.equals(startPosition, object._zoomMouseStart); var zoomingOnVector = object._zoomingOnVector; var rotatingZoom = object._rotatingZoom; @@ -1810,14 +1820,35 @@ define([ var endPos = look3DEndPos; endPos.x = movement.endPosition.x; endPos.y = 0.0; - var start = camera.getPickRay(startPos, look3DStartRay).direction; - var end = camera.getPickRay(endPos, look3DEndRay).direction; + var startRay = camera.getPickRay(startPos, look3DStartRay); + var endRay = camera.getPickRay(endPos, look3DEndRay); var angle = 0.0; + var start; + var end; + + if (camera.frustum instanceof OrthographicFrustum) { + start = startRay.origin; + end = endRay.origin; + + Cartesian3.add(camera.direction, start, start); + Cartesian3.add(camera.direction, end, end); + + Cartesian3.subtract(start, camera.position, start); + Cartesian3.subtract(end, camera.position, end); + + Cartesian3.normalize(start, start); + Cartesian3.normalize(end, end); + } else { + start = startRay.direction; + end = endRay.direction; + } + var dot = Cartesian3.dot(start, end); if (dot < 1.0) { // dot is in [0, 1] angle = Math.acos(dot); } + angle = (movement.startPosition.x > movement.endPosition.x) ? -angle : angle; var horizontalRotationAxis = controller._horizontalRotationAxis; @@ -1833,10 +1864,28 @@ define([ startPos.y = movement.startPosition.y; endPos.x = 0.0; endPos.y = movement.endPosition.y; - start = camera.getPickRay(startPos, look3DStartRay).direction; - end = camera.getPickRay(endPos, look3DEndRay).direction; + startRay = camera.getPickRay(startPos, look3DStartRay); + endRay = camera.getPickRay(endPos, look3DEndRay); angle = 0.0; + + if (camera.frustum instanceof OrthographicFrustum) { + start = startRay.origin; + end = endRay.origin; + + Cartesian3.add(camera.direction, start, start); + Cartesian3.add(camera.direction, end, end); + + Cartesian3.subtract(start, camera.position, start); + Cartesian3.subtract(end, camera.position, end); + + Cartesian3.normalize(start, start); + Cartesian3.normalize(end, end); + } else { + start = startRay.direction; + end = endRay.direction; + } + dot = Cartesian3.dot(start, end); if (dot < 1.0) { // dot is in [0, 1] angle = Math.acos(dot); diff --git a/Source/Scene/ShadowMap.js b/Source/Scene/ShadowMap.js index 171b6a9bb7a6..d65cf65528a8 100644 --- a/Source/Scene/ShadowMap.js +++ b/Source/Scene/ShadowMap.js @@ -45,7 +45,7 @@ define([ './CullFace', './CullingVolume', './DebugCameraPrimitive', - './OrthographicFrustum', + './OrthographicOffCenterFrustum', './PerInstanceColorAppearance', './PerspectiveFrustum', './Primitive', @@ -96,7 +96,7 @@ define([ CullFace, CullingVolume, DebugCameraPrimitive, - OrthographicFrustum, + OrthographicOffCenterFrustum, PerInstanceColorAppearance, PerspectiveFrustum, Primitive, @@ -254,7 +254,7 @@ define([ this._isSpotLight = false; if (this._cascadesEnabled) { // Cascaded shadows are always orthographic. The frustum dimensions are calculated on the fly. - this._shadowMapCamera.frustum = new OrthographicFrustum(); + this._shadowMapCamera.frustum = new OrthographicOffCenterFrustum(); } else if (defined(this._lightCamera.frustum.fov)) { // If the light camera uses a perspective frustum, then the light source is a spot light this._isSpotLight = true; diff --git a/Source/Scene/SkyAtmosphere.js b/Source/Scene/SkyAtmosphere.js index bf0a1274ef2d..89aa3410dab7 100644 --- a/Source/Scene/SkyAtmosphere.js +++ b/Source/Scene/SkyAtmosphere.js @@ -167,8 +167,9 @@ define([ return undefined; } - if ((frameState.mode !== SceneMode.SCENE3D) && - (frameState.mode !== SceneMode.MORPHING)) { + var mode = frameState.mode; + if ((mode !== SceneMode.SCENE3D) && + (mode !== SceneMode.MORPHING)) { return undefined; } @@ -305,7 +306,7 @@ define([ * * @example * skyAtmosphere = skyAtmosphere && skyAtmosphere.destroy(); - * + * * @see SkyAtmosphere#isDestroyed */ SkyAtmosphere.prototype.destroy = function() { diff --git a/Source/Scene/Sun.js b/Source/Scene/Sun.js index 78d22a8b40b1..e0e13a079827 100644 --- a/Source/Scene/Sun.js +++ b/Source/Scene/Sun.js @@ -67,7 +67,7 @@ define([ * * @example * scene.sun = new Cesium.Sun(); - * + * * @see Scene#sun */ function Sun() { @@ -299,11 +299,11 @@ define([ positionEC.w = 1; var positionCC = Matrix4.multiplyByVector(projMatrix, positionEC, scratchCartesian4); - var positionWC = SceneTransforms.clipToDrawingBufferCoordinates(passState.viewport, positionCC, scratchPositionWC); + var positionWC = SceneTransforms.clipToGLWindowCoordinates(passState.viewport, positionCC, scratchPositionWC); positionEC.x = CesiumMath.SOLAR_RADIUS; var limbCC = Matrix4.multiplyByVector(projMatrix, positionEC, scratchCartesian4); - var limbWC = SceneTransforms.clipToDrawingBufferCoordinates(passState.viewport, limbCC, scratchLimbWC); + var limbWC = SceneTransforms.clipToGLWindowCoordinates(passState.viewport, limbCC, scratchLimbWC); this._size = Math.ceil(Cartesian2.magnitude(Cartesian2.subtract(limbWC, positionWC, scratchCartesian4))); this._size = 2.0 * this._size * (1.0 + 2.0 * this._glowLengthTS); @@ -340,7 +340,7 @@ define([ * * @example * sun = sun && sun.destroy(); - * + * * @see Sun#isDestroyed */ Sun.prototype.destroy = function() { diff --git a/Source/Shaders/Builtin/Functions/alphaWeight.glsl b/Source/Shaders/Builtin/Functions/alphaWeight.glsl index 74913da8c59e..ed18c4285cb7 100644 --- a/Source/Shaders/Builtin/Functions/alphaWeight.glsl +++ b/Source/Shaders/Builtin/Functions/alphaWeight.glsl @@ -3,21 +3,29 @@ */ float czm_alphaWeight(float a) { - float z; - if (czm_sceneMode != czm_sceneMode2D) - { - float x = 2.0 * (gl_FragCoord.x - czm_viewport.x) / czm_viewport.z - 1.0; - float y = 2.0 * (gl_FragCoord.y - czm_viewport.y) / czm_viewport.w - 1.0; - z = (gl_FragCoord.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2]; - vec4 q = vec4(x, y, z, 0.0); - q /= gl_FragCoord.w; - z = (czm_inverseProjectionOIT * q).z; + float x = 2.0 * (gl_FragCoord.x - czm_viewport.x) / czm_viewport.z - 1.0; + float y = 2.0 * (gl_FragCoord.y - czm_viewport.y) / czm_viewport.w - 1.0; + float z = (gl_FragCoord.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2]; + vec4 q = vec4(x, y, z, 0.0); + q /= gl_FragCoord.w; + + if (czm_inverseProjection != mat4(0.0)) { + q = czm_inverseProjection * q; + } else { + float top = czm_frustumPlanes.x; + float bottom = czm_frustumPlanes.y; + float left = czm_frustumPlanes.z; + float right = czm_frustumPlanes.w; + + float near = czm_currentFrustum.x; + float far = czm_currentFrustum.y; + + q.x = (q.x * (right - left) + left + right) * 0.5; + q.y = (q.y * (top - bottom) + bottom + top) * 0.5; + q.z = (q.z * (near - far) - near - far) * 0.5; + q.w = 1.0; } - else - { - z = gl_FragCoord.z * (czm_currentFrustum.y - czm_currentFrustum.x) + czm_currentFrustum.x; - } - + // See Weighted Blended Order-Independent Transparency for examples of different weighting functions: // http://jcgt.org/published/0002/02/09/ return pow(a + 0.01, 4.0) + max(1e-2, min(3.0 * 1e3, 100.0 / (1e-5 + pow(abs(z) / 10.0, 3.0) + pow(abs(z) / 200.0, 6.0)))); diff --git a/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl b/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl index 5cb12e9e56fe..f429c26875ec 100644 --- a/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl +++ b/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl @@ -2,7 +2,7 @@ * Transforms a position from window to eye coordinates. * The transform from window to normalized device coordinates is done using components * of (@link czm_viewport} and {@link czm_viewportTransformation} instead of calculating - * the inverse of czm_viewportTransformation. The transformation from + * the inverse of czm_viewportTransformation. The transformation from * normalized device coordinates to clip coordinates is done using positionWC.w, * which is expected to be the scalar used in the perspective divide. The transformation * from clip to eye coordinates is done using {@link czm_inverseProjection}. @@ -30,6 +30,23 @@ vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate) float z = (fragmentCoordinate.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2]; vec4 q = vec4(x, y, z, 1.0); q /= fragmentCoordinate.w; - q = czm_inverseProjection * q; + + if (czm_inverseProjection != mat4(0.0)) { + q = czm_inverseProjection * q; + } else { + float top = czm_frustumPlanes.x; + float bottom = czm_frustumPlanes.y; + float left = czm_frustumPlanes.z; + float right = czm_frustumPlanes.w; + + float near = czm_currentFrustum.x; + float far = czm_currentFrustum.y; + + q.x = (q.x * (right - left) + left + right) * 0.5; + q.y = (q.y * (top - bottom) + bottom + top) * 0.5; + q.z = (q.z * (near - far) - near - far) * 0.5; + q.w = 1.0; + } + return q; } diff --git a/Source/Widgets/ProjectionPicker/ProjectionPicker.css b/Source/Widgets/ProjectionPicker/ProjectionPicker.css new file mode 100644 index 000000000000..ec2ceb0c20e3 --- /dev/null +++ b/Source/Widgets/ProjectionPicker/ProjectionPicker.css @@ -0,0 +1,51 @@ +span.cesium-projectionPicker-wrapper { + display: inline-block; + position: relative; + margin: 0 3px; +} + +.cesium-projectionPicker-visible { + visibility: visible; + opacity: 1; + transition: opacity 0.25s linear; + -webkit-transition: opacity 0.25s linear; + -moz-transition: opacity 0.25s linear; +} + +.cesium-projectionPicker-hidden { + visibility: hidden; + opacity: 0; + transition: visibility 0s 0.25s, opacity 0.25s linear; + -webkit-transition: visibility 0s 0.25s, opacity 0.25s linear; + -moz-transition: visibility 0s 0.25s, opacity 0.25s linear; +} + +.cesium-projectionPicker-wrapper .cesium-projectionPicker-none { + display: none; +} + +.cesium-projectionPicker-wrapper .cesium-projectionPicker-dropDown-icon { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 0; + margin: 3px 0; +} + +.cesium-projectionPicker-wrapper .cesium-projectionPicker-buttonPerspective, +.cesium-projectionPicker-wrapper .cesium-projectionPicker-buttonOrthographic { + margin: 0 0 3px 0; +} + +.cesium-projectionPicker-wrapper .cesium-projectionPicker-buttonPerspective .cesium-projectionPicker-iconOrthographic { + left: 100%; +} + +.cesium-projectionPicker-wrapper .cesium-projectionPicker-buttonOrthographic .cesium-projectionPicker-iconPerspective { + left: -100%; +} + +.cesium-projectionPicker-wrapper .cesium-projectionPicker-selected { + border-color: #2e2; + box-shadow: 0 0 8px #fff, 0 0 8px #fff; +} diff --git a/Source/Widgets/ProjectionPicker/ProjectionPicker.js b/Source/Widgets/ProjectionPicker/ProjectionPicker.js new file mode 100644 index 000000000000..1405115e4113 --- /dev/null +++ b/Source/Widgets/ProjectionPicker/ProjectionPicker.js @@ -0,0 +1,176 @@ +/*global define*/ +define([ + '../../Core/defined', + '../../Core/defineProperties', + '../../Core/destroyObject', + '../../Core/DeveloperError', + '../../Core/FeatureDetection', + '../../ThirdParty/knockout', + '../getElement', + './ProjectionPickerViewModel' + ], function( + defined, + defineProperties, + destroyObject, + DeveloperError, + FeatureDetection, + knockout, + getElement, + ProjectionPickerViewModel) { + 'use strict'; + + var perspectivePath = 'M 28.15625,10.4375 9.125,13.21875 13.75,43.25 41.75,55.09375 50.8125,37 54.5,11.9375 z m 0.125,3 19.976451,0.394265 L 43.03125,16.875 22.6875,14.28125 z M 50.971746,15.705477 47.90625,36.03125 42.53125,46 44.84375,19.3125 z M 12.625,16.03125 l 29.15625,3.6875 -2.65625,31 L 16.4375,41.125 z'; + var orthographicPath = 'm 31.560594,6.5254438 -20.75,12.4687502 0.1875,24.5625 22.28125,11.8125 19.5,-12 0.65625,-0.375 0,-0.75 0.0312,-23.21875 z m 0.0625,3.125 16.65625,9.5000002 -16.125,10.28125 -17.34375,-9.71875 z m 18.96875,11.1875002 0.15625,20.65625 -17.46875,10.59375 0.15625,-20.28125 z m -37.0625,1.25 17.21875,9.625 -0.15625,19.21875 -16.9375,-9 z'; + + /** + * The ProjectionPicker is a single button widget for switching between perspective and orthographic projections. + * + * @alias ProjectionPicker + * @constructor + * + * @param {Element|String} container The DOM element or ID that will contain the widget. + * @param {Scene} scene The Scene instance to use. + * + * @exception {DeveloperError} Element with id "container" does not exist in the document. + * + * @example + * // In HTML head, include a link to the ProjectionPicker.css stylesheet, + * // and in the body, include:
+ * // Note: This code assumes you already have a Scene instance. + * + * var projectionPicker = new Cesium.ProjectionPicker('projectionPickerContainer', scene); + */ + function ProjectionPicker(container, scene) { + //>>includeStart('debug', pragmas.debug); + if (!defined(container)) { + throw new DeveloperError('container is required.'); + } + if (!defined(scene)) { + throw new DeveloperError('scene is required.'); + } + //>>includeEnd('debug'); + + container = getElement(container); + + var viewModel = new ProjectionPickerViewModel(scene); + + viewModel._perspectivePath = perspectivePath; + viewModel._orthographicPath = orthographicPath; + + var wrapper = document.createElement('span'); + wrapper.className = 'cesium-projectionPicker-wrapper cesium-toolbar-button'; + container.appendChild(wrapper); + + var button = document.createElement('button'); + button.type = 'button'; + button.className = 'cesium-button cesium-toolbar-button'; + button.setAttribute('data-bind', '\ +css: { "cesium-projectionPicker-buttonPerspective": !_orthographic,\ + "cesium-projectionPicker-buttonOrthographic": _orthographic,\ + "cesium-button-disabled" : sceneMode === _sceneMode.SCENE2D || _flightInProgress, \ + "cesium-projectionPicker-selected": dropDownVisible },\ +attr: { title: selectedTooltip },\ +click: toggleDropDown'); + button.innerHTML = '\ +\ +'; + wrapper.appendChild(button); + + var perspectiveButton = document.createElement('button'); + perspectiveButton.type = 'button'; + perspectiveButton.className = 'cesium-button cesium-toolbar-button cesium-projectionPicker-dropDown-icon'; + perspectiveButton.setAttribute('data-bind', '\ +css: { "cesium-projectionPicker-visible" : (dropDownVisible && _orthographic),\ + "cesium-projectionPicker-none" : !_orthographic,\ + "cesium-projectionPicker-hidden" : !dropDownVisible },\ +attr: { title: tooltipPerspective },\ +click: switchToPerspective,\ +cesiumSvgPath: { path: _perspectivePath, width: 64, height: 64 }'); + wrapper.appendChild(perspectiveButton); + + var orthographicButton = document.createElement('button'); + orthographicButton.type = 'button'; + orthographicButton.className = 'cesium-button cesium-toolbar-button cesium-projectionPicker-dropDown-icon'; + orthographicButton.setAttribute('data-bind', '\ +css: { "cesium-projectionPicker-visible" : (dropDownVisible && !_orthographic),\ + "cesium-projectionPicker-none" : _orthographic,\ + "cesium-projectionPicker-hidden" : !dropDownVisible},\ +attr: { title: tooltipOrthographic },\ +click: switchToOrthographic,\ +cesiumSvgPath: { path: _orthographicPath, width: 64, height: 64 }'); + wrapper.appendChild(orthographicButton); + + knockout.applyBindings(viewModel, wrapper); + + this._viewModel = viewModel; + this._container = container; + this._wrapper = wrapper; + + this._closeDropDown = function(e) { + if (!wrapper.contains(e.target)) { + viewModel.dropDownVisible = false; + } + }; + if (FeatureDetection.supportsPointerEvents()) { + document.addEventListener('pointerdown', this._closeDropDown, true); + } else { + document.addEventListener('mousedown', this._closeDropDown, true); + document.addEventListener('touchstart', this._closeDropDown, true); + } + } + + defineProperties(ProjectionPicker.prototype, { + /** + * Gets the parent container. + * @memberof ProjectionPicker.prototype + * + * @type {Element} + */ + container : { + get : function() { + return this._container; + } + }, + + /** + * Gets the view model. + * @memberof ProjectionPicker.prototype + * + * @type {ProjectionPickerViewModel} + */ + viewModel : { + get : function() { + return this._viewModel; + } + } + }); + + /** + * @returns {Boolean} true if the object has been destroyed, false otherwise. + */ + ProjectionPicker.prototype.isDestroyed = function() { + return false; + }; + + /** + * Destroys the widget. Should be called if permanently + * removing the widget from layout. + */ + ProjectionPicker.prototype.destroy = function() { + this._viewModel.destroy(); + + if (FeatureDetection.supportsPointerEvents()) { + document.removeEventListener('pointerdown', this._closeDropDown, true); + } else { + document.removeEventListener('mousedown', this._closeDropDown, true); + document.removeEventListener('touchstart', this._closeDropDown, true); + } + + knockout.cleanNode(this._wrapper); + this._container.removeChild(this._wrapper); + + return destroyObject(this); + }; + + return ProjectionPicker; +}); diff --git a/Source/Widgets/ProjectionPicker/ProjectionPickerViewModel.js b/Source/Widgets/ProjectionPicker/ProjectionPickerViewModel.js new file mode 100644 index 000000000000..7dd27365b8b3 --- /dev/null +++ b/Source/Widgets/ProjectionPicker/ProjectionPickerViewModel.js @@ -0,0 +1,262 @@ +/*global define*/ +define([ + '../../Core/Cartesian2', + '../../Core/Cartesian3', + '../../Core/defaultValue', + '../../Core/defined', + '../../Core/defineProperties', + '../../Core/destroyObject', + '../../Core/DeveloperError', + '../../Core/EventHelper', + '../../Core/Math', + '../../Core/Matrix4', + '../../Core/Ray', + '../../Scene/OrthographicFrustum', + '../../Scene/PerspectiveFrustum', + '../../Scene/SceneMode', + '../../ThirdParty/knockout', + '../createCommand' + ], function( + Cartesian2, + Cartesian3, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + EventHelper, + CesiumMath, + Matrix4, + Ray, + OrthographicFrustum, + PerspectiveFrustum, + SceneMode, + knockout, + createCommand) { + 'use strict'; + + /** + * The view model for {@link ProjectionPicker}. + * @alias ProjectionPickerViewModel + * @constructor + * + * @param {Scene} scene The Scene to switch projections. + */ + function ProjectionPickerViewModel(scene) { + //>>includeStart('debug', pragmas.debug); + if (!defined(scene)) { + throw new DeveloperError('scene is required.'); + } + //>>includeEnd('debug'); + + this._scene = scene; + this._orthographic = scene.camera.frustum instanceof OrthographicFrustum; + this._flightInProgress = false; + + /** + * Gets or sets whether the button drop-down is currently visible. This property is observable. + * @type {Boolean} + * @default false + */ + this.dropDownVisible = false; + + /** + * Gets or sets the perspective projection tooltip. This property is observable. + * @type {String} + * @default 'Perspective Projection' + */ + this.tooltipPerspective = 'Perspective Projection'; + + /** + * Gets or sets the orthographic projection tooltip. This property is observable. + * @type {String} + * @default 'Orthographic Projection' + */ + this.tooltipOrthographic = 'Orthographic Projection'; + + /** + * Gets the currently active tooltip. This property is observable. + * @type {String} + */ + this.selectedTooltip = undefined; + + /** + * Gets or sets the current SceneMode. This property is observable. + * @type {SceneMode} + */ + this.sceneMode = scene.mode; + + knockout.track(this, ['_orthographic', '_flightInProgress', 'sceneMode', 'dropDownVisible', 'tooltipPerspective', 'tooltipOrthographic']); + + var that = this; + knockout.defineProperty(this, 'selectedTooltip', function() { + if (that._orthographic) { + return that.tooltipOrthographic; + } + return that.tooltipPerspective; + }); + + this._toggleDropDown = createCommand(function() { + if (that.sceneMode === SceneMode.SCENE2D || that._flightInProgress) { + return; + } + + that.dropDownVisible = !that.dropDownVisible; + }); + + this._eventHelper = new EventHelper(); + this._eventHelper.add(scene.morphComplete, function(transitioner, oldMode, newMode, isMorphing) { + that.sceneMode = newMode; + that._orthographic = newMode === SceneMode.SCENE2D || that._scene.camera.frustum instanceof OrthographicFrustum; + }); + this._eventHelper.add(scene.preRender, function() { + that._flightInProgress = defined(scene.camera._currentFlight); + }); + + this._switchToPerspective = createCommand(function() { + if (that.sceneMode === SceneMode.SCENE2D) { + return; + } + + var scene = that._scene; + var camera = that._scene.camera; + camera.frustum = new PerspectiveFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.fov = CesiumMath.toRadians(60.0); + + that._orthographic = false; + that.dropDownVisible = false; + }); + + var scratchAdjustOrtghographicFrustumMousePosition = new Cartesian2(); + var scratchDepthIntersection = new Cartesian3(); + var pickGlobeScratchRay = new Ray(); + var scratchRayIntersection = new Cartesian3(); + + this._switchToOrthographic = createCommand(function() { + if (that.sceneMode === SceneMode.SCENE2D) { + return; + } + + var scene = that._scene; + var camera = that._scene.camera; + var globe = scene._globe; + + var distance; + if (!Matrix4.equals(Matrix4.IDENTITY, camera.transform)) { + distance = Cartesian3.magnitude(camera.position); + } else if (defined(globe)) { + var depthIntersection; + var rayIntersection; + + var mousePosition = scratchAdjustOrtghographicFrustumMousePosition; + mousePosition.x = scene.drawingBufferWidth / 2.0; + mousePosition.y = scene.drawingBufferHeight / 2.0; + + if (scene.pickPositionSupported) { + depthIntersection = scene.pickPositionWorldCoordinates(mousePosition, scratchDepthIntersection); + } + + var ray = camera.getPickRay(mousePosition, pickGlobeScratchRay); + rayIntersection = globe.pick(ray, scene, scratchRayIntersection); + + var pickDistance = defined(depthIntersection) ? Cartesian3.distance(depthIntersection, camera.positionWC) : Number.POSITIVE_INFINITY; + var rayDistance = defined(rayIntersection) ? Cartesian3.distance(rayIntersection, camera.positionWC) : Number.POSITIVE_INFINITY; + + distance = pickDistance < rayDistance ? pickDistance : rayDistance; + } + + if (!defined(distance)) { + distance = camera.positionCartographic.height; + } + + camera.frustum = new OrthographicFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.width = distance; + + that._orthographic = true; + that.dropDownVisible = false; + }); + + //Used by knockout + this._sceneMode = SceneMode; + } + + defineProperties(ProjectionPickerViewModel.prototype, { + /** + * Gets the scene + * @memberof ProjectionPickerViewModel.prototype + * @type {Scene} + */ + scene : { + get : function() { + return this._scene; + } + }, + + /** + * Gets the command to toggle the drop down box. + * @memberof ProjectionPickerViewModel.prototype + * + * @type {Command} + */ + toggleDropDown : { + get : function() { + return this._toggleDropDown; + } + }, + + /** + * Gets the command to switch to a perspective projection. + * @memberof ProjectionPickerViewModel.prototype + * + * @type {Command} + */ + switchToPerspective : { + get : function() { + return this._switchToPerspective; + } + }, + + /** + * Gets the command to switch to orthographic projection. + * @memberof ProjectionPickerViewModel.prototype + * + * @type {Command} + */ + switchToOrthographic : { + get : function() { + return this._switchToOrthographic; + } + }, + + /** + * Gets whether the scene is currently using an orthographic projection. + * @memberof ProjectionPickerViewModel.prototype + * + * @type {Command} + */ + isOrthographicProjection : { + get : function() { + return this._orthographic; + } + } + }); + + /** + * @returns {Boolean} true if the object has been destroyed, false otherwise. + */ + ProjectionPickerViewModel.prototype.isDestroyed = function() { + return false; + }; + + /** + * Destroys the view model. + */ + ProjectionPickerViewModel.prototype.destroy = function() { + this._eventHelper.removeAll(); + destroyObject(this); + }; + + return ProjectionPickerViewModel; +}); diff --git a/Source/Widgets/VRButton/VRButton.js b/Source/Widgets/VRButton/VRButton.js index 1945fb2df8be..795f50212461 100644 --- a/Source/Widgets/VRButton/VRButton.js +++ b/Source/Widgets/VRButton/VRButton.js @@ -53,6 +53,7 @@ define([ element.type = 'button'; element.className = 'cesium-button cesium-vrButton'; element.setAttribute('data-bind', '\ +css: { "cesium-button-disabled" : _isOrthographic }, \ attr: { title: tooltip },\ click: command,\ enable: isVREnabled,\ diff --git a/Source/Widgets/VRButton/VRButtonViewModel.js b/Source/Widgets/VRButton/VRButtonViewModel.js index ccf9e2067aeb..a5ec9bfbc66e 100644 --- a/Source/Widgets/VRButton/VRButtonViewModel.js +++ b/Source/Widgets/VRButton/VRButtonViewModel.js @@ -5,7 +5,9 @@ define([ '../../Core/defineProperties', '../../Core/destroyObject', '../../Core/DeveloperError', + '../../Core/EventHelper', '../../Core/Fullscreen', + '../../Scene/OrthographicFrustum', '../../ThirdParty/knockout', '../../ThirdParty/NoSleep', '../createCommand', @@ -16,7 +18,9 @@ define([ defineProperties, destroyObject, DeveloperError, + EventHelper, Fullscreen, + OrthographicFrustum, knockout, NoSleep, createCommand, @@ -55,7 +59,11 @@ define([ } } - function toggleVR(viewModel, scene, isVRMode) { + function toggleVR(viewModel, scene, isVRMode, isOrthographic) { + if (isOrthographic()) { + return; + } + if (isVRMode()) { scene.useWebVR = false; if (viewModel._locked) { @@ -139,11 +147,25 @@ define([ return isVRMode() ? 'Exit VR mode' : 'Enter VR mode'; }); + var isOrthographic = knockout.observable(false); + + this._isOrthographic = undefined; + knockout.defineProperty(this, '_isOrthographic', { + get : function() { + return isOrthographic(); + } + }); + + this._eventHelper = new EventHelper(); + this._eventHelper.add(scene.preRender, function() { + isOrthographic(scene.camera.frustum instanceof OrthographicFrustum); + }); + this._locked = false; this._noSleep = new NoSleep(); this._command = createCommand(function() { - toggleVR(that, scene, isVRMode); + toggleVR(that, scene, isVRMode, isOrthographic); }, knockout.getObservable(this, 'isVREnabled')); this._vrElement = defaultValue(getElement(vrElement), document.body); @@ -211,6 +233,8 @@ define([ * properly clean up the view model when it is no longer needed. */ VRButtonViewModel.prototype.destroy = function() { + this._eventHelper.removeAll(); + document.removeEventListener(Fullscreen.changeEventName, this._callback); destroyObject(this); }; diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js index 3fd5049fe410..dad0543c9b1b 100644 --- a/Source/Widgets/Viewer/Viewer.js +++ b/Source/Widgets/Viewer/Viewer.js @@ -37,6 +37,7 @@ define([ '../HomeButton/HomeButton', '../InfoBox/InfoBox', '../NavigationHelpButton/NavigationHelpButton', + '../ProjectionPicker/ProjectionPicker', '../SceneModePicker/SceneModePicker', '../SelectionIndicator/SelectionIndicator', '../subscribeAndEvaluate', @@ -80,6 +81,7 @@ define([ HomeButton, InfoBox, NavigationHelpButton, + ProjectionPicker, SceneModePicker, SelectionIndicator, subscribeAndEvaluate, @@ -186,6 +188,7 @@ define([ var geocoder = viewer._geocoder; var homeButton = viewer._homeButton; var sceneModePicker = viewer._sceneModePicker; + var projectionPicker = viewer._projectionPicker; var baseLayerPicker = viewer._baseLayerPicker; var animation = viewer._animation; var timeline = viewer._timeline; @@ -204,6 +207,9 @@ define([ if(defined(sceneModePicker)) { sceneModePicker.container.style.visibility = visibility; } + if (defined(projectionPicker)) { + projectionPicker.container.style.visibility = visibility; + } if(defined(baseLayerPicker)) { baseLayerPicker.container.style.visibility = visibility; } @@ -279,6 +285,7 @@ define([ * @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun. * @param {ShadowMode} [options.terrainShadows=ShadowMode.RECEIVE_ONLY] Determines if the terrain casts or receives shadows from 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. + * @param {Boolean} [options.projectionPicker=false] If set to true, the ProjectionPicker widget will be created. * * @exception {DeveloperError} Element with id "container" does not exist in the document. * @exception {DeveloperError} options.imageryProvider is not available when using the BaseLayerPicker widget, specify options.selectedImageryProviderViewModel instead. @@ -509,6 +516,11 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to sceneModePicker = new SceneModePicker(toolbar, cesiumWidget.scene); } + var projectionPicker; + if (options.projectionPicker) { + projectionPicker = new ProjectionPicker(toolbar, cesiumWidget.scene); + } + // BaseLayerPicker var baseLayerPicker; var baseLayerPickerDropDown; @@ -640,6 +652,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to this._toolbar = toolbar; this._homeButton = homeButton; this._sceneModePicker = sceneModePicker; + this._projectionPicker = projectionPicker; this._baseLayerPicker = baseLayerPicker; this._navigationHelpButton = navigationHelpButton; this._animation = animation; @@ -808,6 +821,18 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to } }, + /** + * Gets the ProjectionPicker. + * @memberof Viewer.prototype + * @type {ProjectionPicker} + * @readonly + */ + projectionPicker : { + get : function() { + return this._projectionPicker; + } + }, + /** * Gets the BaseLayerPicker. * @memberof Viewer.prototype @@ -1425,6 +1450,10 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to this._sceneModePicker = this._sceneModePicker.destroy(); } + if (defined(this._projectionPicker)) { + this._projectionPicker = this._projectionPicker.destroy(); + } + if (defined(this._baseLayerPicker)) { this._baseLayerPicker = this._baseLayerPicker.destroy(); } diff --git a/Source/Widgets/widgets.css b/Source/Widgets/widgets.css index 324a8a597af3..f291135b644a 100644 --- a/Source/Widgets/widgets.css +++ b/Source/Widgets/widgets.css @@ -8,6 +8,7 @@ @import url(./Geocoder/Geocoder.css); @import url(./InfoBox/InfoBox.css); @import url(./SceneModePicker/SceneModePicker.css); +@import url(./ProjectionPicker/ProjectionPicker.css); @import url(./PerformanceWatchdog/PerformanceWatchdog.css); @import url(./NavigationHelpButton/NavigationHelpButton.css); @import url(./SelectionIndicator/SelectionIndicator.css); diff --git a/Source/Workers/createVerticesFromQuantizedTerrainMesh.js b/Source/Workers/createVerticesFromQuantizedTerrainMesh.js index 3a9d5118dad0..2c81e05a22b5 100644 --- a/Source/Workers/createVerticesFromQuantizedTerrainMesh.js +++ b/Source/Workers/createVerticesFromQuantizedTerrainMesh.js @@ -97,6 +97,11 @@ define([ maximum.y = Number.NEGATIVE_INFINITY; maximum.z = Number.NEGATIVE_INFINITY; + var minLongitude = Number.POSITIVE_INFINITY; + var maxLongitude = Number.NEGATIVE_INFINITY; + var minLatitude = Number.POSITIVE_INFINITY; + var maxLatitude = Number.NEGATIVE_INFINITY; + for (var i = 0; i < quantizedVertexCount; ++i) { var u = uBuffer[i] / maxShort; var v = vBuffer[i] / maxShort; @@ -106,6 +111,11 @@ define([ cartographicScratch.latitude = CesiumMath.lerp(south, north, v); cartographicScratch.height = height; + minLongitude = Math.min(cartographicScratch.longitude, minLongitude); + maxLongitude = Math.max(cartographicScratch.longitude, maxLongitude); + minLatitude = Math.min(cartographicScratch.latitude, minLatitude); + maxLatitude = Math.max(cartographicScratch.latitude, maxLatitude); + var position = ellipsoid.cartographicToCartesian(cartographicScratch); uvs[i] = new Cartesian2(u, v); @@ -174,16 +184,28 @@ define([ var indexBuffer = IndexDatatype.createTypedArray(quantizedVertexCount + edgeVertexCount, indexBufferLength); indexBuffer.set(parameters.indices, 0); + var percentage = 0.0001; + var lonOffset = (maxLongitude - minLongitude) * percentage; + var latOffset = (maxLatitude - minLatitude) * percentage; + var westLongitudeOffset = -lonOffset; + var westLatitudeOffset = 0.0; + var eastLongitudeOffset = lonOffset; + var eastLatitudeOffset = 0.0; + var northLongitudeOffset = 0.0; + var northLatitudeOffset = latOffset; + var southLongitudeOffset = 0.0; + var southLatitudeOffset = -latOffset; + // 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, southMercatorY, oneOverMercatorHeight); + indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.westIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.westSkirtHeight, true, exaggeration, southMercatorY, oneOverMercatorHeight, westLongitudeOffset, westLatitudeOffset); vertexBufferIndex += parameters.westIndices.length * vertexStride; - indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.southIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.southSkirtHeight, false, exaggeration, southMercatorY, oneOverMercatorHeight); + indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.southIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.southSkirtHeight, false, exaggeration, southMercatorY, oneOverMercatorHeight, southLongitudeOffset, southLatitudeOffset); vertexBufferIndex += parameters.southIndices.length * vertexStride; - indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.eastIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.eastSkirtHeight, false, exaggeration, southMercatorY, oneOverMercatorHeight); + indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.eastIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.eastSkirtHeight, false, exaggeration, southMercatorY, oneOverMercatorHeight, eastLongitudeOffset, eastLatitudeOffset); vertexBufferIndex += parameters.eastIndices.length * vertexStride; - addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.northIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.northSkirtHeight, true, exaggeration, southMercatorY, oneOverMercatorHeight); + addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.northIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.northSkirtHeight, true, exaggeration, southMercatorY, oneOverMercatorHeight, northLongitudeOffset, northLatitudeOffset); transferableObjects.push(vertexBuffer.buffer, indexBuffer.buffer); @@ -234,7 +256,7 @@ define([ return hMin; } - function addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, edgeVertices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, skirtLength, isWestOrNorthEdge, exaggeration, southMercatorY, oneOverMercatorHeight) { + function addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, edgeVertices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, skirtLength, isWestOrNorthEdge, exaggeration, southMercatorY, oneOverMercatorHeight, longitudeOffset, latitudeOffset) { var start, end, increment; if (isWestOrNorthEdge) { start = edgeVertices.length - 1; @@ -266,8 +288,8 @@ define([ var h = heights[index]; var uv = uvs[index]; - cartographicScratch.longitude = CesiumMath.lerp(west, east, uv.x); - cartographicScratch.latitude = CesiumMath.lerp(south, north, uv.y); + cartographicScratch.longitude = CesiumMath.lerp(west, east, uv.x) + longitudeOffset; + cartographicScratch.latitude = CesiumMath.lerp(south, north, uv.y) + latitudeOffset; cartographicScratch.height = h - skirtLength; var position = ellipsoid.cartographicToCartesian(cartographicScratch, cartesian3Scratch); diff --git a/Specs/Renderer/AutomaticUniformSpec.js b/Specs/Renderer/AutomaticUniformSpec.js index c0398499b01b..685c61329027 100644 --- a/Specs/Renderer/AutomaticUniformSpec.js +++ b/Specs/Renderer/AutomaticUniformSpec.js @@ -7,6 +7,7 @@ defineSuite([ 'Renderer/Pass', 'Renderer/Texture', 'Scene/OrthographicFrustum', + 'Scene/OrthographicOffCenterFrustum', 'Scene/SceneMode', 'Specs/createCamera', 'Specs/createContext', @@ -19,6 +20,7 @@ defineSuite([ Pass, Texture, OrthographicFrustum, + OrthographicOffCenterFrustum, SceneMode, createCamera, createContext, @@ -406,22 +408,25 @@ defineSuite([ }).contextToRender(); }); - it('has czm_inverseProjectionOIT', function() { - var us = context.uniformState; - us.update(createFrameState(context, createMockCamera( + it('has czm_inverseProjection in 2D', function() { + var frameState = createFrameState(context, createMockCamera( undefined, new Matrix4( - 0.0, -1.0, 0.0, 1.0, - 1.0, 0.0, 0.0, 2.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0)))); + 0.0, -1.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 2.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0))); + frameState.mode = SceneMode.SCENE2D; + + var us = context.uniformState; + us.update(frameState); var fs = 'void main() { ' + - ' bool b0 = (czm_inverseProjectionOIT[0][0] == 0.0) && (czm_inverseProjectionOIT[1][0] == 1.0) && (czm_inverseProjectionOIT[2][0] == 0.0) && (czm_inverseProjectionOIT[3][0] == -2.0); ' + - ' bool b1 = (czm_inverseProjectionOIT[0][1] == -1.0) && (czm_inverseProjectionOIT[1][1] == 0.0) && (czm_inverseProjectionOIT[2][1] == 0.0) && (czm_inverseProjectionOIT[3][1] == 1.0); ' + - ' bool b2 = (czm_inverseProjectionOIT[0][2] == 0.0) && (czm_inverseProjectionOIT[1][2] == 0.0) && (czm_inverseProjectionOIT[2][2] == 1.0) && (czm_inverseProjectionOIT[3][2] == 0.0); ' + - ' bool b3 = (czm_inverseProjectionOIT[0][3] == 0.0) && (czm_inverseProjectionOIT[1][3] == 0.0) && (czm_inverseProjectionOIT[2][3] == 0.0) && (czm_inverseProjectionOIT[3][3] == 1.0); ' + + ' bool b0 = (czm_inverseProjection[0][0] == 0.0) && (czm_inverseProjection[1][0] == 0.0) && (czm_inverseProjection[2][0] == 0.0) && (czm_inverseProjection[3][0] == 0.0); ' + + ' bool b1 = (czm_inverseProjection[0][1] == 0.0) && (czm_inverseProjection[1][1] == 0.0) && (czm_inverseProjection[2][1] == 0.0) && (czm_inverseProjection[3][1] == 0.0); ' + + ' bool b2 = (czm_inverseProjection[0][2] == 0.0) && (czm_inverseProjection[1][2] == 0.0) && (czm_inverseProjection[2][2] == 0.0) && (czm_inverseProjection[3][2] == 0.0); ' + + ' bool b3 = (czm_inverseProjection[0][3] == 0.0) && (czm_inverseProjection[1][3] == 0.0) && (czm_inverseProjection[2][3] == 0.0) && (czm_inverseProjection[3][3] == 0.0); ' + ' gl_FragColor = vec4(b0 && b1 && b2 && b3); ' + '}'; expect({ @@ -430,24 +435,28 @@ defineSuite([ }).contextToRender(); }); - it('has czm_inverseProjectionOIT in 2D', function() { - var us = context.uniformState; + it('has czm_inverseProjection in 3D with orthographic projection', function() { var frameState = createFrameState(context, createMockCamera( undefined, new Matrix4( - 0.0, -1.0, 0.0, 1.0, - 1.0, 0.0, 0.0, 2.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0))); - frameState.mode = SceneMode.SCENE2D; + 0.0, -1.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 2.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0))); + var frustum = new OrthographicFrustum(); + frustum.aspectRatio = 1.0; + frustum.width = 1.0; + frameState.camera.frustum = frustum; + + var us = context.uniformState; us.update(frameState); var fs = 'void main() { ' + - ' bool b0 = (czm_inverseProjectionOIT[0][0] == 1.0) && (czm_inverseProjectionOIT[1][0] == 0.0) && (czm_inverseProjectionOIT[2][0] == 0.0) && (czm_inverseProjectionOIT[3][0] == 0.0); ' + - ' bool b1 = (czm_inverseProjectionOIT[0][1] == 0.0) && (czm_inverseProjectionOIT[1][1] == 1.0) && (czm_inverseProjectionOIT[2][1] == 0.0) && (czm_inverseProjectionOIT[3][1] == 0.0); ' + - ' bool b2 = (czm_inverseProjectionOIT[0][2] == 0.0) && (czm_inverseProjectionOIT[1][2] == 0.0) && (czm_inverseProjectionOIT[2][2] == 1.0) && (czm_inverseProjectionOIT[3][2] == 0.0); ' + - ' bool b3 = (czm_inverseProjectionOIT[0][3] == 0.0) && (czm_inverseProjectionOIT[1][3] == 0.0) && (czm_inverseProjectionOIT[2][3] == 0.0) && (czm_inverseProjectionOIT[3][3] == 1.0); ' + + ' bool b0 = (czm_inverseProjection[0][0] == 0.0) && (czm_inverseProjection[1][0] == 0.0) && (czm_inverseProjection[2][0] == 0.0) && (czm_inverseProjection[3][0] == 0.0); ' + + ' bool b1 = (czm_inverseProjection[0][1] == 0.0) && (czm_inverseProjection[1][1] == 0.0) && (czm_inverseProjection[2][1] == 0.0) && (czm_inverseProjection[3][1] == 0.0); ' + + ' bool b2 = (czm_inverseProjection[0][2] == 0.0) && (czm_inverseProjection[1][2] == 0.0) && (czm_inverseProjection[2][2] == 0.0) && (czm_inverseProjection[3][2] == 0.0); ' + + ' bool b3 = (czm_inverseProjection[0][3] == 0.0) && (czm_inverseProjection[1][3] == 0.0) && (czm_inverseProjection[2][3] == 0.0) && (czm_inverseProjection[3][3] == 0.0); ' + ' gl_FragColor = vec4(b0 && b1 && b2 && b3); ' + '}'; expect({ @@ -1195,7 +1204,7 @@ defineSuite([ it('has czm_eyeHeight2D in Scene2D', function() { var us = context.uniformState; var camera = createCamera(); - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; diff --git a/Specs/Scene/BillboardCollectionSpec.js b/Specs/Scene/BillboardCollectionSpec.js index d91a50742312..d4ab1557d34a 100644 --- a/Specs/Scene/BillboardCollectionSpec.js +++ b/Specs/Scene/BillboardCollectionSpec.js @@ -16,7 +16,8 @@ defineSuite([ 'Scene/BlendOption', 'Scene/HeightReference', 'Scene/HorizontalOrigin', - 'Scene/OrthographicFrustum', + 'Scene/OrthographicOffCenterFrustum', + 'Scene/PerspectiveFrustum', 'Scene/TextureAtlas', 'Scene/VerticalOrigin', 'Specs/createGlobe', @@ -40,7 +41,8 @@ defineSuite([ BlendOption, HeightReference, HorizontalOrigin, - OrthographicFrustum, + OrthographicOffCenterFrustum, + PerspectiveFrustum, TextureAtlas, VerticalOrigin, createGlobe, @@ -90,6 +92,10 @@ defineSuite([ camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.frustum = new PerspectiveFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.fov = CesiumMath.toRadians(60.0); + billboards = new BillboardCollection(); scene.primitives.add(billboards); }); @@ -1357,7 +1363,7 @@ defineSuite([ }); var maxRadii = ellipsoid.maximumRadius; - var orthoFrustum = new OrthographicFrustum(); + var orthoFrustum = new OrthographicOffCenterFrustum(); orthoFrustum.right = maxRadii * Math.PI; orthoFrustum.left = -orthoFrustum.right; orthoFrustum.top = orthoFrustum.right; diff --git a/Specs/Scene/CameraFlightPathSpec.js b/Specs/Scene/CameraFlightPathSpec.js index c96786598626..e45c067522dc 100644 --- a/Specs/Scene/CameraFlightPathSpec.js +++ b/Specs/Scene/CameraFlightPathSpec.js @@ -4,7 +4,7 @@ defineSuite([ 'Core/Cartesian3', 'Core/Cartographic', 'Core/Math', - 'Scene/OrthographicFrustum', + 'Scene/OrthographicOffCenterFrustum', 'Scene/SceneMode', 'Specs/createScene' ], function( @@ -12,7 +12,7 @@ defineSuite([ Cartesian3, Cartographic, CesiumMath, - OrthographicFrustum, + OrthographicOffCenterFrustum, SceneMode, createScene) { 'use strict'; @@ -29,7 +29,7 @@ defineSuite([ function createOrthographicFrustum() { var current = scene.camera.frustum; - var f = new OrthographicFrustum(); + var f = new OrthographicOffCenterFrustum(); f.near = current.near; f.far = current.far; diff --git a/Specs/Scene/CameraSpec.js b/Specs/Scene/CameraSpec.js index c6653dbbf330..d2922e575c20 100644 --- a/Specs/Scene/CameraSpec.js +++ b/Specs/Scene/CameraSpec.js @@ -19,6 +19,7 @@ defineSuite([ 'Scene/CameraFlightPath', 'Scene/MapMode2D', 'Scene/OrthographicFrustum', + 'Scene/OrthographicOffCenterFrustum', 'Scene/PerspectiveFrustum', 'Scene/SceneMode', 'Scene/TweenCollection' @@ -42,6 +43,7 @@ defineSuite([ CameraFlightPath, MapMode2D, OrthographicFrustum, + OrthographicOffCenterFrustum, PerspectiveFrustum, SceneMode, TweenCollection) { @@ -562,6 +564,21 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('update throws with frustum not supported in given mode', function() { + camera.frustum = new PerspectiveFrustum(); + expect(function() { + camera.update(SceneMode.SCENE2D); + }).toThrowDeveloperError(); + + camera.frustum = new OrthographicOffCenterFrustum(); + expect(function() { + camera.update(SceneMode.SCENE3D); + }).toThrowDeveloperError(); + expect(function() { + camera.update(SceneMode.COLUMBUS_VIEW); + }).toThrowDeveloperError(); + }); + it('setView with cartesian in 2D', function() { var ellipsoid = Ellipsoid.WGS84; var projection = new GeographicProjection(ellipsoid); @@ -570,7 +587,7 @@ defineSuite([ camera._mode = SceneMode.SCENE2D; camera._projection = projection; - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.right = maxRadii * Math.PI; frustum.left = -frustum.right; frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth); @@ -631,6 +648,54 @@ defineSuite([ expect(camera.right).toEqualEpsilon(Cartesian3.cross(camera.direction, camera.up, new Cartesian3()), CesiumMath.EPSILON6); }); + it('setView with cartesian in Columbus View and orthographic frustum', function() { + var ellipsoid = Ellipsoid.WGS84; + var projection = new GeographicProjection(ellipsoid); + + camera._mode = SceneMode.COLUMBUS_VIEW; + camera._projection = projection; + + camera.frustum = new OrthographicFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.width = camera.positionCartographic.height; + + var cartesian = Cartesian3.fromDegrees(-75.0, 42.0, 100.0); + camera.setView({ + destination : cartesian + }); + + var cart = ellipsoid.cartesianToCartographic(cartesian); + expect(camera.positionCartographic).toEqualEpsilon(cart, CesiumMath.EPSILON11); + expect(camera.direction).toEqualEpsilon(Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), CesiumMath.EPSILON6); + expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON6); + expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON6); + expect(camera.frustum.width).toEqual(cart.height); + }); + + it('setView with cartesian in 3D and orthographic frustum', function() { + var ellipsoid = Ellipsoid.WGS84; + var projection = new GeographicProjection(ellipsoid); + + camera._mode = SceneMode.SCENE3D; + camera._projection = projection; + + camera.frustum = new OrthographicFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.width = camera.positionCartographic.height; + + var cartesian = Cartesian3.fromDegrees(-75.0, 0.0, 100.0); + camera.setView({ + destination : cartesian + }); + + var cart = ellipsoid.cartesianToCartographic(cartesian); + expect(camera.positionCartographic).toEqualEpsilon(cart, CesiumMath.EPSILON6); + expect(camera.direction).toEqualEpsilon(Cartesian3.normalize(Cartesian3.negate(camera.position, new Cartesian3()), new Cartesian3()), CesiumMath.EPSILON6); + expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON6); + expect(camera.right).toEqualEpsilon(Cartesian3.cross(camera.direction, camera.up, new Cartesian3()), CesiumMath.EPSILON6); + expect(camera.frustum.width).toEqual(cart.height); + }); + it('setView right rotation order', function() { var position = Cartesian3.fromDegrees(-117.16, 32.71, 0.0); var heading = CesiumMath.toRadians(180.0); @@ -930,7 +995,7 @@ defineSuite([ }); it('move clamps position in 2D', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1140,7 +1205,7 @@ defineSuite([ }); it('zooms out 2D', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1159,7 +1224,7 @@ defineSuite([ }); it('zooms in 2D', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1178,7 +1243,7 @@ defineSuite([ }); it('clamps zoom out in 2D', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1202,7 +1267,7 @@ defineSuite([ }); it('clamps zoom in in 2D', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1240,7 +1305,7 @@ defineSuite([ it('zooms in throws with undefined OrthogrphicFrustum properties 2D', function() { camera._mode = SceneMode.SCENE2D; - camera.frustum = new OrthographicFrustum(); + camera.frustum = new OrthographicOffCenterFrustum(); expect(function () { camera.zoomIn(zoomAmount); }).toThrowDeveloperError(); @@ -1296,7 +1361,7 @@ defineSuite([ }); it('lookAt in 2D mode', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1321,7 +1386,7 @@ defineSuite([ }); it('lookAt in 2D mode with heading, pitch and range', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1425,7 +1490,7 @@ defineSuite([ }); it('lookAtTransform in 2D mode', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1450,7 +1515,7 @@ defineSuite([ }); it('lookAtTransform in 2D mode with heading, pitch and range', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -1479,6 +1544,30 @@ defineSuite([ expect(tempCamera.frustum.left).toEqual(-range * 0.5); }); + it('lookAtTransform in 3D with orthographic projection', function() { + var target = new Cartesian3(-1.0, -1.0, 0.0); + var offset = new Cartesian3(1.0, 1.0, 0.0); + var transform = Transforms.eastNorthUpToFixedFrame(target, Ellipsoid.UNIT_SPHERE); + + var tempCamera = Camera.clone(camera); + tempCamera.frustum = new OrthographicFrustum(); + tempCamera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + tempCamera.frustum.width = tempCamera.positionCartographic.height; + + tempCamera.lookAtTransform(transform, offset); + + expect(tempCamera.position).toEqualEpsilon(offset, CesiumMath.EPSILON11); + expect(tempCamera.direction).toEqualEpsilon(Cartesian3.negate(Cartesian3.normalize(offset, new Cartesian3()), new Cartesian3()), CesiumMath.EPSILON11); + expect(tempCamera.right).toEqualEpsilon(Cartesian3.cross(tempCamera.direction, Cartesian3.UNIT_Z, new Cartesian3()), CesiumMath.EPSILON11); + expect(tempCamera.up).toEqualEpsilon(Cartesian3.cross(tempCamera.right, tempCamera.direction, new Cartesian3()), CesiumMath.EPSILON11); + + expect(1.0 - Cartesian3.magnitude(tempCamera.direction)).toBeLessThan(CesiumMath.EPSILON14); + expect(1.0 - Cartesian3.magnitude(tempCamera.up)).toBeLessThan(CesiumMath.EPSILON14); + expect(1.0 - Cartesian3.magnitude(tempCamera.right)).toBeLessThan(CesiumMath.EPSILON14); + + expect(tempCamera.frustum.width).toEqual(Cartesian3.magnitude(tempCamera.position)); + }); + it('lookAtTransform throws when morphing', function() { camera.update(SceneMode.MORPHING); @@ -1553,7 +1642,7 @@ defineSuite([ }); it('setView rectangle in 2D with larger longitude', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.left = -10.0; frustum.right = 10.0; frustum.bottom = -10.0; @@ -1584,7 +1673,7 @@ defineSuite([ }); it('setView rectangle in 2D with larger latitude', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.left = -10.0; frustum.right = 10.0; frustum.bottom = -10.0; @@ -1630,6 +1719,64 @@ defineSuite([ expect(camera.right).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON10); }); + it('setView rectangle in 3D with orthographic frustum', function() { + camera.setView({ + destination : Cartesian3.fromDegrees(-75.0, 42.0, 100.0) + }); + + camera.frustum = new OrthographicFrustum(); + camera.frustum.aspectRatio = 1135 / 630; + camera.frustum.width = camera.positionCartographic.height; + + // force update of off-center frustum + expect(camera.frustum.projectionMatrix).toBeDefined(); + + var rectangle = new Rectangle( + CesiumMath.toRadians(21.25), + CesiumMath.toRadians(41.23), + CesiumMath.toRadians(21.51), + CesiumMath.toRadians(41.38)); + + var projection = new GeographicProjection(); + camera._mode = SceneMode.SCENE3D; + camera._projection = projection; + camera.setView({destination: rectangle}); + + expect(camera.position).toEqualEpsilon(new Cartesian3(4489090.849577177, 1757448.0638960265, 4207738.07588144), CesiumMath.EPSILON6); + expect(camera.direction).toEqualEpsilon(new Cartesian3(-0.6995012374560863, -0.2738499033887593, -0.6600789719506079), CesiumMath.EPSILON10); + expect(camera.up).toEqualEpsilon(new Cartesian3(-0.6146543999545513, -0.2406329524979527, 0.7511962132416727), CesiumMath.EPSILON10); + expect(camera.right).toEqualEpsilon(new Cartesian3(-0.36455176232452197, 0.931183125161794, 0.0), CesiumMath.EPSILON10); + }); + + it('setView rectangle in Columbus view with orthographic frustum', function() { + camera.setView({ + destination : Cartesian3.fromDegrees(-75.0, 42.0, 100.0) + }); + + camera.frustum = new OrthographicFrustum(); + camera.frustum.aspectRatio = 1135 / 630; + camera.frustum.width = camera.positionCartographic.height; + + // force update of off-center frustum + expect(camera.frustum.projectionMatrix).toBeDefined(); + + var rectangle = new Rectangle( + CesiumMath.toRadians(21.25), + CesiumMath.toRadians(41.23), + CesiumMath.toRadians(21.51), + CesiumMath.toRadians(41.38)); + + var projection = new GeographicProjection(); + camera._mode = SceneMode.COLUMBUS_VIEW; + camera._projection = projection; + camera.setView({destination: rectangle}); + + expect(camera.position).toEqualEpsilon(new Cartesian3(2380010.713160189, 4598051.567216165, 28943.06760625122), CesiumMath.EPSILON6); + expect(camera.direction).toEqualEpsilon(new Cartesian3(0.0, 0.0, -1.0), CesiumMath.EPSILON10); + expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON10); + expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON10); + }); + it('getRectangleCameraCoordinates throws without rectangle', function() { expect(function () { camera.getRectangleCameraCoordinates(); @@ -1680,7 +1827,7 @@ defineSuite([ CesiumMath.PI_OVER_TWO); var projection = new GeographicProjection(); var cam = new Camera(scene); - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.right = 1.0; frustum.left = -1.0; frustum.top = 1.0; @@ -1797,7 +1944,7 @@ defineSuite([ camera.direction = Cartesian3.normalize(Cartesian3.negate(camera.position, new Cartesian3()), new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Y); - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.right = maxRadii * Math.PI; frustum.left = -frustum.right; frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth); @@ -1824,7 +1971,7 @@ defineSuite([ camera.direction = Cartesian3.normalize(Cartesian3.negate(camera.position, new Cartesian3()), new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Y); - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.right = maxRadii * Math.PI; frustum.left = -frustum.right; frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth); @@ -1917,8 +2064,8 @@ defineSuite([ expect(ray.direction).toEqualEpsilon(expectedDirection, CesiumMath.EPSILON15); }); - it('get pick ray orthographic', function() { - var frustum = new OrthographicFrustum(); + it('get pick ray orthographic in 2D', function() { + var frustum = new OrthographicOffCenterFrustum(); frustum.left = -10.0; frustum.right = 10.0; frustum.bottom = -10.0; @@ -1927,13 +2074,59 @@ defineSuite([ frustum.far = 21.0; camera.frustum = frustum; + camera.update(SceneMode.SCENE2D); + + var windowCoord = new Cartesian2((3.0 / 5.0) * scene.canvas.clientWidth, (1.0 - (3.0 / 5.0)) * scene.canvas.clientHeight); + var ray = camera.getPickRay(windowCoord); + + var cameraPosition = camera.position; + var expectedPosition = new Cartesian3(cameraPosition.x + 2.0, cameraPosition.y + 2, cameraPosition.z); + expect(ray.origin).toEqualEpsilon(expectedPosition, CesiumMath.EPSILON14); + expect(ray.direction).toEqual(camera.directionWC); + }); + + it('get pick ray orthographic in 3D', function() { + var frustum = new OrthographicFrustum(); + frustum.aspectRatio = 1.0; + frustum.width = 20.0; + frustum.near = 1.0; + frustum.far = 21.0; + camera.frustum = frustum; + + // force off center frustum to update + expect(frustum.projectionMatrix).toBeDefined(); + + camera.update(SceneMode.SCENE3D); + var windowCoord = new Cartesian2((3.0 / 5.0) * scene.canvas.clientWidth, (1.0 - (3.0 / 5.0)) * scene.canvas.clientHeight); var ray = camera.getPickRay(windowCoord); var cameraPosition = camera.position; var expectedPosition = new Cartesian3(cameraPosition.x + 2.0, cameraPosition.y + 2, cameraPosition.z); expect(ray.origin).toEqualEpsilon(expectedPosition, CesiumMath.EPSILON14); - expect(ray.direction).toEqual(camera.direction); + expect(ray.direction).toEqual(camera.directionWC); + }); + + it('get pick ray orthographic in Columbus view', function() { + var frustum = new OrthographicFrustum(); + frustum.aspectRatio = 1.0; + frustum.width = 20.0; + frustum.near = 1.0; + frustum.far = 21.0; + camera.frustum = frustum; + + // force off center frustum to update + expect(frustum.projectionMatrix).toBeDefined(); + + camera.update(SceneMode.COLUMBUS_VIEW); + + var windowCoord = new Cartesian2((3.0 / 5.0) * scene.canvas.clientWidth, (1.0 - (3.0 / 5.0)) * scene.canvas.clientHeight); + var ray = camera.getPickRay(windowCoord); + + var cameraPosition = camera.position; + var expectedPosition = new Cartesian3(cameraPosition.z, cameraPosition.x + 2.0, cameraPosition.y + 2); + expect(ray.origin).toEqualEpsilon(expectedPosition, CesiumMath.EPSILON14); + expect(ray.direction).toEqual(camera.directionWC); }); it('gets magnitude in 2D', function() { @@ -1944,7 +2137,7 @@ defineSuite([ camera._mode = SceneMode.SCENE2D; camera._projection = projection; - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.right = maxRadii * Math.PI; frustum.left = -frustum.right; frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth); @@ -1976,7 +2169,7 @@ defineSuite([ }); it('does not animate in 2D', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 2.0; frustum.left = -2.0; @@ -2280,6 +2473,49 @@ defineSuite([ expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON5); }); + it('viewBoundingSphere in 2D', function() { + var frustum = new OrthographicOffCenterFrustum(); + frustum.left = -10.0; + frustum.right = 10.0; + frustum.bottom = -10.0; + frustum.top = 10.0; + frustum.near = 1.0; + frustum.far = 21.0; + camera.frustum = frustum; + + camera.update(SceneMode.SCENE2D); + + var sphere = new BoundingSphere(Cartesian3.fromDegrees(-117.16, 32.71, 0.0), 10000.0); + camera.viewBoundingSphere(sphere); + camera._setTransform(Matrix4.IDENTITY); + + var distance = frustum.right - frustum.left; + expect(distance).toBeGreaterThan(sphere.radius); + expect(distance).toBeLessThan(sphere.radius * 3.0); + }); + + it('viewBoundingSphere in 3D with orthographic', function() { + var frustum = new OrthographicFrustum(); + frustum.aspectRatio = 1.0; + frustum.width = 20.0; + frustum.near = 1.0; + frustum.far = 21.0; + camera.frustum = frustum; + + camera.update(SceneMode.SCENE3D); + + // force off center update + expect(frustum.projectionMatrix).toBeDefined(); + + var sphere = new BoundingSphere(Cartesian3.fromDegrees(-117.16, 32.71, 0.0), 10000.0); + camera.viewBoundingSphere(sphere); + camera._setTransform(Matrix4.IDENTITY); + + var distance = frustum.width; + expect(distance).toBeGreaterThan(sphere.radius); + expect(distance).toBeLessThan(sphere.radius * 3.0); + }); + it('viewBoundingSphere throws when morphing', function() { camera._mode = SceneMode.MORPHING; @@ -2288,6 +2524,14 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('viewBoundingSphere throws without bounding sphere', function() { + camera._mode = SceneMode.MORPHING; + + expect(function() { + camera.viewBoundingSphere(undefined); + }).toThrowDeveloperError(); + }); + it('flyToBoundingSphere uses CameraFlightPath', function() { spyOn(CameraFlightPath, 'createTween').and.returnValue({ startObject : {}, @@ -2458,7 +2702,7 @@ defineSuite([ camera._mode = SceneMode.SCENE2D; - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.left = -10.0; frustum.right = 10.0; frustum.bottom = -10.0; diff --git a/Specs/Scene/GeometryRenderingSpec.js b/Specs/Scene/GeometryRenderingSpec.js index a0052eb22384..4f43998b967b 100644 --- a/Specs/Scene/GeometryRenderingSpec.js +++ b/Specs/Scene/GeometryRenderingSpec.js @@ -33,6 +33,7 @@ defineSuite([ 'Scene/EllipsoidSurfaceAppearance', 'Scene/Material', 'Scene/PerInstanceColorAppearance', + 'Scene/PerspectiveFrustum', 'Scene/PolylineColorAppearance', 'Scene/Primitive', 'Scene/SceneMode', @@ -72,6 +73,7 @@ defineSuite([ EllipsoidSurfaceAppearance, Material, PerInstanceColorAppearance, + PerspectiveFrustum, PolylineColorAppearance, Primitive, SceneMode, @@ -96,6 +98,15 @@ defineSuite([ scene.destroyForSpecs(); }); + beforeEach(function() { + scene.morphTo3D(0.0); + + var camera = scene.camera; + camera.frustum = new PerspectiveFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.fov = CesiumMath.toRadians(60.0); + }); + afterEach(function() { scene.primitives.removeAll(); primitive = primitive && !primitive.isDestroyed() && primitive.destroy(); diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index 271643969cde..36af82b173af 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -32,6 +32,7 @@ defineSuite([ 'Scene/ColorBlendMode', 'Scene/HeightReference', 'Scene/ModelAnimationLoop', + 'Scene/PerspectiveFrustum', 'Specs/createScene', 'Specs/pollToPromise', 'ThirdParty/when' @@ -68,6 +69,7 @@ defineSuite([ ColorBlendMode, HeightReference, ModelAnimationLoop, + PerspectiveFrustum, createScene, pollToPromise, when) { @@ -156,6 +158,11 @@ defineSuite([ beforeEach(function() { scene.morphTo3D(0.0); + + var camera = scene.camera; + camera.frustum = new PerspectiveFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.fov = CesiumMath.toRadians(60.0); }); function addZoomTo(model) { diff --git a/Specs/Scene/OrthographicFrustumSpec.js b/Specs/Scene/OrthographicOffCenterFrustumSpec.js similarity index 95% rename from Specs/Scene/OrthographicFrustumSpec.js rename to Specs/Scene/OrthographicOffCenterFrustumSpec.js index bfac1d812e5a..7897e568b5ac 100644 --- a/Specs/Scene/OrthographicFrustumSpec.js +++ b/Specs/Scene/OrthographicOffCenterFrustumSpec.js @@ -1,13 +1,13 @@ /*global defineSuite*/ defineSuite([ - 'Scene/OrthographicFrustum', + 'Scene/OrthographicOffCenterFrustum', 'Core/Cartesian2', 'Core/Cartesian3', 'Core/Cartesian4', 'Core/Math', 'Core/Matrix4' ], function( - OrthographicFrustum, + OrthographicOffCenterFrustum, Cartesian2, Cartesian3, Cartesian4, @@ -18,7 +18,7 @@ defineSuite([ var frustum, planes; beforeEach(function() { - frustum = new OrthographicFrustum(); + frustum = new OrthographicOffCenterFrustum(); frustum.near = 1.0; frustum.far = 3.0; frustum.right = 1.0; @@ -152,7 +152,7 @@ defineSuite([ }); it('throws with undefined frustum parameters', function() { - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); expect(function() { return frustum.projectionMatrix; }).toThrowDeveloperError(); @@ -164,7 +164,7 @@ defineSuite([ }); it('clone with result parameter', function() { - var result = new OrthographicFrustum(); + var result = new OrthographicOffCenterFrustum(); var frustum2 = frustum.clone(result); expect(frustum2).toBe(result); expect(frustum).toEqual(frustum2); diff --git a/Specs/Scene/PickSpec.js b/Specs/Scene/PickSpec.js index 58ff84eeb970..bcae4bafb71f 100644 --- a/Specs/Scene/PickSpec.js +++ b/Specs/Scene/PickSpec.js @@ -7,6 +7,7 @@ defineSuite([ 'Core/RectangleGeometry', 'Core/ShowGeometryInstanceAttribute', 'Scene/EllipsoidSurfaceAppearance', + 'Scene/OrthographicFrustum', 'Scene/PerspectiveFrustum', 'Scene/Primitive', 'Scene/SceneMode', @@ -19,6 +20,7 @@ defineSuite([ RectangleGeometry, ShowGeometryInstanceAttribute, EllipsoidSurfaceAppearance, + OrthographicFrustum, PerspectiveFrustum, Primitive, SceneMode, @@ -307,15 +309,20 @@ defineSuite([ var rectangle = createRectangle(); scene.initializeFrame(); expect(scene).toPickPrimitive(rectangle); - scene.morphTo3D(0.0); }); - it('picks in 2D when rotated', function() { - scene.morphTo2D(0.0); + it('picks in 3D with orthographic projection', function() { + var frustum = new OrthographicFrustum(); + frustum.aspectRatio = 1.0; + frustum.width = 20.0; + camera.frustum = frustum; + + // force off center update + expect(frustum.projectionMatrix).toBeDefined(); + camera.setView({ destination : primitiveRectangle }); var rectangle = createRectangle(); scene.initializeFrame(); expect(scene).toPickPrimitive(rectangle); - scene.morphTo3D(0.0); }); }, 'WebGL'); diff --git a/Specs/Scene/PrimitiveCullingSpec.js b/Specs/Scene/PrimitiveCullingSpec.js index f54234048764..ac57eeaaee8f 100644 --- a/Specs/Scene/PrimitiveCullingSpec.js +++ b/Specs/Scene/PrimitiveCullingSpec.js @@ -6,6 +6,7 @@ defineSuite([ 'Core/defaultValue', 'Core/GeometryInstance', 'Core/loadImage', + 'Core/Math', 'Core/Rectangle', 'Core/RectangleGeometry', 'Core/Transforms', @@ -15,6 +16,7 @@ defineSuite([ 'Scene/LabelCollection', 'Scene/Material', 'Scene/PerInstanceColorAppearance', + 'Scene/PerspectiveFrustum', 'Scene/PolylineCollection', 'Scene/Primitive', 'Scene/SceneMode', @@ -27,6 +29,7 @@ defineSuite([ defaultValue, GeometryInstance, loadImage, + CesiumMath, Rectangle, RectangleGeometry, Transforms, @@ -36,6 +39,7 @@ defineSuite([ LabelCollection, Material, PerInstanceColorAppearance, + PerspectiveFrustum, PolylineCollection, Primitive, SceneMode, @@ -61,6 +65,15 @@ defineSuite([ scene.destroyForSpecs(); }); + beforeEach(function() { + scene.morphTo3D(0.0); + + var camera = scene.camera; + camera.frustum = new PerspectiveFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.fov = CesiumMath.toRadians(60.0); + }); + afterEach(function() { scene.primitives.removeAll(); primitive = primitive && primitive.destroy(); diff --git a/Specs/Scene/PrimitiveSpec.js b/Specs/Scene/PrimitiveSpec.js index e362215f4f3e..ca981609044b 100644 --- a/Specs/Scene/PrimitiveSpec.js +++ b/Specs/Scene/PrimitiveSpec.js @@ -26,6 +26,7 @@ defineSuite([ 'Scene/Camera', 'Scene/MaterialAppearance', 'Scene/PerInstanceColorAppearance', + 'Scene/PerspectiveFrustum', 'Scene/SceneMode', 'Specs/BadGeometry', 'Specs/createContext', @@ -59,6 +60,7 @@ defineSuite([ Camera, MaterialAppearance, PerInstanceColorAppearance, + PerspectiveFrustum, SceneMode, BadGeometry, createContext, @@ -99,6 +101,12 @@ defineSuite([ beforeEach(function() { scene.morphTo3D(0); + + var camera = scene.camera; + camera.frustum = new PerspectiveFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.fov = CesiumMath.toRadians(60.0); + scene.frameState.passes.render = true; scene.frameState.passes.pick = false; diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js index df8e6649e9d9..03beddb71f27 100644 --- a/Specs/Scene/SceneSpec.js +++ b/Specs/Scene/SceneSpec.js @@ -27,6 +27,7 @@ defineSuite([ 'Scene/EllipsoidSurfaceAppearance', 'Scene/FrameState', 'Scene/Globe', + 'Scene/PerspectiveFrustum', 'Scene/Primitive', 'Scene/PrimitiveCollection', 'Scene/Scene', @@ -65,6 +66,7 @@ defineSuite([ EllipsoidSurfaceAppearance, FrameState, Globe, + PerspectiveFrustum, Primitive, PrimitiveCollection, Scene, @@ -101,6 +103,11 @@ defineSuite([ scene.fxaa = false; scene.primitives.removeAll(); scene.morphTo3D(0.0); + + var camera = scene.camera; + camera.frustum = new PerspectiveFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.fov = CesiumMath.toRadians(60.0); }); afterAll(function() { diff --git a/Specs/Scene/SceneTransformsSpec.js b/Specs/Scene/SceneTransformsSpec.js index 3592c301d076..3cd8c0cba0ff 100644 --- a/Specs/Scene/SceneTransformsSpec.js +++ b/Specs/Scene/SceneTransformsSpec.js @@ -7,6 +7,7 @@ defineSuite([ 'Core/Math', 'Core/Rectangle', 'Scene/Camera', + 'Scene/OrthographicFrustum', 'Scene/SceneMode', 'Specs/createScene' ], function( @@ -17,6 +18,7 @@ defineSuite([ CesiumMath, Rectangle, Camera, + OrthographicFrustum, SceneMode, createScene) { 'use strict'; @@ -181,6 +183,29 @@ defineSuite([ expect(windowCoordinates.y).toBeLessThan(1.0); }); + it('returns correct window position in 3D with orthographic frustum', function() { + var frustum = new OrthographicFrustum(); + frustum.aspectRatio = 1.0; + frustum.width = 20.0; + scene.camera.frustum = frustum; + + // Update scene state + scene.renderForSpecs(); + + scene.camera.setView({ + destination : Rectangle.fromDegrees(-0.000001, -0.000001, 0.000001, 0.000001) + }); + + var position = Cartesian3.fromDegrees(0,0); + var windowCoordinates = SceneTransforms.wgs84ToWindowCoordinates(scene, position); + + expect(windowCoordinates.x).toBeGreaterThan(0.0); + expect(windowCoordinates.y).toBeGreaterThan(0.0); + + expect(windowCoordinates.x).toBeLessThan(1.0); + expect(windowCoordinates.y).toBeLessThan(1.0); + }); + it('returns correct drawing buffer position in 2D', function() { scene.camera.setView({ destination : Rectangle.fromDegrees(-0.000001, -0.000001, 0.000001, 0.000001) diff --git a/Specs/Scene/ScreenSpaceCameraControllerSpec.js b/Specs/Scene/ScreenSpaceCameraControllerSpec.js index ab6d81d87287..b4d18ff24de7 100644 --- a/Specs/Scene/ScreenSpaceCameraControllerSpec.js +++ b/Specs/Scene/ScreenSpaceCameraControllerSpec.js @@ -12,9 +12,11 @@ defineSuite([ 'Core/Math', 'Core/Ray', 'Core/Transforms', + 'Scene/Camera', 'Scene/CameraEventType', 'Scene/MapMode2D', 'Scene/OrthographicFrustum', + 'Scene/OrthographicOffCenterFrustum', 'Scene/SceneMode', 'Specs/createCamera', 'Specs/createCanvas', @@ -32,9 +34,11 @@ defineSuite([ CesiumMath, Ray, Transforms, + Camera, CameraEventType, MapMode2D, OrthographicFrustum, + OrthographicOffCenterFrustum, SceneMode, createCamera, createCanvas, @@ -183,7 +187,7 @@ defineSuite([ }; var maxRadii = ellipsoid.maximumRadius; - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.right = maxRadii * Math.PI; frustum.left = -frustum.right; frustum.top = frustum.right * (canvas.clientHeight / canvas.clientWidth); @@ -861,6 +865,52 @@ defineSuite([ expect(Cartesian3.magnitude(position)).toBeLessThan(Cartesian3.magnitude(camera.position)); }); + it('zoom in 3D with orthographic projection', function() { + setUp3D(); + + var frustum = new OrthographicFrustum(); + frustum.aspectRatio = 1.0; + frustum.width = 20.0; + camera.frustum = frustum; + + expect(frustum.projectionMatrix).toBeDefined(); + + camera.setView({ destination : Camera.DEFAULT_VIEW_RECTANGLE }); + + var position = Cartesian3.clone(camera.position); + var frustumWidth = camera.frustum.width; + var startPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 4); + var endPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2); + + moveMouse(MouseButtons.RIGHT, startPosition, endPosition); + updateController(); + expect(Cartesian3.magnitude(position)).toBeGreaterThan(Cartesian3.magnitude(camera.position)); + expect(frustumWidth).toBeGreaterThan(camera.frustum.width); + }); + + it('zoom out in 3D with orthographic projection', function() { + setUp3D(); + + var frustum = new OrthographicFrustum(); + frustum.aspectRatio = 1.0; + frustum.width = 20.0; + camera.frustum = frustum; + + expect(frustum.projectionMatrix).toBeDefined(); + + camera.setView({ destination : Camera.DEFAULT_VIEW_RECTANGLE }); + + var position = Cartesian3.clone(camera.position); + var frustumWidth = camera.frustum.width; + var startPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2); + var endPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 4); + + moveMouse(MouseButtons.RIGHT, startPosition, endPosition); + updateController(); + expect(Cartesian3.magnitude(position)).toBeLessThan(Cartesian3.magnitude(camera.position)); + expect(frustumWidth).toBeLessThan(camera.frustum.width); + }); + it('tilts in 3D', function() { setUp3D(); var position = Cartesian3.clone(camera.position); diff --git a/Specs/Scene/ShadowMapSpec.js b/Specs/Scene/ShadowMapSpec.js index 69c4e33af57f..7033e26fce2c 100644 --- a/Specs/Scene/ShadowMapSpec.js +++ b/Specs/Scene/ShadowMapSpec.js @@ -24,7 +24,7 @@ defineSuite([ 'Scene/Camera', 'Scene/Globe', 'Scene/Model', - 'Scene/OrthographicFrustum', + 'Scene/OrthographicOffCenterFrustum', 'Scene/PerInstanceColorAppearance', 'Scene/Primitive', 'Scene/ShadowMode', @@ -56,7 +56,7 @@ defineSuite([ Camera, Globe, Model, - OrthographicFrustum, + OrthographicOffCenterFrustum, PerInstanceColorAppearance, Primitive, ShadowMode, @@ -297,7 +297,7 @@ defineSuite([ var center = new Cartesian3.fromRadians(longitude, latitude, height); scene.camera.lookAt(center, new HeadingPitchRange(0.0, CesiumMath.toRadians(-70.0), 5.0)); - var frustum = new OrthographicFrustum(); + var frustum = new OrthographicOffCenterFrustum(); frustum.left = -50.0; frustum.right = 50.0; frustum.bottom = -50.0; diff --git a/Specs/Widgets/ProjectionPicker/ProjectionPickerSpec.js b/Specs/Widgets/ProjectionPicker/ProjectionPickerSpec.js new file mode 100644 index 000000000000..b0928bb52cf7 --- /dev/null +++ b/Specs/Widgets/ProjectionPicker/ProjectionPickerSpec.js @@ -0,0 +1,128 @@ +/*global defineSuite*/ +defineSuite([ + 'Widgets/ProjectionPicker/ProjectionPicker', + 'Core/FeatureDetection', + 'Specs/createScene', + 'Specs/DomEventSimulator' + ], function( + ProjectionPicker, + FeatureDetection, + createScene, + DomEventSimulator) { + 'use strict'; + + var scene; + + beforeAll(function() { + scene = createScene(); + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + it('can create and destroy', function() { + var container = document.createElement('span'); + container.id = 'testContainer'; + document.body.appendChild(container); + + var widget = new ProjectionPicker('testContainer', scene); + expect(widget.container.id).toBe(container.id); + expect(widget.isDestroyed()).toEqual(false); + + widget.destroy(); + expect(widget.isDestroyed()).toEqual(true); + + document.body.removeChild(container); + }); + + function addCloseOnInputSpec(name, func) { + it(name + ' event closes dropdown if target is not inside container', function() { + var container = document.createElement('span'); + container.id = 'testContainer'; + document.body.appendChild(container); + + var widget = new ProjectionPicker('testContainer', scene); + + widget.viewModel.dropDownVisible = true; + func(document.body); + expect(widget.viewModel.dropDownVisible).toEqual(false); + + widget.viewModel.dropDownVisible = true; + func(container.firstChild); + expect(widget.viewModel.dropDownVisible).toEqual(true); + + widget.destroy(); + document.body.removeChild(container); + }); + } + + function addDisabledDuringFlightSpec(name, func) { + it(name + ' event does nothing during camera flight', function() { + var container = document.createElement('span'); + container.id = 'testContainer'; + document.body.appendChild(container); + + var widget = new ProjectionPicker('testContainer', scene); + + scene.camera.flyHome(100.0); + + func(container.firstChild); + expect(widget.viewModel.dropDownVisible).toEqual(false); + + scene.camera.cancelFlight(); + + widget.destroy(); + document.body.removeChild(container); + }); + } + + function addDisabledIn2DSpec(name, func) { + it(name + ' event does nothing in 2D', function() { + var container = document.createElement('span'); + container.id = 'testContainer'; + document.body.appendChild(container); + + var widget = new ProjectionPicker('testContainer', scene); + + scene.morphTo2D(0.0); + + func(container.firstChild); + expect(widget.viewModel.dropDownVisible).toEqual(false); + + widget.destroy(); + document.body.removeChild(container); + }); + } + + if (FeatureDetection.supportsPointerEvents()) { + addCloseOnInputSpec('pointerDown', DomEventSimulator.firePointerDown); + addDisabledDuringFlightSpec('pointerDown', DomEventSimulator.firePointerDown); + addDisabledIn2DSpec('pointerDown', DomEventSimulator.firePointerDown); + } else { + addCloseOnInputSpec('mousedown', DomEventSimulator.fireMouseDown); + addCloseOnInputSpec('touchstart', DomEventSimulator.fireTouchStart); + addDisabledDuringFlightSpec('mousedown', DomEventSimulator.fireMouseDown); + addDisabledDuringFlightSpec('touchstart', DomEventSimulator.fireTouchStart); + addDisabledIn2DSpec('mousedown', DomEventSimulator.fireMouseDown); + addDisabledIn2DSpec('touchstart', DomEventSimulator.fireTouchStart); + } + + it('constructor throws with no scene', function() { + expect(function() { + return new ProjectionPicker(document.body, undefined); + }).toThrowDeveloperError(); + }); + + it('constructor throws with no element', function() { + expect(function() { + return new ProjectionPicker(undefined, scene); + }).toThrowDeveloperError(); + }); + + it('constructor throws with string element that does not exist', function() { + expect(function() { + return new ProjectionPicker('does not exist', scene); + }).toThrowDeveloperError(); + }); +}, 'WebGL'); diff --git a/Specs/Widgets/ProjectionPicker/ProjectionPickerViewModelSpec.js b/Specs/Widgets/ProjectionPicker/ProjectionPickerViewModelSpec.js new file mode 100644 index 000000000000..4657b90dcfc2 --- /dev/null +++ b/Specs/Widgets/ProjectionPicker/ProjectionPickerViewModelSpec.js @@ -0,0 +1,98 @@ +/*global defineSuite*/ +defineSuite([ + 'Widgets/ProjectionPicker/ProjectionPickerViewModel', + 'Scene/OrthographicFrustum', + 'Scene/PerspectiveFrustum', + 'Scene/SceneMode', + 'Specs/createScene' + ], function( + ProjectionPickerViewModel, + OrthographicFrustum, + PerspectiveFrustum, + SceneMode, + createScene) { + 'use strict'; + + var scene; + + beforeEach(function() { + scene = createScene(); + }); + + afterEach(function() { + scene.destroyForSpecs(); + }); + + it('Can construct and destroy', function() { + var viewModel = new ProjectionPickerViewModel(scene); + expect(viewModel.scene).toBe(scene); + expect(scene.morphComplete.numberOfListeners).toEqual(1); + expect(scene.preRender.numberOfListeners).toEqual(1); + expect(viewModel.isDestroyed()).toEqual(false); + viewModel.destroy(); + expect(viewModel.isDestroyed()).toEqual(true); + expect(scene.morphComplete.numberOfListeners).toEqual(0); + expect(scene.preRender.numberOfListeners).toEqual(0); + }); + + it('dropDownVisible and toggleDropDown work', function() { + var viewModel = new ProjectionPickerViewModel(scene); + + expect(viewModel.dropDownVisible).toEqual(false); + viewModel.toggleDropDown(); + expect(viewModel.dropDownVisible).toEqual(true); + viewModel.dropDownVisible = false; + expect(viewModel.dropDownVisible).toEqual(false); + + viewModel.destroy(); + }); + + it('morphing to 2D calls correct transition', function() { + var viewModel = new ProjectionPickerViewModel(scene); + + expect(scene.mode).toEqual(SceneMode.SCENE3D); + expect(viewModel.isOrthographicProjection).toEqual(false); + + scene.morphTo2D(0); + expect(scene.mode).toEqual(SceneMode.SCENE2D); + expect(viewModel.isOrthographicProjection).toEqual(true); + + viewModel.destroy(); + }); + + it('switching projection calls correct transition', function() { + var viewModel = new ProjectionPickerViewModel(scene); + + expect(scene.mode).toEqual(SceneMode.SCENE3D); + expect(viewModel.isOrthographicProjection).toEqual(false); + expect(scene.camera.frustum instanceof PerspectiveFrustum).toEqual(true); + + viewModel.switchToOrthographic(); + expect(viewModel.isOrthographicProjection).toEqual(true); + expect(scene.camera.frustum instanceof OrthographicFrustum).toEqual(true); + + viewModel.switchToPerspective(); + expect(viewModel.isOrthographicProjection).toEqual(false); + expect(scene.camera.frustum instanceof PerspectiveFrustum).toEqual(true); + + viewModel.destroy(); + }); + + it('selectedTooltip changes on transition', function() { + var viewModel = new ProjectionPickerViewModel(scene); + + viewModel.switchToOrthographic(); + expect(viewModel.selectedTooltip).toEqual(viewModel.tooltipOrthographic); + + viewModel.switchToPerspective(); + expect(viewModel.selectedTooltip).toEqual(viewModel.tooltipPerspective); + + viewModel.destroy(); + }); + + it('create throws with undefined scene', function() { + expect(function() { + return new ProjectionPickerViewModel(); + }).toThrowDeveloperError(); + }); +}, 'WebGL'); diff --git a/Specs/Widgets/SceneModePicker/SceneModePickerViewModelSpec.js b/Specs/Widgets/SceneModePicker/SceneModePickerViewModelSpec.js index a43856889f9a..e07968aec502 100644 --- a/Specs/Widgets/SceneModePicker/SceneModePickerViewModelSpec.js +++ b/Specs/Widgets/SceneModePicker/SceneModePickerViewModelSpec.js @@ -52,7 +52,7 @@ defineSuite([ var viewModel = new SceneModePickerViewModel(scene); viewModel.dropDownVisible = true; - viewModel.morphTo2D(); + viewModel.morphToColumbusView(); expect(viewModel.dropDownVisible).toEqual(false); viewModel.dropDownVisible = true; @@ -60,7 +60,7 @@ defineSuite([ expect(viewModel.dropDownVisible).toEqual(false); viewModel.dropDownVisible = true; - viewModel.morphToColumbusView(); + viewModel.morphTo2D(); expect(viewModel.dropDownVisible).toEqual(false); viewModel.destroy(); @@ -71,17 +71,17 @@ defineSuite([ expect(scene.mode).toEqual(SceneMode.SCENE3D); - viewModel.morphTo2D(); + viewModel.morphToColumbusView(); scene.completeMorph(); - expect(scene.mode).toEqual(SceneMode.SCENE2D); + expect(scene.mode).toEqual(SceneMode.COLUMBUS_VIEW); viewModel.morphTo3D(); scene.completeMorph(); expect(scene.mode).toEqual(SceneMode.SCENE3D); - viewModel.morphToColumbusView(); + viewModel.morphTo2D(); scene.completeMorph(); - expect(scene.mode).toEqual(SceneMode.COLUMBUS_VIEW); + expect(scene.mode).toEqual(SceneMode.SCENE2D); viewModel.destroy(); }); @@ -89,14 +89,14 @@ defineSuite([ it('selectedTooltip changes on transition', function() { var viewModel = new SceneModePickerViewModel(scene); - viewModel.morphTo2D(); - expect(viewModel.selectedTooltip).toEqual(viewModel.tooltip2D); + viewModel.morphToColumbusView(); + expect(viewModel.selectedTooltip).toEqual(viewModel.tooltipColumbusView); viewModel.morphTo3D(); expect(viewModel.selectedTooltip).toEqual(viewModel.tooltip3D); - viewModel.morphToColumbusView(); - expect(viewModel.selectedTooltip).toEqual(viewModel.tooltipColumbusView); + viewModel.morphTo2D(); + expect(viewModel.selectedTooltip).toEqual(viewModel.tooltip2D); viewModel.destroy(); }); diff --git a/Specs/Widgets/VRButton/VRButtonSpec.js b/Specs/Widgets/VRButton/VRButtonSpec.js index 8cf74e0b9b36..7b1293736ce9 100644 --- a/Specs/Widgets/VRButton/VRButtonSpec.js +++ b/Specs/Widgets/VRButton/VRButtonSpec.js @@ -1,12 +1,24 @@ /*global defineSuite*/ defineSuite([ - 'Widgets/VRButton/VRButton' + 'Widgets/VRButton/VRButton', + 'Specs/createScene' ], function( - VRButton) { + VRButton, + createScene) { 'use strict'; + var scene; + + beforeEach(function() { + scene = createScene(); + }); + + afterEach(function() { + scene.destroyForSpecs(); + }); + it('constructor sets default values', function() { - var vrButton = new VRButton(document.body, {}); + var vrButton = new VRButton(document.body, scene); expect(vrButton.container).toBe(document.body); expect(vrButton.viewModel.vrElement).toBe(document.body); expect(vrButton.isDestroyed()).toEqual(false); @@ -16,7 +28,7 @@ defineSuite([ it('constructor sets expected values', function() { var testElement = document.createElement('span'); - var vrButton = new VRButton(document.body, {}, testElement); + var vrButton = new VRButton(document.body, scene, testElement); expect(vrButton.container).toBe(document.body); expect(vrButton.viewModel.vrElement).toBe(testElement); vrButton.destroy(); @@ -26,7 +38,7 @@ defineSuite([ var testElement = document.createElement('span'); testElement.id = 'testElement'; document.body.appendChild(testElement); - var vrButton = new VRButton('testElement', {}); + var vrButton = new VRButton('testElement', scene); expect(vrButton.container).toBe(testElement); document.body.removeChild(testElement); vrButton.destroy(); @@ -34,13 +46,13 @@ defineSuite([ it('throws if container is undefined', function() { expect(function() { - return new VRButton(undefined, {}); + return new VRButton(undefined, scene); }).toThrowDeveloperError(); }); it('throws if container string is undefined', function() { expect(function() { - return new VRButton('testElement', {}); + return new VRButton('testElement', scene); }).toThrowDeveloperError(); }); diff --git a/Specs/Widgets/VRButton/VRButtonViewModelSpec.js b/Specs/Widgets/VRButton/VRButtonViewModelSpec.js index 7821f1e74037..2fdd4ea0ba0c 100644 --- a/Specs/Widgets/VRButton/VRButtonViewModelSpec.js +++ b/Specs/Widgets/VRButton/VRButtonViewModelSpec.js @@ -1,16 +1,22 @@ /*global defineSuite*/ defineSuite([ 'Widgets/VRButton/VRButtonViewModel', - 'Core/Fullscreen' + 'Core/Fullscreen', + 'Specs/createScene' ], function( VRButtonViewModel, - Fullscreen) { + Fullscreen, + createScene) { 'use strict'; var scene; beforeEach(function() { - scene = {}; + scene = createScene(); + }); + + afterEach(function() { + scene.destroyForSpecs(); }); it('constructor sets default values', function() {