bruce-cesium 7.0.2 → 7.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/bruce-cesium.es5.js +1370 -243
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +1368 -241
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/internal/cesium-show-gate.js +100 -0
  7. package/dist/lib/internal/cesium-show-gate.js.map +1 -0
  8. package/dist/lib/internal/cesium-utils.js +207 -1
  9. package/dist/lib/internal/cesium-utils.js.map +1 -1
  10. package/dist/lib/internal/js-utils.js +25 -1
  11. package/dist/lib/internal/js-utils.js.map +1 -1
  12. package/dist/lib/rendering/cesium-animated-in-out.js +66 -7
  13. package/dist/lib/rendering/cesium-animated-in-out.js.map +1 -1
  14. package/dist/lib/rendering/entity-render-engine-model3d.js +17 -8
  15. package/dist/lib/rendering/entity-render-engine-model3d.js.map +1 -1
  16. package/dist/lib/rendering/entity-render-engine-point.js +46 -12
  17. package/dist/lib/rendering/entity-render-engine-point.js.map +1 -1
  18. package/dist/lib/rendering/entity-render-engine-polygon.js +107 -35
  19. package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
  20. package/dist/lib/rendering/entity-render-engine-polyline.js +27 -8
  21. package/dist/lib/rendering/entity-render-engine-polyline.js.map +1 -1
  22. package/dist/lib/rendering/entity-render-engine.js +86 -14
  23. package/dist/lib/rendering/entity-render-engine.js.map +1 -1
  24. package/dist/lib/rendering/menu-item-manager.js +30 -2
  25. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  26. package/dist/lib/rendering/relation-render-engine.js +2 -1
  27. package/dist/lib/rendering/relation-render-engine.js.map +1 -1
  28. package/dist/lib/rendering/render-managers/common/entity-label.js +10 -2
  29. package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
  30. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js +45 -3
  31. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js.map +1 -1
  32. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js +26 -15
  33. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js.map +1 -1
  34. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +50 -3
  35. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  36. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js +3 -2
  37. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js.map +1 -1
  38. package/dist/lib/rendering/render-managers/other/relations-render-manager.js +3 -2
  39. package/dist/lib/rendering/render-managers/other/relations-render-manager.js.map +1 -1
  40. package/dist/lib/rendering/render-managers/render-manager.js +67 -3
  41. package/dist/lib/rendering/render-managers/render-manager.js.map +1 -1
  42. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +2 -0
  43. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  44. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +2 -0
  45. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  46. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +2 -0
  47. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  48. package/dist/lib/rendering/render-managers/tilesets/tileset-pointcloud-render-manager.js +4 -1
  49. package/dist/lib/rendering/render-managers/tilesets/tileset-pointcloud-render-manager.js.map +1 -1
  50. package/dist/lib/rendering/texture-frame-series-animator.js +7 -0
  51. package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
  52. package/dist/lib/rendering/tileset-render-engine.js +18 -0
  53. package/dist/lib/rendering/tileset-render-engine.js.map +1 -1
  54. package/dist/lib/rendering/visual-register-culler.js +191 -97
  55. package/dist/lib/rendering/visual-register-culler.js.map +1 -1
  56. package/dist/lib/rendering/visuals-register.js +263 -25
  57. package/dist/lib/rendering/visuals-register.js.map +1 -1
  58. package/dist/lib/utils/cesium-entity-styler.js +7 -3
  59. package/dist/lib/utils/cesium-entity-styler.js.map +1 -1
  60. package/dist/types/bruce-cesium.d.ts +1 -1
  61. package/dist/types/internal/cesium-show-gate.d.ts +29 -0
  62. package/dist/types/internal/cesium-utils.d.ts +63 -0
  63. package/dist/types/internal/js-utils.d.ts +10 -0
  64. package/dist/types/rendering/cesium-animated-in-out.d.ts +5 -0
  65. package/dist/types/rendering/entity-render-engine-model3d.d.ts +2 -0
  66. package/dist/types/rendering/entity-render-engine-point.d.ts +2 -0
  67. package/dist/types/rendering/entity-render-engine-polygon.d.ts +2 -0
  68. package/dist/types/rendering/entity-render-engine-polyline.d.ts +2 -0
  69. package/dist/types/rendering/entity-render-engine.d.ts +17 -1
  70. package/dist/types/rendering/menu-item-manager.d.ts +6 -0
  71. package/dist/types/rendering/render-managers/entities/entities-datalab-render-manager.d.ts +11 -0
  72. package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +12 -0
  73. package/dist/types/rendering/render-managers/render-manager.d.ts +33 -0
  74. package/dist/types/rendering/render-managers/tilesets/tileset-pointcloud-render-manager.d.ts +3 -0
  75. package/dist/types/rendering/texture-frame-series-animator.d.ts +5 -0
  76. package/dist/types/rendering/tileset-render-engine.d.ts +14 -0
  77. package/dist/types/rendering/visual-register-culler.d.ts +7 -0
  78. package/dist/types/rendering/visuals-register.d.ts +65 -1
  79. package/dist/types/utils/cesium-entity-styler.d.ts +1 -0
  80. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import * as Cesium from 'cesium';
2
- import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, DistanceDisplayCondition, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, HeightReference, ColorBlendMode, ShadowMode, ClassificationType, Model, ArcType, CornerType, ConstantPositionProperty, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, HorizontalOrigin, VerticalOrigin, Cartesian2, SceneTransforms, NearFarScalar, Matrix3, Rectangle, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileColorBlendMode, Cesium3DTileset, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, OrthographicFrustum, EasingFunction, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, CesiumInspector, ClockRange, BoundingSphere, GeometryInstance, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, Intersect, CzmlDataSource } from 'cesium';
3
- import { Cartes, Entity as Entity$1, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Carto, Geometry, Bounds, Color as Color$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, Api, BruceEvent, EntityCoords, DataLab, DelayQueue, EntityHistoricData, BruceApi, EntityRelation, ProgramKey, EntitySource, AccountConcept, RecordChangeFeed, ProjectViewLegacyTile, ProjectViewTile, Camera, Session, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType } from 'bruce-models';
2
+ import { Cartographic, Entity, DistanceDisplayCondition, ColorMaterialProperty, Color, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, ClassificationType, ArcType, HeightReference, CornerType, ShadowMode, ConstantProperty, ConstantPositionProperty, CallbackProperty, HorizontalOrigin, VerticalOrigin, Primitive, Cesium3DTileFeature, ColorBlendMode, Model, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Cartesian2, SceneTransforms, NearFarScalar, Matrix3, Rectangle, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, OrthographicFrustum, EasingFunction, Cesium3DTileColorBlendMode, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Cesium3DTileset, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, CesiumInspector, ClockRange, BoundingSphere, GeometryInstance, ScreenSpaceEventHandler, ScreenSpaceEventType, CustomDataSource, Fullscreen, Intersect, CzmlDataSource } from 'cesium';
3
+ import { Cartes, Entity as Entity$1, ENVIRONMENT, Calculator, Carto, EntityTag, EntityType, Geometry, ObjectUtils, Style, Api, ClientFile, LRUCache, EntityLod, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, Bounds, Color as Color$1, DataLab, DelayQueue, EntityHistoricData, EntityRelation, AccountConcept, RecordChangeFeed, BruceApi, ProgramKey, EntitySource, Camera, ProjectViewTile, ProjectViewLegacyTile, Session, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType } from 'bruce-models';
4
4
 
