diff --git a/src/WorldWindow.js b/src/WorldWindow.js index 3754f6609..58d546ac2 100644 --- a/src/WorldWindow.js +++ b/src/WorldWindow.js @@ -138,6 +138,9 @@ define([ // Internal. Intentionally not documented. this.scratchProjection = Matrix.fromIdentity(); + // Internal. Intentionally not documented. + this.scratchPoint = new Vec3(0, 0, 0); + // Internal. Intentionally not documented. this.hasStencilBuffer = gl.getContextAttributes().stencil; @@ -1504,6 +1507,72 @@ define([ } }; + /** + * Transforms a Cartesian coordinate point to coordinates relative to this WorldWindow's canvas. + *

+ * This stores the converted point in the result argument, and returns a boolean value indicating whether or not the + * converted is successful. This returns false if the Cartesian point is clipped by either the WorldWindow's near + * clipping plane or far clipping plane. + * + * @param {Number} x the Cartesian point's x component in meters + * @param {Number} y the Cartesian point's y component in meters + * @param {Number} z the Cartesian point's z component in meters + * @param {Vec2} result a pre-allocated {@link Vec2} in which to return the screen point + * + * @return {boolean} true if the transformation is successful, otherwise false + * + * @throws {ArgumentError} If the result is null + */ + WorldWindow.prototype.cartesianToScreenPoint = function (x, y, z, result) { + if (!result) { + throw new ArgumentError(Logger.logMessage(Logger.ERROR, "WorldWindow", "cartesianToScreenPoint", + "missingResult")); + } + + // Compute the WorldWindow's modelview-projection matrix. + this.computeViewingTransform(this.scratchProjection, this.scratchModelview); + this.scratchProjection.multiplyMatrix(this.scratchModelview); + + // Transform the Cartesian point to OpenGL screen coordinates. Complete the transformation by converting to + // Android screen coordinates and discarding the screen Z component. + if (this.scratchProjection.project(x, y, z, this.viewport, this.scratchPoint)) { + result[0] = this.scratchPoint[0]; + result[1] = this.viewport.height - this.scratchPoint[1]; + return true; + } + + return false; + }; + + /** + * Transforms a geographic position to coordinates relative to this WorldWindow's canvas. + *

+ * This stores the converted point in the result argument, and returns a boolean value indicating whether or not the + * converted is successful. This returns false if the Cartesian point is clipped by either of the WorldWindow's + * near clipping plane or far clipping plane. + * + * @param {Number} latitude the position's latitude in degrees + * @param {Number} longitude the position's longitude in degrees + * @param {Number} altitude the position's altitude in meters + * @param {Vec2} result a pre-allocated {@link Vec2} in which to return the screen point + * + * @return {boolean} true if the transformation is successful, otherwise false + * + * @throws {ArgumentError} If the result is null + */ + WorldWindow.prototype.geographicToScreenPoint = function (latitude, longitude, altitude, result) { + if (!result) { + throw new ArgumentError(Logger.logMessage(Logger.ERROR, "WorldWindow", "geographicToScreenPoint", + "missingResult")); + } + + // Convert the position from geographic coordinates to Cartesian coordinates. + this.globe.computePointFromPosition(latitude, longitude, altitude, this.scratchPoint); + + // Convert the position from Cartesian coordinates to screen coordinates. + return this.cartesianToScreenPoint(this.scratchPoint[0], this.scratchPoint[1], this.scratchPoint[2], result); + }; + /** * Computes a ray originating at the eyePoint and extending through the specified point in window * coordinates. diff --git a/src/geom/Matrix.js b/src/geom/Matrix.js index cf38ba19d..0609c978d 100644 --- a/src/geom/Matrix.js +++ b/src/geom/Matrix.js @@ -1851,6 +1851,77 @@ define([ return result; }; + /** + * Projects a Cartesian point to screen coordinates. This method assumes this matrix represents an inverse + * modelview-projection matrix. The result of this method is undefined if this matrix is not an inverse + * modelview-projection matrix. + *

+ * The resultant screen point is in OpenGL screen coordinates, with the origin in the bottom-left corner and axes + * that extend up and to the right from the origin. + *

+ * This stores the projected point in the result argument, and returns a boolean value indicating whether or not the + * projection is successful. This returns false if the Cartesian point is clipped by the near clipping plane or the + * far clipping plane. + * + * @param {Number} x the Cartesian point's X component + * @param {Number} y the Cartesian point's y component + * @param {Number} z the Cartesian point's z component + * @param {Rectangle} viewport the viewport defining the screen point's coordinate system + * @param {Vec3} result a pre-allocated {@link Vec3} in which to return the projected point + * + * @return {boolean} true if the transformation is successful, otherwise false + * + * @throws {ArgumentError} If any argument is null + */ + Matrix.prototype.project = function (x, y, z, viewport, result) { + if (!viewport) { + throw new ArgumentError(Logger.logMessage(Logger.ERROR, "Matrix", "project", + "missingViewport")); + } + + if (!result) { + throw new ArgumentError(Logger.logMessage(Logger.ERROR, "Matrix", "project", + "missingResult")); + } + + // Transform the model point from model coordinates to eye coordinates then to clip coordinates. This inverts + // the Z axis and stores the negative of the eye coordinate Z value in the W coordinate. + var sx = this[0] * x + this[1] * y + this[2] * z + this[3]; + var sy = this[4] * x + this[5] * y + this[6] * z + this[7]; + var sz = this[8] * x + this[9] * y + this[10] * z + this[11]; + var sw = this[12] * x + this[13] * y + this[14] * z + this[15]; + + if (sw === 0) { + return false; + } + + // Complete the conversion from model coordinates to clip coordinates by dividing by W. The resultant X, Y + // and Z coordinates are in the range [-1,1]. + sx /= sw; + sy /= sw; + sz /= sw; + + // Clip the point against the near and far clip planes. + if (sz < -1 || sz > 1) { + return false; + } + + // Convert the point from clip coordinate to the range [0,1]. This enables the X and Y coordinates to be + // converted to screen coordinates, and the Z coordinate to represent a depth value in the range[0,1]. + sx = sx * 0.5 + 0.5; + sy = sy * 0.5 + 0.5; + sz = sz * 0.5 + 0.5; + + // Convert the X and Y coordinates from the range [0,1] to screen coordinates. + sx = sx * viewport.width + viewport.x; + sy = sy * viewport.height + viewport.y; + + result[0] = sx; + result[1] = sy; + result[2] = sz; + + return true; + }; /** * Transforms the specified screen point from WebGL screen coordinates to model coordinates. This method assumes diff --git a/test/geom/Matrix.test.js b/test/geom/Matrix.test.js index 0aa63276b..91a5afbcf 100644 --- a/test/geom/Matrix.test.js +++ b/test/geom/Matrix.test.js @@ -1229,6 +1229,56 @@ define([ expect(matrix[15]).toEqual(10); }); + describe("project rejects null parameters", function () { + it("Should throw an exception on missing input parameter", function () { + expect(function () { + var m = Matrix.fromIdentity(); + var dummyParam = "dummy"; + m.project(0, 0, 0, null, dummyParam); + }).toThrow(); + }); + + it("Should throw an exception on missing output variable", function () { + expect(function () { + var m = Matrix.fromIdentity(); + var dummyParam = "dummy"; + m.project(0, 0, 0, dummyParam, null); + }).toThrow(); + }); + }); + + describe("Correctly converts model coordinates to screen coordinates", function () { + + it("projects correctly", function () { + var modelView = new Matrix( + -0.342, 0, 0.939, 2.328e-10, + 0.469, 0.866, 0.171, 18504.137, + -0.813, 0.500, -0.296, -16372797.555, + 0, 0, 0, 1 + ); + + var projection = new Matrix( + 2, 0, 0, 0, + 0, 2, 0, 0, + 0, 0, -1.196, -3254427.538, + 0, 0, -1, 0 + ); + + var modelviewProjection = Matrix.fromIdentity(); + modelviewProjection.setToMultiply(projection, modelView); + var viewport = new Rectangle(0, 0, 848, 848); + var modelPoint = new Vec3(-11925849.053, 8054028.030, -3946244.954); + var result = new Vec3(0, 0, 0); + var expectedResult = new Vec3(637.5, 839, 0); + modelviewProjection.project(modelPoint[0], modelPoint[1], modelPoint[2], viewport, result); + for (var i = 0; i < 3; i++) { + expect(result[i]).toBeCloseTo(expectedResult[i], 3); + } + + }); + }); + + describe("unProject rejects null parameters", function () { it("Should throw an exception on missing input parameter", function () { expect(function () {