bruce-cesium 7.6.1 → 7.6.2

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 (87) hide show
  1. package/dist/bruce-cesium.es5.js +2031 -343
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2024 -340
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +2 -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-manager.js +83 -10
  12. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  13. package/dist/lib/rendering/relation-render-engine.js +24 -5
  14. package/dist/lib/rendering/relation-render-engine.js.map +1 -1
  15. package/dist/lib/rendering/render-managers/common/entity-label-group.js +13 -9
  16. package/dist/lib/rendering/render-managers/common/entity-label-group.js.map +1 -1
  17. package/dist/lib/rendering/render-managers/common/entity-label.js +68 -7
  18. package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
  19. package/dist/lib/rendering/render-managers/common/entity-rerender-maintain-state.js +44 -5
  20. package/dist/lib/rendering/render-managers/common/entity-rerender-maintain-state.js.map +1 -1
  21. package/dist/lib/rendering/render-managers/common/entity-simulation.js +483 -0
  22. package/dist/lib/rendering/render-managers/common/entity-simulation.js.map +1 -0
  23. package/dist/lib/rendering/render-managers/common/historic-trail.js +123 -0
  24. package/dist/lib/rendering/render-managers/common/historic-trail.js.map +1 -0
  25. package/dist/lib/rendering/render-managers/common/historic-utils.js +17 -6
  26. package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -1
  27. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js +3 -2
  28. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js.map +1 -1
  29. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +10 -2
  30. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  31. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js +3 -2
  32. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js.map +1 -1
  33. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +18 -5
  34. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  35. package/dist/lib/rendering/render-managers/entities/entity-render-manager.js +1 -1
  36. package/dist/lib/rendering/render-managers/entities/entity-render-manager.js.map +1 -1
  37. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js +745 -106
  38. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js.map +1 -1
  39. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js +23 -1
  40. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js.map +1 -1
  41. package/dist/lib/rendering/render-managers/other/relations-render-manager.js +23 -1
  42. package/dist/lib/rendering/render-managers/other/relations-render-manager.js.map +1 -1
  43. package/dist/lib/rendering/render-managers/render-manager.js.map +1 -1
  44. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +2 -0
  45. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  46. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +130 -40
  47. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  48. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +3 -0
  49. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  50. package/dist/lib/rendering/tileset-styler.js +1 -1
  51. package/dist/lib/rendering/tileset-styler.js.map +1 -1
  52. package/dist/lib/rendering/visual-register-culler.js +2 -0
  53. package/dist/lib/rendering/visual-register-culler.js.map +1 -1
  54. package/dist/lib/rendering/visuals-register.js +89 -20
  55. package/dist/lib/rendering/visuals-register.js.map +1 -1
  56. package/dist/lib/utils/entity-utils.js +43 -2
  57. package/dist/lib/utils/entity-utils.js.map +1 -1
  58. package/dist/lib/widgets/widget-info-view.js +20 -7
  59. package/dist/lib/widgets/widget-info-view.js.map +1 -1
  60. package/dist/types/bruce-cesium.d.ts +2 -1
  61. package/dist/types/rendering/cesium-animated-property.d.ts +8 -6
  62. package/dist/types/rendering/entity-gatherer.d.ts +6 -1
  63. package/dist/types/rendering/menu-item-manager.d.ts +33 -1
  64. package/dist/types/rendering/relation-render-engine.d.ts +10 -0
  65. package/dist/types/rendering/render-managers/common/entity-label-group.d.ts +3 -2
  66. package/dist/types/rendering/render-managers/common/entity-label.d.ts +4 -0
  67. package/dist/types/rendering/render-managers/common/entity-rerender-maintain-state.d.ts +20 -0
  68. package/dist/types/rendering/render-managers/common/entity-simulation.d.ts +126 -0
  69. package/dist/types/rendering/render-managers/common/historic-trail.d.ts +30 -0
  70. package/dist/types/rendering/render-managers/common/historic-utils.d.ts +4 -0
  71. package/dist/types/rendering/render-managers/entities/entities-datalab-render-manager.d.ts +2 -0
  72. package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +2 -0
  73. package/dist/types/rendering/render-managers/entities/entities-loaded-render-manager.d.ts +2 -0
  74. package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +2 -0
  75. package/dist/types/rendering/render-managers/entities/entity-render-manager.d.ts +2 -0
  76. package/dist/types/rendering/render-managers/other/assembly-render-manager.d.ts +79 -2
  77. package/dist/types/rendering/render-managers/other/legacy-relations-render-manager.d.ts +5 -0
  78. package/dist/types/rendering/render-managers/other/relations-render-manager.d.ts +5 -0
  79. package/dist/types/rendering/render-managers/render-manager.d.ts +4 -0
  80. package/dist/types/rendering/render-managers/tilesets/tileset-arb-render-manager.d.ts +3 -0
  81. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +11 -1
  82. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +3 -0
  83. package/dist/types/rendering/tileset-styler.d.ts +2 -0
  84. package/dist/types/rendering/visuals-register.d.ts +13 -0
  85. package/dist/types/utils/entity-utils.d.ts +6 -0
  86. package/dist/types/widgets/widget-info-view.d.ts +1 -0
  87. 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, Entity, ConstantPositionProperty, HeightReference, Color, DistanceDisplayCondition, ColorMaterialProperty, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, ColorBlendMode, HeadingPitchRoll, Math as Math$1, Transforms, ShadowMode, Cartesian3, ClassificationType, Model, JulianDate, Quaternion, Matrix4, HorizontalOrigin, VerticalOrigin, 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, FeatureDetection, Matrix3, NearFarScalar, KmlDataSource, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileColorBlendMode, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Cesium3DTileset, OrthographicFrustum, EasingFunction, Intersect, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, CustomDataSource, GeometryInstance, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, 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, Bounds, ClientFileValueMap, Color as Color$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, DataLab, DelayQueue, AccountConcept, BruceApi, EntityRelation, RecordChangeFeed, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, ProjectViewBookmarkGroup, 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;
@@ -15794,6 +16430,14 @@ var EntitiesRenderManager;
15794
16430
  */
15795
16431
  this.renderAsIndividualsChain = Promise.resolve();
15796
16432
  const { viewer, apiGetter, monitor, item, register: visualsManager, sharedGetters, onSeriesDiscovered } = params;
16433
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation, () => {
16434
+ // Read at the scene's time only when the item follows history, the same choice its reads make.
16435
+ const bruceEntity = this.item.BruceEntity;
16436
+ if (!(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historic) && !(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historicAttrKey)) {
16437
+ return null;
16438
+ }
16439
+ return { timeMs: JulianDate.toDate(this.viewer.clock.currentTime).getTime(), attrKey: bruceEntity.historicAttrKey };
16440
+ });
15797
16441
  this.viewer = viewer;
15798
16442
  this.sharedGetters = sharedGetters;
15799
16443
  this.monitor = monitor;
@@ -15807,7 +16451,8 @@ var EntitiesRenderManager;
15807
16451
  getApi: () => this.apiGetter.getApi(),
15808
16452
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
15809
16453
  // A second either side, to take in records sitting exactly on the boundary.
15810
- paddingMs: 1000
16454
+ paddingMs: 1000,
16455
+ simulation: params.simulation
15811
16456
  });
15812
16457
  this.useGeojson = item.renderAsGeojson == true;
15813
16458
  if (item.enableClustering) {
@@ -15988,8 +16633,9 @@ var EntitiesRenderManager;
15988
16633
  return;
15989
16634
  }
15990
16635
  this.updateAllowedTypeIDs(data.entityTypeSources, data.entities);
16636
+ // The getter answers from the API, so a simulated Entity in its answer is swapped for its blob.
15991
16637
  if (isTagItem) {
15992
- this.distributeForRender(data.entities.filter((entity) => {
16638
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities.filter((entity) => {
15993
16639
  let entityTags = entity.Bruce["Layer.ID"];
15994
16640
  if (!entityTags) {
15995
16641
  entityTags = [];
@@ -15997,10 +16643,10 @@ var EntitiesRenderManager;
15997
16643
  return entityTags.some((tag) => {
15998
16644
  return tagsToRender.indexOf(tag) !== -1;
15999
16645
  });
16000
- }));
16646
+ })));
16001
16647
  }
16002
16648
  else {
16003
- this.distributeForRender(data.entities);
16649
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities));
16004
16650
  }
16005
16651
  });
16006
16652
  // Cesium itself handles single-visibility ranges very well.
@@ -16040,6 +16686,10 @@ var EntitiesRenderManager;
16040
16686
  // Remove regos directly.
16041
16687
  if (event.action === "D") {
16042
16688
  for (const entityId of affectedIds) {
16689
+ // Deleted in the API, but drawn from its simulation until that is cleared.
16690
+ if (this.reRenderState.isSimulated(entityId)) {
16691
+ continue;
16692
+ }
16043
16693
  this.visualsManager.RemoveRegos({
16044
16694
  entityId,
16045
16695
  menuItemId: this.item.id,
@@ -17169,7 +17819,6 @@ var EntitiesLoadedRenderManager;
17169
17819
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
17170
17820
  // Many things are not supported when this is enabled.
17171
17821
  this.useGeojson = false;
17172
- this.reRenderState = new EntityReRenderMaintainState();
17173
17822
  /**
17174
17823
  * Our default path.
17175
17824
  * We render each entity individually.
@@ -17179,6 +17828,7 @@ var EntitiesLoadedRenderManager;
17179
17828
  */
17180
17829
  this.renderAsIndividualsChain = Promise.resolve();
17181
17830
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
17831
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
17182
17832
  this.viewer = viewer;
17183
17833
  this.apiGetter = apiGetter;
17184
17834
  this.monitor = monitor;
@@ -17301,7 +17951,8 @@ var EntitiesLoadedRenderManager;
17301
17951
  continue;
17302
17952
  }
17303
17953
  }
17304
- this.onGetterUpdate(entities);
17954
+ // The item carries its own Entities, so a simulated one among them is swapped for its blob.
17955
+ this.onGetterUpdate(this.reRenderState.withSimulated(entities));
17305
17956
  });
17306
17957
  this.getter.Start();
17307
17958
  }
@@ -17948,8 +18599,15 @@ var EntitiesIdsRenderManager;
17948
18599
  this.renderQueueActive = false;
17949
18600
  this.renderQueue = [];
17950
18601
  this.renderQueueForceFlags = [];
17951
- this.reRenderState = new EntityReRenderMaintainState();
17952
18602
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
18603
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation, () => {
18604
+ // Read at the scene's time only when the item follows history, the same choice its reads make.
18605
+ const bruceEntity = this.item.BruceEntity;
18606
+ if (!(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historic) && !(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historicAttrKey)) {
18607
+ return null;
18608
+ }
18609
+ return { timeMs: JulianDate.toDate(this.viewer.clock.currentTime).getTime(), attrKey: bruceEntity.historicAttrKey };
18610
+ });
17953
18611
  this.sharedHistoric = params.sharedHistoric;
17954
18612
  this.viewer = viewer;
17955
18613
  this.apiGetter = apiGetter;
@@ -17962,7 +18620,8 @@ var EntitiesIdsRenderManager;
17962
18620
  getApi: () => this.apiGetter.getApi(),
17963
18621
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
17964
18622
  // Padding either side of a requested range, for clock desync between us and the server.
17965
- paddingMs: 15000
18623
+ paddingMs: 15000,
18624
+ simulation: params.simulation
17966
18625
  });
17967
18626
  if (this.item.enableClustering) {
17968
18627
  this.clustering = new PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
@@ -18424,9 +19083,9 @@ var EntityRenderManager;
18424
19083
  this.getter = null;
18425
19084
  this.getterSub = null;
18426
19085
  this.disposed = false;
18427
- this.reRenderState = new EntityReRenderMaintainState();
18428
19086
  this.renderEntityChain = Promise.resolve();
18429
19087
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
19088
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
18430
19089
  this.viewer = viewer;
18431
19090
  this.apiGetter = apiGetter;
18432
19091
  this.monitor = monitor;
@@ -18700,6 +19359,122 @@ var CodeLogicExceptions;
18700
19359
  ];
18701
19360
  })(CodeLogicExceptions || (CodeLogicExceptions = {}));
18702
19361
 
