bruce-cesium 7.6.1 → 7.6.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 (95) hide show
  1. package/dist/bruce-cesium.es5.js +2535 -385
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2524 -382
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +3 -1
  6. package/dist/lib/bruce-cesium.js.map +1 -1
  7. package/dist/lib/rendering/cesium-animated-property.js +63 -107
  8. package/dist/lib/rendering/cesium-animated-property.js.map +1 -1
  9. package/dist/lib/rendering/entity-gatherer.js +11 -3
  10. package/dist/lib/rendering/entity-gatherer.js.map +1 -1
  11. package/dist/lib/rendering/menu-item-creator.js +44 -23
  12. package/dist/lib/rendering/menu-item-creator.js.map +1 -1
  13. package/dist/lib/rendering/menu-item-manager.js +199 -17
  14. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  15. package/dist/lib/rendering/menu-item-styles.js +151 -0
  16. package/dist/lib/rendering/menu-item-styles.js.map +1 -0
  17. package/dist/lib/rendering/relation-render-engine.js +24 -5
  18. package/dist/lib/rendering/relation-render-engine.js.map +1 -1
  19. package/dist/lib/rendering/render-managers/common/entity-label-group.js +13 -9
  20. package/dist/lib/rendering/render-managers/common/entity-label-group.js.map +1 -1
  21. package/dist/lib/rendering/render-managers/common/entity-label.js +68 -7
  22. package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
  23. package/dist/lib/rendering/render-managers/common/entity-rerender-maintain-state.js +44 -5
  24. package/dist/lib/rendering/render-managers/common/entity-rerender-maintain-state.js.map +1 -1
  25. package/dist/lib/rendering/render-managers/common/entity-simulation.js +483 -0
  26. package/dist/lib/rendering/render-managers/common/entity-simulation.js.map +1 -0
  27. package/dist/lib/rendering/render-managers/common/historic-trail.js +123 -0
  28. package/dist/lib/rendering/render-managers/common/historic-trail.js.map +1 -0
  29. package/dist/lib/rendering/render-managers/common/historic-utils.js +17 -6
  30. package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -1
  31. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js +13 -2
  32. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js.map +1 -1
  33. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +20 -2
  34. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  35. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js +13 -2
  36. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js.map +1 -1
  37. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +66 -5
  38. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  39. package/dist/lib/rendering/render-managers/entities/entity-render-manager.js +11 -1
  40. package/dist/lib/rendering/render-managers/entities/entity-render-manager.js.map +1 -1
  41. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js +745 -106
  42. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js.map +1 -1
  43. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js +23 -1
  44. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js.map +1 -1
  45. package/dist/lib/rendering/render-managers/other/relations-render-manager.js +23 -1
  46. package/dist/lib/rendering/render-managers/other/relations-render-manager.js.map +1 -1
  47. package/dist/lib/rendering/render-managers/render-manager.js.map +1 -1
  48. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +19 -0
  49. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  50. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +145 -40
  51. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  52. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +18 -0
  53. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  54. package/dist/lib/rendering/tileset-styler.js +1 -1
  55. package/dist/lib/rendering/tileset-styler.js.map +1 -1
  56. package/dist/lib/rendering/visual-register-culler.js +2 -0
  57. package/dist/lib/rendering/visual-register-culler.js.map +1 -1
  58. package/dist/lib/rendering/visuals-register.js +89 -20
  59. package/dist/lib/rendering/visuals-register.js.map +1 -1
  60. package/dist/lib/utils/entity-utils.js +43 -2
  61. package/dist/lib/utils/entity-utils.js.map +1 -1
  62. package/dist/lib/widgets/tabs-left-panel/widget-left-panel-tab-menu-items.js +64 -12
  63. package/dist/lib/widgets/tabs-left-panel/widget-left-panel-tab-menu-items.js.map +1 -1
  64. package/dist/lib/widgets/widget-info-view.js +20 -7
  65. package/dist/lib/widgets/widget-info-view.js.map +1 -1
  66. package/dist/types/bruce-cesium.d.ts +3 -1
  67. package/dist/types/rendering/cesium-animated-property.d.ts +8 -6
  68. package/dist/types/rendering/entity-gatherer.d.ts +6 -1
  69. package/dist/types/rendering/menu-item-manager.d.ts +58 -1
  70. package/dist/types/rendering/menu-item-styles.d.ts +30 -0
  71. package/dist/types/rendering/relation-render-engine.d.ts +10 -0
  72. package/dist/types/rendering/render-managers/common/entity-label-group.d.ts +3 -2
  73. package/dist/types/rendering/render-managers/common/entity-label.d.ts +4 -0
  74. package/dist/types/rendering/render-managers/common/entity-rerender-maintain-state.d.ts +20 -0
  75. package/dist/types/rendering/render-managers/common/entity-simulation.d.ts +126 -0
  76. package/dist/types/rendering/render-managers/common/historic-trail.d.ts +30 -0
  77. package/dist/types/rendering/render-managers/common/historic-utils.d.ts +4 -0
  78. package/dist/types/rendering/render-managers/entities/entities-datalab-render-manager.d.ts +7 -1
  79. package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +7 -1
  80. package/dist/types/rendering/render-managers/entities/entities-loaded-render-manager.d.ts +7 -1
  81. package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +10 -1
  82. package/dist/types/rendering/render-managers/entities/entity-render-manager.d.ts +7 -1
  83. package/dist/types/rendering/render-managers/other/assembly-render-manager.d.ts +79 -2
  84. package/dist/types/rendering/render-managers/other/legacy-relations-render-manager.d.ts +5 -0
  85. package/dist/types/rendering/render-managers/other/relations-render-manager.d.ts +5 -0
  86. package/dist/types/rendering/render-managers/render-manager.d.ts +14 -1
  87. package/dist/types/rendering/render-managers/tilesets/tileset-arb-render-manager.d.ts +8 -1
  88. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +16 -2
  89. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +8 -1
  90. package/dist/types/rendering/tileset-styler.d.ts +2 -0
  91. package/dist/types/rendering/visuals-register.d.ts +13 -0
  92. package/dist/types/utils/entity-utils.d.ts +6 -0
  93. package/dist/types/widgets/tabs-left-panel/widget-left-panel-tab-menu-items.d.ts +1 -0
  94. package/dist/types/widgets/widget-info-view.d.ts +1 -0
  95. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  import * as Cesium from 'cesium';
2
- import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, ConstantPositionProperty, HeightReference, DistanceDisplayCondition, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, ShaderSource, Ellipsoid, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, PixelDatatype, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, ColorBlendMode, ShadowMode, ClassificationType, Model, HorizontalOrigin, VerticalOrigin, ArcType, CornerType, SceneMode, IntersectionTests, Ray, SceneTransforms, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, FeatureDetection, Matrix3, NearFarScalar, KmlDataSource, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, Ion, IonResource, Cesium3DTileset, OrthographicFrustum, EasingFunction, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Intersect, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, GeometryInstance, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, CustomDataSource, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
- import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, Entity as Entity$1, EntityLod, EntityTag, EntityType, ObjectUtils, Style, UrlUtils, Api, Carto, Geometry as Geometry$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, Bounds, ClientFileValueMap, Color as Color$1, BruceEvent, EntityCoords, DataLab, DelayQueue, AccountConcept, BruceApi, EntityRelation, RecordChangeFeed, EntitySource, Camera, ProgramKey, ProjectViewLegacyTile, Account, AccountLimits, Session, ProjectViewBookmarkGroup, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType, EntityHistoricData } from 'bruce-models';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, HeightReference, Matrix4, ConstantPositionProperty, DistanceDisplayCondition, ColorBlendMode, ShadowMode, ClassificationType, Model, ShaderSource, Ellipsoid, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, PixelDatatype, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, ArcType, CornerType, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, SceneMode, IntersectionTests, Ray, SceneTransforms, HorizontalOrigin, VerticalOrigin, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, FeatureDetection, Cesium3DTileStyle, Cesium3DTileColorBlendMode, HeadingPitchRange, Ion, IonResource, Cesium3DTileset, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Intersect, OrthographicFrustum, EasingFunction, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, GeometryInstance, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, CustomDataSource, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
+ import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, Entity as Entity$1, EntityLod, EntityTag, EntityType, ObjectUtils, Style, UrlUtils, Carto, Geometry as Geometry$1, Bounds, ClientFileValueMap, Color as Color$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, Api, DataLab, DelayQueue, AccountConcept, RecordChangeFeed, BruceApi, EntityRelation, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, ProjectViewBookmarkGroup, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType, EntityHistoricData } from 'bruce-models';
4
4
 
5
5
  /**
6
6
  * Ensures a number is returned from a given value.
@@ -2623,13 +2623,13 @@ var CesiumAnimatedProperty;
2623
2623
  class AnimateTPositionSeries {
2624
2624
  constructor(params) {
2625
2625
  this.lastCalcAttitude = null;
2626
- // Cache for calculated values.
2626
+ // Cache for calculated values: the scene time and set of positions they were worked out for.
2627
2627
  this.lastCalcTime = null;
2628
+ this.lastCalcRevision = -1;
2628
2629
  this.lastCalcPos3d = null;
2629
- this.lastCalcPosApplyOverTime = false;
2630
- this.lastCalcPos3dApplyOverTimeTarget = null;
2631
- this.lastCalcPos3dApplyOverTimeTimeStart = null;
2632
2630
  this.lastCalcHeading = null;
2631
+ // Bumped whenever the positions change, so a still clock re-applies a changed series.
2632
+ this.revision = 0;
2633
2633
  this.removal = null;
2634
2634
  // Track the timeline range for which we have data.
2635
2635
  this.minDate = null;
@@ -2659,6 +2659,7 @@ var CesiumAnimatedProperty;
2659
2659
  if (!newPositions || newPositions.length === 0) {
2660
2660
  return;
2661
2661
  }
2662
+ this.revision++;
2662
2663
  // Add new positions to our map, overwriting any existing positions with the same timestamp
2663
2664
  for (const pos of newPositions) {
2664
2665
  const key = pos.dateTime.toISOString();
@@ -2682,6 +2683,17 @@ var CesiumAnimatedProperty;
2682
2683
  }
2683
2684
  }
2684
2685
  }
2686
+ /**
2687
+ * Replaces every position with these, eg. once some that were simulated have been cleared.
2688
+ * @param positions Positions to animate through from now on.
2689
+ */
2690
+ replacePositions(positions) {
2691
+ this.positionsMap.clear();
2692
+ this.positions = [];
2693
+ this.minDate = null;
2694
+ this.maxDate = null;
2695
+ this.addPositions(positions);
2696
+ }
2685
2697
  /**
2686
2698
  * Get the current date range covered by our positions.
2687
2699
  */
@@ -2729,106 +2741,47 @@ var CesiumAnimatedProperty;
2729
2741
  /*
2730
2742
  * Returns the attitude of the record at an index, or nothing recorded when it names none.
2731
2743
  */
2732
- attitudeAt(index) {
2733
- var _a, _b, _c;
2734
- const pos = index >= 0 ? (_a = this.positions) === null || _a === void 0 ? void 0 : _a[index] : null;
2735
- return { pitch: (_b = pos === null || pos === void 0 ? void 0 : pos.pitch) !== null && _b !== void 0 ? _b : null, roll: (_c = pos === null || pos === void 0 ? void 0 : pos.roll) !== null && _c !== void 0 ? _c : null };
2744
+ /*
2745
+ * Pitch and roll between two positions the short way round; an angle only one names is held.
2746
+ */
2747
+ attitudeBetween(indexLast, indexNext, timeMs) {
2748
+ var _a, _b, _c, _d, _e, _f;
2749
+ const a = indexLast >= 0 ? (_a = this.positions) === null || _a === void 0 ? void 0 : _a[indexLast] : null;
2750
+ const b = indexNext >= 0 ? (_b = this.positions) === null || _b === void 0 ? void 0 : _b[indexNext] : null;
2751
+ const span = a && b ? b.dateTime.getTime() - a.dateTime.getTime() : 0;
2752
+ const t = span > 0 ? (timeMs - a.dateTime.getTime()) / span : 0;
2753
+ const turn = (x, y) => {
2754
+ if (x == null || y == null) {
2755
+ return x !== null && x !== void 0 ? x : null;
2756
+ }
2757
+ return x + (Math$1.negativePiToPi(Math$1.toRadians(y - x)) * 180 / Math.PI) * t;
2758
+ };
2759
+ return { pitch: turn((_c = a === null || a === void 0 ? void 0 : a.pitch) !== null && _c !== void 0 ? _c : null, (_d = b === null || b === void 0 ? void 0 : b.pitch) !== null && _d !== void 0 ? _d : null), roll: turn((_e = a === null || a === void 0 ? void 0 : a.roll) !== null && _e !== void 0 ? _e : null, (_f = b === null || b === void 0 ? void 0 : b.roll) !== null && _f !== void 0 ? _f : null) };
2736
2760
  }
2761
+ /*
2762
+ * Places the series at the scene's time, exactly rather than eased, so what moves with it never lags the clock.
2763
+ */
2737
2764
  update() {
2765
+ var _a;
2738
2766
  if (!this.positions || this.positions.length === 0) {
2739
2767
  return;
2740
2768
  }
2741
- // Get current time.
2742
- let now = this.viewer.scene.lastRenderTime || this.viewer.clock.currentTime;
2743
- const nowTime = JulianDate.toDate(now);
2744
- const nowTimeMs = nowTime.getTime();
2745
- const currentRealTimeMs = Date.now();
2746
- // Calculate the ideal position based on current time.
2747
- const targetPosition = this.calculatePosition(nowTimeMs);
2748
- // Initialize if this is the first update.
2749
- if (!this.lastCalcPos3d) {
2750
- this.lastCalcPos3d = targetPosition.pos3d;
2751
- this.lastCalcAttitude = this.attitudeAt(targetPosition.indexLast);
2752
- this.onUpdate(targetPosition.pos3d, this.lastCalcHeading, this.lastCalcAttitude);
2753
- return;
2754
- }
2755
- // Check if target has changed.
2756
- if (!this.lastCalcPos3dApplyOverTimeTarget ||
2757
- !Cartesian3.equals(this.lastCalcPos3dApplyOverTimeTarget, targetPosition.pos3d)) {
2758
- // If we were already interpolating, get the current interpolated position as our new starting point.
2759
- if (this.lastCalcPosApplyOverTime && this.lastCalcPos3dApplyOverTimeTimeStart) {
2760
- // Calculate where we are in the current interpolation.
2761
- const currentInterpolatedPos = this.interpolatePos3dOverTime(this.lastCalcPos3dApplyOverTimeTimeStart, currentRealTimeMs);
2762
- // Use this as our new starting position.
2763
- this.lastCalcPos3d = currentInterpolatedPos;
2764
- }
2765
- // Set new target and start time.
2766
- this.lastCalcPos3dApplyOverTimeTarget = targetPosition.pos3d;
2767
- this.lastCalcPos3dApplyOverTimeTimeStart = currentRealTimeMs;
2768
- this.lastCalcPosApplyOverTime = true;
2769
- }
2770
- // Calculate the interpolated position.
2771
- let pos3d;
2772
- if (this.lastCalcPosApplyOverTime) {
2773
- pos3d = this.interpolatePos3dOverTime(this.lastCalcPos3dApplyOverTimeTimeStart, currentRealTimeMs);
2774
- // If we've reached the target, update our base position.
2775
- if (Cartesian3.distance(pos3d, this.lastCalcPos3dApplyOverTimeTarget) < 0.01) {
2776
- this.lastCalcPos3d = this.lastCalcPos3dApplyOverTimeTarget;
2777
- this.lastCalcPosApplyOverTime = false;
2778
- }
2769
+ // The clock's time is the frame being drawn; the last render time is the frame before it.
2770
+ const nowTimeMs = JulianDate.toDate(this.viewer.clock.currentTime).getTime();
2771
+ if (nowTimeMs === this.lastCalcTime && this.revision === this.lastCalcRevision) {
2772
+ return;
2779
2773
  }
2780
- else {
2781
- pos3d = this.lastCalcPos3d;
2782
- }
2783
- // Calculate heading.
2784
- if (this.lastCalcPos3dApplyOverTimeTarget) {
2785
- if (this.lastCalcPos3d) {
2786
- const minDistanceThreshold = 0.05;
2787
- const currentToTargetDistance = Cartesian3.distance(this.lastCalcPos3d, this.lastCalcPos3dApplyOverTimeTarget);
2788
- if (currentToTargetDistance >= minDistanceThreshold) {
2789
- const lastPos = {
2790
- pos3d: this.lastCalcPos3d,
2791
- dateTime: this.lastCalcPos3dApplyOverTimeTimeStart ? new Date(this.lastCalcPos3dApplyOverTimeTimeStart) : null,
2792
- heading: null
2793
- };
2794
- const nextPos = {
2795
- pos3d: this.lastCalcPos3dApplyOverTimeTarget,
2796
- dateTime: null,
2797
- heading: null
2798
- };
2799
- if (lastPos && nextPos) {
2800
- this.lastCalcHeading = this.calculateHeading(nowTimeMs, lastPos, nextPos);
2801
- }
2802
- }
2803
- }
2774
+ this.lastCalcTime = nowTimeMs;
2775
+ this.lastCalcRevision = this.revision;
2776
+ const target = this.calculatePosition(nowTimeMs);
2777
+ const lastPos = this.positions[target.indexLast];
2778
+ const nextPos = this.positions[target.indexNext];
2779
+ if (lastPos && nextPos) {
2780
+ this.lastCalcHeading = (_a = this.calculateHeading(nowTimeMs, lastPos, nextPos)) !== null && _a !== void 0 ? _a : this.lastCalcHeading;
2804
2781
  }
2805
- else {
2806
- const posses = this.positions;
2807
- const lastPos = posses[targetPosition.indexLast];
2808
- const nextPos = posses[targetPosition.indexNext];
2809
- if (lastPos && nextPos) {
2810
- this.lastCalcHeading = this.calculateHeading(nowTimeMs, lastPos, nextPos);
2811
- }
2812
- }
2813
- // Provide the calculated values to the caller.
2814
- this.lastCalcAttitude = this.attitudeAt(targetPosition.indexLast);
2815
- this.onUpdate(pos3d, this.lastCalcHeading, this.lastCalcAttitude);
2816
- }
2817
- interpolatePos3dOverTime(startTimeMs, currentTimeMs) {
2818
- const lastPos = this.lastCalcPos3d;
2819
- const targetPos = this.lastCalcPos3dApplyOverTimeTarget;
2820
- if (!lastPos || !targetPos) {
2821
- return lastPos ? lastPos : targetPos;
2822
- }
2823
- const interpolationDuration = 200;
2824
- const elapsedTime = currentTimeMs - startTimeMs;
2825
- let progress = Math.min(elapsedTime / interpolationDuration, 1.0);
2826
- // Optional step to make it smoother.
2827
- progress = this.easeInOutQuad(progress);
2828
- return Cartesian3.lerp(lastPos, targetPos, progress, new Cartesian3());
2829
- }
2830
- easeInOutQuad(t) {
2831
- return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
2782
+ this.lastCalcPos3d = target.pos3d;
2783
+ this.lastCalcAttitude = this.attitudeBetween(target.indexLast, target.indexNext, nowTimeMs);
2784
+ this.onUpdate(target.pos3d, this.lastCalcHeading, this.lastCalcAttitude);
2832
2785
  }
2833
2786
  /**
2834
2787
  * Pre-process headings in the series.
@@ -2854,14 +2807,14 @@ var CesiumAnimatedProperty;
2854
2807
  * Calculate the position at the given time.
2855
2808
  */
2856
2809
  calculatePosition(currentTimeMs) {
2810
+ var _a, _b;
2857
2811
  const posses = this.positions;
2858
2812
  // No positions to interpolate
2859
2813
  if (!posses || posses.length === 0) {
2860
2814
  return {
2861
2815
  pos3d: new Cartesian3(),
2862
2816
  indexLast: -1,
2863
- indexNext: -1,
2864
- applyOverTime: false
2817
+ indexNext: -1
2865
2818
  };
2866
2819
  }
2867
2820
  // See if we're before the first position.
@@ -2869,8 +2822,7 @@ var CesiumAnimatedProperty;
2869
2822
  return {
2870
2823
  pos3d: posses[0].pos3d,
2871
2824
  indexLast: 0,
2872
- indexNext: 0,
2873
- applyOverTime: true
2825
+ indexNext: 0
2874
2826
  };
2875
2827
  }
2876
2828
  // See if we're after the last position.
@@ -2879,8 +2831,7 @@ var CesiumAnimatedProperty;
2879
2831
  return {
2880
2832
  pos3d: posses[lastIndex].pos3d,
2881
2833
  indexLast: lastIndex,
2882
- indexNext: lastIndex,
2883
- applyOverTime: true
2834
+ indexNext: lastIndex
2884
2835
  };
2885
2836
  }
2886
2837
  // Binary search to find the closest position.
@@ -2906,20 +2857,25 @@ var CesiumAnimatedProperty;
2906
2857
  return {
2907
2858
  pos3d: last.pos3d,
2908
2859
  indexLast: lastIndex,
2909
- indexNext: lastIndex,
2910
- applyOverTime: true
2860
+ indexNext: lastIndex
2911
2861
  };
2912
2862
  }
2913
2863
  // Interpolate between the two closest positions.
2914
2864
  const lastTime = last.dateTime.getTime();
2915
2865
  const nextTime = next.dateTime.getTime();
2916
2866
  const progress = (currentTimeMs - lastTime) / (nextTime - lastTime);
2917
- const interpolatedPos = Cartesian3.lerp(last.pos3d, next.pos3d, progress, new Cartesian3());
2867
+ // A straight line between two points on a curved path cuts inside it, so it is lifted back out.
2868
+ const chord = Cartesian3.lerp(last.pos3d, next.pos3d, progress, new Cartesian3());
2869
+ const chordCarto = Cartographic.fromCartesian(chord);
2870
+ const lastHeight = (_a = Cartographic.fromCartesian(last.pos3d)) === null || _a === void 0 ? void 0 : _a.height;
2871
+ const nextHeight = (_b = Cartographic.fromCartesian(next.pos3d)) === null || _b === void 0 ? void 0 : _b.height;
2872
+ const interpolatedPos = chordCarto && lastHeight != null && nextHeight != null
2873
+ ? Cartesian3.fromRadians(chordCarto.longitude, chordCarto.latitude, Math$1.lerp(lastHeight, nextHeight, progress))
2874
+ : chord;
2918
2875
  return {
2919
2876
  pos3d: interpolatedPos,
2920
2877
  indexLast: lastIndex,
2921
- indexNext: nextIndex,
2922
- applyOverTime: false
2878
+ indexNext: nextIndex
2923
2879
  };
2924
2880
  }
