Repository: CesiumGS/cesium
Pull request #12467: Fixes issue #12465 (Entity Tracking broken)
Author: login: jfayot
Labels: name: priority - next release
Created: 2025-02-07T10:46:12Z | Merged: 2025-03-10T15:28:48Z

# Description

This is an attempt to fix broken entity tracking when viewFrom is set:
Restored `boundingSphereScratch` instead of `entityView.boundingSphere` inside `CesiumWidget._onTick`.

## Issue number and link

Fixes #12465 

## Testing plan

- Updated EntityViewSpec
- Tested [Entity tracking sandcastle](http://localhost:8080/Apps/Sandcastle/index.html?src=Entity%20tracking.html)
- Tested [Clamp Entities to Ground](http://localhost:8080/Apps/Sandcastle/index.html?src=Clamp%20Entities%20to%20Ground.html)
- Tested [this sandcastle](http://localhost:8080/Apps/Sandcastle/index.html#c=bVHda8IwEP9Xjj5VcKnDSZ2rMub2JuxB2FNA0uTswtKkJKnihv/70sbCPnwIubvc/T4u3Gjn4SDxiBaWoPEIa3SyrclbX0tpwvt8bbRnUqOlyeiBaqr3reZeGg1MiCepVGmYFekIvqgG2DItOHNeIRHIFbOY/myLCAC8Z0ftpT8F9iiD9LlER8JI2sMBNMbJjm0xyFsz60PE9JTsramfsbKILr3JZ2R2n+f5GO4mZDKdz6ejcYQoB/YFXEABZM0qXABNCMn62GURf7cxldmZA1rFTqTRFU0uMOf+PncWALpzUe1QIfcoXgY30VZoO3dmf28pVJNxUjh/UrgaxDzKujHWQ2tVGvR4rBvFgsmsbPkHesKdi6wARfZztBDyAFIsr/wVhOU7F172rVJb+Yk0WRVZ6P83qgwTUlev0XLX9n672sQiIaTIQnp90hujSmb/IH8D) which demonstrates the error when setting the viewer.selectedEntity
- Tested [this sandcastle](http://localhost:8080/Apps/Sandcastle/index.html#c=pVcLT+s2FP4rVqeJdKRpuWXTXSlo47HpSuiCVLqrjaLJJG5rzbEj22nprvjvO7YTxykZMK1CNDn2Of7Od15uKrjSaEPJlkh0ijjZoguiaJknv1lZtOil9v1CcI0pJ3LR658s+IIPh+iSLEGC9JqgQrAdg5cDBY+Kagp2UUQ5+vLr7OMxSoWQGeVYE9VPFjy1pzYbT9H9w0kt5mV+KyjXRjwejUZ+gQk+01hqkA8+tsRXPANhKMO63jr+MZS6ncdHXoaZprrMCEiPRiN33IIvhUQRIxpRkI9O4Gva4ILXw8M++rrgCD4eBOz0CA9B28EaeGEffQeMoCGKGg8H6Khv6AwMYQPa4zeGHOqBF75lyPOaFKVaR1U8L0ATnjAfJ0sp8kuykoQoAzM2lmPPhLXzXIX45yzzsUVa2FirlHASRLFaPa3SKCEcDFGiEpxl0dcak9s1qWlrAZ00j3GzvKWZXk/Q94EohwySFLNJnaUXggmZ/H51fX3zxW0D5M8+Qy8kAQ2E0fgS5SIjDFlwO/ADeN5SxkC8gQ1Aw6qVyN4/q3bltN5w0bkQOHM/eqjAWyst50tJJ+ggSYbwN8N5wcgl1nhoN6qhc+9SCk7C52TFHg9CPiinOSQBfSJsRv8Gen84btH1ZJZnKWaw9GE0itFrJCkCLofx7iJpS/XaMuX4xKBAnjSBryOUEwgPKogs4J2mJcMy4HFD+eoa7L6PzNfz5b7FcYtxeAWnXPl4sArnJrZQKy/ya/x2fp1fz6/2iavAa4nTv0jmfQrSxW4zTSQtpQQnP/GMPNl+UhH/S8lTg9oUFoTI8GXj0CLQofbk4i3eIVPA1q3Kcu3Zsjbord2GpmyviGqu9joYhyjeVksAsmE0asE/hEaDvg16DCN8pdcPVe8JLWZUkrQy97IJqfLRcKej8OAmknE4ixq1yHe5lxa5kDlmUAWRPzluQPRbCE3ai7woO/jewH5o/zC7DMMEKz0Ay3o9KAsUXX2e94F+yKbQU1jpcnH++dPdn3+02nv7qC6lVAqlQg/K4n9x0ToxbgNw+v+K72a5VER3osxLmBYF253vTHPBcv+Y8ZuY3X9JdCl5xwGmFzS50AHr1QOeOyqsLDJTXr51BVcV28Vs92+Fp5oDvqqcBVdFTfW0i/td1dJKiUqhs/ZCYw+2bfhh4npMUjRqe4b89o7IWh+Myit9Ys9c39sDXr/xH1NGuWlBotRAIVWovios6RNaMgrdUaLHchXomE/tSXsoJH78ti6He1DiDmcegqjPFTR8AqxBw9xgZsDYAdZMreomY1pPkwRkQ+SuGoIQ9sBCFEQ+tvdEx8YM8iaFBsGIydc7Idgjluel1oLDxfnOjAaUmbm96MXIp1GdO++aIM/vOWnOrZH/ckoJKWVu75k7oxf3pkrvGDmrh+FPNC+g7cE9hUVwRdEErijm/j58LMGQTlKl6lKeDkPVaUY3iGanHb8cUMqwUrCyLJm9ryx6Z9Mh7H+hygSG3wurGwgJwzuzbX10du2ESZJMh/DarakdN3uW/wE) to test the original issue of a "flickering" entity when it moves (it should not flicker)

# Author checklist

- [X] I have submitted a Contributor License Agreement
- [X] I have added my name to `CONTRIBUTORS.md`
- [X] I have updated `CHANGES.md` with a short summary of my change
- [X] I have added or updated unit tests to ensure consistent code coverage
- [X] I have updated the inline documentation, and included code examples where relevant
- [X] I have performed a self-review of my code


Linked issue #12465: Entity Tracking sandcastle broken in 1.126
### What happened?

The Entity Tracking sandcastle appears to be broken in 1.126. Worked in 1.125.

https://sandcastle.cesium.com/?src=Entity%20tracking.html

It doesn't appear to track either the satellite or drone in any reference frame.

If I comment out the following lines:

    satellite.viewFrom = new Cesium.Cartesian3(-300, 20, 100);
    drone.viewFrom = new Cesium.Cartesian3(-50, 0, 5);

It starts tracking.

### Reproduction steps

1. View https://sandcastle.cesium.com/?src=Entity%20tracking.html
2. Click drone
3. Camera doesn't track drone
...


### Sandcastle example

https://sandcastle.cesium.com/?src=Entity%20tracking.html

### Environment

Browser: Edge / Firefox / Chrome
CesiumJS Version: 1.126
Operating System: Windows 11

Linked issue comments: user: login: jjspace
created_at: 2025-02-06T16:47:49Z
body: I can confirm the issue is happening but haven't really looked into it. At initial glance I do wonder if it's related to this pr https://github.com/CesiumGS/cesium/pull/12429? It was targeted at some changes to tracking entities so it's possible. 
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-02-06T19:01:19Z
body: You're right @jjspace ! Git bisect points to commit f01671eac8ab80a78e8b6095512b3b80e1b31e7d
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-02-06T19:11:19Z
body: More specifically, restoring `boundingSphereScratch` instead of `entityView.boundingSphere` inside `CesiumWidget._onTick` fixes this issue...
author_association: CONTRIBUTOR

Diff:
diff --git a/CHANGES.md b/CHANGES.md
index db80b0ba1c90..81cf2d3999f5 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,13 @@
 # Change Log
 
+## 1.128 - 2025-04-01
+
+### @cesium/engine
+
+#### Fixes :wrench:
+
+- Fixed broken Entity Tracking [sandcastle](https://sandcastle.cesium.com/?src=Entity%20tracking.html). [#12467](https://github.com/CesiumGS/cesium/pull/12467)
+
 ## 1.127 - 2025-03-03
 
 ### @cesium/engine
diff --git a/packages/engine/Source/Widget/CesiumWidget.js b/packages/engine/Source/Widget/CesiumWidget.js
index b4c34adffd7a..d9a573675c5e 100644
--- a/packages/engine/Source/Widget/CesiumWidget.js
+++ b/packages/engine/Source/Widget/CesiumWidget.js
@@ -1134,6 +1134,8 @@ CesiumWidget.prototype._updateCanAnimate = function (isUpdated) {
   this._clock.canAnimate = isUpdated;
 };
 
+const boundingSphereScratch = new BoundingSphere();
+
 /**
  * @private
  */
@@ -1146,15 +1148,15 @@ CesiumWidget.prototype._onTick = function (clock) {
   }
 
   const entityView = this._entityView;
-  if (defined(entityView) && defined(entityView.boundingSphere)) {
+  if (defined(entityView)) {
     const trackedEntity = this._trackedEntity;
     const trackedState = this._dataSourceDisplay.getBoundingSphere(
       trackedEntity,
       false,
-      entityView.boundingSphere,
+      entityView.boundingSphere ?? boundingSphereScratch,
     );
     if (trackedState === BoundingSphereState.DONE) {
-      entityView.update(time, entityView.boundingSphere);
+      entityView.update(time);
     }
   }
 };
diff --git a/packages/engine/Specs/DataSources/EntityViewSpec.js b/packages/engine/Specs/DataSources/EntityViewSpec.js
index 61223cc1dd0b..687e61245884 100644
--- a/packages/engine/Specs/DataSources/EntityViewSpec.js
+++ b/packages/engine/Specs/DataSources/EntityViewSpec.js
@@ -89,43 +89,74 @@ describe(
       expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
     });
 
-    it("uses entity bounding sphere", function () {
-      const sampleOffset = new Cartesian3(
-        -1.3322676295501878e-15,
-        -7.348469228349534,
-        7.3484692283495345,
-      );
+    it("uses provided bounding sphere", function () {
+      const bs = new BoundingSphere(new Cartesian3(3, 4, 5), 6);
+      scene.camera.viewBoundingSphere(bs);
+      const positionWC = scene.camera.positionWC.clone();
+
       const entity = new Entity();
       entity.position = new ConstantPositionProperty(
         Cartesian3.fromDegrees(0.0, 0.0),
       );
       const view = new EntityView(entity, scene, undefined);
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.INERTIAL;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.VELOCITY;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.ENU;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
+    });
+
+    it("jumps to updated bounding sphere", function () {
+      const bs1 = new BoundingSphere(new Cartesian3(1, 2, 3), 4);
+      scene.camera.viewBoundingSphere(bs1);
+      const positionWC1 = scene.camera.positionWC.clone();
+
+      const bs2 = new BoundingSphere(new Cartesian3(3, 4, 5), 4);
+      scene.camera.viewBoundingSphere(bs2);
+      const positionWC2 = scene.camera.positionWC.clone();
+
+      const entity = new Entity();
+      entity.position = new ConstantPositionProperty(
+        Cartesian3.fromDegrees(0.0, 0.0),
       );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      const view = new EntityView(entity, scene, undefined);
+      view.update(JulianDate.now(), bs1);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC1, 1e-10);
+
+      view.boundingSphere.center = bs2.center;
+      view.update(JulianDate.now());
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC2, 1e-10);
+    });
+
+    it("jumps to new bounding sphere", function () {
+      const bs1 = new BoundingSphere(new Cartesian3(1, 2, 3), 4);
+      scene.camera.viewBoundingSphere(bs1);
+      const positionWC1 = scene.camera.positionWC.clone();
+
+      const bs2 = new BoundingSphere(new Cartesian3(3, 4, 5), 4);
+      scene.camera.viewBoundingSphere(bs2);
+      const positionWC2 = scene.camera.positionWC.clone();
+
+      const entity = new Entity();
+      entity.position = new ConstantPositionProperty(
+        Cartesian3.fromDegrees(0.0, 0.0),
+      );
+      const view = new EntityView(entity, scene, undefined);
+      view.update(JulianDate.now(), bs1);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC1, 1e-10);
+
+      view.boundingSphere = bs2;
+      view.update(JulianDate.now());
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC2, 1e-10);
     });
 
     it("uses entity viewFrom if available and boundingsphere is supplied", function () {


Reviews:
user: login: jjspace
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-18T20:25:17Z
user: login: jfayot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-18T21:07:02Z
user: login: jfayot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T07:46:12Z
user: login: ggetz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T19:35:51Z
user: login: jfayot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T21:04:18Z
user: login: ggetz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T22:12:56Z
user: login: jfayot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T03:16:19Z
user: login: ggetz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T14:56:30Z
user: login: ggetz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T14:57:18Z
user: login: jjspace
body: Thanks for the changes @jfayot, sorry this couldn't make it out with the release yesterday. I can confirm the 3 previous sandcastle examples and the clamping one are all now working correctly as expected so I _think_ this is probably working how we want it now :crossed_fingers:

Just had one comment about the specs.
Also @ggetz would you be able to re-test as well just to double check?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T20:17:27Z
user: login: jfayot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T12:46:10Z
user: login: jjspace
body: Thanks for the changes @jfayot. One more small comment and I think this can get merged.

@ggetz would you also be able to take one last runthrough of tests just to make sure we're not missing anything?
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T21:58:48Z
user: login: jfayot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T08:02:46Z
user: login: ggetz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T15:28:26Z

Review comments:
diff_hunk: @@ -346,12 +342,6 @@ function EntityView(entity, scene, ellipsoid) {
    */
   this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.default);
 
-  /**
-   * The bounding sphere of the object.
-   * @type {BoundingSphere}
-   */
-  this.boundingSphere = undefined;
path: packages/engine/Source/DataSources/EntityView.js
user: login: jjspace
body: Can you explain why this was removed? More than it just works now? The correlation between this and the scratch value and the entities actual bounding sphere all still feels a little funky to me.

If we _do_ still want to remove this we will need to go through the [deprecation process](https://github.com/CesiumGS/cesium/tree/main/Documentation/Contributors/CodingGuide#deprecation-and-breaking-changes) as this is part of the public API and people may be relying on it.
created_at: 2025-02-18T20:25:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -346,12 +342,6 @@ function EntityView(entity, scene, ellipsoid) {
    */
   this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.default);
 
-  /**
-   * The bounding sphere of the object.
-   * @type {BoundingSphere}
-   */
-  this.boundingSphere = undefined;
path: packages/engine/Source/DataSources/EntityView.js
user: login: jfayot
body: I have no answer for you @jjspace.
created_at: 2025-02-18T21:07:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -346,12 +342,6 @@ function EntityView(entity, scene, ellipsoid) {
    */
   this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.default);
 
-  /**
-   * The bounding sphere of the object.
-   * @type {BoundingSphere}
-   */
-  this.boundingSphere = undefined;
path: packages/engine/Source/DataSources/EntityView.js
user: login: jfayot
body: @jjspace I recognize that I have no idea why "it just works now!"

Even this bounding sphere parameter inside `EntityView.update` looks weird: why would you offer this as a parameter when there is no way to get a bounding sphere from an `Entity` in the public API?


In #3954, the bounding sphere was moved from `EntityView` constructor to `update` method, but again, why would you impose to provide a bounding sphere to `EntityView` (which is related to the high level Entity API) when you don't have the ability to get this bounding sphere from Entity's high level API?


Before #3954, the bounding sphere parameter in the constructor was documented as:
`@param {BoundingSphere} [boundingSphere] An initial bounding sphere for setting the default view.`

This "initial" aspect has completely disappeared. My guess is that it should be restored, AND that the Entity's bounding sphere should be computed from inside the `EntityView` implementation and should disappear from the public API.


Otherwise provide a public `Entity.computeBoundingSphere` method...
created_at: 2025-02-20T07:46:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -372,7 +372,8 @@ const getBoundingSphereBoundingSphereScratch = new BoundingSphere();
  * @param {Entity} entity The entity whose bounding sphere to compute.
  * @param {boolean} allowPartial If true, pending bounding spheres are ignored and an answer will be returned from the currently available data.
  *                               If false, the the function will halt and return pending if any of the bounding spheres are pending.
- * @param {BoundingSphere} result The bounding sphere onto which to store the result.
+ * @param {BoundingSphere} [result] The bounding sphere onto which to store the result.
+ *                                  Result parameter may not be provided if one is only interested into the bounding sphere state.
path: packages/engine/Source/DataSources/DataSourceDisplay.js
user: login: ggetz
body: My one concern here is that this would create a new object even when it's not needed, which then needs to get garbage collected later. That's generally fine if the function is not called too frequently, but I think this could be called once every frame, right?

Would it make sense to have another new function `getBoundingSphereState` which can be used without a result parameter, which `getBoundingSphere` then uses under the hood?
created_at: 2025-02-28T19:35:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -372,7 +372,8 @@ const getBoundingSphereBoundingSphereScratch = new BoundingSphere();
  * @param {Entity} entity The entity whose bounding sphere to compute.
  * @param {boolean} allowPartial If true, pending bounding spheres are ignored and an answer will be returned from the currently available data.
  *                               If false, the the function will halt and return pending if any of the bounding spheres are pending.
- * @param {BoundingSphere} result The bounding sphere onto which to store the result.
+ * @param {BoundingSphere} [result] The bounding sphere onto which to store the result.
+ *                                  Result parameter may not be provided if one is only interested into the bounding sphere state.
path: packages/engine/Source/DataSources/DataSourceDisplay.js
user: login: jfayot
body: @ggetz, @jjspace I'm not sure to clearly understand your concern: if no `result` is passed to `DataSourceDisplay.getBoundingSphere` then the test by the end of the function should handle this correctly?

```javascript
  ...
  boundingSpheres.length = count;
  if (defined(result)) {
    BoundingSphere.fromBoundingSpheres(boundingSpheres, result);
  }
  return BoundingSphereState.DONE;
};
```

What am I missing?
created_at: 2025-02-28T21:04:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -372,7 +372,8 @@ const getBoundingSphereBoundingSphereScratch = new BoundingSphere();
  * @param {Entity} entity The entity whose bounding sphere to compute.
  * @param {boolean} allowPartial If true, pending bounding spheres are ignored and an answer will be returned from the currently available data.
  *                               If false, the the function will halt and return pending if any of the bounding spheres are pending.
- * @param {BoundingSphere} result The bounding sphere onto which to store the result.
+ * @param {BoundingSphere} [result] The bounding sphere onto which to store the result.
+ *                                  Result parameter may not be provided if one is only interested into the bounding sphere state.
path: packages/engine/Source/DataSources/DataSourceDisplay.js
user: login: ggetz
body: Ah, I missed that. Because our library has the convention that when a result is undefined, a new object is created, I assumed this did the same.

Perhaps only to avoid assumptions like mine, we should still create a new function that matches our convention then.
created_at: 2025-02-28T22:12:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -372,7 +372,8 @@ const getBoundingSphereBoundingSphereScratch = new BoundingSphere();
  * @param {Entity} entity The entity whose bounding sphere to compute.
  * @param {boolean} allowPartial If true, pending bounding spheres are ignored and an answer will be returned from the currently available data.
  *                               If false, the the function will halt and return pending if any of the bounding spheres are pending.
- * @param {BoundingSphere} result The bounding sphere onto which to store the result.
+ * @param {BoundingSphere} [result] The bounding sphere onto which to store the result.
+ *                                  Result parameter may not be provided if one is only interested into the bounding sphere state.
path: packages/engine/Source/DataSources/DataSourceDisplay.js
user: login: jfayot
body: @ggetz I understand what you mean.

However, optimizing ONLY the returned value wouldn't be of great benefit IMO.
If you look at the function, you'll see that it loops over the vizualizers of the data source and call another getBoundingSphere on each one. 
If you would really like to optimize and save created objects and GC, then this would require you to create getBoundingSphereState methods for each vizualizer...
Then some vizualizers themselves rely on updaters to compute their bounding spheres... so again you have to create getBoundingSphereState on updaters...

I'm not sure all these efforts (and associated regression risk) are worth this PR.

However, in order to match the convention that when a result is undefined, a new object is created, I have duplicated code inside getBoundingSphere to create getBoundingSphereState as suggested.
As you can see, we're still creating objects in this function because of the vizualizers. And I was not able to make getBoundingSphere rely on getBoundingSphereState without breaking too much existing code.

CC @jjspace 
created_at: 2025-03-01T03:16:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -372,7 +372,8 @@ const getBoundingSphereBoundingSphereScratch = new BoundingSphere();
  * @param {Entity} entity The entity whose bounding sphere to compute.
  * @param {boolean} allowPartial If true, pending bounding spheres are ignored and an answer will be returned from the currently available data.
  *                               If false, the the function will halt and return pending if any of the bounding spheres are pending.
- * @param {BoundingSphere} result The bounding sphere onto which to store the result.
+ * @param {BoundingSphere} [result] The bounding sphere onto which to store the result.
+ *                                  Result parameter may not be provided if one is only interested into the bounding sphere state.
path: packages/engine/Source/DataSources/DataSourceDisplay.js
user: login: ggetz
body: > I'm not sure all these efforts (and associated regression risk) are worth this PR.

Thanks @jfayot, I agree. I only wanted to make sure we were being careful about new code added.
created_at: 2025-03-03T14:57:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -89,43 +89,30 @@ describe(
       expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
     });
 
-    it("uses entity bounding sphere", function () {
-      const sampleOffset = new Cartesian3(
-        -1.3322676295501878e-15,
-        -7.348469228349534,
-        7.3484692283495345,
-      );
+    it("uses provided bounding sphere", function () {
+      const bs = new BoundingSphere(new Cartesian3(3, 4, 5), 6);
path: packages/engine/Specs/DataSources/EntityViewSpec.js
user: login: jjspace
body: Can you add a test that modifies the bounding sphere to prove that the camera will follow it on future calls to `update`?

(I didn't check all the other specs so if it already exists just lmk)
created_at: 2025-03-04T20:11:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -89,43 +89,30 @@ describe(
       expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
     });
 
-    it("uses entity bounding sphere", function () {
-      const sampleOffset = new Cartesian3(
-        -1.3322676295501878e-15,
-        -7.348469228349534,
-        7.3484692283495345,
-      );
+    it("uses provided bounding sphere", function () {
+      const bs = new BoundingSphere(new Cartesian3(3, 4, 5), 6);
path: packages/engine/Specs/DataSources/EntityViewSpec.js
user: login: jfayot
body: @jjspace, done! This should be ready for another review
created_at: 2025-03-05T12:46:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -89,43 +89,52 @@ describe(
       expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
     });
 
-    it("uses entity bounding sphere", function () {
-      const sampleOffset = new Cartesian3(
-        -1.3322676295501878e-15,
-        -7.348469228349534,
-        7.3484692283495345,
-      );
+    it("uses provided bounding sphere", function () {
+      const bs = new BoundingSphere(new Cartesian3(3, 4, 5), 6);
+      scene.camera.viewBoundingSphere(bs);
+      const positionWC = scene.camera.positionWC.clone();
+
       const entity = new Entity();
       entity.position = new ConstantPositionProperty(
         Cartesian3.fromDegrees(0.0, 0.0),
       );
       const view = new EntityView(entity, scene, undefined);
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.INERTIAL;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.VELOCITY;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.ENU;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
+    });
+
+    it("jumps to new bounding sphere", function () {
+      const bs1 = new BoundingSphere(new Cartesian3(1, 2, 3), 4);
+      scene.camera.viewBoundingSphere(bs1);
+      const positionWC1 = scene.camera.positionWC.clone();
+
+      const bs2 = new BoundingSphere(new Cartesian3(1, 2, 3), 4);
+      scene.camera.viewBoundingSphere(bs2);
+      const positionWC2 = scene.camera.positionWC.clone();
+
+      const entity = new Entity();
+      entity.position = new ConstantPositionProperty(
+        Cartesian3.fromDegrees(0.0, 0.0),
       );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      const view = new EntityView(entity, scene, undefined);
+      view.update(JulianDate.now(), bs1);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC1, 1e-10);
+
+      view.boundingSphere = bs2;
path: packages/engine/Specs/DataSources/EntityViewSpec.js
user: login: jjspace
body: This test is valuable to make sure changing `EntityView.boundingSphere` works but doesn't mimic the behavior of changing the actual bounding sphere it was set up with originally like calling `getBoundingSphere` does. Maybe another that replaces this line with `bs1.center = new Cartesian3(3, 4, 5);` would also be valuable.
created_at: 2025-03-06T21:57:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -89,43 +89,52 @@ describe(
       expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
     });
 
-    it("uses entity bounding sphere", function () {
-      const sampleOffset = new Cartesian3(
-        -1.3322676295501878e-15,
-        -7.348469228349534,
-        7.3484692283495345,
-      );
+    it("uses provided bounding sphere", function () {
+      const bs = new BoundingSphere(new Cartesian3(3, 4, 5), 6);
+      scene.camera.viewBoundingSphere(bs);
+      const positionWC = scene.camera.positionWC.clone();
+
       const entity = new Entity();
       entity.position = new ConstantPositionProperty(
         Cartesian3.fromDegrees(0.0, 0.0),
       );
       const view = new EntityView(entity, scene, undefined);
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.INERTIAL;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.VELOCITY;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
-      );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
 
       entity.trackingReferenceFrame = TrackingReferenceFrame.ENU;
-      view.update(
-        JulianDate.now(),
-        new BoundingSphere(new Cartesian3(3, 4, 5), 6),
+      view.update(JulianDate.now(), bs);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC, 1e-10);
+    });
+
+    it("jumps to new bounding sphere", function () {
+      const bs1 = new BoundingSphere(new Cartesian3(1, 2, 3), 4);
+      scene.camera.viewBoundingSphere(bs1);
+      const positionWC1 = scene.camera.positionWC.clone();
+
+      const bs2 = new BoundingSphere(new Cartesian3(1, 2, 3), 4);
+      scene.camera.viewBoundingSphere(bs2);
+      const positionWC2 = scene.camera.positionWC.clone();
+
+      const entity = new Entity();
+      entity.position = new ConstantPositionProperty(
+        Cartesian3.fromDegrees(0.0, 0.0),
       );
-      expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10);
+      const view = new EntityView(entity, scene, undefined);
+      view.update(JulianDate.now(), bs1);
+      expect(scene.camera.positionWC).toEqualEpsilon(positionWC1, 1e-10);
+
+      view.boundingSphere = bs2;
path: packages/engine/Specs/DataSources/EntityViewSpec.js
user: login: jfayot
body: Good catch @jjspace. Added new test. Should be ready for a final (hopefully :sweat_smile:) review!
created_at: 2025-03-07T08:02:45Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-02-07T10:46:39Z
body: Thank you for the pull request, @jfayot! 

:white_check_mark: We can confirm we have a CLA on file for you.

author_association: CONTRIBUTOR
user: login: ggetz
created_at: 2025-02-10T19:27:14Z
body: Thank you for the fix @jfayot!

@jjspace Could you please review?
author_association: CONTRIBUTOR
user: login: jjspace
created_at: 2025-02-27T18:41:53Z
body: @jfayot I'm sorry this took so long to get back to. It turned out to be a bit of a "land mine" in the code for me to try (read: struggle) to wrap my head around...

I did some digging into this to try and understand it more completely. First I'll say this whole sequence of functions is a total mess imo with way to many "hard to follow" side effects. Ideally it'd be nice to do a refactor pass on all this camera tracking logic but that's way to big for this PR right now (the camera system itself needs some love too)

<sup>Sorry this turned into a wall of text, I tried to condense but it's important context that I think needs written down somewhere</sup>

The first important thing to realize is that `EntityView.update()` behaves differently the first/second time it's called vs the subsequent calls that happen every frame. `updateTransform` is the same way because of the `updateLookAt` and `saveCamera` arguments that are potentially only different the "first" time when the `objectChanged`. <sub>Side note, the way we're currently using `objectChanged` will _never_ be true after the first time because we always regenerate the `EntityView` completely instead of modifying it</sub>

The `EntityView.boundingSphere` is only set the _first_ time `EntityView.update()` is called (`objectChanged`) _and_ the entity does _not_ have a `viewFrom` property. This is what was causing the satellite tracking to fail because that has a `viewFrom` in the sandcastle so the `EntityView.boundingSpehere` is never set. This caused my previous "fix" to `CesiumWidget._onTick` (in #12429) to never run the `update` calls because `defined(entityView.boundingSphere)` was false. I added this check in the first place because `DataSourceDisplay.getBoundingSphere` requires the bounding sphere to be defined.

If `EntityView.boundingSphere` _is_ defined then it's center will be used in the `updateTranform` function instead of the other transform logic. This is why it was important to call `dataSourceDisplay.getBoundingSphere` _with the exisiting_ `entityView.boundingSphere` because that function updates that bounding sphere itself which is the same reference inside the `EntityView`. This wasn't a problem before #124929 because they were all using the same scratch variable in `CesiumWidget` (in `_onTick` and `updateTrackedEntity`) so it _was_ always referring to the same object.

Given all that, an alternative fix for this that works for all the provided examples without changing the `EntityView` could be this:

```js

const boundingSphereScratch = new BoundingSphere();

/**
 * @private
 */
CesiumWidget.prototype._onTick = function (clock) {
  const time = clock.currentTime;

  const isUpdated = this._dataSourceDisplay.update(time);
  if (this._allowDataSourcesToSuspendAnimation) {
    this._canAnimateUpdateCallback(isUpdated);
  }

  const entityView = this._entityView;
  if (defined(entityView)) {
    const trackedEntity = this._trackedEntity;
    const trackedState = this._dataSourceDisplay.getBoundingSphere(
      trackedEntity,
      false,
      entityView.boundingSphere ?? boundingSphereScratch,
    );
    if (trackedState === BoundingSphereState.DONE) {
      entityView.update(time, undefined);
    }
  }
};
```

This is a more minimal code change. However, in some ways this feels pretty bad too and requires understanding all the interactions in how we're calling `update` and it's internal logic that I outlined above.

I think removing `EntityView.boundingSphere` as you've done could be the right move. This would remove one of the "side effect-ish" behaviors in `updateTransform` relying on `that.boundingSphere`. This will (slightly) solidify the logic that **`EntityView.update` sets the _initial_ location of the camera then subsequent calls to `updateTransform` update the camera relative to how the entity has moved** regardless of the bounding sphere.
If this is the approach we take it probably also makes sense to replace the call to `getBoundingSphere` to a new function that only returns the status for `CesiumWidget._onTick` since that's all we really care about. Or maybe just make the `result` optional.
If this makes the most sense to you too please add the deprecation message to the removed property and update `getBoundingSphere` as/if needed. While it's being deprecated we might need to keep setting it even if we're not using it internally so the scratch variables still need to be updated as required.

Alternatively we could keep `EntityView.boundingSphere` and make sure it's position is _always_ used even for `viewFrom` entities but that seems like a bigger change that may not be necessary.

PS: this is one of my top priorities to hopefully still get it in for the release on Monday. I'll be able to take a look over the weekend too to try and keep it moving without more delays on my part
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-02-27T21:12:57Z
body: Thanks for the analysis @jjspace !

> If this is the approach we take it probably also makes sense to replace the call to getBoundingSphere to a new function that only returns the status for CesiumWidget._onTick since that's all we really care about. Or maybe just make the result optional.

I'm not sure to understand this: is it the call `this._dataSourceDisplay.getBoundingSphere` inside `CesiumWidget.prototype._onTick` that you want to change? If so, then I don't get the idea, because in my understanding, `EntityView.prototype.update` still needs an initial bounding sphere in order to make the call `camera.viewBoundingSphere` work properly...
author_association: CONTRIBUTOR
user: login: jjspace
created_at: 2025-02-27T21:23:55Z
body: > I'm not sure to understand this: is it the call this._dataSourceDisplay.getBoundingSphere inside CesiumWidget.prototype._onTick that you want to change?

@jfayot Yes that's where I meant. We really only care about the _return_ value from that function. We need it to update the passed in `boundingSphere` _only if_ we're still using that bounding sphere in the `EntityView#updateTransform` function to set the `cartesian`

> in my understanding, `EntityView.prototype.update` still needs an initial bounding sphere in order to make the call `camera.viewBoundingSphere` work properly...

This is one of the things that makes this whole sequence of code so confusing. If you actually follow the code through the way it's run the call to `camera.viewBoundingSphere` only happens when `objectChanged` (functionally only the "first" time) _and_ the entity does not have a `viewFrom` property. It's only called in one of multiple code paths and is never called after the first time. You can prove this by adding a log statement in `camera.viewBoundingSphere` and loading the sample sandcastles to check how many times it's called. Also the `boundingSphere` argument is already marked as optional so it's not required
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-02-27T21:50:09Z
body: > Also the boundingSphere argument is already marked as optional so it's not required

Yes you're right, the bounding sphere parameter doesn't change anything!
So if we push the reasoning ahead, the whole code block inside `if (!hasViewFrom && defined(boundingSphere))` statement can be removed...
And if so, the `saveCamera` variable is always true and can also be removed...

Am I getting it right?
author_association: CONTRIBUTOR
user: login: jjspace
created_at: 2025-02-27T21:57:03Z
body: > So if we push the reasoning ahead, the whole code block inside if (!hasViewFrom && defined(boundingSphere)) statement can be removed...

@jfayot This code still does something when the `boundingSphere` _is_ provided and there is not a `viewFrom` property so I'm not currently confident in removing it completely without more testing that it's not actually needed.
Note that there is a call to `update` inside `updateTrackedEntity` at the bottom of `CesiumWidget` and this is usually the "first" call to the function. The one that sets up everything inside those if statements. AFAIK right now this is the only time `objectChanged` is true because it's going from `undefined` to whatever the `entity` passed into the constructor the line above it is. After that call (triggered by `CesiumWidget._postRender` after the `trackedEntity` changes) subsequent calls for the same tracked entity happen inside the `_onTick`.
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-02-28T13:45:54Z
body: > If this is the approach we take it probably also makes sense to replace the call to getBoundingSphere to a new function that only returns the status for CesiumWidget._onTick since that's all we really care about. Or maybe just make the result optional.

@jjspace I get your point now ! However, while testing the different code path, I'm convinced that the call to `getBoundingSphere` is useless inside `_onTick`, which could be rewritten like this:

```javascript
CesiumWidget.prototype._onTick = function (clock) {
  const time = clock.currentTime;

  const isUpdated = this._dataSourceDisplay.update(time);
  if (this._allowDataSourcesToSuspendAnimation) {
    this._canAnimateUpdateCallback(isUpdated);
  }

  const entityView = this._entityView;
  if (defined(entityView)) {
    entityView.update(time);
  }
};
```

While, off course, keeping the bounding sphere call and parameter in function `updateTrackedEntity`:

```javascript
  const bs =
    state !== BoundingSphereState.FAILED
      ? trackedEntityBoundingSphereScratch
      : undefined;
  widget._entityView = new EntityView(trackedEntity, scene, scene.ellipsoid);
  widget._entityView.update(currentTime, bs);
```

Concerning the deprecation of `EntityView.boundingSphere`, two questions:
1. in which release would you like to see it completely removed?
2. Although it is in the public API, it is not visible in EntityView's documentation. Should I just add a deprecation note in the header code which will not be visible unless people dig into the sources:

```javascript
  /**
   * The bounding sphere of the object.
   * @type {BoundingSphere}
   * @deprecated This property has been deprecated and will be removed in Cesium 1.xxx
   */
  this.boundingSphere = undefined;
```

... or should I make move this to `Object.defineProperties(EntityView` in order to make its documentation visible?

```javascript
Object.defineProperties(EntityView, {
  /**
   * The bounding sphere of the object.
   * @type {BoundingSphere}
   * @deprecated This property has been deprecated and will be removed in Cesium 1.xxx
   */
  boundingSphere: {
    get: function () {
      deprecationWarning(
        "EntityView.boundingSphere",
        "EntityView.boundingSphere has been deprecated and will be removed in Cesium 1.xxx",
      );
      return this._boundingSphere;
    },
    set: function (boundingSphere) {
      deprecationWarning(
        "EntityView.boundingSphere",
        "EntityView.boundingSphere has been deprecated and will be removed in Cesium 1.xxx",
      );
      this._boundingSphere = boundingSphere;
    },
  },
});
```

author_association: CONTRIBUTOR
user: login: jjspace
created_at: 2025-02-28T16:11:20Z
body: > I'm convinced that the call to `getBoundingSphere` is useless inside `_onTick`

@jfayot I think it might still be needed simply to make the check against the `BoundingSphereState.DONE` to make sure the entity is actually loaded and ready? if not then I agree this can probably be simplified down. Maybe even as far as the single line version `this.entityView?.update(time)`?

> Concerning the deprecation of `EntityView.boundingSphere`, two questions:

1. Let's do 3 releases. The release on monday will be 1.127 so 1.130 for this one
2. Just to note, it _is_ in the docs for [`EntityView`](https://cesium.com/learn/cesiumjs/ref-doc/EntityView.html). But the second example you posted is better, convert this to a getter/setter and display the deprecation message there. All that matters is that from the perspective of people _consuming_ this API it appears to work the same even if the implementation changed. Also with that change I would make sure to change the place it's currently being set to use the private `_boundingSphere` property so we're not throwing the warning unless people are actually interacting with it themselves.

Please also remember to update the specs to cover these use cases so we can remain confident the tracking is working as expected in all cases

author_association: CONTRIBUTOR
user: login: jjspace
created_at: 2025-02-28T16:52:46Z
body: @jfayot is that the local docs on this branch where you already removed the property? If so then that's why it's not there! Check the [hosted version](https://cesium.com/learn/cesiumjs/ref-doc/EntityView.html) or locally in `main`. Running `npm run build-docs` will rebuild the local docs.
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-02-28T16:54:45Z
body: > @jfayot is that the local docs on this branch where you already removed the property? If so then that's why it's not there! Check the [hosted version](https://cesium.com/learn/cesiumjs/ref-doc/EntityView.html) or locally in `main`. Running `npm run build-docs` will rebuild the local docs.

Just a wrong jsdoc implementation on my side... sorry for that!
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-02-28T17:26:55Z
body: @jjspace this should be ready for another review pass (although still the same test failures)
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-02-28T17:30:01Z
body: @jjspace when you'll have the time, could you also answer my questions from #11674 please? Thanks in advance!
author_association: CONTRIBUTOR
user: login: jjspace
created_at: 2025-02-28T19:22:17Z
body: Thanks for the changes @jfayot, I think these look good now. I can confirm all 3 of the test sandcastle's we've seen issues with are still working as expected and I tested a handful of other tracked entity instances as well.

@ggetz would you be able to do some testing on this as well? Specifically around any other entity tracking cases you can think of that aren't in those sandcastles. I think the code is good but would like a second pass on actually running it so we can be sure we didn't introduce another bug that I'm not spotting.
author_association: CONTRIBUTOR
user: login: ggetz
created_at: 2025-03-03T15:06:56Z
body: The code here, and all of the testing plan looks great to me.

I did notice some trouble tracking an entity which is clamped to ground / 3D Tiles on initial load in [the Clamp Entities to Ground Sandcastle](http://localhost:8080/Apps/Sandcastle/index.html?src=Clamp%20Entities%20to%20Ground.html). It loads so that the camera is underground for me. Does anyone else see the same?
author_association: CONTRIBUTOR
user: login: lukemckinstry
created_at: 2025-03-03T15:16:38Z
body: > I did notice some trouble tracking an entity which is clamped to ground / 3D Tiles on initial load in [the Clamp Entities to Ground Sandcastle](http://localhost:8080/Apps/Sandcastle/index.html?src=Clamp%20Entities%20to%20Ground.html). It loads so that the camera is underground for me. Does anyone else see the same?

I see identical behavior on local vs prod 

https://github.com/user-attachments/assets/79f222b1-7b18-48c0-a00d-2e232e4aba92



author_association: CONTRIBUTOR
user: login: ggetz
created_at: 2025-03-03T15:21:02Z
body: If I'm the only one seeing that problem for whatever reason, I can open a separate issue and we can merge this!
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-03-03T15:34:02Z
body: @ggetz I can see it too... 
author_association: CONTRIBUTOR
user: login: jjspace
created_at: 2025-03-03T15:46:05Z
body: @ggetz I see that problem on this branch but not `main`. I was pretty sure I checked that sandcastle last week and it was fine though...
author_association: CONTRIBUTOR
user: login: lukemckinstry
created_at: 2025-03-03T15:50:20Z
body: I see it too, I was not set up correctly before
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-03-03T15:57:15Z
body: @jjspace I know you won't like it, but it looks like this `EntityView.boundingSphere` is still necessary : I have a fix, but I can't explain why "it just works now"...
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-03-03T16:09:21Z
body: @ggetz, @jjspace roughly, the fix consists in reintroducing the boundingSphere... should I go ahead (that is, removing all the deprecation parts)? Or are you following another track?
author_association: CONTRIBUTOR
user: login: ggetz
created_at: 2025-03-03T16:12:40Z
body: > I know you won't like it, but it looks like this EntityView.boundingSphere is still necessary : I have a fix, but I can't explain why "it just works now"...

https://github.com/CesiumGS/cesium/pull/12495 changed handling asynchronous updates for billboards and labels. Perhaps it "fixed" something related to timing here?
author_association: CONTRIBUTOR
user: login: ggetz
created_at: 2025-03-03T16:15:44Z
body: Given there is still some uncertainty here, let's hold off on getting this into the release today. Let's make sure we're fixing this once and for all. :+1:
author_association: CONTRIBUTOR
user: login: jjspace
created_at: 2025-03-03T16:19:43Z
body: @ggetz @jfayot I believe the issue is that `updateTransform` starts with a `cartesian` set to the value of the entity's position at a given `time`. But for stationary entities that are clamped this value never actually changes so the rest of the `updateTransform` logic always returns the same location. However the clamping logic _does_ change the location of an entity's `boundingSphere`. The previous logic which used the `EntityView.boundingSphere.center` and called `DataSourceDisplay.getBoundingSphere` to update said value would account for this to ensure we are always looking at the most up to date bounding sphere even if it was modified through other processes

If you switch to `main` and look closely the camera starts on the label then jumps around _with the label_ as the G3DT gets loaded in and the clamping logic can update it's position

I think this means we can't deprecate the `boundingSphere` and should keep it around for now. I think the final code will look closer to the more minimal change I outlined in an earlier comment https://github.com/CesiumGS/cesium/pull/12467#issuecomment-2688805307
author_association: CONTRIBUTOR
user: login: jfayot
created_at: 2025-03-03T16:40:12Z
body: > #12495 changed handling asynchronous updates for billboards and labels. Perhaps it "fixed" something related to timing here?

@ggetz I've tested this branch rebased on top of 1.126 (so without #12495) and I still can see the camera going underground.

I do agree with your analysis @jjspace, we can clearly see the camera jumping quickly to the new entity's bounding sphere.

author_association: CONTRIBUTOR
user: login: ggetz
created_at: 2025-03-10T15:28:14Z
body: Thanks @jfayot and @jjspace! This is looking great. I'm glad the code change boiled down to a simple solution.

I've run through manual testing for the problematic examples we've seen previously, and have done an overview of other Sandcastle examples. Everything is working great!
author_association: CONTRIBUTOR