19362
+ /**
19363
+ * The line drawn through where a moving thing has been, from its history, as a whole path or a fading trail.
19364
+ */
19365
+ var HistoricTrail;
19366
+ (function (HistoricTrail) {
19367
+ const BANDS = 12;
19368
+ const SAMPLES = 96;
19369
+ const WIDTH_FAR = 1;
19370
+ const ALPHA_FAR = 0.05;
19371
+ // Above 1, so width and alpha hold near the head and give way further back.
19372
+ const WIDTH_CURVE = 1.4;
19373
+ const ALPHA_CURVE = 1.25;
19374
+ // How strongly a line is drawn where it passes behind the globe, against its own alpha.
19375
+ const BEHIND_ALPHA = 0.35;
19376
+ /**
19377
+ * The trail length a menu item asks for in its historicTrackSeconds, or null for the whole path.
19378
+ */
19379
+ function GetTrailSeconds(item) {
19380
+ const seconds = Number(item === null || item === void 0 ? void 0 : item.historicTrackSeconds);
19381
+ return seconds > 0 ? seconds : null;
19382
+ }
19383
+ HistoricTrail.GetTrailSeconds = GetTrailSeconds;
19384
+ class Trail {
19385
+ constructor(params) {
19386
+ this.entities = [];
19387
+ this.memoTime = null;
19388
+ this.memo = [];
19389
+ this.viewer = params.viewer;
19390
+ const trailing = params.trailSeconds > 0 && Boolean(params.positionAt);
19391
+ if (!trailing) {
19392
+ this.add(params, params.color, params.width, () => { var _a; return (_a = params.getSeries()) !== null && _a !== void 0 ? _a : []; });
19393
+ return;
19394
+ }
19395
+ for (let band = 0; band < BANDS; band++) {
19396
+ // 0 at the head, 1 at the far end.
19397
+ const age = band / (BANDS - 1);
19398
+ const width = params.width - (params.width - WIDTH_FAR) * Math.pow(age, WIDTH_CURVE);
19399
+ const alpha = params.color.alpha - (params.color.alpha - ALPHA_FAR) * Math.pow(age, ALPHA_CURVE);
19400
+ this.add(params, params.color.withAlpha(alpha), Math.max(WIDTH_FAR, width), (time) => {
19401
+ return this.slice(this.sample(params, time), band);
19402
+ });
19403
+ }
19404
+ }
19405
+ /*
19406
+ * One polyline of the trail, its positions read from the scene's time.
19407
+ */
19408
+ add(params, color, width, positions) {
19409
+ const entity = this.viewer.entities.add({
19410
+ id: ObjectUtils.UId(15),
19411
+ polyline: {
19412
+ positions: new CallbackProperty((time) => positions(time), false),
19413
+ width: width,
19414
+ material: color,
19415
+ clampToGround: params.clampToGround,
19416
+ // A path through the air follows no ellipsoid, so it is drawn as the straight runs it holds.
19417
+ arcType: params.clampToGround ? ArcType.GEODESIC : ArcType.NONE,
19418
+ // Drawn dimmer behind the globe rather than winking in and out against the terrain along the limb.
19419
+ depthFailMaterial: params.clampToGround ? undefined : color.withAlpha(color.alpha * BEHIND_ALPHA),
19420
+ zIndex: 0,
19421
+ classificationType: params.clampToGround ? ClassificationType.BOTH : undefined
19422
+ }
19423
+ });
19424
+ entity._noEdit = true;
19425
+ entity._noSelect = true;
19426
+ CesiumEntityStyler.SetDefaultColor({
19427
+ color: color,
19428
+ entity: entity,
19429
+ viewer: this.viewer,
19430
+ override: true,
19431
+ requestRender: false
19432
+ });
19433
+ this.entities.push(entity);
19434
+ }
19435
+ /*
19436
+ * The trail's positions at a time, newest last, kept for that time so every band reads the same set.
19437
+ */
19438
+ sample(params, time) {
19439
+ var _a;
19440
+ if (this.memoTime && JulianDate.equals(this.memoTime, time)) {
19441
+ return this.memo;
19442
+ }
19443
+ this.memoTime = JulianDate.clone(time, (_a = this.memoTime) !== null && _a !== void 0 ? _a : new JulianDate());
19444
+ const nowMs = JulianDate.toDate(time).getTime();
19445
+ const spanMs = params.trailSeconds * 1000;
19446
+ const points = [];
19447
+ for (let i = 0; i <= SAMPLES; i++) {
19448
+ const pos3d = params.positionAt(nowMs - spanMs + spanMs * i / SAMPLES);
19449
+ if (pos3d) {
19450
+ points.push(pos3d);
19451
+ }
19452
+ }
19453
+ this.memo = points;
19454
+ return points;
19455
+ }
19456
+ /*
19457
+ * A band's run of the samples, counted back from the head, sharing its end point with the next band.
19458
+ */
19459
+ slice(points, band) {
19460
+ if (points.length < 2) {
19461
+ return [];
19462
+ }
19463
+ const per = (points.length - 1) / BANDS;
19464
+ const end = points.length - 1 - Math.round(band * per);
19465
+ const start = points.length - 1 - Math.round((band + 1) * per);
19466
+ return start < end ? points.slice(Math.max(0, start), end + 1) : [];
19467
+ }
19468
+ Dispose() {
19469
+ for (const entity of this.entities) {
19470
+ RemoveCEntityFromScene(this.viewer, entity);
19471
+ }
19472
+ this.entities = [];
19473
+ }
19474
+ }
19475
+ HistoricTrail.Trail = Trail;
19476
+ })(HistoricTrail || (HistoricTrail = {}));
19477
+
18703
19478
  class EntityGatherer {
18704
19479
  get OnQueueProgress() {
18705
19480
  if (!this._onQueueProgress) {
@@ -18707,7 +19482,11 @@ class EntityGatherer {
18707
19482
  }
18708
19483
  return this._onQueueProgress;
18709
19484
  }
18710
- constructor(api, viewer, emitEntities) {
19485
+ /**
19486
+ * @param simulation what callers simulate in place of API data, used where this gatherer has no override.
19487
+ */
19488
+ constructor(api, viewer, emitEntities, simulation) {
19489
+ this.simulation = simulation;
18711
19490
  this.disposed = false;
18712
19491
  this.expandSources = false;
18713
19492
  this.historic = null;
@@ -19284,15 +20063,19 @@ class EntityGatherer {
19284
20063
  }
19285
20064
  }
19286
20065
  }
20066
+ // A historic read is as at the scene's time, and so is any simulated record laid over it.
20067
+ const rTimeMs = new Date(rTime).getTime();
20068
+ const atPoint = (entity) => this.historic && this.simulation ? this.simulation.ApplyAt(entity, rTimeMs) : entity;
19287
20069
  for (const entityId of requestedBatch || []) {
20070
+ // A caller's own override is an unsaved edit, newer than any simulation, so it is used as is.
19288
20071
  const override = this.entityDataOverrides.get(entityId);
19289
20072
  if (override) {
19290
20073
  resolvedEntities.push(override);
19291
20074
  continue;
19292
20075
  }
19293
- const found = foundById.get(entityId);
20076
+ const found = this.simulation ? this.simulation.Resolve(entityId, foundById.get(entityId)) : foundById.get(entityId);
19294
20077
  if (found) {
19295
- resolvedEntities.push(found);
20078
+ resolvedEntities.push(atPoint(found));
19296
20079
  continue;
19297
20080
  }
19298
20081
  resolvedEntities.push(this.createFallbackEntity(entityId));
@@ -19656,7 +20439,7 @@ class TilesetStyler {
19656
20439
  this.styleTilesetFeatureFullData(feature, entity, eTags);
19657
20440
  }
19658
20441
  }
19659
- });
20442
+ }, params.simulation);
19660
20443
  // Only Entities Sets bake coordinates into their features, so for any other source this
19661
20444
  // resolves nothing and the gather stays in insertion order.
19662
20445
  this.entityGatherer.SetViewMonitor(this.monitor);
@@ -21186,6 +21969,9 @@ var TilesetCadRenderManager;
21186
21969
  return this.modelTreeUpdate;
21187
21970
  }
21188
21971
  constructor(params) {
21972
+ this.simulationRemoval = null;
21973
+ // Where the tileset stands by its own record, so a cleared simulation puts it back without a refetch.
21974
+ this.restCoords = null;
21189
21975
  this.disposed = false;
21190
21976
  this.modelSpace = false;
21191
21977
  this.cTileset = null;
@@ -21223,7 +22009,7 @@ var TilesetCadRenderManager;
21223
22009
  this.historicAnimation = null;
21224
22010
  this.historicGatherer = null;
21225
22011
  // The trail through the root's own movement, which describes the whole assembly.
21226
- this.trackEntity = null;
22012
+ this.trail = null;
21227
22013
  this.historicPossesLoadingProm = null;
21228
22014
  // Queue of loaded in features that we haven't processed yet.
21229
22015
  this.featureQueue = [];
@@ -21237,6 +22023,7 @@ var TilesetCadRenderManager;
21237
22023
  this.featurePropCache = new Map();
21238
22024
  this.featurePropsChecked = 0;
21239
22025
  const { viewer, register: visualsManager, getters, item, modelSpace, initQueue } = params;
22026
+ this.simulation = params.simulation;
21240
22027
  this.viewer = viewer;
21241
22028
  this.getters = getters;
21242
22029
  this.item = item;
@@ -21401,7 +22188,7 @@ var TilesetCadRenderManager;
21401
22188
  historicPoint: this.viewer.clock.currentTime.toString(),
21402
22189
  api: api
21403
22190
  });
21404
- coords = coordsFromHistoricEntity(root);
22191
+ coords = coordsFromHistoricEntity(this.atClock(root));
21405
22192
  }
21406
22193
  catch (e) {
21407
22194
  console.error(e);
@@ -21442,6 +22229,10 @@ var TilesetCadRenderManager;
21442
22229
  this.doDispose();
21443
22230
  return;
21444
22231
  }
22232
+ this.restCoords = coords;
22233
+ if (this.simulatedRootCoords()) {
22234
+ this.applyCoords(tileset, this.simulatedRootCoords());
22235
+ }
21445
22236
  this.viewer.scene.requestRender();
21446
22237
  TilesetRenderEngine.OnTilesetReady(this.cTileset).then(() => {
21447
22238
  var _a, _b, _c;
@@ -21461,6 +22252,7 @@ var TilesetCadRenderManager;
21461
22252
  }
21462
22253
  this.onCTilesetLoad();
21463
22254
  this.styler.Init({
22255
+ simulation: this.simulation,
21464
22256
  viewer: this.viewer,
21465
22257
  // Default API instead of the account one.
21466
22258
  // Records are driven by the account loading it instead of the source account.
@@ -21476,6 +22268,7 @@ var TilesetCadRenderManager;
21476
22268
  });
21477
22269
  this.viewer.scene.requestRender();
21478
22270
  // Monitor scene time changes and update the root Entity.
22271
+ this.followSimulation(tileset);
21479
22272
  this.viewerDateTimeSub(tileset);
21480
22273
  }
