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

Fixed terrain tile culling problems when under ellipsoid #8397

Merged
merged 7 commits into from
Dec 11, 2019
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 @@ -5,6 +5,7 @@ Change Log

##### Fixes :wrench:
* Fixed Geocoder auto-complete suggestions when hosted inside Web Components. [#8425](https://github.com/AnalyticalGraphicsInc/cesium/pull/8425)
* Fixed terrain tile culling problems when under ellipsoid. [#8397](https://github.com/AnalyticalGraphicsInc/cesium/pull/8397)
* Fixed primitive culling when below the ellipsoid but above terrain. [#8398](https://github.com/AnalyticalGraphicsInc/cesium/pull/8398)

### 1.64.0 - 2019-12-02
Expand Down
218 changes: 162 additions & 56 deletions Source/Core/EllipsoidalOccluder.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Check from './Check.js';
import defaultValue from './defaultValue.js';
import defined from './defined.js';
import defineProperties from './defineProperties.js';
import Ellipsoid from './Ellipsoid.js';
import Rectangle from './Rectangle.js';

/**
Expand Down Expand Up @@ -96,7 +97,7 @@ import Rectangle from './Rectangle.js';
EllipsoidalOccluder.prototype.isPointVisible = function(occludee) {
var ellipsoid = this._ellipsoid;
var occludeeScaledSpacePosition = ellipsoid.transformPositionToScaledSpace(occludee, scratchCartesian);
return this.isScaledSpacePointVisible(occludeeScaledSpacePosition);
return isScaledSpacePointVisible(occludeeScaledSpacePosition, this._cameraPositionInScaledSpace, this._distanceToLimbInScaledSpaceSquared);
};

/**
Expand All @@ -116,16 +117,39 @@ import Rectangle from './Rectangle.js';
* occluder.isScaledSpacePointVisible(scaledSpacePoint); //returns true
*/
EllipsoidalOccluder.prototype.isScaledSpacePointVisible = function(occludeeScaledSpacePosition) {
// See https://cesium.com/blog/2013/04/25/Horizon-culling/
var cv = this._cameraPositionInScaledSpace;
var vhMagnitudeSquared = this._distanceToLimbInScaledSpaceSquared;
var vt = Cartesian3.subtract(occludeeScaledSpacePosition, cv, scratchCartesian);
var vtDotVc = -Cartesian3.dot(vt, cv);
// If vhMagnitudeSquared < 0 then we are below the surface of the ellipsoid and
// in this case, set the culling plane to be on V.
var isOccluded = vhMagnitudeSquared < 0 ? vtDotVc > 0 : (vtDotVc > vhMagnitudeSquared &&
vtDotVc * vtDotVc / Cartesian3.magnitudeSquared(vt) > vhMagnitudeSquared);
return !isOccluded;
return isScaledSpacePointVisible(occludeeScaledSpacePosition, this._cameraPositionInScaledSpace, this._distanceToLimbInScaledSpaceSquared);
};

var scratchCameraPositionInScaledSpaceShrunk = new Cartesian3();

/**
* Similar to {@link EllipsoidalOccluder#isScaledSpacePointVisible} except tests against an
* ellipsoid that has been shrunk by the minimum height when the minimum height is below
* the ellipsoid. This is intended to be used with points generated by
* {@link EllipsoidalOccluder#computeHorizonCullingPointPossiblyUnderEllipsoid} or
* {@link EllipsoidalOccluder#computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid}.
*
* @param {Cartesian3} occludeeScaledSpacePosition The point to test for visibility, represented in the scaled space of the possibly-shrunk ellipsoid.
* @returns {Boolean} <code>true</code> if the occludee is visible; otherwise <code>false</code>.
*/
EllipsoidalOccluder.prototype.isScaledSpacePointVisiblePossiblyUnderEllipsoid = function(occludeeScaledSpacePosition, minimumHeight) {
var ellipsoid = this._ellipsoid;
var vhMagnitudeSquared;
var cv;

if (defined(minimumHeight) && minimumHeight < 0.0 && ellipsoid.minimumRadius > -minimumHeight) {
// This code is similar to the cameraPosition setter, but unrolled for performance because it will be called a lot.
cv = scratchCameraPositionInScaledSpaceShrunk;
cv.x = this._cameraPosition.x / (ellipsoid.radii.x + minimumHeight);
cv.y = this._cameraPosition.y / (ellipsoid.radii.y + minimumHeight);
cv.z = this._cameraPosition.z / (ellipsoid.radii.z + minimumHeight);
vhMagnitudeSquared = cv.x * cv.x + cv.y * cv.y + cv.z * cv.z - 1.0;
} else {
cv = this._cameraPositionInScaledSpace;
vhMagnitudeSquared = this._distanceToLimbInScaledSpaceSquared;
}

return isScaledSpacePointVisible(occludeeScaledSpacePosition, cv, vhMagnitudeSquared);
};

/**
Expand All @@ -145,30 +169,32 @@ import Rectangle from './Rectangle.js';
* @returns {Cartesian3} The computed horizon culling point, expressed in the ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPoint = function(directionToPoint, positions, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object('directionToPoint', directionToPoint);
Check.defined('positions', positions);
//>>includeEnd('debug');

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

var ellipsoid = this._ellipsoid;
var scaledSpaceDirectionToPoint = computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint);
var resultMagnitude = 0.0;

for (var i = 0, len = positions.length; i < len; ++i) {
var position = positions[i];
var candidateMagnitude = computeMagnitude(ellipsoid, position, scaledSpaceDirectionToPoint);
resultMagnitude = Math.max(resultMagnitude, candidateMagnitude);
}

return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
return computeHorizonCullingPointFromPositions(this._ellipsoid, directionToPoint, positions, result);
};

var positionScratch = new Cartesian3();
var scratchEllipsoidShrunk = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);

/**
* Similar to {@link EllipsoidalOccluder#computeHorizonCullingPoint} except computes the culling
* point relative to an ellipsoid that has been shrunk by the minimum height when the minimum height is below
* the ellipsoid. The returned point is expressed in the possibly-shrunk ellipsoid-scaled space and is suitable
* for use with {@link EllipsoidalOccluder#isScaledSpacePointVisiblePossiblyUnderEllipsoid}.
*
* @param {Cartesian3} directionToPoint The direction that the computed point will lie along.
* A reasonable direction to use is the direction from the center of the ellipsoid to
* the center of the bounding sphere computed from the positions. The direction need not
* be normalized.
* @param {Cartesian3[]} positions The positions from which to compute the horizon culling point. The positions
* must be expressed in a reference frame centered at the ellipsoid and aligned with the
* ellipsoid's axes.
* @param {Number} [minimumHeight] The minimum height of all positions. If this value is undefined, all positions are assumed to be above the ellipsoid.
* @param {Cartesian3} [result] The instance on which to store the result instead of allocating a new instance.
* @returns {Cartesian3} The computed horizon culling point, expressed in the possibly-shrunk ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPointPossiblyUnderEllipsoid = function(directionToPoint, positions, minimumHeight, result) {
var possiblyShrunkEllipsoid = getPossiblyShrunkEllipsoid(this._ellipsoid, minimumHeight, scratchEllipsoidShrunk);
return computeHorizonCullingPointFromPositions(possiblyShrunkEllipsoid, directionToPoint, positions, result);
};
/**
* Computes a point that can be used for horizon culling from a list of positions. If the point is below
* the horizon, all of the positions are guaranteed to be below the horizon as well. The returned point
Expand All @@ -188,31 +214,31 @@ import Rectangle from './Rectangle.js';
* @returns {Cartesian3} The computed horizon culling point, expressed in the ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPointFromVertices = function(directionToPoint, vertices, stride, center, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object('directionToPoint', directionToPoint);
Check.defined('vertices', vertices);
Check.typeOf.number('stride', stride);
//>>includeEnd('debug');

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

center = defaultValue(center, Cartesian3.ZERO);
var ellipsoid = this._ellipsoid;
var scaledSpaceDirectionToPoint = computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint);
var resultMagnitude = 0.0;

for (var i = 0, len = vertices.length; i < len; i += stride) {
positionScratch.x = vertices[i] + center.x;
positionScratch.y = vertices[i + 1] + center.y;
positionScratch.z = vertices[i + 2] + center.z;

var candidateMagnitude = computeMagnitude(ellipsoid, positionScratch, scaledSpaceDirectionToPoint);
resultMagnitude = Math.max(resultMagnitude, candidateMagnitude);
}
return computeHorizonCullingPointFromVertices(this._ellipsoid, directionToPoint, vertices, stride, center, result);
};

return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
/**
* Similar to {@link EllipsoidalOccluder#computeHorizonCullingPointFromVertices} except computes the culling
* point relative to an ellipsoid that has been shrunk by the minimum height when the minimum height is below
* the ellipsoid. The returned point is expressed in the possibly-shrunk ellipsoid-scaled space and is suitable
* for use with {@link EllipsoidalOccluder#isScaledSpacePointVisiblePossiblyUnderEllipsoid}.
*
* @param {Cartesian3} directionToPoint The direction that the computed point will lie along.
* A reasonable direction to use is the direction from the center of the ellipsoid to
* the center of the bounding sphere computed from the positions. The direction need not
* be normalized.
* @param {Number[]} vertices The vertices from which to compute the horizon culling point. The positions
* must be expressed in a reference frame centered at the ellipsoid and aligned with the
* ellipsoid's axes.
* @param {Number} [stride=3]
* @param {Cartesian3} [center=Cartesian3.ZERO]
* @param {Number} [minimumHeight] The minimum height of all vertices. If this value is undefined, all vertices are assumed to be above the ellipsoid.
* @param {Cartesian3} [result] The instance on which to store the result instead of allocating a new instance.
* @returns {Cartesian3} The computed horizon culling point, expressed in the possibly-shrunk ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid = function(directionToPoint, vertices, stride, center, minimumHeight, result) {
var possiblyShrunkEllipsoid = getPossiblyShrunkEllipsoid(this._ellipsoid, minimumHeight, scratchEllipsoidShrunk);
return computeHorizonCullingPointFromVertices(possiblyShrunkEllipsoid, directionToPoint, vertices, stride, center, result);
};

var subsampleScratch = [];
Expand Down Expand Up @@ -246,6 +272,86 @@ import Rectangle from './Rectangle.js';
return this.computeHorizonCullingPoint(bs.center, positions, result);
};

var scratchEllipsoidShrunkRadii = new Cartesian3();

function getPossiblyShrunkEllipsoid(ellipsoid, minimumHeight, result) {
if (defined(minimumHeight) && minimumHeight < 0.0 && ellipsoid.minimumRadius > -minimumHeight) {
var ellipsoidShrunkRadii = Cartesian3.fromElements(
ellipsoid.radii.x + minimumHeight,
ellipsoid.radii.y + minimumHeight,
ellipsoid.radii.z + minimumHeight,
scratchEllipsoidShrunkRadii
);
ellipsoid = Ellipsoid.fromCartesian3(ellipsoidShrunkRadii, result);
}
return ellipsoid;
}

function computeHorizonCullingPointFromPositions(ellipsoid, directionToPoint, positions, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object('directionToPoint', directionToPoint);
Check.defined('positions', positions);
//>>includeEnd('debug');

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

var scaledSpaceDirectionToPoint = computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint);
var resultMagnitude = 0.0;

for (var i = 0, len = positions.length; i < len; ++i) {
var position = positions[i];
var candidateMagnitude = computeMagnitude(ellipsoid, position, scaledSpaceDirectionToPoint);
resultMagnitude = Math.max(resultMagnitude, candidateMagnitude);
}

return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
}

var positionScratch = new Cartesian3();

function computeHorizonCullingPointFromVertices(ellipsoid, directionToPoint, vertices, stride, center, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object('directionToPoint', directionToPoint);
Check.defined('vertices', vertices);
Check.typeOf.number('stride', stride);
//>>includeEnd('debug');

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

stride = defaultValue(stride, 3);
center = defaultValue(center, Cartesian3.ZERO);
var scaledSpaceDirectionToPoint = computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint);
var resultMagnitude = 0.0;

for (var i = 0, len = vertices.length; i < len; i += stride) {
positionScratch.x = vertices[i] + center.x;
positionScratch.y = vertices[i + 1] + center.y;
positionScratch.z = vertices[i + 2] + center.z;

var candidateMagnitude = computeMagnitude(ellipsoid, positionScratch, scaledSpaceDirectionToPoint);
resultMagnitude = Math.max(resultMagnitude, candidateMagnitude);
}

return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
}

function isScaledSpacePointVisible(occludeeScaledSpacePosition, cameraPositionInScaledSpace, distanceToLimbInScaledSpaceSquared) {
// See https://cesium.com/blog/2013/04/25/Horizon-culling/
var cv = cameraPositionInScaledSpace;
var vhMagnitudeSquared = distanceToLimbInScaledSpaceSquared;
var vt = Cartesian3.subtract(occludeeScaledSpacePosition, cv, scratchCartesian);
var vtDotVc = -Cartesian3.dot(vt, cv);
// If vhMagnitudeSquared < 0 then we are below the surface of the ellipsoid and
// in this case, set the culling plane to be on V.
var isOccluded = vhMagnitudeSquared < 0 ? vtDotVc > 0 : (vtDotVc > vhMagnitudeSquared &&
vtDotVc * vtDotVc / Cartesian3.magnitudeSquared(vt) > vhMagnitudeSquared);
return !isOccluded;
}

var scaledSpaceScratch = new Cartesian3();
var directionScratch = new Cartesian3();

Expand Down
2 changes: 1 addition & 1 deletion Source/Core/HeightmapTessellator.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ import WebMercatorProjection from './WebMercatorProjection.js';
var occludeePointInScaledSpace;
if (hasRelativeToCenter) {
var occluder = new EllipsoidalOccluder(ellipsoid);
occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions);
occludeePointInScaledSpace = occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(relativeToCenter, positions, minimumHeight);
}

var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter);
Expand Down
4 changes: 2 additions & 2 deletions Source/Core/QuantizedMeshTerrainData.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ import TerrainMesh from './TerrainMesh.js';
var maximumHeight = result.maximumHeight;
var boundingSphere = defaultValue(BoundingSphere.clone(result.boundingSphere), that._boundingSphere);
var obb = defaultValue(OrientedBoundingBox.clone(result.orientedBoundingBox), that._orientedBoundingBox);
var occlusionPoint = Cartesian3.clone(that._horizonOcclusionPoint);
var occludeePointInScaledSpace = defaultValue(Cartesian3.clone(result.occludeePointInScaledSpace), that._horizonOcclusionPoint);
var stride = result.vertexStride;
var terrainEncoding = TerrainEncoding.clone(result.encoding);

Expand All @@ -326,7 +326,7 @@ import TerrainMesh from './TerrainMesh.js';
minimumHeight,
maximumHeight,
boundingSphere,
occlusionPoint,
occludeePointInScaledSpace,
stride,
obb,
terrainEncoding,
Expand Down
16 changes: 8 additions & 8 deletions Source/Scene/GlobeSurfaceTileProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ import TileSelectionResult from './TileSelectionResult.js';
return intersection;
}

if (occluders.ellipsoid.isScaledSpacePointVisible(occludeePointInScaledSpace)) {
if (occluders.ellipsoid.isScaledSpacePointVisiblePossiblyUnderEllipsoid(occludeePointInScaledSpace, tileBoundingRegion.minimumHeight)) {
return intersection;
}

Expand Down Expand Up @@ -805,17 +805,17 @@ import TileSelectionResult from './TileSelectionResult.js';

var cornerPositionsScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];

function computeOccludeePoint(tileProvider, center, rectangle, height, result) {
function computeOccludeePoint(tileProvider, center, rectangle, minimumHeight, maximumHeight, result) {
var ellipsoidalOccluder = tileProvider.quadtree._occluders.ellipsoid;
var ellipsoid = ellipsoidalOccluder.ellipsoid;

var cornerPositions = cornerPositionsScratch;
Cartesian3.fromRadians(rectangle.west, rectangle.south, height, ellipsoid, cornerPositions[0]);
Cartesian3.fromRadians(rectangle.east, rectangle.south, height, ellipsoid, cornerPositions[1]);
Cartesian3.fromRadians(rectangle.west, rectangle.north, height, ellipsoid, cornerPositions[2]);
Cartesian3.fromRadians(rectangle.east, rectangle.north, height, ellipsoid, cornerPositions[3]);
Cartesian3.fromRadians(rectangle.west, rectangle.south, maximumHeight, ellipsoid, cornerPositions[0]);
Cartesian3.fromRadians(rectangle.east, rectangle.south, maximumHeight, ellipsoid, cornerPositions[1]);
Cartesian3.fromRadians(rectangle.west, rectangle.north, maximumHeight, ellipsoid, cornerPositions[2]);
Cartesian3.fromRadians(rectangle.east, rectangle.north, maximumHeight, ellipsoid, cornerPositions[3]);

return ellipsoidalOccluder.computeHorizonCullingPoint(center, cornerPositions, result);
return ellipsoidalOccluder.computeHorizonCullingPointPossiblyUnderEllipsoid(center, cornerPositions, minimumHeight, result);
}

/**
Expand Down Expand Up @@ -863,7 +863,7 @@ import TileSelectionResult from './TileSelectionResult.js';
tile.tilingScheme.ellipsoid,
surfaceTile.orientedBoundingBox);

surfaceTile.occludeePointInScaledSpace = computeOccludeePoint(this, surfaceTile.orientedBoundingBox.center, tile.rectangle, tileBoundingRegion.maximumHeight, surfaceTile.occludeePointInScaledSpace);
surfaceTile.occludeePointInScaledSpace = computeOccludeePoint(this, surfaceTile.orientedBoundingBox.center, tile.rectangle, tileBoundingRegion.minimumHeight, tileBoundingRegion.maximumHeight, surfaceTile.occludeePointInScaledSpace);
}
}

Expand Down
Loading