diff --git a/CHANGES.md b/CHANGES.md
index 28cfb9d6014f..b0dee61c012a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -8,6 +8,10 @@
- Updates use of deprecated options on createImageBitmap. [#12664](https://github.com/CesiumGS/cesium/pull/12664)
+#### Additions :tada:
+
+- Added `HeightReference` to `Cesium3DTileset.ConstructorOptions` to allow clamping point features in 3D Tile vector data to terrain or 3D Tiles [#11710](https://github.com/CesiumGS/cesium/pull/11710)
+
## 1.130 - 2025-06-02
### @cesium/engine
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 4970e96cbfdb..f1ffd8ffccd1 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -206,6 +206,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
- [IKangXu](https://github.com/IKangXu)
- [EMapGIS](https://github.com/EMapGIS)
- [CandyACE](https://github.com/CandyACE)
+- [EARTHBRAIN, Ltd.](https://www.earthbrain.com/en/)
+ - [Gregory Diehl](https://github.com/gdiehleb)
## [Individual CLA](Documentation/Contributors/CLAs/individual-contributor-license-agreement-v1.0.pdf)
diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js
index dcff4e3c0b21..6f2bad9616cf 100644
--- a/packages/engine/Source/Scene/Cesium3DTileset.js
+++ b/packages/engine/Source/Scene/Cesium3DTileset.js
@@ -102,6 +102,8 @@ import ImageryLayerCollection from "./ImageryLayerCollection.js";
* @property {ClippingPlaneCollection} [clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset.
* @property {ClippingPolygonCollection} [clippingPolygons] The {@link ClippingPolygonCollection} used to selectively disable rendering the tileset.
* @property {ClassificationType} [classificationType] Determines whether terrain, 3D Tiles or both will be classified by this tileset. See {@link Cesium3DTileset#classificationType} for details about restrictions and limitations.
+ * @property {HeightReference} [heightReference] Sets the {@link HeightReference} for point features in vector tilesets.
+ * @property {Scene} [scene] The {@link CesiumWidget#scene} that the tileset will be rendered in, required for tilesets that specify a {@link heightReference} value for clamping 3D Tiles vector data content- like points, lines, and labels- to terrain or 3D tiles.
* @property {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid determining the size and shape of the globe.
* @property {object} [pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting.
* @property {Cartesian3} [lightColor] The light color when shading models. When undefined the scene's light color is used instead.
@@ -337,6 +339,8 @@ function Cesium3DTileset(options) {
this._tileDebugLabels = undefined;
this._classificationType = options.classificationType;
+ this._heightReference = options.heightReference;
+ this._scene = options.scene;
this._ellipsoid = options.ellipsoid ?? Ellipsoid.WGS84;
@@ -1808,6 +1812,43 @@ Object.defineProperties(Cesium3DTileset.prototype, {
},
},
+ /**
+ * Specifies if the height is relative to terrain, 3D Tiles, or both.
+ *
+ * This option is only applied to point features in tilesets containing vector data. + * This option requires the Viewer's scene to be passed in through options.scene. + *
+ * + * @memberof Cesium3DTileset.prototype + * + * @type {HeightReference | undefined} + * @default undefined + * + * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy. + * @readonly + */ + heightReference: { + get: function () { + return this._heightReference; + }, + }, + + /** + * The {@link CesiumWidget#scene} that the tileset will be rendered in, required for tilesets that specify a {@link heightReference} value for clamping 3D Tiles vector data content- like points, lines, and labels- to terrain or 3D tiles. + * + * @member of Cesium3DTileset.prototype + * + * @type {Scene | undefined} + * @default undefined + * @readonly + * + */ + scene: { + get: function () { + return this._scene; + }, + }, + /** * Gets an ellipsoid describing the shape of the globe. * diff --git a/packages/engine/Source/Scene/Vector3DTileContent.js b/packages/engine/Source/Scene/Vector3DTileContent.js index 5906132e94a3..ccf915ceed18 100644 --- a/packages/engine/Source/Scene/Vector3DTileContent.js +++ b/packages/engine/Source/Scene/Vector3DTileContent.js @@ -524,7 +524,7 @@ function initialize(content, arrayBuffer, byteOffset) { modelMatrix: modelMatrix, }); } - + const tileset = content._tileset; if (numberOfPolylines > 0) { featureTable.featuresLength = numberOfPolylines; @@ -570,7 +570,6 @@ function initialize(content, arrayBuffer, byteOffset) { ); byteOffset += polylinePositionByteLength; - const tileset = content._tileset; const examineVectorLinesFunction = tileset.examineVectorLinesFunction; if (defined(examineVectorLinesFunction)) { const decodedPositions = decodeVectorPolylinePositions( @@ -625,6 +624,8 @@ function initialize(content, arrayBuffer, byteOffset) { maximumHeight: maxHeight, rectangle: rectangle, batchTable: batchTable, + heightReference: tileset.heightReference, + scene: tileset.scene, }); } } diff --git a/packages/engine/Source/Scene/Vector3DTilePoints.js b/packages/engine/Source/Scene/Vector3DTilePoints.js index ec2dd1351853..c8c2dfa05db2 100644 --- a/packages/engine/Source/Scene/Vector3DTilePoints.js +++ b/packages/engine/Source/Scene/Vector3DTilePoints.js @@ -15,6 +15,7 @@ import LabelCollection from "./LabelCollection.js"; import LabelStyle from "./LabelStyle.js"; import PolylineCollection from "./PolylineCollection.js"; import VerticalOrigin from "./VerticalOrigin.js"; +import HeightReference from "./HeightReference.js"; /** * Creates a batch of points or billboards and labels. @@ -27,8 +28,10 @@ import VerticalOrigin from "./VerticalOrigin.js"; * @param {number} options.minimumHeight The minimum height of the terrain covered by the tile. * @param {number} options.maximumHeight The maximum height of the terrain covered by the tile. * @param {Rectangle} options.rectangle The rectangle containing the tile. + * @param {HeightReference} options.heightReference Determines how billboard and label features are positioned relative to terrain or 3d tiles. * @param {Cesium3DTileBatchTable} options.batchTable The batch table for the tile containing the batched polygons. * @param {Uint16Array} options.batchIds The batch ids for each polygon. + * @param {Scene} options.scene The Cesium Viewer {@link Scene}. This is required for clamping billboards and labels with {@link HeightReference} * * @private */ @@ -42,12 +45,15 @@ function Vector3DTilePoints(options) { this._rectangle = options.rectangle; this._minHeight = options.minimumHeight; this._maxHeight = options.maximumHeight; + this._heightReference = options.heightReference; this._billboardCollection = new BillboardCollection({ batchTable: options.batchTable, + scene: options.scene, }); this._labelCollection = new LabelCollection({ batchTable: options.batchTable, + scene: options.scene, }); this._polylineCollection = new PolylineCollection(); this._polylineCollection._useHighlightColor = true; @@ -175,6 +181,8 @@ function createPoints(points, ellipsoid) { const batchIds = points._batchIds; const numberOfPoints = positions.length / 3; + const heightReference = points._heightReference ?? HeightReference.NONE; + for (let i = 0; i < numberOfPoints; ++i) { const id = batchIds[i]; @@ -183,11 +191,13 @@ function createPoints(points, ellipsoid) { const b = billboardCollection.add(); b.position = position; b._batchIndex = id; + b.heightReference = heightReference; const l = labelCollection.add(); l.text = " "; l.position = position; l._batchIndex = id; + l.heightReference = heightReference; const p = polylineCollection.add(); p.positions = [Cartesian3.clone(position), Cartesian3.clone(position)]; diff --git a/packages/engine/Specs/Scene/Vector3DTileContentSpec.js b/packages/engine/Specs/Scene/Vector3DTileContentSpec.js index 43710059310b..b8616770e615 100644 --- a/packages/engine/Specs/Scene/Vector3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Vector3DTileContentSpec.js @@ -7,6 +7,7 @@ import { Cesium3DTileset, Cesium3DTileStyle, ClassificationType, + HeightReference, Color, ColorGeometryInstanceAttribute, destroyObject, @@ -613,6 +614,22 @@ describe( expect(scene).toRender(whitePixel); }); }); + + it("sets the heightReference for Vector3DTilePoints", async () => { + const heightReference = HeightReference.CLAMP_TO_3D_TILE; + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + vectorTilePointsTileset, + { + heightReference: heightReference, + scene, + }, + ); + const vectorTile = tileset._root.children[0]; + const vectorTilePoint = vectorTile._content._points; + + expect(vectorTilePoint._heightReference).toEqual(heightReference); + }); }); describe("polygons", () => { diff --git a/packages/engine/Specs/Scene/Vector3DTilePointsSpec.js b/packages/engine/Specs/Scene/Vector3DTilePointsSpec.js index b171cc95684b..4f99257fcee2 100644 --- a/packages/engine/Specs/Scene/Vector3DTilePointsSpec.js +++ b/packages/engine/Specs/Scene/Vector3DTilePointsSpec.js @@ -9,6 +9,7 @@ import { DistanceDisplayCondition, Ellipsoid, Math as CesiumMath, + HeightReference, NearFarScalar, Rectangle, Cesium3DTileBatchTable, @@ -577,6 +578,7 @@ describe( maxHeight, cartoPositions, ); + const heightReference = HeightReference.CLAMP_TO_TERRAIN; const batchTable = new Cesium3DTileBatchTable(mockTileset, 1); batchTable.update(mockTileset, scene.frameState); @@ -589,6 +591,8 @@ describe( rectangle: rectangle, minimumHeight: minHeight, maximumHeight: maxHeight, + heightReference: heightReference, + scene, }), ); @@ -616,10 +620,64 @@ describe( }).then(function () { expect(billboard.ready).toEqual(true); expect(scene).toRender([0, 0, 255, 255]); + expect(billboard.heightReference).toEqual(heightReference); }); }); }); + it("renders a point with a label", function () { + const minHeight = 0.0; + const maxHeight = 100.0; + const cartoPositions = [Cartographic.fromDegrees(0.0, 0.0, 10.0)]; + const positions = encodePositions( + rectangle, + minHeight, + maxHeight, + cartoPositions, + ); + const heightReference = HeightReference.CLAMP_TO_TERRAIN; + + const batchTable = new Cesium3DTileBatchTable(mockTileset, 1); + batchTable.update(mockTileset, scene.frameState); + + points = scene.primitives.add( + new Vector3DTilePoints({ + positions: positions, + batchTable: batchTable, + batchIds: new Uint16Array([0]), + rectangle: rectangle, + minimumHeight: minHeight, + maximumHeight: maxHeight, + heightReference: heightReference, + scene, + }), + ); + + const style = new Cesium3DTileStyle({ + labelText: '"test"', + labelColor: "rgba(100, 255, 0, 255)", + labelHorizontalOrigin: HorizontalOrigin.LEFT, + }); + return loadPoints(points).then(function () { + const features = []; + points.createFeatures(mockTileset, features); + points.applyStyle(style, features); + + const collection = points._labelCollection; + expect(collection.length).toEqual(1); + const label = collection.get(0); + expect(label).toBeDefined(); + + scene.camera.lookAt( + Cartesian3.fromDegrees(0.0, 0.0, 10.0), + new Cartesian3(0.0, 0.0, 50.0), + ); + + expect(scene).toRender([100, 255, 0, 255]); + expect(label.heightReference).toEqual(heightReference); + }); + }); + it("renders multiple points with debug color", function () { const minHeight = 0.0; const maxHeight = 100.0;