21481
22274
  catch (e) {
@@ -21531,7 +22324,7 @@ var TilesetCadRenderManager;
21531
22324
  */
21532
22325
  applyRootTrack() {
21533
22326
  var _a;
21534
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
22327
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
21535
22328
  return;
21536
22329
  }
21537
22330
  const trackStyle = EntityRenderEngine.GetTrackStyle({
@@ -21541,26 +22334,14 @@ var TilesetCadRenderManager;
21541
22334
  if (!trackStyle) {
21542
22335
  return;
21543
22336
  }
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,
22337
+ // The series has no position at an arbitrary time, so this draws the whole path rather than a trail.
22338
+ this.trail = new HistoricTrail.Trail({
21561
22339
  viewer: this.viewer,
21562
- override: true,
21563
- requestRender: false
22340
+ color: trackStyle.color,
22341
+ width: trackStyle.width,
22342
+ clampToGround: EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
22343
+ == HeightReference.CLAMP_TO_GROUND,
22344
+ getSeries: () => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; }
21564
22345
  });
21565
22346
  }
21566
22347
  /**
@@ -21613,7 +22394,7 @@ var TilesetCadRenderManager;
21613
22394
  }
21614
22395
  // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
21615
22396
  PrepareHandoff() {
21616
- var _a, _b, _c, _d;
22397
+ var _a, _b, _c, _d, _e;
21617
22398
  if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
21618
22399
  return null;
21619
22400
  }
@@ -21631,9 +22412,11 @@ var TilesetCadRenderManager;
21631
22412
  this.handoffSourceMenuItemId = null;
21632
22413
  // Not transferred, the receiving manager sets up its own fresh copy if needed.
21633
22414
  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);
22415
+ (_b = this.simulationRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
22416
+ this.simulationRemoval = null;
22417
+ (_c = this.styler) === null || _c === void 0 ? void 0 : _c.Dispose();
22418
+ (_d = this.tileLoadRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
22419
+ (_e = this.tileUnloadRemoval) === null || _e === void 0 ? void 0 : _e.call(this);
21637
22420
  this.tileLoadRemoval = null;
21638
22421
  this.tileUnloadRemoval = null;
21639
22422
  const payload = {
@@ -21654,7 +22437,7 @@ var TilesetCadRenderManager;
21654
22437
  // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
21655
22438
  // Then re-parents every already-registered rego to this menu item and re-runs styling.
21656
22439
  AdoptHandoff(payload) {
21657
- var _a, _b, _c, _d;
22440
+ var _a, _b, _c, _d, _e;
21658
22441
  this.cTileset = payload.cTileset;
21659
22442
  this.tileset = payload.tileset;
21660
22443
  this.rootId = payload.rootId;
@@ -21685,6 +22468,7 @@ var TilesetCadRenderManager;
21685
22468
  // Same visuals, same registrations so just re-keyed onto this menu item.
21686
22469
  this.onCTilesetLoad();
21687
22470
  this.styler.Init({
22471
+ simulation: this.simulation,
21688
22472
  viewer: this.viewer,
21689
22473
  api: this.getters.GetBruceApi(),
21690
22474
  cTileset: this.cTileset,
@@ -21718,7 +22502,9 @@ var TilesetCadRenderManager;
21718
22502
  }
21719
22503
  });
21720
22504
  this.viewer.scene.requestRender();
21721
- if (((_d = this.item.BruceEntity) === null || _d === void 0 ? void 0 : _d.historic) && this.tileset) {
22505
+ if (this.tileset) {
22506
+ this.restCoords = (_d = this.restCoords) !== null && _d !== void 0 ? _d : (_e = this.cTileset) === null || _e === void 0 ? void 0 : _e._bruceCoords;
22507
+ this.followSimulation(this.tileset);
21722
22508
  this.viewerDateTimeSub(this.tileset);
21723
22509
  }
21724
22510
  }
@@ -22306,7 +23092,7 @@ var TilesetCadRenderManager;
22306
23092
  this.doDispose();
22307
23093
  }
22308
23094
  doDispose() {
22309
- var _a, _b, _c;
23095
+ var _a, _b, _c, _d;
22310
23096
  if (this.featureQueueInterval) {
22311
23097
  clearInterval(this.featureQueueInterval);
22312
23098
  this.featureQueueInterval = null;
@@ -22341,6 +23127,8 @@ var TilesetCadRenderManager;
22341
23127
  this.handoffSourceMenuItemId = null;
22342
23128
  }
22343
23129
  this.viewerDateTimeDispose();
23130
+ (_d = this.simulationRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
23131
+ this.simulationRemoval = null;
22344
23132
  }
22345
23133
  async ReRender(params) {
22346
23134
  let { entityIds, force, entities, maintain } = params;
@@ -22366,8 +23154,11 @@ var TilesetCadRenderManager;
22366
23154
  * @param tileset
22367
23155
  */
22368
23156
  viewerDateTimeSub(tileset) {
22369
- var _a;
22370
- if (!((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic) || this.viewerDateTimeChangeRemoval) {
23157
+ var _a, _b;
23158
+ // An item that is not historic still follows the root's simulated records, once there are some.
23159
+ const historic = Boolean((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic);
23160
+ const simulated = Boolean(this.rootId && ((_b = this.simulation) === null || _b === void 0 ? void 0 : _b.GetRecords(this.rootId).length));
23161
+ if ((!historic && !simulated) || this.historicGatherer || this.viewerDateTimeChangeRemoval) {
22371
23162
  return;
22372
23163
  }
22373
23164
  const accountId = this.accountIdForRecords();
@@ -22396,6 +23187,8 @@ var TilesetCadRenderManager;
22396
23187
  // Time padding in milliseconds (15 seconds).
22397
23188
  // Helps account for desync between client and server.
22398
23189
  paddingMs: 15000,
23190
+ simulation: this.simulation,
23191
+ simulatedOnly: !historic,
22399
23192
  onRecords: (recordsByIds) => {
22400
23193
  var _a;
22401
23194
  const records = recordsByIds[this.rootId];
@@ -22423,6 +23216,7 @@ var TilesetCadRenderManager;
22423
23216
  */
22424
23217
  const getInitialPosses = async () => {
22425
23218
  this.historicPossesLoadingProm = new Promise(async (res) => {
23219
+ var _a;
22426
23220
  try {
22427
23221
  if (this.disposed) {
22428
23222
  res(false);
@@ -22435,11 +23229,16 @@ var TilesetCadRenderManager;
22435
23229
  return;
22436
23230
  }
22437
23231
  // Also request the current position in case its not in the range.
22438
- const { entity } = await Entity$1.Get({
23232
+ // A simulated root, or an item that is not historic, has no current record to ask the API for.
23233
+ if (!historic || ((_a = this.simulation) === null || _a === void 0 ? void 0 : _a.HasEntity(this.rootId))) {
23234
+ res(positions);
23235
+ return;
23236
+ }
23237
+ const entity = this.atClock((await Entity$1.Get({
22439
23238
  entityId: this.rootId,
22440
23239
  historicPoint: this.viewer.clock.currentTime.toString(),
22441
23240
  api: api
22442
- });
23241
+ })).entity);
22443
23242
  if (this.disposed || !entity) {
22444
23243
  res(false);
22445
23244
  return;
@@ -22519,14 +23318,15 @@ var TilesetCadRenderManager;
22519
23318
  }
22520
23319
  });
22521
23320
  this.viewerDateTimeChangeRemoval = () => {
23321
+ var _a, _b;
22522
23322
  if (this.historicAnimation) {
22523
23323
  this.historicAnimation.stop();
22524
23324
  this.historicAnimation = null;
22525
23325
  }
22526
- if (this.trackEntity) {
22527
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
22528
- this.trackEntity = null;
22529
- }
23326
+ (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
23327
+ this.historicGatherer = null;
23328
+ (_b = this.trail) === null || _b === void 0 ? void 0 : _b.Dispose();
23329
+ this.trail = null;
22530
23330
  if (clockTickRemoval) {
22531
23331
  clockTickRemoval();
22532
23332
  clockTickRemoval = null;
@@ -22534,10 +23334,84 @@ var TilesetCadRenderManager;
22534
23334
  };
22535
23335
  });
22536
23336
  }
22537
- viewerDateTimeDispose() {
23337
+ /*
23338
+ * Keeps the tileset in line with what is simulated for its root Entity.
23339
+ */
23340
+ followSimulation(tileset) {
23341
+ if (this.simulationRemoval || !this.simulation) {
23342
+ return;
23343
+ }
23344
+ this.simulationRemoval = this.simulation.OnChanged.Subscribe((change) => {
23345
+ var _a;
23346
+ if (this.disposed || !this.rootId || !change.entityIds.includes(this.rootId)) {
23347
+ return;
23348
+ }
23349
+ if (change.records) {
23350
+ this.regatherRoot(tileset);
23351
+ }
23352
+ // A simulated root is where the tileset stands outside its history; recorded positions still win.
23353
+ if (change.entities && !((_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.getDateRange().minDate)) {
23354
+ this.applyRootRest(tileset);
23355
+ }
23356
+ });
23357
+ }
23358
+ /*
23359
+ * Re-reads the root's history after its simulated records changed, following the clock if it was not.
23360
+ */
23361
+ async regatherRoot(tileset) {
23362
+ var _a;
23363
+ if (!this.historicGatherer) {
23364
+ this.viewerDateTimeSub(tileset);
23365
+ return;
23366
+ }
23367
+ await this.historicPossesLoadingProm;
23368
+ const { recordsByIds } = await this.historicGatherer.Gather([{ Bruce: { ID: this.rootId } }]);
23369
+ if (this.disposed) {
23370
+ return;
23371
+ }
23372
+ const posses = Manager.HistoricPossesFor({ viewer: this.viewer, item: this.item, records: recordsByIds[this.rootId] || [] });
23373
+ (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.replacePositions(posses);
23374
+ if (!posses.length) {
23375
+ this.applyRootRest(tileset);
23376
+ }
23377
+ }
23378
+ /*
23379
+ * The root as read at the scene's time, with any simulated record at that time laid over it.
23380
+ */
23381
+ atClock(root) {
23382
+ return this.simulation && root
23383
+ ? this.simulation.ApplyAt(root, JulianDate.toDate(this.viewer.clock.currentTime).getTime())
23384
+ : root;
23385
+ }
23386
+ /*
23387
+ * Stands the tileset where its root is outside history: the simulated root, else the root's own record.
23388
+ */
23389
+ applyRootRest(tileset) {
22538
23390
  var _a;
23391
+ this.applyCoords(tileset, (_a = this.simulatedRootCoords()) !== null && _a !== void 0 ? _a : this.restCoords);
23392
+ }
23393
+ /*
23394
+ * Where a simulated root puts the tileset, or null; a patch only places it when it names a whole location.
23395
+ */
23396
+ simulatedRootCoords() {
23397
+ var _a, _b;
23398
+ const simulation = this.rootId ? this.simulation : null;
23399
+ 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);
23400
+ const location = (_b = root === null || root === void 0 ? void 0 : root.Bruce) === null || _b === void 0 ? void 0 : _b.Location;
23401
+ if ((location === null || location === void 0 ? void 0 : location.latitude) == null || (location === null || location === void 0 ? void 0 : location.longitude) == null) {
23402
+ return null;
23403
+ }
23404
+ const pos3d = Cartesian3.fromDegrees(+location.longitude, +location.latitude, EnsureNumber(location.altitude, 0));
23405
+ const transform = root.Bruce.Transform;
23406
+ 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);
23407
+ }
23408
+ viewerDateTimeDispose() {
23409
+ var _a, _b;
22539
23410
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
22540
23411
  this.viewerDateTimeChangeRemoval = null;
23412
+ // Made before the first positions land, so it can outlive a removal that was never set.
23413
+ (_b = this.historicGatherer) === null || _b === void 0 ? void 0 : _b.Dispose();
23414
+ this.historicGatherer = null;
22541
23415
  }
22542
23416
  /**
22543
23417
  * Updates the Tileset with latest coordinates.
@@ -23680,11 +24554,11 @@ var DataLabRenderManager;
23680
24554
  this.viewMonitorRemoval = null;
23681
24555
  this.renderQueue = [];
23682
24556
  this.renderQueueInterval = null;
23683
- this.reRenderState = new EntityReRenderMaintainState();
23684
24557
  this.zoomControl = [];
23685
24558
  this.queryLoadId = 0;
23686
24559
  this.renderDataLabEntitiesChain = Promise.resolve();
23687
24560
  const { viewer, item, apiGetter, monitor, visualsRegister } = params;
24561
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
23688
24562
  this.viewer = viewer;
23689
24563
  this.item = item;
23690
24564
  this.apiGetter = apiGetter;
@@ -23781,7 +24655,8 @@ var DataLabRenderManager;
23781
24655
  if (!((_a = data === null || data === void 0 ? void 0 : data.entities) === null || _a === void 0 ? void 0 : _a.length)) {
23782
24656
  return;
23783
24657
  }
23784
- this.distributeForRender(data.entities);
24658
+ // The getter answers from the API, so a simulated Entity in its answer is swapped for its blob.
24659
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities));
23785
24660
  });
23786
24661
  }
23787
24662
  unsetGetter() {
@@ -25532,6 +26407,7 @@ var TilesetEntitiesRenderManager;
25532
26407
  this.featurePropCache = new Map();
25533
26408
  this.featurePropsChecked = 0;
25534
26409
  const { viewer, register: visualsManager, getters: apiGetter, item, initQueue, monitor } = params;
26410
+ this.simulation = params.simulation;
25535
26411
  this.monitor = monitor;
25536
26412
  this.viewer = viewer;
25537
26413
  this.getters = apiGetter;
@@ -25632,6 +26508,7 @@ var TilesetEntitiesRenderManager;
25632
26508
  if (Manager.NeedsStyler(this.item)) {
25633
26509
  const api = this.getters.GetBruceApi();
25634
26510
  this.styler.Init({
26511
+ simulation: this.simulation,
25635
26512
  viewer: this.viewer,
25636
26513
  api: api,
25637
26514
  cTileset: this.cTileset,
@@ -25989,6 +26866,7 @@ var TilesetEntitiesRenderManager;
25989
26866
  const needsStyler = Manager.NeedsStyler(this.item);
25990
26867
  if (needsStyler) {
25991
26868
  this.styler.Init({
26869
+ simulation: this.simulation,
25992
26870
  viewer: this.viewer,
25993
26871
  api: this.getters.GetBruceApi(),
25994
26872
  cTileset: this.cTileset,
@@ -26680,6 +27558,7 @@ var TilesetArbRenderManager;
26680
27558
  // LCC object if we're using XGrids to render LCC data.
26681
27559
  this.lccObject = null;
26682
27560
  this.viewer = params.viewer;
27561
+ this.simulation = params.simulation;
26683
27562
  this.getters = params.getters;
26684
27563
  this.visualsManager = params.register;
26685
27564
  // We modify the object due to a lot of backwards compatibility.
@@ -26914,6 +27793,7 @@ var TilesetArbRenderManager;
26914
27793
  this.item.Type == MenuItem.EType.IonTileset) {
26915
27794
  const api = this.getters.GetBruceApi();
26916
27795
  this.styler.Init({
27796
+ simulation: this.simulation,
26917
27797
  viewer: this.viewer,
26918
27798
  api: api,
26919
27799
  cTileset: this.cTileset,
@@ -27255,14 +28135,32 @@ var RelationsRenderManager;
27255
28135
  }
27256
28136
  ;
27257
28137
  constructor(params) {
28138
+ var _a, _b;
27258
28139
  // If true, this menu item is disposed and should not be used.
27259
28140
  // Any rendering will be ignored.
27260
28141
  this.disposed = false;
28142
+ this.simulationRemoval = null;
28143
+ this.FollowsSimulation = true;
27261
28144
  const { apiGetter, item, register, viewer } = params;
27262
28145
  this.apiGetter = apiGetter;
27263
28146
  this.item = item;
27264
28147
  this.register = register;
27265
28148
  this.viewer = viewer;
28149
+ this.simulation = params.simulation;
28150
+ // Ends are not this item's own Entities, so it watches the store itself rather than wait to be asked.
28151
+ this.simulationRemoval = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
28152
+ if (this.disposed) {
28153
+ return;
28154
+ }
28155
+ const relations = RelationRenderEngine.TakeRelationsTouching({
28156
+ visualRegister: this.register,
28157
+ menuItemId: this.item.id,
28158
+ entityIds: change.entityIds
28159
+ });
28160
+ if (relations.length) {
28161
+ this.onGetterUpdate(relations);
28162
+ }
28163
+ })) !== null && _b !== void 0 ? _b : null;
27266
28164
  }
27267
28165
  /**
27268
28166
  * Starts rendering the menu item.
@@ -27331,10 +28229,13 @@ var RelationsRenderManager;
27331
28229
  return items;
27332
28230
  }
27333
28231
  Dispose() {
28232
+ var _a;
27334
28233
  if (this.disposed) {
27335
28234
  return;
27336
28235
  }
27337
28236
  this.disposed = true;
28237
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28238
+ this.simulationRemoval = null;
27338
28239
  this.register.RemoveRegos({
27339
28240
  menuItemId: this.item.id
27340
28241
  });
@@ -27573,7 +28474,8 @@ var RelationsRenderManager;
27573
28474
  menuItemId: this.item.id,
27574
28475
  relations: relations,
27575
28476
  viewer: this.viewer,
27576
- visualRegister: this.register
28477
+ visualRegister: this.register,
28478
+ simulation: this.simulation
27577
28479
  });
27578
28480
  if (this.disposed) {
27579
28481
  this.register.RemoveRegos({
@@ -27856,9 +28758,12 @@ var LegacyRelationsRenderManager;
27856
28758
  }
27857
28759
  ;
27858
28760
  constructor(params) {
28761
+ var _a, _b;
27859
28762
  // If true, this menu item is disposed and should not be used.
27860
28763
  // Any rendering will be ignored.
27861
28764
  this.disposed = false;
28765
+ this.simulationRemoval = null;
28766
+ this.FollowsSimulation = true;
27862
28767
  // Record of the currently drawn entity IDs.
27863
28768
  // This is to avoid re-queueing already drawn entities when Init is called multiple times.
27864
28769
  this.drawnIds = [];
@@ -27867,6 +28772,21 @@ var LegacyRelationsRenderManager;
27867
28772
  this.item = item;
27868
28773
  this.register = register;
27869
28774
  this.viewer = viewer;
28775
+ this.simulation = params.simulation;
28776
+ // Ends are not this item's own Entities, so it watches the store itself rather than wait to be asked.
28777
+ this.simulationRemoval = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
28778
+ if (this.disposed) {
28779
+ return;
28780
+ }
28781
+ const relations = RelationRenderEngine.TakeRelationsTouching({
28782
+ visualRegister: this.register,
28783
+ menuItemId: this.item.id,
28784
+ entityIds: change.entityIds
28785
+ });
28786
+ if (relations.length) {
28787
+ this.onGetterUpdate(relations);
28788
+ }
28789
+ })) !== null && _b !== void 0 ? _b : null;
27870
28790
  }
27871
28791
  /**
27872
28792
  * Starts rendering the menu item.
@@ -27909,10 +28829,13 @@ var LegacyRelationsRenderManager;
27909
28829
  this.drawnIds = [].concat(entityIds);
27910
28830
  }
27911
28831
  Dispose() {
28832
+ var _a;
27912
28833
  if (this.disposed) {
27913
28834
  return;
27914
28835
  }
27915
28836
  this.disposed = true;
28837
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28838
+ this.simulationRemoval = null;
27916
28839
  this.register.RemoveRegos({
27917
28840
  menuItemId: this.item.id
27918
28841
  });
@@ -28073,7 +28996,8 @@ var LegacyRelationsRenderManager;
28073
28996
  menuItemId: this.item.id,
28074
28997
  relations: relations,
28075
28998
  viewer: this.viewer,
28076
- visualRegister: this.register
28999
+ visualRegister: this.register,
29000
+ simulation: this.simulation
28077
29001
  });
28078
29002
  if (this.disposed) {
28079
29003
  this.register.RemoveRegos({
@@ -29306,6 +30230,55 @@ var AssemblyRenderManager;
29306
30230
  get ModelSpace() {
29307
30231
  return this.modelSpace;
29308
30232
  }
30233
+ /**
30234
+ * Every node of the Assembly read so far, so a caller can work out motion in the Assembly's own terms.
30235
+ * @param timeMs the scene time for each node's current pose. Default: the clock's.
30236
+ * @returns per node its pose within its parent from the API (local) and as drawn at the time (current).
30237
+ */
30238
+ GetNodes(timeMs) {
30239
+ const at = timeMs !== null && timeMs !== void 0 ? timeMs : JulianDate.toDate(this.viewer.clock.currentTime).getTime();
30240
+ const rowsOf = (m) => m ? [0, 1, 2, 3].map(row => [0, 1, 2, 3].map(column => m[(column * 4) + row])) : null;
30241
+ return Object.values(this.hierarchyNodeByEntityId || {}).map(node => {
30242
+ var _a, _b, _c;
30243
+ const entity = (_a = this.apiEntities.get(node.entityId)) !== null && _a !== void 0 ? _a : null;
30244
+ const rows = (_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition;
30245
+ return {
30246
+ entityId: node.entityId,
30247
+ name: node.name,
30248
+ parentId: (_c = node.parentId) !== null && _c !== void 0 ? _c : null,
30249
+ local: Manager.MatrixFromRows(rows) ? rows : null,
30250
+ current: rowsOf(this.localAt(node.entityId, at)),
30251
+ entity
30252
+ };
30253
+ });
30254
+ }
30255
+ /**
30256
+ * The root's world frame at a time, history and simulation included, eg. to follow or frame the Assembly.
30257
+ * @param timeMs the scene time. Default: the clock's.
30258
+ * @returns null until the root has been read.
30259
+ */
30260
+ GetRootFrame(timeMs) {
30261
+ const root = this.rootAt(timeMs !== null && timeMs !== void 0 ? timeMs : JulianDate.toDate(this.viewer.clock.currentTime).getTime());
30262
+ return root ? Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll) : null;
30263
+ }
30264
+ /**
30265
+ * A part's world frame at a time as its records and its parents place it, size included.
30266
+ * @param entityId the part.
30267
+ * @param timeMs the scene time. Default: the clock's.
30268
+ * @returns null until the root and the part have been read.
30269
+ */
30270
+ GetPartFrame(entityId, timeMs) {
30271
+ const at = timeMs !== null && timeMs !== void 0 ? timeMs : JulianDate.toDate(this.viewer.clock.currentTime).getTime();
30272
+ const rootFrame = this.GetRootFrame(at);
30273
+ const world = entityId === this.rootId ? Matrix4.IDENTITY : this.composeWorld(entityId, at);
30274
+ if (!rootFrame || !world) {
30275
+ return null;
30276
+ }
30277
+ // Where the part is drawn, so its own ground offset is part of its frame.
30278
+ const ground = this.groundOffsets.get(entityId);
30279
+ const placed = ground ? Matrix4.multiply(Matrix4.fromTranslation(ground), world, new Matrix4()) : world;
30280
+ return Matrix4.multiply(rootFrame, placed, new Matrix4());
30281
+ }
29309
30282
  constructor(params) {
29310
30283
  this.disposed = false;
29311
30284
  // Cache of the hierarchy so that our scene-tree can reference it.
@@ -29315,19 +30288,41 @@ var AssemblyRenderManager;
29315
30288
  this.hierarchyNodeByEntityId = null;
29316
30289
  this.modelSpace = false;
29317
30290
  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.
30291
+ // Where the root sits outside its history, and which way it faces in degrees. Children are placed through it.
29320
30292
  this.rootPos3d = null;
29321
30293
  this.rootHeading = 0;
29322
- // Entity ID -> what was drawn for it and where it sits relative to the root.
30294
+ this.rootPitch = 0;
30295
+ this.rootRoll = 0;
30296
+ // The root's recorded positions and attitudes, oldest first, kept apart since a record may name only one.
30297
+ this.rootSeries = [];
30298
+ this.rootAttitude = [];
30299
+ // Every placement worked out for the scene time last asked about, shared by all parts drawn at it.
30300
+ this.pose = null;
30301
+ // Entity ID -> what was drawn for it.
29323
30302
  this.children = new Map();
30303
+ // Entity ID -> its live position within its parent, for every node fetched including undrawn groups.
30304
+ this.localPositions = new Map();
30305
+ // Entity ID -> how much higher the ground under it is than under the root, along the root's up.
30306
+ this.groundOffsets = new Map();
30307
+ // Entity ID -> its recorded poses within its parent, oldest first.
30308
+ this.jointSeries = new Map();
30309
+ // Drawn children that can move with the clock, so a tick places only these and not the whole Assembly.
30310
+ this.animatedIds = new Set();
29324
30311
  // The trail through the root's own movement, which describes the whole Assembly.
29325
- this.trackEntity = null;
30312
+ this.trail = null;
30313
+ this.simulationRemoval = null;
30314
+ this.FollowsSimulation = true;
30315
+ // Entity ID -> the record the API gave, so a cleared simulation falls back to it without a refetch.
30316
+ this.apiEntities = new Map();
30317
+ // Re-reads history and re-applies it, eg. after simulated records change. Set while the clock is followed.
30318
+ this.regather = null;
30319
+ this.followingClock = false;
29326
30320
  // Callback to dispose the link between scene time and the root's historic position.
29327
30321
  this.viewerDateTimeChangeRemoval = null;
29328
30322
  this.historicGatherer = null;
29329
30323
  this.historicAnimation = null;
29330
30324
  const { viewer, register: visualsManager, getters, item } = params;
30325
+ this.simulation = params.simulation;
29331
30326
  this.viewer = viewer;
29332
30327
  this.getters = getters;
29333
30328
  this.item = item;
@@ -29351,10 +30346,10 @@ var AssemblyRenderManager;
29351
30346
  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
30347
  }
29353
30348
  /*
29354
- * The root's frame: east-north-up at its position, turned by its heading.
30349
+ * The root's frame: east-north-up at its position, turned by its heading, pitch and roll.
29355
30350
  */
29356
- static rootFrame(rootPos3d, headingDegrees) {
29357
- const hpr = new HeadingPitchRoll(Math$1.toRadians(headingDegrees || 0), 0, 0);
30351
+ static rootFrame(rootPos3d, headingDegrees, pitchDegrees = 0, rollDegrees = 0) {
30352
+ const hpr = new HeadingPitchRoll(Math$1.toRadians(headingDegrees || 0), Math$1.toRadians(pitchDegrees || 0), Math$1.toRadians(rollDegrees || 0));
29358
30353
  return Transforms.headingPitchRollToFixedFrame(rootPos3d, hpr);
29359
30354
  }
29360
30355
  /**
@@ -29377,28 +30372,553 @@ var AssemblyRenderManager;
29377
30372
  return Matrix4.multiplyByPoint(Manager.rootFrame(rootPos3d, headingDegrees), offset, new Cartesian3());
29378
30373
  }
29379
30374
  /**
29380
- * Moves the whole Assembly to a new root position, without refetching any of its children.
30375
+ * Moves where the root sits outside its history, without refetching children; recorded poses still win.
29381
30376
  * @param params
29382
30377
  */
29383
30378
  ApplyRootPosition(params) {
29384
- var _a, _b;
30379
+ var _a, _b, _c, _d, _e, _f;
29385
30380
  if (this.disposed || !(params === null || params === void 0 ? void 0 : params.pos3d)) {
29386
30381
  return;
29387
30382
  }
29388
30383
  this.rootPos3d = params.pos3d;
29389
30384
  this.rootHeading = (_b = (_a = params.heading) !== null && _a !== void 0 ? _a : this.rootHeading) !== null && _b !== void 0 ? _b : 0;
30385
+ this.rootPitch = (_d = (_c = params.pitch) !== null && _c !== void 0 ? _c : this.rootPitch) !== null && _d !== void 0 ? _d : 0;
30386
+ this.rootRoll = (_f = (_e = params.roll) !== null && _e !== void 0 ? _e : this.rootRoll) !== null && _f !== void 0 ? _f : 0;
30387
+ this.repose(true);
30388
+ }
30389
+ /*
30390
+ * Drops every placement worked out so far, after the root or a joint's records change.
30391
+ * @param everyChild re-place the parts that cannot move as well, because where they rest has changed.
30392
+ */
30393
+ repose(everyChild = false) {
30394
+ this.pose = null;
29390
30395
  for (const child of this.children.values()) {
29391
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, child.offset);
29392
- if (!pos3d) {
30396
+ if (everyChild || Boolean(child.moving) != this.canMove(child.entityId)) {
30397
+ this.bindPlacement(child);
30398
+ }
30399
+ }
30400
+ this.viewer.scene.requestRender();
30401
+ }
30402
+ /*
30403
+ * Takes a node's pose outside its history from its record, and for the root where it stands and faces.
30404
+ */
30405
+ absorbNode(entity) {
30406
+ var _a, _b, _c, _d, _e;
30407
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30408
+ const local = Manager.MatrixFromRows((_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition);
30409
+ if (local) {
30410
+ this.localPositions.set(id, local);
30411
+ }
30412
+ // Anchored on the root, which is usually never drawn; everything under it is placed from the hierarchy.
30413
+ if (id === this.rootId || (!this.rootId && !this.rootPos3d)) {
30414
+ const rootPos3d = EntityUtils.GetPos({
30415
+ viewer: this.viewer,
30416
+ entity,
30417
+ recordHeightRef: Manager.GetRecordHeightRef(this.item),
30418
+ returnHeightRef: HeightReference.NONE,
30419
+ allowRendered: false
30420
+ });
30421
+ if (rootPos3d) {
30422
+ this.rootPos3d = rootPos3d;
30423
+ this.rootHeading = EnsureNumber((_c = entity.Bruce.Transform) === null || _c === void 0 ? void 0 : _c.heading, 0);
30424
+ this.rootPitch = EnsureNumber((_d = entity.Bruce.Transform) === null || _d === void 0 ? void 0 : _d.pitch, 0);
30425
+ this.rootRoll = EnsureNumber((_e = entity.Bruce.Transform) === null || _e === void 0 ? void 0 : _e.roll, 0);
30426
+ }
30427
+ }
30428
+ }
30429
+ /*
30430
+ * A node's record with its simulation laid over it, keeping what a stand-in leaves out, eg. its model.
30431
+ */
30432
+ withSimulation(entity) {
30433
+ var _a;
30434
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30435
+ if (!this.simulation || !id) {
30436
+ return entity;
30437
+ }
30438
+ const standIn = this.simulation.GetEntity(id);
30439
+ if (!standIn) {
30440
+ return this.simulation.Overlay(entity);
30441
+ }
30442
+ return { ...entity, ...standIn, Bruce: { ...entity.Bruce, ...standIn.Bruce } };
30443
+ }
30444
+ /*
30445
+ * Brings the Assembly in line with a change to what is simulated.
30446
+ */
30447
+ onSimulationChanged(change) {
30448
+ var _a;
30449
+ if (this.disposed || !this.hierarchyNodeByEntityId) {
30450
+ return;
30451
+ }
30452
+ const ids = change.entityIds.filter(id => this.hierarchyNodeByEntityId[id]);
30453
+ if (!ids.length) {
30454
+ return;
30455
+ }
30456
+ if (change.entities) {
30457
+ for (const id of ids) {
30458
+ const api = this.apiEntities.get(id);
30459
+ if (!api) {
30460
+ // Not loaded yet, and it will be drawn from its simulation when it is.
30461
+ continue;
30462
+ }
30463
+ const entity = this.withSimulation(api);
30464
+ // A cleared blob leaves the node's own matrix where the API has none, so it is reset.
30465
+ if (!((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.AssemblyPosition)) {
30466
+ this.localPositions.delete(id);
30467
+ }
30468
+ this.absorbNode(entity);
30469
+ const child = this.children.get(id);
30470
+ if (child) {
30471
+ child.entity = entity;
30472
+ }
30473
+ }
30474
+ this.repose(true);
30475
+ }
30476
+ if (change.records) {
30477
+ if (this.regather) {
30478
+ this.regather(true);
30479
+ }
30480
+ else {
30481
+ this.viewerDateTimeSub();
30482
+ }
30483
+ }
30484
+ }
30485
+ /*
30486
+ * Nodes of this Assembly with simulated history.
30487
+ */
30488
+ simulatedNodeIds() {
30489
+ if (!this.simulation || !this.hierarchyNodeByEntityId) {
30490
+ return [];
30491
+ }
30492
+ return this.simulation.GetRecordEntityIds().filter(id => this.hierarchyNodeByEntityId[id]);
30493
+ }
30494
+ /*
30495
+ * Whether a node can move with time: the root has history, or it or an ancestor does.
30496
+ */
30497
+ canMove(entityId) {
30498
+ return this.rootSeries.length > 0 || this.rootAttitude.length > 0 || this.animatedIds.has(entityId);
30499
+ }
30500
+ /*
30501
+ * Gives a child its position and orientation, as functions of scene time only when it can move.
30502
+ */
30503
+ bindPlacement(child) {
30504
+ const id = child.entityId;
30505
+ const moving = this.canMove(id);
30506
+ if (moving) {
30507
+ if (!child.moving) {
30508
+ 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);
30509
+ child.cEntity.orientation = new CallbackProperty((time, result) => {
30510
+ var _a;
30511
+ const orient = (_a = this.placementAt(id, time)) === null || _a === void 0 ? void 0 : _a.orient;
30512
+ return orient ? Quaternion.clone(orient, result) : undefined;
30513
+ }, false);
30514
+ 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);
30515
+ }
30516
+ }
30517
+ else {
30518
+ const placed = this.placementAt(id, this.viewer.clock.currentTime);
30519
+ if (placed) {
30520
+ child.cEntity.position = new ConstantPositionProperty(placed.pos3d);
30521
+ if (placed.orient) {
30522
+ child.cEntity.orientation = new ConstantProperty(placed.orient);
30523
+ }
30524
+ child.cEntity.model.scale = new ConstantProperty(placed.scale);
30525
+ }
30526
+ }
30527
+ if (Boolean(child.moving) != moving) {
30528
+ child.moving = moving;
30529
+ this.visualsManager.SetMoving({ moving, entityIds: [id], menuItemId: this.item.id });
30530
+ }
30531
+ }
30532
+ /*
30533
+ * The one scale a model takes from its part's composed matrix, the largest when they are uneven.
30534
+ */
30535
+ static uniformScale(world) {
30536
+ return Cartesian3.maximumComponent(Matrix4.getScale(world, new Cartesian3()));
30537
+ }
30538
+ static cloneOrUndefined(pos3d, result) {
30539
+ return pos3d ? Cartesian3.clone(pos3d, result) : undefined;
30540
+ }
30541
+ /*
30542
+ * Where a node draws at a scene time, from one composition shared by every part asked about at that time.
30543
+ */
30544
+ placementAt(entityId, time) {
30545
+ if (this.disposed || !time) {
30546
+ return null;
30547
+ }
30548
+ const timeMs = JulianDate.toDate(time).getTime();
30549
+ if (!this.pose || this.pose.timeMs !== timeMs) {
30550
+ const root = this.rootAt(timeMs);
30551
+ this.pose = {
30552
+ timeMs,
30553
+ rootFrame: root ? Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll) : null,
30554
+ worlds: new Map(),
30555
+ placements: new Map()
30556
+ };
30557
+ }
30558
+ const pose = this.pose;
30559
+ if (!pose.rootFrame) {
30560
+ return null;
30561
+ }
30562
+ let placed = pose.placements.get(entityId);
30563
+ if (!placed) {
30564
+ const world = this.composeWorld(entityId, timeMs, pose.worlds);
30565
+ const offset = world
30566
+ ? Matrix4.getTranslation(world, new Cartesian3())
30567
+ : new Cartesian3(0, 0, 0);
30568
+ const ground = this.groundOffsets.get(entityId);
30569
+ if (ground) {
30570
+ Cartesian3.add(offset, ground, offset);
30571
+ }
30572
+ placed = {
30573
+ pos3d: Matrix4.multiplyByPoint(pose.rootFrame, offset, new Cartesian3()),
30574
+ // Without a matrix, eg. the root itself, there is no orientation of its own to apply.
30575
+ orient: world ? EntityUtils.GetAssemblyOrientationInFrame(pose.rootFrame, world) : null,
30576
+ scale: world ? Manager.uniformScale(world) : 1
30577
+ };
30578
+ pose.placements.set(entityId, placed);
30579
+ }
30580
+ return placed;
30581
+ }
30582
+ /*
30583
+ * The root's pose at a time, position and attitude each following their own records as a joint's do.
30584
+ */
30585
+ rootAt(timeMs) {
30586
+ const pos3d = this.rootPositionAt(timeMs);
30587
+ if (!pos3d) {
30588
+ return null;
30589
+ }
30590
+ const live = { heading: this.rootHeading, pitch: this.rootPitch, roll: this.rootRoll };
30591
+ const series = this.rootAttitude;
30592
+ if (!series.length || timeMs < series[0].timeMs) {
30593
+ return { pos3d, ...live };
30594
+ }
30595
+ let i = 0;
30596
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30597
+ i++;
30598
+ }
30599
+ const a = series[i];
30600
+ const b = series[i + 1];
30601
+ const t = b ? (timeMs - a.timeMs) / (b.timeMs - a.timeMs) : 0;
30602
+ const turn = (from, to, fallback) => {
30603
+ var _a, _b, _c;
30604
+ const x = (_a = from !== null && from !== void 0 ? from : fallback) !== null && _a !== void 0 ? _a : 0;
30605
+ const y = (_c = (_b = (b ? to : from)) !== null && _b !== void 0 ? _b : fallback) !== null && _c !== void 0 ? _c : 0;
30606
+ // The short way round, so 350 to 10 degrees turns through north rather than all the way back.
30607
+ return x + (Math$1.negativePiToPi(Math$1.toRadians(y - x)) * 180 / Math.PI) * t;
30608
+ };
30609
+ return {
30610
+ pos3d,
30611
+ heading: turn(a.heading, b === null || b === void 0 ? void 0 : b.heading, live.heading),
30612
+ pitch: turn(a.pitch, b === null || b === void 0 ? void 0 : b.pitch, live.pitch),
30613
+ roll: turn(a.roll, b === null || b === void 0 ? void 0 : b.roll, live.roll)
30614
+ };
30615
+ }
30616
+ /*
30617
+ * Where the root is at a time, from its recorded positions.
30618
+ */
30619
+ rootPositionAt(timeMs) {
30620
+ const series = this.rootSeries;
30621
+ if (!series.length || timeMs < series[0].timeMs) {
30622
+ return this.rootPos3d;
30623
+ }
30624
+ let i = 0;
30625
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30626
+ i++;
30627
+ }
30628
+ const a = series[i];
30629
+ const b = series[i + 1];
30630
+ if (!b) {
30631
+ return a.pos3d;
30632
+ }
30633
+ const t = (timeMs - a.timeMs) / (b.timeMs - a.timeMs);
30634
+ // A straight line between orbit samples cuts up to a kilometre inside the path, so it is lifted back out.
30635
+ const chord = Cartesian3.lerp(a.pos3d, b.pos3d, t, new Cartesian3());
30636
+ const carto = Cartographic.fromCartesian(chord);
30637
+ return carto
30638
+ ? Cartesian3.fromRadians(carto.longitude, carto.latitude, Math$1.lerp(a.height, b.height, t))
30639
+ : chord;
30640
+ }
30641
+ /*
30642
+ * Takes the root's records into its series.
30643
+ * @param replace the records are everything held for the root, so the series becomes exactly them.
30644
+ * @returns whether the series changed.
30645
+ */
30646
+ absorbRootRecords(posses, replace) {
30647
+ if (!(posses === null || posses === void 0 ? void 0 : posses.length) && !(replace && this.rootSeries.length)) {
30648
+ return false;
30649
+ }
30650
+ const byTime = new Map(replace ? [] : this.rootSeries.map(k => [k.timeMs, k]));
30651
+ for (const pos of posses) {
30652
+ const carto = Cartographic.fromCartesian(pos.pos3d);
30653
+ if (!carto) {
29393
30654
  continue;
29394
30655
  }
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);
30656
+ byTime.set(pos.dateTime.getTime(), {
30657
+ timeMs: pos.dateTime.getTime(),
30658
+ pos3d: pos.pos3d,
30659
+ height: carto.height
30660
+ });
30661
+ }
30662
+ const series = Array.from(byTime.values()).sort((x, y) => x.timeMs - y.timeMs);
30663
+ const changed = (series.length !== this.rootSeries.length ||
30664
+ series.some((k, i) => !Cartesian3.equals(k.pos3d, this.rootSeries[i].pos3d)));
30665
+ this.rootSeries = series;
30666
+ return changed;
30667
+ }
30668
+ /*
30669
+ * Takes the angles the root's records name into its attitude series, apart from where it is.
30670
+ * @param replace the records are everything held for the root, as for absorbRootRecords.
30671
+ * @returns whether the series changed.
30672
+ */
30673
+ absorbRootAttitude(records, replace) {
30674
+ const angle = (transform, key) => {
30675
+ var _a;
30676
+ 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)];
30677
+ return value == null || isNaN(+value) ? null : +value;
30678
+ };
30679
+ const byTime = new Map(replace ? [] : this.rootAttitude.map(k => [k.timeMs, k]));
30680
+ for (const record of records || []) {
30681
+ const timeMs = (record === null || record === void 0 ? void 0 : record.dateTime) ? new Date(record.dateTime).getTime() : NaN;
30682
+ const key = {
30683
+ timeMs,
30684
+ heading: angle(record === null || record === void 0 ? void 0 : record.transform, "heading"),
30685
+ pitch: angle(record === null || record === void 0 ? void 0 : record.transform, "pitch"),
30686
+ roll: angle(record === null || record === void 0 ? void 0 : record.transform, "roll")
30687
+ };
30688
+ if (isNaN(timeMs) || (key.heading == null && key.pitch == null && key.roll == null)) {
30689
+ continue;
29399
30690
  }
30691
+ byTime.set(timeMs, key);
29400
30692
  }
29401
- this.viewer.scene.requestRender();
30693
+ const series = Array.from(byTime.values()).sort((x, y) => x.timeMs - y.timeMs);
30694
+ const held = this.rootAttitude;
30695
+ const differs = (k, i) => (k.timeMs !== held[i].timeMs ||
30696
+ k.heading !== held[i].heading ||
30697
+ k.pitch !== held[i].pitch ||
30698
+ k.roll !== held[i].roll);
30699
+ const changed = series.length !== held.length || series.some(differs);
30700
+ this.rootAttitude = series;
30701
+ return changed;
30702
+ }
30703
+ /*
30704
+ * A node's matrix in the root's frame at a time, from each ancestor's pose within its parent.
30705
+ */
30706
+ composeWorld(entityId, timeMs, memo, localOf = (id) => this.localAt(id, timeMs)) {
30707
+ var _a, _b, _c, _d;
30708
+ if (memo === null || memo === void 0 ? void 0 : memo.has(entityId)) {
30709
+ return memo.get(entityId);
30710
+ }
30711
+ let world = null;
30712
+ const local = localOf(entityId);
30713
+ if (local) {
30714
+ const parentId = (_b = (_a = this.hierarchyNodeByEntityId) === null || _a === void 0 ? void 0 : _a[entityId]) === null || _b === void 0 ? void 0 : _b.parentId;
30715
+ const parent = parentId ? this.composeWorld(parentId, timeMs, memo, localOf) : null;
30716
+ world = parent ? Matrix4.multiply(parent, local, new Matrix4()) : Matrix4.clone(local);
30717
+ }
30718
+ else if (entityId !== this.rootId) {
30719
+ // A node whose own position was not fetched falls back to the API's composed matrix.
30720
+ const child = this.children.get(entityId);
30721
+ 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);
30722
+ }
30723
+ memo === null || memo === void 0 ? void 0 : memo.set(entityId, world);
30724
+ return world;
30725
+ }
30726
+ /*
30727
+ * Under a clamped or relative style, lifts a part by how much higher its ground is than the root's.
30728
+ */
30729
+ measureGroundOffset(entity) {
30730
+ var _a;
30731
+ const entityId = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30732
+ this.groundOffsets.delete(entityId);
30733
+ const root = this.apiEntities.get(this.rootId);
30734
+ if (!entityId ||
30735
+ entityId === this.rootId ||
30736
+ !root ||
30737
+ !this.rootPos3d ||
30738
+ Manager.GetRecordHeightRef(this.item) === HeightReference.NONE) {
30739
+ return;
30740
+ }
30741
+ const rootFrame = Manager.rootFrame(this.rootPos3d, this.rootHeading, this.rootPitch, this.rootRoll);
30742
+ const toRoot = Matrix4.inverse(rootFrame, new Matrix4());
30743
+ const groundUp = (record) => {
30744
+ const pos3d = EntityUtils.GetPos({
30745
+ viewer: this.viewer,
30746
+ entity: record,
30747
+ recordHeightRef: HeightReference.CLAMP_TO_GROUND,
30748
+ returnHeightRef: HeightReference.NONE,
30749
+ allowRendered: false
30750
+ });
30751
+ return pos3d ? Matrix4.multiplyByPoint(toRoot, pos3d, new Cartesian3()).z : null;
30752
+ };
30753
+ const part = groundUp(entity);
30754
+ const under = groundUp(root);
30755
+ if (part == null || under == null || Math.abs(part - under) < 1e-6) {
30756
+ return;
30757
+ }
30758
+ this.groundOffsets.set(entityId, new Cartesian3(0, 0, part - under));
30759
+ }
30760
+ /*
30761
+ * The rigid Earth-fixed motion from where a part's record places it to where it is drawn at a time.
30762
+ */
30763
+ motionAt(entityId, time) {
30764
+ if (this.disposed || !time || !this.rootPos3d) {
30765
+ return null;
30766
+ }
30767
+ const timeMs = JulianDate.toDate(time).getTime();
30768
+ const root = this.rootAt(timeMs);
30769
+ const drawn = this.composeWorld(entityId, timeMs, new Map());
30770
+ const stored = this.composeWorld(entityId, timeMs, new Map(), (id) => {
30771
+ var _a, _b, _c, _d;
30772
+ 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);
30773
+ });
30774
+ if (!root || !drawn || !stored) {
30775
+ return null;
30776
+ }
30777
+ const drawnFixed = Matrix4.multiply(Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll), drawn, new Matrix4());
30778
+ const storedFixed = Matrix4.multiply(Manager.rootFrame(this.rootPos3d, this.rootHeading, this.rootPitch, this.rootRoll), stored, new Matrix4());
30779
+ return Matrix4.multiply(drawnFixed, Matrix4.inverse(storedFixed, new Matrix4()), new Matrix4());
30780
+ }
30781
+ /*
30782
+ * A node's pose within its parent at a time, interpolated between the records either side.
30783
+ */
30784
+ localAt(entityId, timeMs) {
30785
+ var _a;
30786
+ // The live pose stands before the first record and the last record holds after it, as in an API read.
30787
+ const live = (_a = this.localPositions.get(entityId)) !== null && _a !== void 0 ? _a : null;
30788
+ const series = this.jointSeries.get(entityId);
30789
+ if (!(series === null || series === void 0 ? void 0 : series.length) || timeMs == null || timeMs < series[0].timeMs) {
30790
+ return live;
30791
+ }
30792
+ let i = 0;
30793
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30794
+ i++;
30795
+ }
30796
+ const a = series[i];
30797
+ const b = series[i + 1];
30798
+ if (!b) {
30799
+ return Matrix4.fromTranslationQuaternionRotationScale(a.translation, a.rotation, a.scale);
30800
+ }
30801
+ const t = (timeMs - a.timeMs) / (b.timeMs - a.timeMs);
30802
+ 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()));
30803
+ }
30804
+ /*
30805
+ * Rebuilds each node's keyframes from the historic records held so far.
30806
+ * @returns whether any node has keyframes.
30807
+ */
30808
+ absorbJointRecords(recordsByIds) {
30809
+ var _a;
30810
+ // Everything held is given, so a node with nothing left, eg. its simulated records cleared, stops moving.
30811
+ for (const entityId of Array.from(this.jointSeries.keys())) {
30812
+ if (!((_a = recordsByIds === null || recordsByIds === void 0 ? void 0 : recordsByIds[entityId]) === null || _a === void 0 ? void 0 : _a.length)) {
30813
+ this.jointSeries.delete(entityId);
30814
+ }
30815
+ }
30816
+ for (const entityId of Object.keys(recordsByIds || {})) {
30817
+ const keyframes = [];
30818
+ for (const record of recordsByIds[entityId] || []) {
30819
+ const matrix = Manager.MatrixFromRows(record.assemblyPosition);
30820
+ const timeMs = new Date(record.dateTime).getTime();
30821
+ if (!matrix || isNaN(timeMs)) {
30822
+ continue;
30823
+ }
30824
+ const scale = Matrix4.getScale(matrix, new Cartesian3());
30825
+ const rotation = Matrix4.getRotation(matrix, new Matrix3());
30826
+ keyframes.push({
30827
+ timeMs,
30828
+ translation: Matrix4.getTranslation(matrix, new Cartesian3()),
30829
+ rotation: Quaternion.fromRotationMatrix(rotation),
30830
+ scale
30831
+ });
30832
+ }
30833
+ if (keyframes.length) {
30834
+ keyframes.sort((x, y) => x.timeMs - y.timeMs);
30835
+ this.jointSeries.set(entityId, keyframes);
30836
+ }
30837
+ }
30838
+ this.refreshAnimated();
30839
+ return this.jointSeries.size > 0;
30840
+ }
30841
+ /*
30842
+ * Rebuilds which drawn children can move with the clock, by walking each one up to the root.
30843
+ */
30844
+ refreshAnimated() {
30845
+ this.animatedIds.clear();
30846
+ if (!this.jointSeries.size) {
30847
+ return;
30848
+ }
30849
+ for (const entityId of this.children.keys()) {
30850
+ if (this.hasAnimatedAncestry(entityId)) {
30851
+ this.animatedIds.add(entityId);
30852
+ }
30853
+ }
30854
+ }
30855
+ /*
30856
+ * Whether a node or anything above it has keyframes, so the clock can move it.
30857
+ */
30858
+ hasAnimatedAncestry(entityId) {
30859
+ var _a, _b;
30860
+ let at = entityId;
30861
+ while (at && this.jointSeries.size) {
30862
+ if (this.jointSeries.has(at)) {
30863
+ return true;
30864
+ }
30865
+ at = (_b = (_a = this.hierarchyNodeByEntityId) === null || _a === void 0 ? void 0 : _a[at]) === null || _b === void 0 ? void 0 : _b.parentId;
30866
+ }
30867
+ return false;
30868
+ }
30869
+ /*
30870
+ * Which nodes to ask history for.
30871
+ */
30872
+ async historicEntityIds(api) {
30873
+ var _a, _b;
30874
+ // Through the Assembly when there is one, so the hierarchy follows the relation type on its record.
30875
+ const assemblyId = this.item["Assembly.ID"];
30876
+ const root = assemblyId ? { key: "root", AssemblyID: assemblyId } : { key: "root", RootID: this.rootId };
30877
+ const historic = {
30878
+ key: "historic",
30879
+ Historic: { HistoricKey: ((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey) || undefined }
30880
+ };
30881
+ const query = {
30882
+ [DataLab.EReqKey.Primary]: { Items: [root, historic], Select: { Type: DataLab.ESelectType.Id } }
30883
+ };
30884
+ const load = 1000;
30885
+ try {
30886
+ const ids = [];
30887
+ for (let skip = 0;; skip += load) {
30888
+ const { items } = await DataLab.Run({ api, query, skip, load });
30889
+ ids.push(...(items !== null && items !== void 0 ? items : []));
30890
+ if (!items || items.length < load) {
30891
+ return ids;
30892
+ }
30893
+ }
30894
+ }
30895
+ catch (e) {
30896
+ // An API without the historic criterion gets asked about every node, as before it existed.
30897
+ console.warn("AssemblyRenderManager.historicEntityIds()", e);
30898
+ }
30899
+ return Object.keys((_b = this.hierarchyNodeByEntityId) !== null && _b !== void 0 ? _b : { [this.rootId]: null });
30900
+ }
30901
+ /**
30902
+ * Builds a matrix from the row-major rows that Bruce/AssemblyPosition and AssemblyWorldPosition use.
30903
+ * @param rows
30904
+ */
30905
+ static MatrixFromRows(rows) {
30906
+ if (!Array.isArray(rows) || rows.length !== 4 || rows.some(r => !Array.isArray(r) || r.length !== 4)) {
30907
+ return null;
30908
+ }
30909
+ 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]);
30910
+ }
30911
+ /**
30912
+ * The inverse of MatrixFromRows.
30913
+ * @param matrix
30914
+ */
30915
+ static RowsFromMatrix(matrix) {
30916
+ const rows = [];
30917
+ for (let r = 0; r < 4; r++) {
30918
+ const row = Matrix4.getRow(matrix, r, new Cartesian4());
30919
+ rows.push([row.x, row.y, row.z, row.w]);
30920
+ }
30921
+ return rows;
29402
30922
  }
