diff --git a/Apps/Sandcastle/gallery/3D Tiles Vertical Exaggeration.html b/Apps/Sandcastle/gallery/3D Tiles Vertical Exaggeration.html new file mode 100644 index 000000000000..ed5b9637b2dc --- /dev/null +++ b/Apps/Sandcastle/gallery/3D Tiles Vertical Exaggeration.html @@ -0,0 +1,154 @@ + + +
+ + + + + +Exaggeration | ++ + + | +
Relative Height | ++ + + | +
SCENE3D
.
+ */
+ czm_eyeEllipsoidNormalEC: new AutomaticUniform({
+ size: 1,
+ datatype: WebGLConstants.FLOAT_VEC3,
+ getValue: function (uniformState) {
+ return uniformState.eyeEllipsoidNormalEC;
+ },
+ }),
+
+ /**
+ * An automatic GLSL uniform containing the ellipsoid radii of curvature at the camera position.
+ * The .x component is the prime vertical radius, .y is the meridional.
+ * This uniform is only valid when the {@link SceneMode} is SCENE3D
.
+ */
+ czm_eyeEllipsoidCurvature: new AutomaticUniform({
+ size: 1,
+ datatype: WebGLConstants.FLOAT_VEC2,
+ getValue: function (uniformState) {
+ return uniformState.eyeEllipsoidCurvature;
+ },
+ }),
+
+ /**
+ * An automatic GLSL uniform containing the transform from model coordinates
+ * to an east-north-up coordinate system centered at the position on the
+ * ellipsoid below the camera.
+ * This uniform is only valid when the {@link SceneMode} is SCENE3D
.
+ */
+ czm_modelToEnu: new AutomaticUniform({
+ size: 1,
+ datatype: WebGLConstants.FLOAT_MAT4,
+ getValue: function (uniformState) {
+ return uniformState.modelToEnu;
+ },
+ }),
+
+ /**
+ * An automatic GLSL uniform containing the the inverse of
+ * {@link AutomaticUniforms.czm_modelToEnu}.
+ * This uniform is only valid when the {@link SceneMode} is SCENE3D
.
+ */
+ czm_enuToModel: new AutomaticUniform({
+ size: 1,
+ datatype: WebGLConstants.FLOAT_MAT4,
+ getValue: function (uniformState) {
+ return uniformState.enuToModel;
+ },
+ }),
+
/**
* An automatic GLSL uniform containing the near distance (x
) and the far distance (y
)
* of the frustum defined by the camera. This is the largest possible frustum, not an individual
diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js
index ffe2b972ecaa..41e2e821b0aa 100644
--- a/packages/engine/Source/Renderer/UniformState.js
+++ b/packages/engine/Source/Renderer/UniformState.js
@@ -144,6 +144,10 @@ function UniformState() {
this._frustum2DWidth = 0.0;
this._eyeHeight = 0.0;
this._eyeHeight2D = new Cartesian2();
+ this._eyeEllipsoidNormalEC = new Cartesian3();
+ this._eyeEllipsoidCurvature = new Cartesian2();
+ this._modelToEnu = new Matrix4();
+ this._enuToModel = new Matrix4();
this._pixelRatio = 1.0;
this._orthographicIn3D = false;
this._backgroundColor = new Color();
@@ -696,6 +700,52 @@ Object.defineProperties(UniformState.prototype, {
},
},
+ /**
+ * The ellipsoid surface normal at the camera position, in model coordinates.
+ * @memberof UniformState.prototype
+ * @type {Cartesian3}
+ */
+ eyeEllipsoidNormalEC: {
+ get: function () {
+ return this._eyeEllipsoidNormalEC;
+ },
+ },
+
+ /**
+ * The ellipsoid radii of curvature at the camera position.
+ * The .x component is the prime vertical radius, .y is the meridional.
+ * @memberof UniformState.prototype
+ * @type {Cartesian2}
+ */
+ eyeEllipsoidCurvature: {
+ get: function () {
+ return this._eyeEllipsoidCurvature;
+ },
+ },
+
+ /**
+ * A transform from model coordinates to an east-north-up coordinate system
+ * centered at the position on the ellipsoid below the camera
+ * @memberof UniformState.prototype
+ * @type {Matrix4}
+ */
+ modelToEnu: {
+ get: function () {
+ return this._modelToEnu;
+ },
+ },
+
+ /**
+ * The inverse of {@link UniformState.prototype.modelToEnu}
+ * @memberof UniformState.prototype
+ * @type {Matrix4}
+ */
+ enuToModel: {
+ get: function () {
+ return this._enuToModel;
+ },
+ },
+
/**
* The sun position in 3D world coordinates at the current scene time.
* @memberof UniformState.prototype
@@ -1068,20 +1118,88 @@ function setInfiniteProjection(uniformState, matrix) {
uniformState._modelViewInfiniteProjectionDirty = true;
}
+const surfacePositionScratch = new Cartesian3();
+const enuTransformScratch = new Matrix4();
+
function setCamera(uniformState, camera) {
Cartesian3.clone(camera.positionWC, uniformState._cameraPosition);
Cartesian3.clone(camera.directionWC, uniformState._cameraDirection);
Cartesian3.clone(camera.rightWC, uniformState._cameraRight);
Cartesian3.clone(camera.upWC, uniformState._cameraUp);
+ const ellipsoid = uniformState._ellipsoid;
+ let surfacePosition;
+
const positionCartographic = camera.positionCartographic;
if (!defined(positionCartographic)) {
- uniformState._eyeHeight = -uniformState._ellipsoid.maximumRadius;
+ uniformState._eyeHeight = -ellipsoid.maximumRadius;
+ if (Cartesian3.magnitude(camera.positionWC) > 0.0) {
+ uniformState._eyeEllipsoidNormalEC = Cartesian3.normalize(
+ camera.positionWC,
+ uniformState._eyeEllipsoidNormalEC
+ );
+ }
+ surfacePosition = ellipsoid.scaleToGeodeticSurface(
+ camera.positionWC,
+ surfacePositionScratch
+ );
} else {
uniformState._eyeHeight = positionCartographic.height;
+ uniformState._eyeEllipsoidNormalEC = ellipsoid.geodeticSurfaceNormalCartographic(
+ positionCartographic,
+ uniformState._eyeEllipsoidNormalEC
+ );
+ surfacePosition = Cartesian3.fromRadians(
+ positionCartographic.longitude,
+ positionCartographic.latitude,
+ 0.0,
+ ellipsoid,
+ surfacePositionScratch
+ );
}
uniformState._encodedCameraPositionMCDirty = true;
+
+ if (!defined(surfacePosition)) {
+ return;
+ }
+
+ uniformState._eyeEllipsoidNormalEC = Matrix3.multiplyByVector(
+ uniformState._viewRotation,
+ uniformState._eyeEllipsoidNormalEC,
+ uniformState._eyeEllipsoidNormalEC
+ );
+
+ const enuToWorld = Transforms.eastNorthUpToFixedFrame(
+ surfacePosition,
+ ellipsoid,
+ enuTransformScratch
+ );
+ uniformState._enuToModel = Matrix4.multiplyTransformation(
+ uniformState.inverseModel,
+ enuToWorld,
+ uniformState._enuToModel
+ );
+ uniformState._modelToEnu = Matrix4.inverseTransformation(
+ uniformState._enuToModel,
+ uniformState._modelToEnu
+ );
+
+ if (
+ !CesiumMath.equalsEpsilon(
+ ellipsoid._radii.x,
+ ellipsoid._radii.y,
+ CesiumMath.EPSILON15
+ )
+ ) {
+ // Ellipsoid curvature calculations assume radii.x === radii.y as is true for WGS84
+ return;
+ }
+
+ uniformState._eyeEllipsoidCurvature = ellipsoid.getLocalCurvature(
+ surfacePosition,
+ uniformState._eyeEllipsoidCurvature
+ );
}
let transformMatrix = new Matrix3();
diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js
index 86efbeec1cf9..7edd883817d8 100644
--- a/packages/engine/Source/Scene/Cesium3DTile.js
+++ b/packages/engine/Source/Scene/Cesium3DTile.js
@@ -43,6 +43,7 @@ import TileBoundingS2Cell from "./TileBoundingS2Cell.js";
import TileBoundingSphere from "./TileBoundingSphere.js";
import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js";
import Pass from "../Renderer/Pass.js";
+import VerticalExaggeration from "../Core/VerticalExaggeration.js";
/**
* A tile in a {@link Cesium3DTileset}. When a tile is first created, its content is not loaded;
@@ -119,6 +120,9 @@ function Cesium3DTile(tileset, baseResource, header, parent) {
*/
this.metadata = findTileMetadata(tileset, header);
+ this._verticalExaggeration = 1.0;
+ this._verticalExaggerationRelativeHeight = 0.0;
+
// Important: tile metadata must be parsed before this line so that the
// metadata semantics TILE_BOUNDING_BOX, TILE_BOUNDING_REGION, or TILE_BOUNDING_SPHERE
// can override header.boundingVolume (if necessary)
@@ -1018,7 +1022,7 @@ Cesium3DTile.prototype.updateVisibility = function (frameState) {
const parentVisibilityPlaneMask = defined(parent)
? parent._visibilityPlaneMask
: CullingVolume.MASK_INDETERMINATE;
- this.updateTransform(parentTransform);
+ this.updateTransform(parentTransform, frameState);
this._distanceToCamera = this.distanceToTile(frameState);
this._centerZDepth = this.distanceToTileCenter(frameState);
this._screenSpaceError = this.getScreenSpaceError(frameState, false);
@@ -1709,12 +1713,18 @@ function createRegion(region, transform, initialTransform, result) {
);
}
+ const rectangleRegion = Rectangle.unpack(region, 0, scratchRectangle);
+
if (defined(result)) {
+ result.rectangle = Rectangle.clone(rectangleRegion, result.rectangle);
+ result.minimumHeight = region[4];
+ result.maximumHeight = region[5];
+ // The TileBoundingRegion was already constructed with the default
+ // WGS84 ellipsoid, so keep it consistent when updating.
+ result.computeBoundingVolumes(Ellipsoid.WGS84);
return result;
}
- const rectangleRegion = Rectangle.unpack(region, 0, scratchRectangle);
-
return new TileBoundingRegion({
rectangle: rectangleRegion,
minimumHeight: region[4],
@@ -1793,26 +1803,117 @@ Cesium3DTile.prototype.createBoundingVolume = function (
const { box, region, sphere } = boundingVolumeHeader;
if (defined(box)) {
- return createBox(box, transform, result);
+ const tileOrientedBoundingBox = createBox(box, transform, result);
+ if (this._verticalExaggeration !== 1.0) {
+ exaggerateBoundingBox(
+ tileOrientedBoundingBox,
+ this._verticalExaggeration,
+ this._verticalExaggerationRelativeHeight
+ );
+ }
+ return tileOrientedBoundingBox;
}
if (defined(region)) {
- return createRegion(region, transform, this._initialTransform, result);
+ const tileBoundingVolume = createRegion(
+ region,
+ transform,
+ this._initialTransform,
+ result
+ );
+ if (this._verticalExaggeration === 1.0) {
+ return tileBoundingVolume;
+ }
+ if (tileBoundingVolume instanceof TileOrientedBoundingBox) {
+ exaggerateBoundingBox(
+ tileBoundingVolume,
+ this._verticalExaggeration,
+ this._verticalExaggerationRelativeHeight
+ );
+ } else {
+ tileBoundingVolume.minimumHeight = VerticalExaggeration.getHeight(
+ tileBoundingVolume.minimumHeight,
+ this._verticalExaggeration,
+ this._verticalExaggerationRelativeHeight
+ );
+ tileBoundingVolume.maximumHeight = VerticalExaggeration.getHeight(
+ tileBoundingVolume.maximumHeight,
+ this._verticalExaggeration,
+ this._verticalExaggerationRelativeHeight
+ );
+ tileBoundingVolume.computeBoundingVolumes(Ellipsoid.WGS84);
+ }
+ return tileBoundingVolume;
}
if (defined(sphere)) {
- return createSphere(sphere, transform, result);
+ const tileBoundingSphere = createSphere(sphere, transform, result);
+ if (this._verticalExaggeration !== 1.0) {
+ const exaggeratedCenter = VerticalExaggeration.getPosition(
+ tileBoundingSphere.center,
+ Ellipsoid.WGS84,
+ this._verticalExaggeration,
+ this._verticalExaggerationRelativeHeight,
+ scratchCenter
+ );
+ const exaggeratedRadius =
+ tileBoundingSphere.radius * this._verticalExaggeration;
+ tileBoundingSphere.update(exaggeratedCenter, exaggeratedRadius);
+ }
+ return tileBoundingSphere;
}
throw new RuntimeError(
"boundingVolume must contain a sphere, region, or box"
);
};
+const scratchExaggeratedCorners = Cartesian3.unpackArray(
+ new Array(8 * 3).fill(0)
+);
+
+/**
+ * Exaggerates the bounding box of a tile based on the provided exaggeration factors.
+ *
+ * @private
+ * @param {TileOrientedBoundingBox} tileOrientedBoundingBox - The oriented bounding box of the tile.
+ * @param {number} exaggeration - The exaggeration factor to apply to the tile's bounding box.
+ * @param {number} exaggerationRelativeHeight - The height relative to which exaggeration will be applied.
+ */
+function exaggerateBoundingBox(
+ tileOrientedBoundingBox,
+ exaggeration,
+ exaggerationRelativeHeight
+) {
+ const exaggeratedCorners = tileOrientedBoundingBox.boundingVolume
+ .computeCorners(scratchExaggeratedCorners)
+ .map((corner) =>
+ VerticalExaggeration.getPosition(
+ corner,
+ Ellipsoid.WGS84,
+ exaggeration,
+ exaggerationRelativeHeight,
+ corner
+ )
+ );
+ const exaggeratedBox = OrientedBoundingBox.fromPoints(
+ exaggeratedCorners,
+ scratchOrientedBoundingBox
+ );
+ tileOrientedBoundingBox.update(
+ exaggeratedBox.center,
+ exaggeratedBox.halfAxes
+ );
+}
+
/**
* Update the tile's transform. The transform is applied to the tile's bounding volumes.
*
* @private
* @param {Matrix4} parentTransform
+ * @param {FrameState} [frameState]
*/
-Cesium3DTile.prototype.updateTransform = function (parentTransform) {
+Cesium3DTile.prototype.updateTransform = function (
+ parentTransform,
+ frameState
+) {
parentTransform = defaultValue(parentTransform, Matrix4.IDENTITY);
const computedTransform = Matrix4.multiplyTransformation(
parentTransform,
@@ -1823,12 +1924,23 @@ Cesium3DTile.prototype.updateTransform = function (parentTransform) {
computedTransform,
this.computedTransform
);
+ const exaggerationChanged =
+ defined(frameState) &&
+ (this._verticalExaggeration !== frameState.verticalExaggeration ||
+ this._verticalExaggerationRelativeHeight !==
+ frameState.verticalExaggerationRelativeHeight);
- if (!transformChanged) {
+ if (!transformChanged && !exaggerationChanged) {
return;
}
-
- Matrix4.clone(computedTransform, this.computedTransform);
+ if (transformChanged) {
+ Matrix4.clone(computedTransform, this.computedTransform);
+ }
+ if (exaggerationChanged) {
+ this._verticalExaggeration = frameState.verticalExaggeration;
+ this._verticalExaggerationRelativeHeight =
+ frameState.verticalExaggerationRelativeHeight;
+ }
// Update the bounding volumes
const header = this._header;
diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js
index 14b50cefe1d7..1ed60a2c1e1e 100644
--- a/packages/engine/Source/Scene/FrameState.js
+++ b/packages/engine/Source/Scene/FrameState.js
@@ -275,18 +275,18 @@ function FrameState(context, creditDisplay, jobScheduler) {
};
/**
- * A scalar used to exaggerate the terrain.
+ * A scalar used to vertically exaggerate the scene
* @type {number}
* @default 1.0
*/
- this.terrainExaggeration = 1.0;
+ this.verticalExaggeration = 1.0;
/**
- * The height relative to which terrain is exaggerated.
+ * The height relative to which the scene is vertically exaggerated.
* @type {number}
* @default 0.0
*/
- this.terrainExaggerationRelativeHeight = 0.0;
+ this.verticalExaggerationRelativeHeight = 0.0;
/**
* @typedef FrameState.ShadowState
diff --git a/packages/engine/Source/Scene/Globe.js b/packages/engine/Source/Scene/Globe.js
index 35dd3ea498cf..fac2e6f3bee8 100644
--- a/packages/engine/Source/Scene/Globe.js
+++ b/packages/engine/Source/Scene/Globe.js
@@ -28,6 +28,7 @@ import ImageryLayerCollection from "./ImageryLayerCollection.js";
import QuadtreePrimitive from "./QuadtreePrimitive.js";
import SceneMode from "./SceneMode.js";
import ShadowMode from "./ShadowMode.js";
+import deprecationWarning from "../Core/deprecationWarning.js";
/**
* The globe rendered in the scene, including its terrain ({@link Globe#terrainProvider})
@@ -340,25 +341,9 @@ function Globe(ellipsoid) {
*/
this.atmosphereBrightnessShift = 0.0;
- /**
- * A scalar used to exaggerate the terrain. Defaults to 1.0
(no exaggeration).
- * A value of 2.0
scales the terrain by 2x.
- * A value of 0.0
makes the terrain completely flat.
- * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
- * @type {number}
- * @default 1.0
- */
- this.terrainExaggeration = 1.0;
-
- /**
- * The height from which terrain is exaggerated. Defaults to 0.0
(scaled relative to ellipsoid surface).
- * Terrain that is above this height will scale upwards and terrain that is below this height will scale downwards.
- * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
- * If {@link Globe#terrainExaggeration} is 1.0
this value will have no effect.
- * @type {number}
- * @default 0.0
- */
- this.terrainExaggerationRelativeHeight = 0.0;
+ this._terrainExaggerationChanged = false;
+ this._terrainExaggeration = 1.0;
+ this._terrainExaggerationRelativeHeight = 0.0;
/**
* Whether to show terrain skirts. Terrain skirts are geometry extending downwards from a tile's edges used to hide seams between neighboring tiles.
@@ -538,6 +523,68 @@ Object.defineProperties(Globe.prototype, {
return this._terrainProviderChanged;
},
},
+ /**
+ * A scalar used to exaggerate the terrain. Defaults to 1.0
(no exaggeration).
+ * A value of 2.0
scales the terrain by 2x.
+ * A value of 0.0
makes the terrain completely flat.
+ * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
+ *
+ * @memberof Globe.prototype
+ * @type {number}
+ * @default 1.0
+ *
+ * @deprecated
+ */
+ terrainExaggeration: {
+ get: function () {
+ deprecationWarning(
+ "Globe.terrainExaggeration",
+ "Globe.terrainExaggeration was deprecated in CesiumJS 1.113. It will be removed in CesiumJS 1.116. Use Scene.verticalExaggeration instead."
+ );
+ return this._terrainExaggeration;
+ },
+ set: function (value) {
+ deprecationWarning(
+ "Globe.terrainExaggeration",
+ "Globe.terrainExaggeration was deprecated in CesiumJS 1.113. It will be removed in CesiumJS 1.116. Use Scene.verticalExaggeration instead."
+ );
+ if (value !== this._terrainExaggeration) {
+ this._terrainExaggeration = value;
+ this._terrainExaggerationChanged = true;
+ }
+ },
+ },
+ /**
+ * The height from which terrain is exaggerated. Defaults to 0.0
(scaled relative to ellipsoid surface).
+ * Terrain that is above this height will scale upwards and terrain that is below this height will scale downwards.
+ * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
+ * If {@link Globe#terrainExaggeration} is 1.0
this value will have no effect.
+ *
+ * @memberof Globe.prototype
+ * @type {number}
+ * @default 0.0
+ *
+ * @deprecated
+ */
+ terrainExaggerationRelativeHeight: {
+ get: function () {
+ deprecationWarning(
+ "Globe.terrainExaggerationRelativeHeight",
+ "Globe.terrainExaggerationRelativeHeight was deprecated in CesiumJS 1.113. It will be removed in CesiumJS 1.116. Use Scene.verticalExaggerationRelativeHeight instead."
+ );
+ return this._terrainExaggerationRelativeHeight;
+ },
+ set: function (value) {
+ deprecationWarning(
+ "Globe.terrainExaggerationRelativeHeight",
+ "Globe.terrainExaggerationRelativeHeight was deprecated in CesiumJS 1.113. It will be removed in CesiumJS 1.116. Use Scene.verticalExaggerationRelativeHeight instead."
+ );
+ if (value !== this._terrainExaggerationRelativeHeight) {
+ this._terrainExaggerationRelativeHeight = value;
+ this._terrainExaggerationChanged = true;
+ }
+ },
+ },
/**
* Gets an event that's raised when the length of the tile load queue has changed since the last render frame. When the load queue is empty,
* all terrain and imagery for the current view have been loaded. The event passes the new length of the tile load queue.
diff --git a/packages/engine/Source/Scene/GlobeSurfaceTile.js b/packages/engine/Source/Scene/GlobeSurfaceTile.js
index 8ac18d1b6904..c1c7ab20dd52 100644
--- a/packages/engine/Source/Scene/GlobeSurfaceTile.js
+++ b/packages/engine/Source/Scene/GlobeSurfaceTile.js
@@ -463,9 +463,9 @@ GlobeSurfaceTile.prototype.updateExaggeration = function (
}
// Check the tile's terrain encoding to see if it has been exaggerated yet
- const exaggeration = frameState.terrainExaggeration;
+ const exaggeration = frameState.verticalExaggeration;
const exaggerationRelativeHeight =
- frameState.terrainExaggerationRelativeHeight;
+ frameState.verticalExaggerationRelativeHeight;
const hasExaggerationScale = exaggeration !== 1.0;
const encoding = mesh.encoding;
@@ -790,9 +790,9 @@ function transform(surfaceTile, frameState, terrainProvider, x, y, level) {
createMeshOptions.x = x;
createMeshOptions.y = y;
createMeshOptions.level = level;
- createMeshOptions.exaggeration = frameState.terrainExaggeration;
+ createMeshOptions.exaggeration = frameState.verticalExaggeration;
createMeshOptions.exaggerationRelativeHeight =
- frameState.terrainExaggerationRelativeHeight;
+ frameState.verticalExaggerationRelativeHeight;
createMeshOptions.throttle = true;
const terrainData = surfaceTile.terrainData;
diff --git a/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js b/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js
index bcf38cd60780..e32b797efc48 100644
--- a/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js
+++ b/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js
@@ -25,7 +25,7 @@ import OrthographicFrustum from "../Core/OrthographicFrustum.js";
import PrimitiveType from "../Core/PrimitiveType.js";
import Rectangle from "../Core/Rectangle.js";
import SphereOutlineGeometry from "../Core/SphereOutlineGeometry.js";
-import TerrainExaggeration from "../Core/TerrainExaggeration.js";
+import VerticalExaggeration from "../Core/VerticalExaggeration.js";
import TerrainQuantization from "../Core/TerrainQuantization.js";
import Visibility from "../Core/Visibility.js";
import WebMercatorProjection from "../Core/WebMercatorProjection.js";
@@ -179,8 +179,8 @@ function GlobeSurfaceTileProvider(options) {
this._hasLoadedTilesThisFrame = false;
this._hasFillTilesThisFrame = false;
- this._oldTerrainExaggeration = undefined;
- this._oldTerrainExaggerationRelativeHeight = undefined;
+ this._oldVerticalExaggeration = undefined;
+ this._oldVerticalExaggerationRelativeHeight = undefined;
}
Object.defineProperties(GlobeSurfaceTileProvider.prototype, {
@@ -448,7 +448,7 @@ GlobeSurfaceTileProvider.prototype.endUpdate = function (frameState) {
);
}
- // When terrain exaggeration changes, all of the loaded tiles need to generate
+ // When vertical exaggeration changes, all of the loaded tiles need to generate
// geodetic surface normals so they can scale properly when rendered.
// When exaggeration is reset, geodetic surface normals are removed to decrease
// memory usage. Some tiles might have been constructed with the correct
@@ -460,16 +460,16 @@ GlobeSurfaceTileProvider.prototype.endUpdate = function (frameState) {
// exaggeration changes.
const quadtree = this.quadtree;
- const exaggeration = frameState.terrainExaggeration;
+ const exaggeration = frameState.verticalExaggeration;
const exaggerationRelativeHeight =
- frameState.terrainExaggerationRelativeHeight;
+ frameState.verticalExaggerationRelativeHeight;
const exaggerationChanged =
- this._oldTerrainExaggeration !== exaggeration ||
- this._oldTerrainExaggerationRelativeHeight !== exaggerationRelativeHeight;
+ this._oldVerticalExaggeration !== exaggeration ||
+ this._oldVerticalExaggerationRelativeHeight !== exaggerationRelativeHeight;
// Keep track of the next time there is a change in exaggeration
- this._oldTerrainExaggeration = exaggeration;
- this._oldTerrainExaggerationRelativeHeight = exaggerationRelativeHeight;
+ this._oldVerticalExaggeration = exaggeration;
+ this._oldVerticalExaggerationRelativeHeight = exaggerationRelativeHeight;
if (exaggerationChanged) {
quadtree.forEachLoadedTile(function (tile) {
@@ -1258,18 +1258,18 @@ function updateTileBoundingRegion(tile, tileProvider, frameState) {
// Update bounding regions from the min and max heights
if (sourceTile !== undefined) {
- const exaggeration = frameState.terrainExaggeration;
+ const exaggeration = frameState.verticalExaggeration;
const exaggerationRelativeHeight =
- frameState.terrainExaggerationRelativeHeight;
+ frameState.verticalExaggerationRelativeHeight;
const hasExaggeration = exaggeration !== 1.0;
if (hasExaggeration) {
hasBoundingVolumesFromMesh = false;
- tileBoundingRegion.minimumHeight = TerrainExaggeration.getHeight(
+ tileBoundingRegion.minimumHeight = VerticalExaggeration.getHeight(
tileBoundingRegion.minimumHeight,
exaggeration,
exaggerationRelativeHeight
);
- tileBoundingRegion.maximumHeight = TerrainExaggeration.getHeight(
+ tileBoundingRegion.maximumHeight = VerticalExaggeration.getHeight(
tileBoundingRegion.maximumHeight,
exaggeration,
exaggerationRelativeHeight
@@ -1625,8 +1625,8 @@ function createTileUniformMap(frameState, globeSurfaceTileProvider) {
u_center3D: function () {
return this.properties.center3D;
},
- u_terrainExaggerationAndRelativeHeight: function () {
- return this.properties.terrainExaggerationAndRelativeHeight;
+ u_verticalExaggerationAndRelativeHeight: function () {
+ return this.properties.verticalExaggerationAndRelativeHeight;
},
u_tileRectangle: function () {
return this.properties.tileRectangle;
@@ -1808,7 +1808,7 @@ function createTileUniformMap(frameState, globeSurfaceTileProvider) {
modifiedModelView: new Matrix4(),
tileRectangle: new Cartesian4(),
- terrainExaggerationAndRelativeHeight: new Cartesian2(1.0, 0.0),
+ verticalExaggerationAndRelativeHeight: new Cartesian2(1.0, 0.0),
dayTextures: [],
dayTextureTranslationAndScale: [],
@@ -2174,9 +2174,9 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) {
const encoding = mesh.encoding;
const tileBoundingRegion = surfaceTile.tileBoundingRegion;
- const exaggeration = frameState.terrainExaggeration;
+ const exaggeration = frameState.verticalExaggeration;
const exaggerationRelativeHeight =
- frameState.terrainExaggerationRelativeHeight;
+ frameState.verticalExaggerationRelativeHeight;
const hasExaggeration = exaggeration !== 1.0;
const hasGeodeticSurfaceNormals = encoding.hasGeodeticSurfaceNormals;
@@ -2431,8 +2431,8 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) {
);
}
- uniformMapProperties.terrainExaggerationAndRelativeHeight.x = exaggeration;
- uniformMapProperties.terrainExaggerationAndRelativeHeight.y = exaggerationRelativeHeight;
+ uniformMapProperties.verticalExaggerationAndRelativeHeight.x = exaggeration;
+ uniformMapProperties.verticalExaggerationAndRelativeHeight.y = exaggerationRelativeHeight;
uniformMapProperties.center3D = mesh.center;
Cartesian3.clone(rtc, uniformMapProperties.rtc);
diff --git a/packages/engine/Source/Scene/GroundPrimitive.js b/packages/engine/Source/Scene/GroundPrimitive.js
index 89e3cd5c0ad1..f67249322b8a 100644
--- a/packages/engine/Source/Scene/GroundPrimitive.js
+++ b/packages/engine/Source/Scene/GroundPrimitive.js
@@ -10,7 +10,7 @@ import DeveloperError from "../Core/DeveloperError.js";
import GeometryInstance from "../Core/GeometryInstance.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
import Rectangle from "../Core/Rectangle.js";
-import TerrainExaggeration from "../Core/TerrainExaggeration.js";
+import VerticalExaggeration from "../Core/VerticalExaggeration.js";
import ClassificationPrimitive from "./ClassificationPrimitive.js";
import ClassificationType from "./ClassificationType.js";
import PerInstanceColorAppearance from "./PerInstanceColorAppearance.js";
@@ -763,15 +763,15 @@ GroundPrimitive.prototype.update = function (frameState) {
// Now compute the min/max heights for the primitive
setMinMaxTerrainHeights(this, rectangle, ellipsoid);
- const exaggeration = frameState.terrainExaggeration;
+ const exaggeration = frameState.verticalExaggeration;
const exaggerationRelativeHeight =
- frameState.terrainExaggerationRelativeHeight;
- this._minHeight = TerrainExaggeration.getHeight(
+ frameState.verticalExaggerationRelativeHeight;
+ this._minHeight = VerticalExaggeration.getHeight(
this._minTerrainHeight,
exaggeration,
exaggerationRelativeHeight
);
- this._maxHeight = TerrainExaggeration.getHeight(
+ this._maxHeight = VerticalExaggeration.getHeight(
this._maxTerrainHeight,
exaggeration,
exaggerationRelativeHeight
diff --git a/packages/engine/Source/Scene/Megatexture.js b/packages/engine/Source/Scene/Megatexture.js
index 759563bd9cfb..5173d98b3911 100644
--- a/packages/engine/Source/Scene/Megatexture.js
+++ b/packages/engine/Source/Scene/Megatexture.js
@@ -366,7 +366,7 @@ Megatexture.getApproximateTextureMemoryByteLength = function (
const voxelCountTotal =
tileCount * dimensions.x * dimensions.y * dimensions.z;
- const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.z));
+ const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.x));
const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);
const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;
const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;
diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js
index 57c174ce725e..75d24a7a7761 100644
--- a/packages/engine/Source/Scene/Model/Model.js
+++ b/packages/engine/Source/Scene/Model/Model.js
@@ -333,6 +333,8 @@ function Model(options) {
this._heightDirty = this._heightReference !== HeightReference.NONE;
this._removeUpdateHeightCallback = undefined;
+ this._verticalExaggerationOn = false;
+
this._clampedModelMatrix = undefined; // For use with height reference
const scene = options.scene;
@@ -1789,6 +1791,7 @@ Model.prototype.update = function (frameState) {
updateSkipLevelOfDetail(this, frameState);
updateClippingPlanes(this, frameState);
updateSceneMode(this, frameState);
+ updateVerticalExaggeration(this, frameState);
this._defaultTexture = frameState.context.defaultTexture;
@@ -1983,6 +1986,14 @@ function updateSceneMode(model, frameState) {
}
}
+function updateVerticalExaggeration(model, frameState) {
+ const verticalExaggerationNeeded = frameState.verticalExaggeration !== 1.0;
+ if (model._verticalExaggerationOn !== verticalExaggerationNeeded) {
+ model.resetDrawCommands();
+ model._verticalExaggerationOn = verticalExaggerationNeeded;
+ }
+}
+
function buildDrawCommands(model, frameState) {
if (!model._drawCommandsBuilt) {
model.destroyPipelineResources();
diff --git a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js
index 5593643409ea..ed82ca255cf4 100644
--- a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js
+++ b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js
@@ -24,6 +24,7 @@ import PrimitiveStatisticsPipelineStage from "./PrimitiveStatisticsPipelineStage
import SceneMode2DPipelineStage from "./SceneMode2DPipelineStage.js";
import SelectedFeatureIdPipelineStage from "./SelectedFeatureIdPipelineStage.js";
import SkinningPipelineStage from "./SkinningPipelineStage.js";
+import VerticalExaggerationPipelineStage from "./VerticalExaggerationPipelineStage.js";
import WireframePipelineStage from "./WireframePipelineStage.js";
/**
@@ -198,6 +199,7 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) {
const mode = frameState.mode;
const use2D =
mode !== SceneMode.SCENE3D && !frameState.scene3DOnly && model._projectTo2D;
+ const exaggerateTerrain = frameState.verticalExaggeration !== 1.0;
const hasMorphTargets =
defined(primitive.morphTargets) && primitive.morphTargets.length > 0;
@@ -281,6 +283,10 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) {
pipelineStages.push(CPUStylingPipelineStage);
}
+ if (exaggerateTerrain) {
+ pipelineStages.push(VerticalExaggerationPipelineStage);
+ }
+
if (hasCustomShader) {
pipelineStages.push(CustomShaderPipelineStage);
}
diff --git a/packages/engine/Source/Scene/Model/VerticalExaggerationPipelineStage.js b/packages/engine/Source/Scene/Model/VerticalExaggerationPipelineStage.js
new file mode 100644
index 000000000000..2a0a059e3123
--- /dev/null
+++ b/packages/engine/Source/Scene/Model/VerticalExaggerationPipelineStage.js
@@ -0,0 +1,60 @@
+import Cartesian2 from "../../Core/Cartesian2.js";
+import ShaderDestination from "../../Renderer/ShaderDestination.js";
+import VerticalExaggerationStageVS from "../../Shaders/Model/VerticalExaggerationStageVS.js";
+
+/**
+ * The custom shader pipeline stage takes GLSL callbacks from the
+ * {@link CustomShader} and inserts them into the overall shader code for the
+ * {@link Model}. The input to the callback is a struct with many
+ * properties that depend on the attributes of the primitive. This shader code
+ * is automatically generated by this stage.
+ *
+ * @namespace VerticalExaggerationPipelineStage
+ *
+ * @private
+ */
+const VerticalExaggerationPipelineStage = {
+ name: "VerticalExaggerationPipelineStage", // Helps with debugging
+};
+
+const scratchExaggerationUniform = new Cartesian2();
+
+/**
+ * Add vertical exaggeration to a shader
+ *
+ * @param {PrimitiveRenderResources} renderResources The render resources for the primitive
+ * @param {ModelComponents.Primitive} primitive The primitive to be rendered
+ * @param {FrameState} frameState The frame state.
+ * @private
+ */
+VerticalExaggerationPipelineStage.process = function (
+ renderResources,
+ primitive,
+ frameState
+) {
+ const { shaderBuilder, uniformMap } = renderResources;
+
+ shaderBuilder.addVertexLines(VerticalExaggerationStageVS);
+
+ shaderBuilder.addDefine(
+ "HAS_VERTICAL_EXAGGERATION",
+ undefined,
+ ShaderDestination.VERTEX
+ );
+
+ shaderBuilder.addUniform(
+ "vec2",
+ "u_verticalExaggerationAndRelativeHeight",
+ ShaderDestination.VERTEX
+ );
+
+ uniformMap.u_verticalExaggerationAndRelativeHeight = function () {
+ return Cartesian2.fromElements(
+ frameState.verticalExaggeration,
+ frameState.verticalExaggerationRelativeHeight,
+ scratchExaggerationUniform
+ );
+ };
+};
+
+export default VerticalExaggerationPipelineStage;
diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js
index 127c6aacfaad..8b3a207ec133 100644
--- a/packages/engine/Source/Scene/Scene.js
+++ b/packages/engine/Source/Scene/Scene.js
@@ -356,6 +356,24 @@ function Scene(options) {
*/
this.nearToFarDistance2D = 1.75e6;
+ /**
+ * The vertical exaggeration of the scene.
+ * When set to 1.0, no exaggeration is applied.
+ *
+ * @type {number}
+ * @default 1.0
+ */
+ this.verticalExaggeration = 1.0;
+
+ /**
+ * The reference height for vertical exaggeration of the scene.
+ * When set to 0.0, the exaggeration is applied relative to the ellipsoid surface.
+ *
+ * @type {number}
+ * @default 0.0
+ */
+ this.verticalExaggerationRelativeHeight = 0.0;
+
/**
* This property is for debugging only; it is not for production use.
* @@ -1880,10 +1898,17 @@ Scene.prototype.updateFrameState = function () { frameState.cameraUnderground = this._cameraUnderground; frameState.globeTranslucencyState = this._globeTranslucencyState; - if (defined(this.globe)) { - frameState.terrainExaggeration = this.globe.terrainExaggeration; - frameState.terrainExaggerationRelativeHeight = this.globe.terrainExaggerationRelativeHeight; + const { globe } = this; + if (defined(globe) && globe._terrainExaggerationChanged) { + // Honor a user-set value for the old deprecated globe.terrainExaggeration. + // This can be removed when Globe.terrainExaggeration is removed. + this.verticalExaggeration = globe._terrainExaggeration; + this.verticalExaggerationRelativeHeight = + globe._terrainExaggerationRelativeHeight; + globe._terrainExaggerationChanged = false; } + frameState.verticalExaggeration = this.verticalExaggeration; + frameState.verticalExaggerationRelativeHeight = this.verticalExaggerationRelativeHeight; if ( defined(this._specularEnvironmentMapAtlas) && diff --git a/packages/engine/Source/Scene/ScreenSpaceCameraController.js b/packages/engine/Source/Scene/ScreenSpaceCameraController.js index 80e05cc5bb3e..b2f64010731a 100644 --- a/packages/engine/Source/Scene/ScreenSpaceCameraController.js +++ b/packages/engine/Source/Scene/ScreenSpaceCameraController.js @@ -17,7 +17,7 @@ import OrthographicFrustum from "../Core/OrthographicFrustum.js"; import Plane from "../Core/Plane.js"; import Quaternion from "../Core/Quaternion.js"; import Ray from "../Core/Ray.js"; -import TerrainExaggeration from "../Core/TerrainExaggeration.js"; +import VerticalExaggeration from "../Core/VerticalExaggeration.js"; import Transforms from "../Core/Transforms.js"; import CameraEventAggregator from "./CameraEventAggregator.js"; import CameraEventType from "./CameraEventType.js"; @@ -2948,9 +2948,7 @@ const scratchPreviousDirection = new Cartesian3(); */ ScreenSpaceCameraController.prototype.update = function () { const scene = this._scene; - const camera = scene.camera; - const globe = scene.globe; - const mode = scene.mode; + const { camera, globe, mode } = scene; if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) { this._globe = undefined; @@ -2962,26 +2960,21 @@ ScreenSpaceCameraController.prototype.update = function () { : scene.mapProjection.ellipsoid; } - const exaggeration = defined(this._globe) - ? this._globe.terrainExaggeration - : 1.0; - const exaggerationRelativeHeight = defined(this._globe) - ? this._globe.terrainExaggerationRelativeHeight - : 0.0; - this._minimumCollisionTerrainHeight = TerrainExaggeration.getHeight( + const { verticalExaggeration, verticalExaggerationRelativeHeight } = scene; + this._minimumCollisionTerrainHeight = VerticalExaggeration.getHeight( this.minimumCollisionTerrainHeight, - exaggeration, - exaggerationRelativeHeight + verticalExaggeration, + verticalExaggerationRelativeHeight ); - this._minimumPickingTerrainHeight = TerrainExaggeration.getHeight( + this._minimumPickingTerrainHeight = VerticalExaggeration.getHeight( this.minimumPickingTerrainHeight, - exaggeration, - exaggerationRelativeHeight + verticalExaggeration, + verticalExaggerationRelativeHeight ); - this._minimumTrackBallHeight = TerrainExaggeration.getHeight( + this._minimumTrackBallHeight = VerticalExaggeration.getHeight( this.minimumTrackBallHeight, - exaggeration, - exaggerationRelativeHeight + verticalExaggeration, + verticalExaggerationRelativeHeight ); this._cameraUnderground = scene.cameraUnderground && defined(this._globe); diff --git a/packages/engine/Source/Scene/TerrainFillMesh.js b/packages/engine/Source/Scene/TerrainFillMesh.js index 20a667b285e2..9d1671493a63 100644 --- a/packages/engine/Source/Scene/TerrainFillMesh.js +++ b/packages/engine/Source/Scene/TerrainFillMesh.js @@ -830,9 +830,9 @@ function createFillMesh(tileProvider, frameState, tile, vertexArraysToDestroy) { const fill = surfaceTile.fill; const rectangle = tile.rectangle; - const exaggeration = frameState.terrainExaggeration; + const exaggeration = frameState.verticalExaggeration; const exaggerationRelativeHeight = - frameState.terrainExaggerationRelativeHeight; + frameState.verticalExaggerationRelativeHeight; const hasExaggeration = exaggeration !== 1.0; const ellipsoid = tile.tilingScheme.ellipsoid; diff --git a/packages/engine/Source/Scene/TileOrientedBoundingBox.js b/packages/engine/Source/Scene/TileOrientedBoundingBox.js index 9737474a5007..40d60451f51f 100644 --- a/packages/engine/Source/Scene/TileOrientedBoundingBox.js +++ b/packages/engine/Source/Scene/TileOrientedBoundingBox.js @@ -106,7 +106,7 @@ Object.defineProperties(TileOrientedBoundingBox.prototype, { * * @memberof TileOrientedBoundingBox.prototype * - * @type {object} + * @type {OrientedBoundingBox} * @readonly */ boundingVolume: { diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index e835b7bb7e7b..a1d2486c9288 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -131,6 +131,38 @@ function VoxelPrimitive(options) { */ this._maxBoundsOld = new Cartesian3(); + /** + * Minimum bounds with vertical exaggeration applied + * + * @type {Cartesian3} + * @private + */ + this._exaggeratedMinBounds = new Cartesian3(); + + /** + * Used to detect if the shape is dirty. + * + * @type {Cartesian3} + * @private + */ + this._exaggeratedMinBoundsOld = new Cartesian3(); + + /** + * Maximum bounds with vertical exaggeration applied + * + * @type {Cartesian3} + * @private + */ + this._exaggeratedMaxBounds = new Cartesian3(); + + /** + * Used to detect if the shape is dirty. + * + * @type {Cartesian3} + * @private + */ + this._exaggeratedMaxBoundsOld = new Cartesian3(); + /** * This member is not known until the provider is ready. * @@ -442,6 +474,7 @@ function initialize(primitive, provider) { // Create the shape object, and update it so it is valid for VoxelTraversal const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType); primitive._shape = new ShapeConstructor(); + updateVerticalExaggeration(primitive); primitive._shapeVisible = updateShapeAndTransforms( primitive, primitive._shape, @@ -964,6 +997,8 @@ VoxelPrimitive.prototype.update = function (frameState) { return; } + updateVerticalExaggeration(this, frameState); + // Check if the shape is dirty before updating it. This needs to happen every // frame because the member variables can be modified externally via the // getters. @@ -1107,6 +1142,32 @@ VoxelPrimitive.prototype.update = function (frameState) { frameState.commandList.push(command); }; +/** + * Update the exaggerated bounds of a primitive to account for vertical exaggeration + * Currently only applies to Ellipsoid shape type + * @param {VoxelPrimitive} primitive + * @param {FrameState} [frameState] + * @private + */ +function updateVerticalExaggeration(primitive, frameState) { + primitive._exaggeratedMinBounds = Cartesian3.clone( + primitive._minBounds, + primitive._exaggeratedMinBounds + ); + primitive._exaggeratedMaxBounds = Cartesian3.clone( + primitive._maxBounds, + primitive._exaggeratedMaxBounds + ); + if (defined(frameState) && primitive.shape === VoxelShapeType.ELLIPSOID) { + const relativeHeight = frameState.verticalExaggerationRelativeHeight; + const exaggeration = frameState.verticalExaggeration; + primitive._exaggeratedMinBounds.z = + (primitive._minBounds.z - relativeHeight) * exaggeration + relativeHeight; + primitive._exaggeratedMaxBounds.z = + (primitive._maxBounds.z - relativeHeight) * exaggeration + relativeHeight; + } +} + /** * Initialize primitive properties that are derived from the voxel provider * @param {VoxelPrimitive} primitive @@ -1203,6 +1264,16 @@ function checkTransformAndBounds(primitive, provider) { updateBound(primitive, "_compoundModelMatrix", "_compoundModelMatrixOld") + updateBound(primitive, "_minBounds", "_minBoundsOld") + updateBound(primitive, "_maxBounds", "_maxBoundsOld") + + updateBound( + primitive, + "_exaggeratedMinBounds", + "_exaggeratedMinBoundsOld" + ) + + updateBound( + primitive, + "_exaggeratedMaxBounds", + "_exaggeratedMaxBoundsOld" + ) + updateBound(primitive, "_minClippingBounds", "_minClippingBoundsOld") + updateBound(primitive, "_maxClippingBounds", "_maxClippingBoundsOld"); return numChanges > 0; @@ -1239,8 +1310,8 @@ function updateBound(primitive, newBoundKey, oldBoundKey) { function updateShapeAndTransforms(primitive, shape, provider) { const visible = shape.update( primitive._compoundModelMatrix, - primitive.minBounds, - primitive.maxBounds, + primitive._exaggeratedMinBounds, + primitive._exaggeratedMaxBounds, primitive.minClippingBounds, primitive.maxClippingBounds ); diff --git a/packages/engine/Source/Shaders/GlobeVS.glsl b/packages/engine/Source/Shaders/GlobeVS.glsl index 65b4d1547ff1..05bd21f37d9b 100644 --- a/packages/engine/Source/Shaders/GlobeVS.glsl +++ b/packages/engine/Source/Shaders/GlobeVS.glsl @@ -11,7 +11,7 @@ in vec3 geodeticSurfaceNormal; #endif #ifdef EXAGGERATION -uniform vec2 u_terrainExaggerationAndRelativeHeight; +uniform vec2 u_verticalExaggerationAndRelativeHeight; #endif uniform vec3 u_center3D; @@ -173,8 +173,8 @@ void main() #endif #if defined(EXAGGERATION) && defined(GEODETIC_SURFACE_NORMALS) - float exaggeration = u_terrainExaggerationAndRelativeHeight.x; - float relativeHeight = u_terrainExaggerationAndRelativeHeight.y; + float exaggeration = u_verticalExaggerationAndRelativeHeight.x; + float relativeHeight = u_verticalExaggerationAndRelativeHeight.y; float newHeight = (height - relativeHeight) * exaggeration + relativeHeight; // stop from going through center of earth diff --git a/packages/engine/Source/Shaders/Model/ModelVS.glsl b/packages/engine/Source/Shaders/Model/ModelVS.glsl index bad132276fdf..e9a4eb5e63ec 100644 --- a/packages/engine/Source/Shaders/Model/ModelVS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelVS.glsl @@ -93,6 +93,10 @@ void main() MetadataStatistics metadataStatistics; metadataStage(metadata, metadataClass, metadataStatistics, attributes); + #ifdef HAS_VERTICAL_EXAGGERATION + verticalExaggerationStage(attributes); + #endif + #ifdef HAS_CUSTOM_VERTEX_SHADER czm_modelVertexOutput vsOutput = defaultVertexOutput(attributes.positionMC); customShaderStage(vsOutput, attributes, featureIds, metadata, metadataClass, metadataStatistics); diff --git a/packages/engine/Source/Shaders/Model/VerticalExaggerationStageVS.glsl b/packages/engine/Source/Shaders/Model/VerticalExaggerationStageVS.glsl new file mode 100644 index 000000000000..c7e4f944d288 --- /dev/null +++ b/packages/engine/Source/Shaders/Model/VerticalExaggerationStageVS.glsl @@ -0,0 +1,39 @@ +void verticalExaggerationStage( + inout ProcessedAttributes attributes +) { + // Compute the distance from the camera to the local center of curvature. + vec4 vertexPositionENU = czm_modelToEnu * vec4(attributes.positionMC, 1.0); + vec2 vertexAzimuth = normalize(vertexPositionENU.xy); + // Curvature = 1 / radius of curvature. + float azimuthalCurvature = dot(vertexAzimuth * vertexAzimuth, czm_eyeEllipsoidCurvature); + float eyeToCenter = 1.0 / azimuthalCurvature + czm_eyeHeight; + + // Compute the approximate ellipsoid normal at the vertex position. + // Uses a circular approximation for the Earth curvature along the geodesic. + vec3 vertexPositionEC = (czm_modelView * vec4(attributes.positionMC, 1.0)).xyz; + vec3 centerToVertex = eyeToCenter * czm_eyeEllipsoidNormalEC + vertexPositionEC; + vec3 vertexNormal = normalize(centerToVertex); + + // Estimate the (sine of the) angle between the camera direction and the vertex normal + float verticalDistance = dot(vertexPositionEC, czm_eyeEllipsoidNormalEC); + float horizontalDistance = length(vertexPositionEC - verticalDistance * czm_eyeEllipsoidNormalEC); + float sinTheta = horizontalDistance / (eyeToCenter + verticalDistance); + bool isSmallAngle = clamp(sinTheta, 0.0, 0.05) == sinTheta; + + // Approximate the change in height above the ellipsoid, from camera to vertex position. + float exactVersine = 1.0 - dot(czm_eyeEllipsoidNormalEC, vertexNormal); + float smallAngleVersine = 0.5 * sinTheta * sinTheta; + float versine = isSmallAngle ? smallAngleVersine : exactVersine; + float dHeight = dot(vertexPositionEC, vertexNormal) - eyeToCenter * versine; + float vertexHeight = czm_eyeHeight + dHeight; + + // Transform the approximate vertex normal to model coordinates. + vec3 vertexNormalMC = (czm_inverseModelView * vec4(vertexNormal, 0.0)).xyz; + vertexNormalMC = normalize(vertexNormalMC); + + // Compute the exaggeration and apply it along the approximate vertex normal. + float stretch = u_verticalExaggerationAndRelativeHeight.x; + float shift = u_verticalExaggerationAndRelativeHeight.y; + float exaggeration = (vertexHeight - shift) * (stretch - 1.0); + attributes.positionMC += exaggeration * vertexNormalMC; +} diff --git a/packages/engine/Specs/Core/EllipsoidSpec.js b/packages/engine/Specs/Core/EllipsoidSpec.js index 356bb5fdbb54..af039cf18fbc 100644 --- a/packages/engine/Specs/Core/EllipsoidSpec.js +++ b/packages/engine/Specs/Core/EllipsoidSpec.js @@ -1,6 +1,11 @@ -import { Cartesian3, Cartographic, Ellipsoid, Rectangle } from "../../index.js"; - -import { Math as CesiumMath } from "../../index.js"; +import { + Cartesian2, + Cartesian3, + Cartographic, + Ellipsoid, + Rectangle, + Math as CesiumMath, +} from "../../index.js"; import createPackableSpecs from "../../../../Specs/createPackableSpecs.js"; @@ -721,6 +726,44 @@ describe("Core/Ellipsoid", function () { ); }); + it("getLocalCurvature throws with no position", function () { + expect(function () { + Ellipsoid.WGS84.getLocalCurvature(undefined); + }).toThrowDeveloperError(); + }); + + it("getLocalCurvature returns expected values at the equator", function () { + const ellipsoid = Ellipsoid.WGS84; + const cartographic = Cartographic.fromDegrees(0.0, 0.0); + const cartesianOnTheSurface = ellipsoid.cartographicToCartesian( + cartographic + ); + const returnedResult = ellipsoid.getLocalCurvature(cartesianOnTheSurface); + const expectedResult = new Cartesian2( + 1.0 / ellipsoid.maximumRadius, + ellipsoid.maximumRadius / + (ellipsoid.minimumRadius * ellipsoid.minimumRadius) + ); + expect(returnedResult).toEqualEpsilon(expectedResult, CesiumMath.EPSILON8); + }); + + it("getLocalCurvature returns expected values at the north pole", function () { + const ellipsoid = Ellipsoid.WGS84; + const cartographic = Cartographic.fromDegrees(0.0, 90.0); + const cartesianOnTheSurface = ellipsoid.cartographicToCartesian( + cartographic + ); + const returnedResult = ellipsoid.getLocalCurvature(cartesianOnTheSurface); + const semiLatusRectum = + (ellipsoid.maximumRadius * ellipsoid.maximumRadius) / + ellipsoid.minimumRadius; + const expectedResult = new Cartesian2( + 1.0 / semiLatusRectum, + 1.0 / semiLatusRectum + ); + expect(returnedResult).toEqualEpsilon(expectedResult, CesiumMath.EPSILON8); + }); + it("ellipsoid is initialized with _squaredXOverSquaredZ property", function () { const ellipsoid = new Ellipsoid(4, 4, 3); diff --git a/packages/engine/Specs/Core/TerrainEncodingSpec.js b/packages/engine/Specs/Core/TerrainEncodingSpec.js index d9903eb7d3dd..2407ed32fff0 100644 --- a/packages/engine/Specs/Core/TerrainEncodingSpec.js +++ b/packages/engine/Specs/Core/TerrainEncodingSpec.js @@ -6,7 +6,7 @@ import { Ellipsoid, Matrix4, TerrainEncoding, - TerrainExaggeration, + VerticalExaggeration, TerrainQuantization, Transforms, } from "../../index.js"; @@ -217,7 +217,7 @@ describe("Core/TerrainEncoding", function () { const exaggeration = 2.0; const exaggerationRelativeHeight = 10.0; - const exaggeratedHeight = TerrainExaggeration.getHeight( + const exaggeratedHeight = VerticalExaggeration.getHeight( height, exaggeration, exaggerationRelativeHeight diff --git a/packages/engine/Specs/Core/VerticalExaggerationSpec.js b/packages/engine/Specs/Core/VerticalExaggerationSpec.js new file mode 100644 index 000000000000..1d261e5c9791 --- /dev/null +++ b/packages/engine/Specs/Core/VerticalExaggerationSpec.js @@ -0,0 +1,126 @@ +import { + Cartesian3, + Ellipsoid, + VerticalExaggeration, + Math as CesiumMath, +} from "../../index.js"; + +describe("Core/VerticalExaggeration", function () { + it("getHeight leaves heights unchanged with a scale of 1.0", function () { + const height = 100.0; + const scale = 1.0; + const relativeHeight = 0.0; + + const result = VerticalExaggeration.getHeight( + height, + scale, + relativeHeight + ); + expect(result).toEqual(height); + }); + + it("getHeight scales up heights above relativeHeight", function () { + const height = 150.0; + const scale = 2.0; + const relativeHeight = 100.0; + + const result = VerticalExaggeration.getHeight( + height, + scale, + relativeHeight + ); + expect(result).toEqual(200.0); + }); + + it("getHeight does not change heights equal to relativeHeight", function () { + const height = 100.0; + const scale = 1.0; + const relativeHeight = 100.0; + + const result = VerticalExaggeration.getHeight( + height, + scale, + relativeHeight + ); + expect(result).toEqual(100.0); + }); + + it("getHeight scales down heights below relativeHeight", function () { + const height = 100.0; + const scale = 2.0; + const relativeHeight = 200.0; + + const result = VerticalExaggeration.getHeight( + height, + scale, + relativeHeight + ); + expect(result).toEqual(0.0); + }); + + it("getPosition leaves positions unchanged with a scale of 1.0", function () { + const position = Cartesian3.fromRadians(0.0, 0.0, 100.0); + const ellipsoid = Ellipsoid.WGS84; + const verticalExaggeration = 1.0; + const verticalExaggerationRelativeHeight = 0.0; + + const result = VerticalExaggeration.getPosition( + position, + ellipsoid, + verticalExaggeration, + verticalExaggerationRelativeHeight + ); + expect(result).toEqualEpsilon(position, CesiumMath.EPSILON8); + }); + + it("getPosition scales up positions above relativeHeight", function () { + const position = Cartesian3.fromRadians(0.0, 0.0, 150.0); + const ellipsoid = Ellipsoid.WGS84; + const verticalExaggeration = 2.0; + const verticalExaggerationRelativeHeight = 100.0; + + const result = VerticalExaggeration.getPosition( + position, + ellipsoid, + verticalExaggeration, + verticalExaggerationRelativeHeight + ); + expect(result).toEqualEpsilon( + Cartesian3.fromRadians(0.0, 0.0, 200.0), + CesiumMath.EPSILON8 + ); + }); + + it("getPosition does not change positions equal to relativeHeight", function () { + const position = Cartesian3.fromRadians(0.0, 0.0, 100.0); + const ellipsoid = Ellipsoid.WGS84; + const verticalExaggeration = 1.0; + const verticalExaggerationRelativeHeight = 100.0; + + const result = VerticalExaggeration.getPosition( + position, + ellipsoid, + verticalExaggeration, + verticalExaggerationRelativeHeight + ); + expect(result).toEqualEpsilon(position, CesiumMath.EPSILON8); + }); + + it("getPosition scales down positions below relativeHeight", function () { + const position = Cartesian3.fromRadians(0.0, 0.0, 100.0); + const ellipsoid = Ellipsoid.WGS84; + const verticalExaggeration = 2.0; + const verticalExaggerationRelativeHeight = 200.0; + + const result = VerticalExaggeration.getPosition( + position, + ellipsoid, + verticalExaggeration, + verticalExaggerationRelativeHeight + ); + expect(result).toEqualEpsilon( + Cartesian3.fromRadians(0.0, 0.0, 0.0), + CesiumMath.EPSILON8 + ); + }); +}); diff --git a/packages/engine/Specs/DataSources/EntityClusterSpec.js b/packages/engine/Specs/DataSources/EntityClusterSpec.js index 511631ccefc7..e24dae714fdf 100644 --- a/packages/engine/Specs/DataSources/EntityClusterSpec.js +++ b/packages/engine/Specs/DataSources/EntityClusterSpec.js @@ -37,6 +37,7 @@ describe( _debug: { tilesWaitingForChildren: 0, }, + updateHeight: function () {}, }, terrainProviderChanged: new Event(), imageryLayersUpdatedEvent: new Event(), @@ -44,16 +45,13 @@ describe( update: function () {}, render: function () {}, endFrame: function () {}, + destroy: function () {}, }; scene.globe.getHeight = function () { return 0.0; }; - scene.globe.destroy = function () {}; - - scene.globe._surface.updateHeight = function () {}; - scene.globe.terrainProviderChanged = new Event(); Object.defineProperties(scene.globe, { terrainProvider: { diff --git a/packages/engine/Specs/Renderer/AutomaticUniformSpec.js b/packages/engine/Specs/Renderer/AutomaticUniformSpec.js index 1ccb4e45b7e3..55319d78300b 100644 --- a/packages/engine/Specs/Renderer/AutomaticUniformSpec.js +++ b/packages/engine/Specs/Renderer/AutomaticUniformSpec.js @@ -2150,6 +2150,54 @@ describe( }).contextToRender(); }); + it("has czm_eyeEllipsoidNormalEC", function () { + const { uniformState } = context; + const frameState = createFrameState(context, createMockCamera()); + const ellipsoid = new Ellipsoid(1.1, 1.1, 1.0); + frameState.mapProjection = new GeographicProjection(ellipsoid); + uniformState.update(frameState); + const fragmentShader = `void main() { + out_FragColor = vec4(czm_eyeEllipsoidNormalEC != vec3(0.0)); + }`; + expect({ context, fragmentShader }).contextToRender(); + }); + + it("has czm_eyeEllipsoidCurvature", function () { + const { uniformState } = context; + const frameState = createFrameState(context, createMockCamera()); + const ellipsoid = new Ellipsoid(1.0, 1.0, 1.0); + frameState.mapProjection = new GeographicProjection(ellipsoid); + uniformState.update(frameState); + const fragmentShader = `void main() { + out_FragColor = vec4(czm_eyeEllipsoidCurvature == vec2(1.0)); + }`; + expect({ context, fragmentShader }).contextToRender(); + }); + + it("has czm_modelToEnu", function () { + const { uniformState } = context; + const frameState = createFrameState(context, createMockCamera()); + const ellipsoid = new Ellipsoid(1.0, 1.0, 1.0); + frameState.mapProjection = new GeographicProjection(ellipsoid); + uniformState.update(frameState); + const fragmentShader = `void main() { + out_FragColor = vec4(czm_modelToEnu != mat4(0.0)); + }`; + expect({ context, fragmentShader }).contextToRender(); + }); + + it("has czm_enuToModel", function () { + const { uniformState } = context; + const frameState = createFrameState(context, createMockCamera()); + const ellipsoid = new Ellipsoid(1.0, 1.0, 1.0); + frameState.mapProjection = new GeographicProjection(ellipsoid); + uniformState.update(frameState); + const fragmentShader = `void main() { + out_FragColor = vec4(czm_enuToModel != mat4(0.0)); + }`; + expect({ context, fragmentShader }).contextToRender(); + }); + it("has czm_ellipsoidRadii", function () { const us = context.uniformState; const frameState = createFrameState(context, createMockCamera()); diff --git a/packages/engine/Specs/Scene/Cesium3DTileSpec.js b/packages/engine/Specs/Scene/Cesium3DTileSpec.js index 1f958814c68f..cb5270986461 100644 --- a/packages/engine/Specs/Scene/Cesium3DTileSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTileSpec.js @@ -52,7 +52,7 @@ describe( refine: "REPLACE", children: [], boundingVolume: { - region: [-1.2, -1.2, 0.0, 0.0, -30, -34], + region: [-1.2, -1.2, 0.0, 0.0, -34, -30], }, }; @@ -131,8 +131,12 @@ describe( const centerLongitude = -1.31968; const centerLatitude = 0.698874; - function getTileTransform(longitude, latitude) { - const transformCenter = Cartesian3.fromRadians(longitude, latitude, 0.0); + function getTileTransform(longitude, latitude, height = 0.0) { + const transformCenter = Cartesian3.fromRadians( + longitude, + latitude, + height + ); const hpr = new HeadingPitchRoll(); const transformMatrix = Transforms.headingPitchRollToFixedFrame( transformCenter, @@ -572,6 +576,166 @@ describe( }); }); + describe("vertical exaggeration", function () { + let scene; + beforeEach(function () { + scene = createScene(); + scene.frameState.passes.render = true; + }); + + afterEach(function () { + scene.destroyForSpecs(); + }); + + it("applies vertical exaggeration to bounding box", function () { + const header = clone(tileWithContentBoundingBox, true); + header.transform = getTileTransform(0.0, 0.0, 100.0); + const tile = new Cesium3DTile( + mockTileset, + "/some_url", + header, + undefined + ); + const boundingBox = tile.boundingVolume.boundingVolume; + const boundingVolumeCenter = Cartesian3.fromRadians(0.0, 0.0, 101.0); + expect(boundingBox.center).toEqualEpsilon( + boundingVolumeCenter, + CesiumMath.EPSILON7 + ); + const boundingVolumeHalfAxes = Matrix3.fromArray([ + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 0.0, + 0.0, + ]); + expect(boundingBox.halfAxes).toEqualEpsilon( + boundingVolumeHalfAxes, + CesiumMath.EPSILON7 + ); + + scene.verticalExaggeration = 2.0; + scene.updateFrameState(); + tile.updateTransform(undefined, scene.frameState); + + const exaggeratedCenter = Cartesian3.fromRadians(0.0, 0.0, 202.0); + expect(boundingBox.center).toEqualEpsilon( + exaggeratedCenter, + CesiumMath.EPSILON7 + ); + // Note orientation flip due to re-computing the box after exaggeration + const exaggeratedHalfAxes = Matrix3.fromArray([ + 4.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + ]); + expect(boundingBox.halfAxes).toEqualEpsilon( + exaggeratedHalfAxes, + CesiumMath.EPSILON4 + ); + }); + + it("applies vertical exaggeration to bounding region", function () { + const tile = new Cesium3DTile( + mockTileset, + "/some_url", + tileWithBoundingRegion, + undefined + ); + const tileBoundingRegion = tile.boundingVolume; + expect(tileBoundingRegion.minimumHeight).toEqualEpsilon( + -34.0, + CesiumMath.EPSILON7 + ); + expect(tileBoundingRegion.maximumHeight).toEqualEpsilon( + -30.0, + CesiumMath.EPSILON7 + ); + const rectangle = Rectangle.pack( + tileBoundingRegion.rectangle, + new Array(4) + ); + expect(rectangle).toEqualEpsilon( + [-1.2, -1.2, 0.0, 0.0], + CesiumMath.EPSILON7 + ); + + scene.verticalExaggeration = 2.0; + scene.verticalExaggerationRelativeHeight = -34.0; + scene.updateFrameState(); + tile.updateTransform(undefined, scene.frameState); + + expect(tileBoundingRegion.minimumHeight).toEqualEpsilon( + -34.0, + CesiumMath.EPSILON7 + ); + expect(tileBoundingRegion.maximumHeight).toEqualEpsilon( + -26.0, + CesiumMath.EPSILON7 + ); + const exaggeratedRectangle = Rectangle.pack( + tileBoundingRegion.rectangle, + new Array(4) + ); + expect(exaggeratedRectangle).toEqualEpsilon( + [-1.2, -1.2, 0.0, 0.0], + CesiumMath.EPSILON7 + ); + }); + + it("applies vertical exaggeration to bounding sphere", function () { + const header = clone(tileWithBoundingSphere, true); + header.transform = getTileTransform( + centerLongitude, + centerLatitude, + 100.0 + ); + const tile = new Cesium3DTile( + mockTileset, + "/some_url", + header, + undefined + ); + const boundingSphere = tile.boundingVolume.boundingVolume; + + const boundingVolumeCenter = Cartesian3.fromRadians( + centerLongitude, + centerLatitude, + 100.0 + ); + expect(boundingSphere.center).toEqualEpsilon( + boundingVolumeCenter, + CesiumMath.EPSILON7 + ); + expect(boundingSphere.radius).toEqualEpsilon(5.0, CesiumMath.EPSILON7); + + scene.verticalExaggeration = 2.0; + scene.updateFrameState(); + tile.updateTransform(undefined, scene.frameState); + + const exaggeratedCenter = Cartesian3.fromRadians( + centerLongitude, + centerLatitude, + 200.0 + ); + expect(boundingSphere.center).toEqualEpsilon( + exaggeratedCenter, + CesiumMath.EPSILON7 + ); + expect(boundingSphere.radius).toEqualEpsilon(10.0, CesiumMath.EPSILON7); + }); + }); + describe("debug bounding volumes", function () { let scene; beforeEach(function () { diff --git a/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 9818f2534b2b..3083f4aa7071 100644 --- a/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -1441,6 +1441,64 @@ describe( }); }); }); + + it("Detects change in vertical exaggeration", function () { + switchViewMode( + SceneMode.SCENE3D, + new GeographicProjection(Ellipsoid.WGS84) + ); + scene.camera.flyHome(0.0); + + scene.verticalExaggeration = 1.0; + scene.verticalExaggerationRelativeHeight = 0.0; + + return updateUntilDone(scene.globe).then(function () { + forEachRenderedTile(scene.globe._surface, 1, undefined, function ( + tile + ) { + const surfaceTile = tile.data; + const encoding = surfaceTile.mesh.encoding; + const boundingSphere = surfaceTile.tileBoundingRegion.boundingSphere; + expect(encoding.exaggeration).toEqual(1.0); + expect(encoding.hasGeodeticSurfaceNormals).toEqual(false); + expect(boundingSphere.radius).toBeLessThan(7000000.0); + }); + + scene.verticalExaggeration = 2.0; + scene.verticalExaggerationRelativeHeight = -1000000.0; + + return updateUntilDone(scene.globe).then(function () { + forEachRenderedTile(scene.globe._surface, 1, undefined, function ( + tile + ) { + const surfaceTile = tile.data; + const encoding = surfaceTile.mesh.encoding; + const boundingSphere = + surfaceTile.tileBoundingRegion.boundingSphere; + expect(encoding.exaggeration).toEqual(2.0); + expect(encoding.hasGeodeticSurfaceNormals).toEqual(true); + expect(boundingSphere.radius).toBeGreaterThan(7000000.0); + }); + + scene.verticalExaggeration = 1.0; + scene.verticalExaggerationRelativeHeight = 0.0; + + return updateUntilDone(scene.globe).then(function () { + forEachRenderedTile(scene.globe._surface, 1, undefined, function ( + tile + ) { + const surfaceTile = tile.data; + const encoding = surfaceTile.mesh.encoding; + const boundingSphere = + surfaceTile.tileBoundingRegion.boundingSphere; + expect(encoding.exaggeration).toEqual(1.0); + expect(encoding.hasGeodeticSurfaceNormals).toEqual(false); + expect(boundingSphere.radius).toBeLessThan(7000000.0); + }); + }); + }); + }); + }); }, "WebGL" ); diff --git a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js index b16fe188eaa2..dff8c900829f 100644 --- a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js @@ -26,6 +26,7 @@ import { SelectedFeatureIdPipelineStage, SkinningPipelineStage, VertexAttributeSemantic, + VerticalExaggerationPipelineStage, WireframePipelineStage, ClassificationType, } from "../../../index.js"; @@ -159,6 +160,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -201,6 +203,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { SelectedFeatureIdPipelineStage, BatchTexturePipelineStage, CPUStylingPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, @@ -247,6 +250,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { SelectedFeatureIdPipelineStage, BatchTexturePipelineStage, CPUStylingPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, @@ -303,6 +307,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, @@ -330,6 +335,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -360,6 +366,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { GeometryPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -390,6 +397,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -430,6 +438,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -464,6 +473,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -497,6 +507,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -527,6 +538,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -558,6 +570,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -587,6 +600,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -619,6 +633,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -660,6 +675,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -692,6 +708,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -723,6 +740,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -754,6 +772,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -784,6 +803,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -815,6 +835,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -845,6 +866,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -875,6 +897,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -906,6 +929,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, PrimitiveOutlinePipelineStage, AlphaPipelineStage, @@ -938,6 +962,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -968,6 +993,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index df62257eb949..b8dec3545c5d 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -2128,13 +2128,11 @@ describe( }, imageryLayersUpdatedEvent: new Event(), destroy: function () {}, + beginFrame: function () {}, + endFrame: function () {}, + terrainProviderChanged: new Event(), }; - globe.beginFrame = function () {}; - - globe.endFrame = function () {}; - - globe.terrainProviderChanged = new Event(); Object.defineProperties(globe, { terrainProvider: { set: function (value) { @@ -3718,6 +3716,25 @@ describe( }); }); + it("resets draw commands when vertical exaggeration changes", function () { + return loadAndZoomToModelAsync( + { + gltf: boxTexturedGltfUrl, + }, + scene + ).then(function (model) { + const resetDrawCommands = spyOn( + model, + "resetDrawCommands" + ).and.callThrough(); + expect(model.ready).toBe(true); + + scene.verticalExaggeration = 2.0; + scene.renderForSpecs(); + expect(resetDrawCommands).toHaveBeenCalled(); + }); + }); + it("does not issue draw commands when ignoreCommands is true", function () { return loadAndZoomToModelAsync( { diff --git a/packages/engine/Specs/Scene/Model/VerticalExaggerationPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/VerticalExaggerationPipelineStageSpec.js new file mode 100644 index 000000000000..0eead95983fe --- /dev/null +++ b/packages/engine/Specs/Scene/Model/VerticalExaggerationPipelineStageSpec.js @@ -0,0 +1,57 @@ +import { + _shadersVerticalExaggerationStageVS, + Cartesian2, + RenderState, + ShaderBuilder, + VerticalExaggerationPipelineStage, +} from "../../../index.js"; +import ShaderBuilderTester from "../../../../../Specs/ShaderBuilderTester.js"; + +describe( + "Scene/Model/VerticalExaggerationPipelineStage", + function () { + const mockModel = {}; + const mockPrimitive = {}; + const mockFrameState = { + verticalExaggeration: 2.0, + verticalExaggerationRelativeHeight: 100.0, + }; + + function mockRenderResources() { + return { + model: mockModel, + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + renderStateOptions: RenderState.getState(RenderState.fromCache()), + }; + } + + it("adds shader lines, defines, and uniforms", function () { + const renderResources = mockRenderResources(); + VerticalExaggerationPipelineStage.process( + renderResources, + mockPrimitive, + mockFrameState + ); + + const shaderBuilder = renderResources.shaderBuilder; + ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, [ + _shadersVerticalExaggerationStageVS, + ]); + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ + "HAS_VERTICAL_EXAGGERATION", + ]); + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, [ + "uniform vec2 u_verticalExaggerationAndRelativeHeight;", + ]); + const expectedUniform = Cartesian2.fromElements( + mockFrameState.verticalExaggeration, + mockFrameState.verticalExaggerationRelativeHeight + ); + expect( + renderResources.uniformMap.u_verticalExaggerationAndRelativeHeight() + ).toEqual(expectedUniform); + }); + }, + "WebGL" +); diff --git a/packages/engine/Specs/Scene/QuadtreePrimitiveSpec.js b/packages/engine/Specs/Scene/QuadtreePrimitiveSpec.js index ae7467ec2c83..1a2e3fc2d3b3 100644 --- a/packages/engine/Specs/Scene/QuadtreePrimitiveSpec.js +++ b/packages/engine/Specs/Scene/QuadtreePrimitiveSpec.js @@ -66,8 +66,8 @@ describe("Scene/QuadtreePrimitive", function () { afterRender: [], pixelRatio: 1.0, - terrainExaggeration: 1.0, - terrainExaggerationRelativeHeight: 0.0, + verticalExaggeration: 1.0, + verticalExaggerationRelativeHeight: 0.0, globeTranslucencyState: new GlobeTranslucencyState(), }; diff --git a/packages/engine/Specs/Scene/SceneSpec.js b/packages/engine/Specs/Scene/SceneSpec.js index 5588be3def88..b8297061da79 100644 --- a/packages/engine/Specs/Scene/SceneSpec.js +++ b/packages/engine/Specs/Scene/SceneSpec.js @@ -523,6 +523,19 @@ describe( scene.destroyForSpecs(); }); + it("sets verticalExaggeration and verticalExaggerationRelativeHeight", function () { + const scene = createScene(); + + expect(scene.verticalExaggeration).toEqual(1.0); + expect(scene.verticalExaggerationRelativeHeight).toEqual(0.0); + + scene.verticalExaggeration = 2.0; + scene.verticalExaggerationRelativeHeight = 100000.0; + + expect(scene.verticalExaggeration).toEqual(2.0); + expect(scene.verticalExaggerationRelativeHeight).toEqual(100000.0); + }); + it("destroys primitive on set globe", function () { const scene = createScene(); const globe = new Globe(Ellipsoid.UNIT_SPHERE); diff --git a/packages/engine/Specs/Scene/ScreenSpaceCameraControllerSpec.js b/packages/engine/Specs/Scene/ScreenSpaceCameraControllerSpec.js index 91ce8e05b279..5da26e0c64aa 100644 --- a/packages/engine/Specs/Scene/ScreenSpaceCameraControllerSpec.js +++ b/packages/engine/Specs/Scene/ScreenSpaceCameraControllerSpec.js @@ -35,6 +35,8 @@ describe("Scene/ScreenSpaceCameraController", function () { this.canvas = canvas; this.camera = camera; this.globe = undefined; + this.verticalExaggeration = 1.0; + this.verticalExaggerationRelativeHeight = 0.0; this.mapProjection = new GeographicProjection(ellipsoid); this.screenSpaceCameraController = undefined; this.cameraUnderground = false; @@ -59,9 +61,6 @@ describe("Scene/ScreenSpaceCameraController", function () { }, }; - this.terrainExaggeration = 1.0; - this.terrainExaggerationRelativeHeight = 0.0; - this.show = true; } beforeAll(function () {