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

Fix billboard on terrain and Globe.getHeight #4622

Merged
merged 21 commits into from
Dec 26, 2016
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
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Change Log
==========
* Fixed issue where billboards on terrain had some offset. [#4598](https://github.com/AnalyticalGraphicsInc/cesium/issues/4598)
* Fixed issue where `globe.getHeight` randomly returned 'undefined'. [#3411](https://github.com/AnalyticalGraphicsInc/cesium/issues/3411)

### 1.29 - 2017-01-02

Expand Down
53 changes: 53 additions & 0 deletions Source/Core/Ellipsoid.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ define([
ellipsoid._maximumRadius = Math.max(x, y, z);

ellipsoid._centerToleranceSquared = CesiumMath.EPSILON1;

if (ellipsoid._radiiSquared.z !== 0) {
ellipsoid._sqauredXOverSquaredZ = ellipsoid._radiiSquared.x / ellipsoid._radiiSquared.z;
}
}

/**
Expand Down Expand Up @@ -86,6 +90,7 @@ define([
this._minimumRadius = undefined;
this._maximumRadius = undefined;
this._centerToleranceSquared = undefined;
this._sqauredXOverSquaredZ = undefined;

initialize(this, x, y, z);
}
Expand Down Expand Up @@ -608,5 +613,53 @@ define([
return this._radii.toString();
};

/**
* Computes a point which is the intersection of the surface normal with the z-axis.
*
* @param {Cartesian3} position the position. must be on the surface of the ellipsoid.
* @param {Number} [buffer = 0.0] A buffer to subtract from the ellipsoid size when checking if the point is inside the ellipsoid.
* In earth case, with common earth datums, there is no need for this buffer since the intersection point is always (relatively) very close to the center.
* In WGS84 datum, intersection point is at max z = +-42841.31151331382 (0.673% of z-axis).
* Intersection point could be outside the ellipsoid if the ratio of MajorAxis / AxisOfRotation is bigger than the square root of 2
* @param {Cartesian} [result] The cartesian to which to copy the result, or undefined to create and
* return a new instance.
* @returns {Cartesian | undefined} the intersection point if it's inside the ellipsoid, undefined otherwise
*
* @exception {DeveloperError} position is required.
* @exception {DeveloperError} Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y).
* @exception {DeveloperError} Ellipsoid.radii.z must be greater than 0.
*/
Ellipsoid.prototype.getSurfaceNormalIntersectionWithZAxis = function(position, buffer, result) {
//>>includeStart('debug', pragmas.debug);
if (!defined(position)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really minor, but could you please add unit tests for these three DeveloperError cases?

throw new DeveloperError('position is required.');
}
if (!CesiumMath.equalsEpsilon(this._radii.x, this._radii.y, CesiumMath.EPSILON15)) {
throw new DeveloperError('Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y)');
}
if (this._radii.z === 0) {
throw new DeveloperError('Ellipsoid.radii.z must be greater than 0');
}
//>>includeEnd('debug');

buffer = defaultValue(buffer, 0.0);

var sqauredXOverSquaredZ = this._sqauredXOverSquaredZ;

if (!defined(result)) {
result = new Cartesian3();
}

result.x = 0.0;
result.y = 0.0;
result.z = position.z * (1 - sqauredXOverSquaredZ);

if (Math.abs(result.z) >= this._radii.z - buffer) {
return undefined;
}

return result;
};

return Ellipsoid;
});
23 changes: 21 additions & 2 deletions Source/Scene/Globe.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ define([
'../Core/Event',
'../Core/IntersectionTests',
'../Core/loadImage',
'../Core/Math',
'../Core/Ray',
'../Core/Rectangle',
'../Renderer/ShaderSource',
Expand Down Expand Up @@ -43,6 +44,7 @@ define([
Event,
IntersectionTests,
loadImage,
CesiumMath,
Ray,
Rectangle,
ShaderSource,
Expand Down Expand Up @@ -424,10 +426,27 @@ define([
}

var ellipsoid = this._surface._tileProvider.tilingScheme.ellipsoid;
var cartesian = ellipsoid.cartographicToCartesian(cartographic, scratchGetHeightCartesian);

//cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`
var cartesian = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0, ellipsoid, scratchGetHeightCartesian);

var ray = scratchGetHeightRay;
Cartesian3.normalize(cartesian, ray.direction);
var surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesian, ray.direction);

// Try to find the intersection point between the surface normal and z-axis.
// minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
var rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesian, 11500.0, ray.origin);

// Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid
if (!defined(rayOrigin)) {
// intersection point is outside the ellipsoid, try other value
// minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
var magnitude = Math.min(defaultValue(tile.data.minimumHeight, 0.0),-11500.0);

// multiply by the *positive* value of the magnitude
var vectorToMinimumPoint = Cartesian3.multiplyByScalar(surfaceNormal, Math.abs(magnitude) + 1, scratchGetHeightIntersection);
Cartesian3.subtract(cartesian, vectorToMinimumPoint, ray.origin);
}

var intersection = tile.data.pick(ray, undefined, undefined, false, scratchGetHeightIntersection);
if (!defined(intersection)) {
Expand Down
29 changes: 23 additions & 6 deletions Source/Scene/QuadtreePrimitive.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ define([
QuadtreePrimitive.prototype.updateHeight = function(cartographic, callback) {
var primitive = this;
var object = {
position : undefined,
positionOnEllipsoidSurface : undefined,
positionCartographic : cartographic,
level : -1,
callback : callback
Expand Down Expand Up @@ -455,7 +455,7 @@ define([
customDataRemoved.length = 0;
}

// Our goal with load ordering is to first load all of the tiles we need to
// Our goal with load ordering is to first load all of the tiles we need to
// render the current scene at full detail. Loading any other tiles is just
// a form of prefetching, and we need not do it at all (other concerns aside). This
// simple and obvious statement gets more complicated when we realize that, because
Expand Down Expand Up @@ -761,13 +761,30 @@ define([
var data = customData[i];

if (tile.level > data.level) {
if (!defined(data.position)) {
data.position = ellipsoid.cartographicToCartesian(data.positionCartographic);
if (!defined(data.positionOnEllipsoidSurface)) {
// cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`
data.positionOnEllipsoidSurface = Cartesian3.fromRadians(data.positionCartographic.longitude, data.positionCartographic.latitude, 0.0, ellipsoid);
}

if (mode === SceneMode.SCENE3D) {
Cartesian3.clone(Cartesian3.ZERO, scratchRay.origin);
Cartesian3.normalize(data.position, scratchRay.direction);
var surfaceNormal = ellipsoid.geodeticSurfaceNormal(data.positionOnEllipsoidSurface, scratchRay.direction);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this duplicate code belong in a utility function for Ray or Ellipsoid? That would also be more testable so we could add unit tests.


// compute origin point

// Try to find the intersection point between the surface normal and z-axis.
// minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
var rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(data.positionOnEllipsoidSurface, 11500.0, scratchRay.origin);

// Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid
if (!defined(rayOrigin)) {
// intersection point is outside the ellipsoid, try other value
// minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
var magnitude = Math.min(defaultValue(tile.data.minimumHeight, 0.0),-11500.0);

// multiply by the *positive* value of the magnitude
var vectorToMinimumPoint = Cartesian3.multiplyByScalar(surfaceNormal, Math.abs(magnitude) + 1, scratchPosition);
Cartesian3.subtract(data.positionOnEllipsoidSurface, vectorToMinimumPoint, scratchRay.origin);
}
} else {
Cartographic.clone(data.positionCartographic, scratchCartographic);

Expand Down
109 changes: 109 additions & 0 deletions Specs/Core/EllipsoidSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,5 +434,114 @@ defineSuite([
expect(cloned).toEqual(myEllipsoid);
});

it('getSurfaceNormalIntersectionWithZAxis throws with no position', function() {
expect(function() {
Ellipsoid.WGS84.getSurfaceNormalIntersectionWithZAxis(undefined);
}).toThrowDeveloperError();
});

it('getSurfaceNormalIntersectionWithZAxis throws if the ellipsoid is not an ellipsoid of revolution', function() {
expect(function() {
var ellipsoid = new Ellipsoid(1,2,3);
var cartesian = new Cartesian3();
ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesian);
}).toThrowDeveloperError();
});

it('getSurfaceNormalIntersectionWithZAxis throws if the ellipsoid has radii.z === 0', function() {
expect(function() {
var ellipsoid = new Ellipsoid(1,2,0);
var cartesian = new Cartesian3();
ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesian);
}).toThrowDeveloperError();
});

it('getSurfaceNormalIntersectionWithZAxis works without a result parameter', function() {
var ellipsoid = Ellipsoid.WGS84;
var cartographic = Cartographic.fromDegrees(35.23,33.23);
var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic);
var returnedResult = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface);
expect(returnedResult instanceof Cartesian3).toBe(true);
});

it('getSurfaceNormalIntersectionWithZAxis works with a result parameter', function() {
var ellipsoid = Ellipsoid.WGS84;
var cartographic = Cartographic.fromDegrees(35.23,33.23);
var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic);
var returnedResult = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined , cartesianOnTheSurface);
expect(returnedResult).toBe(cartesianOnTheSurface);
});

it('getSurfaceNormalIntersectionWithZAxis returns undefined if the result is outside the ellipsoid with buffer parameter', function() {
var ellipsoid = Ellipsoid.WGS84;
var cartographic = Cartographic.fromDegrees(35.23,33.23);
var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic);
var returnedResult = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, ellipsoid.radii.z);
expect(returnedResult).toBe(undefined);
});

it('getSurfaceNormalIntersectionWithZAxis returns undefined if the result is outside the ellipsoid without buffer parameter', function() {
var majorAxis = 10;
var minorAxis = 1;
var ellipsoid = new Ellipsoid(majorAxis,majorAxis,minorAxis);
var cartographic = Cartographic.fromDegrees(45.0,90.0);
var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic);
var returnedResult = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined);
expect(returnedResult).toBe(undefined);
});

it('getSurfaceNormalIntersectionWithZAxis returns a result that is equal to a value that computed in a different way', function() {
var ellipsoid = Ellipsoid.WGS84;
var cartographic = Cartographic.fromDegrees(35.23,33.23);
var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic);
var surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesianOnTheSurface);
var magnitude = cartesianOnTheSurface.x / surfaceNormal.x;

var expected = new Cartesian3();
expected.z = cartesianOnTheSurface.z - surfaceNormal.z * magnitude;
var result = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined);
expect(result).toEqualEpsilon(expected, CesiumMath.EPSILON8);

// at the equator
cartesianOnTheSurface = new Cartesian3(ellipsoid.radii.x, 0 , 0);
result = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined);
expect(result).toEqualEpsilon(Cartesian3.ZERO, CesiumMath.EPSILON8);

});