29403
30923
  Init() {
29404
30924
  this.renderPriority = this.item.renderPriority;
@@ -29406,6 +30926,7 @@ var AssemblyRenderManager;
29406
30926
  this.renderPriority = 1;
29407
30927
  }
29408
30928
  (async () => {
30929
+ var _a, _b;
29409
30930
  if (this.disposed) {
29410
30931
  return;
29411
30932
  }
@@ -29421,6 +30942,8 @@ var AssemblyRenderManager;
29421
30942
  this.hierarchyUpdate.Trigger(hierarchy);
29422
30943
  // Build hierarchy node cache for quick lookups
29423
30944
  this.buildHierarchyNodes(hierarchy);
30945
+ // A set for membership, since searching the list took minutes at 200k nodes.
30946
+ const seenIds = new Set();
29424
30947
  const traverseHierarchy = (node, leavesOnly) => {
29425
30948
  var _a;
29426
30949
  if (node.Children) {
@@ -29429,7 +30952,8 @@ var AssemblyRenderManager;
29429
30952
  }
29430
30953
  }
29431
30954
  const addNode = !leavesOnly || !((_a = node.Children) === null || _a === void 0 ? void 0 : _a.length);
29432
- if (addNode && node.ID && !flatEntityIds.includes(node.ID)) {
30955
+ if (addNode && node.ID && !seenIds.has(node.ID)) {
30956
+ seenIds.add(node.ID);
29433
30957
  flatEntityIds.push(node.ID);
29434
30958
  }
29435
30959
  };
@@ -29448,17 +30972,17 @@ var AssemblyRenderManager;
29448
30972
  }
29449
30973
  }