2925
2881
  /**
@@ -5493,6 +5449,485 @@ function ClusterItems(params) {
5493
5449
  return result;
5494
5450
  }
5495
5451
 
5452
+ /**
5453
+ * Entity data a caller asserts instead of what the API says, held for every menu item until it is cleared.
5454
+ */
5455
+ var EntitySimulation;
5456
+ (function (EntitySimulation) {
5457
+ const VIEWER_KEY = "_nextspaceSimulation";
5458
+ /**
5459
+ * The set used when a caller does not name one.
5460
+ */
5461
+ EntitySimulation.DEFAULT_SET = "default";
5462
+ // Record parts replaced whole rather than merged field by field, eg. a matrix or a geometry.
5463
+ const WHOLE_RECORD_PARTS = new Set(["geometry", "boundaries", "assemblyPosition"]);
5464
+ // Record parts that carry Entity data, in the order they are laid over an Entity.
5465
+ const RECORD_PARTS = ["location", "transform", "geometry", "boundaries", "assemblyPosition", "data"];
5466
+ /**
5467
+ * The store for a viewer, made on first use and shared by everything drawing into that scene.
5468
+ */
5469
+ function Get(viewer) {
5470
+ if (!viewer) {
5471
+ return null;
5472
+ }
5473
+ if (!viewer[VIEWER_KEY]) {
5474
+ viewer[VIEWER_KEY] = new Store();
5475
+ }
5476
+ return viewer[VIEWER_KEY];
5477
+ }
5478
+ EntitySimulation.Get = Get;
5479
+ function isPlainObject(value) {
5480
+ return value != null && typeof value === "object" && !Array.isArray(value);
5481
+ }
5482
+ // Entity values replaced whole by a patch, since half of one geometry over another describes neither.
5483
+ const WHOLE_ENTITY_PARTS = new Set(["VectorGeometry", "Boundaries", "AssemblyPosition"]);
5484
+ /**
5485
+ * Returns base with patch laid over it: objects merge key by key, anything else replaces.
5486
+ * @param whole names keys at the top level, or under Bruce, whose value is replaced rather than merged.
5487
+ */
5488
+ function Overlay(base, patch, whole = WHOLE_ENTITY_PARTS) {
5489
+ const lay = (into, over, depth, underBruce) => {
5490
+ if (over === undefined) {
5491
+ return into;
5492
+ }
5493
+ if (!isPlainObject(over) || !isPlainObject(into)) {
5494
+ return over;
5495
+ }
5496
+ const merged = { ...into };
5497
+ for (const key of Object.keys(over)) {
5498
+ const replace = (depth === 0 || underBruce) && whole.has(key);
5499
+ merged[key] = replace ? over[key] : lay(merged[key], over[key], depth + 1, depth === 0 && key === "Bruce");
5500
+ }
5501
+ return merged;
5502
+ };
5503
+ return lay(base, patch, 0, false);
5504
+ }
5505
+ EntitySimulation.Overlay = Overlay;
5506
+ /*
5507
+ * Lays one record's parts over another's, each part replaced whole where a part is one value.
5508
+ */
5509
+ function overlayRecord(base, over) {
5510
+ return Overlay(base, over, WHOLE_RECORD_PARTS);
5511
+ }
5512
+ /**
5513
+ * Each value a record names, as a path into the record. A part replaced whole is one value.
5514
+ */
5515
+ function recordPaths(record) {
5516
+ const paths = [];
5517
+ const walk = (value, path) => {
5518
+ if (isPlainObject(value) && !(path.length === 1 && WHOLE_RECORD_PARTS.has(path[0]))) {
5519
+ for (const key of Object.keys(value)) {
5520
+ walk(value[key], [...path, key]);
5521
+ }
5522
+ return;
5523
+ }
5524
+ if (value !== undefined) {
5525
+ paths.push(path);
5526
+ }
5527
+ };
5528
+ for (const part of RECORD_PARTS) {
5529
+ if ((record === null || record === void 0 ? void 0 : record[part]) != null) {
5530
+ walk(record[part], [part]);
5531
+ }
5532
+ }
5533
+ return paths;
5534
+ }
5535
+ /*
5536
+ * A copy of the record without the values at these paths, dropping objects left empty.
5537
+ */
5538
+ function withoutPaths(record, paths) {
5539
+ const strip = (value, path) => {
5540
+ if (!isPlainObject(value) || !(path[0] in value)) {
5541
+ return value;
5542
+ }
5543
+ const copy = { ...value };
5544
+ if (path.length === 1) {
5545
+ delete copy[path[0]];
5546
+ }
5547
+ else {
5548
+ const inner = strip(copy[path[0]], path.slice(1));
5549
+ if (isPlainObject(inner) && !Object.keys(inner).length) {
5550
+ delete copy[path[0]];
5551
+ }
5552
+ else {
5553
+ copy[path[0]] = inner;
5554
+ }
5555
+ }
5556
+ return copy;
5557
+ };
5558
+ let stripped = record;
5559
+ for (const path of paths) {
5560
+ stripped = strip(stripped, path);
5561
+ }
5562
+ return stripped;
5563
+ }
5564
+ /*
5565
+ * Whether a record still says anything about its Entity.
5566
+ */
5567
+ function hasContent(record) {
5568
+ return RECORD_PARTS.some(part => {
5569
+ const value = record === null || record === void 0 ? void 0 : record[part];
5570
+ return value != null && !(isPlainObject(value) && !Object.keys(value).length);
5571
+ });
5572
+ }
5573
+ function timeOf(record) {
5574
+ return (record === null || record === void 0 ? void 0 : record.dateTime) ? new Date(record.dateTime).getTime() : NaN;
5575
+ }
5576
+ class Store {
5577
+ constructor() {
5578
+ // Insertion order is precedence: a later set wins.
5579
+ this.sets = new Map();
5580
+ this.onChanged = new BruceEvent();
5581
+ }
5582
+ /**
5583
+ * Triggered after anything is simulated or cleared, naming the Entities affected.
5584
+ */
5585
+ get OnChanged() {
5586
+ return this.onChanged;
5587
+ }
5588
+ /**
5589
+ * Whether anything at all is simulated, so a caller can skip the work when nothing is.
5590
+ */
5591
+ get IsEmpty() {
5592
+ for (const set of this.sets.values()) {
5593
+ if (set.entities.size || set.patches.size || set.records.size) {
5594
+ return false;
5595
+ }
5596
+ }
5597
+ return true;
5598
+ }
5599
+ /**
5600
+ * The names of the sets holding anything, oldest first.
5601
+ */
5602
+ GetSets() {
5603
+ return Array.from(this.sets.keys());
5604
+ }
5605
+ /**
5606
+ * The time each set's records span, oldest set first, for sets holding any records.
5607
+ */
5608
+ GetSetRanges() {
5609
+ const ranges = [];
5610
+ for (const [name, set] of this.sets) {
5611
+ let startMs = Infinity;
5612
+ let endMs = -Infinity;
5613
+ for (const byTime of set.records.values()) {
5614
+ for (const ms of byTime.keys()) {
5615
+ startMs = Math.min(startMs, ms);
5616
+ endMs = Math.max(endMs, ms);
5617
+ }
5618
+ }
5619
+ if (set.records.size) {
5620
+ ranges.push({ set: name, startMs, endMs, entityIds: Array.from(set.records.keys()) });
5621
+ }
5622
+ }
5623
+ return ranges;
5624
+ }
5625
+ setFor(name) {
5626
+ const key = name || EntitySimulation.DEFAULT_SET;
5627
+ let set = this.sets.get(key);
5628
+ if (!set) {
5629
+ set = { entities: new Map(), patches: new Map(), records: new Map() };
5630
+ this.sets.set(key, set);
5631
+ }
5632
+ return set;
5633
+ }
5634
+ /**
5635
+ * Replaces the stand-in for each Entity given, within a set.
5636
+ */
5637
+ SetEntities(entities, set) {
5638
+ this.setBlobs(entities, set, false);
5639
+ }
5640
+ /**
5641
+ * Replaces the patch for each Entity given, within a set. A patch names only the data it changes.
5642
+ */
5643
+ SetPatches(patches, set) {
5644
+ this.setBlobs(patches, set, true);
5645
+ }
5646
+ setBlobs(blobs, set, patch) {
5647
+ var _a;
5648
+ const ids = [];
5649
+ for (const blob of blobs || []) {
5650
+ const id = (_a = blob === null || blob === void 0 ? void 0 : blob.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
5651
+ if (!id) {
5652
+ continue;
5653
+ }
5654
+ const held = this.setFor(set);
5655
+ (patch ? held.patches : held.entities).set(id, blob);
5656
+ ids.push(id);
5657
+ }
5658
+ if (ids.length) {
5659
+ this.onChanged.Trigger({ entityIds: ids, entities: true, records: false, set: set || EntitySimulation.DEFAULT_SET });
5660
+ }
5661
+ }
5662
+ /**
5663
+ * Adds records to each Entity's simulated history within a set, replacing any at the same instant.
5664
+ */
5665
+ SetRecords(records, set) {
5666
+ const ids = new Set();
5667
+ for (const record of records || []) {
5668
+ const id = record === null || record === void 0 ? void 0 : record.entityId;
5669
+ const timeMs = timeOf(record);
5670
+ if (!id || isNaN(timeMs)) {
5671
+ continue;
5672
+ }
5673
+ const held = this.setFor(set);
5674
+ let byTime = held.records.get(id);
5675
+ if (!byTime) {
5676
+ byTime = new Map();
5677
+ held.records.set(id, byTime);
5678
+ }
5679
+ byTime.set(timeMs, record);
5680
+ ids.add(id);
5681
+ }
5682
+ if (ids.size) {
5683
+ this.onChanged.Trigger({ entityIds: Array.from(ids), entities: false, records: true, set: set || EntitySimulation.DEFAULT_SET });
5684
+ }
5685
+ }
5686
+ /**
5687
+ * Forgets what is simulated for these Entities, or for every Entity when none are named.
5688
+ * @param params.set the set to clear. By default every set.
5689
+ * @param params.entities clear the stand-ins and patches. Default true.
5690
+ * @param params.records clear the historic records. Default true.
5691
+ */
5692
+ Clear(params) {
5693
+ var _a, _b;
5694
+ const entities = (params === null || params === void 0 ? void 0 : params.entities) !== false;
5695
+ const records = (params === null || params === void 0 ? void 0 : params.records) !== false;
5696
+ const names = (params === null || params === void 0 ? void 0 : params.set) ? [params.set] : Array.from(this.sets.keys());
5697
+ const cleared = new Set();
5698
+ for (const name of names) {
5699
+ const set = this.sets.get(name);
5700
+ if (!set) {
5701
+ continue;
5702
+ }
5703
+ const ids = (_a = params === null || params === void 0 ? void 0 : params.entityIds) !== null && _a !== void 0 ? _a : [...set.entities.keys(), ...set.patches.keys(), ...set.records.keys()];
5704
+ for (const id of ids) {
5705
+ const hadEntity = entities && set.entities.delete(id);
5706
+ const hadPatch = entities && set.patches.delete(id);
5707
+ const hadRecords = records && set.records.delete(id);
5708
+ if (hadEntity || hadPatch || hadRecords) {
5709
+ cleared.add(id);
5710
+ }
5711
+ }
5712
+ if (!set.entities.size && !set.patches.size && !set.records.size) {
5713
+ this.sets.delete(name);
5714
+ }
5715
+ }
5716
+ if (cleared.size) {
5717
+ this.onChanged.Trigger({ entityIds: Array.from(cleared), entities, records, set: (_b = params === null || params === void 0 ? void 0 : params.set) !== null && _b !== void 0 ? _b : null });
5718
+ }
5719
+ }
5720
+ /**
5721
+ * The Entity's stand-in, with any patches laid over it, or null when nothing stands in for it.
5722
+ */
5723
+ GetEntity(entityId) {
5724
+ var _a;
5725
+ let standIn = null;
5726
+ for (const set of this.sets.values()) {
5727
+ standIn = (_a = set.entities.get(entityId)) !== null && _a !== void 0 ? _a : standIn;
5728
+ }
5729
+ return standIn ? this.Overlay(standIn) : null;
5730
+ }
5731
+ /**
5732
+ * Whether a stand-in replaces this Entity, so the API need not be asked for it.
5733
+ */
5734
+ HasEntity(entityId) {
5735
+ for (const set of this.sets.values()) {
5736
+ if (set.entities.has(entityId)) {
5737
+ return true;
5738
+ }
5739
+ }
5740
+ return false;
5741
+ }
5742
+ /**
5743
+ * Whether a stand-in or a patch changes this Entity.
5744
+ */
5745
+ HasBlob(entityId) {
5746
+ for (const set of this.sets.values()) {
5747
+ if (set.entities.has(entityId) || set.patches.has(entityId)) {
5748
+ return true;
5749
+ }
5750
+ }
5751
+ return false;
5752
+ }
5753
+ /**
5754
+ * Returns an Entity with its simulated patches laid over it. The input is never changed.
5755
+ */
5756
+ Overlay(entity) {
5757
+ var _a;
5758
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
5759
+ if (!id) {
5760
+ return entity;
5761
+ }
5762
+ let result = entity;
5763
+ for (const set of this.sets.values()) {
5764
+ const patch = set.patches.get(id);
5765
+ if (patch) {
5766
+ result = Overlay(result, patch);
5767
+ }
5768
+ }
5769
+ return result;
5770
+ }
5771
+ /**
5772
+ * The Entity as simulated: its stand-in if there is one, otherwise what was read with patches laid over it.
5773
+ * @param entityId the Entity.
5774
+ * @param read what the API gave for it, if it was asked.
5775
+ */
5776
+ Resolve(entityId, read) {
5777
+ var _a;
5778
+ return (_a = this.GetEntity(entityId)) !== null && _a !== void 0 ? _a : (read ? this.Overlay(read) : null);
5779
+ }
5780
+ /**
5781
+ * Every Entity with a simulated stand-in or patch.
5782
+ */
5783
+ GetEntityIds() {
5784
+ const ids = new Set();
5785
+ for (const set of this.sets.values()) {
5786
+ set.entities.forEach((_, id) => ids.add(id));
5787
+ set.patches.forEach((_, id) => ids.add(id));
5788
+ }
5789
+ return Array.from(ids);
5790
+ }
5791
+ /**
5792
+ * Every Entity with simulated history.
5793
+ */
5794
+ GetRecordEntityIds() {
5795
+ const ids = new Set();
5796
+ for (const set of this.sets.values()) {
5797
+ set.records.forEach((_, id) => ids.add(id));
5798
+ }
5799
+ return Array.from(ids);
5800
+ }
5801
+ /**
5802
+ * An Entity's simulated history for a historic key, oldest first, every set's record at an instant merged.
5803
+ */
5804
+ GetRecords(entityId, attrKey) {
5805
+ const byTime = new Map();
5806
+ for (const set of this.sets.values()) {
5807
+ for (const [ms, record] of set.records.get(entityId) || []) {
5808
+ if (attrKey && record.attrKey && record.attrKey !== attrKey) {
5809
+ continue;
5810
+ }
5811
+ const held = byTime.get(ms);
5812
+ byTime.set(ms, held ? overlayRecord(held, record) : record);
5813
+ }
5814
+ }
5815
+ return Array.from(byTime.entries()).sort(([a], [b]) => a - b).map(([, record]) => record);
5816
+ }
5817
+ /**
5818
+ * Each value an Entity's simulated history names, with the instant from which it is the simulation's.
5819
+ */
5820
+ ownedFrom(records) {
5821
+ const owned = new Map();
5822
+ for (const record of records) {
5823
+ const ms = timeOf(record);
5824
+ for (const path of recordPaths(record)) {
5825
+ const key = path.join("\u0000");
5826
+ if (!owned.has(key)) {
5827
+ owned.set(key, { path, fromMs: ms });
5828
+ }
5829
+ }
5830
+ }
5831
+ return Array.from(owned.values());
5832
+ }
5833
+ /**
5834
+ * An Entity as it stands at an instant, with each value its simulated records have named by then laid over it.
5835
+ * @param entity the Entity as read for that instant, from the API or a simulated stand-in.
5836
+ * @param timeMs the instant in milliseconds.
5837
+ * @param attrKey the historic attribute key being followed, or null for any.
5838
+ */
5839
+ ApplyAt(entity, timeMs, attrKey) {
5840
+ var _a, _b, _c;
5841
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
5842
+ if (!id || timeMs == null || isNaN(timeMs)) {
5843
+ return entity;
5844
+ }
5845
+ // Laid oldest first, each record replacing only what it names, which leaves every value at its newest.
5846
+ let state = null;
5847
+ let stateMs = -Infinity;
5848
+ for (const record of this.GetRecords(id, attrKey)) {
5849
+ const ms = timeOf(record);
5850
+ if (ms > timeMs) {
5851
+ break;
5852
+ }
5853
+ const parts = {};
5854
+ for (const part of RECORD_PARTS) {
5855
+ if (record[part] != null) {
5856
+ parts[part] = record[part];
5857
+ }
5858
+ }
5859
+ state = state ? overlayRecord(state, parts) : parts;
5860
+ stateMs = ms;
5861
+ }
5862
+ if (!state) {
5863
+ return entity;
5864
+ }
5865
+ const bruce = { ...entity.Bruce };
5866
+ if (state.location) {
5867
+ bruce.Location = Overlay(bruce.Location, state.location);
5868
+ }
5869
+ if (state.transform) {
5870
+ bruce.Transform = Overlay(bruce.Transform, state.transform);
5871
+ }
5872
+ if (state.geometry) {
5873
+ bruce.VectorGeometry = state.geometry;
5874
+ }
5875
+ if (state.boundaries) {
5876
+ bruce.Boundaries = state.boundaries;
5877
+ }
5878
+ if (state.assemblyPosition) {
5879
+ bruce.AssemblyPosition = state.assemblyPosition;
5880
+ }
5881
+ // Says the newest record the Entity now reflects, the way a read at a point does.
5882
+ const outline = entity.Bruce.Outline || [];
5883
+ const readAt = outline.find(x => !!x.DateTime);
5884
+ const readMs = readAt ? new Date(readAt.DateTime).getTime() : -Infinity;
5885
+ const dated = {
5886
+ ...(readAt || { "Source.Name": "Simulation", Editable: false, Kind: Entity$1.EOutlineKind.Attribute }),
5887
+ DateTime: new Date(Math.max(readMs, stateMs)).toISOString(),
5888
+ ForeignKey: (_b = readAt === null || readAt === void 0 ? void 0 : readAt.ForeignKey) !== null && _b !== void 0 ? _b : attrKey
5889
+ };
5890
+ bruce.Outline = [dated, ...outline.filter(x => x !== readAt)];
5891
+ const data = state.data ? Overlay({ ...entity, Bruce: undefined }, state.data) : entity;
5892
+ return { ...data, Bruce: Overlay(bruce, (_c = state.data) === null || _c === void 0 ? void 0 : _c.Bruce) };
5893
+ }
5894
+ /**
5895
+ * API records with the simulated ones joined in, for the named Entities or every simulated one.
5896
+ */
5897
+ MergeRecords(recordsByIds, attrKey, entityIds) {
5898
+ if (this.IsEmpty) {
5899
+ return recordsByIds;
5900
+ }
5901
+ const ids = entityIds !== null && entityIds !== void 0 ? entityIds : this.GetRecordEntityIds();
5902
+ let merged = null;
5903
+ for (const id of ids) {
5904
+ const simulated = this.GetRecords(id, attrKey);
5905
+ if (!simulated.length) {
5906
+ continue;
5907
+ }
5908
+ const owned = this.ownedFrom(simulated);
5909
+ const byTime = new Map();
5910
+ for (const record of (recordsByIds === null || recordsByIds === void 0 ? void 0 : recordsByIds[id]) || []) {
5911
+ const ms = timeOf(record);
5912
+ const kept = withoutPaths(record, owned.filter(x => x.fromMs <= ms).map(x => x.path));
5913
+ if (hasContent(kept)) {
5914
+ byTime.set(ms, kept);
5915
+ }
5916
+ }
5917
+ for (const record of simulated) {
5918
+ const ms = timeOf(record);
5919
+ const held = byTime.get(ms);
5920
+ byTime.set(ms, held ? overlayRecord(held, record) : record);
5921
+ }
5922
+ merged = merged !== null && merged !== void 0 ? merged : { ...(recordsByIds || {}) };
5923
+ merged[id] = Array.from(byTime.entries()).sort(([a], [b]) => a - b).map(([, record]) => record);
5924
+ }
5925
+ return merged !== null && merged !== void 0 ? merged : recordsByIds;
5926
+ }
5927
+ }
5928
+ EntitySimulation.Store = Store;
5929
+ })(EntitySimulation || (EntitySimulation = {}));
5930
+
5496
5931
  /**
5497
5932
  * Returns cesium property's value.
5498
5933
  * This will check if it's one that changes over time, or just a fixed value.
@@ -5548,6 +5983,8 @@ var EntityLabel;
5548
5983
  const PHOTO_CACHE_KEY = "_nextspaceLabelPhotos";
5549
5984
  // Clearance around a model's box, as a share of its half-extent on each axis.
5550
5985
  const MODEL_BOX_PADDING = 0.02;
5986
+ // How far a moving model's box may drift before it is moved, in metres.
5987
+ const MODEL_BOX_FOLLOW_TOLERANCE = 0.001;
5551
5988
  // Share of each edge drawn out from its corners while the box is idle. Hovering draws the edges whole.
5552
5989
  const MODEL_BOX_BRACKET = 0.2;
5553
5990
  // Under this many pixels across, a box reads as a smudge and the ring takes over.
@@ -6764,6 +7201,7 @@ var EntityLabel;
6764
7201
  constructor() {
6765
7202
  // Whether a box competes for space comes only from inputs (camera distance, hover, the pointer), never from
6766
7203
  // how its label was laid out, so an animation moving a label cannot feed back into which boxes show.
7204
+ // corners is where the box is drawn this frame: stored, moved by the entry's motion.
6767
7205
  this.entries = new Map();
6768
7206
  this.groups = null;
6769
7207
  // The hovered Entity. Its box is drawn on its own and whole, ahead of every other box.
@@ -6781,7 +7219,7 @@ var EntityLabel;
6781
7219
  Set(id, corners, entityId, inputs) {
6782
7220
  const current = this.entries.get(id);
6783
7221
  // A label rebuilt over the same box keeps its place and how far it has animated.
6784
- if (corners && (current === null || current === void 0 ? void 0 : current.corners) === corners && current.entityId === entityId) {
7222
+ if (corners && (current === null || current === void 0 ? void 0 : current.stored) === corners && current.entityId === entityId) {
6785
7223
  Object.assign(current, inputs);
6786
7224
  return;
6787
7225
  }
@@ -6789,17 +7227,41 @@ var EntityLabel;
6789
7227
  return;
6790
7228
  }
6791
7229
  if (corners) {
6792
- this.entries.set(id, { ...inputs, corners, entityId, active: false, wanted: false });
7230
+ this.entries.set(id, { ...inputs, corners, stored: corners, entityId, active: false, wanted: false });
6793
7231
  }
6794
7232
  else
6795
7233
  this.entries.delete(id);
6796
7234
  this.since.delete(id);
7235
+ this.forget(id);
7236
+ }
7237
+ /*
7238
+ * Drops the groups and outlines a box was in, so they are worked out again from where it is now.
7239
+ */
7240
+ forget(id) {
6797
7241
  this.groups = null;
6798
7242
  for (const [key, group] of this.outlines) {
6799
7243
  if (group.members.includes(id))
6800
7244
  this.outlines.delete(key);
6801
7245
  }
6802
7246
  }
7247
+ /*
7248
+ * Moves each box whose model moved since the last frame, keeping where it has got to in showing.
7249
+ */
7250
+ follow() {
7251
+ var _a;
7252
+ for (const [id, entry] of this.entries) {
7253
+ const motion = (_a = entry.motion) === null || _a === void 0 ? void 0 : _a.call(entry);
7254
+ if (!motion) {
7255
+ continue;
7256
+ }
7257
+ const moved = entry.stored.map(corner => Matrix4.multiplyByPoint(motion, corner, new Cartesian3()));
7258
+ if (moved.every((corner, i) => Cartesian3.equalsEpsilon(corner, entry.corners[i], 0, MODEL_BOX_FOLLOW_TOLERANCE))) {
7259
+ continue;
7260
+ }
7261
+ entry.corners = moved;
7262
+ this.forget(id);
7263
+ }
7264
+ }
6803
7265
  /*
6804
7266
  * The box drawn for a label this frame, or undefined when it has none or lost its place.
6805
7267
  */
@@ -6867,6 +7329,7 @@ var EntityLabel;
6867
7329
  */
6868
7330
  Resolve(viewer, pointer) {
6869
7331
  var _a, _b, _c, _d;
7332
+ this.follow();
6870
7333
  this.frames.clear();
6871
7334
  this.hulls.clear();
6872
7335
  this.drawn = [];
@@ -7289,9 +7752,16 @@ var EntityLabel;
7289
7752
  }
7290
7753
  if (this.api) {
7291
7754
  const rego = this.rego;
7755
+ const simulation = EntitySimulation.Get(this.viewer);
7756
+ let simulated = null;
7757
+ if (simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob(rego.entityId)) {
7758
+ // A stand-in is named as is; a patch is laid over the API's record first.
7759
+ const read = simulation.HasEntity(rego.entityId) ? null : (await Entity$1.Get({ api: this.api, entityId: rego.entityId, migrated: true })).entity;
7760
+ simulated = simulation.Resolve(rego.entityId, read);
7761
+ }
7292
7762
  this.rego.name = await Entity$1.CalculateNameAsync({
7293
7763
  api: this.api,
7294
- entity: null,
7764
+ entity: simulated,
7295
7765
  entityId: rego.entityId,
7296
7766
  defaultToId: false
7297
7767
  });
@@ -7320,6 +7790,21 @@ var EntityLabel;
7320
7790
  clearTimeout(this.loadTimeout);
7321
7791
  this.loadTimeout = null;
7322
7792
  }
7793
+ /*
7794
+ * Reads the label's Entity again and redraws it, eg. after what is simulated for it changed.
7795
+ */
7796
+ Reload() {
7797
+ if (this.disposed) {
7798
+ return;
7799
+ }
7800
+ this.additionalTexts = this.options.text != null ? [] : null;
7801
+ this.loadedName = false;
7802
+ // The name came from whatever record the visual was drawn from, so it is worked out again too.
7803
+ if (this.rego) {
7804
+ this.rego.name = null;
7805
+ }
7806
+ this.createLabel();
7807
+ }
7323
7808
  /*
7324
7809
  * Rebuilds only when configuration changes; state refreshes must not recreate DOM every frame.
7325
7810
  */
@@ -7550,7 +8035,7 @@ var EntityLabel;
7550
8035
  }
7551
8036
  clearTimeout(this.loadTimeout);
