Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Improve position selected for camera in viewRectangle in 3D. #2764

Merged
merged 7 commits into from
Jun 4, 2015
Merged
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Change Log

### 1.11 - 2015-07-01

* Improved the algorithm that `Camera.viewRectangle` uses to select the position of the camera, so that the specified rectangle is now better centered on the screen.
* The performance statistics displayed by setting `scene.debugShowFramesPerSecond` to `true` can now be styled using the `cesium-performanceDisplay` CSS classes in `shared.css`.

### 1.10 - 2015-06-01
Expand Down
21 changes: 16 additions & 5 deletions Source/Core/EllipsoidGeodesic.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,18 @@ define([

defineProperties(EllipsoidGeodesic.prototype, {
/**
* The surface distance between the start and end point
* Gets the ellipsoid.
* @memberof EllipsoidGeodesic.prototype
* @type {Ellipsoid}
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
},

/**
* Gets the surface distance between the start and end point
* @memberof EllipsoidGeodesic.prototype
* @type {Number}
*/
Expand All @@ -241,7 +252,7 @@ define([
},

/**
* The initial planetodetic point on the path.
* Gets the initial planetodetic point on the path.
* @memberof EllipsoidGeodesic.prototype
* @type {Cartographic}
*/
Expand All @@ -252,7 +263,7 @@ define([
},

/**
* The final planetodetic point on the path.
* Gets the final planetodetic point on the path.
* @memberof EllipsoidGeodesic.prototype
* @type {Cartographic}
*/
Expand All @@ -263,7 +274,7 @@ define([
},

/**
* The heading at the initial point.
* Gets the heading at the initial point.
* @memberof EllipsoidGeodesic.prototype
* @type {Number}
*/
Expand All @@ -280,7 +291,7 @@ define([
},

/**
* The heading at the final point.
* Gets the heading at the final point.
* @memberof EllipsoidGeodesic.prototype
* @type {Number}
*/
Expand Down
133 changes: 99 additions & 34 deletions Source/Scene/Camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ define([
'../Core/DeveloperError',
'../Core/EasingFunction',
'../Core/Ellipsoid',
'../Core/EllipsoidGeodesic',
'../Core/Event',
'../Core/IntersectionTests',
'../Core/Math',
Expand All @@ -34,6 +35,7 @@ define([
DeveloperError,
EasingFunction,
Ellipsoid,
EllipsoidGeodesic,
Event,
IntersectionTests,
CesiumMath,
Expand Down Expand Up @@ -1656,13 +1658,19 @@ define([
Cartesian3.normalize(this.up, this.up);
};

var viewRectangle3DCartographic = new Cartographic();
var viewRectangle3DCartographic1 = new Cartographic();
var viewRectangle3DCartographic2 = new Cartographic();
var viewRectangle3DNorthEast = new Cartesian3();
var viewRectangle3DSouthWest = new Cartesian3();
var viewRectangle3DNorthWest = new Cartesian3();
var viewRectangle3DSouthEast = new Cartesian3();
var viewRectangle3DNorthCenter = new Cartesian3();
var viewRectangle3DSouthCenter = new Cartesian3();
var viewRectangle3DCenter = new Cartesian3();
var viewRectangle3DEquator = new Cartesian3();
var defaultRF = {direction: new Cartesian3(), right: new Cartesian3(), up: new Cartesian3()};
var viewRectangle3DEllipsoidGeodesic;

function rectangleCameraPosition3D (camera, rectangle, ellipsoid, result, positionOnly) {
if (!defined(result)) {
result = new Cartesian3();
Expand All @@ -1672,6 +1680,7 @@ define([
if (positionOnly) {
cameraRF = defaultRF;
}

var north = rectangle.north;
var south = rectangle.south;
var east = rectangle.east;
Expand All @@ -1682,59 +1691,115 @@ define([
east += CesiumMath.TWO_PI;
}

var cart = viewRectangle3DCartographic;
// Find the midpoint latitude.
//
// EllipsoidGeodesic will fail if the north and south edges are very close to being on opposite sides of the ellipsoid.
// Ideally we'd just call EllipsoidGeodesic.setEndPoints and let it throw when it detects this case, but sadly it doesn't
// even look for this case in optimized builds, so we have to test for it here instead.
//
// Fortunately, this case can only happen (here) when north is very close to the north pole and south is very close to the south pole,
// so handle it just by using 0 latitude as the center. It's certainliy possible to use a smaller tolerance
// than one degree here, but one degree is safe and putting the center at 0 latitude should be good enough for any
// rectangle that spans 178+ of the 180 degrees of latitude.
var longitude = (west + east) * 0.5;
var latitude;
if (south < -CesiumMath.PI_OVER_TWO + CesiumMath.RADIANS_PER_DEGREE && north > CesiumMath.PI_OVER_TWO - CesiumMath.RADIANS_PER_DEGREE) {
latitude = 0.0;
} else {
var northCartographic = viewRectangle3DCartographic1;
northCartographic.longitude = longitude;
northCartographic.latitude = north;
northCartographic.height = 0.0;

var southCartographic = viewRectangle3DCartographic2;
southCartographic.longitude = longitude;
southCartographic.latitude = south;
southCartographic.height = 0.0;

var ellipsoidGeodesic = viewRectangle3DEllipsoidGeodesic;
if (!defined(ellipsoidGeodesic) || ellipsoidGeodesic.ellipsoid !== ellipsoid) {
viewRectangle3DEllipsoidGeodesic = ellipsoidGeodesic = new EllipsoidGeodesic(undefined, undefined, ellipsoid);
}

ellipsoidGeodesic.setEndPoints(northCartographic, southCartographic);
latitude = ellipsoidGeodesic.interpolateUsingFraction(0.5, viewRectangle3DCartographic1).latitude;
}

var centerCartographic = viewRectangle3DCartographic1;
centerCartographic.longitude = longitude;
centerCartographic.latitude = latitude;
centerCartographic.height = 0.0;

var center = ellipsoid.cartographicToCartesian(centerCartographic, viewRectangle3DCenter);

var cart = viewRectangle3DCartographic1;
cart.longitude = east;
cart.latitude = north;
var northEast = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthEast);
cart.longitude = west;
var northWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthWest);
cart.longitude = longitude;
var northCenter = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthCenter);
cart.latitude = south;
var southCenter = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthCenter);
cart.longitude = east;
var southEast = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthEast);
cart.longitude = west;
var southWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthWest);
cart.latitude = north;
var northWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthWest);

var center = Cartesian3.subtract(northEast, southWest, viewRectangle3DCenter);
Cartesian3.multiplyByScalar(center, 0.5, center);
Cartesian3.add(southWest, center, center);

var mag = Cartesian3.magnitude(center);
if (mag < CesiumMath.EPSILON6) {
cart.longitude = (east + west) * 0.5;
cart.latitude = (north + south) * 0.5;
ellipsoid.cartographicToCartesian(cart, center);
}

Cartesian3.subtract(northWest, center, northWest);
Cartesian3.subtract(southEast, center, southEast);
Cartesian3.subtract(northEast, center, northEast);
Cartesian3.subtract(southWest, center, southWest);
Cartesian3.subtract(northCenter, center, northCenter);
Cartesian3.subtract(southCenter, center, southCenter);

var direction = Cartesian3.negate(center, cameraRF.direction);
Cartesian3.normalize(direction, direction);
var direction = ellipsoid.geodeticSurfaceNormal(center, cameraRF.direction);
Cartesian3.negate(direction, direction);
var right = Cartesian3.cross(direction, Cartesian3.UNIT_Z, cameraRF.right);
Cartesian3.normalize(right, right);
var up = Cartesian3.cross(right, direction, cameraRF.up);

var height = Math.max(
Math.abs(Cartesian3.dot(up, northWest)),
Math.abs(Cartesian3.dot(up, southEast)),
Math.abs(Cartesian3.dot(up, northEast)),
Math.abs(Cartesian3.dot(up, southWest))
);
var width = Math.max(
Math.abs(Cartesian3.dot(right, northWest)),
Math.abs(Cartesian3.dot(right, southEast)),
Math.abs(Cartesian3.dot(right, northEast)),
Math.abs(Cartesian3.dot(right, southWest))
);

var tanPhi = Math.tan(camera.frustum.fovy * 0.5);
var tanTheta = camera.frustum.aspectRatio * tanPhi;
var d = Math.max(width / tanTheta, height / tanPhi);

var scalar = mag + d;
Cartesian3.normalize(center, center);
return Cartesian3.multiplyByScalar(center, scalar, result);
function computeD(direction, upOrRight, corner, tanThetaOrPhi) {
var opposite = Math.abs(Cartesian3.dot(upOrRight, corner));
return opposite / tanThetaOrPhi - Cartesian3.dot(direction, corner);
}

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));
}

return Cartesian3.add(center, Cartesian3.multiplyByScalar(direction, -d, viewRectangle3DEquator), result);
}

var viewRectangleCVCartographic = new Cartographic();
Expand Down
29 changes: 21 additions & 8 deletions Specs/Scene/CameraSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1294,10 +1294,10 @@ defineSuite([
CesiumMath.toRadians(21.51),
CesiumMath.toRadians(41.38));
camera.viewRectangle(rectangle, Ellipsoid.WGS84);
expect(camera.position).toEqualEpsilon(new Cartesian3(4481581.054168208, 1754494.5938935655, 4200573.072090136), CesiumMath.EPSILON6);
expect(camera.direction).toEqualEpsilon(new Cartesian3(-0.7015530983057745, -0.2746510892984876, -0.6575637074875123), CesiumMath.EPSILON10);
expect(camera.up).toEqualEpsilon(new Cartesian3(-0.6123128513437499, -0.23971441651266895, 0.7533989451779698), CesiumMath.EPSILON10);
expect(camera.right).toEqualEpsilon(new Cartesian3(-0.36454934142973716, 0.9311840729217532, 0.0), CesiumMath.EPSILON10);
expect(camera.position).toEqualEpsilon(new Cartesian3(4481555.454147325, 1754498.0086281248, 4200627.581953675), CesiumMath.EPSILON6);
expect(camera.direction).toEqualEpsilon(new Cartesian3(-0.6995108433013301, -0.27385366401082994, -0.6600672320390594), CesiumMath.EPSILON10);
expect(camera.up).toEqualEpsilon(new Cartesian3(-0.6146434679470263, -0.24062867269250837, 0.75120652898407), CesiumMath.EPSILON10);
expect(camera.right).toEqualEpsilon(new Cartesian3(-0.36455176232452213, 0.9311831251617939, 0), CesiumMath.EPSILON10);
});

it('views rectangle in 3D (3)', function() {
Expand All @@ -1307,10 +1307,23 @@ defineSuite([
CesiumMath.toRadians(157.0),
CesiumMath.toRadians(0.0));
camera.viewRectangle(rectangle);
expect(camera.position).toEqualEpsilon(new Cartesian3(-7210721.873278953, 8105929.1576369405, -5972336.199381728), CesiumMath.EPSILON6);
expect(camera.direction).toEqualEpsilon(new Cartesian3(0.5822498554483325, -0.6545358652367963, 0.48225294913469874), CesiumMath.EPSILON10);
expect(camera.up).toEqualEpsilon(new Cartesian3(-0.32052676705406324, 0.3603199946588929, 0.8760320159964963), CesiumMath.EPSILON10);
expect(camera.right).toEqualEpsilon(new Cartesian3(-0.7471597536218517, -0.6646444933705039, 0.0), CesiumMath.EPSILON10);
expect(camera.position).toEqualEpsilon(new Cartesian3(-6017603.25625715, 9091606.78076493, -5075070.862292178), CesiumMath.EPSILON6);
expect(camera.direction).toEqualEpsilon(new Cartesian3(0.5000640869795608, -0.7555144216716235, 0.4232420909591703), CesiumMath.EPSILON10);
expect(camera.up).toEqualEpsilon(new Cartesian3(-0.23360296374117637, 0.35293557895291494, 0.9060166292295686), CesiumMath.EPSILON10);
expect(camera.right).toEqualEpsilon(new Cartesian3(-0.8338858220671682, -0.5519369853120581, 0), CesiumMath.EPSILON10);
});

it('views rectangle in 3D (4)', function() {
var rectangle = new Rectangle(
CesiumMath.toRadians(90.0),
CesiumMath.toRadians(-62.0),
CesiumMath.toRadians(174.0),
CesiumMath.toRadians(-4.0));
camera.viewRectangle(rectangle);
expect(camera.position).toEqualEpsilon(new Cartesian3(-7307919.685704952, 8116267.060310548, -7085995.891547672), CesiumMath.EPSILON6);
expect(camera.direction).toEqualEpsilon(new Cartesian3(0.5607858365117034, -0.622815768168856, 0.5455453826109309), CesiumMath.EPSILON10);
expect(camera.up).toEqualEpsilon(new Cartesian3(-0.3650411126627274, 0.4054192281503986, 0.8380812821629494), CesiumMath.EPSILON10);
expect(camera.right).toEqualEpsilon(new Cartesian3(-0.7431448254773944, -0.6691306063588581, 0), CesiumMath.EPSILON10);
});

it('views rectangle in 3D across IDL', function() {
Expand Down