29450
30974
  this.rootId = rootId;
30975
+ 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
30976
  if (this.disposed) {
29452
30977
  return;
29453
30978
  }
29454
30979
  else if (flatEntityIds.length === 0) {
29455
30980
  return;
29456
30981
  }
30982
+ // Followed while the parts load: in a large Assembly loading takes far longer than anyone waits.
30983
+ const following = this.viewerDateTimeSub();
29457
30984
  await this.processEntityIds(flatEntityIds);
29458
- if (this.disposed) {
29459
- return;
29460
- }
29461
- this.viewerDateTimeSub();
30985
+ await following;
29462
30986
  }
29463
30987
  catch (e) {
29464
30988
  console.error("AssemblyRenderManager.Manager.Init()", e);
@@ -29485,7 +31009,7 @@ var AssemblyRenderManager;
29485
31009
  if (!batch.length) {
29486
31010
  continue;
29487
31011
  }
29488
- const { entities: fetched } = await Entity$1.GetListByIds({
31012
+ const { entities: apiEntities } = await Entity$1.GetListByIds({
29489
31013
  entityIds: batch,
29490
31014
  api: this.getters.GetBruceApi(),
29491
31015
  expandLODs: true,
@@ -29497,6 +31021,16 @@ var AssemblyRenderManager;
29497
31021
  if (this.disposed) {
29498
31022
  return;
29499
31023
  }
31024
+ const fetched = apiEntities.map((entity) => {
31025
+ var _a;
31026
+ this.apiEntities.set((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID, entity);
31027
+ return this.withSimulation(entity);
31028
+ });
31029
+ for (const entity of fetched) {
31030
+ this.absorbNode(entity);
31031
+ }
31032
+ // Placements already worked out did not know about this batch's positions.
31033
+ this.pose = null;
29500
31034
  for (const entity of fetched) {
29501
31035
  // Filter for valid LODs then take the highest quality one (lowest level).
29502
31036
  let lods = entity.Bruce.LOD || [];
@@ -29519,57 +31053,46 @@ var AssemblyRenderManager;
29519
31053
  // Won't bother with vector data for this experiment.
29520
31054
  continue;
29521
31055
  }
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) {
31056
+ const entityId = entity.Bruce.ID;
31057
+ this.measureGroundOffset(entity);
31058
+ // A placement cached before the offset was known would leave the part off its ground.
31059
+ (_a = this.pose) === null || _a === void 0 ? void 0 : _a.placements.delete(entityId);
31060
+ if (!this.canMove(entityId) && !this.placementAt(entityId, this.viewer.clock.currentTime)) {
29531
31061
  continue;
29532
31062
  }
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
31063
  const cEntity = this.viewer.entities.add({
29547
31064
  model: {
29548
31065
  uri: lod["ClientFile"].URL,
29549
31066
  ...ViewUtils.DefaultLightingProps(),
29550
31067
  heightReference: HeightReference.NONE
29551
- },
29552
- position: pos3d,
29553
- orientation: orient ? new ConstantProperty(orient) : undefined
31068
+ }
29554
31069
  });
29555
31070
  const prior = this.children.get(entityId);
29556
31071
  if (prior && prior.cEntity !== cEntity) {
29557
31072
  RemoveCEntityFromScene(this.viewer, prior.cEntity);
29558
31073
  }
29559
- this.children.set(entityId, {
31074
+ const child = {
29560
31075
  entityId: entityId,
29561
31076
  entity: entity,
29562
- cEntity: cEntity,
29563
- offset: offset
29564
- });
31077
+ cEntity: cEntity
31078
+ };
31079
+ this.children.set(entityId, child);
31080
+ // Joins the animated set by its own ancestry, so a late part never re-walks the rest.
31081
+ if (this.hasAnimatedAncestry(entityId)) {
31082
+ this.animatedIds.add(entityId);
31083
+ }
29565
31084
  const geometryRadius = Entity$1.GetValue({
29566
31085
  entity: entity,
29567
31086
  path: ["Bruce", "GeometryRadius"]
29568
31087
  });
29569
31088
  cEntity.model["_radius"] = geometryRadius;
29570
31089
  cEntity.model["_radiusLoaded"] = true;
31090
+ // Placed before it is registered, so the register never measures a visual with no position.
31091
+ this.bindPlacement(child);
29571
31092
  this.visualsManager.AddRego({
29572
31093
  rego: {
31094
+ moving: child.moving ? true : null,
31095
+ motion: (time) => this.motionAt(entity.Bruce.ID, time),
29573
31096
  canEdit: true,
29574
31097
  entityId: entity.Bruce.ID,
29575
31098
  menuItemId: this.item.id,
@@ -29589,47 +31112,89 @@ var AssemblyRenderManager;
29589
31112
  }
29590
31113
  }
29591
31114
  /*
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.
31115
+ * Follows the scene clock, moving the Assembly along the root's history and each joint along its own.
29594
31116
  */
29595
- viewerDateTimeSub() {
29596
- if (!Manager.IsHistoric(this.item) || this.viewerDateTimeChangeRemoval || !this.rootId) {
31117
+ async viewerDateTimeSub() {
31118
+ var _a;
31119
+ const historic = Manager.IsHistoric(this.item);
31120
+ if (this.followingClock || !this.rootId || (!historic && !this.simulatedNodeIds().length)) {
29597
31121
  return;
29598
31122
  }
31123
+ this.followingClock = true;
29599
31124
  const api = this.getters.GetBruceApi();
29600
- const rootEntity = { Bruce: { ID: this.rootId } };
31125
+ const historicIds = historic ? await this.historicEntityIds(api) : [];
31126
+ if (this.disposed) {
31127
+ return;
31128
+ }
31129
+ const attrKey = (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey;
29601
31130
  const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29602
31131
  const toPosses = (records) => {
29603
31132
  return CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, recordHeightRef, HeightReference.NONE, records);
29604
31133
  };
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));
31134
+ if (historicIds.length) {
31135
+ this.historicGatherer = new HistoricGatherer({
31136
+ viewer: this.viewer,
31137
+ getApi: () => api,
31138
+ getAttrKey: () => attrKey,
31139
+ // For clock desync between whatever wrote the record and this client.
31140
+ paddingMs: 15000,
31141
+ simulation: this.simulation,
31142
+ onRecords: (recordsByIds) => {
31143
+ var _a;
31144
+ const records = recordsByIds[this.rootId];
31145
+ if (records === null || records === void 0 ? void 0 : records.length) {
31146
+ const posses = toPosses(records);
31147
+ (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.SupplementSeries(posses);
31148
+ const moved = this.absorbRootRecords(posses, false);
31149
+ if (this.absorbRootAttitude(records, false) || moved) {
31150
+ this.repose();
31151
+ }
31152
+ }
31153
+ // Joint records go in together once the gather lands, so parents never draw apart from children.
29615
31154
  }
29616
- }
29617
- });
31155
+ });
31156
+ }
31157
+ // The nodes with history: what the API has records for, and whatever has been simulated since.
31158
+ const nodes = () => Array.from(new Set([...historicIds, ...this.simulatedNodeIds()]));
29618
31159
  let gathering = false;
29619
- const gather = async () => {
31160
+ // Set when what is held changed without a fetch, eg. a simulated record arriving.
31161
+ let stale = true;
31162
+ const gather = async (force) => {
31163
+ var _a, _b;
31164
+ stale = stale || Boolean(force);
29620
31165
  if (this.disposed || gathering) {
29621
31166
  return;
29622
31167
  }
29623
31168
  gathering = true;
29624
31169
  try {
29625
- const { recordsByIds } = await this.historicGatherer.Gather([rootEntity]);
31170
+ const ids = nodes();
31171
+ let fetched = false;
31172
+ let recordsByIds;
31173
+ if (this.historicGatherer) {
31174
+ ({ fetched, recordsByIds } = await this.historicGatherer.Gather(ids.map(id => ({ Bruce: { ID: id } }))));
31175
+ }
31176
+ else {
31177
+ recordsByIds = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.MergeRecords({}, attrKey, ids)) !== null && _b !== void 0 ? _b : {};
31178
+ }
29626
31179
  if (this.disposed) {
29627
31180
  return;
29628
31181
  }
31182
+ // A gather served from what is held changes nothing and runs every tick, so it stops here.
31183
+ if (!fetched && !stale && this.jointSeries.size) {
31184
+ return;
31185
+ }
31186
+ stale = false;
31187
+ let changed = this.absorbJointRecords(recordsByIds);
29629
31188
  const posses = toPosses(recordsByIds[this.rootId] || []);
31189
+ changed = this.absorbRootRecords(posses, true) || changed;
31190
+ changed = this.absorbRootAttitude(recordsByIds[this.rootId] || [], true) || changed;
31191
+ if (changed) {
31192
+ this.repose();
31193
+ }
29630
31194
  if (!posses.length) {
29631
31195
  return;
29632
31196
  }
31197
+ // Held for the trail alone; where the root is drawn comes from rootAt(), never from its smoothing.
29633
31198
  if (!this.historicAnimation) {
29634
31199
  this.historicAnimation = new CesiumAnimatedProperty.AnimatePositionSeries({
29635
31200
  posses: posses,
@@ -29643,15 +31208,21 @@ var AssemblyRenderManager;
29643
31208
  }
29644
31209
  catch (e) {
29645
31210
  console.error("AssemblyRenderManager.viewerDateTimeSub()", e);
31211
+ // A gather that throws would throw again straight away, so the retry waits for the next tick.
31212
+ stale = false;
29646
31213
  }
29647
31214
  finally {
29648
31215
  gathering = false;
31216
+ // A change that landed mid-gather is not left for a tick that may never come on a paused clock.
31217
+ if (stale && !this.disposed) {
31218
+ gather();
31219
+ }
29649
31220
  }
29650
31221
  };
31222
+ this.regather = gather;
29651
31223
  gather();
29652
31224
  let lastTime = null;
29653
31225
  const tickRemoval = this.viewer.clock.onTick.addEventListener(() => {
29654
- var _a;
29655
31226
  if (this.disposed) {
29656
31227
  return;
29657
31228
  }
@@ -29661,15 +31232,9 @@ var AssemblyRenderManager;
29661
31232
  }
29662
31233
  lastTime = now;
29663
31234
  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
- });
31235
+ // Moving parts read the clock themselves when drawn, so the scene only needs a new frame.
31236
+ if (this.rootSeries.length || this.rootAttitude.length || this.animatedIds.size) {
31237
+ this.viewer.scene.requestRender();
29673
31238
  }
29674
31239
  });
29675
31240
  this.viewerDateTimeChangeRemoval = () => {
@@ -29678,6 +31243,10 @@ var AssemblyRenderManager;
29678
31243
  (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
29679
31244
  this.historicGatherer = null;
29680
31245
  this.historicAnimation = null;
31246
+ this.regather = null;
31247
+ this.rootSeries = [];
31248
+ this.rootAttitude = [];
31249
+ this.pose = null;
29681
31250
  };
29682
31251
  }
29683
31252
  /*
@@ -29685,7 +31254,7 @@ var AssemblyRenderManager;
29685
31254
  */
29686
31255
  applyRootTrack() {
29687
31256
  var _a;
29688
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
31257
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
29689
31258
  return;
29690
31259
  }
29691
31260
  const trackStyle = EntityRenderEngine.GetTrackStyle({
@@ -29695,41 +31264,35 @@ var AssemblyRenderManager;
29695
31264
  if (!trackStyle) {
29696
31265
  return;
29697
31266
  }
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,
31267
+ this.trail = new HistoricTrail.Trail({
29715
31268
  viewer: this.viewer,
29716
- override: true,
29717
- requestRender: false
31269
+ color: trackStyle.color,
31270
+ width: trackStyle.width,
31271
+ clampToGround: EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
31272
+ == HeightReference.CLAMP_TO_GROUND,
31273
+ getSeries: () => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; },
31274
+ trailSeconds: HistoricTrail.GetTrailSeconds(this.item),
31275
+ // Where the root is drawn, so the trail ends on the model; nothing before the first record held.
31276
+ positionAt: (timeMs) => (this.rootSeries.length && timeMs >= this.rootSeries[0].timeMs ? this.rootPositionAt(timeMs) : null)
29718
31277
  });
29719
31278
  }
29720
31279
  Dispose() {
29721
- var _a;
31280
+ var _a, _b, _c;
29722
31281
  if (this.disposed) {
29723
31282
  return;
29724
31283
  }
29725
31284
  this.disposed = true;
29726
31285
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
29727
31286
  this.viewerDateTimeChangeRemoval = null;
29728
- if (this.trackEntity) {
29729
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
29730
- this.trackEntity = null;
29731
- }
31287
+ (_b = this.simulationRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
31288
+ this.simulationRemoval = null;
31289
+ this.apiEntities.clear();
31290
+ (_c = this.trail) === null || _c === void 0 ? void 0 : _c.Dispose();
31291
+ this.trail = null;
29732
31292
  this.children.clear();
31293
+ this.localPositions.clear();
31294
+ this.jointSeries.clear();
31295
+ this.animatedIds.clear();
29733
31296
  this.visualsManager.RemoveRegos({
29734
31297
  menuItemId: this.item.id
29735
31298
  });
@@ -30187,6 +31750,9 @@ var MenuItemManager;
30187
31750
  this.OnSeriesUpdated.Trigger({ itemId, segments: [] });
30188
31751
  }
30189
31752
  }
31753
+ get Simulation() {
31754
+ return this.simulation;
31755
+ }
30190
31756
  get Monitor() {
30191
31757
  return this.sharedMonitor;
30192
31758
  }
@@ -30212,6 +31778,7 @@ var MenuItemManager;
30212
31778
  this.visualsRegister = visualsRegister;
30213
31779
  this.sharedGetters = new SharedGetters.Cache();
30214
31780
  this.sharedHistoric = new SharedHistoricGetters.Cache();
31781
+ this.simulation = EntitySimulation.Get(this.viewer);
30215
31782
  this.sharedMonitor = new CesiumViewMonitor.Monitor(this.viewer);
30216
31783
  this.tilesetInitQueue = new InitQueueTileset(this.getters);
30217
31784
  }
@@ -30425,7 +31992,8 @@ var MenuItemManager;
30425
31992
  monitor: this.sharedMonitor,
30426
31993
  item: params.item,
30427
31994
  sharedGetters: this.sharedGetters,
30428
- onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment)
31995
+ onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment),
31996
+ simulation: this.simulation
30429
31997
  });
