Skip to content

Add geographicToScreenPoint function (ported from Android) #844

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/WorldWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1504,6 +1507,72 @@ define([
}
};

/**
* Transforms a Cartesian coordinate point to coordinates relative to this WorldWindow's canvas.
* <p/>
* 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.
* <p/>
* 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.
Expand Down
71 changes: 71 additions & 0 deletions src/geom/Matrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p/>
* 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.
* <p/>
* 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
Expand Down
50 changes: 50 additions & 0 deletions test/geom/Matrix.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down