7552
8037
  this.loadTimeout = setTimeout(async () => {
7553
- var _a, _b, _c, _d, _e;
8038
+ var _a, _b, _c, _d, _e, _f;
7554
8039
  if (this.disposed || this.counter != counter) {
7555
8040
  return;
7556
8041
  }
@@ -7577,16 +8062,19 @@ var EntityLabel;
7577
8062
  const attributes = (_a = this.options.attributes) !== null && _a !== void 0 ? _a : (_c = (_b = entityType === null || entityType === void 0 ? void 0 : entityType.VisualSettings) === null || _b === void 0 ? void 0 : _b.label) === null || _c === void 0 ? void 0 : _c.attributes;
7578
8063
  // Read for its attributes, and for a photo when the label has no icon of its own.
7579
8064
  if (attributes || !HasIcon(this.options)) {
7580
- let entity = null;
8065
+ // A simulated stand-in is labelled as is, and a patch is laid over the API's.
8066
+ const simulation = EntitySimulation.Get(this.viewer);
8067
+ let entity = (_d = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(this.rego.entityId)) !== null && _d !== void 0 ? _d : null;
7581
8068
  try {
7582
- entity = (await Entity$1.Get({
8069
+ entity = entity !== null && entity !== void 0 ? entity : (await Entity$1.Get({
7583
8070
  api: this.api,
7584
8071
  entityId: this.rego.entityId,
7585
- scenario: ((_d = this.rego.outline) === null || _d === void 0 ? void 0 : _d.length) ? (_e = this.rego.outline.find(x => !!x.Scenario)) === null || _e === void 0 ? void 0 : _e.Scenario : null,
8072
+ scenario: ((_e = this.rego.outline) === null || _e === void 0 ? void 0 : _e.length) ? (_f = this.rego.outline.find(x => !!x.Scenario)) === null || _f === void 0 ? void 0 : _f.Scenario : null,
7586
8073
  schemaId: this.rego.schema,
7587
8074
  migrated: true,
7588
8075
  expandAttachments: !HasIcon(this.options)
7589
8076
  })).entity;
8077
+ entity = entity && simulation ? simulation.Resolve(this.rego.entityId, entity) : entity;
7590
8078
  }
7591
8079
  // Entity doesn't exist.
7592
8080
  // Likely a graphic with an ID that points to no record.
@@ -7970,11 +8458,17 @@ var EntityLabel;
7970
8458
  // Past the wait a slow record no longer holds the label back; a box that comes later eases in.
7971
8459
  setTimeout(settle, MODEL_BOX_WAIT);
7972
8460
  this.modelBox.load.then(corners => {
8461
+ var _a, _b;
7973
8462
  if (!corners || this.disposed || this.counter !== counter) {
7974
8463
  return;
7975
8464
  }
7976
8465
  // A record that disagrees with where the model was found describes another placement, such as a moved tileset.
8466
+ // A part known to have moved from its record, eg. a joint turning, is judged where it has moved to.
8467
+ const motion = (_b = (_a = this.rego) === null || _a === void 0 ? void 0 : _a.motion) === null || _b === void 0 ? void 0 : _b.call(_a, this.viewer.clock.currentTime);
7977
8468
  const centre = Cartesian3.midpoint(corners[0], corners[7], new Cartesian3());
8469
+ if (motion) {
8470
+ Matrix4.multiplyByPoint(motion, centre, centre);
8471
+ }
7978
8472
  if (Cartesian3.distance(centre, found) > Cartesian3.distance(corners[0], corners[7]) * 0.75 + 2) {
7979
8473
  return;
7980
8474
  }
@@ -7982,6 +8476,7 @@ var EntityLabel;
7982
8476
  // Let go a little further out than it is taken, so a camera resting at the limit does not flip it.
7983
8477
  let near = false;
7984
8478
  layer.modelBoxes.Set(this.id, corners, rego.entityId, {
8479
+ motion: rego.motion ? () => rego.motion(this.viewer.clock.currentTime) : undefined,
7985
8480
  selected: () => { var _a, _b; return Boolean((_b = (_a = this.visualRegister) === null || _a === void 0 ? void 0 : _a.GetState({ entityId: rego.entityId, menuItemId: rego.menuItemId })) === null || _b === void 0 ? void 0 : _b.selected); },
7986
8481
  hovered: () => { var _a, _b; return Boolean((_b = (_a = this.visualRegister) === null || _a === void 0 ? void 0 : _a.IsLabelHovered) === null || _b === void 0 ? void 0 : _b.call(_a, rego.entityId, rego.menuItemId)); },
7987
8482
  near: () => {
@@ -8963,9 +9458,9 @@ var EntityLabelGroup;
8963
9458
  * Reads the entities behind items that name one without carrying it, attachments included,
8964
9459
  * so a label's default image needs no second request.
8965
9460
  */
8966
- async function Hydrate(items, api, signal) {
9461
+ async function Hydrate(items, api, signal, simulation) {
8967
9462
  var _a, _b;
8968
- if (!api) {
9463
+ if (!api && !simulation) {
8969
9464
  return items;
8970
9465
  }
8971
9466
  const hydrated = items.slice();
@@ -8976,12 +9471,15 @@ var EntityLabelGroup;
8976
9471
  }
8977
9472
  const chunk = missing.slice(start, start + HYDRATE_AT_ONCE);
8978
9473
  try {
8979
- const { entities } = await Entity$1.GetListByIds({
8980
- api, entityIds: chunk.map(at => hydrated[at].entityId), expandAttachments: true, migrated: true
8981
- });
9474
+ // A simulated stand-in is labelled as is, never from the API, and a patch is laid over the API's.
9475
+ const asked = chunk.filter(at => !(simulation === null || simulation === void 0 ? void 0 : simulation.HasEntity(hydrated[at].entityId)));
9476
+ const { entities } = api && asked.length ? await Entity$1.GetListByIds({
9477
+ api, entityIds: asked.map(at => hydrated[at].entityId), expandAttachments: true, migrated: true
9478
+ }) : { entities: [] };
8982
9479
  const found = new Map((entities !== null && entities !== void 0 ? entities : []).map(entity => { var _a; return [(_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID, entity]; }));
8983
9480
  for (const at of chunk) {
8984
- const entity = found.get(hydrated[at].entityId);
9481
+ const id = hydrated[at].entityId;
9482
+ const entity = simulation ? simulation.Resolve(id, found.get(id)) : found.get(id);
8985
9483
  if (entity) {
8986
9484
  // The API leaves Attachment out when there are none; an empty list records that they were read.
8987
9485
  hydrated[at] = { ...hydrated[at], entity: { ...entity, Bruce: { ...entity.Bruce, Attachment: (_b = (_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.Attachment) !== null && _b !== void 0 ? _b : [] } } };
@@ -8998,7 +9496,7 @@ var EntityLabelGroup;
8998
9496
  /*
8999
9497
  * Uses the existing DataLab grammar, discarding actions and projections that cannot return Entities.
9000
9498
  */
9001
- async function Loader(source, api, signal, hydrate) {
9499
+ async function Loader(source, api, signal, hydrate, simulation) {
9002
9500
  var _a;
9003
9501
  var _b;
9004
9502
  if (source.type === "provider") {
@@ -9013,7 +9511,7 @@ var EntityLabelGroup;
9013
9511
  return async (offset, limit, signal) => {
9014
9512
  const page = items.slice(offset, offset + limit);
9015
9513
  return {
9016
- items: hydrate ? await Hydrate(page, api, signal) : page,
9514
+ items: hydrate ? await Hydrate(page, api, signal, simulation) : page,
9017
9515
  hasMore: offset + limit < items.length
9018
9516
  };
9019
9517
  };
@@ -9155,7 +9653,7 @@ var EntityLabelGroup;
9155
9653
  const items = new Map();
9156
9654
  let truncated = false;
9157
9655
  try {
9158
- const load = await Loader(definition.source, (_g = this.register.ApiGetters) === null || _g === void 0 ? void 0 : _g.GetBruceApi(), controller.signal, NeedsEntityData(definition.options));
9656
+ const load = await Loader(definition.source, (_g = this.register.ApiGetters) === null || _g === void 0 ? void 0 : _g.GetBruceApi(), controller.signal, NeedsEntityData(definition.options), EntitySimulation.Get(this.register.Viewer));
9159
9657
  let offset = 0;
9160
9658
  for (let pageNumber = 0;; pageNumber++) {
9161
9659
  if (controller.signal.aborted || this.register.Disposed) {
@@ -9977,8 +10475,25 @@ var RelationRenderEngine;
9977
10475
  return `${relation["Related.Entity.ID"]}-${relation["Principal.Entity.ID"]}-${relation["Relation.Type.ID"]}`;
9978
10476
  }
9979
10477
  RelationRenderEngine.GetRenderGroupId = GetRenderGroupId;
10478
+ /**
10479
+ * Removes and returns a menu item's drawn relations that end at, or carry data from, any of these Entities.
10480
+ */
10481
+ function TakeRelationsTouching(params) {
10482
+ const { visualRegister, menuItemId } = params;
10483
+ const ids = new Set(params.entityIds);
10484
+ const touched = visualRegister.GetRegos({ menuItemId }).filter((rego) => {
10485
+ const relation = rego.relation;
10486
+ return relation && [relation["Principal.Entity.ID"], relation["Related.Entity.ID"], relation["Data.Entity.ID"]]
10487
+ .some(id => id && ids.has(id));
10488
+ });
10489
+ for (const rego of touched) {
10490
+ visualRegister.RemoveRegos({ relation: rego.relation, entityId: rego.entityId, menuItemId, requestRender: false });
10491
+ }
10492
+ return touched.map(rego => rego.relation);
10493
+ }
10494
+ RelationRenderEngine.TakeRelationsTouching = TakeRelationsTouching;
9980
10495
  async function Render(params) {
9981
- const { apiGetter, viewer, visualRegister, menuItemId, relations } = params;
10496
+ const { apiGetter, viewer, visualRegister, menuItemId, relations, simulation } = params;
9982
10497
  const api = apiGetter.getApi(apiGetter.accountId);
9983
10498
  const rendered = {};
9984
10499
  let neededEntityIdsIds = [];
@@ -9996,13 +10511,15 @@ var RelationRenderEngine;
9996
10511
  }
9997
10512
  // Turn the set unique.
9998
10513
  neededEntityIdsIds = neededEntityIdsIds.filter((v, i, a) => a.indexOf(v) === i);
9999
- // Get all entities in one go.
10000
- let entities = (await Entity$1.GetListByIds({
10001
- entityIds: neededEntityIdsIds,
10514
+ // Get all entities in one go, other than the simulated ones.
10515
+ const simulated = neededEntityIdsIds.map(id => simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(id)).filter(x => !!x);
10516
+ const apiIds = neededEntityIdsIds.filter(id => !(simulation === null || simulation === void 0 ? void 0 : simulation.HasEntity(id)));
10517
+ let entities = (apiIds.length ? (await Entity$1.GetListByIds({
10518
+ entityIds: apiIds,
10002
10519
  api: apiGetter.getApi(),
10003
10520
  migrated: true,
10004
10521
  maxSearchTimeSec: 60 * 2
10005
- })).entities;
10522
+ })).entities.map(entity => simulation ? simulation.Overlay(entity) : entity) : []).concat(simulated);
10006
10523
  // Create a map for quick reference.
10007
10524
  const entitiesMap = {};
10008
10525
  for (let i = 0; i < entities.length; i++) {
@@ -10496,6 +11013,8 @@ function isCullingIgnored(viewer, rego) {
10496
11013
  rego.overrideShow != null ||
10497
11014
  // Being dragged around right now, so its extent is changing faster than we could measure it.
10498
11015
  rego.editing == true ||
11016
+ // Placed by the clock every frame, so no measurement of where it is stays true.
11017
+ rego.moving == true ||
10499
11018
  // Part of a collection we did not create, eg. a geojson data source, which may have its own rules for
10500
11019
  // what is in the scene. Our own per-menu-item data sources are fair game.
10501
11020
  VisualsRegister.IsForeignCollection(rego) ||
@@ -11335,6 +11854,7 @@ var VisualsRegister;
11335
11854
  }
11336
11855
  get OnLabelGroupsChanged() { return this.labelGroupsChanged; }
11337
11856
  constructor(params) {
11857
+ var _a, _b;
11338
11858
  this.id = ObjectUtils.UId();
11339
11859
  this.disposed = false;
11340
11860
  this.rego = {};
@@ -11374,6 +11894,8 @@ var VisualsRegister;
11374
11894
  this.menuItemSources = new Map();
11375
11895
  // Set when the data source order no longer matches the zIndexes seen, so it is re-applied once.
11376
11896
  this.sourceOrderDirty = false;
11897
+ // Stops following what is simulated, so labels can read it again when it changes.
11898
+ this.simulationRemoval = null;
11377
11899
  this.labelGroupsChanged = new BruceEvent();
11378
11900
  this.hoverRingState = {};
11379
11901
  this.retiringHoverLabels = new Map();
@@ -11408,6 +11930,33 @@ var VisualsRegister;
11408
11930
  this.cameraCullerDispose = VisualRegisterCuller.Monitor({
11409
11931
  register: this
11410
11932
  });
11933
+ this.simulationRemoval = (_b = (_a = EntitySimulation.Get(this.viewer)) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => this.reloadLabels(change.entityIds))) !== null && _b !== void 0 ? _b : null;
11934
+ }
11935
+ /*
11936
+ * Tells labels to read their Entity again, since they keep what they read when drawn.
11937
+ */
11938
+ reloadLabels(entityIds) {
11939
+ var _a, _b, _c, _d;
11940
+ if (this.disposed) {
11941
+ return;
11942
+ }
11943
+ const groupIds = new Set();
11944
+ for (const entityId of entityIds) {
11945
+ for (const rego of this.GetRegos({ entityId })) {
11946
+ (_a = EntityLabel.GetLabel({ rego })) === null || _a === void 0 ? void 0 : _a.Reload();
11947
+ }
11948
+ if (((_b = this.labelHover) === null || _b === void 0 ? void 0 : _b.entityId) === entityId) {
11949
+ (_c = this.hoverLabel) === null || _c === void 0 ? void 0 : _c.Reload();
11950
+ }
11951
+ const groupId = (_d = this.GetLabelGroupFor(entityId)) === null || _d === void 0 ? void 0 : _d.groupId;
11952
+ if (groupId) {
11953
+ groupIds.add(groupId);
11954
+ }
11955
+ }
11956
+ // A group may have read the Entity in with its items, so the group is read again as a whole.
11957
+ for (const groupId of groupIds) {
11958
+ this.RefreshLabelGroup(groupId).catch((e) => console.error(e));
11959
+ }
11411
11960
  }
11412
11961
  /**
11413
11962
  * Returns the collection a menu item's visuals belong in, creating its data source on first use.
@@ -12310,7 +12859,9 @@ var VisualsRegister;
12310
12859
  (_b = this.viewer.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
12311
12860
  }
12312
12861
  Dispose() {
12313
- var _a, _b, _c, _d, _e;
12862
+ var _a, _b, _c, _d, _e, _f;
12863
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
12864
+ this.simulationRemoval = null;
12314
12865
  CesiumEntityStyler.SetDimming(this.viewer, this.id, false);
12315
12866
  this.ClearLabelHover();
12316
12867
  clearTimeout(this.hoverRingReset);
@@ -12320,14 +12871,14 @@ var VisualsRegister;
12320
12871
  EntityLabel.Detatch({ label });
12321
12872
  }
12322
12873
  this.retiringHoverLabels.clear();
12323
- (_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.Dispose();
12874
+ (_b = this.labelGroups) === null || _b === void 0 ? void 0 : _b.Dispose();
12324
12875
  this.labelGroupsChanged.Clear();
12325
12876
  for (const regos of Object.values(this.rego)) {
12326
12877
  for (const rego of regos)
12327
12878
  EntityLabel.Detatch({ rego, force: true });
12328
12879
  }
12329
12880
  EntityLabel.SetContainer(this.viewer);
12330
- (_b = this.cameraCullerDispose) === null || _b === void 0 ? void 0 : _b.call(this, {
12881
+ (_c = this.cameraCullerDispose) === null || _c === void 0 ? void 0 : _c.call(this, {
12331
12882
  uncull: false
12332
12883
  });
12333
12884
  this.cameraCullerDispose = null;
@@ -12343,7 +12894,7 @@ var VisualsRegister;
12343
12894
  this.disposed = true;
12344
12895
  for (const record of Array.from(this.menuItemSources.values())) {
12345
12896
  try {
12346
- (_d = (_c = this.viewer) === null || _c === void 0 ? void 0 : _c.dataSources) === null || _d === void 0 ? void 0 : _d.remove(record.source, true);
12897
+ (_e = (_d = this.viewer) === null || _d === void 0 ? void 0 : _d.dataSources) === null || _e === void 0 ? void 0 : _e.remove(record.source, true);
12347
12898
  }
12348
12899
  catch (e) {
12349
12900
  console.error(e);
@@ -12352,7 +12903,7 @@ var VisualsRegister;
12352
12903
  this.menuItemSources.clear();
12353
12904
  // Hand every tileset's show flag back to its owner, holding whatever the owner last asked for.
12354
12905
  for (const rego of Array.from(this.tilesets.values())) {
12355
- (_e = rego.gate) === null || _e === void 0 ? void 0 : _e.Release();
12906
+ (_f = rego.gate) === null || _f === void 0 ? void 0 : _f.Release();
12356
12907
  }
12357
12908
  this.tilesets.clear();
12358
12909
  }
@@ -12526,21 +13077,7 @@ var VisualsRegister;
12526
13077
  changed = true;
12527
13078
  }
12528
13079
  if (editing) {
12529
- const parts = EntityUtils.GatherEntity({
12530
- entity: rego.visual
12531
- });
12532
- for (let k = 0; k < parts.length; k++) {
12533
- const part = parts[k];
12534
- if (!(part instanceof Entity)) {
12535
- continue;
12536
- }
12537
- const removalPending = VisualRegisterCuller.CancelQueuedSceneChange(part);
12538
- const wasCulled = part[VisualRegisterCuller.VISUAL_CULL_KEY] == true || removalPending;
12539
- delete part[VisualRegisterCuller.VISUAL_CULL_KEY];
12540
- if (wasCulled && !IsCEntityInScene(this.viewer, part)) {
12541
- AddCEntityToScene(this.viewer, part, rego.collection);
12542
- }
12543
- }
13080
+ this.restoreCulled(rego);
12544
13081
  }
12545
13082
  else {
12546
13083
  VisualRegisterCuller.InvalidateBounds(rego.visual);
@@ -12552,6 +13089,56 @@ var VisualsRegister;
12552
13089
  (_b = (_a = this.viewer) === null || _a === void 0 ? void 0 : _a.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
12553
13090
  }
12554
13091
  }
13092
+ /**
13093
+ * Marks visuals as placed by the clock, exempting them from culling until it is turned back off.
13094
+ */
13095
+ SetMoving(params) {
13096
+ const { entityIds, moving, menuItemId } = params;
13097
+ if (!(entityIds === null || entityIds === void 0 ? void 0 : entityIds.length)) {
13098
+ return;
13099
+ }
13100
+ for (let i = 0; i < entityIds.length; i++) {
13101
+ const regos = this.rego[entityIds[i]];
13102
+ if (!regos) {
13103
+ continue;
13104
+ }
13105
+ for (let j = 0; j < regos.length; j++) {
13106
+ const rego = regos[j];
13107
+ if ((menuItemId && rego.menuItemId !== menuItemId) || Boolean(rego.moving) == Boolean(moving)) {
13108
+ continue;
13109
+ }
13110
+ // Null rather than false, so the rego does not carry a key for every visual that ever moved.
13111
+ rego.moving = moving ? true : null;
13112
+ if (moving) {
13113
+ this.restoreCulled(rego);
13114
+ }
13115
+ else {
13116
+ VisualRegisterCuller.InvalidateBounds(rego.visual);
13117
+ VisualRegisterCuller.MarkShouldRecheck(this.viewer);
13118
+ }
13119
+ }
13120
+ }
13121
+ }
13122
+ /*
13123
+ * Puts back whatever the culler took out of a visual that is now exempt from it.
13124
+ */
13125
+ restoreCulled(rego) {
13126
+ const parts = EntityUtils.GatherEntity({
13127
+ entity: rego.visual
13128
+ });
13129
+ for (let k = 0; k < parts.length; k++) {
13130
+ const part = parts[k];
13131
+ if (!(part instanceof Entity)) {
13132
+ continue;
13133
+ }
13134
+ const removalPending = VisualRegisterCuller.CancelQueuedSceneChange(part);
13135
+ const wasCulled = part[VisualRegisterCuller.VISUAL_CULL_KEY] == true || removalPending;
13136
+ delete part[VisualRegisterCuller.VISUAL_CULL_KEY];
13137
+ if (wasCulled && !IsCEntityInScene(this.viewer, part)) {
13138
+ AddCEntityToScene(this.viewer, part, rego.collection);
13139
+ }
13140
+ }
13141
+ }
12555
13142
  SetSelected(params) {
12556
13143
  var _a;
12557
13144
  let { entityIds, selected, refreshIfSelected, requestRender, menuItemId, source } = params;
@@ -14044,12 +14631,41 @@ var VisualsRegister;
14044
14631
  * Tracks per-entity rerender state for unsaved blob maintenance and stale API response suppression.
14045
14632
  */
14046
14633
  class EntityReRenderMaintainState {
14047
- constructor() {
14634
+ constructor(simulation, pointInTime) {
14635
+ this.simulation = simulation;
14636
+ this.pointInTime = pointInTime;
14048
14637
  this.maintainedEntitiesById = {};
14049
14638
  this.apiRevisionByEntityId = {};
14050
14639
  this.skipApiRunByEntityId = {};
14051
14640
  this.checkRunId = 0;
14052
14641
  }
14642
+ /*
14643
+ * Lays each Entity's simulated record for the current point over it, when the manager reads at one.
14644
+ */
14645
+ atPoint(entities) {
14646
+ var _a;
14647
+ const point = (_a = this.pointInTime) === null || _a === void 0 ? void 0 : _a.call(this);
14648
+ if (!point || !this.simulation || this.simulation.IsEmpty || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
14649
+ return entities;
14650
+ }
14651
+ return entities.map((entity) => this.simulation.ApplyAt(entity, point.timeMs, point.attrKey));
14652
+ }
14653
+ /**
14654
+ * Whether a simulated stand-in replaces this Entity, so nothing from the API may replace what is drawn.
14655
+ */
14656
+ isSimulated(entityId) {
14657
+ var _a;
14658
+ return Boolean(entityId && ((_a = this.simulation) === null || _a === void 0 ? void 0 : _a.HasEntity(entityId)));
14659
+ }
14660
+ /**
14661
+ * Swaps any simulated Entity in a set the API returned for its stand-in, or lays its patch over it.
14662
+ */
14663
+ withSimulated(entities) {
14664
+ if (!this.simulation || this.simulation.IsEmpty || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
14665
+ return entities;
14666
+ }
14667
+ return this.atPoint(entities.map((entity) => { var _a, _b; return (_b = this.simulation.Resolve((_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID, entity)) !== null && _b !== void 0 ? _b : entity; }));
14668
+ }
14053
14669
  setMaintainedEntities(entities) {
14054
14670
  var _a;
14055
14671
  for (let i = 0; i < entities.length; i++) {
@@ -14071,15 +14687,21 @@ class EntityReRenderMaintainState {
14071
14687
  }
14072
14688
  }
14073
14689
  getMaintainedEntities(entityIds) {
14690
+ var _a;
14074
14691
  const entities = [];
14075
14692
  for (let i = 0; i < entityIds.length; i++) {
14076
14693
  const id = entityIds[i];
14077
14694
  if (!id) {
14078
14695
  continue;
14079
14696
  }
14080
- const entity = this.maintainedEntitiesById[id];
14081
- if (entity) {
14082
- entities.push(entity);
14697
+ // A maintained blob is this manager's own unsaved edit, newer than a simulation, so it is drawn as is.
14698
+ const maintained = this.maintainedEntitiesById[id];
14699
+ const simulated = maintained ? null : (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.GetEntity(id);
14700
+ if (maintained) {
14701
+ entities.push(maintained);
14702
+ }
14703
+ else if (simulated) {
14704
+ entities.push(this.atPoint([simulated])[0]);
14083
14705
  }
14084
14706
  }
14085
14707
  return entities;
@@ -14136,7 +14758,7 @@ class EntityReRenderMaintainState {
14136
14758
  }
14137
14759
  filterCurrentApiEntities(params) {
14138
14760
  const { entities, revisions, runId } = params;
14139
- return entities.filter((entity) => {
14761
+ const current = entities.filter((entity) => {
14140
14762
  var _a, _b, _c;
14141
14763
  const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
14142
14764
  if (!id) {
@@ -14145,8 +14767,12 @@ class EntityReRenderMaintainState {
14145
14767
  if (runId != null && this.shouldSkipInRun(id, runId)) {
14146
14768
  return false;
14147
14769
  }
14770
+ if (this.isSimulated(id)) {
14771
+ return false;
14772
+ }
14148
14773
  return ((_b = this.apiRevisionByEntityId[id]) !== null && _b !== void 0 ? _b : 0) === ((_c = revisions[id]) !== null && _c !== void 0 ? _c : 0);
14149
14774
  });
14775
+ return this.atPoint(this.simulation && !this.simulation.IsEmpty ? current.map((entity) => this.simulation.Overlay(entity)) : current);
14150
14776
  }
14151
14777
  }
14152
14778
 
@@ -15582,7 +16208,7 @@ class HistoricGatherer {
15582
16208
  this.cache = new HistoricRangeCache();
15583
16209
  }
15584
16210
  get Records() {
15585
- return this.cache.GetRecords();
16211
+ return this.withSimulated(this.cache.GetRecords());
15586
16212
  }
15587
16213
  Clear() {
15588
16214
  this.cache.Clear();
@@ -15606,8 +16232,9 @@ class HistoricGatherer {
15606
16232
  */
15607
16233
  async Gather(entities) {
15608
16234
  var _a;
15609
- if (this.disposed || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
15610
- return { fetched: false, recordsByIds: this.cache.GetRecords() };
16235
+ const entityIds = entities === null || entities === void 0 ? void 0 : entities.map(x => { var _a; return (_a = x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }).filter(x => !!x);
16236
+ if (this.disposed || !(entities === null || entities === void 0 ? void 0 : entities.length) || this.params.simulatedOnly) {
16237
+ return { fetched: false, recordsByIds: this.withSimulated(this.cache.GetRecords(), entityIds) };
15611
16238
  }
15612
16239
  const { viewer } = this.params;
15613
16240
  const windowStart = new Date(viewer.clock.startTime.toString()).getTime();
@@ -15627,7 +16254,7 @@ class HistoricGatherer {
15627
16254
  if (plan.coarse && !this.cache.CoversCoarse(plan.coarse.start, plan.coarse.stop)) {
15628
16255
  const records = await this.request(plan.coarse.start, plan.coarse.stop, entities, plan.coarse.sample);
15629
16256
  if (this.disposed) {
15630
- return { fetched, recordsByIds: this.cache.GetRecords() };
16257
+ return { fetched, recordsByIds: this.withSimulated(this.cache.GetRecords(), entityIds) };
15631
16258
  }
15632
16259
  this.cache.Absorb(plan.coarse.start, this.holdTo(plan.coarse.stop, liveEdge, liveTail), records, true);
15633
16260
  fetched = true;
@@ -15662,7 +16289,17 @@ class HistoricGatherer {
15662
16289
  }
15663
16290
  }
15664
16291
  }
15665
- return { fetched, recordsByIds: this.cache.GetRecords() };
16292
+ return { fetched, recordsByIds: this.withSimulated(this.cache.GetRecords(), entityIds) };
16293
+ }
16294
+ /*
16295
+ * Joins the simulated records for these Entities, or every simulated Entity, into an answer.
16296
+ */
16297
+ withSimulated(recordsByIds, entityIds) {
16298
+ const simulation = this.params.simulation;
16299
+ if (!simulation || simulation.IsEmpty) {
16300
+ return recordsByIds;
16301
+ }
16302
+ return simulation.MergeRecords(recordsByIds, this.params.getAttrKey(), entityIds);
15666
16303
  }
15667
16304
  /*
15668
16305
  * Returns how much of a range that was just read may be marked as held.
@@ -15697,7 +16334,7 @@ class HistoricGatherer {
15697
16334
  }).then((res) => {
15698
16335
  const records = res.recordsByIds || {};
15699
16336
  if (!this.disposed && this.params.onRecords) {
15700
- this.params.onRecords(records);
16337
+ this.params.onRecords(this.withSimulated(records, entityIds));
15701
16338
  }
15702
16339
  return records;
15703
16340
  }).finally(() => {
@@ -15774,7 +16411,6 @@ var EntitiesRenderManager;
15774
16411
  this.renderQueue = [];
15775
16412
  this.renderQueueInterval = null;
15776
16413
  this.sources = [];
15777
- this.reRenderState = new EntityReRenderMaintainState();
15778
16414
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
15779
16415
  // Many things are not supported when this is enabled.
15780
16416
  this.useGeojson = false;
@@ -15782,6 +16418,10 @@ var EntitiesRenderManager;
15782
16418
  this.zoomControl = [];
15783
16419
  // Scenario for retrieving Entities.
15784
16420
  this.scenario = null;
16421
+ // Hand-off key of the data currently drawn, or null when it is unknown or nothing is drawn.
16422
+ this.drawnKey = null;
16423
+ // How the drawn entities were last told to look, see getDrawnSettingsSignature.
16424
+ this.drawnSettings = null;
15785
16425
  // Type IDs that we'll allow to be rendered when receiving Entities.
15786
16426
  // This might be exactly the Menu Item Type ID because of local data sources.
15787
16427
  this.allowedTypeIDs = [];
@@ -15794,6 +16434,14 @@ var EntitiesRenderManager;
15794
16434
  */
15795
16435
  this.renderAsIndividualsChain = Promise.resolve();
15796
16436
  const { viewer, apiGetter, monitor, item, register: visualsManager, sharedGetters, onSeriesDiscovered } = params;
16437
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation, () => {
16438
+ // Read at the scene's time only when the item follows history, the same choice its reads make.
16439
+ const bruceEntity = this.item.BruceEntity;
16440
+ if (!(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historic) && !(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historicAttrKey)) {
16441
+ return null;
16442
+ }
16443
+ return { timeMs: JulianDate.toDate(this.viewer.clock.currentTime).getTime(), attrKey: bruceEntity.historicAttrKey };
16444
+ });
15797
16445
  this.viewer = viewer;
15798
16446
  this.sharedGetters = sharedGetters;
15799
16447
  this.monitor = monitor;
@@ -15807,7 +16455,8 @@ var EntitiesRenderManager;
15807
16455
  getApi: () => this.apiGetter.getApi(),
15808
16456
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
15809
16457
  // A second either side, to take in records sitting exactly on the boundary.
15810
- paddingMs: 1000
16458
+ paddingMs: 1000,
16459
+ simulation: params.simulation
15811
16460
  });
15812
16461
  this.useGeojson = item.renderAsGeojson == true;
15813
16462
  if (item.enableClustering) {
@@ -15831,9 +16480,26 @@ var EntitiesRenderManager;
15831
16480
  if (this.disposed) {
15832
16481
  return;
15833
16482
  }
16483
+ // Landing on the data already drawn re-applies settings instead of removing and re-fetching it.
16484
+ const nextKey = shouldRender ? Manager.GetHandoffKey(this.item) : null;
16485
+ const keepDrawn = nextKey != null && nextKey === this.drawnKey;
16486
+ const restyle = keepDrawn && this.drawnSettings !== this.getDrawnSettingsSignature();
16487
+ this.drawnKey = nextKey;
16488
+ this.drawnSettings = this.getDrawnSettingsSignature();
15834
16489
  this.unsetGetter();
15835
16490
  (_a = this.viewMonitorRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
15836
16491
  this.viewMonitorRemoval = null;
16492
+ if (keepDrawn) {
16493
+ this.setGetter();
16494
+ this.viewMonitorRemoval = this.monitor.Updated().Subscribe(() => {
16495
+ var _a;
16496
+ (_a = this.entityCheckQueue) === null || _a === void 0 ? void 0 : _a.Call();
16497
+ });
16498
+ if (restyle) {
16499
+ this.ReRender({ entityIds: Object.keys(this.renderedEntities), force: true });
16500
+ }
16501
+ return;
16502
+ }
15837
16503
  clearInterval(this.renderQueueInterval);
15838
16504
  this.renderQueueInterval = null;
15839
16505
  this.renderQueue = [];
@@ -15867,6 +16533,18 @@ var EntitiesRenderManager;
15867
16533
  }
15868
16534
  })();
15869
16535
  }
16536
+ /*
16537
+ * Everything about how drawn entities look that applying the item's settings can change.
16538
+ */
16539
+ getDrawnSettingsSignature() {
16540
+ var _a;
16541
+ return JSON.stringify({
16542
+ zoomControl: RenderManager.GetZoomControlSignature(this.zoomControl),
16543
+ renderPriority: (_a = this.renderPriority) !== null && _a !== void 0 ? _a : null,
16544
+ historicDrawTrack: Boolean(this.item.historicDrawTrack),
16545
+ historicInterpolation: Boolean(this.item.historicInterpolation)
16546
+ });
16547
+ }
15870
16548
  applyItemSettings() {
15871
16549
  var _a, _b, _c, _d, _e, _f;
15872
16550
  // Reset allowed list if there's change.
@@ -15988,8 +16666,9 @@ var EntitiesRenderManager;
15988
16666
  return;
15989
16667
  }
15990
16668
  this.updateAllowedTypeIDs(data.entityTypeSources, data.entities);
16669
+ // The getter answers from the API, so a simulated Entity in its answer is swapped for its blob.
15991
16670
  if (isTagItem) {
15992
- this.distributeForRender(data.entities.filter((entity) => {
16671
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities.filter((entity) => {
15993
16672
  let entityTags = entity.Bruce["Layer.ID"];
15994
16673
  if (!entityTags) {
15995
16674
  entityTags = [];
@@ -15997,10 +16676,10 @@ var EntitiesRenderManager;
15997
16676
  return entityTags.some((tag) => {
15998
16677
  return tagsToRender.indexOf(tag) !== -1;
15999
16678
  });
16000
- }));
16679
+ })));
16001
16680
  }
16002
16681
  else {
16003
- this.distributeForRender(data.entities);
16682
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities));
16004
16683
  }
16005
16684
  });
16006
16685
  // Cesium itself handles single-visibility ranges very well.
@@ -16040,6 +16719,10 @@ var EntitiesRenderManager;
16040
16719
  // Remove regos directly.
16041
16720
  if (event.action === "D") {
16042
16721
  for (const entityId of affectedIds) {
16722
+ // Deleted in the API, but drawn from its simulation until that is cleared.
16723
+ if (this.reRenderState.isSimulated(entityId)) {
16724
+ continue;
16725
+ }
16043
16726
  this.visualsManager.RemoveRegos({
16044
16727
  entityId,
16045
16728
  menuItemId: this.item.id,
@@ -16237,10 +16920,13 @@ var EntitiesRenderManager;
16237
16920
  console.warn("Geojson rendering does not support multiple zoom controls. Only the first one will be used.");
16238
16921
  this.zoomControl = [this.zoomControl[0]];
16239
16922
  }
16923
+ this.drawnSettings = this.getDrawnSettingsSignature();
16240
16924
  changed = true;
16241
16925
  }
16242
16926
  if (scenario != null) {
16243
16927
  this.scenario = scenario;
16928
+ // The drawn records now belong to a scenario the item does not name.
16929
+ this.drawnKey = null;
16244
16930
  changed = true;
16245
16931
  }
16246
16932
  if (changed) {
@@ -16255,6 +16941,16 @@ var EntitiesRenderManager;
16255
16941
  this.doEntityCheck(entityIds);
16256
16942
  }
16257
16943
  }
16944
+ /**
16945
+ * Switches to another of the item's own styles, restyling what is drawn without re-creating it.
16946
+ */
16947
+ SwapStyle(item) {
16948
+ if (this.disposed) {
16949
+ return;
16950
+ }
16951
+ this.item = item;
16952
+ this.UpdateSettings({ zoomControl: item.CameraZoomSettings });
16953
+ }
16258
16954
  /**
16259
16955
  * Narrows a camera-driven check to the entities whose zoom band could actually have changed.
16260
16956
  * @param ids
@@ -17008,6 +17704,8 @@ var EntitiesRenderManager;
17008
17704
  AdoptHandoff(payload) {
17009
17705
  var _a;
17010
17706
  const { shouldRender } = this.applyItemSettings();
17707
+ this.drawnKey = shouldRender ? Manager.GetHandoffKey(this.item) : null;
17708
+ this.drawnSettings = this.getDrawnSettingsSignature();
17011
17709
  this.visualsManager.ReassignMenuItem({
17012
17710
  fromMenuItemId: payload.sourceMenuItemId,
17013
17711
  toMenuItemId: this.item.id,
@@ -17169,7 +17867,6 @@ var EntitiesLoadedRenderManager;
17169
17867
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
17170
17868
  // Many things are not supported when this is enabled.
17171
17869
  this.useGeojson = false;
17172
- this.reRenderState = new EntityReRenderMaintainState();
17173
17870
  /**
17174
17871
  * Our default path.
17175
17872
  * We render each entity individually.
@@ -17179,6 +17876,7 @@ var EntitiesLoadedRenderManager;
17179
17876
  */
17180
17877
  this.renderAsIndividualsChain = Promise.resolve();
17181
17878
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
17879
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
17182
17880
  this.viewer = viewer;
17183
17881
  this.apiGetter = apiGetter;
17184
17882
  this.monitor = monitor;
@@ -17301,7 +17999,8 @@ var EntitiesLoadedRenderManager;
17301
17999
  continue;
17302
18000
  }
17303
18001
  }
17304
- this.onGetterUpdate(entities);
18002
+ // The item carries its own Entities, so a simulated one among them is swapped for its blob.
18003
+ this.onGetterUpdate(this.reRenderState.withSimulated(entities));
17305
18004
  });
17306
18005
  this.getter.Start();
17307
18006
  }
@@ -17388,6 +18087,16 @@ var EntitiesLoadedRenderManager;
17388
18087
  this.doEntityCheck(entityIds);
17389
18088
  }
17390
18089
  }
18090
+ /**
18091
+ * Switches to another of the item's own styles, restyling what is drawn without re-creating it.
18092
+ */
18093
+ SwapStyle(item) {
18094
+ if (this.disposed) {
18095
+ return;
18096
+ }
18097
+ this.item = item;
18098
+ this.UpdateSettings({ zoomControl: item.CameraZoomSettings });
18099
+ }
17391
18100
  async doEntityCheck(ids, force = false) {
17392
18101
  var _a, _b;
17393
18102
  const api = this.apiGetter.getApi();
@@ -17948,8 +18657,15 @@ var EntitiesIdsRenderManager;
17948
18657
  this.renderQueueActive = false;
17949
18658
  this.renderQueue = [];
17950
18659
  this.renderQueueForceFlags = [];
17951
- this.reRenderState = new EntityReRenderMaintainState();
17952
18660
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
18661
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation, () => {
18662
+ // Read at the scene's time only when the item follows history, the same choice its reads make.
18663
+ const bruceEntity = this.item.BruceEntity;
18664
+ if (!(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historic) && !(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historicAttrKey)) {
18665
+ return null;
18666
+ }
18667
+ return { timeMs: JulianDate.toDate(this.viewer.clock.currentTime).getTime(), attrKey: bruceEntity.historicAttrKey };
18668
+ });
17953
18669
  this.sharedHistoric = params.sharedHistoric;
17954
18670
  this.viewer = viewer;
17955
18671
  this.apiGetter = apiGetter;
@@ -17962,7 +18678,8 @@ var EntitiesIdsRenderManager;
17962
18678
  getApi: () => this.apiGetter.getApi(),
17963
18679
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
17964
18680
  // Padding either side of a requested range, for clock desync between us and the server.
17965
- paddingMs: 15000
18681
+ paddingMs: 15000,
18682
+ simulation: params.simulation
17966
18683
  });
17967
18684
  if (this.item.enableClustering) {
17968
18685
  this.clustering = new PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
@@ -18085,6 +18802,16 @@ var EntitiesIdsRenderManager;
18085
18802
  this.onGetterUpdate(entityIds);
18086
18803
  }
18087
18804
  }
18805
+ /**
18806
+ * Switches to another of the item's own styles, restyling what is drawn without re-creating it.
18807
+ */
18808
+ SwapStyle(item) {
18809
+ if (this.disposed) {
18810
+ return;
18811
+ }
18812
+ this.item = item;
18813
+ this.UpdateSettings({ zoomControl: item.CameraZoomSettings });
18814
+ }
18088
18815
  /*
18089
18816
  * The lane this item reads through, or null when it does not follow the clock.
18090
18817
  */
@@ -18424,9 +19151,9 @@ var EntityRenderManager;
18424
19151
  this.getter = null;
18425
19152
  this.getterSub = null;
18426
19153
  this.disposed = false;
18427
- this.reRenderState = new EntityReRenderMaintainState();
18428
19154
  this.renderEntityChain = Promise.resolve();
18429
19155
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
19156
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
18430
19157
  this.viewer = viewer;
18431
19158
  this.apiGetter = apiGetter;
18432
19159
  this.monitor = monitor;
@@ -18548,6 +19275,16 @@ var EntityRenderManager;
18548
19275
  this.onGetterUpdate(entityId, true);
18549
19276
  }
18550
19277
  }
19278
+ /**
19279
+ * Switches to another of the item's own styles, restyling what is drawn without re-creating it.
19280
+ */
19281
+ SwapStyle(item) {
19282
+ if (this.disposed) {
19283
+ return;
19284
+ }
19285
+ this.item = item;
19286
+ this.UpdateSettings({ zoomControl: item.CameraZoomSettings });
19287
+ }
18551
19288
  renderEntity(entity, force = false) {
18552
19289
  // Waiting for prior call to finish resolving.
18553
19290
  // This avoids race conditions against two sets of overlapping Entities.
@@ -18700,6 +19437,122 @@ var CodeLogicExceptions;
18700
19437
  ];
18701
19438
  })(CodeLogicExceptions || (CodeLogicExceptions = {}));
18702
19439
 
19440
+ /**
19441
+ * The line drawn through where a moving thing has been, from its history, as a whole path or a fading trail.
19442
+ */
19443
+ var HistoricTrail;
19444
+ (function (HistoricTrail) {
19445
+ const BANDS = 12;
19446
+ const SAMPLES = 96;
19447
+ const WIDTH_FAR = 1;
19448
+ const ALPHA_FAR = 0.05;
19449
+ // Above 1, so width and alpha hold near the head and give way further back.
19450
+ const WIDTH_CURVE = 1.4;
19451
+ const ALPHA_CURVE = 1.25;
19452
+ // How strongly a line is drawn where it passes behind the globe, against its own alpha.
19453
+ const BEHIND_ALPHA = 0.35;
19454
+ /**
19455
+ * The trail length a menu item asks for in its historicTrackSeconds, or null for the whole path.
19456
+ */
19457
+ function GetTrailSeconds(item) {
19458
+ const seconds = Number(item === null || item === void 0 ? void 0 : item.historicTrackSeconds);
19459
+ return seconds > 0 ? seconds : null;
19460
+ }
19461
+ HistoricTrail.GetTrailSeconds = GetTrailSeconds;
19462
+ class Trail {
19463
+ constructor(params) {
19464
+ this.entities = [];
19465
+ this.memoTime = null;
19466
+ this.memo = [];
19467
+ this.viewer = params.viewer;
19468
+ const trailing = params.trailSeconds > 0 && Boolean(params.positionAt);
19469
+ if (!trailing) {
19470
+ this.add(params, params.color, params.width, () => { var _a; return (_a = params.getSeries()) !== null && _a !== void 0 ? _a : []; });
19471
+ return;
19472
+ }
19473
+ for (let band = 0; band < BANDS; band++) {
19474
+ // 0 at the head, 1 at the far end.
19475
+ const age = band / (BANDS - 1);
19476
+ const width = params.width - (params.width - WIDTH_FAR) * Math.pow(age, WIDTH_CURVE);
19477
+ const alpha = params.color.alpha - (params.color.alpha - ALPHA_FAR) * Math.pow(age, ALPHA_CURVE);
19478
+ this.add(params, params.color.withAlpha(alpha), Math.max(WIDTH_FAR, width), (time) => {
19479
+ return this.slice(this.sample(params, time), band);
19480
+ });
19481
+ }
19482
+ }
19483
+ /*
19484
+ * One polyline of the trail, its positions read from the scene's time.
19485
+ */
19486
+ add(params, color, width, positions) {
19487
+ const entity = this.viewer.entities.add({
19488
+ id: ObjectUtils.UId(15),
19489
+ polyline: {
19490
+ positions: new CallbackProperty((time) => positions(time), false),
19491
+ width: width,
19492
+ material: color,
19493
+ clampToGround: params.clampToGround,
19494
+ // A path through the air follows no ellipsoid, so it is drawn as the straight runs it holds.
19495
+ arcType: params.clampToGround ? ArcType.GEODESIC : ArcType.NONE,
19496
+ // Drawn dimmer behind the globe rather than winking in and out against the terrain along the limb.
19497
+ depthFailMaterial: params.clampToGround ? undefined : color.withAlpha(color.alpha * BEHIND_ALPHA),
19498
+ zIndex: 0,
19499
+ classificationType: params.clampToGround ? ClassificationType.BOTH : undefined
19500
+ }
19501
+ });
19502
+ entity._noEdit = true;
19503
+ entity._noSelect = true;
19504
+ CesiumEntityStyler.SetDefaultColor({
19505
+ color: color,
19506
+ entity: entity,
19507
+ viewer: this.viewer,
19508
+ override: true,
19509
+ requestRender: false
19510
+ });
19511
+ this.entities.push(entity);
19512
+ }
19513
+ /*
19514
+ * The trail's positions at a time, newest last, kept for that time so every band reads the same set.
19515
+ */
19516
+ sample(params, time) {
19517
+ var _a;
19518
+ if (this.memoTime && JulianDate.equals(this.memoTime, time)) {
19519
+ return this.memo;
19520
+ }
19521
+ this.memoTime = JulianDate.clone(time, (_a = this.memoTime) !== null && _a !== void 0 ? _a : new JulianDate());
19522
+ const nowMs = JulianDate.toDate(time).getTime();
19523
+ const spanMs = params.trailSeconds * 1000;
19524
+ const points = [];
19525
+ for (let i = 0; i <= SAMPLES; i++) {
19526
+ const pos3d = params.positionAt(nowMs - spanMs + spanMs * i / SAMPLES);
19527
+ if (pos3d) {
19528
+ points.push(pos3d);
19529
+ }
19530
+ }
19531
+ this.memo = points;
19532
+ return points;
19533
+ }
19534
+ /*
19535
+ * A band's run of the samples, counted back from the head, sharing its end point with the next band.
19536
+ */
19537
+ slice(points, band) {
19538
+ if (points.length < 2) {
19539
+ return [];
19540
+ }
19541
+ const per = (points.length - 1) / BANDS;
19542
+ const end = points.length - 1 - Math.round(band * per);
19543
+ const start = points.length - 1 - Math.round((band + 1) * per);
19544
+ return start < end ? points.slice(Math.max(0, start), end + 1) : [];
19545
+ }
19546
+ Dispose() {
19547
+ for (const entity of this.entities) {
19548
+ RemoveCEntityFromScene(this.viewer, entity);
19549
+ }
19550
+ this.entities = [];
19551
+ }
19552
+ }
19553
+ HistoricTrail.Trail = Trail;
19554
+ })(HistoricTrail || (HistoricTrail = {}));
19555
+
18703
19556
  class EntityGatherer {
18704
19557
  get OnQueueProgress() {
18705
19558
  if (!this._onQueueProgress) {
@@ -18707,7 +19560,11 @@ class EntityGatherer {
18707
19560
  }
18708
19561
  return this._onQueueProgress;
18709
19562
  }
18710
- constructor(api, viewer, emitEntities) {
19563
+ /**
19564
+ * @param simulation what callers simulate in place of API data, used where this gatherer has no override.
19565
+ */
19566
+ constructor(api, viewer, emitEntities, simulation) {
19567
+ this.simulation = simulation;
18711
19568
  this.disposed = false;
18712
19569
  this.expandSources = false;
18713
19570
  this.historic = null;
@@ -19284,15 +20141,19 @@ class EntityGatherer {
19284
20141
  }
19285
20142
  }
19286
20143
  }
20144
+ // A historic read is as at the scene's time, and so is any simulated record laid over it.
20145
+ const rTimeMs = new Date(rTime).getTime();
20146
+ const atPoint = (entity) => this.historic && this.simulation ? this.simulation.ApplyAt(entity, rTimeMs) : entity;
19287
20147
  for (const entityId of requestedBatch || []) {
20148
+ // A caller's own override is an unsaved edit, newer than any simulation, so it is used as is.
19288
20149
  const override = this.entityDataOverrides.get(entityId);
19289
20150
  if (override) {
19290
20151
  resolvedEntities.push(override);
19291
20152
  continue;
19292
20153
  }
19293
- const found = foundById.get(entityId);
20154
+ const found = this.simulation ? this.simulation.Resolve(entityId, foundById.get(entityId)) : foundById.get(entityId);
19294
20155
  if (found) {
19295
- resolvedEntities.push(found);
20156
+ resolvedEntities.push(atPoint(found));
19296
20157
  continue;
19297
20158
  }
19298
20159
  resolvedEntities.push(this.createFallbackEntity(entityId));
@@ -19656,7 +20517,7 @@ class TilesetStyler {
19656
20517
  this.styleTilesetFeatureFullData(feature, entity, eTags);
19657
20518
  }
19658
20519
  }
19659
- });
20520
+ }, params.simulation);
19660
20521
  // Only Entities Sets bake coordinates into their features, so for any other source this
19661
20522
  // resolves nothing and the gather stays in insertion order.
19662
20523
  this.entityGatherer.SetViewMonitor(this.monitor);
@@ -21186,6 +22047,9 @@ var TilesetCadRenderManager;
21186
22047
  return this.modelTreeUpdate;
21187
22048
  }
21188
22049
  constructor(params) {
22050
+ this.simulationRemoval = null;
22051
+ // Where the tileset stands by its own record, so a cleared simulation puts it back without a refetch.
22052
+ this.restCoords = null;
21189
22053
  this.disposed = false;
21190
22054
  this.modelSpace = false;
21191
22055
  this.cTileset = null;
@@ -21223,7 +22087,7 @@ var TilesetCadRenderManager;
21223
22087
  this.historicAnimation = null;
21224
22088
  this.historicGatherer = null;
21225
22089
  // The trail through the root's own movement, which describes the whole assembly.
21226
- this.trackEntity = null;
22090
+ this.trail = null;
21227
22091
  this.historicPossesLoadingProm = null;
21228
22092
  // Queue of loaded in features that we haven't processed yet.
21229
22093
  this.featureQueue = [];
@@ -21237,6 +22101,7 @@ var TilesetCadRenderManager;
21237
22101
  this.featurePropCache = new Map();
21238
22102
  this.featurePropsChecked = 0;
21239
22103
  const { viewer, register: visualsManager, getters, item, modelSpace, initQueue } = params;
22104
+ this.simulation = params.simulation;
21240
22105
  this.viewer = viewer;
21241
22106
  this.getters = getters;
21242
22107
  this.item = item;
@@ -21401,7 +22266,7 @@ var TilesetCadRenderManager;
21401
22266
  historicPoint: this.viewer.clock.currentTime.toString(),
21402
22267
  api: api
21403
22268
  });
21404
- coords = coordsFromHistoricEntity(root);
22269
+ coords = coordsFromHistoricEntity(this.atClock(root));
21405
22270
  }
21406
22271
  catch (e) {
21407
22272
  console.error(e);
@@ -21442,6 +22307,10 @@ var TilesetCadRenderManager;
21442
22307
  this.doDispose();
21443
22308
  return;
21444
22309
  }
22310
+ this.restCoords = coords;
22311
+ if (this.simulatedRootCoords()) {
22312
+ this.applyCoords(tileset, this.simulatedRootCoords());
22313
+ }
21445
22314
  this.viewer.scene.requestRender();
21446
22315
  TilesetRenderEngine.OnTilesetReady(this.cTileset).then(() => {
21447
22316
  var _a, _b, _c;
@@ -21461,6 +22330,7 @@ var TilesetCadRenderManager;
21461
22330
  }
21462
22331
  this.onCTilesetLoad();
21463
22332
  this.styler.Init({
22333
+ simulation: this.simulation,
21464
22334
  viewer: this.viewer,
21465
22335
  // Default API instead of the account one.
21466
22336
  // Records are driven by the account loading it instead of the source account.
@@ -21476,6 +22346,7 @@ var TilesetCadRenderManager;
21476
22346
  });
21477
22347
  this.viewer.scene.requestRender();
21478
22348
  // Monitor scene time changes and update the root Entity.
22349
+ this.followSimulation(tileset);
21479
22350
  this.viewerDateTimeSub(tileset);
21480
22351
  }
21481
22352
  catch (e) {
@@ -21531,7 +22402,7 @@ var TilesetCadRenderManager;
21531
22402
  */
21532
22403
  applyRootTrack() {
21533
22404
  var _a;
21534
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
22405
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
21535
22406
  return;
21536
22407
  }
21537
22408
  const trackStyle = EntityRenderEngine.GetTrackStyle({
@@ -21541,26 +22412,14 @@ var TilesetCadRenderManager;
21541
22412
  if (!trackStyle) {
21542
22413
  return;
21543
22414
  }
21544
- this.trackEntity = this.viewer.entities.add({
21545
- id: ObjectUtils.UId(15),
21546
- polyline: {
21547
- positions: new CallbackProperty(() => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; }, false),
21548
- width: trackStyle.width,
21549
- material: trackStyle.color,
21550
- clampToGround: EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
21551
- == HeightReference.CLAMP_TO_GROUND,
21552
- zIndex: 0,
21553
- classificationType: ClassificationType.BOTH
21554
- }
21555
- });
21556
- this.trackEntity._noEdit = true;
21557
- this.trackEntity._noSelect = true;
21558
- CesiumEntityStyler.SetDefaultColor({
21559
- color: trackStyle.color,
21560
- entity: this.trackEntity,
22415
+ // The series has no position at an arbitrary time, so this draws the whole path rather than a trail.
22416
+ this.trail = new HistoricTrail.Trail({
21561
22417
  viewer: this.viewer,
21562
- override: true,
21563
- requestRender: false
22418
+ color: trackStyle.color,
22419
+ width: trackStyle.width,
22420
+ clampToGround: EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
22421
+ == HeightReference.CLAMP_TO_GROUND,
22422
+ getSeries: () => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; }
21564
22423
  });
21565
22424
  }
21566
22425
  /**
@@ -21613,7 +22472,7 @@ var TilesetCadRenderManager;
21613
22472
  }
21614
22473
  // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
21615
22474
  PrepareHandoff() {
21616
- var _a, _b, _c, _d;
22475
+ var _a, _b, _c, _d, _e;
21617
22476
  if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
21618
22477
  return null;
21619
22478
  }
@@ -21631,9 +22490,11 @@ var TilesetCadRenderManager;
21631
22490
  this.handoffSourceMenuItemId = null;
21632
22491
  // Not transferred, the receiving manager sets up its own fresh copy if needed.
21633
22492
  this.viewerDateTimeDispose();
21634
- (_b = this.styler) === null || _b === void 0 ? void 0 : _b.Dispose();
21635
- (_c = this.tileLoadRemoval) === null || _c === void 0 ? void 0 : _c.call(this);
21636
- (_d = this.tileUnloadRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
22493
+ (_b = this.simulationRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
22494
+ this.simulationRemoval = null;
22495
+ (_c = this.styler) === null || _c === void 0 ? void 0 : _c.Dispose();
22496
+ (_d = this.tileLoadRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
22497
+ (_e = this.tileUnloadRemoval) === null || _e === void 0 ? void 0 : _e.call(this);
21637
22498
  this.tileLoadRemoval = null;
21638
22499
  this.tileUnloadRemoval = null;
21639
22500
  const payload = {
@@ -21654,7 +22515,7 @@ var TilesetCadRenderManager;
21654
22515
  // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
21655
22516
  // Then re-parents every already-registered rego to this menu item and re-runs styling.
21656
22517
  AdoptHandoff(payload) {
21657
- var _a, _b, _c, _d;
22518
+ var _a, _b, _c, _d, _e;
21658
22519
  this.cTileset = payload.cTileset;
21659
22520
  this.tileset = payload.tileset;
21660
22521
  this.rootId = payload.rootId;
@@ -21685,6 +22546,7 @@ var TilesetCadRenderManager;
21685
22546
  // Same visuals, same registrations so just re-keyed onto this menu item.
21686
22547
  this.onCTilesetLoad();
21687
22548
  this.styler.Init({
22549
+ simulation: this.simulation,
21688
22550
  viewer: this.viewer,
21689
22551
  api: this.getters.GetBruceApi(),
21690
22552
  cTileset: this.cTileset,
@@ -21718,7 +22580,9 @@ var TilesetCadRenderManager;
21718
22580
  }
21719
22581
  });
21720
22582
  this.viewer.scene.requestRender();
21721
- if (((_d = this.item.BruceEntity) === null || _d === void 0 ? void 0 : _d.historic) && this.tileset) {
22583
+ if (this.tileset) {
22584
+ this.restCoords = (_d = this.restCoords) !== null && _d !== void 0 ? _d : (_e = this.cTileset) === null || _e === void 0 ? void 0 : _e._bruceCoords;
22585
+ this.followSimulation(this.tileset);
21722
22586
  this.viewerDateTimeSub(this.tileset);
21723
22587
  }
21724
22588
  }
@@ -22296,6 +23160,21 @@ var TilesetCadRenderManager;
22296
23160
  }
22297
23161
  return children;
22298
23162
  }
23163
+ /**
23164
+ * Switches to another of the item's own styles, restyling the loaded tileset without reloading it.
23165
+ */
23166
+ SwapStyle(item) {
23167
+ var _a;
23168
+ if (this.disposed) {
23169
+ return;
23170
+ }
23171
+ this.item = item;
23172
+ // Before the tileset loads this only stores the pick, and the styler's Init reads the new item.
23173
+ this.styler.UpdateStyleMapping({
23174
+ styleMapping: (_a = item.StyleMapping) !== null && _a !== void 0 ? _a : [],
23175
+ fallbackStyleId: item.styleId
23176
+ });
23177
+ }
22299
23178
  Dispose() {
22300
23179
  var _a;
22301
23180
  if (this.disposed) {
@@ -22306,7 +23185,7 @@ var TilesetCadRenderManager;
22306
23185
  this.doDispose();
22307
23186
  }
22308
23187
  doDispose() {
22309
- var _a, _b, _c;
23188
+ var _a, _b, _c, _d;
22310
23189
  if (this.featureQueueInterval) {
22311
23190
  clearInterval(this.featureQueueInterval);
22312
23191
  this.featureQueueInterval = null;
@@ -22341,6 +23220,8 @@ var TilesetCadRenderManager;
22341
23220
  this.handoffSourceMenuItemId = null;
22342
23221
  }
22343
23222
  this.viewerDateTimeDispose();
23223
+ (_d = this.simulationRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
23224
+ this.simulationRemoval = null;
22344
23225
  }
22345
23226
  async ReRender(params) {
22346
23227
  let { entityIds, force, entities, maintain } = params;
@@ -22366,8 +23247,11 @@ var TilesetCadRenderManager;
22366
23247
  * @param tileset
22367
23248
  */
22368
23249
  viewerDateTimeSub(tileset) {
22369
- var _a;
22370
- if (!((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic) || this.viewerDateTimeChangeRemoval) {
23250
+ var _a, _b;
23251
+ // An item that is not historic still follows the root's simulated records, once there are some.
23252
+ const historic = Boolean((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic);
23253
+ const simulated = Boolean(this.rootId && ((_b = this.simulation) === null || _b === void 0 ? void 0 : _b.GetRecords(this.rootId).length));
23254
+ if ((!historic && !simulated) || this.historicGatherer || this.viewerDateTimeChangeRemoval) {
22371
23255
  return;
22372
23256
  }
22373
23257
  const accountId = this.accountIdForRecords();
@@ -22396,6 +23280,8 @@ var TilesetCadRenderManager;
22396
23280
  // Time padding in milliseconds (15 seconds).
22397
23281
  // Helps account for desync between client and server.
22398
23282
  paddingMs: 15000,
23283
+ simulation: this.simulation,
23284
+ simulatedOnly: !historic,
22399
23285
  onRecords: (recordsByIds) => {
22400
23286
  var _a;
22401
23287
  const records = recordsByIds[this.rootId];
@@ -22423,6 +23309,7 @@ var TilesetCadRenderManager;
22423
23309
  */
22424
23310
  const getInitialPosses = async () => {
22425
23311
  this.historicPossesLoadingProm = new Promise(async (res) => {
23312
+ var _a;
22426
23313
  try {
22427
23314
  if (this.disposed) {
22428
23315
  res(false);
@@ -22435,11 +23322,16 @@ var TilesetCadRenderManager;
22435
23322
  return;
22436
23323
  }
22437
23324
  // Also request the current position in case its not in the range.
22438
- const { entity } = await Entity$1.Get({
23325
+ // A simulated root, or an item that is not historic, has no current record to ask the API for.
23326
+ if (!historic || ((_a = this.simulation) === null || _a === void 0 ? void 0 : _a.HasEntity(this.rootId))) {
23327
+ res(positions);
23328
+ return;
23329
+ }
23330
+ const entity = this.atClock((await Entity$1.Get({
22439
23331
  entityId: this.rootId,
22440
23332
  historicPoint: this.viewer.clock.currentTime.toString(),
22441
23333
  api: api
22442
- });
23334
+ })).entity);
22443
23335
  if (this.disposed || !entity) {
22444
23336
  res(false);
22445
23337
  return;
@@ -22519,14 +23411,15 @@ var TilesetCadRenderManager;
22519
23411
  }
22520
23412
  });
22521
23413
  this.viewerDateTimeChangeRemoval = () => {
23414
+ var _a, _b;
22522
23415
  if (this.historicAnimation) {
22523
23416
  this.historicAnimation.stop();
22524
23417
  this.historicAnimation = null;
22525
23418
  }
22526
- if (this.trackEntity) {
22527
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
22528
- this.trackEntity = null;
22529
- }
23419
+ (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
23420
+ this.historicGatherer = null;
23421
+ (_b = this.trail) === null || _b === void 0 ? void 0 : _b.Dispose();
23422
+ this.trail = null;
22530
23423
  if (clockTickRemoval) {
22531
23424
  clockTickRemoval();
22532
23425
  clockTickRemoval = null;
@@ -22534,10 +23427,84 @@ var TilesetCadRenderManager;
22534
23427
  };
22535
23428
  });
22536
23429
  }
22537
- viewerDateTimeDispose() {
23430
+ /*
23431
+ * Keeps the tileset in line with what is simulated for its root Entity.
23432
+ */
23433
+ followSimulation(tileset) {
23434
+ if (this.simulationRemoval || !this.simulation) {
23435
+ return;
23436
+ }
23437
+ this.simulationRemoval = this.simulation.OnChanged.Subscribe((change) => {
23438
+ var _a;
23439
+ if (this.disposed || !this.rootId || !change.entityIds.includes(this.rootId)) {
23440
+ return;
23441
+ }
23442
+ if (change.records) {
23443
+ this.regatherRoot(tileset);
23444
+ }
23445
+ // A simulated root is where the tileset stands outside its history; recorded positions still win.
23446
+ if (change.entities && !((_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.getDateRange().minDate)) {
23447
+ this.applyRootRest(tileset);
23448
+ }
23449
+ });
23450
+ }
23451
+ /*
23452
+ * Re-reads the root's history after its simulated records changed, following the clock if it was not.
23453
+ */
23454
+ async regatherRoot(tileset) {
22538
23455
  var _a;
23456
+ if (!this.historicGatherer) {
23457
+ this.viewerDateTimeSub(tileset);
23458
+ return;
23459
+ }
23460
+ await this.historicPossesLoadingProm;
23461
+ const { recordsByIds } = await this.historicGatherer.Gather([{ Bruce: { ID: this.rootId } }]);
23462
+ if (this.disposed) {
23463
+ return;
23464
+ }
23465
+ const posses = Manager.HistoricPossesFor({ viewer: this.viewer, item: this.item, records: recordsByIds[this.rootId] || [] });
23466
+ (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.replacePositions(posses);
23467
+ if (!posses.length) {
23468
+ this.applyRootRest(tileset);
23469
+ }
23470
+ }
23471
+ /*
23472
+ * The root as read at the scene's time, with any simulated record at that time laid over it.
23473
+ */
23474
+ atClock(root) {
23475
+ return this.simulation && root
23476
+ ? this.simulation.ApplyAt(root, JulianDate.toDate(this.viewer.clock.currentTime).getTime())
23477
+ : root;
23478
+ }
23479
+ /*
23480
+ * Stands the tileset where its root is outside history: the simulated root, else the root's own record.
23481
+ */
23482
+ applyRootRest(tileset) {
23483
+ var _a;
23484
+ this.applyCoords(tileset, (_a = this.simulatedRootCoords()) !== null && _a !== void 0 ? _a : this.restCoords);
23485
+ }
23486
+ /*
23487
+ * Where a simulated root puts the tileset, or null; a patch only places it when it names a whole location.
23488
+ */
23489
+ simulatedRootCoords() {
23490
+ var _a, _b;
23491
+ const simulation = this.rootId ? this.simulation : null;
23492
+ const root = (_a = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(this.rootId)) !== null && _a !== void 0 ? _a : ((simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob(this.rootId)) ? simulation.Overlay({ Bruce: { ID: this.rootId } }) : null);
23493
+ const location = (_b = root === null || root === void 0 ? void 0 : root.Bruce) === null || _b === void 0 ? void 0 : _b.Location;
23494
+ if ((location === null || location === void 0 ? void 0 : location.latitude) == null || (location === null || location === void 0 ? void 0 : location.longitude) == null) {
23495
+ return null;
23496
+ }
23497
+ const pos3d = Cartesian3.fromDegrees(+location.longitude, +location.latitude, EnsureNumber(location.altitude, 0));
23498
+ const transform = root.Bruce.Transform;
23499
+ return coordsFromHistoricPos3d(this.cTileset, this.rootId, pos3d, EnsureNumber(transform === null || transform === void 0 ? void 0 : transform.heading, 0), transform === null || transform === void 0 ? void 0 : transform.pitch, transform === null || transform === void 0 ? void 0 : transform.roll);
23500
+ }
23501
+ viewerDateTimeDispose() {
23502
+ var _a, _b;
22539
23503
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
22540
23504
  this.viewerDateTimeChangeRemoval = null;
23505
+ // Made before the first positions land, so it can outlive a removal that was never set.
23506
+ (_b = this.historicGatherer) === null || _b === void 0 ? void 0 : _b.Dispose();
23507
+ this.historicGatherer = null;
22541
23508
  }
22542
23509
  /**
22543
23510
  * Updates the Tileset with latest coordinates.
@@ -23680,11 +24647,11 @@ var DataLabRenderManager;
23680
24647
  this.viewMonitorRemoval = null;
23681
24648
  this.renderQueue = [];
23682
24649
  this.renderQueueInterval = null;
23683
- this.reRenderState = new EntityReRenderMaintainState();
23684
24650
  this.zoomControl = [];
23685
24651
  this.queryLoadId = 0;
23686
24652
  this.renderDataLabEntitiesChain = Promise.resolve();
23687
24653
  const { viewer, item, apiGetter, monitor, visualsRegister } = params;
24654
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
23688
24655
  this.viewer = viewer;
23689
24656
  this.item = item;
23690
24657
  this.apiGetter = apiGetter;
@@ -23781,7 +24748,8 @@ var DataLabRenderManager;
23781
24748
  if (!((_a = data === null || data === void 0 ? void 0 : data.entities) === null || _a === void 0 ? void 0 : _a.length)) {
23782
24749
  return;
23783
24750
  }
23784
- this.distributeForRender(data.entities);
24751
+ // The getter answers from the API, so a simulated Entity in its answer is swapped for its blob.
24752
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities));
23785
24753
  });
23786
24754
  }
23787
24755
  unsetGetter() {
@@ -23936,6 +24904,16 @@ var DataLabRenderManager;
23936
24904
  this.doEntityCheck(entityIds);
23937
24905
  }
23938
24906
  }
24907
+ /**
24908
+ * Switches to another of the item's own styles, restyling what is drawn without re-creating it.
24909
+ */
24910
+ SwapStyle(item) {
24911
+ if (this.disposed) {
24912
+ return;
24913
+ }
24914
+ this.item = item;
24915
+ this.UpdateSettings({ zoomControl: item.CameraZoomSettings });
24916
+ }
23939
24917
  async doEntityCheck(ids, force = false) {
23940
24918
  var _a, _b, _c, _d;
23941
24919
  if (this.isRunningCheck) {
@@ -25532,6 +26510,7 @@ var TilesetEntitiesRenderManager;
25532
26510
  this.featurePropCache = new Map();
25533
26511
  this.featurePropsChecked = 0;
25534
26512
  const { viewer, register: visualsManager, getters: apiGetter, item, initQueue, monitor } = params;
26513
+ this.simulation = params.simulation;
25535
26514
  this.monitor = monitor;
25536
26515
  this.viewer = viewer;
25537
26516
  this.getters = apiGetter;
@@ -25632,6 +26611,7 @@ var TilesetEntitiesRenderManager;
25632
26611
  if (Manager.NeedsStyler(this.item)) {
25633
26612
  const api = this.getters.GetBruceApi();
25634
26613
  this.styler.Init({
26614
+ simulation: this.simulation,
25635
26615
  viewer: this.viewer,
25636
26616
  api: api,
25637
26617
  cTileset: this.cTileset,
@@ -25847,6 +26827,21 @@ var TilesetEntitiesRenderManager;
25847
26827
  }
25848
26828
  }
25849
26829
  }
26830
+ /**
26831
+ * Switches to another of the item's own styles, restyling the loaded tileset without reloading it.
26832
+ */
26833
+ SwapStyle(item) {
26834
+ if (this.disposed) {
26835
+ return;
26836
+ }
26837
+ this.item = item;
26838
+ if (Manager.NeedsStyler(item)) {
26839
+ this.styler.UpdateStyleMapping({
26840
+ styleMapping: [],
26841
+ fallbackStyleId: item.styleId
26842
+ });
26843
+ }
26844
+ }
25850
26845
  Dispose() {
25851
26846
  var _a;
25852
26847
  if (this.disposed) {
@@ -25989,6 +26984,7 @@ var TilesetEntitiesRenderManager;
25989
26984
  const needsStyler = Manager.NeedsStyler(this.item);
25990
26985
  if (needsStyler) {
25991
26986
  this.styler.Init({
26987
+ simulation: this.simulation,
25992
26988
  viewer: this.viewer,
25993
26989
  api: this.getters.GetBruceApi(),
25994
26990
  cTileset: this.cTileset,
@@ -26680,6 +27676,7 @@ var TilesetArbRenderManager;
26680
27676
  // LCC object if we're using XGrids to render LCC data.
26681
27677
  this.lccObject = null;
26682
27678
  this.viewer = params.viewer;
27679
+ this.simulation = params.simulation;
26683
27680
  this.getters = params.getters;
26684
27681
  this.visualsManager = params.register;
26685
27682
  // We modify the object due to a lot of backwards compatibility.
@@ -26914,6 +27911,7 @@ var TilesetArbRenderManager;
26914
27911
  this.item.Type == MenuItem.EType.IonTileset) {
26915
27912
  const api = this.getters.GetBruceApi();
26916
27913
  this.styler.Init({
27914
+ simulation: this.simulation,
26917
27915
  viewer: this.viewer,
26918
27916
  api: api,
26919
27917
  cTileset: this.cTileset,
@@ -27100,6 +28098,23 @@ var TilesetArbRenderManager;
27100
28098
  this.viewer.zoomTo(this.cTileset, new HeadingPitchRange(0.0, -0.5, this.cTileset.boundingSphere.radius / 4.0));
27101
28099
  }
27102
28100
  }
28101
+ /**
28102
+ * Switches to another of the item's own styles, restyling the loaded tileset without reloading it.
28103
+ */
28104
+ SwapStyle(item) {
28105
+ var _a;
28106
+ if (this.disposed) {
28107
+ return;
28108
+ }
28109
+ // Copied for the same reason the constructor copies it.
28110
+ this.item = JSON.parse(JSON.stringify(item));
28111
+ if (this.item.ApplyStyles || this.item.Type == MenuItem.EType.IonTileset) {
28112
+ this.styler.UpdateStyleMapping({
28113
+ styleMapping: (_a = this.item.StyleMapping) !== null && _a !== void 0 ? _a : [],
28114
+ fallbackStyleId: this.item.styleId
28115
+ });
28116
+ }
28117
+ }
27103
28118
  Dispose() {
27104
28119
  if (this.disposed) {
27105
28120
  return;
@@ -27255,14 +28270,32 @@ var RelationsRenderManager;
27255
28270
  }
27256
28271
  ;
27257
28272
  constructor(params) {
28273
+ var _a, _b;
27258
28274
  // If true, this menu item is disposed and should not be used.
27259
28275
  // Any rendering will be ignored.
27260
28276
  this.disposed = false;
28277
+ this.simulationRemoval = null;
28278
+ this.FollowsSimulation = true;
27261
28279
  const { apiGetter, item, register, viewer } = params;
27262
28280
  this.apiGetter = apiGetter;
27263
28281
  this.item = item;
27264
28282
  this.register = register;
27265
28283
  this.viewer = viewer;
28284
+ this.simulation = params.simulation;
28285
+ // Ends are not this item's own Entities, so it watches the store itself rather than wait to be asked.
28286
+ this.simulationRemoval = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
28287
+ if (this.disposed) {
28288
+ return;
28289
+ }
28290
+ const relations = RelationRenderEngine.TakeRelationsTouching({
28291
+ visualRegister: this.register,
28292
+ menuItemId: this.item.id,
28293
+ entityIds: change.entityIds
28294
+ });
28295
+ if (relations.length) {
28296
+ this.onGetterUpdate(relations);
28297
+ }
28298
+ })) !== null && _b !== void 0 ? _b : null;
27266
28299
  }
27267
28300
  /**
27268
28301
  * Starts rendering the menu item.
@@ -27331,10 +28364,13 @@ var RelationsRenderManager;
27331
28364
  return items;
27332
28365
  }
27333
28366
  Dispose() {
28367
+ var _a;
27334
28368
  if (this.disposed) {
27335
28369
  return;
27336
28370
  }
27337
28371
  this.disposed = true;
28372
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28373
+ this.simulationRemoval = null;
27338
28374
  this.register.RemoveRegos({
27339
28375
  menuItemId: this.item.id
27340
28376
  });
@@ -27573,7 +28609,8 @@ var RelationsRenderManager;
27573
28609
  menuItemId: this.item.id,
27574
28610
  relations: relations,
27575
28611
  viewer: this.viewer,
27576
- visualRegister: this.register
28612
+ visualRegister: this.register,
28613
+ simulation: this.simulation
27577
28614
  });
27578
28615
  if (this.disposed) {
27579
28616
  this.register.RemoveRegos({
@@ -27856,9 +28893,12 @@ var LegacyRelationsRenderManager;
27856
28893
  }
27857
28894
  ;
27858
28895
  constructor(params) {
28896
+ var _a, _b;
27859
28897
  // If true, this menu item is disposed and should not be used.
27860
28898
  // Any rendering will be ignored.
27861
28899
  this.disposed = false;
28900
+ this.simulationRemoval = null;
28901
+ this.FollowsSimulation = true;
27862
28902
  // Record of the currently drawn entity IDs.
27863
28903
  // This is to avoid re-queueing already drawn entities when Init is called multiple times.
27864
28904
  this.drawnIds = [];
@@ -27867,6 +28907,21 @@ var LegacyRelationsRenderManager;
27867
28907
  this.item = item;
27868
28908
  this.register = register;
27869
28909
  this.viewer = viewer;
28910
+ this.simulation = params.simulation;
28911
+ // Ends are not this item's own Entities, so it watches the store itself rather than wait to be asked.
28912
+ this.simulationRemoval = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
28913
+ if (this.disposed) {
28914
+ return;
28915
+ }
28916
+ const relations = RelationRenderEngine.TakeRelationsTouching({
28917
+ visualRegister: this.register,
28918
+ menuItemId: this.item.id,
28919
+ entityIds: change.entityIds
28920
+ });
28921
+ if (relations.length) {
28922
+ this.onGetterUpdate(relations);
28923
+ }
28924
+ })) !== null && _b !== void 0 ? _b : null;
27870
28925
  }
27871
28926
  /**
27872
28927
  * Starts rendering the menu item.
@@ -27909,10 +28964,13 @@ var LegacyRelationsRenderManager;
27909
28964
  this.drawnIds = [].concat(entityIds);
27910
28965
  }
27911
28966
  Dispose() {
28967
+ var _a;
27912
28968
  if (this.disposed) {
27913
28969
  return;
27914
28970
  }
27915
28971
  this.disposed = true;
28972
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28973
+ this.simulationRemoval = null;
27916
28974
  this.register.RemoveRegos({
27917
28975
  menuItemId: this.item.id
27918
28976
  });
@@ -28073,7 +29131,8 @@ var LegacyRelationsRenderManager;
28073
29131
  menuItemId: this.item.id,
28074
29132
  relations: relations,
28075
29133
  viewer: this.viewer,
28076
- visualRegister: this.register
29134
+ visualRegister: this.register,
29135
+ simulation: this.simulation
28077
29136
  });
28078
29137
  if (this.disposed) {
28079
29138
  this.register.RemoveRegos({
@@ -29306,6 +30365,55 @@ var AssemblyRenderManager;
29306
30365
  get ModelSpace() {
29307
30366
  return this.modelSpace;
29308
30367
  }
30368
+ /**
30369
+ * Every node of the Assembly read so far, so a caller can work out motion in the Assembly's own terms.
30370
+ * @param timeMs the scene time for each node's current pose. Default: the clock's.
30371
+ * @returns per node its pose within its parent from the API (local) and as drawn at the time (current).
30372
+ */
30373
+ GetNodes(timeMs) {
30374
+ const at = timeMs !== null && timeMs !== void 0 ? timeMs : JulianDate.toDate(this.viewer.clock.currentTime).getTime();
30375
+ const rowsOf = (m) => m ? [0, 1, 2, 3].map(row => [0, 1, 2, 3].map(column => m[(column * 4) + row])) : null;
30376
+ return Object.values(this.hierarchyNodeByEntityId || {}).map(node => {
30377
+ var _a, _b, _c;
30378
+ const entity = (_a = this.apiEntities.get(node.entityId)) !== null && _a !== void 0 ? _a : null;
30379
+ const rows = (_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition;
30380
+ return {
30381
+ entityId: node.entityId,
30382
+ name: node.name,
30383
+ parentId: (_c = node.parentId) !== null && _c !== void 0 ? _c : null,
30384
+ local: Manager.MatrixFromRows(rows) ? rows : null,
30385
+ current: rowsOf(this.localAt(node.entityId, at)),
30386
+ entity
30387
+ };
30388
+ });
30389
+ }
30390
+ /**
30391
+ * The root's world frame at a time, history and simulation included, eg. to follow or frame the Assembly.
30392
+ * @param timeMs the scene time. Default: the clock's.
30393
+ * @returns null until the root has been read.
30394
+ */
30395
+ GetRootFrame(timeMs) {
30396
+ const root = this.rootAt(timeMs !== null && timeMs !== void 0 ? timeMs : JulianDate.toDate(this.viewer.clock.currentTime).getTime());
30397
+ return root ? Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll) : null;
30398
+ }
30399
+ /**
30400
+ * A part's world frame at a time as its records and its parents place it, size included.
30401
+ * @param entityId the part.
30402
+ * @param timeMs the scene time. Default: the clock's.
30403
+ * @returns null until the root and the part have been read.
30404
+ */
30405
+ GetPartFrame(entityId, timeMs) {
30406
+ const at = timeMs !== null && timeMs !== void 0 ? timeMs : JulianDate.toDate(this.viewer.clock.currentTime).getTime();
30407
+ const rootFrame = this.GetRootFrame(at);
30408
+ const world = entityId === this.rootId ? Matrix4.IDENTITY : this.composeWorld(entityId, at);
30409
+ if (!rootFrame || !world) {
30410
+ return null;
30411
+ }
30412
+ // Where the part is drawn, so its own ground offset is part of its frame.
30413
+ const ground = this.groundOffsets.get(entityId);
30414
+ const placed = ground ? Matrix4.multiply(Matrix4.fromTranslation(ground), world, new Matrix4()) : world;
30415
+ return Matrix4.multiply(rootFrame, placed, new Matrix4());
30416
+ }
29309
30417
  constructor(params) {
29310
30418
  this.disposed = false;
29311
30419
  // Cache of the hierarchy so that our scene-tree can reference it.
@@ -29315,19 +30423,41 @@ var AssemblyRenderManager;
29315
30423
  this.hierarchyNodeByEntityId = null;
29316
30424
  this.modelSpace = false;
29317
30425
  this.rootId = null;
29318
- // Where the root sits now, and which way it faces.
29319
- // Every child is placed through this, so a root move is one value changing rather than a refetch of the whole Assembly.
30426
+ // Where the root sits outside its history, and which way it faces in degrees. Children are placed through it.
29320
30427
  this.rootPos3d = null;
29321
30428
  this.rootHeading = 0;
29322
- // Entity ID -> what was drawn for it and where it sits relative to the root.
30429
+ this.rootPitch = 0;
30430
+ this.rootRoll = 0;
30431
+ // The root's recorded positions and attitudes, oldest first, kept apart since a record may name only one.
30432
+ this.rootSeries = [];
30433
+ this.rootAttitude = [];
30434
+ // Every placement worked out for the scene time last asked about, shared by all parts drawn at it.
30435
+ this.pose = null;
30436
+ // Entity ID -> what was drawn for it.
29323
30437
  this.children = new Map();
30438
+ // Entity ID -> its live position within its parent, for every node fetched including undrawn groups.
30439
+ this.localPositions = new Map();
30440
+ // Entity ID -> how much higher the ground under it is than under the root, along the root's up.
30441
+ this.groundOffsets = new Map();
30442
+ // Entity ID -> its recorded poses within its parent, oldest first.
30443
+ this.jointSeries = new Map();
30444
+ // Drawn children that can move with the clock, so a tick places only these and not the whole Assembly.
30445
+ this.animatedIds = new Set();
29324
30446
  // The trail through the root's own movement, which describes the whole Assembly.
29325
- this.trackEntity = null;
30447
+ this.trail = null;
30448
+ this.simulationRemoval = null;
30449
+ this.FollowsSimulation = true;
30450
+ // Entity ID -> the record the API gave, so a cleared simulation falls back to it without a refetch.
30451
+ this.apiEntities = new Map();
30452
+ // Re-reads history and re-applies it, eg. after simulated records change. Set while the clock is followed.
30453
+ this.regather = null;
30454
+ this.followingClock = false;
29326
30455
  // Callback to dispose the link between scene time and the root's historic position.
29327
30456
  this.viewerDateTimeChangeRemoval = null;
29328
30457
  this.historicGatherer = null;
29329
30458
  this.historicAnimation = null;
29330
30459
  const { viewer, register: visualsManager, getters, item } = params;
30460
+ this.simulation = params.simulation;
29331
30461
  this.viewer = viewer;
29332
30462
  this.getters = getters;
29333
30463
  this.item = item;
@@ -29351,10 +30481,10 @@ var AssemblyRenderManager;
29351
30481
  return EntityRenderEngine.GetHeightRef((_b = (_a = item === null || item === void 0 ? void 0 : item.Style) === null || _a === void 0 ? void 0 : _a.Settings) === null || _b === void 0 ? void 0 : _b["modelStyle"]);
29352
30482
  }
29353
30483
  /*
29354
- * The root's frame: east-north-up at its position, turned by its heading.
30484
+ * The root's frame: east-north-up at its position, turned by its heading, pitch and roll.
29355
30485
  */
29356
- static rootFrame(rootPos3d, headingDegrees) {
29357
- const hpr = new HeadingPitchRoll(Math$1.toRadians(headingDegrees || 0), 0, 0);
30486
+ static rootFrame(rootPos3d, headingDegrees, pitchDegrees = 0, rollDegrees = 0) {
30487
+ const hpr = new HeadingPitchRoll(Math$1.toRadians(headingDegrees || 0), Math$1.toRadians(pitchDegrees || 0), Math$1.toRadians(rollDegrees || 0));
29358
30488
  return Transforms.headingPitchRollToFixedFrame(rootPos3d, hpr);
29359
30489
  }
29360
30490
  /**
@@ -29377,28 +30507,553 @@ var AssemblyRenderManager;
29377
30507
  return Matrix4.multiplyByPoint(Manager.rootFrame(rootPos3d, headingDegrees), offset, new Cartesian3());
29378
30508
  }
29379
30509
  /**
29380
- * Moves the whole Assembly to a new root position, without refetching any of its children.
30510
+ * Moves where the root sits outside its history, without refetching children; recorded poses still win.
29381
30511
  * @param params
29382
30512
  */
29383
30513
  ApplyRootPosition(params) {
29384
- var _a, _b;
30514
+ var _a, _b, _c, _d, _e, _f;
29385
30515
  if (this.disposed || !(params === null || params === void 0 ? void 0 : params.pos3d)) {
29386
30516
  return;
29387
30517
  }
29388
30518
  this.rootPos3d = params.pos3d;
29389
30519
  this.rootHeading = (_b = (_a = params.heading) !== null && _a !== void 0 ? _a : this.rootHeading) !== null && _b !== void 0 ? _b : 0;
30520
+ this.rootPitch = (_d = (_c = params.pitch) !== null && _c !== void 0 ? _c : this.rootPitch) !== null && _d !== void 0 ? _d : 0;
30521
+ this.rootRoll = (_f = (_e = params.roll) !== null && _e !== void 0 ? _e : this.rootRoll) !== null && _f !== void 0 ? _f : 0;
30522
+ this.repose(true);
30523
+ }
30524
+ /*
30525
+ * Drops every placement worked out so far, after the root or a joint's records change.
30526
+ * @param everyChild re-place the parts that cannot move as well, because where they rest has changed.
30527
+ */
30528
+ repose(everyChild = false) {
30529
+ this.pose = null;
29390
30530
  for (const child of this.children.values()) {
29391
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, child.offset);
29392
- if (!pos3d) {
30531
+ if (everyChild || Boolean(child.moving) != this.canMove(child.entityId)) {
30532
+ this.bindPlacement(child);
30533
+ }
30534
+ }
30535
+ this.viewer.scene.requestRender();
30536
+ }
30537
+ /*
30538
+ * Takes a node's pose outside its history from its record, and for the root where it stands and faces.
30539
+ */
30540
+ absorbNode(entity) {
30541
+ var _a, _b, _c, _d, _e;
30542
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30543
+ const local = Manager.MatrixFromRows((_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition);
30544
+ if (local) {
30545
+ this.localPositions.set(id, local);
30546
+ }
30547
+ // Anchored on the root, which is usually never drawn; everything under it is placed from the hierarchy.
30548
+ if (id === this.rootId || (!this.rootId && !this.rootPos3d)) {
30549
+ const rootPos3d = EntityUtils.GetPos({
30550
+ viewer: this.viewer,
30551
+ entity,
30552
+ recordHeightRef: Manager.GetRecordHeightRef(this.item),
30553
+ returnHeightRef: HeightReference.NONE,
30554
+ allowRendered: false
30555
+ });
30556
+ if (rootPos3d) {
30557
+ this.rootPos3d = rootPos3d;
30558
+ this.rootHeading = EnsureNumber((_c = entity.Bruce.Transform) === null || _c === void 0 ? void 0 : _c.heading, 0);
30559
+ this.rootPitch = EnsureNumber((_d = entity.Bruce.Transform) === null || _d === void 0 ? void 0 : _d.pitch, 0);
30560
+ this.rootRoll = EnsureNumber((_e = entity.Bruce.Transform) === null || _e === void 0 ? void 0 : _e.roll, 0);
30561
+ }
30562
+ }
30563
+ }
30564
+ /*
30565
+ * A node's record with its simulation laid over it, keeping what a stand-in leaves out, eg. its model.
30566
+ */
30567
+ withSimulation(entity) {
30568
+ var _a;
30569
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30570
+ if (!this.simulation || !id) {
30571
+ return entity;
30572
+ }
30573
+ const standIn = this.simulation.GetEntity(id);
30574
+ if (!standIn) {
30575
+ return this.simulation.Overlay(entity);
30576
+ }
30577
+ return { ...entity, ...standIn, Bruce: { ...entity.Bruce, ...standIn.Bruce } };
30578
+ }
30579
+ /*
30580
+ * Brings the Assembly in line with a change to what is simulated.
30581
+ */
30582
+ onSimulationChanged(change) {
30583
+ var _a;
30584
+ if (this.disposed || !this.hierarchyNodeByEntityId) {
30585
+ return;
30586
+ }
30587
+ const ids = change.entityIds.filter(id => this.hierarchyNodeByEntityId[id]);
30588
+ if (!ids.length) {
30589
+ return;
30590
+ }
30591
+ if (change.entities) {
30592
+ for (const id of ids) {
30593
+ const api = this.apiEntities.get(id);
30594
+ if (!api) {
30595
+ // Not loaded yet, and it will be drawn from its simulation when it is.
30596
+ continue;
30597
+ }
30598
+ const entity = this.withSimulation(api);
30599
+ // A cleared blob leaves the node's own matrix where the API has none, so it is reset.
30600
+ if (!((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.AssemblyPosition)) {
30601
+ this.localPositions.delete(id);
30602
+ }
30603
+ this.absorbNode(entity);
30604
+ const child = this.children.get(id);
30605
+ if (child) {
30606
+ child.entity = entity;
30607
+ }
30608
+ }
30609
+ this.repose(true);
30610
+ }
30611
+ if (change.records) {
30612
+ if (this.regather) {
30613
+ this.regather(true);
30614
+ }
30615
+ else {
30616
+ this.viewerDateTimeSub();
30617
+ }
30618
+ }
30619
+ }
30620
+ /*
30621
+ * Nodes of this Assembly with simulated history.
30622
+ */
30623
+ simulatedNodeIds() {
30624
+ if (!this.simulation || !this.hierarchyNodeByEntityId) {
30625
+ return [];
30626
+ }
30627
+ return this.simulation.GetRecordEntityIds().filter(id => this.hierarchyNodeByEntityId[id]);
30628
+ }
30629
+ /*
30630
+ * Whether a node can move with time: the root has history, or it or an ancestor does.
30631
+ */
30632
+ canMove(entityId) {
30633
+ return this.rootSeries.length > 0 || this.rootAttitude.length > 0 || this.animatedIds.has(entityId);
30634
+ }
30635
+ /*
30636
+ * Gives a child its position and orientation, as functions of scene time only when it can move.
30637
+ */
30638
+ bindPlacement(child) {
30639
+ const id = child.entityId;
30640
+ const moving = this.canMove(id);
30641
+ if (moving) {
30642
+ if (!child.moving) {
30643
+ child.cEntity.position = new CallbackProperty((time, result) => { var _a; return Manager.cloneOrUndefined((_a = this.placementAt(id, time)) === null || _a === void 0 ? void 0 : _a.pos3d, result); }, false);
30644
+ child.cEntity.orientation = new CallbackProperty((time, result) => {
30645
+ var _a;
30646
+ const orient = (_a = this.placementAt(id, time)) === null || _a === void 0 ? void 0 : _a.orient;
30647
+ return orient ? Quaternion.clone(orient, result) : undefined;
30648
+ }, false);
30649
+ child.cEntity.model.scale = new CallbackProperty((time) => { var _a, _b; return (_b = (_a = this.placementAt(id, time)) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1; }, false);
30650
+ }
30651
+ }
30652
+ else {
30653
+ const placed = this.placementAt(id, this.viewer.clock.currentTime);
30654
+ if (placed) {
30655
+ child.cEntity.position = new ConstantPositionProperty(placed.pos3d);
30656
+ if (placed.orient) {
30657
+ child.cEntity.orientation = new ConstantProperty(placed.orient);
30658
+ }
30659
+ child.cEntity.model.scale = new ConstantProperty(placed.scale);
30660
+ }
30661
+ }
30662
+ if (Boolean(child.moving) != moving) {
30663
+ child.moving = moving;
30664
+ this.visualsManager.SetMoving({ moving, entityIds: [id], menuItemId: this.item.id });
30665
+ }
30666
+ }
30667
+ /*
30668
+ * The one scale a model takes from its part's composed matrix, the largest when they are uneven.
30669
+ */
30670
+ static uniformScale(world) {
30671
+ return Cartesian3.maximumComponent(Matrix4.getScale(world, new Cartesian3()));
30672
+ }
30673
+ static cloneOrUndefined(pos3d, result) {
30674
+ return pos3d ? Cartesian3.clone(pos3d, result) : undefined;
30675
+ }
30676
+ /*
30677
+ * Where a node draws at a scene time, from one composition shared by every part asked about at that time.
30678
+ */
30679
+ placementAt(entityId, time) {
30680
+ if (this.disposed || !time) {
30681
+ return null;
30682
+ }
30683
+ const timeMs = JulianDate.toDate(time).getTime();
30684
+ if (!this.pose || this.pose.timeMs !== timeMs) {
30685
+ const root = this.rootAt(timeMs);
30686
+ this.pose = {
30687
+ timeMs,
30688
+ rootFrame: root ? Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll) : null,
30689
+ worlds: new Map(),
30690
+ placements: new Map()
30691
+ };
30692
+ }
30693
+ const pose = this.pose;
30694
+ if (!pose.rootFrame) {
30695
+ return null;
30696
+ }
30697
+ let placed = pose.placements.get(entityId);
30698
+ if (!placed) {
30699
+ const world = this.composeWorld(entityId, timeMs, pose.worlds);
30700
+ const offset = world
30701
+ ? Matrix4.getTranslation(world, new Cartesian3())
30702
+ : new Cartesian3(0, 0, 0);
30703
+ const ground = this.groundOffsets.get(entityId);
30704
+ if (ground) {
30705
+ Cartesian3.add(offset, ground, offset);
30706
+ }
30707
+ placed = {
30708
+ pos3d: Matrix4.multiplyByPoint(pose.rootFrame, offset, new Cartesian3()),
30709
+ // Without a matrix, eg. the root itself, there is no orientation of its own to apply.
30710
+ orient: world ? EntityUtils.GetAssemblyOrientationInFrame(pose.rootFrame, world) : null,
30711
+ scale: world ? Manager.uniformScale(world) : 1
30712
+ };
30713
+ pose.placements.set(entityId, placed);
30714
+ }
30715
+ return placed;
30716
+ }
30717
+ /*
30718
+ * The root's pose at a time, position and attitude each following their own records as a joint's do.
30719
+ */
30720
+ rootAt(timeMs) {
30721
+ const pos3d = this.rootPositionAt(timeMs);
30722
+ if (!pos3d) {
30723
+ return null;
30724
+ }
30725
+ const live = { heading: this.rootHeading, pitch: this.rootPitch, roll: this.rootRoll };
30726
+ const series = this.rootAttitude;
30727
+ if (!series.length || timeMs < series[0].timeMs) {
30728
+ return { pos3d, ...live };
30729
+ }
30730
+ let i = 0;
30731
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30732
+ i++;
30733
+ }
30734
+ const a = series[i];
30735
+ const b = series[i + 1];
30736
+ const t = b ? (timeMs - a.timeMs) / (b.timeMs - a.timeMs) : 0;
30737
+ const turn = (from, to, fallback) => {
30738
+ var _a, _b, _c;
30739
+ const x = (_a = from !== null && from !== void 0 ? from : fallback) !== null && _a !== void 0 ? _a : 0;
30740
+ const y = (_c = (_b = (b ? to : from)) !== null && _b !== void 0 ? _b : fallback) !== null && _c !== void 0 ? _c : 0;
30741
+ // The short way round, so 350 to 10 degrees turns through north rather than all the way back.
30742
+ return x + (Math$1.negativePiToPi(Math$1.toRadians(y - x)) * 180 / Math.PI) * t;
30743
+ };
30744
+ return {
30745
+ pos3d,
30746
+ heading: turn(a.heading, b === null || b === void 0 ? void 0 : b.heading, live.heading),
30747
+ pitch: turn(a.pitch, b === null || b === void 0 ? void 0 : b.pitch, live.pitch),
30748
+ roll: turn(a.roll, b === null || b === void 0 ? void 0 : b.roll, live.roll)
30749
+ };
30750
+ }
30751
+ /*
30752
+ * Where the root is at a time, from its recorded positions.
30753
+ */
30754
+ rootPositionAt(timeMs) {
30755
+ const series = this.rootSeries;
30756
+ if (!series.length || timeMs < series[0].timeMs) {
30757
+ return this.rootPos3d;
30758
+ }
30759
+ let i = 0;
30760
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30761
+ i++;
30762
+ }
30763
+ const a = series[i];
30764
+ const b = series[i + 1];
30765
+ if (!b) {
30766
+ return a.pos3d;
30767
+ }
30768
+ const t = (timeMs - a.timeMs) / (b.timeMs - a.timeMs);
30769
+ // A straight line between orbit samples cuts up to a kilometre inside the path, so it is lifted back out.
30770
+ const chord = Cartesian3.lerp(a.pos3d, b.pos3d, t, new Cartesian3());
30771
+ const carto = Cartographic.fromCartesian(chord);
30772
+ return carto
30773
+ ? Cartesian3.fromRadians(carto.longitude, carto.latitude, Math$1.lerp(a.height, b.height, t))
30774
+ : chord;
30775
+ }
30776
+ /*
30777
+ * Takes the root's records into its series.
30778
+ * @param replace the records are everything held for the root, so the series becomes exactly them.
30779
+ * @returns whether the series changed.
30780
+ */
30781
+ absorbRootRecords(posses, replace) {
30782
+ if (!(posses === null || posses === void 0 ? void 0 : posses.length) && !(replace && this.rootSeries.length)) {
30783
+ return false;
30784
+ }
30785
+ const byTime = new Map(replace ? [] : this.rootSeries.map(k => [k.timeMs, k]));
30786
+ for (const pos of posses) {
30787
+ const carto = Cartographic.fromCartesian(pos.pos3d);
30788
+ if (!carto) {
29393
30789
  continue;
29394
30790
  }
29395
- child.cEntity.position = new ConstantPositionProperty(pos3d);
29396
- const orient = EntityUtils.GetAssemblyOrientation(child.entity, pos3d);
29397
- if (orient) {
29398
- child.cEntity.orientation = new ConstantProperty(orient);
30791
+ byTime.set(pos.dateTime.getTime(), {
30792
+ timeMs: pos.dateTime.getTime(),
30793
+ pos3d: pos.pos3d,
30794
+ height: carto.height
30795
+ });
30796
+ }
30797
+ const series = Array.from(byTime.values()).sort((x, y) => x.timeMs - y.timeMs);
30798
+ const changed = (series.length !== this.rootSeries.length ||
30799
+ series.some((k, i) => !Cartesian3.equals(k.pos3d, this.rootSeries[i].pos3d)));
30800
+ this.rootSeries = series;
30801
+ return changed;
30802
+ }
30803
+ /*
30804
+ * Takes the angles the root's records name into its attitude series, apart from where it is.
30805
+ * @param replace the records are everything held for the root, as for absorbRootRecords.
30806
+ * @returns whether the series changed.
30807
+ */
30808
+ absorbRootAttitude(records, replace) {
30809
+ const angle = (transform, key) => {
30810
+ var _a;
30811
+ const value = (_a = transform === null || transform === void 0 ? void 0 : transform[key]) !== null && _a !== void 0 ? _a : transform === null || transform === void 0 ? void 0 : transform[key[0].toUpperCase() + key.slice(1)];
30812
+ return value == null || isNaN(+value) ? null : +value;
30813
+ };
30814
+ const byTime = new Map(replace ? [] : this.rootAttitude.map(k => [k.timeMs, k]));
30815
+ for (const record of records || []) {
30816
+ const timeMs = (record === null || record === void 0 ? void 0 : record.dateTime) ? new Date(record.dateTime).getTime() : NaN;
30817
+ const key = {
30818
+ timeMs,
30819
+ heading: angle(record === null || record === void 0 ? void 0 : record.transform, "heading"),
30820
+ pitch: angle(record === null || record === void 0 ? void 0 : record.transform, "pitch"),
30821
+ roll: angle(record === null || record === void 0 ? void 0 : record.transform, "roll")
30822
+ };
30823
+ if (isNaN(timeMs) || (key.heading == null && key.pitch == null && key.roll == null)) {
30824
+ continue;
29399
30825
  }
30826
+ byTime.set(timeMs, key);
29400
30827
  }
29401
- this.viewer.scene.requestRender();
30828
+ const series = Array.from(byTime.values()).sort((x, y) => x.timeMs - y.timeMs);
30829
+ const held = this.rootAttitude;
30830
+ const differs = (k, i) => (k.timeMs !== held[i].timeMs ||
30831
+ k.heading !== held[i].heading ||
30832
+ k.pitch !== held[i].pitch ||
30833
+ k.roll !== held[i].roll);
30834
+ const changed = series.length !== held.length || series.some(differs);
30835
+ this.rootAttitude = series;
30836
+ return changed;
30837
+ }
30838
+ /*
30839
+ * A node's matrix in the root's frame at a time, from each ancestor's pose within its parent.
30840
+ */
30841
+ composeWorld(entityId, timeMs, memo, localOf = (id) => this.localAt(id, timeMs)) {
30842
+ var _a, _b, _c, _d;
30843
+ if (memo === null || memo === void 0 ? void 0 : memo.has(entityId)) {
30844
+ return memo.get(entityId);
30845
+ }
30846
+ let world = null;
30847
+ const local = localOf(entityId);
30848
+ if (local) {
30849
+ const parentId = (_b = (_a = this.hierarchyNodeByEntityId) === null || _a === void 0 ? void 0 : _a[entityId]) === null || _b === void 0 ? void 0 : _b.parentId;
30850
+ const parent = parentId ? this.composeWorld(parentId, timeMs, memo, localOf) : null;
30851
+ world = parent ? Matrix4.multiply(parent, local, new Matrix4()) : Matrix4.clone(local);
30852
+ }
30853
+ else if (entityId !== this.rootId) {
30854
+ // A node whose own position was not fetched falls back to the API's composed matrix.
30855
+ const child = this.children.get(entityId);
30856
+ world = Manager.MatrixFromRows((_d = (_c = child === null || child === void 0 ? void 0 : child.entity) === null || _c === void 0 ? void 0 : _c.Bruce) === null || _d === void 0 ? void 0 : _d.AssemblyWorldPosition);
30857
+ }
30858
+ memo === null || memo === void 0 ? void 0 : memo.set(entityId, world);
30859
+ return world;
30860
+ }
30861
+ /*
30862
+ * Under a clamped or relative style, lifts a part by how much higher its ground is than the root's.
30863
+ */
30864
+ measureGroundOffset(entity) {
30865
+ var _a;
30866
+ const entityId = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30867
+ this.groundOffsets.delete(entityId);
30868
+ const root = this.apiEntities.get(this.rootId);
30869
+ if (!entityId ||
30870
+ entityId === this.rootId ||
30871
+ !root ||
30872
+ !this.rootPos3d ||
30873
+ Manager.GetRecordHeightRef(this.item) === HeightReference.NONE) {
30874
+ return;
30875
+ }
30876
+ const rootFrame = Manager.rootFrame(this.rootPos3d, this.rootHeading, this.rootPitch, this.rootRoll);
30877
+ const toRoot = Matrix4.inverse(rootFrame, new Matrix4());
30878
+ const groundUp = (record) => {
30879
+ const pos3d = EntityUtils.GetPos({
30880
+ viewer: this.viewer,
30881
+ entity: record,
30882
+ recordHeightRef: HeightReference.CLAMP_TO_GROUND,
30883
+ returnHeightRef: HeightReference.NONE,
30884
+ allowRendered: false
30885
+ });
30886
+ return pos3d ? Matrix4.multiplyByPoint(toRoot, pos3d, new Cartesian3()).z : null;
30887
+ };
30888
+ const part = groundUp(entity);
30889
+ const under = groundUp(root);
30890
+ if (part == null || under == null || Math.abs(part - under) < 1e-6) {
30891
+ return;
30892
+ }
30893
+ this.groundOffsets.set(entityId, new Cartesian3(0, 0, part - under));
30894
+ }
30895
+ /*
30896
+ * The rigid Earth-fixed motion from where a part's record places it to where it is drawn at a time.
30897
+ */
30898
+ motionAt(entityId, time) {
30899
+ if (this.disposed || !time || !this.rootPos3d) {
30900
+ return null;
30901
+ }
30902
+ const timeMs = JulianDate.toDate(time).getTime();
30903
+ const root = this.rootAt(timeMs);
30904
+ const drawn = this.composeWorld(entityId, timeMs, new Map());
30905
+ const stored = this.composeWorld(entityId, timeMs, new Map(), (id) => {
30906
+ var _a, _b, _c, _d;
30907
+ return ((_d = (_c = Manager.MatrixFromRows((_b = (_a = this.apiEntities.get(id)) === null || _a === void 0 ? void 0 : _a.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition)) !== null && _c !== void 0 ? _c : this.localPositions.get(id)) !== null && _d !== void 0 ? _d : null);
30908
+ });
30909
+ if (!root || !drawn || !stored) {
30910
+ return null;
30911
+ }
30912
+ const drawnFixed = Matrix4.multiply(Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll), drawn, new Matrix4());
30913
+ const storedFixed = Matrix4.multiply(Manager.rootFrame(this.rootPos3d, this.rootHeading, this.rootPitch, this.rootRoll), stored, new Matrix4());
30914
+ return Matrix4.multiply(drawnFixed, Matrix4.inverse(storedFixed, new Matrix4()), new Matrix4());
30915
+ }
30916
+ /*
30917
+ * A node's pose within its parent at a time, interpolated between the records either side.
30918
+ */
30919
+ localAt(entityId, timeMs) {
30920
+ var _a;
30921
+ // The live pose stands before the first record and the last record holds after it, as in an API read.
30922
+ const live = (_a = this.localPositions.get(entityId)) !== null && _a !== void 0 ? _a : null;
30923
+ const series = this.jointSeries.get(entityId);
30924
+ if (!(series === null || series === void 0 ? void 0 : series.length) || timeMs == null || timeMs < series[0].timeMs) {
30925
+ return live;
30926
+ }
30927
+ let i = 0;
30928
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30929
+ i++;
30930
+ }
30931
+ const a = series[i];
30932
+ const b = series[i + 1];
30933
+ if (!b) {
30934
+ return Matrix4.fromTranslationQuaternionRotationScale(a.translation, a.rotation, a.scale);
30935
+ }
30936
+ const t = (timeMs - a.timeMs) / (b.timeMs - a.timeMs);
30937
+ return Matrix4.fromTranslationQuaternionRotationScale(Cartesian3.lerp(a.translation, b.translation, t, new Cartesian3()), Quaternion.slerp(a.rotation, b.rotation, t, new Quaternion()), Cartesian3.lerp(a.scale, b.scale, t, new Cartesian3()));
30938
+ }
30939
+ /*
30940
+ * Rebuilds each node's keyframes from the historic records held so far.
30941
+ * @returns whether any node has keyframes.
30942
+ */
30943
+ absorbJointRecords(recordsByIds) {
30944
+ var _a;
30945
+ // Everything held is given, so a node with nothing left, eg. its simulated records cleared, stops moving.
30946
+ for (const entityId of Array.from(this.jointSeries.keys())) {
30947
+ if (!((_a = recordsByIds === null || recordsByIds === void 0 ? void 0 : recordsByIds[entityId]) === null || _a === void 0 ? void 0 : _a.length)) {
30948
+ this.jointSeries.delete(entityId);
30949
+ }
30950
+ }
30951
+ for (const entityId of Object.keys(recordsByIds || {})) {
30952
+ const keyframes = [];
30953
+ for (const record of recordsByIds[entityId] || []) {
30954
+ const matrix = Manager.MatrixFromRows(record.assemblyPosition);
30955
+ const timeMs = new Date(record.dateTime).getTime();
30956
+ if (!matrix || isNaN(timeMs)) {
30957
+ continue;
30958
+ }
30959
+ const scale = Matrix4.getScale(matrix, new Cartesian3());
30960
+ const rotation = Matrix4.getRotation(matrix, new Matrix3());
30961
+ keyframes.push({
30962
+ timeMs,
30963
+ translation: Matrix4.getTranslation(matrix, new Cartesian3()),
30964
+ rotation: Quaternion.fromRotationMatrix(rotation),
30965
+ scale
30966
+ });
30967
+ }
30968
+ if (keyframes.length) {
30969
+ keyframes.sort((x, y) => x.timeMs - y.timeMs);
30970
+ this.jointSeries.set(entityId, keyframes);
30971
+ }
30972
+ }
30973
+ this.refreshAnimated();
30974
+ return this.jointSeries.size > 0;
30975
+ }
30976
+ /*
30977
+ * Rebuilds which drawn children can move with the clock, by walking each one up to the root.
30978
+ */
30979
+ refreshAnimated() {
30980
+ this.animatedIds.clear();
30981
+ if (!this.jointSeries.size) {
30982
+ return;
30983
+ }
30984
+ for (const entityId of this.children.keys()) {
30985
+ if (this.hasAnimatedAncestry(entityId)) {
30986
+ this.animatedIds.add(entityId);
30987
+ }
30988
+ }
30989
+ }
30990
+ /*
30991
+ * Whether a node or anything above it has keyframes, so the clock can move it.
30992
+ */
30993
+ hasAnimatedAncestry(entityId) {
30994
+ var _a, _b;
30995
+ let at = entityId;
30996
+ while (at && this.jointSeries.size) {
30997
+ if (this.jointSeries.has(at)) {
30998
+ return true;
30999
+ }
31000
+ at = (_b = (_a = this.hierarchyNodeByEntityId) === null || _a === void 0 ? void 0 : _a[at]) === null || _b === void 0 ? void 0 : _b.parentId;
31001
+ }
31002
+ return false;
31003
+ }
31004
+ /*
31005
+ * Which nodes to ask history for.
31006
+ */
31007
+ async historicEntityIds(api) {
31008
+ var _a, _b;
31009
+ // Through the Assembly when there is one, so the hierarchy follows the relation type on its record.
31010
+ const assemblyId = this.item["Assembly.ID"];
31011
+ const root = assemblyId ? { key: "root", AssemblyID: assemblyId } : { key: "root", RootID: this.rootId };
31012
+ const historic = {
31013
+ key: "historic",
31014
+ Historic: { HistoricKey: ((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey) || undefined }
31015
+ };
31016
+ const query = {
31017
+ [DataLab.EReqKey.Primary]: { Items: [root, historic], Select: { Type: DataLab.ESelectType.Id } }
31018
+ };
31019
+ const load = 1000;
31020
+ try {
31021
+ const ids = [];
31022
+ for (let skip = 0;; skip += load) {
31023
+ const { items } = await DataLab.Run({ api, query, skip, load });
31024
+ ids.push(...(items !== null && items !== void 0 ? items : []));
31025
+ if (!items || items.length < load) {
31026
+ return ids;
31027
+ }
31028
+ }
31029
+ }
31030
+ catch (e) {
31031
+ // An API without the historic criterion gets asked about every node, as before it existed.
31032
+ console.warn("AssemblyRenderManager.historicEntityIds()", e);
31033
+ }
31034
+ return Object.keys((_b = this.hierarchyNodeByEntityId) !== null && _b !== void 0 ? _b : { [this.rootId]: null });
31035
+ }
31036
+ /**
31037
+ * Builds a matrix from the row-major rows that Bruce/AssemblyPosition and AssemblyWorldPosition use.
31038
+ * @param rows
31039
+ */
31040
+ static MatrixFromRows(rows) {
31041
+ if (!Array.isArray(rows) || rows.length !== 4 || rows.some(r => !Array.isArray(r) || r.length !== 4)) {
31042
+ return null;
31043
+ }
31044
+ return new Matrix4(+rows[0][0], +rows[0][1], +rows[0][2], +rows[0][3], +rows[1][0], +rows[1][1], +rows[1][2], +rows[1][3], +rows[2][0], +rows[2][1], +rows[2][2], +rows[2][3], +rows[3][0], +rows[3][1], +rows[3][2], +rows[3][3]);
31045
+ }
31046
+ /**
31047
+ * The inverse of MatrixFromRows.
31048
+ * @param matrix
31049
+ */
31050
+ static RowsFromMatrix(matrix) {
31051
+ const rows = [];
31052
+ for (let r = 0; r < 4; r++) {
31053
+ const row = Matrix4.getRow(matrix, r, new Cartesian4());
31054
+ rows.push([row.x, row.y, row.z, row.w]);
31055
+ }
31056
+ return rows;
29402
31057
  }
29403
31058
  Init() {
29404
31059
  this.renderPriority = this.item.renderPriority;
@@ -29406,6 +31061,7 @@ var AssemblyRenderManager;
29406
31061
  this.renderPriority = 1;
29407
31062
  }
29408
31063
  (async () => {
31064
+ var _a, _b;
29409
31065
  if (this.disposed) {
29410
31066
  return;
29411
31067
  }
@@ -29421,6 +31077,8 @@ var AssemblyRenderManager;
29421
31077
  this.hierarchyUpdate.Trigger(hierarchy);
29422
31078
  // Build hierarchy node cache for quick lookups
29423
31079
  this.buildHierarchyNodes(hierarchy);
31080
+ // A set for membership, since searching the list took minutes at 200k nodes.
31081
+ const seenIds = new Set();
29424
31082
  const traverseHierarchy = (node, leavesOnly) => {
29425
31083
  var _a;
29426
31084
  if (node.Children) {
@@ -29429,7 +31087,8 @@ var AssemblyRenderManager;
29429
31087
  }
29430
31088
  }
29431
31089
  const addNode = !leavesOnly || !((_a = node.Children) === null || _a === void 0 ? void 0 : _a.length);
29432
- if (addNode && node.ID && !flatEntityIds.includes(node.ID)) {
31090
+ if (addNode && node.ID && !seenIds.has(node.ID)) {
31091
+ seenIds.add(node.ID);
29433
31092
  flatEntityIds.push(node.ID);
29434
31093
  }
29435
31094
  };
@@ -29448,17 +31107,17 @@ var AssemblyRenderManager;
29448
31107
  }
29449
31108
  }
29450
31109
  this.rootId = rootId;
31110
+ this.simulationRemoval = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => this.onSimulationChanged(change))) !== null && _b !== void 0 ? _b : null;
29451
31111
  if (this.disposed) {
29452
31112
  return;
29453
31113
  }
29454
31114
  else if (flatEntityIds.length === 0) {
29455
31115
  return;
29456
31116
  }
31117
+ // Followed while the parts load: in a large Assembly loading takes far longer than anyone waits.
31118
+ const following = this.viewerDateTimeSub();
29457
31119
  await this.processEntityIds(flatEntityIds);
29458
- if (this.disposed) {
29459
- return;
29460
- }
29461
- this.viewerDateTimeSub();
31120
+ await following;
29462
31121
  }
29463
31122
  catch (e) {
29464
31123
  console.error("AssemblyRenderManager.Manager.Init()", e);
@@ -29485,7 +31144,7 @@ var AssemblyRenderManager;
29485
31144
  if (!batch.length) {
29486
31145
  continue;
29487
31146
  }
29488
- const { entities: fetched } = await Entity$1.GetListByIds({
31147
+ const { entities: apiEntities } = await Entity$1.GetListByIds({
29489
31148
  entityIds: batch,
29490
31149
  api: this.getters.GetBruceApi(),
29491
31150
  expandLODs: true,
@@ -29497,6 +31156,16 @@ var AssemblyRenderManager;
29497
31156
  if (this.disposed) {
29498
31157
  return;
29499
31158
  }
31159
+ const fetched = apiEntities.map((entity) => {
31160
+ var _a;
31161
+ this.apiEntities.set((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID, entity);
31162
+ return this.withSimulation(entity);
31163
+ });
31164
+ for (const entity of fetched) {
31165
+ this.absorbNode(entity);
31166
+ }
31167
+ // Placements already worked out did not know about this batch's positions.
31168
+ this.pose = null;
29500
31169
  for (const entity of fetched) {
29501
31170
  // Filter for valid LODs then take the highest quality one (lowest level).
29502
31171
  let lods = entity.Bruce.LOD || [];
@@ -29519,57 +31188,46 @@ var AssemblyRenderManager;
29519
31188
  // Won't bother with vector data for this experiment.
29520
31189
  continue;
29521
31190
  }
29522
- const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29523
- const recordPos3d = EntityUtils.GetPos({
29524
- viewer: this.viewer,
29525
- entity,
29526
- recordHeightRef: recordHeightRef,
29527
- returnHeightRef: HeightReference.NONE,
29528
- allowRendered: false
29529
- });
29530
- if (!recordPos3d) {
31191
+ const entityId = entity.Bruce.ID;
31192
+ this.measureGroundOffset(entity);
31193
+ // A placement cached before the offset was known would leave the part off its ground.
31194
+ (_a = this.pose) === null || _a === void 0 ? void 0 : _a.placements.delete(entityId);
31195
+ if (!this.canMove(entityId) && !this.placementAt(entityId, this.viewer.clock.currentTime)) {
29531
31196
  continue;
29532
31197
  }
29533
- const entityId = entity.Bruce.ID;
29534
- if (entityId === this.rootId || !this.rootPos3d) {
29535
- // Nothing has a frame to be relative to until the root has one.
29536
- this.rootPos3d = recordPos3d;
29537
- this.rootHeading = EnsureNumber((_a = entity.Bruce.Transform) === null || _a === void 0 ? void 0 : _a.heading, 0);
29538
- }
29539
- // Held as an offset in the root's frame, so a root move places it again without
29540
- // anything being fetched. Where the root already moved, that is where it goes.
29541
- const offset = Manager.ChildOffsetIn(this.rootPos3d, this.rootHeading, recordPos3d);
29542
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, offset);
29543
- // Optional: a child with no world matrix is still worth drawing, it just has no
29544
- // orientation of its own to apply.
29545
- const orient = EntityUtils.GetAssemblyOrientation(entity, pos3d);
29546
31198
  const cEntity = this.viewer.entities.add({
29547
31199
  model: {
29548
31200
  uri: lod["ClientFile"].URL,
29549
31201
  ...ViewUtils.DefaultLightingProps(),
29550
31202
  heightReference: HeightReference.NONE
29551
- },
29552
- position: pos3d,
29553
- orientation: orient ? new ConstantProperty(orient) : undefined
31203
+ }
29554
31204
  });
29555
31205
  const prior = this.children.get(entityId);
29556
31206
  if (prior && prior.cEntity !== cEntity) {
29557
31207
  RemoveCEntityFromScene(this.viewer, prior.cEntity);
29558
31208
  }
29559
- this.children.set(entityId, {
31209
+ const child = {
29560
31210
  entityId: entityId,
29561
31211
  entity: entity,
29562
- cEntity: cEntity,
29563
- offset: offset
29564
- });
31212
+ cEntity: cEntity
31213
+ };
31214
+ this.children.set(entityId, child);
31215
+ // Joins the animated set by its own ancestry, so a late part never re-walks the rest.
31216
+ if (this.hasAnimatedAncestry(entityId)) {
31217
+ this.animatedIds.add(entityId);
31218
+ }
29565
31219
  const geometryRadius = Entity$1.GetValue({
29566
31220
  entity: entity,
29567
31221
  path: ["Bruce", "GeometryRadius"]
29568
31222
  });
29569
31223
  cEntity.model["_radius"] = geometryRadius;
29570
31224
  cEntity.model["_radiusLoaded"] = true;
31225
+ // Placed before it is registered, so the register never measures a visual with no position.
31226
+ this.bindPlacement(child);
29571
31227
  this.visualsManager.AddRego({
29572
31228
  rego: {
31229
+ moving: child.moving ? true : null,
31230
+ motion: (time) => this.motionAt(entity.Bruce.ID, time),
29573
31231
  canEdit: true,
29574
31232
  entityId: entity.Bruce.ID,
29575
31233
  menuItemId: this.item.id,
@@ -29589,47 +31247,89 @@ var AssemblyRenderManager;
29589
31247
  }
29590
31248
  }
29591
31249
  /*
29592
- * Follows the scene clock and moves the whole Assembly along the root Entity's historic path.
29593
- * Only the root is fetched: everything under it is placed through ApplyRootPosition.
31250
+ * Follows the scene clock, moving the Assembly along the root's history and each joint along its own.
29594
31251
  */
29595
- viewerDateTimeSub() {
29596
- if (!Manager.IsHistoric(this.item) || this.viewerDateTimeChangeRemoval || !this.rootId) {
31252
+ async viewerDateTimeSub() {
31253
+ var _a;
31254
+ const historic = Manager.IsHistoric(this.item);
31255
+ if (this.followingClock || !this.rootId || (!historic && !this.simulatedNodeIds().length)) {
29597
31256
  return;
29598
31257
  }
31258
+ this.followingClock = true;
29599
31259
  const api = this.getters.GetBruceApi();
29600
- const rootEntity = { Bruce: { ID: this.rootId } };
31260
+ const historicIds = historic ? await this.historicEntityIds(api) : [];
31261
+ if (this.disposed) {
31262
+ return;
31263
+ }
31264
+ const attrKey = (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey;
29601
31265
  const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29602
31266
  const toPosses = (records) => {
29603
31267
  return CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, recordHeightRef, HeightReference.NONE, records);
29604
31268
  };
29605
- this.historicGatherer = new HistoricGatherer({
29606
- viewer: this.viewer,
29607
- getApi: () => api,
29608
- getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
29609
- // For clock desync between whatever wrote the record and this client.
29610
- paddingMs: 15000,
29611
- onRecords: (recordsByIds) => {
29612
- const records = recordsByIds[this.rootId];
29613
- if ((records === null || records === void 0 ? void 0 : records.length) && this.historicAnimation) {
29614
- this.historicAnimation.SupplementSeries(toPosses(records));
31269
+ if (historicIds.length) {
31270
+ this.historicGatherer = new HistoricGatherer({
31271
+ viewer: this.viewer,
31272
+ getApi: () => api,
31273
+ getAttrKey: () => attrKey,
31274
+ // For clock desync between whatever wrote the record and this client.
31275
+ paddingMs: 15000,
31276
+ simulation: this.simulation,
31277
+ onRecords: (recordsByIds) => {
31278
+ var _a;
31279
+ const records = recordsByIds[this.rootId];
31280
+ if (records === null || records === void 0 ? void 0 : records.length) {
31281
+ const posses = toPosses(records);
31282
+ (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.SupplementSeries(posses);
31283
+ const moved = this.absorbRootRecords(posses, false);
31284
+ if (this.absorbRootAttitude(records, false) || moved) {
31285
+ this.repose();
31286
+ }
31287
+ }
31288
+ // Joint records go in together once the gather lands, so parents never draw apart from children.
29615
31289
  }
29616
- }
29617
- });
31290
+ });
31291
+ }
31292
+ // The nodes with history: what the API has records for, and whatever has been simulated since.
31293
+ const nodes = () => Array.from(new Set([...historicIds, ...this.simulatedNodeIds()]));
29618
31294
  let gathering = false;
29619
- const gather = async () => {
31295
+ // Set when what is held changed without a fetch, eg. a simulated record arriving.
31296
+ let stale = true;
31297
+ const gather = async (force) => {
31298
+ var _a, _b;
31299
+ stale = stale || Boolean(force);
29620
31300
  if (this.disposed || gathering) {
29621
31301
  return;
29622
31302
  }
29623
31303
  gathering = true;
29624
31304
  try {
29625
- const { recordsByIds } = await this.historicGatherer.Gather([rootEntity]);
31305
+ const ids = nodes();
31306
+ let fetched = false;
31307
+ let recordsByIds;
31308
+ if (this.historicGatherer) {
31309
+ ({ fetched, recordsByIds } = await this.historicGatherer.Gather(ids.map(id => ({ Bruce: { ID: id } }))));
31310
+ }
31311
+ else {
31312
+ recordsByIds = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.MergeRecords({}, attrKey, ids)) !== null && _b !== void 0 ? _b : {};
31313
+ }
29626
31314
  if (this.disposed) {
29627
31315
  return;
29628
31316
  }
31317
+ // A gather served from what is held changes nothing and runs every tick, so it stops here.
31318
+ if (!fetched && !stale && this.jointSeries.size) {
31319
+ return;
31320
+ }
31321
+ stale = false;
31322
+ let changed = this.absorbJointRecords(recordsByIds);
29629
31323
  const posses = toPosses(recordsByIds[this.rootId] || []);
31324
+ changed = this.absorbRootRecords(posses, true) || changed;
31325
+ changed = this.absorbRootAttitude(recordsByIds[this.rootId] || [], true) || changed;
31326
+ if (changed) {
31327
+ this.repose();
31328
+ }
29630
31329
  if (!posses.length) {
29631
31330
  return;
29632
31331
  }
31332
+ // Held for the trail alone; where the root is drawn comes from rootAt(), never from its smoothing.
29633
31333
  if (!this.historicAnimation) {
29634
31334
  this.historicAnimation = new CesiumAnimatedProperty.AnimatePositionSeries({
29635
31335
  posses: posses,
@@ -29643,15 +31343,21 @@ var AssemblyRenderManager;
29643
31343
  }
29644
31344
  catch (e) {
29645
31345
  console.error("AssemblyRenderManager.viewerDateTimeSub()", e);
31346
+ // A gather that throws would throw again straight away, so the retry waits for the next tick.
31347
+ stale = false;
29646
31348
  }
29647
31349
  finally {
29648
31350
  gathering = false;
31351
+ // A change that landed mid-gather is not left for a tick that may never come on a paused clock.
31352
+ if (stale && !this.disposed) {
31353
+ gather();
31354
+ }
29649
31355
  }
29650
31356
  };
31357
+ this.regather = gather;
29651
31358
  gather();
29652
31359
  let lastTime = null;
29653
31360
  const tickRemoval = this.viewer.clock.onTick.addEventListener(() => {
29654
- var _a;
29655
31361
  if (this.disposed) {
29656
31362
  return;
29657
31363
  }
@@ -29661,15 +31367,9 @@ var AssemblyRenderManager;
29661
31367
  }
29662
31368
  lastTime = now;
29663
31369
  gather();
29664
- if (!this.historicAnimation) {
29665
- return;
29666
- }
29667
- const pos3d = this.historicAnimation.GetValue();
29668
- if (pos3d) {
29669
- this.ApplyRootPosition({
29670
- pos3d: pos3d,
29671
- heading: (_a = this.historicAnimation.GetHeadingDegrees()) !== null && _a !== void 0 ? _a : this.rootHeading
29672
- });
31370
+ // Moving parts read the clock themselves when drawn, so the scene only needs a new frame.
31371
+ if (this.rootSeries.length || this.rootAttitude.length || this.animatedIds.size) {
31372
+ this.viewer.scene.requestRender();
29673
31373
  }
29674
31374
  });
29675
31375
  this.viewerDateTimeChangeRemoval = () => {
@@ -29678,6 +31378,10 @@ var AssemblyRenderManager;
29678
31378
  (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
29679
31379
  this.historicGatherer = null;
29680
31380
  this.historicAnimation = null;
31381
+ this.regather = null;
31382
+ this.rootSeries = [];
31383
+ this.rootAttitude = [];
31384
+ this.pose = null;
29681
31385
  };
29682
31386
  }
29683
31387
  /*
@@ -29685,7 +31389,7 @@ var AssemblyRenderManager;
29685
31389
  */
29686
31390
  applyRootTrack() {
29687
31391
  var _a;
29688
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
31392
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
29689
31393
  return;
29690
31394
  }
29691
31395
  const trackStyle = EntityRenderEngine.GetTrackStyle({
@@ -29695,41 +31399,35 @@ var AssemblyRenderManager;
29695
31399
  if (!trackStyle) {
29696
31400
  return;
29697
31401
  }
29698
- this.trackEntity = this.viewer.entities.add({
29699
- id: ObjectUtils.UId(15),
29700
- polyline: {
29701
- positions: new CallbackProperty(() => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; }, false),
29702
- width: trackStyle.width,
29703
- material: trackStyle.color,
29704
- clampToGround: EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
29705
- == HeightReference.CLAMP_TO_GROUND,
29706
- zIndex: 0,
29707
- classificationType: ClassificationType.BOTH
29708
- }
29709
- });
29710
- this.trackEntity._noEdit = true;
29711
- this.trackEntity._noSelect = true;
29712
- CesiumEntityStyler.SetDefaultColor({
29713
- color: trackStyle.color,
29714
- entity: this.trackEntity,
31402
+ this.trail = new HistoricTrail.Trail({
29715
31403
  viewer: this.viewer,
29716
- override: true,
29717
- requestRender: false
31404
+ color: trackStyle.color,
31405
+ width: trackStyle.width,
31406
+ clampToGround: EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
31407
+ == HeightReference.CLAMP_TO_GROUND,
31408
+ getSeries: () => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; },
31409
+ trailSeconds: HistoricTrail.GetTrailSeconds(this.item),
31410
+ // Where the root is drawn, so the trail ends on the model; nothing before the first record held.
31411
+ positionAt: (timeMs) => (this.rootSeries.length && timeMs >= this.rootSeries[0].timeMs ? this.rootPositionAt(timeMs) : null)
29718
31412
  });
29719
31413
  }
29720
31414
  Dispose() {
29721
- var _a;
31415
+ var _a, _b, _c;
29722
31416
  if (this.disposed) {
29723
31417
  return;
29724
31418
  }
29725
31419
  this.disposed = true;
29726
31420
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
29727
31421
  this.viewerDateTimeChangeRemoval = null;
29728
- if (this.trackEntity) {
29729
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
29730
- this.trackEntity = null;
29731
- }
31422
+ (_b = this.simulationRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
31423
+ this.simulationRemoval = null;
31424
+ this.apiEntities.clear();
31425
+ (_c = this.trail) === null || _c === void 0 ? void 0 : _c.Dispose();
31426
+ this.trail = null;
29732
31427
  this.children.clear();
31428
+ this.localPositions.clear();
31429
+ this.jointSeries.clear();
31430
+ this.animatedIds.clear();
29733
31431
  this.visualsManager.RemoveRegos({
29734
31432
  menuItemId: this.item.id
29735
31433
  });
@@ -29952,6 +31650,153 @@ var AssemblyRenderManager;
29952
31650
  AssemblyRenderManager.Manager = Manager;
29953
31651
  })(AssemblyRenderManager || (AssemblyRenderManager = {}));
29954
31652
 
31653
+ /**
31654
+ * Reads and picks between a menu item's ordered styles, writing the pick to the single field renderers read.
31655
+ */
31656
+ var MenuItemStyles;
31657
+ (function (MenuItemStyles) {
31658
+ // Tileset items whose render manager applies styleId through a Styler.
31659
+ const TILESET_TYPES = [
31660
+ MenuItem.EType.CadTileset,
31661
+ MenuItem.EType.ArbTileset,
31662
+ MenuItem.EType.IonTileset,
31663
+ MenuItem.EType.EntityTileset
31664
+ ];
31665
+ // Items whose style lives on their zoom control rows.
31666
+ const ZOOM_CONTROL_TYPES = [
31667
+ MenuItem.EType.Entities,
31668
+ MenuItem.EType.Entity,
31669
+ MenuItem.EType.EntitiesIds,
31670
+ MenuItem.EType.EntitiesLoaded,
31671
+ MenuItem.EType.DataLab
31672
+ ];
31673
+ /**
31674
+ * Returns the ordered styles a user can swap between for the item, or an empty array when it offers no choice.
31675
+ */
31676
+ function GetStyleIds(item) {
31677
+ var _a;
31678
+ if (isStyledTileset(item)) {
31679
+ return cleanIds(item.styleIds);
31680
+ }
31681
+ if (ZOOM_CONTROL_TYPES.includes(item === null || item === void 0 ? void 0 : item.Type)) {
31682
+ return cleanIds((_a = getVisibleRow(item.CameraZoomSettings)) === null || _a === void 0 ? void 0 : _a.StyleIDs);
31683
+ }
31684
+ return [];
31685
+ }
31686
+ MenuItemStyles.GetStyleIds = GetStyleIds;
31687
+ /**
31688
+ * Returns the style the item currently renders with, from styleId or its one non-hidden zoom row.
31689
+ */
31690
+ function GetStyleId(item) {
31691
+ var _a;
31692
+ if (TILESET_TYPES.includes(item === null || item === void 0 ? void 0 : item.Type)) {
31693
+ return item.styleId;
31694
+ }
31695
+ if (ZOOM_CONTROL_TYPES.includes(item === null || item === void 0 ? void 0 : item.Type)) {
31696
+ return (_a = getVisibleRow(item.CameraZoomSettings)) === null || _a === void 0 ? void 0 : _a.StyleID;
31697
+ }
31698
+ return undefined;
31699
+ }
31700
+ MenuItemStyles.GetStyleId = GetStyleId;
31701
+ /**
31702
+ * Returns a copy of the item rendering with the given style, or null when that style is not one of its options.
31703
+ */
31704
+ function WithStyleId(item, styleId) {
31705
+ const styleIds = GetStyleIds(item);
31706
+ const picked = styleIds.find(x => isSameId(x, styleId));
31707
+ if (picked == null) {
31708
+ return null;
31709
+ }
31710
+ if (isStyledTileset(item)) {
31711
+ return { ...item, styleId: picked };
31712
+ }
31713
+ const zItem = item;
31714
+ const visible = getVisibleRow(zItem.CameraZoomSettings);
31715
+ return {
31716
+ ...zItem,
31717
+ CameraZoomSettings: zItem.CameraZoomSettings.map(row => {
31718
+ if (row !== visible) {
31719
+ return row;
31720
+ }
31721
+ // An inline style would win over the picked ID.
31722
+ const next = { ...row, StyleID: picked };
31723
+ delete next.Style;
31724
+ return next;
31725
+ })
31726
+ };
31727
+ }
31728
+ MenuItemStyles.WithStyleId = WithStyleId;
31729
+ /**
31730
+ * Returns the item on the first of its styles unless it already uses one, or the same object when it has none.
31731
+ */
31732
+ function Resolve(item) {
31733
+ var _a;
31734
+ const styleIds = GetStyleIds(item);
31735
+ if (!styleIds.length) {
31736
+ return item;
31737
+ }
31738
+ const current = GetStyleId(item);
31739
+ if (current != null && styleIds.some(x => isSameId(x, current))) {
31740
+ return item;
31741
+ }
31742
+ return (_a = WithStyleId(item, styleIds[0])) !== null && _a !== void 0 ? _a : item;
31743
+ }
31744
+ MenuItemStyles.Resolve = Resolve;
31745
+ /**
31746
+ * Returns a copy on the style a bookmark alternation picked, or the item itself when that is not an option.
31747
+ */
31748
+ function ApplyAlternation(item, alternation) {
31749
+ var _a, _b, _c;
31750
+ // Alternations are partial items and may carry no Type, so the pick is read from whichever field is present.
31751
+ const alt = alternation;
31752
+ const picked = (_a = alt === null || alt === void 0 ? void 0 : alt.styleId) !== null && _a !== void 0 ? _a : (_b = getVisibleRow(alt === null || alt === void 0 ? void 0 : alt.CameraZoomSettings)) === null || _b === void 0 ? void 0 : _b.StyleID;
31753
+ if (picked == null) {
31754
+ return item;
31755
+ }
31756
+ return (_c = WithStyleId(item, picked)) !== null && _c !== void 0 ? _c : item;
31757
+ }
31758
+ MenuItemStyles.ApplyAlternation = ApplyAlternation;
31759
+ /**
31760
+ * Returns true when the two items differ by nothing but which of their styles is picked.
31761
+ */
31762
+ function IsStyleChangeOnly(from, to) {
31763
+ if (!from || !to || from.Type !== to.Type) {
31764
+ return false;
31765
+ }
31766
+ const toStyleId = GetStyleId(to);
31767
+ if (toStyleId == null || isSameId(GetStyleId(from), toStyleId)) {
31768
+ return false;
31769
+ }
31770
+ const moved = WithStyleId(from, toStyleId);
31771
+ // A false mismatch only costs the in-place swap, so key order is not normalized.
31772
+ return Boolean(moved) && JSON.stringify(moved) === JSON.stringify(to);
31773
+ }
31774
+ MenuItemStyles.IsStyleChangeOnly = IsStyleChangeOnly;
31775
+ function isStyledTileset(item) {
31776
+ if (!TILESET_TYPES.includes(item === null || item === void 0 ? void 0 : item.Type)) {
31777
+ return false;
31778
+ }
31779
+ // These two only run a Styler when asked to.
31780
+ if (item.Type === MenuItem.EType.ArbTileset || item.Type === MenuItem.EType.EntityTileset) {
31781
+ return Boolean(item.ApplyStyles);
31782
+ }
31783
+ return true;
31784
+ }
31785
+ function getVisibleRow(rows) {
31786
+ const visible = (rows !== null && rows !== void 0 ? rows : []).filter(x => x && x.DisplayType !== ZoomControl.EDisplayType.Hidden);
31787
+ return visible.length === 1 ? visible[0] : null;
31788
+ }
31789
+ function cleanIds(ids) {
31790
+ return (ids !== null && ids !== void 0 ? ids : []).filter(x => x != null && x !== "");
31791
+ }
31792
+ /*
31793
+ * Compares style references, which can be an ID typed as a string or a number.
31794
+ */
31795
+ function isSameId(a, b) {
31796
+ return a != null && b != null && String(a) === String(b);
31797
+ }
31798
+ })(MenuItemStyles || (MenuItemStyles = {}));
31799
+
29955
31800
  /**
29956
31801
  * Util to batch Tileset and Root Entity coord requests between Menu Items.
29957
31802
  * This avoids per-menu-item requests and allows for them to request together if enabled within N ms of each other.
@@ -30187,12 +32032,17 @@ var MenuItemManager;
30187
32032
  this.OnSeriesUpdated.Trigger({ itemId, segments: [] });
30188
32033
  }
30189
32034
  }
32035
+ get Simulation() {
32036
+ return this.simulation;
32037
+ }
30190
32038
  get Monitor() {
30191
32039
  return this.sharedMonitor;
30192
32040
  }
30193
32041
  constructor(params) {
30194
32042
  this.items = [];
30195
32043
  this.pendingHandoffPool = [];
32044
+ // The settings bookmark alternations overwrite on a view's items, as that view authored them.
32045
+ this.authoredAlternations = new WeakMap();
30196
32046
  this.onUpdate = null;
30197
32047
  // Segments discovered so far per menu item ID.
30198
32048
  this.seriesSegments = new Map();
@@ -30212,6 +32062,7 @@ var MenuItemManager;
30212
32062
  this.visualsRegister = visualsRegister;
30213
32063
  this.sharedGetters = new SharedGetters.Cache();
30214
32064
  this.sharedHistoric = new SharedHistoricGetters.Cache();
32065
+ this.simulation = EntitySimulation.Get(this.viewer);
30215
32066
  this.sharedMonitor = new CesiumViewMonitor.Monitor(this.viewer);
30216
32067
  this.tilesetInitQueue = new InitQueueTileset(this.getters);
30217
32068
  }
@@ -30247,7 +32098,7 @@ var MenuItemManager;
30247
32098
  * @returns
30248
32099
  */
30249
32100
  RenderItem(params) {
30250
- var _a, _b, _c, _d, _e, _f, _g;
32101
+ var _a, _b, _c, _d, _e, _f, _g, _h;
30251
32102
  if (this.viewer.isDestroyed()) {
30252
32103
  return null;
30253
32104
  }
@@ -30271,13 +32122,21 @@ var MenuItemManager;
30271
32122
  if (params.modelSpace == null) {
30272
32123
  params.modelSpace = false;
30273
32124
  }
32125
+ // An item with a style set renders its first style unless the caller already picked one of them.
32126
+ params = { ...params, item: MenuItemStyles.Resolve(params.item) };
30274
32127
  let rItem = this.items.find(x => x.id == params.item.id);
30275
32128
  let create = true;
30276
32129
  // Item already exists so we'll check if we should re-create it.
30277
32130
  if (rItem) {
32131
+ // Only the picked style changed, so what is drawn gets restyled rather than rebuilt.
32132
+ if (MenuItemStyles.IsStyleChangeOnly(rItem.item, params.item) && this.isStyleSwapCapable(rItem.renderManager)) {
32133
+ rItem.renderManager.SwapStyle(params.item);
32134
+ rItem.item = params.item;
32135
+ create = false;
32136
+ }
30278
32137
  // This means we're updating an entities menu item re-using the same id.
30279
32138
  // Eg: a bookmark swap that keeps the same item but changes tags, style, or zoom settings.
30280
- if (rItem.type == MenuItem.EType.Entities && params.item.Type == MenuItem.EType.Entities) {
32139
+ else if (rItem.type == MenuItem.EType.Entities && params.item.Type == MenuItem.EType.Entities) {
30281
32140
  rItem.renderManager.Init({
30282
32141
  item: params.item,
30283
32142
  });
@@ -30425,7 +32284,8 @@ var MenuItemManager;
30425
32284
  monitor: this.sharedMonitor,
30426
32285
  item: params.item,
30427
32286
  sharedGetters: this.sharedGetters,
30428
- onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment)
32287
+ onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment),
32288
+ simulation: this.simulation
30429
32289
  });
30430
32290
  break;
30431
32291
  case MenuItem.EType.EntitiesLoaded:
@@ -30434,7 +32294,8 @@ var MenuItemManager;
30434
32294
  register: this.visualsRegister,
30435
32295
  apiGetter: params.apiGetter,
30436
32296
  monitor: this.sharedMonitor,
30437
- item: params.item
32297
+ item: params.item,
32298
+ simulation: this.simulation
30438
32299
  });
30439
32300
  break;
30440
32301
  case MenuItem.EType.EntitiesIds:
@@ -30444,7 +32305,8 @@ var MenuItemManager;
30444
32305
  apiGetter: params.apiGetter,
30445
32306
  monitor: this.sharedMonitor,
30446
32307
  item: params.item,
30447
- sharedHistoric: this.sharedHistoric
32308
+ sharedHistoric: this.sharedHistoric,
32309
+ simulation: this.simulation
30448
32310
  });
30449
32311
  break;
30450
32312
  case MenuItem.EType.Entity:
@@ -30453,7 +32315,8 @@ var MenuItemManager;
30453
32315
  register: this.visualsRegister,
30454
32316
  apiGetter: params.apiGetter,
30455
32317
  monitor: this.sharedMonitor,
30456
- item: params.item
32318
+ item: params.item,
32319
+ simulation: this.simulation
30457
32320
  });
30458
32321
  break;
30459
32322
  case MenuItem.EType.CadTileset:
@@ -30463,7 +32326,8 @@ var MenuItemManager;
30463
32326
  getters: this.getters,
30464
32327
  item: params.item,
30465
32328
  modelSpace: params.modelSpace,
30466
- initQueue: this.tilesetInitQueue
32329
+ initQueue: this.tilesetInitQueue,
32330
+ simulation: this.simulation
30467
32331
  });
30468
32332
  break;
30469
32333
  case MenuItem.EType.Assembly:
@@ -30473,6 +32337,7 @@ var MenuItemManager;
30473
32337
  getters: params.getters,
30474
32338
  item: params.item,
30475
32339
  modelSpace: params.modelSpace,
32340
+ simulation: this.simulation
30476
32341
  });
30477
32342
  break;
30478
32343
  case MenuItem.EType.Osm:
@@ -30500,7 +32365,8 @@ var MenuItemManager;
30500
32365
  getters: this.getters,
30501
32366
  monitor: this.sharedMonitor,
30502
32367
  item: params.item,
30503
- initQueue: this.tilesetInitQueue
32368
+ initQueue: this.tilesetInitQueue,
32369
+ simulation: this.simulation
30504
32370
  });
30505
32371
  break;
30506
32372
  case MenuItem.EType.Kml:
@@ -30524,7 +32390,8 @@ var MenuItemManager;
30524
32390
  getters: params.getters,
30525
32391
  item: params.item,
30526
32392
  viewer: this.viewer,
30527
- register: this.visualsRegister
32393
+ register: this.visualsRegister,
32394
+ simulation: this.simulation
30528
32395
  });
30529
32396
  break;
30530
32397
  // Deprecated to support a few demos until new one is phased in.
@@ -30534,7 +32401,8 @@ var MenuItemManager;
30534
32401
  apiGetter: params.apiGetter,
30535
32402
  item: params.item,
30536
32403
  register: this.visualsRegister,
30537
- viewer: this.viewer
32404
+ viewer: this.viewer,
32405
+ simulation: this.simulation
30538
32406
  });
30539
32407
  break;
30540
32408
  case MenuItem.EType.Relationships:
@@ -30542,7 +32410,8 @@ var MenuItemManager;
30542
32410
  apiGetter: params.apiGetter,
30543
32411
  item: params.item,
30544
32412
  register: this.visualsRegister,
30545
- viewer: this.viewer
32413
+ viewer: this.viewer,
32414
+ simulation: this.simulation
30546
32415
  });
30547
32416
  break;
30548
32417
  case MenuItem.EType.GooglePhotoTileset:
@@ -30574,7 +32443,8 @@ var MenuItemManager;
30574
32443
  item: params.item,
30575
32444
  apiGetter: params.apiGetter,
30576
32445
  monitor: this.sharedMonitor,
30577
- visualsRegister: this.visualsRegister
32446
+ visualsRegister: this.visualsRegister,
32447
+ simulation: this.simulation
30578
32448
  });
30579
32449
  break;
30580
32450
  // Any "none" type can be ignored. These are just containers for other items.
@@ -30601,7 +32471,17 @@ var MenuItemManager;
30601
32471
  // We no longer support folder items being both a folder and an action.
30602
32472
  // Now an item must have no action to be a folder.
30603
32473
  const isFolder = !params.item.Type || params.item.Type == MenuItem.EType.None;
30604
- if (params.item.Children && params.recursive != false && isFolder) {
32474
+ const isRadioFolder = isFolder && Boolean(params.item.childrenAsRadios);
32475
+ if (((_f = params.item.Children) === null || _f === void 0 ? void 0 : _f.length) && params.recursive != false && isRadioFolder) {
32476
+ const picked = this.pickRadioChild(params.item.Children, params.preferredIds);
32477
+ const others = params.item.Children.filter(x => x !== picked && this.items.some(y => y.id === x.id));
32478
+ const childId = this.renderInPlaceOf(others.map(x => x.id), () => this.RenderItem({
32479
+ ...params,
32480
+ item: picked
32481
+ }));
32482
+ rItem.childIds = childId ? [childId] : [];
32483
+ }
32484
+ else if (params.item.Children && params.recursive != false && isFolder) {
30605
32485
  for (let i = 0; i < params.item.Children.length; i++) {
30606
32486
  const childId = this.RenderItem({
30607
32487
  ...params,
@@ -30610,8 +32490,8 @@ var MenuItemManager;
30610
32490
  rItem.childIds.push(childId);
30611
32491
  }
30612
32492
  }
30613
- if (rItem.renderManager && !((_f = rItem.renderManager) === null || _f === void 0 ? void 0 : _f.Disposed)) {
30614
- (_g = this.onUpdate) === null || _g === void 0 ? void 0 : _g.Trigger({ isEnabling: true, itemId: rItem.id });
32493
+ if (rItem.renderManager && !((_g = rItem.renderManager) === null || _g === void 0 ? void 0 : _g.Disposed)) {
32494
+ (_h = this.onUpdate) === null || _h === void 0 ? void 0 : _h.Trigger({ isEnabling: true, itemId: rItem.id });
30615
32495
  }
30616
32496
  return rItem.id;
30617
32497
  }
@@ -30660,9 +32540,74 @@ var MenuItemManager;
30660
32540
  (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
30661
32541
  }
30662
32542
  }
32543
+ /**
32544
+ * Replaces enabled menu items with another, which reuses their graphics where both draw the same data.
32545
+ */
32546
+ SwapItem(params) {
32547
+ const { fromIds, item, modelSpace } = params;
32548
+ const parents = this.items.filter(x => x.childIds.some(id => fromIds.includes(id)));
32549
+ const id = this.renderInPlaceOf(fromIds, () => this.RenderItem({
32550
+ item,
32551
+ modelSpace
32552
+ }));
32553
+ // Keeps a radio folder's removal reaching the child that replaced its old one.
32554
+ for (const parent of parents) {
32555
+ const kept = parent.childIds.filter(x => !fromIds.includes(x));
32556
+ parent.childIds = id && !kept.includes(id) ? kept.concat(id) : kept;
32557
+ }
32558
+ return id;
32559
+ }
32560
+ /**
32561
+ * Restyles an enabled menu item in place with another of its own styles, returning false when it cannot.
32562
+ */
32563
+ SetItemStyle(params) {
32564
+ var _a;
32565
+ const { menuItemId, styleId } = params;
32566
+ const rItem = this.items.find(x => x.id === menuItemId);
32567
+ if (!rItem || !this.isStyleSwapCapable(rItem.renderManager) || rItem.renderManager.Disposed) {
32568
+ return false;
32569
+ }
32570
+ const next = MenuItemStyles.WithStyleId(rItem.item, styleId);
32571
+ if (!next) {
32572
+ return false;
32573
+ }
32574
+ rItem.renderManager.SwapStyle(next);
32575
+ rItem.item = next;
32576
+ (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger({ isEnabling: true, itemId: rItem.id });
32577
+ return true;
32578
+ }
32579
+ /*
32580
+ * Picks the child the caller asked for, else the one already enabled, else the first.
32581
+ */
32582
+ pickRadioChild(children, preferredIds) {
32583
+ var _a, _b;
32584
+ return ((_b = (_a = children.find(x => preferredIds === null || preferredIds === void 0 ? void 0 : preferredIds.includes(x.id))) !== null && _a !== void 0 ? _a : children.find(x => this.items.some(y => y.id === x.id))) !== null && _b !== void 0 ? _b : children[0]);
32585
+ }
32586
+ /*
32587
+ * Removes the items so the render can claim their graphics, then disposes whatever it left unclaimed.
32588
+ */
32589
+ renderInPlaceOf(removeIds, render) {
32590
+ const pooledBefore = new Set(this.pendingHandoffPool);
32591
+ for (const id of removeIds) {
32592
+ this.RemoveItemById({
32593
+ menuItemId: id,
32594
+ allowHandoff: true
32595
+ });
32596
+ }
32597
+ const pooled = this.pendingHandoffPool.filter(x => !pooledBefore.has(x));
32598
+ try {
32599
+ return render();
32600
+ }
32601
+ finally {
32602
+ this.disposePooled(x => pooled.includes(x));
32603
+ }
32604
+ }
30663
32605
  isHandoffCapable(renderManager) {
30664
32606
  return Boolean(renderManager) && typeof renderManager.PrepareHandoff === "function";
30665
32607
  }
32608
+ isStyleSwapCapable(renderManager) {
32609
+ return Boolean(renderManager) && typeof renderManager.SwapStyle === "function";
32610
+ }
30666
32611
  // Resolves the hand-off key a menu item's data would have.
30667
32612
  computeHandoffKey(item, modelSpace) {
30668
32613
  if (item.Type === MenuItem.EType.CadTileset) {
@@ -30738,12 +32683,15 @@ var MenuItemManager;
30738
32683
  }
30739
32684
  // Disposes everything left unclaimed in the pending-hand-off pool.
30740
32685
  FlushPendingHandoffs() {
32686
+ this.disposePooled(() => true);
32687
+ }
32688
+ disposePooled(match) {
30741
32689
  var _a;
30742
32690
  if (!this.pendingHandoffPool.length) {
30743
32691
  return;
30744
32692
  }
30745
- const pool = this.pendingHandoffPool;
30746
- this.pendingHandoffPool = [];
32693
+ const pool = this.pendingHandoffPool.filter(match);
32694
+ this.pendingHandoffPool = this.pendingHandoffPool.filter(x => !match(x));
30747
32695
  for (const item of pool) {
30748
32696
  try {
30749
32697
  (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
@@ -30754,6 +32702,26 @@ var MenuItemManager;
30754
32702
  this.ClearSeriesSegments(item.id);
30755
32703
  }
30756
32704
  }
32705
+ /**
32706
+ * Returns the tags and attenuation the item's view authored, before any bookmark wrote over them.
32707
+ */
32708
+ GetAuthoredAlternation(item) {
32709
+ var _a;
32710
+ let authored = this.authoredAlternations.get(item);
32711
+ if (!authored) {
32712
+ const eItem = item;
32713
+ const tItem = item;
32714
+ authored = JSON.parse(JSON.stringify({
32715
+ id: item.id,
32716
+ Type: item.Type,
32717
+ BruceEntity: { SelectedExpandLayers: (_a = eItem.BruceEntity) === null || _a === void 0 ? void 0 : _a.SelectedExpandLayers },
32718
+ attenuation: tItem.attenuation,
32719
+ attenuationMax: tItem.attenuationMax
32720
+ }));
32721
+ this.authoredAlternations.set(item, authored);
32722
+ }
32723
+ return authored;
32724
+ }
30757
32725
  GetEnabledItemIds() {
30758
32726
  return this.items.map(x => x.id);
30759
32727
  }
@@ -30787,6 +32755,63 @@ var MenuItemManager;
30787
32755
  }
30788
32756
  }
30789
32757
  }
32758
+ /**
32759
+ * Draws Entities from data given here as well as, or instead of, the API's, until ClearSimulation is called.
32760
+ * @param params.set the simulation this belongs to, so it can be cleared apart from others. Default "default".
32761
+ * @param params.entities stand-ins that replace each Entity's record; API data for them is ignored.
32762
+ * @param params.patches partial Entities laid over each Entity; live API data shows where a patch is silent.
32763
+ * @param params.records historic records naming entityId, dateTime and only the data they change.
32764
+ * @param params.menuItemIds the items to redraw. By default every item already drawing one of the Entities.
32765
+ */
32766
+ async Simulate(params) {
32767
+ const { set, entities, patches, records, menuItemIds } = params;
32768
+ this.simulation.SetEntities(entities, set);
32769
+ this.simulation.SetPatches(patches, set);
32770
+ this.simulation.SetRecords(records, set);
32771
+ const entityIds = [
32772
+ ...(entities || []).map(x => { var _a; return (_a = x === null || x === void 0 ? void 0 : x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }),
32773
+ ...(patches || []).map(x => { var _a; return (_a = x === null || x === void 0 ? void 0 : x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }),
32774
+ ...(records || []).map(x => x === null || x === void 0 ? void 0 : x.entityId)
32775
+ ];
32776
+ await this.reRenderSimulated(entityIds, menuItemIds);
32777
+ }
32778
+ /**
32779
+ * Stops simulating these Entities, or every Entity when none are named, and redraws them.
32780
+ * @param params.set the simulation to clear. By default every one.
32781
+ * @param params.entities clear the stand-ins and patches. Default true.
32782
+ * @param params.records clear the historic records. Default true.
32783
+ */
32784
+ async ClearSimulation(params) {
32785
+ var _a;
32786
+ const entityIds = (_a = params === null || params === void 0 ? void 0 : params.entityIds) !== null && _a !== void 0 ? _a : [
32787
+ ...this.simulation.GetEntityIds(),
32788
+ ...this.simulation.GetRecordEntityIds()
32789
+ ];
32790
+ this.simulation.Clear({ set: params === null || params === void 0 ? void 0 : params.set, entityIds, entities: params === null || params === void 0 ? void 0 : params.entities, records: params === null || params === void 0 ? void 0 : params.records });
32791
+ await this.reRenderSimulated(entityIds, params === null || params === void 0 ? void 0 : params.menuItemIds);
32792
+ }
32793
+ /*
32794
+ * Redraws simulated Entities only in the items holding a visual for them, unless the caller names the items.
32795
+ */
32796
+ async reRenderSimulated(entityIds, menuItemIds) {
32797
+ const ids = Array.from(new Set(entityIds.filter(x => !!x)));
32798
+ if (!ids.length) {
32799
+ return;
32800
+ }
32801
+ const items = (menuItemIds === null || menuItemIds === void 0 ? void 0 : menuItemIds.length) ? this.items.filter(x => menuItemIds.includes(x.id)) : this.items;
32802
+ for (const item of items) {
32803
+ // Some managers follow the store themselves, and redrawing them here would only repeat that.
32804
+ if (!item.renderManager || item.renderManager.FollowsSimulation) {
32805
+ continue;
32806
+ }
32807
+ const drawn = (menuItemIds === null || menuItemIds === void 0 ? void 0 : menuItemIds.length)
32808
+ ? ids
32809
+ : ids.filter(id => this.visualsRegister.GetRego({ entityId: id, menuItemId: item.id }));
32810
+ if (drawn.length) {
32811
+ await item.renderManager.ReRender({ entityIds: drawn, force: true });
32812
+ }
32813
+ }
32814
+ }
30790
32815
  }
30791
32816
  MenuItemManager.Manager = Manager;
30792
32817
  })(MenuItemManager || (MenuItemManager = {}));
@@ -31771,6 +33796,7 @@ var EMenuItemState;
31771
33796
  })(EMenuItemState || (EMenuItemState = {}));
31772
33797
  // Icons are inline so the library stays dependency free.
31773
33798
  const ICON_CHECK = `<svg viewBox="0 0 11 8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 4L4 7L10 1" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
33799
+ const ICON_RADIO = `<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="6" cy="6" r="3.5" fill="white"/></svg>`;
31774
33800
  const ICON_PARTIAL_CHECK = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 12H16" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
31775
33801
  const ICON_CHEV_DOWN = `<svg viewBox="0 0 16 9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15 1L8 8L1 1" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
31776
33802
  const ICON_CHEV_RIGHT = `<svg viewBox="0 0 10 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 1.5L8.5 8.5L1.5 15.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
@@ -31974,6 +34000,10 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
31974
34000
  margin-right: 8px;
31975
34001
  }
31976
34002
 
34003
+ .NextspaceMenuItemRow >.NextspaceMenuItemRowItem >.NextspaceMenuItemRowCheckbox[is-radio="true"] {
34004
+ border-radius: 50%;
34005
+ }
34006
+
31977
34007
  .NextspaceMenuItemRow >.NextspaceMenuItemRowItem >.NextspaceMenuItemRowToggle {
31978
34008
  margin-right: 3px;
31979
34009
  }
@@ -32224,13 +34254,15 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32224
34254
  }
32225
34255
  return this._waitedForView ? "No view is loaded." : "Loading..";
32226
34256
  }
32227
- _renderRow(item, depth, parent) {
34257
+ _renderRow(item, depth, parent, radioParent) {
32228
34258
  var _a;
32229
34259
  if (!(item === null || item === void 0 ? void 0 : item.id)) {
32230
34260
  return;
32231
34261
  }
32232
34262
  const isFolder = !item.Type || item.Type === MenuItem.EType.None;
32233
- const children = item.hideChildren ? [] : ((_a = item.Children) !== null && _a !== void 0 ? _a : []);
34263
+ const isRadioFolder = isFolder && Boolean(item.childrenAsRadios);
34264
+ // Radios are the folder's whole point, so they show even when the folder also hides its children.
34265
+ const children = item.hideChildren && !isRadioFolder ? [] : ((_a = item.Children) !== null && _a !== void 0 ? _a : []);
32234
34266
  const hasChildren = children.length > 0;
32235
34267
  const row = document.createElement("div");
32236
34268
  row.className = "NextspaceMenuItemRow";
@@ -32262,8 +34294,9 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32262
34294
  }
32263
34295
  const checkbox = document.createElement("div");
32264
34296
  checkbox.className = "NextspaceMenuItemRowCheckbox";
34297
+ checkbox.setAttribute("is-radio", radioParent ? "true" : "false");
32265
34298
  checkbox.onclick = () => {
32266
- this._toggleItem(item);
34299
+ this._toggleItem(item, radioParent);
32267
34300
  };
32268
34301
  itemElement.appendChild(checkbox);
32269
34302
  const title = document.createElement("div");
@@ -32313,10 +34346,11 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32313
34346
  itemElement,
32314
34347
  checkboxElement: checkbox,
32315
34348
  countElement: count,
32316
- childrenElement
34349
+ childrenElement,
34350
+ radioParent
32317
34351
  });
32318
34352
  for (const child of children) {
32319
- this._renderRow(child, depth + 1, childrenElement);
34353
+ this._renderRow(child, depth + 1, childrenElement, isRadioFolder ? item : undefined);
32320
34354
  }
32321
34355
  }
32322
34356
  _getOpen(itemId, depth) {
@@ -32351,7 +34385,10 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32351
34385
  for (const row of Array.from(this._rows.values())) {
32352
34386
  const state = this._resolveState(row.item, enabledIds);
32353
34387
  row.itemElement.setAttribute("is-enabled", state === EMenuItemState.Disabled ? "false" : "true");
32354
- if (state === EMenuItemState.Enabled) {
34388
+ if (row.radioParent && state !== EMenuItemState.Disabled) {
34389
+ row.checkboxElement.innerHTML = ICON_RADIO;
34390
+ }
34391
+ else if (state === EMenuItemState.Enabled) {
32355
34392
  row.checkboxElement.innerHTML = ICON_CHECK;
32356
34393
  }
32357
34394
  else if (state === EMenuItemState.ChildEnabled) {
@@ -32366,17 +34403,33 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32366
34403
  * Works out a single item's state, rolling up children so a folder can report a partial state.
32367
34404
  */
32368
34405
  _resolveState(item, enabledIds) {
32369
- var _a;
34406
+ var _a, _b;
32370
34407
  const isFolder = !item.Type || item.Type === MenuItem.EType.None;
32371
34408
  if (!isFolder) {
32372
34409
  return enabledIds.has(item.id) ? EMenuItemState.Enabled : EMenuItemState.Disabled;
32373
34410
  }
34411
+ // Only one radio is ever enabled, so the folder is as enabled as that one.
34412
+ if (item.childrenAsRadios && ((_a = item.Children) === null || _a === void 0 ? void 0 : _a.length)) {
34413
+ const states = item.Children.map(x => this._resolveState(x, enabledIds));
34414
+ if (states.includes(EMenuItemState.Enabled)) {
34415
+ return EMenuItemState.Enabled;
34416
+ }
34417
+ return states.includes(EMenuItemState.ChildEnabled) ? EMenuItemState.ChildEnabled : EMenuItemState.Disabled;
34418
+ }
32374
34419
  // Folders derive from the leaves below them rather than their own enabled flag.
32375
34420
  let actionCount = 0;
32376
34421
  let enabledActionCount = 0;
32377
34422
  const traverse = (current) => {
32378
- var _a;
34423
+ var _a, _b;
32379
34424
  const currentIsFolder = !current.Type || current.Type === MenuItem.EType.None;
34425
+ // A radio folder counts as one choice rather than as each of its radios.
34426
+ if (currentIsFolder && current.childrenAsRadios && ((_a = current.Children) === null || _a === void 0 ? void 0 : _a.length)) {
34427
+ actionCount += 1;
34428
+ if (this._resolveState(current, enabledIds) === EMenuItemState.Enabled) {
34429
+ enabledActionCount += 1;
34430
+ }
34431
+ return;
34432
+ }
32380
34433
  if (!currentIsFolder) {
32381
34434
  actionCount += 1;
32382
34435
  if (enabledIds.has(current.id)) {
@@ -32384,11 +34437,11 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32384
34437
  }
32385
34438
  return;
32386
34439
  }
32387
- if ((_a = current.Children) === null || _a === void 0 ? void 0 : _a.length) {
34440
+ if ((_b = current.Children) === null || _b === void 0 ? void 0 : _b.length) {
32388
34441
  current.Children.forEach(traverse);
32389
34442
  }
32390
34443
  };
32391
- (_a = item.Children) === null || _a === void 0 ? void 0 : _a.forEach(traverse);
34444
+ (_b = item.Children) === null || _b === void 0 ? void 0 : _b.forEach(traverse);
32392
34445
  if (actionCount <= 0) {
32393
34446
  return enabledIds.has(item.id) ? EMenuItemState.Enabled : EMenuItemState.Disabled;
32394
34447
  }
@@ -32414,7 +34467,7 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32414
34467
  row.countElement.textContent = count.toLocaleString();
32415
34468
  }
32416
34469
  }
32417
- _toggleItem(item) {
34470
+ _toggleItem(item, radioParent) {
32418
34471
  var _a;
32419
34472
  const manager = (_a = this._widget) === null || _a === void 0 ? void 0 : _a._getManager();
32420
34473
  if (!manager) {
@@ -32422,7 +34475,21 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32422
34475
  }
32423
34476
  const enabledIds = new Set(manager.GetEnabledItemIds());
32424
34477
  const state = this._resolveState(item, enabledIds);
32425
- if (state === EMenuItemState.Enabled) {
34478
+ // A radio stays picked until another one is, the folder's own checkbox turns them all off.
34479
+ if (radioParent && state === EMenuItemState.Enabled) {
34480
+ return;
34481
+ }
34482
+ let siblingIds = [];
34483
+ if (radioParent && state === EMenuItemState.Disabled) {
34484
+ siblingIds = radioParent.Children.filter(x => x !== item).flatMap(x => this._getEnabledIdsUnder(x, enabledIds));
34485
+ }
34486
+ if (siblingIds.length) {
34487
+ manager.SwapItem({
34488
+ fromIds: siblingIds,
34489
+ item
34490
+ });
34491
+ }
34492
+ else if (state === EMenuItemState.Enabled) {
32426
34493
  this._disableItem(item);
32427
34494
  }
32428
34495
  else {
@@ -32432,6 +34499,16 @@ class WidgetLeftPanelTabMenuItems extends WidgetLeftPanelTab.ATab {
32432
34499
  }
32433
34500
  this._stateQueue.Call(true);
32434
34501
  }
34502
+ /*
34503
+ * Returns the highest enabled ids at or below the item, since removing one removes everything under it.
34504
+ */
34505
+ _getEnabledIdsUnder(item, enabledIds) {
34506
+ var _a;
34507
+ if (enabledIds.has(item.id)) {
34508
+ return [item.id];
34509
+ }
34510
+ return ((_a = item.Children) !== null && _a !== void 0 ? _a : []).flatMap(x => this._getEnabledIdsUnder(x, enabledIds));
34511
+ }
32435
34512
  _disableItem(item) {
32436
34513
  var _a, _b;
32437
34514
  const manager = (_a = this._widget) === null || _a === void 0 ? void 0 : _a._getManager();
@@ -33964,33 +36041,50 @@ var MenuItemCreator;
33964
36041
  }
33965
36042
  const renderedItemIds = [];
33966
36043
  if ((menuItems === null || menuItems === void 0 ? void 0 : menuItems.length) && (enabledIds === null || enabledIds === void 0 ? void 0 : enabledIds.length)) {
33967
- const traverseItems = async (item) => {
36044
+ const alternateItems = (item) => {
36045
+ var _a, _b, _c, _d;
36046
+ // The project view menu item contains the instruction to enable tags, the bookmark specifies which ones.
36047
+ if (item.Type == MenuItem.EType.Entities && ((_a = item.BruceEntity) === null || _a === void 0 ? void 0 : _a.ExpandLayers)) {
36048
+ const eItem = item;
36049
+ // A setting the bookmark leaves out gets the view's own value back, not the last bookmark's.
36050
+ const authored = manager.GetAuthoredAlternation(item);
36051
+ const eItemAlt = alternations[item.id];
36052
+ const tags = (_b = eItemAlt === null || eItemAlt === void 0 ? void 0 : eItemAlt.BruceEntity) === null || _b === void 0 ? void 0 : _b.SelectedExpandLayers;
36053
+ eItem.BruceEntity = {
36054
+ ...eItem.BruceEntity,
36055
+ SelectedExpandLayers: tags !== undefined ? tags : (_c = authored.BruceEntity) === null || _c === void 0 ? void 0 : _c.SelectedExpandLayers,
36056
+ };
36057
+ }
36058
+ else if (item.Type == MenuItem.EType.EntityTileset) {
36059
+ const tItem = item;
36060
+ const authored = manager.GetAuthoredAlternation(item);
36061
+ const tItemAlt = alternations[item.id];
36062
+ const hasAttenuation = (tItemAlt === null || tItemAlt === void 0 ? void 0 : tItemAlt.attenuation) !== undefined || (tItemAlt === null || tItemAlt === void 0 ? void 0 : tItemAlt.attenuationMax) !== undefined;
36063
+ const source = hasAttenuation ? tItemAlt : authored;
36064
+ tItem.attenuation = source.attenuation;
36065
+ tItem.attenuationMax = source.attenuationMax;
36066
+ }
36067
+ (_d = item.Children) === null || _d === void 0 ? void 0 : _d.forEach(alternateItems);
36068
+ };
36069
+ /*
36070
+ * Lays each picked style over a copy, so it never stays on the view's item for a later bookmark.
36071
+ */
36072
+ const styleItems = (item) => {
33968
36073
  var _a;
33969
- if (alternations[item.id]) {
33970
- // The only alternation we support right now is tag menu item ones.
33971
- // The project view menu item contains the instruction to enable tags, the bookmark specifies which ones.
33972
- if (item.Type == MenuItem.EType.Entities) {
33973
- const eItem = item;
33974
- if (eItem.BruceEntity.ExpandLayers) {
33975
- const eItemAlt = alternations[item.id];
33976
- eItem.BruceEntity = {
33977
- ...eItem.BruceEntity,
33978
- SelectedExpandLayers: (_a = eItemAlt.BruceEntity) === null || _a === void 0 ? void 0 : _a.SelectedExpandLayers,
33979
- };
33980
- }
33981
- }
33982
- else if (item.Type == MenuItem.EType.EntityTileset) {
33983
- const tItem = item;
33984
- const tItemAlt = alternations[item.id];
33985
- tItem.attenuation = tItemAlt.attenuation;
33986
- tItem.attenuationMax = tItemAlt.attenuationMax;
33987
- }
36074
+ let next = alternations[item.id] ? MenuItemStyles.ApplyAlternation(item, alternations[item.id]) : item;
36075
+ const children = (_a = item.Children) === null || _a === void 0 ? void 0 : _a.map(styleItems);
36076
+ if (children === null || children === void 0 ? void 0 : children.some((child, i) => child !== item.Children[i])) {
36077
+ next = { ...next, Children: children };
33988
36078
  }
36079
+ return next;
36080
+ };
36081
+ const traverseItems = async (item) => {
33989
36082
  if (enabledIds.includes(item.id)) {
33990
36083
  const itemId = await manager.RenderItem({
33991
36084
  item: item,
33992
36085
  getters: getters,
33993
- modelSpace: contentType === ProjectViewBookmark.EContentType.WEB_3D_MODEL_SPACE
36086
+ modelSpace: contentType === ProjectViewBookmark.EContentType.WEB_3D_MODEL_SPACE,
36087
+ preferredIds: enabledIds
33994
36088
  });
33995
36089
  renderedItemIds.push(itemId);
33996
36090
  }
@@ -34000,8 +36094,11 @@ var MenuItemCreator;
34000
36094
  }
34001
36095
  }
34002
36096
  };
34003
- for (let i = 0; i < menuItems.length; i++) {
34004
- await traverseItems(menuItems[i]);
36097
+ // Tag and attenuation alternations write into the view's items, since Navigator's tag tree reads them back.
36098
+ menuItems.forEach(alternateItems);
36099
+ const styled = menuItems.map(styleItems);
36100
+ for (let i = 0; i < styled.length; i++) {
36101
+ await traverseItems(styled[i]);
34005
36102
  }
34006
36103
  }
34007
36104
  return {
@@ -39346,6 +41443,7 @@ function groupAttributes(paths, attributes) {
39346
41443
  }
39347
41444
  class WidgetInfoView extends Widget.AWidget {
39348
41445
  constructor(params) {
41446
+ var _a;
39349
41447
  super(params);
39350
41448
  this.STYLESHEET_ID = "nextspace-info-view-stylesheet";
39351
41449
  this._generateStyles();
@@ -39355,10 +41453,19 @@ class WidgetInfoView extends Widget.AWidget {
39355
41453
  this._updateInfoView();
39356
41454
  }
39357
41455
  });
41456
+ // The selected Entity is read once per selection, so a change to its simulation reads it again.
41457
+ this._simulationRemoval = (_a = this._manager.Simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
41458
+ if (this._lastSelectedId && change.entityIds.includes(this._lastSelectedId)) {
41459
+ this._lastSelectedId = null;
41460
+ this._updateInfoView();
41461
+ }
41462
+ });
39358
41463
  }
39359
41464
  Dispose() {
41465
+ var _a;
39360
41466
  super.Dispose();
39361
41467
  this._updateRemoval();
41468
+ (_a = this._simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
39362
41469
  }
39363
41470
  _generateStyles() {
39364
41471
  if (document.getElementById(this.STYLESHEET_ID)) {
@@ -39820,7 +41927,7 @@ class WidgetInfoView extends Widget.AWidget {
39820
41927
  }
39821
41928
  }
39822
41929
  async _updateInfoView() {
39823
- var _b, _c, _d, _e;
41930
+ var _a, _c, _d, _e, _f;
39824
41931
  const selectedIds = this._manager.VisualsRegister.GetSelected();
39825
41932
  if (selectedIds.length <= 0) {
39826
41933
  this._element.style.display = "none";
@@ -39842,17 +41949,20 @@ class WidgetInfoView extends Widget.AWidget {
39842
41949
  this._summary.innerHTML = "";
39843
41950
  this._imageContainer.style.display = "none";
39844
41951
  const api = this._apiGetters.GetBruceApi();
39845
- const { entity } = await Entity$1.Get({
41952
+ // A simulated Entity is shown as simulated: a stand-in as is, a patch over what the API has.
41953
+ const simulation = this._manager.Simulation;
41954
+ let entity = (_a = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(firstId)) !== null && _a !== void 0 ? _a : (await Entity$1.Get({
39846
41955
  entityId: firstId,
39847
41956
  api: api,
39848
41957
  migrated: true,
39849
41958
  expandLocation: DISPLAY_ASSEMBLY_INFO
39850
- });
41959
+ })).entity;
41960
+ entity = entity && simulation ? simulation.Resolve(firstId, entity) : entity;
39851
41961
  if (!entity) {
39852
41962
  throw (new Error("Could not get entity."));
39853
41963
  }
39854
41964
  const { entityType } = await EntityType.Get({
39855
- entityTypeId: (_b = entity.Bruce) === null || _b === void 0 ? void 0 : _b["EntityType.ID"],
41965
+ entityTypeId: (_c = entity.Bruce) === null || _c === void 0 ? void 0 : _c["EntityType.ID"],
39856
41966
  api: api
39857
41967
  });
39858
41968
  let name = Entity$1.CalculateName({
@@ -39875,7 +41985,7 @@ class WidgetInfoView extends Widget.AWidget {
39875
41985
  const defaultImage = images.length ? images[0] : null;
39876
41986
  if (defaultImage) {
39877
41987
  this._imageContainer.style.display = "flex";
39878
- const url = (_c = defaultImage.URL) !== null && _c !== void 0 ? _c : ClientFile.GetUrl({
41988
+ const url = (_d = defaultImage.URL) !== null && _d !== void 0 ? _d : ClientFile.GetUrl({
39879
41989
  fileId: defaultImage["ClientFile.ID"],
39880
41990
  api: api,
39881
41991
  viaCdn: true
@@ -39888,7 +41998,7 @@ class WidgetInfoView extends Widget.AWidget {
39888
41998
  this._summary.appendChild(detailsTitle);
39889
41999
  // TODO: Selection for what mode instead of always doing "all".
39890
42000
  const attributes = gatherAttributes(EMode.ShowAll, entity, entityType);
39891
- const groups = groupAttributes(attributes, (_e = (_d = entityType.DataSchema) === null || _d === void 0 ? void 0 : _d.Structure) !== null && _e !== void 0 ? _e : []);
42001
+ const groups = groupAttributes(attributes, (_f = (_e = entityType.DataSchema) === null || _e === void 0 ? void 0 : _e.Structure) !== null && _f !== void 0 ? _f : []);
39892
42002
  for (const group of groups) {
39893
42003
  this._generateAttrGroup(entityType, entity, group);
39894
42004
  }
@@ -42485,7 +44595,7 @@ var EntityUtils;
42485
44595
  * @param params
42486
44596
  */
42487
44597
  async function GetLocation(params) {
42488
- var _a, _b, _c, _d;
44598
+ var _a, _b, _c, _d, _e;
42489
44599
  let { api, viewer, samples, visualRegister, minimumAlt, paddingAlt } = params;
42490
44600
  if (!paddingAlt) {
42491
44601
  paddingAlt = 0;
@@ -43033,6 +45143,13 @@ var EntityUtils;
43033
45143
  recordSamples.push(sample);
43034
45144
  }
43035
45145
  }
45146
+ // A simulated Entity is where its blob says, never where the API has it.
45147
+ const simulation = EntitySimulation.Get(viewer);
45148
+ for (const sample of recordSamples) {
45149
+ if (!sample.entity && sample.entityId) {
45150
+ sample.entity = (_e = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(sample.entityId)) !== null && _e !== void 0 ? _e : sample.entity;
45151
+ }
45152
+ }
43036
45153
  // Bulk requesting Entity records.
43037
45154
  try {
43038
45155
  const toSearchIds = recordSamples.filter(x => !x.entity && x.entityId).map(x => x.entityId);
@@ -43053,7 +45170,8 @@ var EntityUtils;
43053
45170
  for (let i = 0; i < recordSamples.length; i++) {
43054
45171
  const sample = recordSamples[i];
43055
45172
  if (!sample.entity && sample.entityId) {
43056
- sample.entity = entitiesMap.get(sample.entityId);
45173
+ const read = entitiesMap.get(sample.entityId);
45174
+ sample.entity = read && simulation ? simulation.Overlay(read) : read;
43057
45175
  }
43058
45176
  }
43059
45177
  }
@@ -43067,6 +45185,11 @@ var EntityUtils;
43067
45185
  // We have to detect whatever is an Assembly Entity and expand it.
43068
45186
  try {
43069
45187
  const needsExpansion = recordSamples.filter(x => {
45188
+ var _a;
45189
+ // Expanding reads the API's record, which would replace the stand-in or undo the patch.
45190
+ if (simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob((_a = x.entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID)) {
45191
+ return false;
45192
+ }
43070
45193
  const assemblyPos = Entity$1.GetValue({
43071
45194
  entity: x.entity,
43072
45195
  path: ["Bruce", "AssemblyPosition"]
@@ -43425,6 +45548,21 @@ var EntityUtils;
43425
45548
  return Quaternion.multiply(combined, _assemblyAxisShift, new Quaternion());
43426
45549
  }
43427
45550
  EntityUtils.GetAssemblyOrientation = GetAssemblyOrientation;
45551
+ /**
45552
+ * Returns the orientation a part's model is drawn with, for its world matrix within the root's frame.
45553
+ * @param rootFrame the root's frame in the world, eg. from Transforms.headingPitchRollToFixedFrame.
45554
+ * @param world the part's matrix within the root's frame.
45555
+ */
45556
+ function GetAssemblyOrientationInFrame(rootFrame, world) {
45557
+ if (!rootFrame || !world) {
45558
+ return null;
45559
+ }
45560
+ const combined = Matrix4.multiply(rootFrame, world, new Matrix4());
45561
+ const rotation = Matrix4.getRotation(combined, new Matrix3());
45562
+ const q = Quaternion.fromRotationMatrix(rotation);
45563
+ return Quaternion.multiply(q, _assemblyAxisShift, new Quaternion());
45564
+ }
45565
+ EntityUtils.GetAssemblyOrientationInFrame = GetAssemblyOrientationInFrame;
43428
45566
  /**
43429
45567
  * Returns the position of the entity.
43430
45568
  * This is an async variant that checks for related assemblies.
@@ -43434,6 +45572,18 @@ var EntityUtils;
43434
45572
  */
43435
45573
  async function GetPosAsync(params) {
43436
45574
  let { viewer, entityId, entity, visualRegister, returnHeightRef, recordHeightRef, api } = params;
45575
+ // A simulated Entity is where its stand-in says, or where its patch puts the API's record.
45576
+ const simulation = EntitySimulation.Get(viewer);
45577
+ entity = entity !== null && entity !== void 0 ? entity : simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(entityId);
45578
+ if (!entity && entityId && (simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob(entityId))) {
45579
+ try {
45580
+ const read = (await Entity$1.Get({ api, entityId, expandLocation: false, migrated: true })).entity;
45581
+ entity = read ? simulation.Overlay(read) : null;
45582
+ }
45583
+ catch (e) {
45584
+ console.error(e);
45585
+ }
45586
+ }
43437
45587
  // We have issues where imported BRZ files have improper expandLocation=true info.
43438
45588
  // As a result we'll double-request the data.
43439
45589
  // If there is a location then we won't bother using expanded information.
@@ -53434,7 +55584,7 @@ var StyleUtils;
53434
55584
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
53435
55585
  })(StyleUtils || (StyleUtils = {}));
53436
55586
 
53437
- const VERSION = "7.6.1";
55587
+ const VERSION = "7.6.3";
53438
55588
  /**
53439
55589
  * Updates the environment instance used by bruce-cesium to one specified.
53440
55590
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
@@ -53451,5 +55601,5 @@ const getENVIRONMENT = () => {
53451
55601
  return ENVIRONMENT;
53452
55602
  };
53453
55603
 
53454
- export { VERSION, setENVIRONMENT, getENVIRONMENT, ClusterItems, PointClustering, PointClusterSize, CesiumAnimatedInOut, DisplacedSurfacePrimitive, CesiumAnimatedProperty, isStyleChanged, isOutlineChanged$1 as isOutlineChanged, StyleEffective, EntityRenderEngine, EntityRenderEngineModel3d, EntityRenderEnginePoint, extractMetadataFromFileUrl, EntityRenderEnginePolygon, EntityRenderEnginePolyline, TextureFrameSeriesAnimator, TextureValueLabels, MenuItemCreator, MenuItemManager, CesiumParabola, EntityLabel, EntityLabelGroup, LiveCursor, SharedGetters, DataSourceStaticKmlManager, DataLabRenderManager, EntitiesIdsRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, AssemblyRenderManager, GoogleSearchRenderManager, RelationsRenderManager, RenderManager, TilesetArbRenderManager, TilesetCadRenderManager, TilesetEntitiesRenderManager, TilesetGooglePhotosRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TileRenderEngine, createTileset, TilesetRenderEngine, ViewRenderEngine, VisualRegisterCuller, VisualsRegister, XGridsRenderEngine, CesiumEntityStyler, DrawingUtils, EntityUtils, MeasureUtils, StyleUtils, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_KEY, CESIUM_TIMELINE_INTERVAL_KEY, CESIUM_MODEL_SPACE_KEY, DEFAULT_LIVE_PADDING_SECONDS, DEFAULT_BRIGHTNESS_SHIFT, DEFAULT_LIGHT_INTENSITY, LIGHTING_SETTINGS_VERSION, LIGHT_INTENSITY_COMPENSATION, MAX_COMPENSATED_LIGHT_INTENSITY, ViewUtils, CesiumViewMonitor, ViewGroundArea, ViewerEventTracker, ViewerUtils, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, WidgetControlViewBar, WidgetControlViewBarSearch, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks, WidgetLeftPanelTabDashboard, EMenuItemState, WidgetLeftPanelTabMenuItems, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, ECursor, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, CreateTabIconSvg, WidgetLeftPanel, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar };
55604
+ export { VERSION, setENVIRONMENT, getENVIRONMENT, ClusterItems, PointClustering, PointClusterSize, CesiumAnimatedInOut, DisplacedSurfacePrimitive, CesiumAnimatedProperty, isStyleChanged, isOutlineChanged$1 as isOutlineChanged, StyleEffective, EntityRenderEngine, EntityRenderEngineModel3d, EntityRenderEnginePoint, extractMetadataFromFileUrl, EntityRenderEnginePolygon, EntityRenderEnginePolyline, TextureFrameSeriesAnimator, TextureValueLabels, MenuItemCreator, MenuItemManager, MenuItemStyles, CesiumParabola, EntityLabel, EntityLabelGroup, LiveCursor, SharedGetters, EntitySimulation, DataSourceStaticKmlManager, DataLabRenderManager, EntitiesIdsRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, AssemblyRenderManager, GoogleSearchRenderManager, RelationsRenderManager, RenderManager, TilesetArbRenderManager, TilesetCadRenderManager, TilesetEntitiesRenderManager, TilesetGooglePhotosRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TileRenderEngine, createTileset, TilesetRenderEngine, ViewRenderEngine, VisualRegisterCuller, VisualsRegister, XGridsRenderEngine, CesiumEntityStyler, DrawingUtils, EntityUtils, MeasureUtils, StyleUtils, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_KEY, CESIUM_TIMELINE_INTERVAL_KEY, CESIUM_MODEL_SPACE_KEY, DEFAULT_LIVE_PADDING_SECONDS, DEFAULT_BRIGHTNESS_SHIFT, DEFAULT_LIGHT_INTENSITY, LIGHTING_SETTINGS_VERSION, LIGHT_INTENSITY_COMPENSATION, MAX_COMPENSATED_LIGHT_INTENSITY, ViewUtils, CesiumViewMonitor, ViewGroundArea, ViewerEventTracker, ViewerUtils, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, WidgetControlViewBar, WidgetControlViewBarSearch, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks, WidgetLeftPanelTabDashboard, EMenuItemState, WidgetLeftPanelTabMenuItems, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, ECursor, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, CreateTabIconSvg, WidgetLeftPanel, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar };
53455
55605
  //# sourceMappingURL=bruce-cesium.es5.js.map