30430
31998
  break;
30431
31999
  case MenuItem.EType.EntitiesLoaded:
@@ -30434,7 +32002,8 @@ var MenuItemManager;
30434
32002
  register: this.visualsRegister,
30435
32003
  apiGetter: params.apiGetter,
30436
32004
  monitor: this.sharedMonitor,
30437
- item: params.item
32005
+ item: params.item,
32006
+ simulation: this.simulation
30438
32007
  });
30439
32008
  break;
30440
32009
  case MenuItem.EType.EntitiesIds:
@@ -30444,7 +32013,8 @@ var MenuItemManager;
30444
32013
  apiGetter: params.apiGetter,
30445
32014
  monitor: this.sharedMonitor,
30446
32015
  item: params.item,
30447
- sharedHistoric: this.sharedHistoric
32016
+ sharedHistoric: this.sharedHistoric,
32017
+ simulation: this.simulation
30448
32018
  });
30449
32019
  break;
30450
32020
  case MenuItem.EType.Entity:
@@ -30453,7 +32023,8 @@ var MenuItemManager;
30453
32023
  register: this.visualsRegister,
30454
32024
  apiGetter: params.apiGetter,
30455
32025
  monitor: this.sharedMonitor,
30456
- item: params.item
32026
+ item: params.item,
32027
+ simulation: this.simulation
30457
32028
  });
