diff --git a/CHANGES.md b/CHANGES.md index 80715cfcf3d0..e122a982bb61 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,12 +11,14 @@ Beta Releases * Renamed `Widgets/Fullscreen` folder to `Widgets/FullscreenButton` along with associated objects/files. * `FullscreenWidget` -> `FullscreenButton` * `FullscreenViewModel` -> `FullscreenButtonViewModel` +* Added `SceneTransforms.wgs84ToWindowCoordinates`. [#746](https://github.com/AnalyticalGraphicsInc/cesium/issues/746). +* Added `fromElements` to `Cartesian2`, `Cartesian3`, and `Cartesian4`. * Added `DrawCommand.cull` to avoid redundant visibility checks. * Added `czm_morphTime` automatic GLSL uniform. * Added support for floating-point textures. * Fixed polyline clipping artifact. [#728](https://github.com/AnalyticalGraphicsInc/cesium/issues/728). * Added `IntersectionTests.trianglePlaneIntersection`. -* Fixed polygon crossing international date line for 2D and Columbus view. +* Fixed polygon crossing International Date Line for 2D and Columbus view. [#99](https://github.com/AnalyticalGraphicsInc/cesium/issues/99). * Added `computeHorizonCullingPoint`, `computeHorizonCullingPointFromVertices`, and `computeHorizonCullingPointFromExtent` methods to `EllipsoidalOccluder` and used them to build a more accurate horizon occlusion test for terrain rendering. * Added sun visualization. See `Sun` and `Scene.sun`. * Added a new `HomeButton` widget for returning to the default view of the current scene mode. diff --git a/Source/Core/Cartesian2.js b/Source/Core/Cartesian2.js index b0c5376c73ef..0032743a3f13 100644 --- a/Source/Core/Cartesian2.js +++ b/Source/Core/Cartesian2.js @@ -76,6 +76,25 @@ define([ return result; }; + /** + * Creates a Cartesian2 instance from x and y coordinates. + * @memberof Cartesian2 + * + * @param {Number} x The x coordinate. + * @param {Number} y The y coordinate. + * @param {Cartesian2} [result] The object onto which to store the result. + * @return {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. + */ + Cartesian2.fromElements = function(x, y, result) { + if (typeof result === 'undefined') { + return new Cartesian2(x, y); + } + + result.x = x; + result.y = y; + return result; + }; + /** * Duplicates a Cartesian2 instance. * @memberof Cartesian2 diff --git a/Source/Core/Cartesian3.js b/Source/Core/Cartesian3.js index 303bbaa23f75..2eaa4c0ec5e0 100644 --- a/Source/Core/Cartesian3.js +++ b/Source/Core/Cartesian3.js @@ -111,6 +111,27 @@ define([ return result; }; + /** + * Creates a Cartesian3 instance from x, y and z coordinates. + * @memberof Cartesian3 + * + * @param {Number} x The x coordinate. + * @param {Number} y The y coordinate. + * @param {Number} z The z coordinate. + * @param {Cartesian3} [result] The object onto which to store the result. + * @return {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided. + */ + Cartesian3.fromElements = function(x, y, z, result) { + if (typeof result === 'undefined') { + return new Cartesian3(x, y, z); + } + + result.x = x; + result.y = y; + result.z = z; + return result; + }; + /** * Duplicates a Cartesian3 instance. * @memberof Cartesian3 diff --git a/Source/Core/Cartesian4.js b/Source/Core/Cartesian4.js index 356b1870e38e..11611268db8a 100644 --- a/Source/Core/Cartesian4.js +++ b/Source/Core/Cartesian4.js @@ -92,6 +92,29 @@ define([ return result; }; + /** + * Creates a Cartesian4 instance from x, y, z and w coordinates. + * @memberof Cartesian4 + * + * @param {Number} x The x coordinate. + * @param {Number} y The y coordinate. + * @param {Number} z The z coordinate. + * @param {Number} w The w coordinate. + * @param {Cartesian4} [result] The object onto which to store the result. + * @return {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided. + */ + Cartesian4.fromElements = function(x, y, z, w, result) { + if (typeof result === 'undefined') { + return new Cartesian4(x, y, z, w); + } + + result.x = x; + result.y = y; + result.z = z; + result.w = w; + return result; + }; + /** * Duplicates a Cartesian4 instance. * @memberof Cartesian4 diff --git a/Source/Scene/Billboard.js b/Source/Scene/Billboard.js index c42f1699f1de..ef5603233620 100644 --- a/Source/Scene/Billboard.js +++ b/Source/Scene/Billboard.js @@ -2,31 +2,29 @@ define([ '../Core/defaultValue', '../Core/DeveloperError', - '../Core/BoundingRectangle', '../Core/Color', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Cartographic', '../Core/Math', - '../Core/Matrix4', './HorizontalOrigin', './VerticalOrigin', - './SceneMode' + './SceneMode', + './SceneTransforms' ], function( defaultValue, DeveloperError, - BoundingRectangle, Color, Cartesian2, Cartesian3, Cartesian4, Cartographic, CesiumMath, - Matrix4, HorizontalOrigin, VerticalOrigin, - SceneMode) { + SceneMode, + SceneTransforms) { "use strict"; var EMPTY_OBJECT = {}; @@ -559,54 +557,21 @@ define([ }; var tempCartesian4 = new Cartesian4(); - var tempCartographic = new Cartographic(); Billboard._computeActualPosition = function(position, frameState, modelMatrix) { - var mode = frameState.mode; - - if (mode === SceneMode.SCENE3D) { + if (frameState.mode === SceneMode.SCENE3D) { return position; } modelMatrix.multiplyByPoint(position, tempCartesian4); - - var projection = frameState.scene2D.projection; - var cartographic = projection.getEllipsoid().cartesianToCartographic(tempCartesian4, tempCartographic); - if (typeof cartographic === 'undefined') { - return undefined; - } - - var projectedPosition = projection.project(cartographic); - if (mode === SceneMode.MORPHING) { - var morphTime = frameState.morphTime; - var x = CesiumMath.lerp(projectedPosition.z, tempCartesian4.x, morphTime); - var y = CesiumMath.lerp(projectedPosition.x, tempCartesian4.y, morphTime); - var z = CesiumMath.lerp(projectedPosition.y, tempCartesian4.z, morphTime); - return new Cartesian3(x, y, z); - } - if (mode === SceneMode.SCENE2D) { - return new Cartesian3(0.0, projectedPosition.x, projectedPosition.y); - } - if (mode === SceneMode.COLUMBUS_VIEW) { - return new Cartesian3(projectedPosition.z, projectedPosition.x, projectedPosition.y); - } - return undefined; + return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian4); }; - var scratchViewport = new BoundingRectangle(); - var scratchViewportTransform = new Matrix4(); Billboard._computeScreenSpacePosition = function(modelMatrix, position, eyeOffset, pixelOffset, context, frameState) { // This function is basically a stripped-down JavaScript version of BillboardCollectionVS.glsl - var camera = frameState.camera; var view = camera.getViewMatrix(); var projection = camera.frustum.getProjectionMatrix(); - // Assuming viewport takes up the entire canvas... - var canvas = context.getCanvas(); - scratchViewport.width = canvas.clientWidth; - scratchViewport.height = canvas.clientHeight; - var viewportTransformation = Matrix4.computeViewportTransformation(scratchViewport, 0.0, 1.0, scratchViewportTransform); - // Model to eye coordinates var mv = view.multiply(modelMatrix); var positionEC = mv.multiplyByPoint(position); @@ -617,12 +582,8 @@ define([ positionEC.y += eyeOffset.y + zEyeOffset.y; positionEC.z += zEyeOffset.z; - // Eye to window coordinates, e.g., czm_eyeToWindowCoordinates - var q = projection.multiplyByVector(positionEC); // clip coordinates - q.x /= q.w; // normalized device coordinates - q.y /= q.w; - q.z /= q.w; - var positionWC = viewportTransformation.multiplyByPoint(q); // window coordinates + var positionCC = projection.multiplyByVector(positionEC); // clip coordinates + var positionWC = SceneTransforms.clipToWindowCoordinates(context.getCanvas(), positionCC); // Apply pixel offset var uniformState = context.getUniformState(); diff --git a/Source/Scene/SceneTransforms.js b/Source/Scene/SceneTransforms.js new file mode 100644 index 000000000000..0f40539995e9 --- /dev/null +++ b/Source/Scene/SceneTransforms.js @@ -0,0 +1,151 @@ +/*global define*/ +define([ + '../Core/defaultValue', + '../Core/DeveloperError', + '../Core/Cartographic', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Matrix4', + '../Core/BoundingRectangle', + '../Core/Math', + './SceneMode' + ], + function( + defaultValue, + DeveloperError, + Cartographic, + Cartesian2, + Cartesian3, + Cartesian4, + Matrix4, + BoundingRectangle, + CesiumMath, + SceneMode) { + "use strict"; + + /** + * Functions that do scene-dependent transforms between rendering-related coordinate systems. + * + * @exports SceneTransforms + */ + var SceneTransforms = {}; + + var actualPosition = new Cartesian3(); + var positionCC = new Cartesian4(); + + /** + * Transforms a position in WGS84 coordinates to window coordinates. This is commonly used to place an + * HTML element at the same screen position as an object in the scene. + * + * @memberof SceneTransforms + * + * @param {Scene} scene The scene. + * @param {Cartesian3} position The position in WGS84 (world) coordinates. + * @param {Cartesian2} [result=undefined] An optional object to return the input position transformed to window coordinates. + * + * @return {Cartesian2} The modified result parameter or a new Cartesian3 instance if one was not provided. This may be undefined if the input position is near the center of the ellipsoid. + * + * @exception {DeveloperError} scene is required. + * @exception {DeveloperError} position is required. + * + * @example + * // Output the window position of longitude/latitude (0, 0) every time the mouse moves. + * var scene = widget.scene; + * var ellipsoid = widget.centralBody.getEllipsoid(); + * var position = ellipsoid.cartographicToCartesian(new Cartographic(0.0, 0.0)); + * var handler = new Cesium.ScreenSpaceEventHandler(scene.getCanvas()); + * handler.setInputAction(function(movement) { + * console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position)); + * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + */ + SceneTransforms.wgs84ToWindowCoordinates = function(scene, position, result) { + if (typeof scene === 'undefined') { + throw new DeveloperError('scene is required.'); + } + + if (typeof position === 'undefined') { + throw new DeveloperError('position is required.'); + } + + // Transform for 3D, 2D, or Columbus view + SceneTransforms.computeActualWgs84Position(scene.getFrameState(), position, actualPosition); + + if (typeof actualPosition === 'undefined') { + result = undefined; + return undefined; + } + + // View-projection matrix to transform from world coordinates to clip coordinates + var viewProjection = scene.getUniformState().getViewProjection(); + viewProjection.multiplyByPoint(actualPosition, positionCC); + + return SceneTransforms.clipToWindowCoordinates(scene.getCanvas(), positionCC, result); + }; + + var projectedPosition = new Cartesian3(); + var positionInCartographic = new Cartographic(); + + /** + * @private + */ + SceneTransforms.computeActualWgs84Position = function(frameState, position, result) { + var mode = frameState.mode; + + if (mode === SceneMode.SCENE3D) { + return Cartesian3.clone(position, result); + } + + var projection = frameState.scene2D.projection; + projection.getEllipsoid().cartesianToCartographic(position, positionInCartographic); + if (typeof positionInCartographic === 'undefined') { + result = undefined; + return result; + } + + projection.project(positionInCartographic, projectedPosition); + + if (mode === SceneMode.COLUMBUS_VIEW) { + return Cartesian3.fromElements(projectedPosition.z, projectedPosition.x, projectedPosition.y, result); + } + + if (mode === SceneMode.SCENE2D) { + return Cartesian3.fromElements(0.0, projectedPosition.x, projectedPosition.y, result); + } + + // mode === SceneMode.MORPHING + var morphTime = frameState.morphTime; + return Cartesian3.fromElements( + CesiumMath.lerp(projectedPosition.z, position.x, morphTime), + CesiumMath.lerp(projectedPosition.x, position.y, morphTime), + CesiumMath.lerp(projectedPosition.y, position.z, morphTime), + result); + }; + + var positionNDC = new Cartesian3(); + var positionWC = new Cartesian4(); + var viewport = new BoundingRectangle(); + var viewportTransform = new Matrix4(); + + /** + * @private + */ + SceneTransforms.clipToWindowCoordinates = function(canvas, position, result) { + // Perspective divide to transform from clip coordinates to normalized device coordinates + positionNDC.x = position.x / position.w; + positionNDC.y = position.y / position.w; + positionNDC.z = position.z / position.w; + + // Assuming viewport takes up the entire canvas... + viewport.width = canvas.clientWidth; + viewport.height = canvas.clientHeight; + Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform); + + // Viewport transform to transform from clip coordinates to window coordinates + viewportTransform.multiplyByPoint(positionNDC, positionWC); + + return Cartesian2.fromCartesian4(positionWC, result); + }; + + return SceneTransforms; +}); diff --git a/Specs/Core/Cartesian2Spec.js b/Specs/Core/Cartesian2Spec.js index 111205170a51..420751584b20 100644 --- a/Specs/Core/Cartesian2Spec.js +++ b/Specs/Core/Cartesian2Spec.js @@ -618,4 +618,17 @@ defineSuite([ Cartesian2.equalsEpsilon(new Cartesian2(), new Cartesian2(), undefined); }).toThrow(); }); + + it('fromElements returns a cartesian2 with corrrect coordinates', function(){ + var cartesian2 = Cartesian2.fromElements(2, 2); + var expectedResult = new Cartesian2(2, 2); + expect(cartesian2).toEqual(expectedResult); + }); + + it('fromElements result param returns cartesian2 with correct coordinates', function(){ + var cartesian2 = new Cartesian2(); + Cartesian2.fromElements(2, 2, cartesian2); + var expectedResult = new Cartesian2(2, 2); + expect(cartesian2).toEqual(expectedResult); + }); }); diff --git a/Specs/Core/Cartesian3Spec.js b/Specs/Core/Cartesian3Spec.js index e3dcc18449a9..7ce064648d2c 100644 --- a/Specs/Core/Cartesian3Spec.js +++ b/Specs/Core/Cartesian3Spec.js @@ -702,4 +702,17 @@ defineSuite([ Cartesian3.cross(left, undefined); }).toThrow(); }); + + it('fromElements returns a cartesian3 with corrrect coordinates', function(){ + var cartesian2 = Cartesian3.fromElements(2, 2, 4); + var expectedResult = new Cartesian3(2, 2, 4); + expect(cartesian2).toEqual(expectedResult); + }); + + it('fromElements result param returns cartesian3 with correct coordinates', function(){ + var cartesian3 = new Cartesian3(); + Cartesian3.fromElements(2, 2, 4, cartesian3); + var expectedResult = new Cartesian3(2, 2, 4); + expect(cartesian3).toEqual(expectedResult); + }); }); diff --git a/Specs/Core/Cartesian4Spec.js b/Specs/Core/Cartesian4Spec.js index 1055ad93a0c2..ce4d9539e3fe 100644 --- a/Specs/Core/Cartesian4Spec.js +++ b/Specs/Core/Cartesian4Spec.js @@ -624,4 +624,17 @@ defineSuite([ Cartesian4.equalsEpsilon(new Cartesian4(), new Cartesian4(), undefined); }).toThrow(); }); + + it('fromElements returns a cartesian4 with corrrect coordinates', function(){ + var cartesian4 = Cartesian4.fromElements(2, 2, 4, 7); + var expectedResult = new Cartesian4(2, 2, 4, 7); + expect(cartesian4).toEqual(expectedResult); + }); + + it('fromElements result param returns cartesian4 with correct coordinates', function(){ + var cartesian4 = new Cartesian4(); + Cartesian4.fromElements(2, 2, 4, 7, cartesian4); + var expectedResult = new Cartesian4(2, 2, 4, 7); + expect(cartesian4).toEqual(expectedResult); + }); }); diff --git a/Specs/Scene/SceneTransformsSpec.js b/Specs/Scene/SceneTransformsSpec.js new file mode 100644 index 000000000000..a0043c0f3b52 --- /dev/null +++ b/Specs/Scene/SceneTransformsSpec.js @@ -0,0 +1,57 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/SceneTransforms', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math', + 'Specs/createScene', + 'Specs/destroyScene' + ], function( + SceneTransforms, + Cartographic, + Ellipsoid, + CesiumMath, + createScene, + destroyScene) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var scene; + + beforeAll(function() { + scene = createScene(); + }); + + afterAll(function() { + destroyScene(scene); + }); + it('throws an exception without scene', function() { + var ellipsoid = Ellipsoid.WGS84; + var position = ellipsoid.cartographicToCartesian(new Cartographic(0.0, 0.0)); + expect(function() { + SceneTransforms.wgs84ToWindowCoordinates(undefined, position); + }).toThrow(); + }); + + it('throws an exception without position', function() { + expect(function() { + SceneTransforms.wgs84ToWindowCoordinates(scene); + }).toThrow(); + }); + + it('returns correct position', function() { + var ellipsoid = Ellipsoid.WGS84; + var positionCartographic = ellipsoid.cartesianToCartographic(scene.getCamera().position); + positionCartographic.height = 0.0; + var position = ellipsoid.cartographicToCartesian(positionCartographic); + + // Update scene state + scene.initializeFrame(); + scene.render(); + + var windowCoordinates = SceneTransforms.wgs84ToWindowCoordinates(scene, position); + expect(windowCoordinates.x).toEqualEpsilon(0.5, CesiumMath.EPSILON3); + expect(windowCoordinates.y).toEqualEpsilon(0.5, CesiumMath.EPSILON3); + }); + +}, 'WebGL');