5
5
  /**
6
6
  * Ensures a number is returned from a given value.
@@ -19,6 +19,28 @@ function EnsureNumber(value, defaultNum) {
19
19
  }
20
20
  return value;
21
21
  }
22
+ /**
23
+ * Returns an entity's own name when it is good enough to use as-is, or null when it has to be resolved properly.
24
+ * @param entity
25
+ */
26
+ function GetUsableEntityName(entity) {
27
+ var _a;
28
+ return IsUsableEntityName((_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.Name) ? String(entity.Bruce.Name).trim() : null;
29
+ }
30
+ /**
31
+ * Returns whether a name is a real one rather than empty or a placeholder.
32
+ * @param name
33
+ */
34
+ function IsUsableEntityName(name) {
35
+ if (typeof name !== "string") {
36
+ return false;
37
+ }
38
+ const trimmed = name.trim();
39
+ if (!trimmed) {
40
+ return false;
41
+ }
42
+ return !trimmed.toLowerCase().includes("unnamed");
43
+ }
22
44
 
23
45
  var MeasureUtils;
24
46
  (function (MeasureUtils) {
@@ -1956,7 +1978,11 @@ function refreshColor(viewer, graphic, opacity) {
1956
1978
  }
1957
1979
  }
1958
1980
  else if (graphic instanceof CorridorGraphics) {
1959
- graphic.material = new ColorMaterialProperty(color);
1981
+ // See if it's changed before applying, same flicker/rebuild concern as the polyline above.
1982
+ const currentColor = calculateCurColor(viewer, graphic);
1983
+ if (currentColor == null || !currentColor.equals(color)) {
1984
+ graphic.material = new ColorMaterialProperty(color);
1985
+ }
1960
1986
  }
1961
1987
  else if (graphic instanceof PointGraphics) {
1962
1988
  const animateColor = new CesiumAnimatedProperty.AnimateColor({
@@ -2204,11 +2230,11 @@ var CesiumEntityStyler;
2204
2230
  * @returns
2205
2231
  */
2206
2232
  function SetDefaultColor(params) {
2207
- const { viewer, entity, requestRender, override } = params;
2233
+ const { viewer, entity, requestRender, override, includeSiblings } = params;
2208
2234
  if (!entity) {
2209
2235
  return;
2210
2236
  }
2211
- const parts = EntityUtils.GatherEntity({
2237
+ const parts = includeSiblings == false ? [entity] : EntityUtils.GatherEntity({
2212
2238
  entity: entity,
2213
2239
  selectable: true
2214
2240
  });
@@ -3846,6 +3872,202 @@ function GetCValue(viewer, obj) {
3846
3872
  }
3847
3873
  return obj;
3848
3874
  }
3875
+ /*
3876
+ * Marks an EntityCollection as one this library created and fully controls (a menu item's own data source).
3877
+ */
3878
+ const OWNED_CCOLLECTION_KEY = "_bruceOwnedCollection";
3879
+ function MarkCCollectionOwned(collection) {
3880
+ if (collection) {
3881
+ collection[OWNED_CCOLLECTION_KEY] = true;
3882
+ }
3883
+ }
3884
+ function IsCCollectionOwned(collection) {
3885
+ return Boolean(collection && collection[OWNED_CCOLLECTION_KEY]);
3886
+ }
3887
+ /**
3888
+ * Runs a bulk collection mutation with every given collection's events suspended, so Cesium raises one
3889
+ * collectionChanged per collection for the whole pass instead of one per entity.
3890
+ *
3891
+ * Takes a set of collections rather than assuming `viewer.entities`, because managed visuals live in their menu
3892
+ * item's own data source: suspending the wrong collection silently does nothing.
3893
+ * @param collections Duplicates and empty entries are ignored.
3894
+ * @param run
3895
+ */
3896
+ function WithSuspendedCCollections(collections, run) {
3897
+ const unique = [];
3898
+ for (const collection of collections) {
3899
+ if (collection && unique.indexOf(collection) === -1) {
3900
+ unique.push(collection);
3901
+ }
3902
+ }
3903
+ for (const collection of unique) {
3904
+ collection.suspendEvents();
3905
+ }
3906
+ try {
3907
+ return run();
3908
+ }
3909
+ finally {
3910
+ for (const collection of unique) {
3911
+ try {
3912
+ collection.resumeEvents();
3913
+ }
3914
+ catch (e) {
3915
+ console.error(e);
3916
+ }
3917
+ }
3918
+ }
3919
+ }
3920
+ /**
3921
+ * Returns the collection a graphic belongs to, falling back to the viewer's own when it has never been added.
3922
+ * @param viewer
3923
+ * @param entity
3924
+ */
3925
+ function GetCEntityCollection(viewer, entity) {
3926
+ const owner = entity === null || entity === void 0 ? void 0 : entity.entityCollection;
3927
+ return owner ? owner : viewer === null || viewer === void 0 ? void 0 : viewer.entities;
3928
+ }
3929
+ /**
3930
+ * Returns whether a graphic is currently in the scene, looking in whichever collection owns it.
3931
+ * @param viewer
3932
+ * @param entity
3933
+ */
3934
+ function IsCEntityInScene(viewer, entity) {
3935
+ const collection = GetCEntityCollection(viewer, entity);
3936
+ return Boolean(collection && entity && collection.contains(entity));
3937
+ }
3938
+ /**
3939
+ * Removes a graphic from whichever collection owns it. Returns whether it removed anything.
3940
+ * @param viewer
3941
+ * @param entity
3942
+ */
3943
+ function RemoveCEntityFromScene(viewer, entity) {
3944
+ var _a;
3945
+ if (!entity) {
3946
+ return false;
3947
+ }
3948
+ const collection = GetCEntityCollection(viewer, entity);
3949
+ if (collection === null || collection === void 0 ? void 0 : collection.contains(entity)) {
3950
+ return collection.remove(entity);
3951
+ }
3952
+ // A stale owner reference and the graphic ended up in the viewer's collection anyway.
3953
+ if (collection !== (viewer === null || viewer === void 0 ? void 0 : viewer.entities) && ((_a = viewer === null || viewer === void 0 ? void 0 : viewer.entities) === null || _a === void 0 ? void 0 : _a.contains(entity))) {
3954
+ return viewer.entities.remove(entity);
3955
+ }
3956
+ return false;
3957
+ }
3958
+ /**
3959
+ * Adds a graphic back into whichever collection owns it, or the viewer's if it has no owner yet.
3960
+ * @param viewer
3961
+ * @param entity
3962
+ */
3963
+ function AddCEntityToScene(viewer, entity, fallbackCollection) {
3964
+ if (!entity) {
3965
+ return false;
3966
+ }
3967
+ // An existing owner always wins, so a graphic is never migrated out from under whoever holds it.
3968
+ const owner = entity.entityCollection;
3969
+ const collection = owner ? owner : (fallbackCollection ? fallbackCollection : viewer === null || viewer === void 0 ? void 0 : viewer.entities);
3970
+ if (!collection || collection.contains(entity)) {
3971
+ return false;
3972
+ }
3973
+ collection.add(entity);
3974
+ return true;
3975
+ }
3976
+ /**
3977
+ * Returns whether toggling `show` on these graphics is safe, rather than having to add/remove them from the
3978
+ * entity collection to hide them.
3979
+ *
3980
+ * Toggling show is far cheaper than re-adding the graphic to the viewer however a Cesium bug means we can't always use it.
3981
+ * See: https://github.com/CesiumGS/cesium/issues/9370
3982
+ * @param parts The graphic and any sibling graphics, eg. from EntityUtils.GatherEntity.
3983
+ */
3984
+ function CanToggleShowSafely(parts) {
3985
+ if (!(parts === null || parts === void 0 ? void 0 : parts.length)) {
3986
+ return false;
3987
+ }
3988
+ for (const part of parts) {
3989
+ if (!(part instanceof Entity)) {
3990
+ // Not an Entity, so not something whose `show` we can drive.
3991
+ return false;
3992
+ }
3993
+ if (part.polyline && isTruthyCValue(part.polyline.clampToGround)) {
3994
+ return false;
3995
+ }
3996
+ const materialGraphics = [part.polygon, part.corridor, part.ellipse, part.rectangle, part.wall, part.polylineVolume];
3997
+ for (const graphic of materialGraphics) {
3998
+ if (graphic && !isPlainColorMaterial(graphic.material)) {
3999
+ return false;
4000
+ }
4001
+ }
4002
+ }
4003
+ return true;
4004
+ }
4005
+ /**
4006
+ * Returns whether a material is a plain colour, which is what keeps a graphic out of Cesium's per-material batches.
4007
+ * Anything unrecognised counts as not plain, so callers stay on the conservative side.
4008
+ * @param material
4009
+ */
4010
+ function isPlainColorMaterial(material) {
4011
+ if (material == null) {
4012
+ // Cesium defaults an absent material to white, a plain colour.
4013
+ return true;
4014
+ }
4015
+ return material instanceof ColorMaterialProperty || material instanceof Color;
4016
+ }
4017
+ /**
4018
+ * Resolves a possibly-property boolean without needing a viewer clock, treating anything undeterminable as true
4019
+ * so callers stay on the conservative side.
4020
+ * @param value
4021
+ */
4022
+ function isTruthyCValue(value) {
4023
+ if (value == null) {
4024
+ return false;
4025
+ }
4026
+ if (typeof value.getValue === "function") {
4027
+ if (value instanceof ConstantProperty) {
4028
+ return Boolean(value.valueOf());
4029
+ }
4030
+ // A non-constant property could be true at any moment, so assume it is.
4031
+ return true;
4032
+ }
4033
+ return Boolean(value);
4034
+ }
4035
+ /**
4036
+ * Returns if two resolved Cesium property values are equal, using the value's own equals() when it has one.
4037
+ * @param a
4038
+ * @param b
4039
+ */
4040
+ function AreCValuesEqual(a, b) {
4041
+ if (a === b) {
4042
+ return true;
4043
+ }
4044
+ // One side is null/undefined and the other isn't.
4045
+ if (a == null || b == null) {
4046
+ return false;
4047
+ }
4048
+ if (typeof a.equals === "function") {
4049
+ return a.equals(b) === true;
4050
+ }
4051
+ return false;
4052
+ }
4053
+ /**
4054
+ * Wraps a value in a ConstantProperty and assigns it to a graphic's property, but only when the resolved
4055
+ * value actually differs. Returns whether it assigned.
4056
+ * @param params
4057
+ */
4058
+ function SetCPropIfChanged(params) {
4059
+ const { viewer, graphic, property, value, compare } = params;
4060
+ if (!graphic) {
4061
+ return false;
4062
+ }
4063
+ const oldValue = GetCValue(viewer, graphic[property]);
4064
+ const isEqual = compare ? compare(oldValue, value) : AreCValuesEqual(oldValue, value);
4065
+ if (isEqual) {
4066
+ return false;
4067
+ }
4068
+ graphic[property] = new ConstantProperty(value);
4069
+ return true;
4070
+ }
3849
4071
  function ColorToCColor(color) {
3850
4072
  return new Color(color.red ? color.red / 255 : 0, color.green ? color.green / 255 : 0, color.blue ? color.blue / 255 : 0, color.alpha);
3851
4073
  }
@@ -4334,6 +4556,85 @@ var ViewUtils;
4334
4556
  ViewUtils.GetModelSpace = GetModelSpace;
4335
4557
  })(ViewUtils || (ViewUtils = {}));
4336
4558
 
4559
+ /*
4560
+ * Lets something decide WHEN a `show` flag is honoured, without owning WHAT it should be.
4561
+ */
4562
+ const SHOW_GATE_KEY = "_bruceShowGate";
4563
+ /**
4564
+ * Installs a show gate on an object, or returns the one already installed.
4565
+ * Returns null when the property cannot be intercepted or restored, in which case the caller must leave `show` alone rather than fall back to writing it directly.
4566
+ * @param target Anything with a plain, configurable `show` property.
4567
+ */
4568
+ function InstallShowGate(target) {
4569
+ if (!target) {
4570
+ return null;
4571
+ }
4572
+ const existing = target[SHOW_GATE_KEY];
4573
+ if (existing) {
4574
+ return existing;
4575
+ }
4576
+ const descriptor = Object.getOwnPropertyDescriptor(target, "show");
4577
+ // An accessor already, or something we could not put back, so refuse rather than half-apply.
4578
+ if (descriptor && (!descriptor.configurable || descriptor.get || descriptor.set)) {
4579
+ return null;
4580
+ }
4581
+ let intended = target.show !== false;
4582
+ let suppressed = false;
4583
+ try {
4584
+ Object.defineProperty(target, "show", {
4585
+ configurable: true,
4586
+ enumerable: descriptor ? descriptor.enumerable : true,
4587
+ get: () => intended && !suppressed,
4588
+ set: (value) => {
4589
+ intended = value !== false;
4590
+ }
4591
+ });
4592
+ }
4593
+ catch (e) {
4594
+ console.error("Failed to install a show gate.", e);
4595
+ return null;
4596
+ }
4597
+ const gate = {
4598
+ get Intended() {
4599
+ return intended;
4600
+ },
4601
+ get Suppressed() {
4602
+ return suppressed;
4603
+ },
4604
+ SetSuppressed(value) {
4605
+ const next = Boolean(value);
4606
+ if (next === suppressed) {
4607
+ return false;
4608
+ }
4609
+ const before = intended && !suppressed;
4610
+ suppressed = next;
4611
+ return (intended && !suppressed) !== before;
4612
+ },
4613
+ Release() {
4614
+ try {
4615
+ delete target[SHOW_GATE_KEY];
4616
+ Object.defineProperty(target, "show", {
4617
+ configurable: true,
4618
+ enumerable: descriptor ? descriptor.enumerable : true,
4619
+ writable: true,
4620
+ // The intent, not the gated value: releasing must not leave something culled forever.
4621
+ value: intended
4622
+ });
4623
+ }
4624
+ catch (e) {
4625
+ console.error("Failed to release a show gate.", e);
4626
+ }
4627
+ }
4628
+ };
4629
+ Object.defineProperty(target, SHOW_GATE_KEY, {
4630
+ value: gate,
4631
+ configurable: true,
4632
+ enumerable: false,
4633
+ writable: true
4634
+ });
4635
+ return gate;
4636
+ }
4637
+
4337
4638
  /**
4338
4639
  * Returns cesium property's value.
4339
4640
  * This will check if it's one that changes over time, or just a fixed value.
@@ -4411,10 +4712,17 @@ var EntityLabel;
4411
4712
  * Loads the Entity's name and updates the corresponding visual rego.
4412
4713
  */
4413
4714
  async loadName() {
4715
+ var _a;
4414
4716
  if (this.loadedName) {
4415
4717
  return;
4416
4718
  }
4417
4719
  try {
4720
+ // A rego that already holds a real name needs no lookup. Placeholders do not count, or a cached
4721
+ // "Unnamed Entity" would stop the real name ever being resolved.
4722
+ if (IsUsableEntityName((_a = this.rego) === null || _a === void 0 ? void 0 : _a.name)) {
4723
+ this.loadedName = true;
4724
+ return;
4725
+ }
4418
4726
  if (this.api) {
4419
4727
  const rego = this.rego;
4420
4728
  this.rego.name = await Entity$1.CalculateNameAsync({
@@ -5601,7 +5909,7 @@ var RelationRenderEngine;
5601
5909
  return;
5602
5910
  }
5603
5911
  if (parabola) {
5604
- const visible = cEntity && cEntity.show != false && params.viewer.entities.contains(cEntity);
5912
+ const visible = cEntity && cEntity.show != false && IsCEntityInScene(params.viewer, cEntity);
5605
5913
  parabola.Hidden = !visible;
5606
5914
  }
5607
5915
  updatePosses();
@@ -5629,18 +5937,58 @@ const POINT_MIN_RADIUS = 15;
5629
5937
  // If a shape has more than this amount of points, we'll avoid checking it because it will take too long.
5630
5938
  // We will put responsibility on the bookmark creator to view shapes they can see.
5631
5939
  const DO_NOT_CULL_AT_LEN_POINTS = 5000;
5632
- // Returns terrain height at a given position.
5633
- // This will cache the results based on the terrain provider and position.
5634
- const _terrainCache = new LRUCache(1000);
5635
- // Positions within this tolerance from each-other will be re-used.
5636
- const TERRAIN_HEIGHT_TOLERANCE = 400;
5637
- // Tolerance to use when number of entities is lower than x in the scene.
5638
- const TERRAIN_HEIGHT_TOLERANCE_LOW_COUNT = 30;
5639
- const TERRAIN_HEIGHT_TOLERANCE_LOW_ENTITY_THRESHOLD = 100;
5940
+ const _terrainCache = new LRUCache(20000);
5941
+ /*
5942
+ * Past this camera distance the terrain sample is skipped entirely (resolving a long-standing TODO here).
5943
+ * The sphere's radius is padded by TERRAIN_SKIP_RADIUS_PAD instead, which covers any plausible unresolved ground height.
5944
+ */
5945
+ const TERRAIN_SKIP_DISTANCE = 60000;
5946
+ const TERRAIN_SKIP_RADIUS_PAD = 1500;
5947
+ // Edge length of a coarse grid cell, and of the finer cell used where the ground turns out to be varied.
5948
+ const TERRAIN_CELL_SIZE = 400;
5949
+ const TERRAIN_CELL_SIZE_FINE = 50;
5950
+ // A cell spanning more than this much height cannot be answered with a single average.
5951
+ const TERRAIN_CELL_MAX_SPREAD = 25;
5952
+ /**
5953
+ * Builds the grid key for a position at a given cell size.
5954
+ */
5955
+ function terrainCellKey(terrainId, pos3d, cellSize) {
5956
+ const x = Math.floor(pos3d.x / cellSize);
5957
+ const y = Math.floor(pos3d.y / cellSize);
5958
+ const z = Math.floor(pos3d.z / cellSize);
5959
+ return `${terrainId}_${cellSize}_${x}_${y}_${z}`;
5960
+ }
5961
+ /**
5962
+ * Folds a sample into a cell, refining its estimate and noticing when the region is too varied to average.
5963
+ */
5964
+ function recordTerrainSample(key, height) {
5965
+ const existing = _terrainCache.Get(key);
5966
+ if (!existing) {
5967
+ const cell = {
5968
+ offset: height,
5969
+ count: 1,
5970
+ min: height,
5971
+ max: height,
5972
+ needsDetail: false
5973
+ };
5974
+ _terrainCache.Set(key, cell);
5975
+ return cell;
5976
+ }
5977
+ existing.count++;
5978
+ // Running mean, so the estimate keeps improving rather than being whatever landed first.
5979
+ existing.offset += (height - existing.offset) / existing.count;
5980
+ existing.min = Math.min(existing.min, height);
5981
+ existing.max = Math.max(existing.max, height);
5982
+ if (existing.max - existing.min > TERRAIN_CELL_MAX_SPREAD) {
5983
+ existing.needsDetail = true;
5984
+ }
5985
+ _terrainCache.Set(key, existing);
5986
+ return existing;
5987
+ }
5640
5988
  /**
5641
5989
  * Returns the terrain height for a given position.
5642
5990
  * This will not sample most-detailed and instead the current state of the terrain.
5643
- * TODO: If something is VERY far from the camera, we should just return 0.
5991
+ * Callers past TERRAIN_SKIP_DISTANCE should not be asking at all, see shouldCullEntity.
5644
5992
  * @param viewer
5645
5993
  * @param pos3d
5646
5994
  * @returns
@@ -5657,37 +6005,36 @@ const getTerrainOffset = (viewer, pos3d) => {
5657
6005
  return 0;
5658
6006
  }
5659
6007
  const terrainId = terrain.tilesetId;
5660
- // Check for existing values within TOLERANCE distance.
5661
- const keys = Array.from(_terrainCache.Entries());
5662
- const tolerance = viewer.entities.values.length > TERRAIN_HEIGHT_TOLERANCE_LOW_ENTITY_THRESHOLD ? TERRAIN_HEIGHT_TOLERANCE : TERRAIN_HEIGHT_TOLERANCE_LOW_COUNT;
5663
- for (let i = 0; i < keys.length; i++) {
5664
- const [key, offset] = keys[i];
5665
- const [terrainId2, x, y, z] = key.split("_");
5666
- if (terrainId2 != terrainId) {
5667
- continue;
5668
- }
5669
- const pos3d2 = new Cartesian3(parseFloat(x), parseFloat(y), parseFloat(z));
5670
- const distance = Cartesian3.distance(pos3d, pos3d2);
5671
- if (distance < tolerance) {
5672
- return offset;
5673
- }
6008
+ // Always start coarse and let the region teach us where that is not good enough, rather than guessing from a scene-wide entity count.
6009
+ // A cell that turns out to span varied ground flags itself and its lookups move to the finer grid.
6010
+ const coarseKey = terrainCellKey(terrainId, pos3d, TERRAIN_CELL_SIZE);
6011
+ const coarseCell = _terrainCache.Get(coarseKey);
6012
+ const useFine = Boolean(coarseCell === null || coarseCell === void 0 ? void 0 : coarseCell.needsDetail);
6013
+ const key = useFine ? terrainCellKey(terrainId, pos3d, TERRAIN_CELL_SIZE_FINE) : coarseKey;
6014
+ const cell = useFine ? _terrainCache.Get(key) : coarseCell;
6015
+ if (cell && !cell.needsDetail) {
6016
+ return cell.offset;
5674
6017
  }
5675
- const key = terrainId + "_" + pos3d.x + "_" + pos3d.y + "_" + pos3d.z;
5676
- let offset = _terrainCache.Get(key);
5677
- if (offset == null) {
5678
- const carto = Cartographic.fromCartesian(pos3d);
5679
- let height = 0;
5680
- try {
5681
- height = viewer.scene.globe.getHeight(carto);
5682
- }
5683
- catch (e) {
5684
- // Likely out of rendered area.
5685
- // This means it's not our problem since the user isn't looking there :)
5686
- }
5687
- offset = height;
5688
- _terrainCache.Set(key, offset);
6018
+ const carto = Cartographic.fromCartesian(pos3d);
6019
+ let height = 0;
6020
+ try {
6021
+ height = viewer.scene.globe.getHeight(carto);
5689
6022
  }
5690
- return offset;
6023
+ catch (e) {
6024
+ // Likely out of rendered area.
6025
+ // This means it's not our problem since the user isn't looking there :)
6026
+ }
6027
+ if (height == null || isNaN(height)) {
6028
+ // Terrain tile not loaded yet. Don't record it, so a later pass can sample it properly.
6029
+ return coarseCell ? coarseCell.offset : 0;
6030
+ }
6031
+ // Always fold into the coarse cell as well, so its spread keeps being learned even while the finer grid is
6032
+ // answering lookups for this region.
6033
+ const recorded = recordTerrainSample(key, height);
6034
+ if (useFine) {
6035
+ recordTerrainSample(coarseKey, height);
6036
+ }
6037
+ return recorded.offset;
5691
6038
  };
5692
6039
  /**
5693
6040
  * Converts given pos3d into a an absolute position.
@@ -5871,6 +6218,10 @@ function shouldCullEntity(viewer, cEntity) {
5871
6218
  if (cEntity.model) {
5872
6219
  const model = cEntity.model;
5873
6220
  if (model._radius && !isNaN(model._radius)) {
6221
+ // _radius is already measured from the model's origin rather than its own centre: the model3d
6222
+ // engine folds the bounding sphere's centre offset into it (plus padding for badly-centred
6223
+ // models), and measures it at the entity's scale. So it can be used as-is against a sphere
6224
+ // centred on the entity position.
5874
6225
  sphere.radius = Math.max(model._radius, MODEL_MIN_RADIUS);
5875
6226
  }
5876
6227
  else {
@@ -5884,12 +6235,10 @@ function shouldCullEntity(viewer, cEntity) {
5884
6235
  let radius = getValue$1(viewer, cEntity.ellipse.semiMajorAxis);
5885
6236
  const extrusion = getValue$1(viewer, cEntity.ellipse.extrudedHeight);
5886
6237
  if (extrusion && !isNaN(extrusion)) {
5887
- if (!radius) {
5888
- radius = extrusion;
5889
- }
5890
- else if (extrusion > radius / 2) {
5891
- radius = extrusion;
5892
- }
6238
+ // Expand rather than replace: measured from the base centre, the top rim of an extruded ellipse
6239
+ // sits at sqrt(radius^2 + height^2). Taking whichever of the two is larger leaves the rim outside
6240
+ // the sphere, and for something both wide and tall it would actually shrink it.
6241
+ radius = radius ? Math.sqrt((radius * radius) + (extrusion * extrusion)) : extrusion;
5893
6242
  }
5894
6243
  if (radius && !isNaN(radius)) {
5895
6244
  sphere.radius = radius;
@@ -5899,12 +6248,11 @@ function shouldCullEntity(viewer, cEntity) {
5899
6248
  else if (cEntity.polygon) {
5900
6249
  const extrusion = getValue$1(viewer, cEntity.polygon.extrudedHeight);
5901
6250
  if (extrusion && !isNaN(extrusion)) {
5902
- if (!sphere.radius) {
5903
- sphere.radius = extrusion;
5904
- }
5905
- else if (extrusion > sphere.radius / 2) {
5906
- sphere.radius = extrusion;
5907
- }
6251
+ // Same as the ellipse above: the footprint sphere has to grow to reach the extruded top corners,
6252
+ // at sqrt(footprint^2 + height^2) from the footprint centre.
6253
+ sphere.radius = sphere.radius
6254
+ ? Math.sqrt((sphere.radius * sphere.radius) + (extrusion * extrusion))
6255
+ : extrusion;
5908
6256
  }
5909
6257
  }
5910
6258
  else if (cEntity.point || cEntity.billboard) {
@@ -5926,7 +6274,15 @@ function shouldCullEntity(viewer, cEntity) {
5926
6274
  return false;
5927
6275
  }
5928
6276
  const adjustedSphere = boundingSphere.sphere.clone();
5929
- adjustedSphere.center = adjustPos3d(viewer, boundingSphere.heightRef, adjustedSphere.center);
6277
+ // Terrain only matters here because it moves the sphere onto the ground.
6278
+ // Far enough away that a plausible ground height is a rounding error against the viewing distance, sampling it is wasted work.
6279
+ const cameraDistance = Cartesian3.distance(camera.position, adjustedSphere.center);
6280
+ if (cameraDistance > TERRAIN_SKIP_DISTANCE) {
6281
+ adjustedSphere.radius += TERRAIN_SKIP_RADIUS_PAD;
6282
+ }
6283
+ else {
6284
+ adjustedSphere.center = adjustPos3d(viewer, boundingSphere.heightRef, adjustedSphere.center);
6285
+ }
5930
6286
  const cullingVolume = camera.frustum.computeCullingVolume(camera.position, camera.direction, camera.up);
5931
6287
  const visibility = cullingVolume.computeVisibility(adjustedSphere);
5932
6288
  return visibility !== Intersect.INSIDE && visibility !== Intersect.INTERSECTING;
@@ -5999,38 +6355,67 @@ function isCullingIgnored(viewer, rego) {
5999
6355
  rego.relation != null ||
6000
6356
  // Explicitly being either shown or hidden.
6001
6357
  rego.overrideShow != null ||
6002
- // Part of an independent collection.
6003
- // Won't touch in case there is special logic.
6004
- rego.collection ||
6358
+ // Part of a collection we did not create, eg. a geojson data source, which may have its own rules for
6359
+ // what is in the scene. Our own per-menu-item data sources are fair game.
6360
+ (rego.collection && !IsCCollectionOwned(rego.collection)) ||
6005
6361
  // We won't cull historic records as they may be interpolating their locations.
6006
6362
  Boolean(rego.outline && rego.outline.some(x => !!x.DateTime))) {
6007
6363
  return true;
6008
6364
  }
6009
- const visual = rego.visual;
6010
- // We will only cull clamped stuff since it seems that's where we get benefits from.
6011
- let heightRef;
6012
- if (visual.polygon) {
6013
- heightRef = getValue$1(viewer, visual.polygon.heightReference);
6014
- }
6015
- else if (visual.polyline) {
6016
- heightRef = getValue$1(viewer, visual.polyline.clampToGround) == false ? HeightReference.NONE : HeightReference.CLAMP_TO_GROUND;
6017
- }
6018
- else if (visual.corridor) {
6019
- heightRef = getValue$1(viewer, visual.corridor.heightReference);
6020
- }
6021
- else if (visual.ellipse) {
6022
- heightRef = getValue$1(viewer, visual.ellipse.heightReference);
6023
- }
6024
- else if (visual.model) {
6025
- heightRef = getValue$1(viewer, visual.model.heightReference);
6365
+ return false;
6366
+ }
6367
+ // A tileset's bounds are padded by this fraction before testing, so panning does not immediately evict something
6368
+ // at the edge of view and pay to re-traverse it moments later.
6369
+ const TILESET_CULL_PADDING = 0.25;
6370
+ /**
6371
+ * Suppresses tilesets whose padded bounds are outside the view, and restores ones that come back.
6372
+ *
6373
+ * Worth doing separately from entity culling because Cesium gates a tileset's whole update on its show flag: a
6374
+ * hidden tileset skips traversal, tile selection and request scheduling entirely, where hiding its features still
6375
+ * does all of that and merely draws nothing. There are only ever a handful of tilesets, so this needs no batching.
6376
+ * @param register
6377
+ */
6378
+ function cullTilesets(register) {
6379
+ var _a;
6380
+ const viewer = register.Viewer;
6381
+ const tilesets = register.GetTilesets();
6382
+ if (!tilesets.length || viewer.isDestroyed() || !viewer.scene) {
6383
+ return;
6026
6384
  }
6027
- else if (visual.point) {
6028
- heightRef = getValue$1(viewer, visual.point.heightReference);
6385
+ const camera = viewer.scene.camera;
6386
+ const cullingVolume = camera.frustum.computeCullingVolume(camera.position, camera.direction, camera.up);
6387
+ let changed = false;
6388
+ for (const rego of tilesets) {
6389
+ const tileset = rego.tileset;
6390
+ if (!tileset || !rego.gate) {
6391
+ continue;
6392
+ }
6393
+ // A destroyed tileset should not be held onto, let alone culled.
6394
+ if ((_a = tileset.isDestroyed) === null || _a === void 0 ? void 0 : _a.call(tileset)) {
6395
+ register.RemoveTileset(tileset);
6396
+ continue;
6397
+ }
6398
+ let sphere;
6399
+ try {
6400
+ sphere = tileset.boundingSphere;
6401
+ }
6402
+ catch (e) {
6403
+ // Not ready yet. Leave it alone rather than guessing.
6404
+ continue;
6405
+ }
6406
+ if (!sphere || isNaN(sphere.radius) || sphere.radius <= 0) {
6407
+ continue;
6408
+ }
6409
+ const padded = sphere.clone();
6410
+ padded.radius = sphere.radius * (1 + TILESET_CULL_PADDING);
6411
+ const visible = cullingVolume.computeVisibility(padded) !== Intersect.OUTSIDE;
6412
+ if (register.SetTilesetCulled(tileset, !visible)) {
6413
+ changed = true;
6414
+ }
6029
6415
  }
6030
- else if (visual.billboard) {
6031
- heightRef = getValue$1(viewer, visual.billboard.heightReference);
6416
+ if (changed) {
6417
+ viewer.scene.requestRender();
6032
6418
  }
6033
- return heightRef != HeightReference.CLAMP_TO_GROUND;
6034
6419
  }
6035
6420
  /**
6036
6421
  * Runs through all entities in the register and culls them if they are out of the viewport.
@@ -6046,6 +6431,7 @@ function runCullChecker(register) {
6046
6431
  const entityIds = register.GetEntityIds();
6047
6432
  // We'll use the terrain cache per culler cycle.
6048
6433
  _terrainCache.Clear();
6434
+ cullTilesets(register);
6049
6435
  checkInterval = setInterval(() => {
6050
6436
  var _a;
6051
6437
  if (viewer.isDestroyed() || !viewer.scene) {
@@ -6098,7 +6484,7 @@ function runCullChecker(register) {
6098
6484
  else {
6099
6485
  delete part[VisualRegisterCuller.VISUAL_CULL_KEY];
6100
6486
  if (rego.overrideShow != false &&
6101
- !viewer.entities.contains(part) &&
6487
+ !IsCEntityInScene(viewer, part) &&
6102
6488
  !state.hidden &&
6103
6489
  (!isIsolatedAny || state.isolated)) {
6104
6490
  toAdd.push(part);
@@ -6138,16 +6524,17 @@ function removeEntities(viewer, entities) {
6138
6524
  }
6139
6525
  const removal = viewer.scene.postRender.addEventListener(() => {
6140
6526
  removal();
6141
- for (let i = 0; i < entities.length; i++) {
6142
- const entity = entities[i];
6143
- if (entityRemoveAddState.get(entity.id) != false) {
6144
- continue;
6145
- }
6146
- entityRemoveAddState.delete(entity.id);
6147
- if (viewer.entities.contains(entity)) {
6148
- viewer.entities.remove(entity);
6527
+ // Batched, so Cesium raises one collectionChanged for the whole cull pass instead of one per entity.
6528
+ WithSuspendedCCollections(entities.map(x => GetCEntityCollection(viewer, x)), () => {
6529
+ for (let i = 0; i < entities.length; i++) {
6530
+ const entity = entities[i];
6531
+ if (entityRemoveAddState.get(entity.id) != false) {
6532
+ continue;
6533
+ }
6534
+ entityRemoveAddState.delete(entity.id);
6535
+ RemoveCEntityFromScene(viewer, entity);
6149
6536
  }
6150
- }
6537
+ });
6151
6538
  });
6152
6539
  }
6153
6540
  /**
@@ -6162,16 +6549,17 @@ function addEntities(viewer, entities) {
6162
6549
  }
6163
6550
  const removal = viewer.scene.postRender.addEventListener(() => {
6164
6551
  removal();
6165
- for (let i = 0; i < entities.length; i++) {
6166
- const entity = entities[i];
6167
- if (entityRemoveAddState.get(entity.id) != true) {
6168
- continue;
6169
- }
6170
- entityRemoveAddState.delete(entity.id);
6171
- if (!viewer.entities.contains(entity)) {
6172
- viewer.entities.add(entity);
6552
+ // Batched, so Cesium raises one collectionChanged for the whole cull pass instead of one per entity.
6553
+ WithSuspendedCCollections(entities.map(x => GetCEntityCollection(viewer, x)), () => {
6554
+ for (let i = 0; i < entities.length; i++) {
6555
+ const entity = entities[i];
6556
+ if (entityRemoveAddState.get(entity.id) != true) {
6557
+ continue;
6558
+ }
6559
+ entityRemoveAddState.delete(entity.id);
6560
+ AddCEntityToScene(viewer, entity);
6173
6561
  }
6174
- }
6562
+ });
6175
6563
  });
6176
6564
  }
6177
6565
  var VisualRegisterCuller;
@@ -6242,6 +6630,19 @@ var VisualRegisterCuller;
6242
6630
  };
6243
6631
  }
6244
6632
  VisualRegisterCuller.Monitor = Monitor;
6633
+ /**
6634
+ * Runs the tileset visibility pass immediately, rather than waiting for the next cull cycle.
6635
+ * Exposed so the behaviour is directly assertable, and for a caller that needs it applied now.
6636
+ * @param register
6637
+ */
6638
+ function CullTilesets(register) {
6639
+ cullTilesets(register);
6640
+ }
6641
+ VisualRegisterCuller.CullTilesets = CullTilesets;
6642
+ function IsCullingIgnored(viewer, rego) {
6643
+ return isCullingIgnored(viewer, rego);
6644
+ }
6645
+ VisualRegisterCuller.IsCullingIgnored = IsCullingIgnored;
6245
6646
  function IsCulled(viewer, rego, visual) {
6246
6647
  if (isCullingIgnored(viewer, rego)) {
6247
6648
  return false;
@@ -6289,7 +6690,7 @@ function isAlive$1(viewer, visual) {
6289
6690
  return false;
6290
6691
  }
6291
6692
  if (visual instanceof Entity) {
6292
- return viewer.entities.contains(visual);
6693
+ return IsCEntityInScene(viewer, visual);
6293
6694
  }
6294
6695
  else if (visual instanceof Primitive) {
6295
6696
  return viewer.scene.primitives.contains(visual);
@@ -6316,6 +6717,32 @@ function removeEntity(viewer, visual) {
6316
6717
  viewer
6317
6718
  });
6318
6719
  }
6720
+ /**
6721
+ * Returns the highest zIndex across a graphic and its siblings, or null when none of them set one.
6722
+ * @param viewer
6723
+ * @param visual
6724
+ */
6725
+ function getHighestZIndex(viewer, visual) {
6726
+ if (!(visual instanceof Entity)) {
6727
+ return null;
6728
+ }
6729
+ let highest = null;
6730
+ const parts = EntityUtils.GatherEntity({
6731
+ entity: visual
6732
+ });
6733
+ for (const part of parts) {
6734
+ if (!(part instanceof Entity)) {
6735
+ continue;
6736
+ }
6737
+ for (const graphic of [part.polygon, part.polyline, part.corridor, part.ellipse, part.rectangle]) {
6738
+ const zIndex = graphic ? GetCValue(viewer, graphic.zIndex) : null;
6739
+ if (zIndex != null && !isNaN(zIndex) && (highest == null || zIndex > highest)) {
6740
+ highest = zIndex;
6741
+ }
6742
+ }
6743
+ }
6744
+ return highest;
6745
+ }
6319
6746
  const MAX_SHOW_DEPTH = 10;
6320
6747
  function updateCEntityShow(viewer, visual, rego, show, ignoreParent, depth = 0) {
6321
6748
  if (depth > MAX_SHOW_DEPTH) {
@@ -6334,26 +6761,24 @@ function updateCEntityShow(viewer, visual, rego, show, ignoreParent, depth = 0)
6334
6761
  updateCEntityShow(viewer, sibling, rego, show, true, depth + 1);
6335
6762
  }
6336
6763
  }
6337
- /**
6338
- * Do NOT use ".show" as it causes crashes in Cesium polylines that are clamped to ground.
6339
- * We could target them specifically here but we may be getting overall performance gain by just removing stuff from the scene.
6340
- */
6341
- if (rego.collection) {
6342
- if (!show && rego.collection.contains(visual)) {
6343
- rego.collection.remove(visual);
6344
- }
6345
- else if (show && !rego.collection.contains(visual)) {
6346
- rego.collection.add(visual);
6764
+ if (show) {
6765
+ // Always restore membership on the way back in. Something else may have evicted this graphic while it
6766
+ // was hidden (the culler removes outright), and `show` alone would leave it invisible.
6767
+ AddCEntityToScene(viewer, visual, rego.collection);
6768
+ if (visual.show !== true) {
6769
+ visual.show = true;
6347
6770
  }
6771
+ return;
6348
6772
  }
6349
- else {
6350
- if (!show && viewer.entities.contains(visual)) {
6351
- viewer.entities.remove(visual);
6352
- }
6353
- else if (show && !viewer.entities.contains(visual)) {
6354
- viewer.entities.add(visual);
6773
+ // Hiding via `show` only updates a per-instance attribute, where leaving the collection rebuilds the whole
6774
+ // geometry batch. But it is not safe for every graphic, see CanToggleShowSafely for exactly why.
6775
+ if (CanToggleShowSafely([visual])) {
6776
+ if (visual.show !== false) {
6777
+ visual.show = false;
6355
6778
  }
6779
+ return;
6356
6780
  }
6781
+ RemoveCEntityFromScene(viewer, visual);
6357
6782
  }
6358
6783
  function updateEntityShow(viewer, rego, show) {
6359
6784
  const visual = rego.visual;
@@ -6666,12 +7091,128 @@ var VisualsRegister;
6666
7091
  // This lets us modify the update without re-adding the item to the queue if it's already in it.
6667
7092
  this.updateQueueSettings = {};
6668
7093
  this.updateQueueInterval = null;
7094
+ // Per menu item collections so we can refresh sets of Entities without affecting others.
7095
+ this.menuItemSources = new Map();
7096
+ // Set when the data source order no longer matches the zIndexes seen, so it is re-applied once.
7097
+ this.sourceOrderDirty = false;
7098
+ /*
7099
+ * Tilesets in the scene, keyed by the tileset itself.
7100
+ */
7101
+ this.tilesets = new Map();
6669
7102
  this.viewer = params.viewer;
6670
7103
  this.apiGetters = params.apiGetters;
6671
7104
  this.cameraCullerDispose = VisualRegisterCuller.Monitor({
6672
7105
  register: this
6673
7106
  });
6674
7107
  }
7108
+ /**
7109
+ * Returns the collection a menu item's visuals belong in, creating its data source on first use.
7110
+ * Returns null when there is nothing to key off, leaving the caller on the viewer's own collection.
7111
+ * @param menuItemId
7112
+ */
7113
+ ensureMenuItemCollection(menuItemId) {
7114
+ var _a;
7115
+ if (!menuItemId || this.disposed || !((_a = this.viewer) === null || _a === void 0 ? void 0 : _a.dataSources)) {
7116
+ return null;
7117
+ }
7118
+ const existing = this.menuItemSources.get(menuItemId);
7119
+ if (existing) {
7120
+ return existing.source.entities;
7121
+ }
7122
+ const source = new CustomDataSource(`menu-item-${menuItemId}`);
7123
+ const record = {
7124
+ source,
7125
+ maxZIndex: 0,
7126
+ added: false
7127
+ };
7128
+ MarkCCollectionOwned(source.entities);
7129
+ this.menuItemSources.set(menuItemId, record);
7130
+ // Adding is asynchronous, so ordering has to wait until it is actually in the collection.
7131
+ Promise.resolve(this.viewer.dataSources.add(source))
7132
+ .then(() => {
7133
+ record.added = true;
7134
+ this.applySourceOrder();
7135
+ })
7136
+ .catch((e) => {
7137
+ console.error(e);
7138
+ });
7139
+ return source.entities;
7140
+ }
7141
+ /**
7142
+ * Notes the highest zIndex seen for a menu item, since that is what its data source has to sort by.
7143
+ * zIndex orders geometry within a data source but not between them, so a whole layer stacks by the
7144
+ * highest value anything in it asked for.
7145
+ * @param menuItemId
7146
+ * @param visual
7147
+ */
7148
+ trackSourceZIndex(menuItemId, visual) {
7149
+ const record = this.menuItemSources.get(menuItemId);
7150
+ if (!record) {
7151
+ return;
7152
+ }
7153
+ const zIndex = getHighestZIndex(this.viewer, visual);
7154
+ if (zIndex == null || zIndex <= record.maxZIndex) {
7155
+ return;
7156
+ }
7157
+ record.maxZIndex = zIndex;
7158
+ this.sourceOrderDirty = true;
7159
+ this.applySourceOrder();
7160
+ }
7161
+ /**
7162
+ * Re-stacks the menu item data sources so a higher zIndex sits above a lower one.
7163
+ */
7164
+ applySourceOrder() {
7165
+ var _a;
7166
+ if (this.disposed || !this.sourceOrderDirty || !((_a = this.viewer) === null || _a === void 0 ? void 0 : _a.dataSources)) {
7167
+ return;
7168
+ }
7169
+ const records = Array.from(this.menuItemSources.values()).filter(x => x.added);
7170
+ if (records.length < 2) {
7171
+ // Nothing to order against yet, so leave the flag set for when there is.
7172
+ return;
7173
+ }
7174
+ this.sourceOrderDirty = false;
7175
+ // Raising each in ascending order leaves the highest on top.
7176
+ records.sort((a, b) => a.maxZIndex - b.maxZIndex);
7177
+ for (const record of records) {
7178
+ try {
7179
+ this.viewer.dataSources.raiseToTop(record.source);
7180
+ }
7181
+ catch (e) {
7182
+ console.error(e);
7183
+ }
7184
+ }
7185
+ }
7186
+ /**
7187
+ * Drops a menu item's data source once nothing is registered under it any more.
7188
+ * @param menuItemId
7189
+ */
7190
+ releaseMenuItemCollection(menuItemId) {
7191
+ var _a, _b, _c;
7192
+ const record = this.menuItemSources.get(menuItemId);
7193
+ if (!record) {
7194
+ return;
7195
+ }
7196
+ if ((_a = this.entityIdsByMenuItem[menuItemId]) === null || _a === void 0 ? void 0 : _a.size) {
7197
+ return;
7198
+ }
7199
+ this.menuItemSources.delete(menuItemId);
7200
+ try {
7201
+ (_c = (_b = this.viewer) === null || _b === void 0 ? void 0 : _b.dataSources) === null || _c === void 0 ? void 0 : _c.remove(record.source, true);
7202
+ }
7203
+ catch (e) {
7204
+ console.error(e);
7205
+ }
7206
+ }
7207
+ /** Every collection a bulk pass over this register's visuals could mutate. */
7208
+ allSceneCollections() {
7209
+ var _a;
7210
+ const collections = [(_a = this.viewer) === null || _a === void 0 ? void 0 : _a.entities];
7211
+ for (const record of Array.from(this.menuItemSources.values())) {
7212
+ collections.push(record.source.entities);
7213
+ }
7214
+ return collections;
7215
+ }
6675
7216
  indexMenuItemEntity(menuItemId, entityId) {
6676
7217
  if (!menuItemId) {
6677
7218
  return;
@@ -7261,9 +7802,11 @@ var VisualsRegister;
7261
7802
  return;
7262
7803
  }
7263
7804
  const end = Math.min(index + BATCH_SIZE, items.length);
7264
- for (; index < end; index++) {
7265
- perItem(items[index]);
7266
- }
7805
+ WithSuspendedCCollections(this.allSceneCollections(), () => {
7806
+ for (; index < end; index++) {
7807
+ perItem(items[index]);
7808
+ }
7809
+ });
7267
7810
  this.viewer.scene.requestRender();
7268
7811
  if (index >= items.length) {
7269
7812
  clearInterval(interval);
@@ -7420,7 +7963,7 @@ var VisualsRegister;
7420
7963
  markEntity(this, rego, rego.visual, false);
7421
7964
  }
7422
7965
  Dispose() {
7423
- var _a;
7966
+ var _a, _b, _c, _d;
7424
7967
  (_a = this.cameraCullerDispose) === null || _a === void 0 ? void 0 : _a.call(this, {
7425
7968
  uncull: false
7426
7969
  });
@@ -7435,6 +7978,20 @@ var VisualsRegister;
7435
7978
  }
7436
7979
  this.activeChunkedIntervals.clear();
7437
7980
  this.disposed = true;
7981
+ for (const record of Array.from(this.menuItemSources.values())) {
7982
+ try {
7983
+ (_c = (_b = this.viewer) === null || _b === void 0 ? void 0 : _b.dataSources) === null || _c === void 0 ? void 0 : _c.remove(record.source, true);
7984
+ }
7985
+ catch (e) {
7986
+ console.error(e);
7987
+ }
7988
+ }
7989
+ this.menuItemSources.clear();
7990
+ // Hand every tileset's show flag back to its owner, holding whatever the owner last asked for.
7991
+ for (const rego of Array.from(this.tilesets.values())) {
7992
+ (_d = rego.gate) === null || _d === void 0 ? void 0 : _d.Release();
7993
+ }
7994
+ this.tilesets.clear();
7438
7995
  }
7439
7996
  ForceUpdate(params) {
7440
7997
  var _a;
@@ -8030,6 +8587,14 @@ var VisualsRegister;
8030
8587
  });
8031
8588
  }
8032
8589
  const entityId = rego.entityId;
8590
+ // Scope this visual to its menu item's own data source, so a rebuild of it cannot re-tessellate
8591
+ // other layers. A caller that already picked a collection (eg. geojson) keeps its own.
8592
+ if (!rego.collection && rego.menuItemId) {
8593
+ const collection = this.ensureMenuItemCollection(rego.menuItemId);
8594
+ if (collection) {
8595
+ rego.collection = collection;
8596
+ }
8597
+ }
8033
8598
  // Add the new visual to the register.
8034
8599
  const entityRegos = (_a = this.rego[entityId]) !== null && _a !== void 0 ? _a : [];
8035
8600
  entityRegos.push(rego);
@@ -8037,6 +8602,7 @@ var VisualsRegister;
8037
8602
  this.indexMenuItemEntity(rego.menuItemId, entityId);
8038
8603
  // Mark the visual as part of this register so selection works.
8039
8604
  markEntity(this, rego, rego.visual, false);
8605
+ this.trackSourceZIndex(rego.menuItemId, rego.visual);
8040
8606
  // Run any updates on the visual based on the calculated state.
8041
8607
  // Eg: visibility, opacity, selection, etc.
8042
8608
  this.queueUpdate({
@@ -8341,9 +8907,13 @@ var VisualsRegister;
8341
8907
  this.startLazyRemoval(entityIds, removeOne, onComplete);
8342
8908
  return;
8343
8909
  }
8344
- for (const entityId of entityIds) {
8345
- removeOne(entityId);
8346
- }
8910
+ WithSuspendedCCollections(this.allSceneCollections(), () => {
8911
+ for (const entityId of entityIds) {
8912
+ removeOne(entityId);
8913
+ }
8914
+ });
8915
+ this.releaseMenuItemCollection(menuItemId);
8916
+ this.RemoveTilesetsByMenuItem(menuItemId);
8347
8917
  }
8348
8918
  else if (entityId) {
8349
8919
  const entityRegos = this.rego[entityId];
@@ -8526,8 +9096,75 @@ var VisualsRegister;
8526
9096
  };
8527
9097
  }
8528
9098
  /**
8529
- * Returns all registered entity ids.
9099
+ * Registers a tileset so the register can decide when it is worth drawing.
9100
+ * @param params
9101
+ */
9102
+ AddTileset(params) {
9103
+ const { tileset, menuItemId } = params;
9104
+ if (!tileset || this.disposed) {
9105
+ return;
9106
+ }
9107
+ const existing = this.tilesets.get(tileset);
9108
+ if (existing) {
9109
+ existing.menuItemId = menuItemId !== null && menuItemId !== void 0 ? menuItemId : existing.menuItemId;
9110
+ return;
9111
+ }
9112
+ this.tilesets.set(tileset, {
9113
+ tileset,
9114
+ menuItemId,
9115
+ // Null when the property could not be intercepted, in which case this tileset simply never gets
9116
+ // culled rather than us fighting its owner over the same field.
9117
+ gate: InstallShowGate(tileset),
9118
+ culled: false
9119
+ });
9120
+ }
9121
+ /**
9122
+ * Stops tracking a tileset and hands its show flag back to its owner.
9123
+ * @param tileset
9124
+ */
9125
+ RemoveTileset(tileset) {
9126
+ var _a;
9127
+ const rego = this.tilesets.get(tileset);
9128
+ if (!rego) {
9129
+ return;
9130
+ }
9131
+ this.tilesets.delete(tileset);
9132
+ (_a = rego.gate) === null || _a === void 0 ? void 0 : _a.Release();
9133
+ }
9134
+ /**
9135
+ * Stops tracking every tileset belonging to a menu item.
9136
+ * @param menuItemId
8530
9137
  */
9138
+ RemoveTilesetsByMenuItem(menuItemId) {
9139
+ if (!menuItemId) {
9140
+ return;
9141
+ }
9142
+ for (const rego of Array.from(this.tilesets.values())) {
9143
+ if (rego.menuItemId === menuItemId) {
9144
+ this.RemoveTileset(rego.tileset);
9145
+ }
9146
+ }
9147
+ }
9148
+ /**
9149
+ * Every tileset currently tracked.
9150
+ */
9151
+ GetTilesets() {
9152
+ return Array.from(this.tilesets.values());
9153
+ }
9154
+ /**
9155
+ * Suppresses or restores a tileset.
9156
+ * Returns whether its effective visibility changed, so a caller can skip requesting a render when nothing did.
9157
+ * @param tileset
9158
+ * @param culled
9159
+ */
9160
+ SetTilesetCulled(tileset, culled) {
9161
+ const rego = this.tilesets.get(tileset);
9162
+ if (!(rego === null || rego === void 0 ? void 0 : rego.gate)) {
9163
+ return false;
9164
+ }
9165
+ rego.culled = Boolean(culled);
9166
+ return rego.gate.SetSuppressed(rego.culled);
9167
+ }
8531
9168
  GetEntityIds() {
8532
9169
  const ids = [];
8533
9170
  for (const entityId in this.rego) {
@@ -9901,7 +10538,11 @@ var EntitiesRenderManager;
9901
10538
  const displayItems = this.zoomControl ? (_d = this.zoomControl) === null || _d === void 0 ? void 0 : _d.filter(x => x.DisplayType != ZoomControl.EDisplayType.Hidden) : [];
9902
10539
  const shouldCheck = displayItems.length > 1;
9903
10540
  this.entityCheckQueue = new DelayQueue(() => {
9904
- this.doEntityCheck(Object.keys(this.renderedEntities));
10541
+ // With a single band this pass is not looking for band changes at all, it is the periodic registry cleanup, so it always runs over everything.
10542
+ const ids = shouldCheck
10543
+ ? this.filterPossibleBandChanges(Object.keys(this.renderedEntities))
10544
+ : Object.keys(this.renderedEntities);
10545
+ this.doEntityCheck(ids);
9905
10546
  }, shouldCheck ? 3000 : 30000);
9906
10547
  const feed = (_e = this.apiGetter.getApi()) === null || _e === void 0 ? void 0 : _e.RecordChangeFeed;
9907
10548
  if (feed) {
@@ -10144,6 +10785,30 @@ var EntitiesRenderManager;
10144
10785
  this.doEntityCheck(entityIds);
10145
10786
  }
10146
10787
  }
10788
+ /**
10789
+ * Narrows a camera-driven check to the entities whose zoom band could actually have changed.
10790
+ * @param ids
10791
+ */
10792
+ filterPossibleBandChanges(ids) {
10793
+ return ids.filter((id) => {
10794
+ const rego = this.visualsManager.GetRego({
10795
+ entityId: id,
10796
+ menuItemId: this.item.id
10797
+ });
10798
+ // Anything without a live graphic to measure against has to be checked.
10799
+ if (!(rego === null || rego === void 0 ? void 0 : rego.visual) || rego.stale) {
10800
+ return true;
10801
+ }
10802
+ const bandCheck = rego.visual._bandCheck;
10803
+ if (!bandCheck) {
10804
+ return true;
10805
+ }
10806
+ return RenderManager.CouldBandHaveChanged({
10807
+ viewer: this.viewer,
10808
+ bandCheck
10809
+ });
10810
+ });
10811
+ }
10147
10812
  async doEntityCheck(ids) {
10148
10813
  var _a, _b;
10149
10814
  if (this.isRunningCheck) {
@@ -10815,6 +11480,19 @@ var EntitiesRenderManager;
10815
11480
  optimizeTolerance: (_o = item.optimizeTolerance) !== null && _o !== void 0 ? _o : ""
10816
11481
  });
10817
11482
  }
11483
+ /**
11484
+ * Everything about a menu item that changes how its entities are drawn, as a comparable string.
11485
+ * Two items with the same signature produce identical graphics, so a hand-off between them needs no
11486
+ * re-render (and so no re-fetch of entity data the hand-off just avoided requesting).
11487
+ * A false mismatch only costs the optimization, so key ordering inside an inline Style is not normalized.
11488
+ */
11489
+ static GetRenderSignature(item) {
11490
+ return JSON.stringify({
11491
+ zoomControl: RenderManager.GetZoomControlSignature(item.CameraZoomSettings),
11492
+ historicDrawTrack: Boolean(item.historicDrawTrack),
11493
+ historicInterpolation: Boolean(item.historicInterpolation)
11494
+ });
11495
+ }
10818
11496
  PrepareHandoff() {
10819
11497
  var _a;
10820
11498
  if (this.disposed || this.useGeojson || this.item.enableClustering) {
@@ -10826,7 +11504,8 @@ var EntitiesRenderManager;
10826
11504
  internalByEntityId: new Map(this.internalByEntityId),
10827
11505
  allowedTypeIDs: [...this.allowedTypeIDs],
10828
11506
  renderQueue: [...this.renderQueue],
10829
- sourceMenuItemId: this.item.id
11507
+ sourceMenuItemId: this.item.id,
11508
+ renderSignature: Manager.GetRenderSignature(this.item)
10830
11509
  };
10831
11510
  this.unsetGetter();
10832
11511
  (_a = this.viewMonitorRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
@@ -10866,7 +11545,12 @@ var EntitiesRenderManager;
10866
11545
  if ((_a = payload.renderQueue) === null || _a === void 0 ? void 0 : _a.length) {
10867
11546
  this.distributeForRender(payload.renderQueue);
10868
11547
  }
10869
- this.ReRender({ entityIds: Object.keys(this.renderedEntities), force: true });
11548
+ // Adopted graphics are already drawn the way this item wants them, so re-rendering would only
11549
+ // re-request every entity from the API, which is the cost the hand-off exists to avoid.
11550
+ const needsRestyle = payload.renderSignature !== Manager.GetRenderSignature(this.item);
11551
+ if (needsRestyle) {
11552
+ this.ReRender({ entityIds: Object.keys(this.renderedEntities), force: true });
11553
+ }
10870
11554
  this.viewer.scene.requestRender();
10871
11555
  }
10872
11556
  }
@@ -11314,7 +11998,7 @@ var EntitiesLoadedRenderManager;
11314
11998
  * @param force TODO: This should re-render entities that are already rendered.
11315
11999
  */
11316
12000
  async renderAsGeojson(entities, force) {
11317
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
12001
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
11318
12002
  entities = entities.filter((entity) => {
11319
12003
  var _a;
11320
12004
  return !this.renderedEntities[(_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID];
@@ -11365,38 +12049,42 @@ var EntitiesLoadedRenderManager;
11365
12049
  if (!entities.length) {
11366
12050
  return;
11367
12051
  }
12052
+ // A menu item can declare that nothing about its render may touch the API. Styling then has to come from
12053
+ // the zoom item's inline Style, and names from what the entities already carry.
12054
+ const offline = Boolean((_a = this.item) === null || _a === void 0 ? void 0 : _a.offline);
11368
12055
  let style = null;
11369
- if ((zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) && zoomItem.StyleID != -1) {
12056
+ if (!offline && (zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) && zoomItem.StyleID != -1) {
11370
12057
  try {
11371
- style = (_a = (await Style.Get({
12058
+ style = (_b = (await Style.Get({
11372
12059
  api: this.apiGetter.getApi(),
11373
12060
  styleId: zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID
11374
- })).style) === null || _a === void 0 ? void 0 : _a.Settings;
12061
+ })).style) === null || _b === void 0 ? void 0 : _b.Settings;
11375
12062
  }
11376
12063
  // Probably deleted.
11377
12064
  catch (e) {
11378
12065
  console.error(e);
11379
12066
  }
11380
12067
  }
11381
- let entityTypeId = (_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b["EntityType.ID"];
12068
+ let entityTypeId = (_c = this.item.BruceEntity) === null || _c === void 0 ? void 0 : _c["EntityType.ID"];
11382
12069
  if (!entityTypeId) {
11383
- entityTypeId = (_d = (_c = entities.find(x => { var _a; return !!((_a = x.Bruce) === null || _a === void 0 ? void 0 : _a["EntityType.ID"]); })) === null || _c === void 0 ? void 0 : _c.Bruce) === null || _d === void 0 ? void 0 : _d["EntityType.ID"];
12070
+ entityTypeId = (_e = (_d = entities.find(x => { var _a; return !!((_a = x.Bruce) === null || _a === void 0 ? void 0 : _a["EntityType.ID"]); })) === null || _d === void 0 ? void 0 : _d.Bruce) === null || _e === void 0 ? void 0 : _e["EntityType.ID"];
11384
12071
  }
11385
- // Getting type regardless cause it's needed for name calculations.
12072
+ // Normally fetched regardless, since name calculation needs it. Offline callers take the names the
12073
+ // entities already carry instead.
11386
12074
  let entityType;
11387
- if (entityTypeId) {
12075
+ if (entityTypeId && !offline) {
11388
12076
  // Try using the Entity Type default if one is specified in the menu item.
11389
12077
  try {
11390
- entityType = (_e = (await EntityType.Get({
12078
+ entityType = (_f = (await EntityType.Get({
11391
12079
  entityTypeId: entityTypeId,
11392
12080
  api: this.apiGetter.getApi()
11393
- }))) === null || _e === void 0 ? void 0 : _e.entityType;
12081
+ }))) === null || _f === void 0 ? void 0 : _f.entityType;
11394
12082
  if (!style && entityType) {
11395
12083
  if (entityType["DisplaySetting.ID"] && entityType["DisplaySetting.ID"] > 0) {
11396
- style = (_f = (await Style.Get({
12084
+ style = (_g = (await Style.Get({
11397
12085
  api: this.apiGetter.getApi(),
11398
12086
  styleId: entityType["DisplaySetting.ID"]
11399
- })).style) === null || _f === void 0 ? void 0 : _f.Settings;
12087
+ })).style) === null || _g === void 0 ? void 0 : _g.Settings;
11400
12088
  }
11401
12089
  }
11402
12090
  }
@@ -11404,9 +12092,13 @@ var EntitiesLoadedRenderManager;
11404
12092
  console.error(e);
11405
12093
  }
11406
12094
  }
11407
- const pStyle = (_g = style === null || style === void 0 ? void 0 : style.polygonStyle) !== null && _g !== void 0 ? _g : {};
11408
- const lStyle = (_h = style === null || style === void 0 ? void 0 : style.polylineStyle) !== null && _h !== void 0 ? _h : {};
11409
- const polygonsClamped = ((_j = pStyle === null || pStyle === void 0 ? void 0 : pStyle.altitudeOption) === null || _j === void 0 ? void 0 : _j.id) == null ? true : ((_k = pStyle === null || pStyle === void 0 ? void 0 : pStyle.altitudeOption) === null || _k === void 0 ? void 0 : _k.id) == 0;
12095
+ // Fall back to whatever the zoom item carried inline, which is the only source offline.
12096
+ if (!style) {
12097
+ style = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.Style;
12098
+ }
12099
+ const pStyle = (_h = style === null || style === void 0 ? void 0 : style.polygonStyle) !== null && _h !== void 0 ? _h : {};
12100
+ const lStyle = (_j = style === null || style === void 0 ? void 0 : style.polylineStyle) !== null && _j !== void 0 ? _j : {};
12101
+ const polygonsClamped = ((_k = pStyle === null || pStyle === void 0 ? void 0 : pStyle.altitudeOption) === null || _k === void 0 ? void 0 : _k.id) == null ? true : ((_l = pStyle === null || pStyle === void 0 ? void 0 : pStyle.altitudeOption) === null || _l === void 0 ? void 0 : _l.id) == 0;
11410
12102
  const bFillColor = Calculator.GetColor(pStyle.fillColor, {}, []);
11411
12103
  const cFillColor = bFillColor ? colorToCColor$2(bFillColor) : Color.fromCssColorString("rgba(139, 195, 74, 0.8)");
11412
12104
  const bLineColor = Calculator.GetColor(pStyle.lineColor, {}, []);
@@ -11640,6 +12332,7 @@ var EntitiesLoadedRenderManager;
11640
12332
  return next;
11641
12333
  }
11642
12334
  async doRenderAsIndividuals(entities, force = false) {
12335
+ var _a;
11643
12336
  const { updated, entities: cEntities } = await EntityRenderEngine.Render({
11644
12337
  viewer: this.viewer,
11645
12338
  apiGetter: this.apiGetter,
@@ -11647,6 +12340,8 @@ var EntitiesLoadedRenderManager;
11647
12340
  menuItemId: this.item.id,
11648
12341
  visualRegister: this.visualsManager,
11649
12342
  zoomControl: this.item.CameraZoomSettings,
12343
+ // Carries the promise through to the engines, which do the rest of the API avoidance.
12344
+ offline: Boolean((_a = this.item) === null || _a === void 0 ? void 0 : _a.offline),
11650
12345
  force
11651
12346
  });
11652
12347
  if (this.disposed) {
@@ -13877,6 +14572,7 @@ var TilesetRenderEngine;
13877
14572
  }
13878
14573
  TilesetRenderEngine.ApplySettings = ApplySettings;
13879
14574
  async function Render(params) {
14575
+ var _a, _b, _c;
13880
14576
  const api = params.apiGetter.getApi(params.accountId);
13881
14577
  // Ensure regional url is loaded.
13882
14578
  await api.Loading;
@@ -13940,6 +14636,10 @@ var TilesetRenderEngine;
13940
14636
  console.error(e);
13941
14637
  }
13942
14638
  });
14639
+ (_a = params.register) === null || _a === void 0 ? void 0 : _a.AddTileset({
14640
+ tileset: cTileset,
14641
+ menuItemId: params.menuItemId
14642
+ });
13943
14643
  return cTileset;
13944
14644
  }
13945
14645
  else if (params.tileset.type === Tileset.EType.EntitiesSet) {
@@ -13956,6 +14656,10 @@ var TilesetRenderEngine;
13956
14656
  const cTileset = await createTileset(loadUrl, null, params.noMemoryLimit);
13957
14657
  params.viewer.scene.primitives.add(cTileset);
13958
14658
  watchTilesetDateRange(params.viewer, cTileset, params.tileset);
14659
+ (_b = params.register) === null || _b === void 0 ? void 0 : _b.AddTileset({
14660
+ tileset: cTileset,
14661
+ menuItemId: params.menuItemId
14662
+ });
13959
14663
  return cTileset;
13960
14664
  }
13961
14665
  else if (params.tileset.type == Tileset.EType.PointCloud) {
@@ -14001,6 +14705,10 @@ var TilesetRenderEngine;
14001
14705
  console.error(e);
14002
14706
  }
14003
14707
  });
14708
+ (_c = params.register) === null || _c === void 0 ? void 0 : _c.AddTileset({
14709
+ tileset: cTileset,
14710
+ menuItemId: params.menuItemId
14711
+ });
14004
14712
  return cTileset;
14005
14713
  }
14006
14714
  else {
@@ -14009,6 +14717,7 @@ var TilesetRenderEngine;
14009
14717
  }
14010
14718
  TilesetRenderEngine.Render = Render;
14011
14719
  async function RenderLegacy(params) {
14720
+ var _a;
14012
14721
  let { apiGetter, viewer, tileset, ionId, loadUrl, viaCdn, accountId, noMemoryLimit, backFaceCulling } = params;
14013
14722
  let settings = tileset === null || tileset === void 0 ? void 0 : tileset.Settings;
14014
14723
  settings = {
@@ -14131,6 +14840,10 @@ var TilesetRenderEngine;
14131
14840
  console.error(e);
14132
14841
  }
14133
14842
  });
14843
+ (_a = params.register) === null || _a === void 0 ? void 0 : _a.AddTileset({
14844
+ tileset: cTileset,
14845
+ menuItemId: params.menuItemId
14846
+ });
14134
14847
  return cTileset;
14135
14848
  }
14136
14849
  TilesetRenderEngine.RenderLegacy = RenderLegacy;
@@ -14645,6 +15358,8 @@ var TilesetCadRenderManager;
14645
15358
  }
14646
15359
  // Render outside the shared process because we can't control file requests from Cesium.
14647
15360
  const cTileset = this.cTileset = await TilesetRenderEngine.Render({
15361
+ register: this.visualsManager,
15362
+ menuItemId: this.item.id,
14648
15363
  loadUrl: loadUrl,
14649
15364
  apiGetter: this.getters.GetBruceGetter(),
14650
15365
  tileset: tileset,
@@ -16978,7 +17693,12 @@ var DataLabRenderManager;
16978
17693
  const displayItems = this.zoomControl ? this.zoomControl.filter(x => x.DisplayType != ZoomControl.EDisplayType.Hidden) : [];
16979
17694
  const shouldCheck = displayItems.length > 1;
16980
17695
  this.entityCheckQueue = new DelayQueue(() => {
16981
- this.doEntityCheck(Object.keys(this.renderedEntities));
17696
+ // With a single band this pass is not looking for band changes at all, so it always runs over
17697
+ // everything. With more than one, most entities can be ruled out without asking the server.
17698
+ const ids = shouldCheck
17699
+ ? this.filterPossibleBandChanges(Object.keys(this.renderedEntities))
17700
+ : Object.keys(this.renderedEntities);
17701
+ this.doEntityCheck(ids);
16982
17702
  }, shouldCheck ? 3000 : 30000);
16983
17703
  (_b = this.viewMonitorRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
16984
17704
  this.viewMonitorRemoval = this.monitor.Updated().Subscribe(() => {
@@ -16986,6 +17706,30 @@ var DataLabRenderManager;
16986
17706
  (_a = this.entityCheckQueue) === null || _a === void 0 ? void 0 : _a.Call();
16987
17707
  });
16988
17708
  }
17709
+ /**
17710
+ * Narrows a camera-driven check to the entities whose zoom band could actually have changed, so a pan or
17711
+ * zoom stops re-requesting the whole rendered set. See RenderManager.CouldBandHaveChanged.
17712
+ * @param ids
17713
+ */
17714
+ filterPossibleBandChanges(ids) {
17715
+ return ids.filter((id) => {
17716
+ const rego = this.visualsManager.GetRego({
17717
+ entityId: id,
17718
+ menuItemId: this.item.id
17719
+ });
17720
+ if (!(rego === null || rego === void 0 ? void 0 : rego.visual) || rego.stale) {
17721
+ return true;
17722
+ }
17723
+ const bandCheck = rego.visual._bandCheck;
17724
+ if (!bandCheck) {
17725
+ return true;
17726
+ }
17727
+ return RenderManager.CouldBandHaveChanged({
17728
+ viewer: this.viewer,
17729
+ bandCheck
17730
+ });
17731
+ });
17732
+ }
16989
17733
  preventCurrentCheckApiRefresh(entityIds) {
16990
17734
  var _a;
16991
17735
  this.reRenderState.markSkipForCurrentRun(entityIds, this.isRunningCheck);
@@ -17312,6 +18056,15 @@ var DataLabRenderManager;
17312
18056
  }
17313
18057
  return `datalab:${queryId}`;
17314
18058
  }
18059
+ /**
18060
+ * Everything about this menu item that changes how its entities are drawn, as a comparable string.
18061
+ * The hand-off key is only the query id, so this is what catches a styling difference between two items sharing that query.
18062
+ */
18063
+ static GetRenderSignature(item) {
18064
+ return JSON.stringify({
18065
+ zoomControl: RenderManager.GetZoomControlSignature(item.CameraZoomSettings)
18066
+ });
18067
+ }
17315
18068
  PrepareHandoff() {
17316
18069
  var _a, _b, _c;
17317
18070
  if (this.disposed || !this.getter) {
@@ -17332,7 +18085,8 @@ var DataLabRenderManager;
17332
18085
  getter,
17333
18086
  renderedEntities: this.renderedEntities,
17334
18087
  renderQueue: [...this.renderQueue],
17335
- sourceMenuItemId: this.item.id
18088
+ sourceMenuItemId: this.item.id,
18089
+ renderSignature: Manager.GetRenderSignature(this.item)
17336
18090
  };
17337
18091
  this.renderQueue = [];
17338
18092
  this.queryLoadId += 1;
@@ -17355,7 +18109,10 @@ var DataLabRenderManager;
17355
18109
  if ((_a = payload.renderQueue) === null || _a === void 0 ? void 0 : _a.length) {
17356
18110
  this.distributeForRender(payload.renderQueue);
17357
18111
  }
17358
- this.ReRender({ entityIds: Object.keys(this.renderedEntities), force: true });
18112
+ const needsRestyle = payload.renderSignature !== Manager.GetRenderSignature(this.item);
18113
+ if (needsRestyle) {
18114
+ this.ReRender({ entityIds: Object.keys(this.renderedEntities), force: true });
18115
+ }
17359
18116
  this.viewer.scene.requestRender();
17360
18117
  }
17361
18118
  }
@@ -18340,6 +19097,8 @@ var TilesetEntitiesRenderManager;
18340
19097
  }
18341
19098
  }
18342
19099
  const cTileset = this.cTileset = await TilesetRenderEngine.Render({
19100
+ register: this.visualsManager,
19101
+ menuItemId: this.item.id,
18343
19102
  apiGetter: this.getters.GetBruceGetter(),
18344
19103
  tileset: tileset,
18345
19104
  viewer: this.viewer,
@@ -18831,11 +19590,12 @@ var TilesetPointcloudRenderManager;
18831
19590
  constructor(params) {
18832
19591
  this.disposed = false;
18833
19592
  this.cTileset = null;
18834
- const { viewer, getters: apiGetter, item, initQueue } = params;
19593
+ const { viewer, getters: apiGetter, item, initQueue, register } = params;
18835
19594
  this.viewer = viewer;
18836
19595
  this.getters = apiGetter;
18837
19596
  this.item = item;
18838
19597
  this.initQueue = initQueue;
19598
+ this.visualsManager = register;
18839
19599
  }
18840
19600
  Init() {
18841
19601
  var _a;
@@ -18886,6 +19646,8 @@ var TilesetPointcloudRenderManager;
18886
19646
  return;
18887
19647
  }
18888
19648
  this.cTileset = await TilesetRenderEngine.Render({
19649
+ register: this.visualsManager,
19650
+ menuItemId: this.item.id,
18889
19651
  apiGetter: this.getters.GetBruceGetter(),
18890
19652
  tileset: tileset,
18891
19653
  viewer: this.viewer,
@@ -19478,6 +20240,8 @@ var TilesetArbRenderManager;
19478
20240
  if (!accountId) {
19479
20241
  accountId = this.getters.GetAccountId();
19480
20242
  }
20243
+ // Deliberately NOT registered with the visuals register, so it is never whole-tileset culled.
20244
+ // An arbitrary tileset can be produced outside our system and its declared bounds cannot be trusted.
19481
20245
  this.cTileset = await TilesetRenderEngine.RenderLegacy({
19482
20246
  apiGetter: this.getters.GetBruceGetter(),
19483
20247
  tileset: legacyTileset,
@@ -20146,8 +20910,8 @@ var RelationsRenderManager;
20146
20910
  * @param cEntity
20147
20911
  */
20148
20912
  const killCEntity = (cEntity) => {
20149
- if (cEntity && !this.viewer.isDestroyed() && this.viewer.entities.contains(cEntity)) {
20150
- this.viewer.entities.remove(cEntity);
20913
+ if (cEntity && !this.viewer.isDestroyed()) {
20914
+ RemoveCEntityFromScene(this.viewer, cEntity);
20151
20915
  }
20152
20916
  if (cEntity === null || cEntity === void 0 ? void 0 : cEntity._dispose) {
20153
20917
  cEntity._dispose();
@@ -20626,8 +21390,8 @@ var LegacyRelationsRenderManager;
20626
21390
  * @param cEntity
20627
21391
  */
20628
21392
  const killCEntity = (cEntity) => {
20629
- if (cEntity && !this.viewer.isDestroyed() && this.viewer.entities.contains(cEntity)) {
20630
- this.viewer.entities.remove(cEntity);
21393
+ if (cEntity && !this.viewer.isDestroyed()) {
21394
+ RemoveCEntityFromScene(this.viewer, cEntity);
20631
21395
  }
20632
21396
  if (cEntity === null || cEntity === void 0 ? void 0 : cEntity._dispose) {
20633
21397
  cEntity._dispose();
@@ -22658,6 +23422,7 @@ var MenuItemManager;
22658
23422
  rItem.renderManager = new TilesetPointcloudRenderManager.Manager({
22659
23423
  viewer: this.viewer,
22660
23424
  getters: this.getters,
23425
+ register: this.visualsRegister,
22661
23426
  item: params.item,
22662
23427
  initQueue: this.tilesetInitQueue
22663
23428
  });
@@ -22812,6 +23577,8 @@ var MenuItemManager;
22812
23577
  }
22813
23578
  if (allowHandoff && this.isHandoffCapable(item.renderManager)) {
22814
23579
  this.pendingHandoffPool.push(item);
23580
+ // Series segments stay recorded under the pooled id for now. A claimed hand-off
23581
+ // transfers them to the claiming item, and a flushed one clears them.
22815
23582
  }
22816
23583
  else {
22817
23584
  try {
@@ -22820,9 +23587,9 @@ var MenuItemManager;
22820
23587
  catch (e) {
22821
23588
  console.error(e);
22822
23589
  }
23590
+ this.ClearSeriesSegments(item.id);
22823
23591
  }
22824
23592
  this.items = this.items.filter(x => x.id !== id);
22825
- this.ClearSeriesSegments(item.id);
22826
23593
  (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
22827
23594
  }
22828
23595
  }
@@ -22870,14 +23637,38 @@ var MenuItemManager;
22870
23637
  if (poolIndex < 0) {
22871
23638
  return null;
22872
23639
  }
22873
- const pooled = this.pendingHandoffPool[poolIndex].renderManager;
23640
+ const pooledItem = this.pendingHandoffPool[poolIndex];
23641
+ const pooled = pooledItem.renderManager;
22874
23642
  const payload = pooled.PrepareHandoff();
22875
23643
  if (!payload) {
22876
23644
  return null;
22877
23645
  }
22878
23646
  this.pendingHandoffPool.splice(poolIndex, 1);
23647
+ this.transferSeriesSegments(pooledItem.id, item.id);
22879
23648
  return payload;
22880
23649
  }
23650
+ /**
23651
+ * Moves any series segments recorded for one menu item over to another, for a claimed hand-off.
23652
+ * The adopting manager may legitimately skip re-rendering (nothing to restyle), and segments are only
23653
+ * ever reported from inside a render, so without this the claiming item would end up with none.
23654
+ */
23655
+ transferSeriesSegments(fromItemId, toItemId) {
23656
+ const segments = this.seriesSegments.get(fromItemId);
23657
+ if (!(segments === null || segments === void 0 ? void 0 : segments.length)) {
23658
+ this.ClearSeriesSegments(fromItemId);
23659
+ return;
23660
+ }
23661
+ this.seriesSegments.delete(fromItemId);
23662
+ this.OnSeriesUpdated.Trigger({ itemId: fromItemId, segments: [] });
23663
+ const existing = this.seriesSegments.get(toItemId) || [];
23664
+ for (const segment of segments) {
23665
+ if (!existing.some((s) => s.key === segment.key)) {
23666
+ existing.push(segment);
23667
+ }
23668
+ }
23669
+ this.seriesSegments.set(toItemId, existing);
23670
+ this.OnSeriesUpdated.Trigger({ itemId: toItemId, segments: existing });
23671
+ }
22881
23672
  // Disposes everything left unclaimed in the pending-hand-off pool.
22882
23673
  FlushPendingHandoffs() {
22883
23674
  var _a;
@@ -22893,6 +23684,7 @@ var MenuItemManager;
22893
23684
  catch (e) {
22894
23685
  console.error(e);
22895
23686
  }
23687
+ this.ClearSeriesSegments(item.id);
22896
23688
  }
22897
23689
  }
22898
23690
  GetEnabledItemIds() {
@@ -34269,6 +35061,10 @@ function distanceToPolygon(point, polygon) {
34269
35061
  }
34270
35062
  return minDistance;
34271
35063
  }
35064
+ // Slack added when proving a zoom band cannot have changed.
35065
+ // See CouldBandHaveChanged: an entity's measured distance can jump by its altitude spread as the camera crosses over its footprint,
35066
+ // so being this close to a band edge means re-checking rather than trusting the bound.
35067
+ const BAND_EDGE_MARGIN_METERS = 500;
34272
35068
  // Cache for distance calculations.
34273
35069
  // It will keep only the most recent 3000 entries.
34274
35070
  const distanceCache = new LRUCache(3000);
@@ -34412,7 +35208,7 @@ function calculateDistance(point, entity) {
34412
35208
  var RenderManager;
34413
35209
  (function (RenderManager) {
34414
35210
  function DetermineZoomItem(params) {
34415
- const { viewer, entity, zoomControl } = params;
35211
+ const { viewer, entity, zoomControl, out } = params;
34416
35212
  const pos = EntityUtils.GetPos({
34417
35213
  viewer,
34418
35214
  entity
@@ -34425,11 +35221,16 @@ var RenderManager;
34425
35221
  if (!cameraPos || !cameraPos.latitude) {
34426
35222
  return null;
34427
35223
  }
34428
- const distance = calculateDistance({
35224
+ const cameraCarto = {
34429
35225
  altitude: cameraPos.height,
34430
35226
  latitude: Math$1.toDegrees(cameraPos.latitude),
34431
35227
  longitude: Math$1.toDegrees(cameraPos.longitude)
34432
- }, entity);
35228
+ };
35229
+ const distance = calculateDistance(cameraCarto, entity);
35230
+ if (out) {
35231
+ out.distance = distance;
35232
+ out.cameraPos = Cartesian3.fromDegrees(cameraCarto.longitude, cameraCarto.latitude, cameraCarto.altitude);
35233
+ }
34433
35234
  let zoomItem = (!distance && distance != 0) ? null : GetZoomControlFromDistance({
34434
35235
  zoomControl,
34435
35236
  distance
@@ -34442,6 +35243,41 @@ var RenderManager;
34442
35243
  return zoomItem;
34443
35244
  }
34444
35245
  RenderManager.DetermineZoomItem = DetermineZoomItem;
35246
+ /**
35247
+ * Returns whether an entity could have moved into a different zoom band since its graphic was rendered,
35248
+ * given where the camera is now.
35249
+ *
35250
+ * Answers true whenever it cannot prove otherwise, so a caller may safely skip only on a false.
35251
+ */
35252
+ function CouldBandHaveChanged(params) {
35253
+ var _a;
35254
+ const { viewer, bandCheck } = params;
35255
+ if (!bandCheck || bandCheck.distance == null || !bandCheck.cameraPos) {
35256
+ return true;
35257
+ }
35258
+ // An absent range is not something to reason about. Values that ARE present go through EnsureNumber so
35259
+ // this agrees with how GetZoomControlFromDistance matched the band in the first place.
35260
+ if (bandCheck.min == null || bandCheck.max == null) {
35261
+ return true;
35262
+ }
35263
+ const min = EnsureNumber(bandCheck.min);
35264
+ const max = EnsureNumber(bandCheck.max);
35265
+ if (!(max > min)) {
35266
+ return true;
35267
+ }
35268
+ const cameraPos = (_a = viewer.camera) === null || _a === void 0 ? void 0 : _a.positionCartographic;
35269
+ if (!cameraPos || !cameraPos.latitude) {
35270
+ return true;
35271
+ }
35272
+ const nowPos = Cartesian3.fromDegrees(Math$1.toDegrees(cameraPos.longitude), Math$1.toDegrees(cameraPos.latitude), cameraPos.height);
35273
+ const travelled = Cartesian3.distance(nowPos, bandCheck.cameraPos);
35274
+ if (isNaN(travelled)) {
35275
+ return true;
35276
+ }
35277
+ const slack = travelled + BAND_EDGE_MARGIN_METERS;
35278
+ return (bandCheck.distance - slack) < min || (bandCheck.distance + slack) > max;
35279
+ }
35280
+ RenderManager.CouldBandHaveChanged = CouldBandHaveChanged;
34445
35281
  function GetZoomControlFromDistance(params) {
34446
35282
  const { zoomControl, distance } = params;
34447
35283
  for (let i = 0; i < zoomControl.length; i++) {
@@ -34453,6 +35289,26 @@ var RenderManager;
34453
35289
  return null;
34454
35290
  }
34455
35291
  RenderManager.GetZoomControlFromDistance = GetZoomControlFromDistance;
35292
+ /**
35293
+ * Everything in a zoom control that changes how an entity is drawn, as a comparable string.
35294
+ * Used to tell whether two menu items would produce identical graphics (see a manager's GetRenderSignature).
35295
+ * A false mismatch only costs an optimization, so key ordering inside an inline Style is not normalized.
35296
+ */
35297
+ function GetZoomControlSignature(zoomControl) {
35298
+ return JSON.stringify([].concat(zoomControl !== null && zoomControl !== void 0 ? zoomControl : []).map((zoomItem) => {
35299
+ var _a, _b, _c, _d, _e, _f, _g;
35300
+ return ({
35301
+ MinZoom: (_a = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.MinZoom) !== null && _a !== void 0 ? _a : null,
35302
+ MaxZoom: (_b = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.MaxZoom) !== null && _b !== void 0 ? _b : null,
35303
+ DisplayType: (_c = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.DisplayType) !== null && _c !== void 0 ? _c : null,
35304
+ StyleID: (_d = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) !== null && _d !== void 0 ? _d : null,
35305
+ Style: (_e = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.Style) !== null && _e !== void 0 ? _e : null,
35306
+ LODCategoryID: (_f = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.LODCategoryID) !== null && _f !== void 0 ? _f : null,
35307
+ LODLevel: (_g = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.LODLevel) !== null && _g !== void 0 ? _g : null
35308
+ });
35309
+ }));
35310
+ }
35311
+ RenderManager.GetZoomControlSignature = GetZoomControlSignature;
34456
35312
  function GetZoomMinMax(params) {
34457
35313
  const { zoomControl } = params;
34458
35314
  let min = 0;
@@ -34541,8 +35397,8 @@ var EntityRenderEnginePoint;
34541
35397
  // We'll remove all that aren't in the allowed (direct) list.
34542
35398
  for (let i = 0; i < parts.length - 1; i++) {
34543
35399
  const part = parts[i];
34544
- if (part && part instanceof Entity && params.viewer.entities.contains(part) && !cEntity._siblingGraphics.includes(part)) {
34545
- params.viewer.entities.remove(part);
35400
+ if (part && part instanceof Entity && !cEntity._siblingGraphics.includes(part)) {
35401
+ RemoveCEntityFromScene(params.viewer, part);
34546
35402
  }
34547
35403
  }
34548
35404
  if (cEntity._parentEntity) {
@@ -34886,7 +35742,6 @@ var EntityRenderEnginePoint;
34886
35742
  cEntity.ellipse.semiMajorAxis = new ConstantProperty(radius);
34887
35743
  cEntity.ellipse.semiMinorAxis = new ConstantProperty(radius);
34888
35744
  }
34889
- cEntity.ellipse.outlineWidth = undefined;
34890
35745
  {
34891
35746
  const curHeightRef = GetCValue(params.viewer, cEntity.ellipse.heightReference);
34892
35747
  if (curHeightRef != heightRef) {
@@ -34907,13 +35762,26 @@ var EntityRenderEnginePoint;
34907
35762
  cEntity.ellipse.extrudedHeight = new ConstantProperty(extrusion.value);
34908
35763
  }
34909
35764
  }
34910
- cEntity.ellipse.zIndex = new ConstantProperty(1);
34911
- cEntity.ellipse.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance));
35765
+ SetCPropIfChanged({
35766
+ viewer: params.viewer,
35767
+ graphic: cEntity.ellipse,
35768
+ property: "zIndex",
35769
+ value: 1
35770
+ });
35771
+ SetCPropIfChanged({
35772
+ viewer: params.viewer,
35773
+ graphic: cEntity.ellipse,
35774
+ property: "distanceDisplayCondition",
35775
+ value: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance)
35776
+ });
34912
35777
  const curClassification = GetCValue(params.viewer, cEntity.ellipse.classificationType);
34913
35778
  if (curClassification != classification) {
34914
35779
  cEntity.ellipse.classificationType = new ConstantProperty(classification);
34915
35780
  }
34916
- cEntity.position = new ConstantPositionProperty(pos3d === null || pos3d === void 0 ? void 0 : pos3d.clone());
35781
+ const curPos = GetCValue(params.viewer, cEntity.position);
35782
+ if (!curPos || !pos3d || !Cartesian3.equals(curPos, pos3d)) {
35783
+ cEntity.position = new ConstantPositionProperty(pos3d === null || pos3d === void 0 ? void 0 : pos3d.clone());
35784
+ }
34917
35785
  // We'll use "SetDefaultColor" to updating the internal reference and to allow for an animation.
34918
35786
  // WARNING: ellipse does not support animation (yet?).
34919
35787
  CesiumEntityStyler.SetDefaultColor({
@@ -34921,6 +35789,8 @@ var EntityRenderEnginePoint;
34921
35789
  entity: cEntity,
34922
35790
  viewer: params.viewer,
34923
35791
  override: true,
35792
+ // Fill colour only. The outline sibling gets its own cOutline baked below.
35793
+ includeSiblings: false,
34924
35794
  requestRender: false
34925
35795
  });
34926
35796
  cEntity.show = true;
@@ -34959,8 +35829,18 @@ var EntityRenderEnginePoint;
34959
35829
  outlineEntity.ellipse.extrudedHeight = new ConstantProperty(outlineExtrusion.value);
34960
35830
  }
34961
35831
  }
34962
- outlineEntity.ellipse.zIndex = new ConstantProperty(2);
34963
- outlineEntity.ellipse.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance));
35832
+ SetCPropIfChanged({
35833
+ viewer: params.viewer,
35834
+ graphic: outlineEntity.ellipse,
35835
+ property: "zIndex",
35836
+ value: 2
35837
+ });
35838
+ SetCPropIfChanged({
35839
+ viewer: params.viewer,
35840
+ graphic: outlineEntity.ellipse,
35841
+ property: "distanceDisplayCondition",
35842
+ value: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance)
35843
+ });
34964
35844
  const curClassification = GetCValue(params.viewer, outlineEntity.ellipse.classificationType);
34965
35845
  if (curClassification != classification) {
34966
35846
  outlineEntity.ellipse.classificationType = new ConstantProperty(classification);
@@ -34972,6 +35852,7 @@ var EntityRenderEnginePoint;
34972
35852
  entity: outlineEntity,
34973
35853
  viewer: params.viewer,
34974
35854
  override: true,
35855
+ includeSiblings: false,
34975
35856
  requestRender: false
34976
35857
  });
34977
35858
  outlineEntity.show = true;
@@ -35236,7 +36117,7 @@ var EntityRenderEnginePoint;
35236
36117
  // De-dupe the tag IDs.
35237
36118
  tagIds = Array.from(new Set(tagIds));
35238
36119
  // Request if at least one then create a quick lookup map for tags.
35239
- const tags = tagIds.length > 0 ? (await EntityTag.GetListByIds({
36120
+ const tags = (!params.offline && tagIds.length > 0) ? (await EntityTag.GetListByIds({
35240
36121
  api,
35241
36122
  tagIds
35242
36123
  })).tags : [];
@@ -35248,7 +36129,7 @@ var EntityRenderEnginePoint;
35248
36129
  for (let i = 0; i < params.entities.length; i++) {
35249
36130
  const entity = params.entities[i];
35250
36131
  const zoomItem = params.zoomItems[entity.Bruce.ID];
35251
- const style = zoomItem.StyleID != -1 ? (_a = (await getStyle$1(api, entity, zoomItem.StyleID))) === null || _a === void 0 ? void 0 : _a.Settings : zoomItem.Style;
36132
+ const style = (!params.offline && zoomItem.StyleID != -1) ? (_a = (await getStyle$1(api, entity, zoomItem.StyleID))) === null || _a === void 0 ? void 0 : _a.Settings : zoomItem.Style;
35252
36133
  // Pass through tags that may be needed for styling.
35253
36134
  const tagIds = entity.Bruce["Layer.ID"];
35254
36135
  const tags = [];
@@ -35286,7 +36167,7 @@ var EntityRenderEnginePoint;
35286
36167
  rego: rego
35287
36168
  });
35288
36169
  if (cEntity) {
35289
- const name = await getName(api, entity);
36170
+ const name = await getName(api, entity, params.offline);
35290
36171
  cEntity.name = name;
35291
36172
  cEntity._renderGroup = EntityRenderEngine.GetRenderGroupId(zoomItem);
35292
36173
  cEntity.styleId = zoomItem.StyleID == -1 ? -1 : (+zoomItem.StyleID || null);
@@ -35457,7 +36338,16 @@ function getCylinderExtrusion(entity, tags, heightRef, style) {
35457
36338
  }
35458
36339
  return data;
35459
36340
  }
35460
- async function getName(api, entity) {
36341
+ async function getName(api, entity, offline) {
36342
+ var _a;
36343
+ const ownName = GetUsableEntityName(entity);
36344
+ if (ownName) {
36345
+ return ownName;
36346
+ }
36347
+ if (offline) {
36348
+ // Anything further needs a request, so offline callers take what the entity carries, placeholder or not.
36349
+ return ((_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.Name) || "Unnamed Entity";
36350
+ }
35461
36351
  try {
35462
36352
  const typeId = entity.Bruce["EntityType.ID"];
35463
36353
  let type = null;
@@ -35637,8 +36527,8 @@ var EntityRenderEngineModel3d;
35637
36527
  // We'll remove all that aren't in the allowed (direct) list.
35638
36528
  for (let i = 0; i < parts.length - 1; i++) {
35639
36529
  const part = parts[i];
35640
- if (part && part instanceof Entity && params.viewer.entities.contains(part) && !cEntity._siblingGraphics.includes(part)) {
35641
- params.viewer.entities.remove(part);
36530
+ if (part && part instanceof Entity && !cEntity._siblingGraphics.includes(part)) {
36531
+ RemoveCEntityFromScene(params.viewer, part);
35642
36532
  }
35643
36533
  }
35644
36534
  if (cEntity._parentEntity) {
@@ -36131,7 +37021,7 @@ var EntityRenderEngineModel3d;
36131
37021
  // De-dupe the tag IDs.
36132
37022
  tagIds = Array.from(new Set(tagIds));
36133
37023
  // Request if at least one then create a quick lookup map for tags.
36134
- const tags = tagIds.length > 0 ? (await EntityTag.GetListByIds({
37024
+ const tags = (!params.offline && tagIds.length > 0) ? (await EntityTag.GetListByIds({
36135
37025
  api,
36136
37026
  tagIds
36137
37027
  })).tags : [];
@@ -36143,7 +37033,7 @@ var EntityRenderEngineModel3d;
36143
37033
  for (let i = 0; i < params.entities.length; i++) {
36144
37034
  const entity = params.entities[i];
36145
37035
  const zoomItem = params.zoomItems[entity.Bruce.ID];
36146
- const style = zoomItem.StyleID != -1 ? (_a = (await getStyle$2(api, entity, zoomItem.StyleID))) === null || _a === void 0 ? void 0 : _a.Settings : zoomItem.Style;
37036
+ const style = (!params.offline && zoomItem.StyleID != -1) ? (_a = (await getStyle$2(api, entity, zoomItem.StyleID))) === null || _a === void 0 ? void 0 : _a.Settings : zoomItem.Style;
36147
37037
  // Pass through tags that may be needed for styling.
36148
37038
  const tagIds = entity.Bruce["Layer.ID"];
36149
37039
  const tags = [];
@@ -36182,8 +37072,8 @@ var EntityRenderEngineModel3d;
36182
37072
  for (let i = 0; i < params.entities.length; i++) {
36183
37073
  const entity = params.entities[i];
36184
37074
  const zoomItem = params.zoomItems[entity.Bruce.ID];
36185
- const styleRecord = zoomItem.StyleID != -1 ? (await getStyle$2(api, entity, zoomItem.StyleID)) : null;
36186
- const style = zoomItem.StyleID != -1 ? styleRecord === null || styleRecord === void 0 ? void 0 : styleRecord.Settings : zoomItem.Style;
37075
+ const styleRecord = (!params.offline && zoomItem.StyleID != -1) ? (await getStyle$2(api, entity, zoomItem.StyleID)) : null;
37076
+ const style = (!params.offline && zoomItem.StyleID != -1) ? styleRecord === null || styleRecord === void 0 ? void 0 : styleRecord.Settings : zoomItem.Style;
36187
37077
  const lod = lodData.find(x => x.entityId == entity.Bruce.ID);
36188
37078
  if (!(lod === null || lod === void 0 ? void 0 : lod.clientFileId)) {
36189
37079
  continue;
@@ -36229,7 +37119,7 @@ var EntityRenderEngineModel3d;
36229
37119
  rego: rego
36230
37120
  });
36231
37121
  if (cEntity) {
36232
- const name = await getName$1(api, entity);
37122
+ const name = await getName$1(api, entity, params.offline);
36233
37123
  cEntity.name = name;
36234
37124
  cEntity._renderGroup = EntityRenderEngine.GetRenderGroupId(zoomItem);
36235
37125
  cEntity.styleId = zoomItem.StyleID == -1 ? -1 : styleRecord === null || styleRecord === void 0 ? void 0 : styleRecord.ID;
@@ -36255,7 +37145,16 @@ function getHeightRef$1(style, defaultStyle) {
36255
37145
  }
36256
37146
  return heightRef;
36257
37147
  }
36258
- async function getName$1(api, entity) {
37148
+ async function getName$1(api, entity, offline) {
37149
+ var _a;
37150
+ const ownName = GetUsableEntityName(entity);
37151
+ if (ownName) {
37152
+ return ownName;
37153
+ }
37154
+ if (offline) {
37155
+ // Anything further needs a request, so offline callers take what the entity carries, placeholder or not.
37156
+ return ((_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.Name) || "Unnamed Entity";
37157
+ }
36259
37158
  try {
36260
37159
  const typeId = entity.Bruce["EntityType.ID"];
36261
37160
  let type = null;
@@ -36528,8 +37427,8 @@ var EntityRenderEnginePolyline;
36528
37427
  // Kill all expect last part. Last one is the primary entity.
36529
37428
  for (let i = 0; i < parts.length - 1; i++) {
36530
37429
  const part = parts[i];
36531
- if (part && part instanceof Entity && params.viewer.entities.contains(part)) {
36532
- params.viewer.entities.remove(part);
37430
+ if (part && part instanceof Entity) {
37431
+ RemoveCEntityFromScene(params.viewer, part);
36533
37432
  }
36534
37433
  }
36535
37434
  cEntity._siblingGraphics = [];
@@ -36560,7 +37459,12 @@ var EntityRenderEnginePolyline;
36560
37459
  if (oldClampToGround != newClampToGround) {
36561
37460
  cEntity.polyline.clampToGround = new ConstantProperty(newClampToGround);
36562
37461
  }
36563
- cEntity.polyline.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance));
37462
+ SetCPropIfChanged({
37463
+ viewer: params.viewer,
37464
+ graphic: cEntity.polyline,
37465
+ property: "distanceDisplayCondition",
37466
+ value: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance)
37467
+ });
36564
37468
  cEntity.corridor = undefined;
36565
37469
  }
36566
37470
  else {
@@ -36584,7 +37488,12 @@ var EntityRenderEnginePolyline;
36584
37488
  if (oldZIndex != getZIndex(style, entity, params.tags)) {
36585
37489
  cEntity.corridor.zIndex = new ConstantProperty(getZIndex(style, entity, params.tags));
36586
37490
  }
36587
- cEntity.corridor.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width));
37491
+ SetCPropIfChanged({
37492
+ viewer: params.viewer,
37493
+ graphic: cEntity.corridor,
37494
+ property: "distanceDisplayCondition",
37495
+ value: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width)
37496
+ });
36588
37497
  cEntity.polyline = undefined;
36589
37498
  }
36590
37499
  // We'll use "SetDefaultColor" to updating the internal reference and to allow for an animation.
@@ -36652,7 +37561,7 @@ var EntityRenderEnginePolyline;
36652
37561
  // De-dupe the tag IDs.
36653
37562
  tagIds = Array.from(new Set(tagIds));
36654
37563
  // Request if at least one then create a quick lookup map for tags.
36655
- const tags = tagIds.length > 0 ? (await EntityTag.GetListByIds({
37564
+ const tags = (!params.offline && tagIds.length > 0) ? (await EntityTag.GetListByIds({
36656
37565
  api,
36657
37566
  tagIds
36658
37567
  })).tags : [];
@@ -36664,7 +37573,7 @@ var EntityRenderEnginePolyline;
36664
37573
  for (let i = 0; i < params.entities.length; i++) {
36665
37574
  const entity = params.entities[i];
36666
37575
  const zoomItem = params.zoomItems[entity.Bruce.ID];
36667
- const style = zoomItem.StyleID != -1 ? (_a = (await getStyle$3(api, entity, zoomItem.StyleID))) === null || _a === void 0 ? void 0 : _a.Settings : zoomItem.Style;
37576
+ const style = (!params.offline && zoomItem.StyleID != -1) ? (_a = (await getStyle$3(api, entity, zoomItem.StyleID))) === null || _a === void 0 ? void 0 : _a.Settings : zoomItem.Style;
36668
37577
  // Pass through tags that may be needed for styling.
36669
37578
  const tagIds = entity.Bruce["Layer.ID"];
36670
37579
  const tags = [];
@@ -36687,7 +37596,7 @@ var EntityRenderEnginePolyline;
36687
37596
  rendered: (_c = params.rendered) === null || _c === void 0 ? void 0 : _c.get(entity.Bruce.ID)
36688
37597
  });
36689
37598
  if (cEntity) {
36690
- const name = await getName$2(api, entity);
37599
+ const name = await getName$2(api, entity, params.offline);
36691
37600
  cEntity.name = name;
36692
37601
  cEntity._renderGroup = EntityRenderEngine.GetRenderGroupId(zoomItem);
36693
37602
  cEntity.styleId = zoomItem.StyleID == -1 ? -1 : (+zoomItem.StyleID || null);
@@ -36698,7 +37607,16 @@ var EntityRenderEnginePolyline;
36698
37607
  }
36699
37608
  EntityRenderEnginePolyline.RenderGroup = RenderGroup;
36700
37609
  })(EntityRenderEnginePolyline || (EntityRenderEnginePolyline = {}));
36701
- async function getName$2(api, entity) {
37610
+ async function getName$2(api, entity, offline) {
37611
+ var _a;
37612
+ const ownName = GetUsableEntityName(entity);
37613
+ if (ownName) {
37614
+ return ownName;
37615
+ }
37616
+ if (offline) {
37617
+ // Anything further needs a request, so offline callers take what the entity carries, placeholder or not.
37618
+ return ((_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.Name) || "Unnamed Entity";
37619
+ }
36702
37620
  try {
36703
37621
  const typeId = entity.Bruce["EntityType.ID"];
36704
37622
  let type = null;
@@ -36940,6 +37858,13 @@ var TextureFrameSeriesAnimator;
36940
37858
  GetArchiveUrl() {
36941
37859
  return this.archiveUrl;
36942
37860
  }
37861
+ /**
37862
+ * The property this instance presents frames through, for a caller that needs to rebuild the entity's
37863
+ * material (eg: re-tinting for opacity/selection) without detaching the animation.
37864
+ */
37865
+ GetImageProperty() {
37866
+ return this.imageProperty;
37867
+ }
36943
37868
  onClockTick(currentTime) {
36944
37869
  if (this.disposed) {
36945
37870
  return;
@@ -37146,7 +38071,7 @@ var EntityRenderEnginePolygon;
37146
38071
  let textureDataUri = null;
37147
38072
  let textureTraceEffective = null;
37148
38073
  let frameArchive = null;
37149
- if (fillType === Style.EPolygonFillType.Texture) {
38074
+ if (fillType === Style.EPolygonFillType.Texture && !params.offline) {
37150
38075
  try {
37151
38076
  const resolved = await resolveTexturedFill({
37152
38077
  api: params.api,
@@ -37227,8 +38152,8 @@ var EntityRenderEnginePolygon;
37227
38152
  // We'll remove all that aren't in the allowed (direct) list.
37228
38153
  for (let i = 0; i < parts.length - 1; i++) {
37229
38154
  const part = parts[i];
37230
- if (part && part instanceof Entity && params.viewer.entities.contains(part) && !cEntity._siblingGraphics.includes(part)) {
37231
- params.viewer.entities.remove(part);
38155
+ if (part && part instanceof Entity && !cEntity._siblingGraphics.includes(part)) {
38156
+ RemoveCEntityFromScene(params.viewer, part);
37232
38157
  }
37233
38158
  }
37234
38159
  if (cEntity._parentEntity) {
@@ -37238,7 +38163,8 @@ var EntityRenderEnginePolygon;
37238
38163
  };
37239
38164
  const hasOutline = width > 0 && cLineColor;
37240
38165
  let cEntity = null;
37241
- if (!params.rendered || !params.rendered.polygon) {
38166
+ const isExistingGraphic = Boolean(params.rendered && params.rendered.polygon);
38167
+ if (!isExistingGraphic) {
37242
38168
  cEntity = new Entity({
37243
38169
  id: ObjectUtils.UId(15),
37244
38170
  polygon: {
@@ -37251,7 +38177,7 @@ var EntityRenderEnginePolygon;
37251
38177
  classificationType: classification,
37252
38178
  perPositionHeight: heightRef == HeightReference.CLAMP_TO_GROUND ? false : true,
37253
38179
  zIndex: zIndex,
37254
- distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width <= 0 || !cLineColor || units == "m" ? size : null, true)
38180
+ distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width <= 0 || !cLineColor || units == "m" ? size : null, true, size)
37255
38181
  },
37256
38182
  position: EntityUtils.GetPos({
37257
38183
  viewer: params.viewer,
@@ -37305,9 +38231,38 @@ var EntityRenderEnginePolygon;
37305
38231
  if (oldZIndex != zIndex) {
37306
38232
  cEntity.polygon.zIndex = new ConstantProperty(zIndex);
37307
38233
  }
37308
- cEntity.polygon.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width <= 0 || !cLineColor || units == "m" ? size : null, true));
37309
- cEntity.polygon.shadows = new ConstantProperty(ShadowMode.ENABLED);
37310
- cEntity.polygon.classificationType = new ConstantProperty(classification);
38234
+ SetCPropIfChanged({
38235
+ viewer: params.viewer,
38236
+ graphic: cEntity.polygon,
38237
+ property: "distanceDisplayCondition",
38238
+ value: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width <= 0 || !cLineColor || units == "m" ? size : null, true, size)
38239
+ });
38240
+ SetCPropIfChanged({
38241
+ viewer: params.viewer,
38242
+ graphic: cEntity.polygon,
38243
+ property: "shadows",
38244
+ value: ShadowMode.ENABLED
38245
+ });
38246
+ SetCPropIfChanged({
38247
+ viewer: params.viewer,
38248
+ graphic: cEntity.polygon,
38249
+ property: "classificationType",
38250
+ value: classification
38251
+ });
38252
+ cEntity.show = true;
38253
+ }
38254
+ // Must run before any material is baked below: the Animator takes over polygon.material, so a bake
38255
+ // has to know whether it is baking over a live animation or over a disposed one's restored material.
38256
+ const animator = syncTextureFrameArchive(cEntity, frameArchive, params.viewer, style.textureColorMask);
38257
+ if (animator) {
38258
+ CesiumEntityStyler.SetDefaultTextureImage({
38259
+ entity: cEntity,
38260
+ image: animator.GetImageProperty(),
38261
+ viewer: params.viewer,
38262
+ requestRender: false
38263
+ });
38264
+ }
38265
+ else if (isExistingGraphic) {
37311
38266
  // TODO: animate the texture!
37312
38267
  if (textureDataUri) {
37313
38268
  // Rebaking texture to ensure we don't revert to the wrong one later.
@@ -37334,12 +38289,12 @@ var EntityRenderEnginePolygon;
37334
38289
  entity: cEntity,
37335
38290
  viewer: params.viewer,
37336
38291
  override: true,
38292
+ // Fill colour only. The outline sibling gets its own line colour baked below.
38293
+ includeSiblings: false,
37337
38294
  requestRender: false
37338
38295
  });
37339
38296
  }
37340
- cEntity.show = true;
37341
38297
  }
37342
- syncTextureFrameArchive(cEntity, frameArchive, params.viewer, style.textureColorMask);
37343
38298
  if (frameArchive && params.onSeriesDiscovered) {
37344
38299
  const segment = frameArchiveToSeriesSegment(frameArchive, params.entity);
37345
38300
  if (segment) {
@@ -37372,7 +38327,7 @@ var EntityRenderEnginePolygon;
37372
38327
  classificationType: ClassificationType.TERRAIN,
37373
38328
  arcType: ArcType.GEODESIC,
37374
38329
  zIndex: zIndex,
37375
- distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance)
38330
+ distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, null, false, size)
37376
38331
  }) : null,
37377
38332
  corridor: units == "m" ? {
37378
38333
  positions: borderPosses,
@@ -37384,7 +38339,7 @@ var EntityRenderEnginePolygon;
37384
38339
  zIndex: zIndex + 1,
37385
38340
  cornerType: CornerType.MITERED,
37386
38341
  classificationType: classification,
37387
- distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width),
38342
+ distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width, false, size),
37388
38343
  shadows: ShadowMode.ENABLED
37389
38344
  } : null,
37390
38345
  show: true
@@ -37411,11 +38366,21 @@ var EntityRenderEnginePolygon;
37411
38366
  }
37412
38367
  const oldMaterial = GetCValue(params.viewer, cEntityBorder.polyline.material);
37413
38368
  const newMaterial = new ColorMaterialProperty(cLineColor);
37414
- if (compareColorMaterials(params.viewer, oldMaterial, newMaterial)) {
38369
+ if (!areColorMaterialsEqual(params.viewer, oldMaterial, newMaterial)) {
37415
38370
  cEntityBorder.polyline.material = newMaterial;
37416
38371
  }
37417
- cEntityBorder.polyline.classificationType = new ConstantProperty(ClassificationType.TERRAIN);
37418
- cEntityBorder.polyline.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance));
38372
+ SetCPropIfChanged({
38373
+ viewer: params.viewer,
38374
+ graphic: cEntityBorder.polyline,
38375
+ property: "classificationType",
38376
+ value: ClassificationType.TERRAIN
38377
+ });
38378
+ SetCPropIfChanged({
38379
+ viewer: params.viewer,
38380
+ graphic: cEntityBorder.polyline,
38381
+ property: "distanceDisplayCondition",
38382
+ value: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, null, false, size)
38383
+ });
37419
38384
  cEntityBorder.corridor = undefined;
37420
38385
  }
37421
38386
  else {
@@ -37441,15 +38406,35 @@ var EntityRenderEnginePolygon;
37441
38406
  }
37442
38407
  const oldMaterial = GetCValue(params.viewer, cEntityBorder.corridor.material);
37443
38408
  const newMaterial = new ColorMaterialProperty(cLineColor);
37444
- if (compareColorMaterials(params.viewer, oldMaterial, newMaterial)) {
38409
+ if (!areColorMaterialsEqual(params.viewer, oldMaterial, newMaterial)) {
37445
38410
  cEntityBorder.corridor.material = newMaterial;
37446
38411
  }
37447
- cEntityBorder.corridor.fill = new ConstantProperty(true);
37448
- cEntityBorder.corridor.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width));
38412
+ SetCPropIfChanged({
38413
+ viewer: params.viewer,
38414
+ graphic: cEntityBorder.corridor,
38415
+ property: "fill",
38416
+ value: true
38417
+ });
38418
+ SetCPropIfChanged({
38419
+ viewer: params.viewer,
38420
+ graphic: cEntityBorder.corridor,
38421
+ property: "distanceDisplayCondition",
38422
+ value: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width, false, size)
38423
+ });
37449
38424
  cEntityBorder.polyline = undefined;
37450
38425
  }
37451
38426
  cEntityBorder.show = true;
37452
38427
  }
38428
+ // Bake the outline's own colour as its default, so a deselect/opacity refresh restores the line
38429
+ // colour rather than falling back to the fill's.
38430
+ CesiumEntityStyler.SetDefaultColor({
38431
+ color: cLineColor,
38432
+ entity: cEntityBorder,
38433
+ viewer: params.viewer,
38434
+ override: true,
38435
+ includeSiblings: false,
38436
+ requestRender: false
38437
+ });
37453
38438
  cEntityBorder._parentEntity = cEntity;
37454
38439
  cEntity._siblingGraphics.push(cEntityBorder);
37455
38440
  for (let i = 0; i < holePosses.length; i++) {
@@ -37465,7 +38450,7 @@ var EntityRenderEnginePolygon;
37465
38450
  classificationType: ClassificationType.TERRAIN,
37466
38451
  arcType: ArcType.GEODESIC,
37467
38452
  zIndex: zIndex,
37468
- distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance)
38453
+ distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, null, false, size)
37469
38454
  }) : null,
37470
38455
  corridor: units == "m" ? {
37471
38456
  positions: borderPosses,
@@ -37477,7 +38462,7 @@ var EntityRenderEnginePolygon;
37477
38462
  zIndex: zIndex + 1,
37478
38463
  cornerType: CornerType.MITERED,
37479
38464
  classificationType: classification,
37480
- distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width),
38465
+ distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width, false, size),
37481
38466
  shadows: ShadowMode.ENABLED,
37482
38467
  } : null,
37483
38468
  show: true
@@ -37518,7 +38503,7 @@ var EntityRenderEnginePolygon;
37518
38503
  // De-dupe the tag IDs.
37519
38504
  tagIds = Array.from(new Set(tagIds));
37520
38505
  // Request if at least one then create a quick lookup map for tags.
37521
- const tags = tagIds.length > 0 ? (await EntityTag.GetListByIds({
38506
+ const tags = (!params.offline && tagIds.length > 0) ? (await EntityTag.GetListByIds({
37522
38507
  api,
37523
38508
  tagIds
37524
38509
  })).tags : [];
@@ -37530,7 +38515,7 @@ var EntityRenderEnginePolygon;
37530
38515
  for (let i = 0; i < params.entities.length; i++) {
37531
38516
  const entity = params.entities[i];
37532
38517
  const zoomItem = params.zoomItems[entity.Bruce.ID];
37533
- const style = zoomItem.StyleID != -1 ? (_a = (await getStyle$4(api, entity, zoomItem.StyleID))) === null || _a === void 0 ? void 0 : _a.Settings : zoomItem.Style;
38518
+ const style = (!params.offline && zoomItem.StyleID != -1) ? (_a = (await getStyle$4(api, entity, zoomItem.StyleID))) === null || _a === void 0 ? void 0 : _a.Settings : zoomItem.Style;
37534
38519
  // Pass through tags that may be needed for styling.
37535
38520
  const tagIds = entity.Bruce["Layer.ID"];
37536
38521
  const tags = [];
@@ -37565,7 +38550,7 @@ var EntityRenderEnginePolygon;
37565
38550
  console.error(`Polygon.RenderGroup: failed to render entity ${((_d = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _d === void 0 ? void 0 : _d.ID) || "<NONE>"}:`, e);
37566
38551
  }
37567
38552
  if (cEntity) {
37568
- const name = await getName$3(api, entity);
38553
+ const name = await getName$3(api, entity, params.offline);
37569
38554
  cEntity.name = name;
37570
38555
  cEntity._renderGroup = EntityRenderEngine.GetRenderGroupId(zoomItem);
37571
38556
  cEntity.styleId = zoomItem.StyleID == -1 ? -1 : (+zoomItem.StyleID || null);
@@ -37587,7 +38572,16 @@ var EntityRenderEnginePolygon;
37587
38572
  }
37588
38573
  EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator = DisposeTextureFrameSeriesAnimator;
37589
38574
  })(EntityRenderEnginePolygon || (EntityRenderEnginePolygon = {}));
37590
- async function getName$3(api, entity) {
38575
+ async function getName$3(api, entity, offline) {
38576
+ var _a;
38577
+ const ownName = GetUsableEntityName(entity);
38578
+ if (ownName) {
38579
+ return ownName;
38580
+ }
38581
+ if (offline) {
38582
+ // Anything further needs a request, so offline callers take what the entity carries, placeholder or not.
38583
+ return ((_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.Name) || "Unnamed Entity";
38584
+ }
37591
38585
  try {
37592
38586
  const typeId = entity.Bruce["EntityType.ID"];
37593
38587
  let type = null;
@@ -37691,6 +38685,7 @@ const _textureCache = new LRUCache(50);
37691
38685
  /**
37692
38686
  * Reconciles cEntity's TextureFrameSeriesAnimator.Animator (if any) against this render's resolved texture.
37693
38687
  * Called once cEntity is fully created/updated and definitely has a `polygon` graphics.
38688
+ * Returns the animator now driving the entity, or null if this render has no frame series.
37694
38689
  * @param cEntity
37695
38690
  * @param frameArchive
37696
38691
  * @param viewer
@@ -37699,22 +38694,24 @@ const _textureCache = new LRUCache(50);
37699
38694
  function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask) {
37700
38695
  const existing = cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
37701
38696
  if (frameArchive && existing && !existing.IsDisposed() && existing.GetArchiveUrl() === frameArchive.url) {
37702
- return;
38697
+ return existing;
37703
38698
  }
37704
38699
  if (existing && !existing.IsDisposed()) {
37705
38700
  existing.Dispose();
37706
38701
  cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = null;
37707
38702
  }
37708
- if (frameArchive) {
37709
- const animator = new TextureFrameSeriesAnimator.Animator({
37710
- viewer,
37711
- entity: cEntity,
37712
- archiveUrl: frameArchive.url,
37713
- frames: frameArchive.metadata.Frames,
37714
- textureColorMask
37715
- });
37716
- cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = animator;
38703
+ if (!frameArchive) {
38704
+ return null;
37717
38705
  }
38706
+ const animator = new TextureFrameSeriesAnimator.Animator({
38707
+ viewer,
38708
+ entity: cEntity,
38709
+ archiveUrl: frameArchive.url,
38710
+ frames: frameArchive.metadata.Frames,
38711
+ textureColorMask
38712
+ });
38713
+ cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = animator;
38714
+ return animator;
37718
38715
  }
37719
38716
  /**
37720
38717
  * Derives a min/max/label time-range segment from a resolved frame archive's Frames metadata,
@@ -37881,7 +38878,7 @@ async function buildTintedTextureDataUri(url, mask) {
37881
38878
  * @param b
37882
38879
  * @returns
37883
38880
  */
37884
- function compareColorMaterials(viewer, a, b) {
38881
+ function areColorMaterialsEqual(viewer, a, b) {
37885
38882
  // Same reference.
37886
38883
  if (a == b) {
37887
38884
  return true;
@@ -38202,15 +39199,44 @@ var StyleEffective;
38202
39199
  })(StyleEffective || (StyleEffective = {}));
38203
39200
  var EntityRenderEngine;
38204
39201
  (function (EntityRenderEngine) {
39202
+ /**
39203
+ * Identifies the zoom band a graphic was last rendered for, covering everything in that band which
39204
+ * changes how the entity is drawn. Used to decide whether a re-render is needed at all.
39205
+ *
39206
+ * Deliberately NOT the reuse key: most of a band's identity is plain graphics properties that can be
39207
+ * patched onto the existing graphic, so see GetGraphicShapeId for that decision.
39208
+ */
38205
39209
  function GetRenderGroupId(zoomItem) {
38206
39210
  if (!zoomItem) {
38207
39211
  return null;
38208
39212
  }
38209
- return zoomItem.MinZoom + "-" + zoomItem.MaxZoom;
39213
+ return RenderManager.GetZoomControlSignature([zoomItem]);
38210
39214
  }
38211
39215
  EntityRenderEngine.GetRenderGroupId = GetRenderGroupId;
39216
+ /**
39217
+ * Identifies the kind of Cesium graphic a zoom item + entity resolves to, ignoring anything that is merely a property on it.
39218
+ */
39219
+ function GetGraphicShapeId(zoomItem, entity) {
39220
+ var _a;
39221
+ let displayType = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.DisplayType;
39222
+ if (!displayType) {
39223
+ displayType = ZoomControl.EDisplayType.Hidden;
39224
+ }
39225
+ // Backwards compatibility, same mapping the render pass applies.
39226
+ else if (displayType == "polygon") {
39227
+ displayType = ZoomControl.EDisplayType.Geometry;
39228
+ }
39229
+ // Multi-geometry builds a container graphic holding sibling graphics rather than a flat one.
39230
+ const geometry = Entity$1.GetValue({
39231
+ entity: entity,
39232
+ path: ["Bruce", "VectorGeometry"]
39233
+ });
39234
+ const isMulti = ((_a = geometry === null || geometry === void 0 ? void 0 : geometry.MultiGeometry) === null || _a === void 0 ? void 0 : _a.length) > 1;
39235
+ return displayType + "|" + (isMulti ? "multi" : "single");
39236
+ }
39237
+ EntityRenderEngine.GetGraphicShapeId = GetGraphicShapeId;
38212
39238
  async function Render(params) {
38213
- var _a, _b, _c, _d;
39239
+ var _a, _b, _c;
38214
39240
  const groupRenderParams = {
38215
39241
  apiGetter: params.apiGetter,
38216
39242
  viewer: params.viewer,
@@ -38218,10 +39244,15 @@ var EntityRenderEngine;
38218
39244
  zoomItems: {},
38219
39245
  menuItemId: params.menuItemId,
38220
39246
  onSeriesDiscovered: params.onSeriesDiscovered,
38221
- visualRegister: params.visualRegister
39247
+ visualRegister: params.visualRegister,
39248
+ offline: params.offline
38222
39249
  };
38223
39250
  const updated = new Map();
38224
39251
  const cEntities = new Map();
39252
+ // Entity ID -> the graphic shape resolved for this pass, stamped onto the results at the end.
39253
+ const shapeIds = new Map();
39254
+ // Entity ID -> what its zoom band was resolved from, stamped alongside the shape.
39255
+ const bandChecks = new Map();
38225
39256
  const models = [];
38226
39257
  const multiGeometry = [];
38227
39258
  const polygons = [];
@@ -38271,11 +39302,21 @@ var EntityRenderEngine;
38271
39302
  for (let i = 0; i < params.entities.length; i++) {
38272
39303
  let entity = params.entities[i];
38273
39304
  const id = entity.Bruce.ID;
39305
+ const resolution = {};
38274
39306
  const zoomItem = RenderManager.DetermineZoomItem({
38275
39307
  viewer: params.viewer,
38276
39308
  entity,
38277
- zoomControl: params.zoomControl
38278
- });
39309
+ zoomControl: params.zoomControl,
39310
+ out: resolution
39311
+ });
39312
+ if (zoomItem && resolution.distance != null && resolution.cameraPos) {
39313
+ bandChecks.set(id, {
39314
+ distance: resolution.distance,
39315
+ cameraPos: resolution.cameraPos,
39316
+ min: zoomItem.MinZoom,
39317
+ max: zoomItem.MaxZoom
39318
+ });
39319
+ }
38279
39320
  let displayType = zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.DisplayType;
38280
39321
  if (!displayType) {
38281
39322
  displayType = ZoomControl.EDisplayType.Hidden;
@@ -38289,8 +39330,13 @@ var EntityRenderEngine;
38289
39330
  entityId: id,
38290
39331
  menuItemId: params.menuItemId
38291
39332
  });
39333
+ const existingVisual = existingRego === null || existingRego === void 0 ? void 0 : existingRego.visual;
38292
39334
  const newRenderId = GetRenderGroupId(zoomItem);
38293
- const oldRenderId = (_a = existingRego === null || existingRego === void 0 ? void 0 : existingRego.visual) === null || _a === void 0 ? void 0 : _a._renderGroup;
39335
+ const oldRenderId = existingVisual === null || existingVisual === void 0 ? void 0 : existingVisual._renderGroup;
39336
+ // Resolved before prepareGeometry() below, which flattens a single-item MultiGeometry and
39337
+ // would otherwise make the same entity resolve to a different shape than it was stamped with.
39338
+ const newShapeId = GetGraphicShapeId(zoomItem, entity);
39339
+ shapeIds.set(id, newShapeId);
38294
39340
  if (!params.force &&
38295
39341
  newRenderId == oldRenderId &&
38296
39342
  !(existingRego === null || existingRego === void 0 ? void 0 : existingRego.stale) &&
@@ -38300,13 +39346,13 @@ var EntityRenderEngine;
38300
39346
  cEntities.set(id, existingRego.visual);
38301
39347
  }
38302
39348
  else {
38303
- // Add so we can re-use the graphic and update it.
38304
- if (existingRego && newRenderId == oldRenderId) {
39349
+ const oldShapeId = existingVisual === null || existingVisual === void 0 ? void 0 : existingVisual._graphicShape;
39350
+ if (existingRego && newShapeId == oldShapeId) {
38305
39351
  cEntities.set(id, existingRego.visual);
38306
39352
  // Flag as no longer stale as we're unlikely to recreate the rego if we're reusing the graphic.
38307
39353
  existingRego.stale = false;
38308
39354
  // Update metadata for the same reason.
38309
- existingRego.outline = (_b = entity.Bruce) === null || _b === void 0 ? void 0 : _b.Outline;
39355
+ existingRego.outline = (_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.Outline;
38310
39356
  existingRego.entityTypeId = entity.Bruce["EntityType.ID"];
38311
39357
  updated.set(id, true);
38312
39358
  }
@@ -38320,7 +39366,7 @@ var EntityRenderEngine;
38320
39366
  entity: entity,
38321
39367
  path: ["Bruce", "VectorGeometry"]
38322
39368
  });
38323
- if ((_c = geometry === null || geometry === void 0 ? void 0 : geometry.MultiGeometry) === null || _c === void 0 ? void 0 : _c.length) {
39369
+ if ((_b = geometry === null || geometry === void 0 ? void 0 : geometry.MultiGeometry) === null || _b === void 0 ? void 0 : _b.length) {
38324
39370
  multiGeometry.push(entity);
38325
39371
  }
38326
39372
  else {
@@ -38363,7 +39409,7 @@ var EntityRenderEngine;
38363
39409
  entity: entity,
38364
39410
  path: ["Bruce", "VectorGeometry"]
38365
39411
  });
38366
- if (!((_d = geometry === null || geometry === void 0 ? void 0 : geometry.MultiGeometry) === null || _d === void 0 ? void 0 : _d.length)) {
39412
+ if (!((_c = geometry === null || geometry === void 0 ? void 0 : geometry.MultiGeometry) === null || _c === void 0 ? void 0 : _c.length)) {
38367
39413
  polygons.push(entity);
38368
39414
  continue;
38369
39415
  }
@@ -38482,6 +39528,21 @@ var EntityRenderEngine;
38482
39528
  }
38483
39529
  }
38484
39530
  }
39531
+ // Stamped centrally rather than per-engine so the value always matches what the gate above compared,
39532
+ // regardless of which engine (or fallback engine) ended up producing the graphic.
39533
+ cEntities.forEach((cEntity, id) => {
39534
+ if (!cEntity) {
39535
+ return;
39536
+ }
39537
+ const shapeId = shapeIds.get(id);
39538
+ if (shapeId) {
39539
+ cEntity._graphicShape = shapeId;
39540
+ }
39541
+ const bandCheck = bandChecks.get(id);
39542
+ if (bandCheck) {
39543
+ cEntity._bandCheck = bandCheck;
39544
+ }
39545
+ });
38485
39546
  return {
38486
39547
  entities: cEntities,
38487
39548
  updated
@@ -38517,9 +39578,7 @@ var EntityRenderEngine;
38517
39578
  });
38518
39579
  }
38519
39580
  }
38520
- if (viewer.entities.contains(entity)) {
38521
- viewer.entities.remove(entity);
38522
- }
39581
+ RemoveCEntityFromScene(viewer, entity);
38523
39582
  }
38524
39583
  doRemove({
38525
39584
  viewer: params.viewer,
@@ -38554,9 +39613,10 @@ var EntityRenderEngine;
38554
39613
  * @param max
38555
39614
  * @param meterSize size of "thing" in meters, eg polyline is 0.5 meters width. If size is provided we can potentially cull it earlier.
38556
39615
  * @param isPolygon
39616
+ * @param fitMeterSize overall extent of the "thing" in meters, eg a parcel's bounding-box diagonal. If provided we keep it visible far enough out to actually fit on screen.
38557
39617
  * @returns
38558
39618
  */
38559
- function GetDisplayCondition(min, max, meterSize, isPolygon) {
39619
+ function GetDisplayCondition(min, max, meterSize, isPolygon, fitMeterSize) {
38560
39620
  // Max is required.
38561
39621
  if (isNaN(+max)) {
38562
39622
  return undefined;
@@ -38586,6 +39646,15 @@ var EntityRenderEngine;
38586
39646
  return undefined;
38587
39647
  }
38588
39648
  }
39649
+ // The configured max is a guess about viewing distance, and for a large thing that guess is often short:
39650
+ // it vanishes while still overflowing the screen, so you can never see all of it at once.
39651
+ // A thing of size S needs roughly S * 0.87 of camera distance to fit a 60deg vertical fov, so we
39652
+ // floor the max at a margin above that rather than culling something you haven't finished zooming out to.
39653
+ fitMeterSize = +fitMeterSize;
39654
+ if (!isNaN(fitMeterSize) && fitMeterSize > 0) {
39655
+ const FIT_DISTANCE_PER_METER = 1.4;
39656
+ max = Math.max(max, fitMeterSize * FIT_DISTANCE_PER_METER);
39657
+ }
38589
39658
  return new DistanceDisplayCondition(min, max);
38590
39659
  }
38591
39660
  EntityRenderEngine.GetDisplayCondition = GetDisplayCondition;
@@ -38608,8 +39677,72 @@ function getColor$3(viewer, obj) {
38608
39677
  }
38609
39678
  return value;
38610
39679
  }
39680
+ const pendingRemovalsByViewer = new WeakMap();
39681
+ function scheduleEntityRemoval(viewer, entity) {
39682
+ let pending = pendingRemovalsByViewer.get(viewer);
39683
+ if (!pending) {
39684
+ pending = { entities: new Set(), hook: null };
39685
+ pendingRemovalsByViewer.set(viewer, pending);
39686
+ }
39687
+ pending.entities.add(entity);
39688
+ if (pending.hook) {
39689
+ return;
39690
+ }
39691
+ pending.hook = viewer.scene.postRender.addEventListener(() => {
39692
+ var _a;
39693
+ (_a = pending.hook) === null || _a === void 0 ? void 0 : _a.call(pending);
39694
+ pending.hook = null;
39695
+ const batch = Array.from(pending.entities);
39696
+ pending.entities.clear();
39697
+ if (!batch.length) {
39698
+ return;
39699
+ }
39700
+ WithSuspendedCCollections(batch.map(x => GetCEntityCollection(viewer, x)), () => {
39701
+ for (const item of batch) {
39702
+ try {
39703
+ EntityRenderEngine.Remove({
39704
+ viewer,
39705
+ entity: item
39706
+ });
39707
+ }
39708
+ catch (e) {
39709
+ console.error(e);
39710
+ }
39711
+ }
39712
+ });
39713
+ });
39714
+ }
38611
39715
  var CesiumAnimatedInOut;
38612
39716
  (function (CesiumAnimatedInOut) {
39717
+ /**
39718
+ * Runs any removals already queued for this viewer immediately, instead of waiting for the next render.
39719
+ * For teardown paths that need the scene emptied synchronously.
39720
+ */
39721
+ function FlushPendingRemovals(viewer) {
39722
+ var _a;
39723
+ const pending = pendingRemovalsByViewer.get(viewer);
39724
+ if (!(pending === null || pending === void 0 ? void 0 : pending.entities.size)) {
39725
+ return;
39726
+ }
39727
+ const batch = Array.from(pending.entities);
39728
+ pending.entities.clear();
39729
+ (_a = pending.hook) === null || _a === void 0 ? void 0 : _a.call(pending);
39730
+ pending.hook = null;
39731
+ WithSuspendedCCollections(batch.map(x => GetCEntityCollection(viewer, x)), () => {
39732
+ for (const item of batch) {
39733
+ try {
39734
+ EntityRenderEngine.Remove({
39735
+ viewer,
39736
+ entity: item
39737
+ });
39738
+ }
39739
+ catch (e) {
39740
+ console.error(e);
39741
+ }
39742
+ }
39743
+ });
39744
+ }
39745
+ CesiumAnimatedInOut.FlushPendingRemovals = FlushPendingRemovals;
38613
39746
  /**
38614
39747
  * Animates an Entity out of the scene.
38615
39748
  * Removes it from the scene at the end of the animation.
@@ -38628,13 +39761,7 @@ var CesiumAnimatedInOut;
38628
39761
  return;
38629
39762
  }
38630
39763
  removed = true;
38631
- const removal = viewer.scene.postRender.addEventListener(() => {
38632
- removal();
38633
- EntityRenderEngine.Remove({
38634
- viewer,
38635
- entity
38636
- });
38637
- });
39764
+ scheduleEntityRemoval(viewer, entity);
38638
39765
  };
38639
39766
  for (let i = 0; i < pieces.length; i++) {
38640
39767
  const piece = pieces[i];
@@ -39184,7 +40311,7 @@ var StyleUtils;
39184
40311
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
39185
40312
  })(StyleUtils || (StyleUtils = {}));
39186
40313
 
39187
- const VERSION = "7.0.2";
40314
+ const VERSION = "7.0.3";
39188
40315
  /**
39189
40316
  * Updates the environment instance used by bruce-cesium to one specified.
39190
40317
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.