30458
32029
  break;
30459
32030
  case MenuItem.EType.CadTileset:
@@ -30463,7 +32034,8 @@ var MenuItemManager;
30463
32034
  getters: this.getters,
30464
32035
  item: params.item,
30465
32036
  modelSpace: params.modelSpace,
30466
- initQueue: this.tilesetInitQueue
32037
+ initQueue: this.tilesetInitQueue,
32038
+ simulation: this.simulation
30467
32039
  });
30468
32040
  break;
30469
32041
  case MenuItem.EType.Assembly:
@@ -30473,6 +32045,7 @@ var MenuItemManager;
30473
32045
  getters: params.getters,
30474
32046
  item: params.item,
30475
32047
  modelSpace: params.modelSpace,
32048
+ simulation: this.simulation
30476
32049
  });
30477
32050
  break;
30478
32051
  case MenuItem.EType.Osm:
@@ -30500,7 +32073,8 @@ var MenuItemManager;
30500
32073
  getters: this.getters,
30501
32074
  monitor: this.sharedMonitor,
30502
32075
  item: params.item,
30503
- initQueue: this.tilesetInitQueue
32076
+ initQueue: this.tilesetInitQueue,
32077
+ simulation: this.simulation
30504
32078
  });
30505
32079
  break;
30506
32080
  case MenuItem.EType.Kml:
@@ -30524,7 +32098,8 @@ var MenuItemManager;
30524
32098
  getters: params.getters,
30525
32099
  item: params.item,
30526
32100
  viewer: this.viewer,
30527
- register: this.visualsRegister
32101
+ register: this.visualsRegister,
32102
+ simulation: this.simulation
30528
32103
  });
30529
32104
  break;
30530
32105
  // Deprecated to support a few demos until new one is phased in.
@@ -30534,7 +32109,8 @@ var MenuItemManager;
30534
32109
  apiGetter: params.apiGetter,
30535
32110
  item: params.item,
30536
32111
  register: this.visualsRegister,
30537
- viewer: this.viewer
32112
+ viewer: this.viewer,
32113
+ simulation: this.simulation
30538
32114
  });
30539
32115
  break;
30540
32116
  case MenuItem.EType.Relationships:
@@ -30542,7 +32118,8 @@ var MenuItemManager;
30542
32118
  apiGetter: params.apiGetter,
30543
32119
  item: params.item,
30544
32120
  register: this.visualsRegister,
30545
- viewer: this.viewer
32121
+ viewer: this.viewer,
32122
+ simulation: this.simulation
30546
32123
  });
30547
32124
  break;
30548
32125
  case MenuItem.EType.GooglePhotoTileset:
@@ -30574,7 +32151,8 @@ var MenuItemManager;
30574
32151
  item: params.item,
