Skip to content

Commit

Permalink
added possiblyUnderEllipsoid culling fix
Browse files Browse the repository at this point in the history
  • Loading branch information
IanLilleyT committed Dec 4, 2019
1 parent 63e97a5 commit 435bce5
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 56 deletions.
173 changes: 138 additions & 35 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 @@ -116,35 +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();

/**
* 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
* is expressed in the ellipsoid-scaled space and is suitable for use with
* {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
* 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} 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 {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 ellipsoid-scaled space.
* @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.computeHorizonCullingPoint = function(directionToPoint, positions, result) {
EllipsoidalOccluder.prototype.isScaledSpacePointVisiblePossiblyUnderEllipsoid = function(occludeeScaledSpacePosition, minimumHeight) {
var ellipsoid = this._ellipsoid;
if (defined(minimumHeight) && minimumHeight < 0.0 && ellipsoid.minimumRadius > -minimumHeight) {
var cameraPositionInScaledSpaceShrunk = Cartesian3.fromElements(
this._cameraPosition.x / (ellipsoid.radii.x + minimumHeight),
this._cameraPosition.y / (ellipsoid.radii.y + minimumHeight),
this._cameraPosition.z / (ellipsoid.radii.z + minimumHeight),
scratchCameraPositionInScaledSpaceShrunk
);

var distanceToLimbInScaledSpaceSquaredShrunk = Cartesian3.magnitudeSquared(cameraPositionInScaledSpaceShrunk) - 1.0;
return isScaledSpacePointVisible(occludeeScaledSpacePosition, cameraPositionInScaledSpaceShrunk, distanceToLimbInScaledSpaceSquaredShrunk);
}

return isScaledSpacePointVisible(occludeeScaledSpacePosition, this._cameraPositionInScaledSpace, this._distanceToLimbInScaledSpaceSquared);
};

function computeHorizonCullingPoint(ellipsoid, directionToPoint, positions, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object('directionToPoint', directionToPoint);
Check.defined('positions', positions);
Expand All @@ -154,7 +159,6 @@ import Rectangle from './Rectangle.js';
result = new Cartesian3();
}

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

Expand All @@ -165,9 +169,7 @@ import Rectangle from './Rectangle.js';
}

return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
};

var positionScratch = new Cartesian3();
}

/**
* Computes a point that can be used for horizon culling from a list of positions. If the point is below
Expand All @@ -179,15 +181,41 @@ import Rectangle from './Rectangle.js';
* 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 {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 {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 ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPointFromVertices = function(directionToPoint, vertices, stride, center, result) {
EllipsoidalOccluder.prototype.computeHorizonCullingPoint = function(directionToPoint, positions, result) {
return computeHorizonCullingPoint(this._ellipsoid, directionToPoint, positions, result);
};

/**
* 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);
return computeHorizonCullingPoint(possiblyShrunkEllipsoid, directionToPoint, positions, 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);
Expand All @@ -198,8 +226,8 @@ import Rectangle from './Rectangle.js';
result = new Cartesian3();
}

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

Expand All @@ -213,6 +241,52 @@ import Rectangle from './Rectangle.js';
}

return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, 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
* is expressed in the ellipsoid-scaled space and is suitable for use with
* {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
*
* @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 {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 ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPointFromVertices = function(directionToPoint, vertices, stride, center, result) {
return computeHorizonCullingPointFromVertices(this._ellipsoid, directionToPoint, vertices, stride, center, 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);
return computeHorizonCullingPointFromVertices(possiblyShrunkEllipsoid, directionToPoint, vertices, stride, center, result);
};

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

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

function getPossiblyShrunkEllipsoid(ellipsoid, minimumHeight) {
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, scratchEllipsoidShrunk);
}
return ellipsoid;
}

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 @@ -599,7 +599,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 @@ -802,17 +802,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 @@ -860,7 +860,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

0 comments on commit 435bce5

Please sign in to comment.