Skip to content

Commit

Permalink
added per tile shrunk-ellipsoid horizon culling
Browse files Browse the repository at this point in the history
  • Loading branch information
IanLilleyT committed Nov 14, 2019
1 parent 63e97a5 commit b053e7e
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 31 deletions.
154 changes: 144 additions & 10 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,16 +117,40 @@ 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 scratchEllipsoidShrunkRadii = new Cartesian3();
var scratchEllipsoidShrunk = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
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},
* {@link EllipsoidalOccluder#computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid}, or
* {@link EllipsoidalOccluder#computeHorizonCullingPointFromRectanglePossiblyUnderEllipsoid}.
*
* @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;
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
);
var ellipsoidShrunk = Ellipsoid.fromCartesian3(ellipsoidShrunkRadii, scratchEllipsoidShrunk);
var cameraPositionInScaledSpaceShrunk = ellipsoidShrunk.transformPositionToScaledSpace(this._cameraPosition, scratchCameraPositionInScaledSpaceShrunk);
var distanceToLimbinScaledSpaceSquaredShrunk = Cartesian3.magnitudeSquared(cameraPositionInScaledSpaceShrunk) - 1.0;
return isScaledSpacePointVisible(occludeeScaledSpacePosition, cameraPositionInScaledSpaceShrunk, distanceToLimbinScaledSpaceSquaredShrunk);
}

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

/**
Expand Down Expand Up @@ -167,6 +192,30 @@ import Rectangle from './Rectangle.js';
return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, 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 that = this;
return computeHorizonCullingPointPossiblyUnderEllipsoid(that, minimumHeight, function() {
return that.computeHorizonCullingPoint(directionToPoint, positions, result);
});
};

var positionScratch = new Cartesian3();

/**
Expand Down Expand Up @@ -215,6 +264,32 @@ import Rectangle from './Rectangle.js';
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 that = this;
return computeHorizonCullingPointPossiblyUnderEllipsoid(that, minimumHeight, function() {
return that.computeHorizonCullingPointFromVertices(directionToPoint, vertices, stride, center, result);
});
};

var subsampleScratch = [];

/**
Expand Down Expand Up @@ -246,6 +321,65 @@ import Rectangle from './Rectangle.js';
return this.computeHorizonCullingPoint(bs.center, positions, result);
};

/**
* Similar to {@link EllipsoidalOccluder#computeHorizonCullingPointFromRectangle} except computes the culling
* point relative to an ellipsoid that has been shrunk by the height when the 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 {Rectangle} rectangle The rectangle for which to compute the horizon culling point.
* @param {Ellipsoid} ellipsoid The ellipsoid on which the rectangle is defined. This may be different from
* the ellipsoid used by this instance for occlusion testing.
* @param {Number} [height] The height of the rectangle. If this value is undefined, the rectangle is assumed to be on the occluder's 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.computeHorizonCullingPointFromRectanglePossiblyUnderEllipsoid = function(rectangle, ellipsoid, height, result) {
var that = this;
return computeHorizonCullingPointPossiblyUnderEllipsoid(that, height, function() {
return that.computeHorizonCullingPointFromRectangle(rectangle, ellipsoid, result);
});
};

var scratchEllipsoidShrunkRadii2 = new Cartesian3();
var scratchEllipsoidShrunk2 = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);

function computeHorizonCullingPointPossiblyUnderEllipsoid(ellipsoidalOccluder, minimumHeight, func) {
var ellipsoid = ellipsoidalOccluder._ellipsoid;

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,
scratchEllipsoidShrunkRadii2
);
var ellipsoidShrunk = Ellipsoid.fromCartesian3(ellipsoidShrunkRadii, scratchEllipsoidShrunk2);

// The horizon culling point is calculated with respect to the shrunk ellipsoid, so
// temporarily change change the occluder's ellipsoid to the shrunk ellipsoid.
ellipsoidalOccluder._ellipsoid = ellipsoidShrunk;
var result = func();
ellipsoidalOccluder._ellipsoid = ellipsoid;
return result;
}

return func();
}

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
14 changes: 7 additions & 7 deletions Source/Scene/TerrainFillMesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ import TileSelectionResult from './TileSelectionResult.js';
minimumHeight,
maximumHeight,
BoundingSphere.fromOrientedBoundingBox(obb),
computeOccludeePoint(tileProvider, obb.center, rectangle, maximumHeight),
computeOccludeePoint(tileProvider, obb.center, rectangle, minimumHeight, maximumHeight),
encoding.getStride(),
obb,
encoding,
Expand Down Expand Up @@ -1183,16 +1183,16 @@ 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);
}
export default TerrainFillMesh;
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js';
}

var occluder = new EllipsoidalOccluder(ellipsoid);
var occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions);
var occludeePointInScaledSpace = occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(relativeToCenter, positions, minHeight);

var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter);
var encoding = new TerrainEncoding(aaBox, skirtOptions.hMin, maxHeight, fromENU, false, includeWebMercatorT);
Expand Down
Loading

0 comments on commit b053e7e

Please sign in to comment.