30575
32152
  apiGetter: params.apiGetter,
30576
32153
  monitor: this.sharedMonitor,
30577
- visualsRegister: this.visualsRegister
32154
+ visualsRegister: this.visualsRegister,
32155
+ simulation: this.simulation
30578
32156
  });
30579
32157
  break;
30580
32158
  // Any "none" type can be ignored. These are just containers for other items.
@@ -30787,6 +32365,63 @@ var MenuItemManager;
30787
32365
  }
30788
32366
  }
30789
32367
  }
32368
+ /**
32369
+ * Draws Entities from data given here as well as, or instead of, the API's, until ClearSimulation is called.
32370
+ * @param params.set the simulation this belongs to, so it can be cleared apart from others. Default "default".
32371
+ * @param params.entities stand-ins that replace each Entity's record; API data for them is ignored.
32372
+ * @param params.patches partial Entities laid over each Entity; live API data shows where a patch is silent.
32373
+ * @param params.records historic records naming entityId, dateTime and only the data they change.
32374
+ * @param params.menuItemIds the items to redraw. By default every item already drawing one of the Entities.
32375
+ */
32376
+ async Simulate(params) {
32377
+ const { set, entities, patches, records, menuItemIds } = params;
32378
+ this.simulation.SetEntities(entities, set);
32379
+ this.simulation.SetPatches(patches, set);
32380
+ this.simulation.SetRecords(records, set);
32381
+ const entityIds = [
32382
+ ...(entities || []).map(x => { var _a; return (_a = x === null || x === void 0 ? void 0 : x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }),
32383
+ ...(patches || []).map(x => { var _a; return (_a = x === null || x === void 0 ? void 0 : x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }),
32384
+ ...(records || []).map(x => x === null || x === void 0 ? void 0 : x.entityId)
32385
+ ];
32386
+ await this.reRenderSimulated(entityIds, menuItemIds);
32387
+ }
32388
+ /**
32389
+ * Stops simulating these Entities, or every Entity when none are named, and redraws them.
32390
+ * @param params.set the simulation to clear. By default every one.
32391
+ * @param params.entities clear the stand-ins and patches. Default true.
32392
+ * @param params.records clear the historic records. Default true.
32393
+ */
32394
+ async ClearSimulation(params) {
32395
+ var _a;
32396
+ const entityIds = (_a = params === null || params === void 0 ? void 0 : params.entityIds) !== null && _a !== void 0 ? _a : [
32397
+ ...this.simulation.GetEntityIds(),
32398
+ ...this.simulation.GetRecordEntityIds()
32399
+ ];
32400
+ 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 });
32401
+ await this.reRenderSimulated(entityIds, params === null || params === void 0 ? void 0 : params.menuItemIds);
32402
+ }
32403
+ /*
32404
+ * Redraws simulated Entities only in the items holding a visual for them, unless the caller names the items.
32405
+ */
32406
+ async reRenderSimulated(entityIds, menuItemIds) {
32407
+ const ids = Array.from(new Set(entityIds.filter(x => !!x)));
32408
+ if (!ids.length) {
32409
+ return;
32410
+ }
32411
+ const items = (menuItemIds === null || menuItemIds === void 0 ? void 0 : menuItemIds.length) ? this.items.filter(x => menuItemIds.includes(x.id)) : this.items;
32412
+ for (const item of items) {
32413
+ // Some managers follow the store themselves, and redrawing them here would only repeat that.
32414
+ if (!item.renderManager || item.renderManager.FollowsSimulation) {
32415
+ continue;
32416
+ }
32417
+ const drawn = (menuItemIds === null || menuItemIds === void 0 ? void 0 : menuItemIds.length)
32418
+ ? ids
32419
+ : ids.filter(id => this.visualsRegister.GetRego({ entityId: id, menuItemId: item.id }));
32420
+ if (drawn.length) {
32421
+ await item.renderManager.ReRender({ entityIds: drawn, force: true });
32422
+ }
32423
+ }
32424
+ }
30790
32425
  }
30791
32426
  MenuItemManager.Manager = Manager;
30792
32427
  })(MenuItemManager || (MenuItemManager = {}));
@@ -39346,6 +40981,7 @@ function groupAttributes(paths, attributes) {
39346
40981
  }
39347
40982
  class WidgetInfoView extends Widget.AWidget {
39348
40983
  constructor(params) {
40984
+ var _a;
39349
40985
  super(params);
39350
40986
  this.STYLESHEET_ID = "nextspace-info-view-stylesheet";
39351
40987
  this._generateStyles();
@@ -39355,10 +40991,19 @@ class WidgetInfoView extends Widget.AWidget {
39355
40991
  this._updateInfoView();
39356
40992
  }
39357
40993
  });
40994
+ // The selected Entity is read once per selection, so a change to its simulation reads it again.
40995
+ this._simulationRemoval = (_a = this._manager.Simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
40996
+ if (this._lastSelectedId && change.entityIds.includes(this._lastSelectedId)) {
40997
+ this._lastSelectedId = null;
40998
+ this._updateInfoView();
40999
+ }
41000
+ });
39358
41001
  }
39359
41002
  Dispose() {
41003
+ var _a;
39360
41004
  super.Dispose();
39361
41005
  this._updateRemoval();
41006
+ (_a = this._simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
39362
41007
  }
39363
41008
  _generateStyles() {
39364
41009
  if (document.getElementById(this.STYLESHEET_ID)) {
@@ -39820,7 +41465,7 @@ class WidgetInfoView extends Widget.AWidget {
39820
41465
  }
39821
41466
  }
39822
41467
  async _updateInfoView() {
39823
- var _b, _c, _d, _e;
41468
+ var _a, _c, _d, _e, _f;
39824
41469
  const selectedIds = this._manager.VisualsRegister.GetSelected();
39825
41470
  if (selectedIds.length <= 0) {
39826
41471
  this._element.style.display = "none";
@@ -39842,17 +41487,20 @@ class WidgetInfoView extends Widget.AWidget {
39842
41487
  this._summary.innerHTML = "";
39843
41488
  this._imageContainer.style.display = "none";
39844
41489
  const api = this._apiGetters.GetBruceApi();
39845
- const { entity } = await Entity$1.Get({
41490
+ // A simulated Entity is shown as simulated: a stand-in as is, a patch over what the API has.
41491
+ const simulation = this._manager.Simulation;
41492
+ let entity = (_a = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(firstId)) !== null && _a !== void 0 ? _a : (await Entity$1.Get({
39846
41493
  entityId: firstId,
39847
41494
  api: api,
39848
41495
  migrated: true,
39849
41496
  expandLocation: DISPLAY_ASSEMBLY_INFO
39850
- });
41497
+ })).entity;
41498
+ entity = entity && simulation ? simulation.Resolve(firstId, entity) : entity;
39851
41499
  if (!entity) {
39852
41500
  throw (new Error("Could not get entity."));
39853
41501
  }
39854
41502
  const { entityType } = await EntityType.Get({
39855
- entityTypeId: (_b = entity.Bruce) === null || _b === void 0 ? void 0 : _b["EntityType.ID"],
41503
+ entityTypeId: (_c = entity.Bruce) === null || _c === void 0 ? void 0 : _c["EntityType.ID"],
39856
41504
  api: api
39857
41505
  });
39858
41506
  let name = Entity$1.CalculateName({
@@ -39875,7 +41523,7 @@ class WidgetInfoView extends Widget.AWidget {
39875
41523
  const defaultImage = images.length ? images[0] : null;
39876
41524
  if (defaultImage) {
39877
41525
  this._imageContainer.style.display = "flex";
39878
- const url = (_c = defaultImage.URL) !== null && _c !== void 0 ? _c : ClientFile.GetUrl({
41526
+ const url = (_d = defaultImage.URL) !== null && _d !== void 0 ? _d : ClientFile.GetUrl({
39879
41527
  fileId: defaultImage["ClientFile.ID"],
39880
41528
  api: api,
39881
41529
  viaCdn: true
@@ -39888,7 +41536,7 @@ class WidgetInfoView extends Widget.AWidget {
39888
41536
  this._summary.appendChild(detailsTitle);
39889
41537
  // TODO: Selection for what mode instead of always doing "all".
39890
41538
  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 : []);
41539
+ const groups = groupAttributes(attributes, (_f = (_e = entityType.DataSchema) === null || _e === void 0 ? void 0 : _e.Structure) !== null && _f !== void 0 ? _f : []);
39892
41540
  for (const group of groups) {
39893
41541
  this._generateAttrGroup(entityType, entity, group);
39894
41542
  }
@@ -42485,7 +44133,7 @@ var EntityUtils;
42485
44133
  * @param params
42486
44134
  */
42487
44135
  async function GetLocation(params) {
42488
- var _a, _b, _c, _d;
44136
+ var _a, _b, _c, _d, _e;
42489
44137
  let { api, viewer, samples, visualRegister, minimumAlt, paddingAlt } = params;
42490
44138
  if (!paddingAlt) {
42491
44139
  paddingAlt = 0;
@@ -43033,6 +44681,13 @@ var EntityUtils;
43033
44681
  recordSamples.push(sample);
43034
44682
  }
43035
44683
  }
44684
+ // A simulated Entity is where its blob says, never where the API has it.
44685
+ const simulation = EntitySimulation.Get(viewer);
44686
+ for (const sample of recordSamples) {
44687
+ if (!sample.entity && sample.entityId) {
44688
+ sample.entity = (_e = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(sample.entityId)) !== null && _e !== void 0 ? _e : sample.entity;
44689
+ }
44690
+ }
43036
44691
  // Bulk requesting Entity records.
43037
44692
  try {
43038
44693
  const toSearchIds = recordSamples.filter(x => !x.entity && x.entityId).map(x => x.entityId);
@@ -43053,7 +44708,8 @@ var EntityUtils;
43053
44708
  for (let i = 0; i < recordSamples.length; i++) {
43054
44709
  const sample = recordSamples[i];
43055
44710
  if (!sample.entity && sample.entityId) {
43056
- sample.entity = entitiesMap.get(sample.entityId);
44711
+ const read = entitiesMap.get(sample.entityId);
44712
+ sample.entity = read && simulation ? simulation.Overlay(read) : read;
43057
44713
  }
43058
44714
  }
43059
44715
  }
@@ -43067,6 +44723,11 @@ var EntityUtils;
43067
44723
  // We have to detect whatever is an Assembly Entity and expand it.
43068
44724
  try {
43069
44725
  const needsExpansion = recordSamples.filter(x => {
44726
+ var _a;
44727
+ // Expanding reads the API's record, which would replace the stand-in or undo the patch.
44728
+ if (simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob((_a = x.entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID)) {
44729
+ return false;
44730
+ }
43070
44731
  const assemblyPos = Entity$1.GetValue({
43071
44732
  entity: x.entity,
43072
44733
  path: ["Bruce", "AssemblyPosition"]
@@ -43425,6 +45086,21 @@ var EntityUtils;
43425
45086
  return Quaternion.multiply(combined, _assemblyAxisShift, new Quaternion());
43426
45087
  }
43427
45088
  EntityUtils.GetAssemblyOrientation = GetAssemblyOrientation;
45089
+ /**
45090
+ * Returns the orientation a part's model is drawn with, for its world matrix within the root's frame.
45091
+ * @param rootFrame the root's frame in the world, eg. from Transforms.headingPitchRollToFixedFrame.
45092
+ * @param world the part's matrix within the root's frame.
45093
+ */
45094
+ function GetAssemblyOrientationInFrame(rootFrame, world) {
45095
+ if (!rootFrame || !world) {
45096
+ return null;
45097
+ }
45098
+ const combined = Matrix4.multiply(rootFrame, world, new Matrix4());
45099
+ const rotation = Matrix4.getRotation(combined, new Matrix3());
45100
+ const q = Quaternion.fromRotationMatrix(rotation);
45101
+ return Quaternion.multiply(q, _assemblyAxisShift, new Quaternion());
45102
+ }
45103
+ EntityUtils.GetAssemblyOrientationInFrame = GetAssemblyOrientationInFrame;
43428
45104
  /**
43429
45105
  * Returns the position of the entity.
43430
45106
  * This is an async variant that checks for related assemblies.
@@ -43434,6 +45110,18 @@ var EntityUtils;
43434
45110
  */
43435
45111
  async function GetPosAsync(params) {
43436
45112
  let { viewer, entityId, entity, visualRegister, returnHeightRef, recordHeightRef, api } = params;
45113
+ // A simulated Entity is where its stand-in says, or where its patch puts the API's record.
45114
+ const simulation = EntitySimulation.Get(viewer);
45115
+ entity = entity !== null && entity !== void 0 ? entity : simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(entityId);
45116
+ if (!entity && entityId && (simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob(entityId))) {
45117
+ try {
45118
+ const read = (await Entity$1.Get({ api, entityId, expandLocation: false, migrated: true })).entity;
45119
+ entity = read ? simulation.Overlay(read) : null;
45120
+ }
45121
+ catch (e) {
45122
+ console.error(e);
45123
+ }
45124
+ }
43437
45125
  // We have issues where imported BRZ files have improper expandLocation=true info.
43438
45126
  // As a result we'll double-request the data.
43439
45127
  // If there is a location then we won't bother using expanded information.
@@ -53434,7 +55122,7 @@ var StyleUtils;
53434
55122
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
53435
55123
  })(StyleUtils || (StyleUtils = {}));
53436
55124
 
53437
- const VERSION = "7.6.1";
55125
+ const VERSION = "7.6.2";
53438
55126
  /**
53439
55127
  * Updates the environment instance used by bruce-cesium to one specified.
53440
55128
  * 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 +55139,5 @@ const getENVIRONMENT = () => {
53451
55139
  return ENVIRONMENT;
53452
55140
  };
53453
55141
 
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 };
55142
+ 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, 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
55143
  //# sourceMappingURL=bruce-cesium.es5.js.map