diff --git a/CHANGES.md b/CHANGES.md index bb89af0be62f..caaeb3946191 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/Source/Core/EllipsoidalOccluder.js b/Source/Core/EllipsoidalOccluder.js index 8a2c1edc6a66..fe98656937a8 100644 --- a/Source/Core/EllipsoidalOccluder.js +++ b/Source/Core/EllipsoidalOccluder.js @@ -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'; /** @@ -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); }; /** @@ -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} true if the occludee is visible; otherwise false. + */ + 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); }; /** @@ -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 @@ -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 = []; @@ -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(); diff --git a/Source/Core/HeightmapTessellator.js b/Source/Core/HeightmapTessellator.js index 2f240f46a003..e4f9eef6933c 100644 --- a/Source/Core/HeightmapTessellator.js +++ b/Source/Core/HeightmapTessellator.js @@ -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); diff --git a/Source/Core/QuantizedMeshTerrainData.js b/Source/Core/QuantizedMeshTerrainData.js index 5c0c5ea14b0e..38086459e4d4 100644 --- a/Source/Core/QuantizedMeshTerrainData.js +++ b/Source/Core/QuantizedMeshTerrainData.js @@ -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); @@ -326,7 +326,7 @@ import TerrainMesh from './TerrainMesh.js'; minimumHeight, maximumHeight, boundingSphere, - occlusionPoint, + occludeePointInScaledSpace, stride, obb, terrainEncoding, diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 20c7eacf0587..904b50cdecbb 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -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; } @@ -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); } /** @@ -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); } } diff --git a/Source/Scene/TerrainFillMesh.js b/Source/Scene/TerrainFillMesh.js index c63f735a2946..9b579c2f8983 100644 --- a/Source/Scene/TerrainFillMesh.js +++ b/Source/Scene/TerrainFillMesh.js @@ -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, @@ -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; diff --git a/Source/WorkersES6/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/WorkersES6/createVerticesFromGoogleEarthEnterpriseBuffer.js index 3781c0279e26..1431da04d195 100644 --- a/Source/WorkersES6/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/WorkersES6/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -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); diff --git a/Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js b/Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js index 7719829b4e66..c65af0f91aca 100644 --- a/Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js +++ b/Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js @@ -6,6 +6,7 @@ import Cartesian3 from '../Core/Cartesian3.js'; import Cartographic from '../Core/Cartographic.js'; import defined from '../Core/defined.js'; import Ellipsoid from '../Core/Ellipsoid.js'; +import EllipsoidalOccluder from '../Core/EllipsoidalOccluder.js'; import IndexDatatype from '../Core/IndexDatatype.js'; import CesiumMath from '../Core/Math.js'; import Matrix4 from '../Core/Matrix4.js'; @@ -132,11 +133,18 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; var boundingSphere; if (exaggeration !== 1.0) { - // Bounding volumes and horizon culling point need to be recomputed since the tile payload assumes no exaggeration. + // Bounding volumes need to be recomputed since the tile payload assumes no exaggeration. boundingSphere = BoundingSphere.fromPoints(positions); orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, ellipsoid); } + var occludeePointInScaledSpace; + if (exaggeration !== 1.0 || minimumHeight < 0.0) { + // Horizon culling point needs to be recomputed since the tile payload assumes no exaggeration. + var occluder = new EllipsoidalOccluder(ellipsoid); + occludeePointInScaledSpace = occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(center, positions, minimumHeight); + } + var hMin = minimumHeight; hMin = Math.min(hMin, findMinMaxSkirts(parameters.westIndices, parameters.westSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum)); hMin = Math.min(hMin, findMinMaxSkirts(parameters.southIndices, parameters.southSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum)); @@ -218,6 +226,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; maximumHeight : maximumHeight, boundingSphere : boundingSphere, orientedBoundingBox : orientedBoundingBox, + occludeePointInScaledSpace : occludeePointInScaledSpace, encoding : encoding, skirtIndex : parameters.indices.length }; diff --git a/Source/WorkersES6/upsampleQuantizedTerrainMesh.js b/Source/WorkersES6/upsampleQuantizedTerrainMesh.js index 1939127b11e9..8048ce886d8d 100644 --- a/Source/WorkersES6/upsampleQuantizedTerrainMesh.js +++ b/Source/WorkersES6/upsampleQuantizedTerrainMesh.js @@ -269,7 +269,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; var orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, ellipsoid, orientedBoundingBoxScratch); var occluder = new EllipsoidalOccluder(ellipsoid); - var horizonOcclusionPoint = occluder.computeHorizonCullingPointFromVertices(boundingSphere.center, cartesianVertices, 3, boundingSphere.center, horizonOcclusionPointScratch); + var horizonOcclusionPoint = occluder.computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid(boundingSphere.center, cartesianVertices, 3, boundingSphere.center, minimumHeight, horizonOcclusionPointScratch); var heightRange = maximumHeight - minimumHeight; diff --git a/Specs/Core/EllipsoidalOccluderSpec.js b/Specs/Core/EllipsoidalOccluderSpec.js index 0d677094926e..4c1c2d3d4422 100644 --- a/Specs/Core/EllipsoidalOccluderSpec.js +++ b/Specs/Core/EllipsoidalOccluderSpec.js @@ -39,6 +39,34 @@ describe('Core/EllipsoidalOccluder', function() { expect(occluder.isScaledSpacePointVisible(scaledSpacePoint)).toEqual(true); }); + it('isScaledSpacePointVisiblePossiblyUnderEllipsoid example works as claimed', function() { + // Tests points that are halfway inside a unit sphere: + // 1) on the diagonal + // 2) on the +y-axis + // The camera is on the +z-axis so it will be able to see the diagonal point but not the +y-axis point. + + var cameraPosition = new Cartesian3(0, 0, 1.0); + var ellipsoid = new Ellipsoid(1.0, 1.0, 1.0); + var occluder = new EllipsoidalOccluder(ellipsoid, cameraPosition); + var height = -0.5; + + var direction = Cartesian3.normalize(new Cartesian3(1.0, 1.0, 1.0), new Cartesian3()); + var point = Cartesian3.multiplyByScalar(direction, 0.5, new Cartesian3()); + var scaledSpacePoint = occluder.computeHorizonCullingPoint(point, [point]); + var scaledSpacePointShrunk = occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(point, [point], height); + + expect(occluder.isScaledSpacePointVisible(scaledSpacePoint)).toEqual(false); + expect(occluder.isScaledSpacePointVisiblePossiblyUnderEllipsoid(scaledSpacePointShrunk, height)).toEqual(true); + + direction = new Cartesian3(0.0, 1.0, 0.0); + point = Cartesian3.multiplyByScalar(direction, 0.5, new Cartesian3()); + scaledSpacePoint = occluder.computeHorizonCullingPoint(point, [point]); + scaledSpacePointShrunk = occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(point, [point], height); + + expect(occluder.isScaledSpacePointVisible(scaledSpacePoint)).toEqual(false); + expect(occluder.isScaledSpacePointVisiblePossiblyUnderEllipsoid(scaledSpacePointShrunk, height)).toEqual(false); + }); + it('reports not visible when point is directly behind ellipsoid', function() { var ellipsoid = Ellipsoid.WGS84; var occluder = new EllipsoidalOccluder(ellipsoid); @@ -178,6 +206,19 @@ describe('Core/EllipsoidalOccluder', function() { expect(foundOneNearZero).toBe(true); }); + + it('computes a point under the ellipsoid with computeHorizonCullingPointPossiblyUnderEllipsoid', function() { + var ellipsoid = new Ellipsoid(12345.0, 4567.0, 8910.0); + var ellipsoidalOccluder = new EllipsoidalOccluder(ellipsoid); + var positions = [new Cartesian3(12344.0, 0.0, 0.0)]; + var directionToPoint = new Cartesian3(1.0, 0.0, 0.0); + + var result = ellipsoidalOccluder.computeHorizonCullingPointPossiblyUnderEllipsoid(directionToPoint, positions, -1.0); + + expect(result.x).toEqualEpsilon(1.0, CesiumMath.EPSILON14); + expect(result.y).toEqualEpsilon(0.0, CesiumMath.EPSILON14); + expect(result.z).toEqualEpsilon(0.0, CesiumMath.EPSILON14); + }); }); describe('computeHorizonCullingPointFromVertices', function() { @@ -243,6 +284,20 @@ describe('Core/EllipsoidalOccluder', function() { expect(result1.y).toEqualEpsilon(result2.y, CesiumMath.EPSILON14); expect(result1.z).toEqualEpsilon(result2.z, CesiumMath.EPSILON14); }); + + it('computes a point under the ellipsoid with computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid', function() { + var ellipsoid = new Ellipsoid(12345.0, 4567.0, 8910.0); + var ellipsoidalOccluder = new EllipsoidalOccluder(ellipsoid); + var vertices = [12344.0, 0.0, 0.0]; + var directionToPoint = new Cartesian3(1.0, 0.0, 0.0); + var center = Cartesian3.ZERO; + + var result = ellipsoidalOccluder.computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid(directionToPoint, vertices, 3, center, -1.0); + + expect(result.x).toEqualEpsilon(1.0, CesiumMath.EPSILON14); + expect(result.y).toEqualEpsilon(0.0, CesiumMath.EPSILON14); + expect(result.z).toEqualEpsilon(0.0, CesiumMath.EPSILON14); + }); }); describe('computeHorizonCullingPointFromRectangle', function() {