From 947ba72d40e04a2263a9daa4aed8d9c8f995c9dd Mon Sep 17 00:00:00 2001 From: YunVlad Date: Mon, 13 May 2024 17:31:02 +0300 Subject: [PATCH 01/13] Add splitDirection to PointPrimitive Added the splitDirection property to PointPrimitive. Updated PointPrimitive Collection and shaders to work with the property. --- .../engine/Source/Scene/PointPrimitive.js | 30 ++++++++++++++- .../Source/Scene/PointPrimitiveCollection.js | 37 +++++++++++++++++++ .../Shaders/PointPrimitiveCollectionFS.glsl | 4 ++ .../Shaders/PointPrimitiveCollectionVS.glsl | 3 ++ 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Scene/PointPrimitive.js b/packages/engine/Source/Scene/PointPrimitive.js index 08307252f649..a4c9cf979bb9 100644 --- a/packages/engine/Source/Scene/PointPrimitive.js +++ b/packages/engine/Source/Scene/PointPrimitive.js @@ -11,6 +11,7 @@ import Matrix4 from "../Core/Matrix4.js"; import NearFarScalar from "../Core/NearFarScalar.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; +import SplitDirection from "./SplitDirection.js" /** *
@@ -117,6 +118,11 @@ function PointPrimitive(options, pointPrimitiveCollection) { this._pointPrimitiveCollection = pointPrimitiveCollection; this._dirty = false; this._index = -1; //Used only by PointPrimitiveCollection + + this._splitDirection = defaultValue( + options.splitDirection, + SplitDirection.NONE + ); } const SHOW_INDEX = (PointPrimitive.SHOW_INDEX = 0); @@ -129,7 +135,8 @@ const SCALE_BY_DISTANCE_INDEX = (PointPrimitive.SCALE_BY_DISTANCE_INDEX = 6); const TRANSLUCENCY_BY_DISTANCE_INDEX = (PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX = 7); const DISTANCE_DISPLAY_CONDITION_INDEX = (PointPrimitive.DISTANCE_DISPLAY_CONDITION_INDEX = 8); const DISABLE_DEPTH_DISTANCE_INDEX = (PointPrimitive.DISABLE_DEPTH_DISTANCE_INDEX = 9); -PointPrimitive.NUMBER_OF_PROPERTIES = 10; +const SPLIT_DIRECTION_INDEX = (PointPrimitive.SPLIT_DIRECTION_INDEX = 10); +PointPrimitive.NUMBER_OF_PROPERTIES = 11; function makeDirty(pointPrimitive, propertyChanged) { const pointPrimitiveCollection = pointPrimitive._pointPrimitiveCollection; @@ -486,6 +493,24 @@ Object.defineProperties(PointPrimitive.prototype, { } }, }, + + /** + * The {@link SplitDirection} to apply to this point. + * @memberof PointPrimitive.prototype + * @type {SplitDirection} + * @default {@link SplitDirection.NONE} + */ + splitDirection: { + get: function () { + return this._splitDirection; + }, + set: function (value) { + if (this._splitDirection !== value) { + this._splitDirection = value; + makeDirty(this, SPLIT_DIRECTION_INDEX); + } + }, + }, }); PointPrimitive.prototype.getPickId = function (context) { @@ -657,7 +682,8 @@ PointPrimitive.prototype.equals = function (other) { this._distanceDisplayCondition, other._distanceDisplayCondition ) && - this._disableDepthTestDistance === other._disableDepthTestDistance) + this._disableDepthTestDistance === other._disableDepthTestDistance && + this._splitDirection === other._splitDirection) ); }; diff --git a/packages/engine/Source/Scene/PointPrimitiveCollection.js b/packages/engine/Source/Scene/PointPrimitiveCollection.js index ce42db08cf2b..a945c9feedc1 100644 --- a/packages/engine/Source/Scene/PointPrimitiveCollection.js +++ b/packages/engine/Source/Scene/PointPrimitiveCollection.js @@ -38,6 +38,7 @@ const DISTANCE_DISPLAY_CONDITION_INDEX = PointPrimitive.DISTANCE_DISPLAY_CONDITION_INDEX; const DISABLE_DEPTH_DISTANCE_INDEX = PointPrimitive.DISABLE_DEPTH_DISTANCE_INDEX; +const SPLIT_DIRECTION_INDEX = PointPrimitive.SPLIT_DIRECTION_INDEX; const NUMBER_OF_PROPERTIES = PointPrimitive.NUMBER_OF_PROPERTIES; const attributeLocations = { @@ -47,6 +48,7 @@ const attributeLocations = { compressedAttribute1: 3, // show, translucency by distance, some free space scaleByDistance: 4, distanceDisplayConditionAndDisableDepth: 5, + splitDirection: 6, }; /** @@ -216,6 +218,7 @@ function PointPrimitiveCollection(options) { BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX + BufferUsage.STATIC_DRAW, // SPLIT_DIRECTION_INDEX ]; const that = this; @@ -497,6 +500,12 @@ function createVAF(context, numberOfPointPrimitives, buffersUsage) { componentDatatype: ComponentDatatype.FLOAT, usage: buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX], }, + { + index: attributeLocations.splitDirection, + componentsPerAttribute: 1, + componentDatatype: ComponentDatatype.FLOAT, + usage: buffersUsage[SPLIT_DIRECTION_INDEX], + }, ], numberOfPointPrimitives ); // 1 vertex per pointPrimitive @@ -700,6 +709,24 @@ function writeDistanceDisplayConditionAndDepthDisable( writer(i, near, far, disableDepthTestDistance); } +function writeSplitDirection( + pointPrimitiveCollection, + context, + vafWriters, + pointPrimitive +) { + const i = pointPrimitive._index; + const writer = vafWriters[attributeLocations.splitDirection]; + let direction = 0.0; + + const split = pointPrimitive.splitDirection; + if (defined(split)) { + direction = split; + } + + writer(i, direction); +} + function writePointPrimitive( pointPrimitiveCollection, context, @@ -736,6 +763,12 @@ function writePointPrimitive( vafWriters, pointPrimitive ); + writeSplitDirection( + pointPrimitiveCollection, + context, + vafWriters, + pointPrimitive + ); } function recomputeActualPositions( @@ -930,6 +963,10 @@ PointPrimitiveCollection.prototype.update = function (frameState) { writers.push(writeDistanceDisplayConditionAndDepthDisable); } + if (properties[SPLIT_DIRECTION_INDEX]) { + writers.push(writeSplitDirection); + } + const numWriters = writers.length; vafWriters = this._vaf.writers; diff --git a/packages/engine/Source/Shaders/PointPrimitiveCollectionFS.glsl b/packages/engine/Source/Shaders/PointPrimitiveCollectionFS.glsl index 69fddf8ba256..edfa6e221144 100644 --- a/packages/engine/Source/Shaders/PointPrimitiveCollectionFS.glsl +++ b/packages/engine/Source/Shaders/PointPrimitiveCollectionFS.glsl @@ -3,9 +3,13 @@ in vec4 v_outlineColor; in float v_innerPercent; in float v_pixelDistance; in vec4 v_pickColor; +in float v_splitDirection; void main() { + if (v_splitDirection < 0.0 && gl_FragCoord.x > czm_splitPosition) discard; + if (v_splitDirection > 0.0 && gl_FragCoord.x < czm_splitPosition) discard; + // The distance in UV space from this fragment to the center of the point, at most 0.5. float distanceToCenter = length(gl_PointCoord - vec2(0.5)); // The max distance stops one pixel shy of the edge to leave space for anti-aliasing. diff --git a/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl b/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl index a4028d50ab1c..0e725388a042 100644 --- a/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl +++ b/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl @@ -6,12 +6,14 @@ in vec4 compressedAttribute0; // color, outlineColor, pick in vec4 compressedAttribute1; // show, translucency by distance, some free space in vec4 scaleByDistance; // near, nearScale, far, farScale in vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance +in float splitDirection; // splitDirection out vec4 v_color; out vec4 v_outlineColor; out float v_innerPercent; out float v_pixelDistance; out vec4 v_pickColor; +out float v_splitDirection; const float SHIFT_LEFT8 = 256.0; const float SHIFT_RIGHT8 = 1.0 / 256.0; @@ -180,4 +182,5 @@ void main() gl_Position *= show; v_pickColor = pickColor; + v_splitDirection = splitDirection; } From ddea3b718a847bfb60fb38d65c27ed340bdfa503 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Mon, 13 May 2024 17:59:28 +0300 Subject: [PATCH 02/13] Add splitDirection to PointPrimitive Added the splitDirection property to PointPrimitive. Updated PointGraphics and PointVisualizer. --- .../engine/Source/DataSources/PointGraphics.js | 17 +++++++++++++++++ .../Source/DataSources/PointVisualizer.js | 7 +++++++ 2 files changed, 24 insertions(+) diff --git a/packages/engine/Source/DataSources/PointGraphics.js b/packages/engine/Source/DataSources/PointGraphics.js index 6f1cbba104eb..4e287c605efe 100644 --- a/packages/engine/Source/DataSources/PointGraphics.js +++ b/packages/engine/Source/DataSources/PointGraphics.js @@ -19,6 +19,7 @@ import createPropertyDescriptor from "./createPropertyDescriptor.js"; * @property {Property | NearFarScalar} [translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera. * @property {Property | DistanceDisplayCondition} [distanceDisplayCondition] A Property specifying at what distance from the camera that this point will be displayed. * @property {Property | number} [disableDepthTestDistance] A Property specifying the distance from the camera at which to disable the depth test to. + * @property {Property | SplitDirection} [splitDirection] A Property specifying the {@link SplitDirection} split to apply to this point. */ /** @@ -51,6 +52,8 @@ function PointGraphics(options) { this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; + this._splitDirection = undefined; + this._splitDirectionSubscription = undefined; this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } @@ -154,6 +157,14 @@ Object.defineProperties(PointGraphics.prototype, { disableDepthTestDistance: createPropertyDescriptor( "disableDepthTestDistance" ), + + /** + * Gets or sets the Property specifying the {@link SplitDirection} of this point. + * @memberof PointGraphics.prototype + * @type {Property|undefined} + * @default SplitDirection.NONE + */ + splitDirection: createPropertyDescriptor("splitDirection"), }); /** @@ -176,6 +187,7 @@ PointGraphics.prototype.clone = function (result) { result.translucencyByDistance = this._translucencyByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; + result.splitDirection = this.splitDirection; return result; }; @@ -217,5 +229,10 @@ PointGraphics.prototype.merge = function (source) { this.disableDepthTestDistance, source.disableDepthTestDistance ); + + this.splitDirection = defaultValue( + this.splitDirection, + source.splitDirection + ); }; export default PointGraphics; diff --git a/packages/engine/Source/DataSources/PointVisualizer.js b/packages/engine/Source/DataSources/PointVisualizer.js index 2210646daa9a..330050669749 100644 --- a/packages/engine/Source/DataSources/PointVisualizer.js +++ b/packages/engine/Source/DataSources/PointVisualizer.js @@ -10,12 +10,14 @@ import createBillboardPointCallback from "../Scene/createBillboardPointCallback. import HeightReference from "../Scene/HeightReference.js"; import BoundingSphereState from "./BoundingSphereState.js"; import Property from "./Property.js"; +import SplitDirection from "../Scene/SplitDirection.js" const defaultColor = Color.WHITE; const defaultOutlineColor = Color.BLACK; const defaultOutlineWidth = 0.0; const defaultPixelSize = 1.0; const defaultDisableDepthTestDistance = 0.0; +const defaultSplitDirection = SplitDirection.NONE; const colorScratch = new Color(); const positionScratch = new Cartesian3(); @@ -192,6 +194,11 @@ PointVisualizer.prototype.update = function (time) { time, defaultDisableDepthTestDistance ); + pointPrimitive.splitDirection = Property.getValueOrDefault( + pointGraphics._splitDirection, + time, + defaultSplitDirection + ); } else if (defined(billboard)) { billboard.show = true; billboard.position = position; From 2681891b2f574bc873e6dc2f3ce47fb108b1ef7f Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 14 May 2024 11:30:57 +0300 Subject: [PATCH 03/13] Specs Update Updated Specs PointPrimitive Collection, Point Graphics and PointVisualizer to work with the new splitDirection property --- .../Specs/DataSources/PointGraphicsSpec.js | 18 ++++++++++++++++++ .../Specs/DataSources/PointVisualizerSpec.js | 9 +++++++++ .../Scene/PointPrimitiveCollectionSpec.js | 6 ++++++ 3 files changed, 33 insertions(+) diff --git a/packages/engine/Specs/DataSources/PointGraphicsSpec.js b/packages/engine/Specs/DataSources/PointGraphicsSpec.js index 1ff48e39dbd6..e48878dc4406 100644 --- a/packages/engine/Specs/DataSources/PointGraphicsSpec.js +++ b/packages/engine/Specs/DataSources/PointGraphicsSpec.js @@ -5,6 +5,7 @@ import { ConstantProperty, PointGraphics, HeightReference, + SplitDirection, } from "../../index.js"; describe("DataSources/PointGraphics", function () { @@ -19,6 +20,7 @@ describe("DataSources/PointGraphics", function () { heightReference: HeightReference.RELATIVE_TO_GROUND, distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, + splitDirection: SplitDirection.LEFT, }; const point = new PointGraphics(options); @@ -31,6 +33,7 @@ describe("DataSources/PointGraphics", function () { expect(point.heightReference).toBeInstanceOf(ConstantProperty); expect(point.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(point.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); + expect(point.splitDirection).toBeInstanceOf(ConstantProperty); expect(point.color.getValue()).toEqual(options.color); expect(point.pixelSize.getValue()).toEqual(options.pixelSize); @@ -45,6 +48,9 @@ describe("DataSources/PointGraphics", function () { expect(point.disableDepthTestDistance.getValue()).toEqual( options.disableDepthTestDistance ); + expect(point.splitDirection.getValue()).toEqual( + options.splitDirection + ); }); it("merge assigns unassigned properties", function () { @@ -62,6 +68,7 @@ describe("DataSources/PointGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const target = new PointGraphics(); target.merge(source); @@ -78,6 +85,9 @@ describe("DataSources/PointGraphics", function () { expect(target.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); + expect(target.splitDirection).toBe( + source.splitDirection + ); }); it("merge does not assign assigned properties", function () { @@ -95,6 +105,7 @@ describe("DataSources/PointGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const color = new ConstantProperty(Color.WHITE); const pixelSize = new ConstantProperty(1); @@ -108,6 +119,7 @@ describe("DataSources/PointGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); const disableDepthTestDistance = new ConstantProperty(20.0); + const splitDirection = new ConstantProperty(SplitDirection.RIGHT); const target = new PointGraphics(); target.color = color; @@ -119,6 +131,7 @@ describe("DataSources/PointGraphics", function () { target.heightReference = heightReference; target.distanceDisplayCondition = distanDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; + target.splitDirection = splitDirection; target.merge(source); expect(target.color).toBe(color); @@ -130,6 +143,7 @@ describe("DataSources/PointGraphics", function () { expect(target.heightReference).toBe(heightReference); expect(target.distanceDisplayCondition).toBe(distanDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); + expect(target.splitDirection).toBe(splitDirection); }); it("clone works", function () { @@ -147,6 +161,7 @@ describe("DataSources/PointGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const result = source.clone(); expect(result.color).toBe(source.color); @@ -162,6 +177,9 @@ describe("DataSources/PointGraphics", function () { expect(result.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); + expect(result.splitDirection).toBe( + source.splitDirection + ); }); it("merge throws if source undefined", function () { diff --git a/packages/engine/Specs/DataSources/PointVisualizerSpec.js b/packages/engine/Specs/DataSources/PointVisualizerSpec.js index 95ccba067581..0fa97473b4af 100644 --- a/packages/engine/Specs/DataSources/PointVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/PointVisualizerSpec.js @@ -17,6 +17,7 @@ import { BillboardCollection, HeightReference, PointPrimitiveCollection, + SplitDirection, } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; @@ -151,6 +152,7 @@ describe( scaleByDistance: new NearFarScalar(11, 12, 13, 14), distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, + splitDirection: SplitDirection.LEFT, }, }); const point = entity.point; @@ -180,6 +182,9 @@ describe( expect(pointPrimitive.disableDepthTestDistance).toEqual( point.disableDepthTestDistance.getValue(time) ); + expect(pointPrimitive.splitDirection).toEqual( + point.splitDirection.getValue(time) + ); point.color = new Color(0.15, 0.16, 0.17, 0.18); point.outlineColor = new Color(0.19, 0.2, 0.21, 0.22); @@ -191,6 +196,7 @@ describe( 1000000.0 ); point.disableDepthTestDistance = 20.0; + point.splitDirection = SplitDirection.RIGHT; visualizer.update(time); @@ -212,6 +218,9 @@ describe( expect(pointPrimitive.disableDepthTestDistance).toEqual( point.disableDepthTestDistance.getValue(time) ); + expect(pointPrimitive.splitDirection).toEqual( + point.splitDirection.getValue(time) + ); point.show = false; visualizer.update(time); diff --git a/packages/engine/Specs/Scene/PointPrimitiveCollectionSpec.js b/packages/engine/Specs/Scene/PointPrimitiveCollectionSpec.js index fc5844f0bdfe..748cb3175d1a 100644 --- a/packages/engine/Specs/Scene/PointPrimitiveCollectionSpec.js +++ b/packages/engine/Specs/Scene/PointPrimitiveCollectionSpec.js @@ -10,6 +10,7 @@ import { BlendOption, PointPrimitive, PointPrimitiveCollection, + SplitDirection, } from "../../index.js"; import { Math as CesiumMath } from "../../index.js"; @@ -65,6 +66,7 @@ describe( expect(p.distanceDisplayCondition).not.toBeDefined(); expect(p.disableDepthTestDistance).toEqual(0.0); expect(p.id).not.toBeDefined(); + expect(p.splitDirection).toEqual(SplitDirection.NONE); }); it("can add and remove before first render.", function () { @@ -96,6 +98,7 @@ describe( distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, id: "id", + splitDirection: SplitDirection.LEFT, }); expect(p.show).toEqual(false); @@ -121,6 +124,7 @@ describe( ); expect(p.disableDepthTestDistance).toEqual(10.0); expect(p.id).toEqual("id"); + expect(p.splitDirection).toEqual(SplitDirection.LEFT); }); it("sets pointPrimitive properties", function () { @@ -135,6 +139,7 @@ describe( p.translucencyByDistance = new NearFarScalar(1.0e6, 1.0, 1.0e8, 0.0); p.distanceDisplayCondition = new DistanceDisplayCondition(10.0, 100.0); p.disableDepthTestDistance = 10.0; + p.splitDirection = SplitDirection.LEFT; expect(p.show).toEqual(false); expect(p.position).toEqual(new Cartesian3(1.0, 2.0, 3.0)); @@ -158,6 +163,7 @@ describe( new DistanceDisplayCondition(10.0, 100.0) ); expect(p.disableDepthTestDistance).toEqual(10.0); + expect(p.splitDirection).toEqual(SplitDirection.LEFT); }); it("is not destroyed", function () { From d141e935582949cc2f57af21a29fc542fb33ddd9 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 14 May 2024 12:18:27 +0300 Subject: [PATCH 04/13] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0ab8b351ec62..368a4124da6e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -389,3 +389,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu - [dslming](https://github.com/dslming) - [Peter A. Jonsson](https://github.com/pjonsson) - [Zhongxiang Wang](https://github.com/plainheart) +- [Vladislav Yunev](https://github.com/YunVlad) From 90fba653c030ec88ced629d43a190117a38558ec Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 14 May 2024 12:42:24 +0300 Subject: [PATCH 05/13] Update CHANGES.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c6577709d2b1..3c8d60d4fcba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Change Log +- Added SplitDirection property for display PointPrimitive relative to the `Scene.splitPosition`. + ### 1.118 #### @cesium/engine From 2a6e949706289d15183d33822ceff5cbb44c4c4e Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 14 May 2024 16:22:54 +0300 Subject: [PATCH 06/13] Fixed formatting Fixed formatting in PointGraphics, PointVisualizer, PointPrimitive, PointGraphicsSpec --- packages/engine/Source/DataSources/PointGraphics.js | 2 +- .../engine/Source/DataSources/PointVisualizer.js | 2 +- packages/engine/Source/Scene/PointPrimitive.js | 2 +- .../engine/Specs/DataSources/PointGraphicsSpec.js | 12 +++--------- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/engine/Source/DataSources/PointGraphics.js b/packages/engine/Source/DataSources/PointGraphics.js index 4e287c605efe..87cacbeb827f 100644 --- a/packages/engine/Source/DataSources/PointGraphics.js +++ b/packages/engine/Source/DataSources/PointGraphics.js @@ -164,7 +164,7 @@ Object.defineProperties(PointGraphics.prototype, { * @type {Property|undefined} * @default SplitDirection.NONE */ - splitDirection: createPropertyDescriptor("splitDirection"), + splitDirection: createPropertyDescriptor("splitDirection"), }); /** diff --git a/packages/engine/Source/DataSources/PointVisualizer.js b/packages/engine/Source/DataSources/PointVisualizer.js index 330050669749..c1b252a1d692 100644 --- a/packages/engine/Source/DataSources/PointVisualizer.js +++ b/packages/engine/Source/DataSources/PointVisualizer.js @@ -10,7 +10,7 @@ import createBillboardPointCallback from "../Scene/createBillboardPointCallback. import HeightReference from "../Scene/HeightReference.js"; import BoundingSphereState from "./BoundingSphereState.js"; import Property from "./Property.js"; -import SplitDirection from "../Scene/SplitDirection.js" +import SplitDirection from "../Scene/SplitDirection.js"; const defaultColor = Color.WHITE; const defaultOutlineColor = Color.BLACK; diff --git a/packages/engine/Source/Scene/PointPrimitive.js b/packages/engine/Source/Scene/PointPrimitive.js index a4c9cf979bb9..dc911db6eca9 100644 --- a/packages/engine/Source/Scene/PointPrimitive.js +++ b/packages/engine/Source/Scene/PointPrimitive.js @@ -11,7 +11,7 @@ import Matrix4 from "../Core/Matrix4.js"; import NearFarScalar from "../Core/NearFarScalar.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; -import SplitDirection from "./SplitDirection.js" +import SplitDirection from "./SplitDirection.js"; /** *
diff --git a/packages/engine/Specs/DataSources/PointGraphicsSpec.js b/packages/engine/Specs/DataSources/PointGraphicsSpec.js index e48878dc4406..699e801e71de 100644 --- a/packages/engine/Specs/DataSources/PointGraphicsSpec.js +++ b/packages/engine/Specs/DataSources/PointGraphicsSpec.js @@ -48,9 +48,7 @@ describe("DataSources/PointGraphics", function () { expect(point.disableDepthTestDistance.getValue()).toEqual( options.disableDepthTestDistance ); - expect(point.splitDirection.getValue()).toEqual( - options.splitDirection - ); + expect(point.splitDirection.getValue()).toEqual(options.splitDirection); }); it("merge assigns unassigned properties", function () { @@ -85,9 +83,7 @@ describe("DataSources/PointGraphics", function () { expect(target.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); - expect(target.splitDirection).toBe( - source.splitDirection - ); + expect(target.splitDirection).toBe(source.splitDirection); }); it("merge does not assign assigned properties", function () { @@ -177,9 +173,7 @@ describe("DataSources/PointGraphics", function () { expect(result.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); - expect(result.splitDirection).toBe( - source.splitDirection - ); + expect(result.splitDirection).toBe(source.splitDirection); }); it("merge throws if source undefined", function () { From 65431b370a0849c6df19295bbf64ac9253ef4f21 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Wed, 15 May 2024 15:02:19 +0300 Subject: [PATCH 07/13] Update CHANGES.md Resolving conflict --- CHANGES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3c8d60d4fcba..01fb57271203 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,16 +2,17 @@ - Added SplitDirection property for display PointPrimitive relative to the `Scene.splitPosition`. -### 1.118 +### 1.118 - 2024-06-01 #### @cesium/engine ##### Fixes :wrench: +- Fixed a bug where `scene.pickPosition` returned incorrect results against the globe when `depthTestAgainstTerrain` is `false`. [#4368](https://github.com/CesiumGS/cesium/issues/4368) - Fixed a bug where `TaskProcessor` worker loading would check the worker module ID rather than the absolute URL when determining if it is cross-origin. [#11833](https://github.com/CesiumGS/cesium/pull/11833) - Fixed a bug where cross-origin workers would error when loaded with the CommonJS `importScripts` shim instead of an ESM `import`. [#11833](https://github.com/CesiumGS/cesium/pull/11833) -### 1.117 +### 1.117 - 2024-05-01 #### @cesium/engine From 16f21fa599121d7728f37a10fe8acf69ebfe683a Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 28 May 2024 14:10:56 +0300 Subject: [PATCH 08/13] Add splitDirection to Billboard Added the splitDirection property to Billboard. Updated BillboardCollection and shaders to work with the property. Updated BillboardGraphics and BillboardVisualizer. --- .../Source/DataSources/BillboardGraphics.js | 16 ++++++ .../Source/DataSources/BillboardVisualizer.js | 7 +++ packages/engine/Source/Scene/Billboard.js | 31 +++++++++++- .../Source/Scene/BillboardCollection.js | 49 +++++++++++++++++++ .../Source/Shaders/BillboardCollectionFS.glsl | 4 ++ .../Source/Shaders/BillboardCollectionVS.glsl | 4 +- 6 files changed, 108 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/DataSources/BillboardGraphics.js b/packages/engine/Source/DataSources/BillboardGraphics.js index e6686ecaf4ff..bdd950736c2b 100644 --- a/packages/engine/Source/DataSources/BillboardGraphics.js +++ b/packages/engine/Source/DataSources/BillboardGraphics.js @@ -29,6 +29,7 @@ import createPropertyDescriptor from "./createPropertyDescriptor.js"; * @property {Property | BoundingRectangle} [imageSubRegion] A Property specifying a {@link BoundingRectangle} that defines a sub-region of the image to use for the billboard, rather than the entire image, measured in pixels from the bottom-left. * @property {Property | DistanceDisplayCondition} [distanceDisplayCondition] A Property specifying at what distance from the camera that this billboard will be displayed. * @property {Property | number} [disableDepthTestDistance] A Property specifying the distance from the camera at which to disable the depth test to. + * @property {Property | SplitDirection} [splitDirection] A Property specifying the {@link SplitDirection} of the billboard. */ /** @@ -89,6 +90,8 @@ function BillboardGraphics(options) { this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; + this._splitDirection = undefined; + this._splitDirectionSubscription = undefined; this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } @@ -330,6 +333,14 @@ Object.defineProperties(BillboardGraphics.prototype, { disableDepthTestDistance: createPropertyDescriptor( "disableDepthTestDistance" ), + + /** + * Gets or sets the Property specifying the {@link SplitDirection} of this billboard. + * @memberof BillboardGraphics.prototype + * @type {Property|undefined} + * @default SplitDirection.NONE + */ + splitDirection: createPropertyDescriptor("splitDirection"), }); /** @@ -362,6 +373,7 @@ BillboardGraphics.prototype.clone = function (result) { result.imageSubRegion = this._imageSubRegion; result.distanceDisplayCondition = this._distanceDisplayCondition; result.disableDepthTestDistance = this._disableDepthTestDistance; + result.splitDirection = this._splitDirection; return result; }; @@ -425,5 +437,9 @@ BillboardGraphics.prototype.merge = function (source) { this._disableDepthTestDistance, source.disableDepthTestDistance ); + this.splitDirection = defaultValue( + this.splitDirection, + source.splitDirection + ); }; export default BillboardGraphics; diff --git a/packages/engine/Source/DataSources/BillboardVisualizer.js b/packages/engine/Source/DataSources/BillboardVisualizer.js index 954e72047a6a..a8cff2aa4053 100644 --- a/packages/engine/Source/DataSources/BillboardVisualizer.js +++ b/packages/engine/Source/DataSources/BillboardVisualizer.js @@ -13,6 +13,7 @@ import HorizontalOrigin from "../Scene/HorizontalOrigin.js"; import VerticalOrigin from "../Scene/VerticalOrigin.js"; import BoundingSphereState from "./BoundingSphereState.js"; import Property from "./Property.js"; +import SplitDirection from "../Scene/SplitDirection.js"; const defaultColor = Color.WHITE; const defaultEyeOffset = Cartesian3.ZERO; @@ -24,6 +25,7 @@ const defaultAlignedAxis = Cartesian3.ZERO; const defaultHorizontalOrigin = HorizontalOrigin.CENTER; const defaultVerticalOrigin = VerticalOrigin.CENTER; const defaultSizeInMeters = false; +const defaultSplitDirection = SplitDirection.NONE; const positionScratch = new Cartesian3(); const colorScratch = new Color(); @@ -219,6 +221,11 @@ BillboardVisualizer.prototype.update = function (time) { billboardGraphics._disableDepthTestDistance, time ); + billboard.splitDirection = Property.getValueOrDefault( + billboardGraphics._splitDirection, + time, + defaultSplitDirection + ); const subRegion = Property.getValueOrUndefined( billboardGraphics._imageSubRegion, diff --git a/packages/engine/Source/Scene/Billboard.js b/packages/engine/Source/Scene/Billboard.js index f7c0264ae1fb..cdf018b10c5c 100644 --- a/packages/engine/Source/Scene/Billboard.js +++ b/packages/engine/Source/Scene/Billboard.js @@ -21,6 +21,7 @@ import HorizontalOrigin from "./HorizontalOrigin.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; import VerticalOrigin from "./VerticalOrigin.js"; +import SplitDirection from "./SplitDirection.js"; /** * @typedef {object} Billboard.ConstructorOptions @@ -49,6 +50,7 @@ import VerticalOrigin from "./VerticalOrigin.js"; * @property {BoundingRectangle} [imageSubRegion] A {@link BoundingRectangle} Specifying the sub-region of the image to use for the billboard, rather than the entire image. * @property {DistanceDisplayCondition} [distanceDisplayCondition] A {@link DistanceDisplayCondition} Specifying the distance from the camera at which this billboard will be displayed. * @property {number} [disableDepthTestDistance] A number specifying the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. + * @property {SplitDirection} [splitDirection] A {@link SplitDirection} Specifying the split property of the billboard. */ /** @@ -250,6 +252,11 @@ function Billboard(options, billboardCollection) { this._outlineWidth = defaultValue(options.outlineWidth, 0.0); this._updateClamping(); + + this._splitDirection = defaultValue( + options.splitDirection, + SplitDirection.NONE + ); } const SHOW_INDEX = (Billboard.SHOW_INDEX = 0); @@ -270,7 +277,8 @@ const DISTANCE_DISPLAY_CONDITION = (Billboard.DISTANCE_DISPLAY_CONDITION = 14); const DISABLE_DEPTH_DISTANCE = (Billboard.DISABLE_DEPTH_DISTANCE = 15); Billboard.TEXTURE_COORDINATE_BOUNDS = 16; const SDF_INDEX = (Billboard.SDF_INDEX = 17); -Billboard.NUMBER_OF_PROPERTIES = 18; +const SPLIT_DIRECTION_INDEX = (Billboard.SPLIT_DIRECTION_INDEX = 18); +Billboard.NUMBER_OF_PROPERTIES = 19; function makeDirty(billboard, propertyChanged) { const billboardCollection = billboard._billboardCollection; @@ -1072,6 +1080,24 @@ Object.defineProperties(Billboard.prototype, { } }, }, + + /** + * Gets or sets the {@link SplitDirection} of this billboard. + * @memberof Billboard.prototype + * @type {SplitDirection} + * @default {@link SplitDirection.NONE} + */ + splitDirection: { + get: function () { + return this._splitDirection; + }, + set: function (value) { + if (this._splitDirection !== value) { + this._splitDirection = value; + makeDirty(this, SPLIT_DIRECTION_INDEX); + } + }, + }, }); Billboard.prototype.getPickId = function (context) { @@ -1565,7 +1591,8 @@ Billboard.prototype.equals = function (other) { this._distanceDisplayCondition, other._distanceDisplayCondition ) && - this._disableDepthTestDistance === other._disableDepthTestDistance) + this._disableDepthTestDistance === other._disableDepthTestDistance && + this._splitDirection === other._splitDirection) ); }; diff --git a/packages/engine/Source/Scene/BillboardCollection.js b/packages/engine/Source/Scene/BillboardCollection.js index f62dc2dffea4..b4d2ffdf7ab9 100644 --- a/packages/engine/Source/Scene/BillboardCollection.js +++ b/packages/engine/Source/Scene/BillboardCollection.js @@ -54,6 +54,7 @@ const DISTANCE_DISPLAY_CONDITION_INDEX = Billboard.DISTANCE_DISPLAY_CONDITION; const DISABLE_DEPTH_DISTANCE = Billboard.DISABLE_DEPTH_DISTANCE; const TEXTURE_COORDINATE_BOUNDS = Billboard.TEXTURE_COORDINATE_BOUNDS; const SDF_INDEX = Billboard.SDF_INDEX; +const SPLIT_DIRECTION_INDEX = Billboard.SPLIT_DIRECTION_INDEX; const NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES; let attributeLocations; @@ -71,6 +72,7 @@ const attributeLocationsBatched = { textureCoordinateBoundsOrLabelTranslate: 9, a_batchId: 10, sdf: 11, + splitDirection: 12, }; const attributeLocationsInstanced = { @@ -87,6 +89,7 @@ const attributeLocationsInstanced = { textureCoordinateBoundsOrLabelTranslate: 10, a_batchId: 11, sdf: 12, + splitDirection: 13, }; /** @@ -311,6 +314,7 @@ function BillboardCollection(options) { BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX BufferUsage.STATIC_DRAW, // TEXTURE_COORDINATE_BOUNDS + BufferUsage.STATIC_DRAW, // SPLIT_DIRECTION_INDEX ]; this._highlightColor = Color.clone(Color.WHITE); // Only used by Vector3DTilePoints @@ -777,6 +781,12 @@ function createVAF( componentDatatype: ComponentDatatype.FLOAT, usage: buffersUsage[TEXTURE_COORDINATE_BOUNDS], }, + { + index: attributeLocations.splitDirection, + componentsPerAttribute: 1, + componentDatatype: ComponentDatatype.FLOAT, + usage: buffersUsage[SPLIT_DIRECTION_INDEX], + }, ]; // Instancing requires one non-instanced attribute. @@ -1586,6 +1596,34 @@ function writeSDF( } } +function writeSplitDirection( + billboardCollection, + frameState, + textureAtlasCoordinates, + vafWriters, + billboard +) { + const writer = vafWriters[attributeLocations.splitDirection]; + let direction = 0.0; + + const split = billboard.splitDirection; + if (defined(split)) { + direction = split; + } + + let i; + if (billboardCollection._instanced) { + i = billboard._index; + writer(i, direction); + } else { + i = billboard._index * 4; + writer(i + 0, direction); + writer(i + 1, direction); + writer(i + 2, direction); + writer(i + 3, direction); + } +} + function writeBillboard( billboardCollection, frameState, @@ -1670,6 +1708,13 @@ function writeBillboard( vafWriters, billboard ); + writeSplitDirection( + billboardCollection, + frameState, + textureAtlasCoordinates, + vafWriters, + billboard + ); } function recomputeActualPositions( @@ -1981,6 +2026,10 @@ BillboardCollection.prototype.update = function (frameState) { writers.push(writeSDF); } + if (properties[SPLIT_DIRECTION_INDEX]) { + writers.push(writeSplitDirection); + } + const numWriters = writers.length; vafWriters = this._vaf.writers; diff --git a/packages/engine/Source/Shaders/BillboardCollectionFS.glsl b/packages/engine/Source/Shaders/BillboardCollectionFS.glsl index d4c72d2c3bbd..4a6d8731ddd9 100644 --- a/packages/engine/Source/Shaders/BillboardCollectionFS.glsl +++ b/packages/engine/Source/Shaders/BillboardCollectionFS.glsl @@ -7,6 +7,7 @@ uniform vec4 u_highlightColor; in vec2 v_textureCoordinates; in vec4 v_pickColor; in vec4 v_color; +in float v_splitDirection; #ifdef SDF in vec4 v_outlineColor; @@ -86,6 +87,9 @@ vec4 getSDFColor(vec2 position, float outlineWidth, vec4 outlineColor, float smo void main() { + if (v_splitDirection < 0.0 && gl_FragCoord.x > czm_splitPosition) discard; + if (v_splitDirection > 0.0 && gl_FragCoord.x < czm_splitPosition) discard; + vec4 color = texture(u_atlas, v_textureCoordinates); #ifdef SDF diff --git a/packages/engine/Source/Shaders/BillboardCollectionVS.glsl b/packages/engine/Source/Shaders/BillboardCollectionVS.glsl index d8f927d07b59..2d454d487f38 100644 --- a/packages/engine/Source/Shaders/BillboardCollectionVS.glsl +++ b/packages/engine/Source/Shaders/BillboardCollectionVS.glsl @@ -11,6 +11,7 @@ in vec4 scaleByDistance; // near, nearScale, far, far in vec4 pixelOffsetScaleByDistance; // near, nearScale, far, farScale in vec4 compressedAttribute3; // distance display condition near, far, disableDepthTestDistance, dimensions in vec2 sdf; // sdf outline color (rgb) and width (w) +in float splitDirection; // splitDirection #if defined(VERTEX_DEPTH_CHECK) || defined(FRAGMENT_DEPTH_CHECK) in vec4 textureCoordinateBoundsOrLabelTranslate; // the min and max x and y values for the texture coordinates #endif @@ -28,6 +29,7 @@ out mat2 v_rotationMatrix; out vec4 v_pickColor; out vec4 v_color; +out float v_splitDirection; #ifdef SDF out vec4 v_outlineColor; out float v_outlineWidth; @@ -430,5 +432,5 @@ if (lengthSq < disableDepthTestDistance) { v_color = color; v_color.a *= translucency; - + v_splitDirection = splitDirection; } From 5fa741d232ddd36d79ba283a3939a128ef19161e Mon Sep 17 00:00:00 2001 From: YunVlad Date: Thu, 30 May 2024 18:48:55 +0300 Subject: [PATCH 09/13] Add Sandcastle example Add Sandcastle example for SplitDirection property for Points --- .../gallery/Points SplitDirection.html | 135 ++++++++++++++++++ .../gallery/Points SplitDirection.jpg | Bin 0 -> 46796 bytes 2 files changed, 135 insertions(+) create mode 100644 Apps/Sandcastle/gallery/Points SplitDirection.html create mode 100644 Apps/Sandcastle/gallery/Points SplitDirection.jpg diff --git a/Apps/Sandcastle/gallery/Points SplitDirection.html b/Apps/Sandcastle/gallery/Points SplitDirection.html new file mode 100644 index 000000000000..35dc2a965809 --- /dev/null +++ b/Apps/Sandcastle/gallery/Points SplitDirection.html @@ -0,0 +1,135 @@ + + + + + + + + + Cesium Demo + + + + + + +
+
+
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Points SplitDirection.jpg b/Apps/Sandcastle/gallery/Points SplitDirection.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f0367d7cac37d3268a62d57031e82b6771e7347b GIT binary patch literal 46796 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 literal 0 HcmV?d00001 From fffcf2bf5012a902c5d5e6c4ad197cafd97901fc Mon Sep 17 00:00:00 2001 From: YunVlad Date: Thu, 30 May 2024 19:01:18 +0300 Subject: [PATCH 10/13] Changed the transmission of the splitDirection attribute The splitDirection attribute was packaged as the 4th component to distanceDisplayConditionAndDisableDepth. Updated PointPrimitiveCollection and shader. --- .../Source/Scene/PointPrimitiveCollection.js | 56 ++++++------------- .../Shaders/PointPrimitiveCollectionVS.glsl | 17 +++--- 2 files changed, 24 insertions(+), 49 deletions(-) diff --git a/packages/engine/Source/Scene/PointPrimitiveCollection.js b/packages/engine/Source/Scene/PointPrimitiveCollection.js index a945c9feedc1..20ab1c5d6e1d 100644 --- a/packages/engine/Source/Scene/PointPrimitiveCollection.js +++ b/packages/engine/Source/Scene/PointPrimitiveCollection.js @@ -47,8 +47,7 @@ const attributeLocations = { compressedAttribute0: 2, // color, outlineColor, pick color compressedAttribute1: 3, // show, translucency by distance, some free space scaleByDistance: 4, - distanceDisplayConditionAndDisableDepth: 5, - splitDirection: 6, + distanceDisplayConditionAndDisableDepthAndSplitDirection: 5, }; /** @@ -218,7 +217,6 @@ function PointPrimitiveCollection(options) { BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX - BufferUsage.STATIC_DRAW, // SPLIT_DIRECTION_INDEX ]; const that = this; @@ -495,17 +493,12 @@ function createVAF(context, numberOfPointPrimitives, buffersUsage) { usage: buffersUsage[SCALE_BY_DISTANCE_INDEX], }, { - index: attributeLocations.distanceDisplayConditionAndDisableDepth, - componentsPerAttribute: 3, + index: + attributeLocations.distanceDisplayConditionAndDisableDepthAndSplitDirection, + componentsPerAttribute: 4, componentDatatype: ComponentDatatype.FLOAT, usage: buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX], }, - { - index: attributeLocations.splitDirection, - componentsPerAttribute: 1, - componentDatatype: ComponentDatatype.FLOAT, - usage: buffersUsage[SPLIT_DIRECTION_INDEX], - }, ], numberOfPointPrimitives ); // 1 vertex per pointPrimitive @@ -674,7 +667,7 @@ function writeScaleByDistance( writer(i, near, nearValue, far, farValue); } -function writeDistanceDisplayConditionAndDepthDisable( +function writeDistanceDisplayConditionAndDepthDisableAndSplitDirection( pointPrimitiveCollection, context, vafWriters, @@ -682,7 +675,10 @@ function writeDistanceDisplayConditionAndDepthDisable( ) { const i = pointPrimitive._index; const writer = - vafWriters[attributeLocations.distanceDisplayConditionAndDisableDepth]; + vafWriters[ + attributeLocations + .distanceDisplayConditionAndDisableDepthAndSplitDirection + ]; let near = 0.0; let far = Number.MAX_VALUE; @@ -706,25 +702,12 @@ function writeDistanceDisplayConditionAndDepthDisable( } } - writer(i, near, far, disableDepthTestDistance); -} - -function writeSplitDirection( - pointPrimitiveCollection, - context, - vafWriters, - pointPrimitive -) { - const i = pointPrimitive._index; - const writer = vafWriters[attributeLocations.splitDirection]; let direction = 0.0; - const split = pointPrimitive.splitDirection; if (defined(split)) { direction = split; } - - writer(i, direction); + writer(i, near, far, disableDepthTestDistance, direction); } function writePointPrimitive( @@ -757,13 +740,7 @@ function writePointPrimitive( vafWriters, pointPrimitive ); - writeDistanceDisplayConditionAndDepthDisable( - pointPrimitiveCollection, - context, - vafWriters, - pointPrimitive - ); - writeSplitDirection( + writeDistanceDisplayConditionAndDepthDisableAndSplitDirection( pointPrimitiveCollection, context, vafWriters, @@ -958,13 +935,12 @@ PointPrimitiveCollection.prototype.update = function (frameState) { if ( properties[DISTANCE_DISPLAY_CONDITION_INDEX] || - properties[DISABLE_DEPTH_DISTANCE_INDEX] + properties[DISABLE_DEPTH_DISTANCE_INDEX] || + properties[SPLIT_DIRECTION_INDEX] ) { - writers.push(writeDistanceDisplayConditionAndDepthDisable); - } - - if (properties[SPLIT_DIRECTION_INDEX]) { - writers.push(writeSplitDirection); + writers.push( + writeDistanceDisplayConditionAndDepthDisableAndSplitDirection + ); } const numWriters = writers.length; diff --git a/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl b/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl index 0e725388a042..936556b494f8 100644 --- a/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl +++ b/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl @@ -2,11 +2,10 @@ uniform float u_maxTotalPointSize; in vec4 positionHighAndSize; in vec4 positionLowAndOutline; -in vec4 compressedAttribute0; // color, outlineColor, pick color -in vec4 compressedAttribute1; // show, translucency by distance, some free space -in vec4 scaleByDistance; // near, nearScale, far, farScale -in vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance -in float splitDirection; // splitDirection +in vec4 compressedAttribute0; // color, outlineColor, pick color +in vec4 compressedAttribute1; // show, translucency by distance, some free space +in vec4 scaleByDistance; // near, nearScale, far, farScale +in vec4 distanceDisplayConditionAndDisableDepthAndSplitDirection; // near, far, disableDepthTestDistance, splitDirection out vec4 v_color; out vec4 v_outlineColor; @@ -137,8 +136,8 @@ void main() #endif #ifdef DISTANCE_DISPLAY_CONDITION - float nearSq = distanceDisplayConditionAndDisableDepth.x; - float farSq = distanceDisplayConditionAndDisableDepth.y; + float nearSq = distanceDisplayConditionAndDisableDepthAndSplitDirection.x; + float farSq = distanceDisplayConditionAndDisableDepthAndSplitDirection.y; if (lengthSq < nearSq || lengthSq > farSq) { // push vertex behind camera to force it to be clipped positionEC.xyz = vec3(0.0, 0.0, 1.0); @@ -149,7 +148,7 @@ void main() czm_vertexLogDepth(); #ifdef DISABLE_DEPTH_DISTANCE - float disableDepthTestDistance = distanceDisplayConditionAndDisableDepth.z; + float disableDepthTestDistance = distanceDisplayConditionAndDisableDepthAndSplitDirection.z; if (disableDepthTestDistance == 0.0 && czm_minimumDisableDepthTestDistance != 0.0) { disableDepthTestDistance = czm_minimumDisableDepthTestDistance; @@ -182,5 +181,5 @@ void main() gl_Position *= show; v_pickColor = pickColor; - v_splitDirection = splitDirection; + v_splitDirection = distanceDisplayConditionAndDisableDepthAndSplitDirection.w; } From fe68fb340894bcbfe78bb4638308dc24c76f9045 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 4 Jun 2024 14:27:39 +0300 Subject: [PATCH 11/13] Updated PointVisualizer and example Fixed a bug where the splitDirection property stopped working when applying the heightReference parameter. Updated the example for Billboards. --- .../gallery/Points SplitDirection.html | 135 ------------- Apps/Sandcastle/gallery/SplitDirection.html | 191 ++++++++++++++++++ ... SplitDirection.jpg => SplitDirection.jpg} | Bin .../Source/DataSources/PointVisualizer.js | 5 + 4 files changed, 196 insertions(+), 135 deletions(-) delete mode 100644 Apps/Sandcastle/gallery/Points SplitDirection.html create mode 100644 Apps/Sandcastle/gallery/SplitDirection.html rename Apps/Sandcastle/gallery/{Points SplitDirection.jpg => SplitDirection.jpg} (100%) diff --git a/Apps/Sandcastle/gallery/Points SplitDirection.html b/Apps/Sandcastle/gallery/Points SplitDirection.html deleted file mode 100644 index 35dc2a965809..000000000000 --- a/Apps/Sandcastle/gallery/Points SplitDirection.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - Cesium Demo - - - - - - -
-
-
-

Loading...

-
- - - diff --git a/Apps/Sandcastle/gallery/SplitDirection.html b/Apps/Sandcastle/gallery/SplitDirection.html new file mode 100644 index 000000000000..9afc74a99854 --- /dev/null +++ b/Apps/Sandcastle/gallery/SplitDirection.html @@ -0,0 +1,191 @@ + + + + + + + + + Cesium Demo + + + + + +
+
+
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Points SplitDirection.jpg b/Apps/Sandcastle/gallery/SplitDirection.jpg similarity index 100% rename from Apps/Sandcastle/gallery/Points SplitDirection.jpg rename to Apps/Sandcastle/gallery/SplitDirection.jpg diff --git a/packages/engine/Source/DataSources/PointVisualizer.js b/packages/engine/Source/DataSources/PointVisualizer.js index c1b252a1d692..0fe2f9f00c81 100644 --- a/packages/engine/Source/DataSources/PointVisualizer.js +++ b/packages/engine/Source/DataSources/PointVisualizer.js @@ -222,6 +222,11 @@ PointVisualizer.prototype.update = function (time) { time, defaultDisableDepthTestDistance ); + billboard.splitDirection = Property.getValueOrDefault( + pointGraphics._splitDirection, + time, + defaultSplitDirection + ); billboard.heightReference = heightReference; const newColor = Property.getValueOrDefault( From 505196a5073134b3ea91553a5c51affe483fee52 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 4 Jun 2024 15:14:43 +0300 Subject: [PATCH 12/13] Specs Update Updated Specs BillboardCollectionSpec, BillboardGraphicsSpec and BillboardVisualizerSpec to work with the new splitDirection property. Updated PointVisualizerSpec to work with billboard example. --- .../Specs/DataSources/BillboardGraphicsSpec.js | 14 +++++++++++++- .../Specs/DataSources/BillboardVisualizerSpec.js | 9 +++++++++ .../Specs/DataSources/PointVisualizerSpec.js | 4 ++++ .../engine/Specs/Scene/BillboardCollectionSpec.js | 6 ++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/engine/Specs/DataSources/BillboardGraphicsSpec.js b/packages/engine/Specs/DataSources/BillboardGraphicsSpec.js index 7e09f073df0b..6da4f355301c 100644 --- a/packages/engine/Specs/DataSources/BillboardGraphicsSpec.js +++ b/packages/engine/Specs/DataSources/BillboardGraphicsSpec.js @@ -9,6 +9,7 @@ import { HeightReference, HorizontalOrigin, VerticalOrigin, + SplitDirection, } from "../../index.js"; describe("DataSources/BillboardGraphics", function () { @@ -33,6 +34,7 @@ describe("DataSources/BillboardGraphics", function () { sizeInMeters: true, distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, + splitDirection: SplitDirection.LEFT, }; const billboard = new BillboardGraphics(options); @@ -56,6 +58,7 @@ describe("DataSources/BillboardGraphics", function () { expect(billboard.sizeInMeters).toBeInstanceOf(ConstantProperty); expect(billboard.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(billboard.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); + expect(billboard.splitDirection).toBeInstanceOf(ConstantProperty); expect(billboard.image.getValue()).toEqual(options.image); expect(billboard.rotation.getValue()).toEqual(options.rotation); @@ -89,6 +92,7 @@ describe("DataSources/BillboardGraphics", function () { expect(billboard.disableDepthTestDistance.getValue()).toEqual( options.disableDepthTestDistance ); + expect(billboard.splitDirection.getValue()).toEqual(options.splitDirection); }); it("merge assigns unassigned properties", function () { @@ -118,7 +122,8 @@ describe("DataSources/BillboardGraphics", function () { source.distanceDisplayCondition = new ConstantProperty( new DistanceDisplayCondition(10.0, 100.0) ); - source.disableDepthTestDistance = 10.0; + source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const target = new BillboardGraphics(); target.merge(source); @@ -149,6 +154,7 @@ describe("DataSources/BillboardGraphics", function () { expect(target.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); + expect(target.splitDirection).toBe(source.splitDirection); }); it("merge does not assign assigned properties", function () { @@ -179,6 +185,7 @@ describe("DataSources/BillboardGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const image = new ConstantProperty(""); const imageSubRegion = new ConstantProperty(); @@ -206,6 +213,7 @@ describe("DataSources/BillboardGraphics", function () { new DistanceDisplayCondition() ); const disableDepthTestDistance = new ConstantProperty(10.0); + const splitDirection = new ConstantProperty(SplitDirection.LEFT); const target = new BillboardGraphics(); target.image = image; @@ -228,6 +236,7 @@ describe("DataSources/BillboardGraphics", function () { target.sizeInMeters = sizeInMeters; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; + target.splitDirection = splitDirection; target.merge(source); @@ -251,6 +260,7 @@ describe("DataSources/BillboardGraphics", function () { expect(target.sizeInMeters).toBe(sizeInMeters); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); + expect(target.splitDirection).toBe(splitDirection); }); it("clone works", function () { @@ -281,6 +291,7 @@ describe("DataSources/BillboardGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const result = source.clone(); expect(result.image).toBe(source.image); @@ -309,6 +320,7 @@ describe("DataSources/BillboardGraphics", function () { expect(result.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); + expect(result.splitDirection).toBe(source.splitDirection); }); it("merge throws if source undefined", function () { diff --git a/packages/engine/Specs/DataSources/BillboardVisualizerSpec.js b/packages/engine/Specs/DataSources/BillboardVisualizerSpec.js index 90268aad7121..a01bec49ccbf 100644 --- a/packages/engine/Specs/DataSources/BillboardVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/BillboardVisualizerSpec.js @@ -17,6 +17,7 @@ import { HeightReference, HorizontalOrigin, VerticalOrigin, + SplitDirection, } from "../../index.js"; import createGlobe from "../../../../Specs/createGlobe.js"; @@ -167,6 +168,7 @@ describe( new DistanceDisplayCondition(10.0, 100.0) ); billboard.disableDepthTestDistance = new ConstantProperty(10.0); + billboard.splitDirection = new ConstantProperty(SplitDirection.LEFT); visualizer.update(time); @@ -220,6 +222,9 @@ describe( expect(bb.disableDepthTestDistance).toEqual( testObject.billboard.disableDepthTestDistance.getValue(time) ); + expect(bb.splitDirection).toEqual( + testObject.billboard.splitDirection.getValue(time) + ); expect(bb._imageSubRegion).toEqual( testObject.billboard.imageSubRegion.getValue(time) ); @@ -275,6 +280,7 @@ describe( new DistanceDisplayCondition(10.0, 100.0) ); billboard.disableDepthTestDistance = new ConstantProperty(10.0); + billboard.splitDirection = new ConstantProperty(SplitDirection.LEFT); visualizer.update(time); @@ -342,6 +348,9 @@ describe( expect(bb.disableDepthTestDistance).toEqual( testObject.billboard.disableDepthTestDistance.getValue(time) ); + expect(bb.splitDirection).toEqual( + testObject.billboard.splitDirection.getValue(time) + ); expect(bb.image).toBeDefined(); expect(bb._imageSubRegion).toEqual( testObject.billboard.imageSubRegion.getValue(time) diff --git a/packages/engine/Specs/DataSources/PointVisualizerSpec.js b/packages/engine/Specs/DataSources/PointVisualizerSpec.js index 0fa97473b4af..6f17a7e0b033 100644 --- a/packages/engine/Specs/DataSources/PointVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/PointVisualizerSpec.js @@ -245,6 +245,7 @@ describe( distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, heightReference: HeightReference.CLAMP_TO_GROUND, + splitDirection: SplitDirection.LEFT, }, }); const point = entity.point; @@ -267,6 +268,9 @@ describe( expect(billboard.disableDepthTestDistance).toEqual( point.disableDepthTestDistance.getValue(time) ); + expect(billboard.splitDirection).toEqual( + point.splitDirection.getValue(time) + ); //expect(billboard.color).toEqual(point.color.getValue(time)); //expect(billboard.outlineColor).toEqual(point.outlineColor.getValue(time)); //expect(billboard.outlineWidth).toEqual(point.outlineWidth.getValue(time)); diff --git a/packages/engine/Specs/Scene/BillboardCollectionSpec.js b/packages/engine/Specs/Scene/BillboardCollectionSpec.js index a47d52468462..5d1b2791bc85 100644 --- a/packages/engine/Specs/Scene/BillboardCollectionSpec.js +++ b/packages/engine/Specs/Scene/BillboardCollectionSpec.js @@ -22,6 +22,7 @@ import { HorizontalOrigin, TextureAtlas, VerticalOrigin, + SplitDirection, } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; @@ -120,6 +121,7 @@ describe( expect(b.sizeInMeters).toEqual(false); expect(b.distanceDisplayCondition).toBeUndefined(); expect(b.disableDepthTestDistance).toBeUndefined(); + expect(b.splitDirection).toEqual(SplitDirection.NONE); }); it("can add and remove before first update.", function () { @@ -155,6 +157,7 @@ describe( distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, id: "id", + splitDirection: SplitDirection.LEFT, }); expect(b.show).toEqual(false); @@ -188,6 +191,7 @@ describe( ); expect(b.disableDepthTestDistance).toEqual(10.0); expect(b.id).toEqual("id"); + expect(b.splitDirection).toEqual(SplitDirection.LEFT); }); it("sets billboard properties", function () { @@ -211,6 +215,7 @@ describe( b.sizeInMeters = true; b.distanceDisplayCondition = new DistanceDisplayCondition(10.0, 100.0); b.disableDepthTestDistance = 10.0; + b.splitDirection = SplitDirection.LEFT; expect(b.show).toEqual(false); expect(b.position).toEqual(new Cartesian3(1.0, 2.0, 3.0)); @@ -242,6 +247,7 @@ describe( new DistanceDisplayCondition(10.0, 100.0) ); expect(b.disableDepthTestDistance).toEqual(10.0); + expect(b.splitDirection).toEqual(SplitDirection.LEFT); }); it("required properties throw for undefined", function () { From dceb99a8418b9709f7a88d73fca9c2e23f957a4d Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 26 Jul 2024 10:46:44 -0400 Subject: [PATCH 13/13] Update CHANGES.md --- CHANGES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 170bca45ab45..816d0cb07cfc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,13 @@ # Change Log -- Added SplitDirection property for display PointPrimitive and Billboard relative to the `Scene.splitPosition`. [#11982](https://github.com/CesiumGS/cesium/pull/11982) - ### 1.120 - 2024-08-01 #### @cesium/engine +##### Additions :tada: + +- Added SplitDirection property for display PointPrimitive and Billboard relative to the `Scene.splitPosition`. [#11982](https://github.com/CesiumGS/cesium/pull/11982) + ##### Fixes :wrench: - Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063)