Skip to content

Commit

Permalink
Merge pull request #2764 from AnalyticalGraphicsInc/viewRectangle
Browse files Browse the repository at this point in the history
Improve position selected for camera in viewRectangle in 3D.
  • Loading branch information
bagnell committed Jun 4, 2015
2 parents f6257a5 + 9c7d3b7 commit f1b3aab
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 47 deletions.
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

0 comments on commit f1b3aab

Please sign in to comment.