it('getSurfaceNormalIntersectionWithZAxis returns a result that when it\'s used as an origin for a vector with the surface normal direction it produces an accurate cartographic', function() {
var ellipsoid = Ellipsoid.WGS84;
var cartographic = Cartographic.fromDegrees(35.23,33.23);
var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic);
var surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesianOnTheSurface);

var result = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined);

var surfaceNormalWithLength = Cartesian3.multiplyByScalar(surfaceNormal, ellipsoid.maximumRadius, new Cartesian3());
var position = Cartesian3.add(result,surfaceNormalWithLength,new Cartesian3());
var resultCartographic = ellipsoid.cartesianToCartographic(position);
resultCartographic.height = 0.0;
expect(resultCartographic).toEqualEpsilon(cartographic, CesiumMath.EPSILON8);

// at the north pole
cartographic = Cartographic.fromDegrees(0,90);
cartesianOnTheSurface = new Cartesian3(0, 0 ,ellipsoid.radii.z);
surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesianOnTheSurface);
surfaceNormalWithLength = Cartesian3.multiplyByScalar(surfaceNormal, ellipsoid.maximumRadius, new Cartesian3());
result = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined);
position = Cartesian3.add(result,surfaceNormalWithLength,new Cartesian3());
resultCartographic = ellipsoid.cartesianToCartographic(position);
resultCartographic.height = 0.0;
expect(resultCartographic).toEqualEpsilon(cartographic, CesiumMath.EPSILON8);

});

it('ellipsoid is initialized with _sqauredXOverSquaredZ property', function() {
var ellipsoid = new Ellipsoid(4 , 4 , 3);

var sqauredXOverSquaredZ = ellipsoid.radiiSquared.x / ellipsoid.radiiSquared.z;
expect(ellipsoid._sqauredXOverSquaredZ).toEqual(sqauredXOverSquaredZ);
});

createPackableSpecs(Ellipsoid, Ellipsoid.WGS84, [Ellipsoid.WGS84.radii.x, Ellipsoid.WGS84.radii.y, Ellipsoid.WGS84.radii.z]);
});