bruce-cesium 7.6.0 → 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 +2057 -348
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2050 -345
  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 +94 -12
  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 +5 -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, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, PixelDatatype, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, ColorBlendMode, HeadingPitchRoll, ShadowMode, ClassificationType, Model, JulianDate, Quaternion, ArcType, CornerType, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, HorizontalOrigin, VerticalOrigin, SceneMode, IntersectionTests, Ray, SceneTransforms, FeatureDetection, Matrix3, NearFarScalar, KmlDataSource, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileColorBlendMode, Cesium3DTileset, OrthographicFrustum, EasingFunction, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, Intersect, PolygonPipeline, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, GeometryInstance, CustomDataSource, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
- import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, Entity as Entity$1, EntityLod, EntityTag, EntityType, ObjectUtils, Style, UrlUtils, Carto, Geometry as Geometry$1, Bounds, ClientFileValueMap, Color as Color$1, Api, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, 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.
@@ -6436,6 +6873,22 @@ var EntityLabel;
6436
6873
  return centre && half && (axes === null || axes === void 0 ? void 0 : axes.every(Boolean)) ? { centre, axes, half } : null;
6437
6874
  }
6438
6875
  EntityLabel.ReadOrientedBox = ReadOrientedBox;
6876
+ /*
6877
+ * Whether a placement turns what it places. Scale alone does not count.
6878
+ */
6879
+ function isRotated(matrix) {
6880
+ for (let column = 0; column < 3; column++) {
6881
+ const axis = [0, 1, 2].map(row => Number(matrix[row][column]));
6882
+ const length = Math.hypot(...axis);
6883
+ if (!(length > 0)) {
6884
+ return false;
6885
+ }
6886
+ if (axis.some((value, row) => Math.abs(value / length - Number(row === column)) > 1e-5)) {
6887
+ return true;
6888
+ }
6889
+ }
6890
+ return false;
6891
+ }
6439
6892
  /*
6440
6893
  * The eight padded world corners of an assembly Entity's geometry box, or null when the record cannot give one.
6441
6894
  *
@@ -6454,6 +6907,10 @@ var EntityLabel;
6454
6907
  if ((matrix === null || matrix === void 0 ? void 0 : matrix.length) !== 4 || !matrix.every(row => (row === null || row === void 0 ? void 0 : row.length) === 4) || !Number.isFinite(+(root === null || root === void 0 ? void 0 : root.latitude)) || !Number.isFinite(+(root === null || root === void 0 ? void 0 : root.longitude))) {
6455
6908
  return null;
6456
6909
  }
6910
+ // A pure translation can hide a rotation baked into the vertices, which leaves the extents loose.
6911
+ if (!shape && !isRotated(matrix)) {
6912
+ return null;
6913
+ }
6457
6914
  const pivot = ((_c = bruce.GeometryPivot) === null || _c === void 0 ? void 0 : _c.length) >= 3 ? bruce.GeometryPivot.slice(0, 3).map(Number) : [0, 0, 0];
6458
6915
  // Without an oriented box the record's own extents are an axis-aligned box about the pivot.
6459
6916
  const box = shape || { centre: pivot, axes: [[1, 0, 0], [0, 1, 0], [0, 0, 1]], half: half };
@@ -6744,6 +7201,7 @@ var EntityLabel;
6744
7201
  constructor() {
6745
7202
  // Whether a box competes for space comes only from inputs (camera distance, hover, the pointer), never from
6746
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.
6747
7205
  this.entries = new Map();
6748
7206
  this.groups = null;
6749
7207
  // The hovered Entity. Its box is drawn on its own and whole, ahead of every other box.
@@ -6761,7 +7219,7 @@ var EntityLabel;
6761
7219
  Set(id, corners, entityId, inputs) {
6762
7220
  const current = this.entries.get(id);
6763
7221
  // A label rebuilt over the same box keeps its place and how far it has animated.
6764
- 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) {
6765
7223
  Object.assign(current, inputs);
6766
7224
  return;
6767
7225
  }
@@ -6769,17 +7227,41 @@ var EntityLabel;
6769
7227
  return;
6770
7228
  }
6771
7229
  if (corners) {
6772
- 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 });
6773
7231
  }
6774
7232
  else
6775
7233
  this.entries.delete(id);
6776
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) {
6777
7241
  this.groups = null;
6778
7242
  for (const [key, group] of this.outlines) {
6779
7243
  if (group.members.includes(id))
6780
7244
  this.outlines.delete(key);
6781
7245
  }
6782
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
+ }
6783
7265
  /*
6784
7266
  * The box drawn for a label this frame, or undefined when it has none or lost its place.
6785
7267
  */
@@ -6847,6 +7329,7 @@ var EntityLabel;
6847
7329
  */
6848
7330
  Resolve(viewer, pointer) {
6849
7331
  var _a, _b, _c, _d;
7332
+ this.follow();
6850
7333
  this.frames.clear();
6851
7334
  this.hulls.clear();
6852
7335
  this.drawn = [];
@@ -7269,9 +7752,16 @@ var EntityLabel;
7269
7752
  }
7270
7753
  if (this.api) {
7271
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
+ }
7272
7762
  this.rego.name = await Entity$1.CalculateNameAsync({
7273
7763
  api: this.api,
7274
- entity: null,
7764
+ entity: simulated,
7275
7765
  entityId: rego.entityId,
7276
7766
  defaultToId: false
7277
7767
  });
@@ -7300,6 +7790,21 @@ var EntityLabel;
7300
7790
  clearTimeout(this.loadTimeout);
7301
7791
  this.loadTimeout = null;
7302
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
+ }
7303
7808
  /*
7304
7809
  * Rebuilds only when configuration changes; state refreshes must not recreate DOM every frame.
7305
7810
  */
@@ -7530,7 +8035,7 @@ var EntityLabel;
7530
8035
  }
7531
8036
  clearTimeout(this.loadTimeout);
7532
8037
  this.loadTimeout = setTimeout(async () => {
7533
- var _a, _b, _c, _d, _e;
8038
+ var _a, _b, _c, _d, _e, _f;
7534
8039
  if (this.disposed || this.counter != counter) {
7535
8040
  return;
7536
8041
  }
@@ -7557,16 +8062,19 @@ var EntityLabel;
7557
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;
7558
8063
  // Read for its attributes, and for a photo when the label has no icon of its own.
7559
8064
  if (attributes || !HasIcon(this.options)) {
7560
- 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;
7561
8068
  try {
7562
- entity = (await Entity$1.Get({
8069
+ entity = entity !== null && entity !== void 0 ? entity : (await Entity$1.Get({
7563
8070
  api: this.api,
7564
8071
  entityId: this.rego.entityId,
7565
- 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,
7566
8073
  schemaId: this.rego.schema,
7567
8074
  migrated: true,
7568
8075
  expandAttachments: !HasIcon(this.options)
7569
8076
  })).entity;
8077
+ entity = entity && simulation ? simulation.Resolve(this.rego.entityId, entity) : entity;
7570
8078
  }
7571
8079
  // Entity doesn't exist.
7572
8080
  // Likely a graphic with an ID that points to no record.
@@ -7950,11 +8458,17 @@ var EntityLabel;
7950
8458
  // Past the wait a slow record no longer holds the label back; a box that comes later eases in.
7951
8459
  setTimeout(settle, MODEL_BOX_WAIT);
7952
8460
  this.modelBox.load.then(corners => {
8461
+ var _a, _b;
7953
8462
  if (!corners || this.disposed || this.counter !== counter) {
7954
8463
  return;
7955
8464
  }
7956
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);
7957
8468
  const centre = Cartesian3.midpoint(corners[0], corners[7], new Cartesian3());
8469
+ if (motion) {
8470
+ Matrix4.multiplyByPoint(motion, centre, centre);
8471
+ }
7958
8472
  if (Cartesian3.distance(centre, found) > Cartesian3.distance(corners[0], corners[7]) * 0.75 + 2) {
7959
8473
  return;
7960
8474
  }
@@ -7962,6 +8476,7 @@ var EntityLabel;
7962
8476
  // Let go a little further out than it is taken, so a camera resting at the limit does not flip it.
7963
8477
  let near = false;
7964
8478
  layer.modelBoxes.Set(this.id, corners, rego.entityId, {
8479
+ motion: rego.motion ? () => rego.motion(this.viewer.clock.currentTime) : undefined,
7965
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); },
7966
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)); },
7967
8482
  near: () => {
@@ -8168,7 +8683,7 @@ var EntityLabel;
8168
8683
  viewportWidth: this.viewer.canvas.clientWidth,
8169
8684
  id: this.id, entityKey: ((_x = this.rego) === null || _x === void 0 ? void 0 : _x.entityId) ? JSON.stringify([this.rego.entityId, this.rego.menuItemId || "", this.Transient && !this.Persistent ? "hover" : ""]) : undefined,
8170
8685
  entityId: (_y = this.rego) === null || _y === void 0 ? void 0 : _y.entityId, group: this.groupId, x, y, width, height, anchorX, anchorY, depth, leaderLength, leaderRun, leaderSide, distance,
8171
- iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius, modelBox: boxed || undefined, boxBlend,
8686
+ iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius, modelBox: boxed || undefined, boxBlend, follows: !pinned && Boolean(transientPivot || cursorMotion.target),
8172
8687
  // Priority shows the hovered member individually without dragging its cluster along.
8173
8688
  opacity: waiting ? 0 : opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
8174
8689
  };
@@ -8295,15 +8810,16 @@ var EntityLabel;
8295
8810
  const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((_l = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _l !== void 0 ? _l : 100) ? ((_m = ring === null || ring === void 0 ? void 0 : ring.nearRadius) !== null && _m !== void 0 ? _m : 10) : 0, hovered ? ((_o = ring === null || ring === void 0 ? void 0 : ring.hoverRadius) !== null && _o !== void 0 ? _o : (this.Transient && !this.Persistent ? 15.4 : 22)) : 0));
8296
8811
  const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
8297
8812
  // Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
8298
- const targetOuter = iconRing || targetRadius || 2.5;
8299
- const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
8813
+ // A ring around the cursor marks nothing, so a riding label keeps only its leader.
8814
+ const targetOuter = frame.follows ? 0 : iconRing || targetRadius || 2.5;
8815
+ const targetInner = frame.follows ? 0 : iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
8300
8816
  const now = Date.now();
8301
8817
  const duration = (_p = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _p !== void 0 ? _p : 200;
8302
8818
  const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
8303
8819
  let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
8304
8820
  if (!ringAnimation) {
8305
- const outer = iconRing || 2.5;
8306
- const inner = iconRing;
8821
+ const outer = frame.follows ? 0 : iconRing || 2.5;
8822
+ const inner = frame.follows ? 0 : iconRing;
8307
8823
  ringAnimation = { target: outer, targetInner: inner, start: now, outer, inner, fromOuter: outer, fromInner: inner };
8308
8824
  }
8309
8825
  const animateRing = () => {
@@ -8942,9 +9458,9 @@ var EntityLabelGroup;
8942
9458
  * Reads the entities behind items that name one without carrying it, attachments included,
8943
9459
  * so a label's default image needs no second request.
8944
9460
  */
8945
- async function Hydrate(items, api, signal) {
9461
+ async function Hydrate(items, api, signal, simulation) {
8946
9462
  var _a, _b;
8947
- if (!api) {
9463
+ if (!api && !simulation) {
8948
9464
  return items;
8949
9465
  }
8950
9466
  const hydrated = items.slice();
@@ -8955,12 +9471,15 @@ var EntityLabelGroup;
8955
9471
  }
8956
9472
  const chunk = missing.slice(start, start + HYDRATE_AT_ONCE);
8957
9473
  try {
8958
- const { entities } = await Entity$1.GetListByIds({
8959
- api, entityIds: chunk.map(at => hydrated[at].entityId), expandAttachments: true, migrated: true
8960
- });
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: [] };
8961
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]; }));
8962
9480
  for (const at of chunk) {
8963
- 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);
8964
9483
  if (entity) {
8965
9484
  // The API leaves Attachment out when there are none; an empty list records that they were read.
8966
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 : [] } } };
@@ -8977,7 +9496,7 @@ var EntityLabelGroup;
8977
9496
  /*
8978
9497
  * Uses the existing DataLab grammar, discarding actions and projections that cannot return Entities.
8979
9498
  */
8980
- async function Loader(source, api, signal, hydrate) {
9499
+ async function Loader(source, api, signal, hydrate, simulation) {
8981
9500
  var _a;
8982
9501
  var _b;
8983
9502
  if (source.type === "provider") {
@@ -8992,7 +9511,7 @@ var EntityLabelGroup;
8992
9511
  return async (offset, limit, signal) => {
8993
9512
  const page = items.slice(offset, offset + limit);
8994
9513
  return {
8995
- items: hydrate ? await Hydrate(page, api, signal) : page,
9514
+ items: hydrate ? await Hydrate(page, api, signal, simulation) : page,
8996
9515
  hasMore: offset + limit < items.length
8997
9516
  };
8998
9517
  };
@@ -9134,7 +9653,7 @@ var EntityLabelGroup;
9134
9653
  const items = new Map();
9135
9654
  let truncated = false;
9136
9655
  try {
9137
- 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));
9138
9657
  let offset = 0;
9139
9658
  for (let pageNumber = 0;; pageNumber++) {
9140
9659
  if (controller.signal.aborted || this.register.Disposed) {
@@ -9956,8 +10475,25 @@ var RelationRenderEngine;
9956
10475
  return `${relation["Related.Entity.ID"]}-${relation["Principal.Entity.ID"]}-${relation["Relation.Type.ID"]}`;
9957
10476
  }
9958
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;
9959
10495
  async function Render(params) {
9960
- const { apiGetter, viewer, visualRegister, menuItemId, relations } = params;
10496
+ const { apiGetter, viewer, visualRegister, menuItemId, relations, simulation } = params;
9961
10497
  const api = apiGetter.getApi(apiGetter.accountId);
9962
10498
  const rendered = {};
9963
10499
  let neededEntityIdsIds = [];
@@ -9975,13 +10511,15 @@ var RelationRenderEngine;
9975
10511
  }
9976
10512
  // Turn the set unique.
9977
10513
  neededEntityIdsIds = neededEntityIdsIds.filter((v, i, a) => a.indexOf(v) === i);
9978
- // Get all entities in one go.
9979
- let entities = (await Entity$1.GetListByIds({
9980
- 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,
9981
10519
  api: apiGetter.getApi(),
9982
10520
  migrated: true,
9983
10521
  maxSearchTimeSec: 60 * 2
9984
- })).entities;
10522
+ })).entities.map(entity => simulation ? simulation.Overlay(entity) : entity) : []).concat(simulated);
9985
10523
  // Create a map for quick reference.
9986
10524
  const entitiesMap = {};
9987
10525
  for (let i = 0; i < entities.length; i++) {
@@ -10475,6 +11013,8 @@ function isCullingIgnored(viewer, rego) {
10475
11013
  rego.overrideShow != null ||
10476
11014
  // Being dragged around right now, so its extent is changing faster than we could measure it.
10477
11015
  rego.editing == true ||
11016
+ // Placed by the clock every frame, so no measurement of where it is stays true.
11017
+ rego.moving == true ||
10478
11018
  // Part of a collection we did not create, eg. a geojson data source, which may have its own rules for
10479
11019
  // what is in the scene. Our own per-menu-item data sources are fair game.
10480
11020
  VisualsRegister.IsForeignCollection(rego) ||
@@ -11314,6 +11854,7 @@ var VisualsRegister;
11314
11854
  }
11315
11855
  get OnLabelGroupsChanged() { return this.labelGroupsChanged; }
11316
11856
  constructor(params) {
11857
+ var _a, _b;
11317
11858
  this.id = ObjectUtils.UId();
11318
11859
  this.disposed = false;
11319
11860
  this.rego = {};
@@ -11353,6 +11894,8 @@ var VisualsRegister;
11353
11894
  this.menuItemSources = new Map();
11354
11895
  // Set when the data source order no longer matches the zIndexes seen, so it is re-applied once.
11355
11896
  this.sourceOrderDirty = false;
11897
+ // Stops following what is simulated, so labels can read it again when it changes.
11898
+ this.simulationRemoval = null;
11356
11899
  this.labelGroupsChanged = new BruceEvent();
11357
11900
  this.hoverRingState = {};
11358
11901
  this.retiringHoverLabels = new Map();
@@ -11387,6 +11930,33 @@ var VisualsRegister;
11387
11930
  this.cameraCullerDispose = VisualRegisterCuller.Monitor({
11388
11931
  register: this
11389
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
+ }
11390
11960
  }
11391
11961
  /**
11392
11962
  * Returns the collection a menu item's visuals belong in, creating its data source on first use.
@@ -12289,7 +12859,9 @@ var VisualsRegister;
12289
12859
  (_b = this.viewer.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
12290
12860
  }
12291
12861
  Dispose() {
12292
- 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;
12293
12865
  CesiumEntityStyler.SetDimming(this.viewer, this.id, false);
12294
12866
  this.ClearLabelHover();
12295
12867
  clearTimeout(this.hoverRingReset);
@@ -12299,14 +12871,14 @@ var VisualsRegister;
12299
12871
  EntityLabel.Detatch({ label });
12300
12872
  }
12301
12873
  this.retiringHoverLabels.clear();
12302
- (_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.Dispose();
12874
+ (_b = this.labelGroups) === null || _b === void 0 ? void 0 : _b.Dispose();
12303
12875
  this.labelGroupsChanged.Clear();
12304
12876
  for (const regos of Object.values(this.rego)) {
12305
12877
  for (const rego of regos)
12306
12878
  EntityLabel.Detatch({ rego, force: true });
12307
12879
  }
12308
12880
  EntityLabel.SetContainer(this.viewer);
12309
- (_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, {
12310
12882
  uncull: false
12311
12883
  });
12312
12884
  this.cameraCullerDispose = null;
@@ -12322,7 +12894,7 @@ var VisualsRegister;
12322
12894
  this.disposed = true;
12323
12895
  for (const record of Array.from(this.menuItemSources.values())) {
12324
12896
  try {
12325
- (_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);
12326
12898
  }
12327
12899
  catch (e) {
12328
12900
  console.error(e);
@@ -12331,7 +12903,7 @@ var VisualsRegister;
12331
12903
  this.menuItemSources.clear();
12332
12904
  // Hand every tileset's show flag back to its owner, holding whatever the owner last asked for.
12333
12905
  for (const rego of Array.from(this.tilesets.values())) {
12334
- (_e = rego.gate) === null || _e === void 0 ? void 0 : _e.Release();
12906
+ (_f = rego.gate) === null || _f === void 0 ? void 0 : _f.Release();
12335
12907
  }
12336
12908
  this.tilesets.clear();
12337
12909
  }
@@ -12505,21 +13077,7 @@ var VisualsRegister;
12505
13077
  changed = true;
12506
13078
  }
12507
13079
  if (editing) {
12508
- const parts = EntityUtils.GatherEntity({
12509
- entity: rego.visual
12510
- });
12511
- for (let k = 0; k < parts.length; k++) {
12512
- const part = parts[k];
12513
- if (!(part instanceof Entity)) {
12514
- continue;
12515
- }
12516
- const removalPending = VisualRegisterCuller.CancelQueuedSceneChange(part);
12517
- const wasCulled = part[VisualRegisterCuller.VISUAL_CULL_KEY] == true || removalPending;
12518
- delete part[VisualRegisterCuller.VISUAL_CULL_KEY];
12519
- if (wasCulled && !IsCEntityInScene(this.viewer, part)) {
12520
- AddCEntityToScene(this.viewer, part, rego.collection);
12521
- }
12522
- }
13080
+ this.restoreCulled(rego);
12523
13081
  }
12524
13082
  else {
12525
13083
  VisualRegisterCuller.InvalidateBounds(rego.visual);
@@ -12531,6 +13089,56 @@ var VisualsRegister;
12531
13089
  (_b = (_a = this.viewer) === null || _a === void 0 ? void 0 : _a.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
12532
13090
  }
12533
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
+ }
12534
13142
  SetSelected(params) {
12535
13143
  var _a;
12536
13144
  let { entityIds, selected, refreshIfSelected, requestRender, menuItemId, source } = params;
@@ -14023,12 +14631,41 @@ var VisualsRegister;
14023
14631
  * Tracks per-entity rerender state for unsaved blob maintenance and stale API response suppression.
14024
14632
  */
14025
14633
  class EntityReRenderMaintainState {
14026
- constructor() {
14634
+ constructor(simulation, pointInTime) {
14635
+ this.simulation = simulation;
14636
+ this.pointInTime = pointInTime;
14027
14637
  this.maintainedEntitiesById = {};
14028
14638
  this.apiRevisionByEntityId = {};
14029
14639
  this.skipApiRunByEntityId = {};
14030
14640
  this.checkRunId = 0;
14031
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
+ }
14032
14669
  setMaintainedEntities(entities) {
14033
14670
  var _a;
14034
14671
  for (let i = 0; i < entities.length; i++) {
@@ -14050,15 +14687,21 @@ class EntityReRenderMaintainState {
14050
14687
  }
14051
14688
  }
14052
14689
  getMaintainedEntities(entityIds) {
14690
+ var _a;
14053
14691
  const entities = [];
14054
14692
  for (let i = 0; i < entityIds.length; i++) {
14055
14693
  const id = entityIds[i];
14056
14694
  if (!id) {
14057
14695
  continue;
14058
14696
  }
14059
- const entity = this.maintainedEntitiesById[id];
14060
- if (entity) {
14061
- 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]);
14062
14705
  }
14063
14706
  }
14064
14707
  return entities;
@@ -14115,7 +14758,7 @@ class EntityReRenderMaintainState {
14115
14758
  }
14116
14759
  filterCurrentApiEntities(params) {
14117
14760
  const { entities, revisions, runId } = params;
14118
- return entities.filter((entity) => {
14761
+ const current = entities.filter((entity) => {
14119
14762
  var _a, _b, _c;
14120
14763
  const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
14121
14764
  if (!id) {
@@ -14124,8 +14767,12 @@ class EntityReRenderMaintainState {
14124
14767
  if (runId != null && this.shouldSkipInRun(id, runId)) {
14125
14768
  return false;
14126
14769
  }
14770
+ if (this.isSimulated(id)) {
14771
+ return false;
14772
+ }
14127
14773
  return ((_b = this.apiRevisionByEntityId[id]) !== null && _b !== void 0 ? _b : 0) === ((_c = revisions[id]) !== null && _c !== void 0 ? _c : 0);
14128
14774
  });
14775
+ return this.atPoint(this.simulation && !this.simulation.IsEmpty ? current.map((entity) => this.simulation.Overlay(entity)) : current);
14129
14776
  }
14130
14777
  }
14131
14778
 
@@ -15561,7 +16208,7 @@ class HistoricGatherer {
15561
16208
  this.cache = new HistoricRangeCache();
15562
16209
  }
15563
16210
  get Records() {
15564
- return this.cache.GetRecords();
16211
+ return this.withSimulated(this.cache.GetRecords());
15565
16212
  }
15566
16213
  Clear() {
15567
16214
  this.cache.Clear();
@@ -15585,8 +16232,9 @@ class HistoricGatherer {
15585
16232
  */
15586
16233
  async Gather(entities) {
15587
16234
  var _a;
15588
- if (this.disposed || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
15589
- 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) };
15590
16238
  }
15591
16239
  const { viewer } = this.params;
15592
16240
  const windowStart = new Date(viewer.clock.startTime.toString()).getTime();
@@ -15606,7 +16254,7 @@ class HistoricGatherer {
15606
16254
  if (plan.coarse && !this.cache.CoversCoarse(plan.coarse.start, plan.coarse.stop)) {
15607
16255
  const records = await this.request(plan.coarse.start, plan.coarse.stop, entities, plan.coarse.sample);
15608
16256
  if (this.disposed) {
15609
- return { fetched, recordsByIds: this.cache.GetRecords() };
16257
+ return { fetched, recordsByIds: this.withSimulated(this.cache.GetRecords(), entityIds) };
15610
16258
  }
15611
16259
  this.cache.Absorb(plan.coarse.start, this.holdTo(plan.coarse.stop, liveEdge, liveTail), records, true);
15612
16260
  fetched = true;
@@ -15641,7 +16289,17 @@ class HistoricGatherer {
15641
16289
  }
15642
16290
  }
15643
16291
  }
15644
- 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);
15645
16303
  }
15646
16304
  /*
15647
16305
  * Returns how much of a range that was just read may be marked as held.
@@ -15676,7 +16334,7 @@ class HistoricGatherer {
15676
16334
  }).then((res) => {
15677
16335
  const records = res.recordsByIds || {};
15678
16336
  if (!this.disposed && this.params.onRecords) {
15679
- this.params.onRecords(records);
16337
+ this.params.onRecords(this.withSimulated(records, entityIds));
15680
16338
  }
15681
16339
  return records;
15682
16340
  }).finally(() => {
@@ -15753,7 +16411,6 @@ var EntitiesRenderManager;
15753
16411
  this.renderQueue = [];
15754
16412
  this.renderQueueInterval = null;
15755
16413
  this.sources = [];
15756
- this.reRenderState = new EntityReRenderMaintainState();
15757
16414
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
15758
16415
  // Many things are not supported when this is enabled.
15759
16416
  this.useGeojson = false;
@@ -15773,6 +16430,14 @@ var EntitiesRenderManager;
15773
16430
  */
15774
16431
  this.renderAsIndividualsChain = Promise.resolve();
15775
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
+ });
15776
16441
  this.viewer = viewer;
15777
16442
  this.sharedGetters = sharedGetters;
15778
16443
  this.monitor = monitor;
@@ -15786,7 +16451,8 @@ var EntitiesRenderManager;
15786
16451
  getApi: () => this.apiGetter.getApi(),
15787
16452
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
15788
16453
  // A second either side, to take in records sitting exactly on the boundary.
15789
- paddingMs: 1000
16454
+ paddingMs: 1000,
16455
+ simulation: params.simulation
15790
16456
  });
15791
16457
  this.useGeojson = item.renderAsGeojson == true;
15792
16458
  if (item.enableClustering) {
@@ -15967,8 +16633,9 @@ var EntitiesRenderManager;
15967
16633
  return;
15968
16634
  }
15969
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.
15970
16637
  if (isTagItem) {
15971
- this.distributeForRender(data.entities.filter((entity) => {
16638
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities.filter((entity) => {
15972
16639
  let entityTags = entity.Bruce["Layer.ID"];
15973
16640
  if (!entityTags) {
15974
16641
  entityTags = [];
@@ -15976,10 +16643,10 @@ var EntitiesRenderManager;
15976
16643
  return entityTags.some((tag) => {
15977
16644
  return tagsToRender.indexOf(tag) !== -1;
15978
16645
  });
15979
- }));
16646
+ })));
15980
16647
  }
15981
16648
  else {
15982
- this.distributeForRender(data.entities);
16649
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities));
15983
16650
  }
15984
16651
  });
15985
16652
  // Cesium itself handles single-visibility ranges very well.
@@ -16019,6 +16686,10 @@ var EntitiesRenderManager;
16019
16686
  // Remove regos directly.
16020
16687
  if (event.action === "D") {
16021
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
+ }
16022
16693
  this.visualsManager.RemoveRegos({
16023
16694
  entityId,
16024
16695
  menuItemId: this.item.id,
@@ -17148,7 +17819,6 @@ var EntitiesLoadedRenderManager;
17148
17819
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
17149
17820
  // Many things are not supported when this is enabled.
17150
17821
  this.useGeojson = false;
17151
- this.reRenderState = new EntityReRenderMaintainState();
17152
17822
  /**
17153
17823
  * Our default path.
17154
17824
  * We render each entity individually.
@@ -17158,6 +17828,7 @@ var EntitiesLoadedRenderManager;
17158
17828
  */
17159
17829
  this.renderAsIndividualsChain = Promise.resolve();
17160
17830
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
17831
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
17161
17832
  this.viewer = viewer;
17162
17833
  this.apiGetter = apiGetter;
17163
17834
  this.monitor = monitor;
@@ -17280,7 +17951,8 @@ var EntitiesLoadedRenderManager;
17280
17951
  continue;
17281
17952
  }
17282
17953
  }
17283
- 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));
17284
17956
  });
17285
17957
  this.getter.Start();
17286
17958
  }
@@ -17927,8 +18599,15 @@ var EntitiesIdsRenderManager;
17927
18599
  this.renderQueueActive = false;
17928
18600
  this.renderQueue = [];
17929
18601
  this.renderQueueForceFlags = [];
17930
- this.reRenderState = new EntityReRenderMaintainState();
17931
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
+ });
17932
18611
  this.sharedHistoric = params.sharedHistoric;
17933
18612
  this.viewer = viewer;
17934
18613
  this.apiGetter = apiGetter;
@@ -17941,7 +18620,8 @@ var EntitiesIdsRenderManager;
17941
18620
  getApi: () => this.apiGetter.getApi(),
17942
18621
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
17943
18622
  // Padding either side of a requested range, for clock desync between us and the server.
17944
- paddingMs: 15000
18623
+ paddingMs: 15000,
18624
+ simulation: params.simulation
17945
18625
  });
17946
18626
  if (this.item.enableClustering) {
17947
18627
  this.clustering = new PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
@@ -18403,9 +19083,9 @@ var EntityRenderManager;
18403
19083
  this.getter = null;
18404
19084
  this.getterSub = null;
18405
19085
  this.disposed = false;
18406
- this.reRenderState = new EntityReRenderMaintainState();
18407
19086
  this.renderEntityChain = Promise.resolve();
18408
19087
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
19088
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
18409
19089
  this.viewer = viewer;
18410
19090
  this.apiGetter = apiGetter;
18411
19091
  this.monitor = monitor;
@@ -18679,6 +19359,122 @@ var CodeLogicExceptions;
18679
19359
  ];
18680
19360
  })(CodeLogicExceptions || (CodeLogicExceptions = {}));
18681
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
+
18682
19478
  class EntityGatherer {
18683
19479
  get OnQueueProgress() {
18684
19480
  if (!this._onQueueProgress) {
@@ -18686,7 +19482,11 @@ class EntityGatherer {
18686
19482
  }
18687
19483
  return this._onQueueProgress;
18688
19484
  }
18689
- 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;
18690
19490
  this.disposed = false;
18691
19491
  this.expandSources = false;
18692
19492
  this.historic = null;
@@ -19263,15 +20063,19 @@ class EntityGatherer {
19263
20063
  }
19264
20064
  }
19265
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;
19266
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.
19267
20071
  const override = this.entityDataOverrides.get(entityId);
19268
20072
  if (override) {
19269
20073
  resolvedEntities.push(override);
19270
20074
  continue;
19271
20075
  }
19272
- const found = foundById.get(entityId);
20076
+ const found = this.simulation ? this.simulation.Resolve(entityId, foundById.get(entityId)) : foundById.get(entityId);
19273
20077
  if (found) {
19274
- resolvedEntities.push(found);
20078
+ resolvedEntities.push(atPoint(found));
19275
20079
  continue;
19276
20080
  }
19277
20081
  resolvedEntities.push(this.createFallbackEntity(entityId));
@@ -19635,7 +20439,7 @@ class TilesetStyler {
19635
20439
  this.styleTilesetFeatureFullData(feature, entity, eTags);
19636
20440
  }
19637
20441
  }
19638
- });
20442
+ }, params.simulation);
19639
20443
  // Only Entities Sets bake coordinates into their features, so for any other source this
19640
20444
  // resolves nothing and the gather stays in insertion order.
19641
20445
  this.entityGatherer.SetViewMonitor(this.monitor);
@@ -21165,6 +21969,9 @@ var TilesetCadRenderManager;
21165
21969
  return this.modelTreeUpdate;
21166
21970
  }
21167
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;
21168
21975
  this.disposed = false;
21169
21976
  this.modelSpace = false;
21170
21977
  this.cTileset = null;
@@ -21202,7 +22009,7 @@ var TilesetCadRenderManager;
21202
22009
  this.historicAnimation = null;
21203
22010
  this.historicGatherer = null;
21204
22011
  // The trail through the root's own movement, which describes the whole assembly.
21205
- this.trackEntity = null;
22012
+ this.trail = null;
21206
22013
  this.historicPossesLoadingProm = null;
21207
22014
  // Queue of loaded in features that we haven't processed yet.
21208
22015
  this.featureQueue = [];
@@ -21216,6 +22023,7 @@ var TilesetCadRenderManager;
21216
22023
  this.featurePropCache = new Map();
21217
22024
  this.featurePropsChecked = 0;
21218
22025
  const { viewer, register: visualsManager, getters, item, modelSpace, initQueue } = params;
22026
+ this.simulation = params.simulation;
21219
22027
  this.viewer = viewer;
21220
22028
  this.getters = getters;
21221
22029
  this.item = item;
@@ -21380,7 +22188,7 @@ var TilesetCadRenderManager;
21380
22188
  historicPoint: this.viewer.clock.currentTime.toString(),
21381
22189
  api: api
21382
22190
  });
21383
- coords = coordsFromHistoricEntity(root);
22191
+ coords = coordsFromHistoricEntity(this.atClock(root));
21384
22192
  }
21385
22193
  catch (e) {
21386
22194
  console.error(e);
@@ -21421,6 +22229,10 @@ var TilesetCadRenderManager;
21421
22229
  this.doDispose();
21422
22230
  return;
21423
22231
  }
22232
+ this.restCoords = coords;
22233
+ if (this.simulatedRootCoords()) {
22234
+ this.applyCoords(tileset, this.simulatedRootCoords());
22235
+ }
21424
22236
  this.viewer.scene.requestRender();
21425
22237
  TilesetRenderEngine.OnTilesetReady(this.cTileset).then(() => {
21426
22238
  var _a, _b, _c;
@@ -21440,6 +22252,7 @@ var TilesetCadRenderManager;
21440
22252
  }
21441
22253
  this.onCTilesetLoad();
21442
22254
  this.styler.Init({
22255
+ simulation: this.simulation,
21443
22256
  viewer: this.viewer,
21444
22257
  // Default API instead of the account one.
21445
22258
  // Records are driven by the account loading it instead of the source account.
@@ -21455,6 +22268,7 @@ var TilesetCadRenderManager;
21455
22268
  });
21456
22269
  this.viewer.scene.requestRender();
21457
22270
  // Monitor scene time changes and update the root Entity.
22271
+ this.followSimulation(tileset);
21458
22272
  this.viewerDateTimeSub(tileset);
21459
22273
  }
21460
22274
  catch (e) {
@@ -21510,7 +22324,7 @@ var TilesetCadRenderManager;
21510
22324
  */
21511
22325
  applyRootTrack() {
21512
22326
  var _a;
21513
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
22327
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
21514
22328
  return;
21515
22329
  }
21516
22330
  const trackStyle = EntityRenderEngine.GetTrackStyle({
@@ -21520,26 +22334,14 @@ var TilesetCadRenderManager;
21520
22334
  if (!trackStyle) {
21521
22335
  return;
21522
22336
  }
21523
- this.trackEntity = this.viewer.entities.add({
21524
- id: ObjectUtils.UId(15),
21525
- polyline: {
21526
- 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),
21527
- width: trackStyle.width,
21528
- material: trackStyle.color,
21529
- clampToGround: EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
21530
- == HeightReference.CLAMP_TO_GROUND,
21531
- zIndex: 0,
21532
- classificationType: ClassificationType.BOTH
21533
- }
21534
- });
21535
- this.trackEntity._noEdit = true;
21536
- this.trackEntity._noSelect = true;
21537
- CesiumEntityStyler.SetDefaultColor({
21538
- color: trackStyle.color,
21539
- 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({
21540
22339
  viewer: this.viewer,
21541
- override: true,
21542
- 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 : []; }
21543
22345
  });
21544
22346
  }
21545
22347
  /**
@@ -21592,7 +22394,7 @@ var TilesetCadRenderManager;
21592
22394
  }
21593
22395
  // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
21594
22396
  PrepareHandoff() {
21595
- var _a, _b, _c, _d;
22397
+ var _a, _b, _c, _d, _e;
21596
22398
  if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
21597
22399
  return null;
21598
22400
  }
@@ -21610,9 +22412,11 @@ var TilesetCadRenderManager;
21610
22412
  this.handoffSourceMenuItemId = null;
21611
22413
  // Not transferred, the receiving manager sets up its own fresh copy if needed.
21612
22414
  this.viewerDateTimeDispose();
21613
- (_b = this.styler) === null || _b === void 0 ? void 0 : _b.Dispose();
21614
- (_c = this.tileLoadRemoval) === null || _c === void 0 ? void 0 : _c.call(this);
21615
- (_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);
21616
22420
  this.tileLoadRemoval = null;
21617
22421
  this.tileUnloadRemoval = null;
21618
22422
  const payload = {
@@ -21633,7 +22437,7 @@ var TilesetCadRenderManager;
21633
22437
  // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
21634
22438
  // Then re-parents every already-registered rego to this menu item and re-runs styling.
21635
22439
  AdoptHandoff(payload) {
21636
- var _a, _b, _c, _d;
22440
+ var _a, _b, _c, _d, _e;
21637
22441
  this.cTileset = payload.cTileset;
21638
22442
  this.tileset = payload.tileset;
21639
22443
  this.rootId = payload.rootId;
@@ -21664,6 +22468,7 @@ var TilesetCadRenderManager;
21664
22468
  // Same visuals, same registrations so just re-keyed onto this menu item.
21665
22469
  this.onCTilesetLoad();
21666
22470
  this.styler.Init({
22471
+ simulation: this.simulation,
21667
22472
  viewer: this.viewer,
21668
22473
  api: this.getters.GetBruceApi(),
21669
22474
  cTileset: this.cTileset,
@@ -21697,7 +22502,9 @@ var TilesetCadRenderManager;
21697
22502
  }
21698
22503
  });
21699
22504
  this.viewer.scene.requestRender();
21700
- 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);
21701
22508
  this.viewerDateTimeSub(this.tileset);
21702
22509
  }
21703
22510
  }
@@ -22285,7 +23092,7 @@ var TilesetCadRenderManager;
22285
23092
  this.doDispose();
22286
23093
  }
22287
23094
  doDispose() {
22288
- var _a, _b, _c;
23095
+ var _a, _b, _c, _d;
22289
23096
  if (this.featureQueueInterval) {
22290
23097
  clearInterval(this.featureQueueInterval);
22291
23098
  this.featureQueueInterval = null;
@@ -22320,6 +23127,8 @@ var TilesetCadRenderManager;
22320
23127
  this.handoffSourceMenuItemId = null;
22321
23128
  }
22322
23129
  this.viewerDateTimeDispose();
23130
+ (_d = this.simulationRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
23131
+ this.simulationRemoval = null;
22323
23132
  }
22324
23133
  async ReRender(params) {
22325
23134
  let { entityIds, force, entities, maintain } = params;
@@ -22345,8 +23154,11 @@ var TilesetCadRenderManager;
22345
23154
  * @param tileset
22346
23155
  */
22347
23156
  viewerDateTimeSub(tileset) {
22348
- var _a;
22349
- 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) {
22350
23162
  return;
22351
23163
  }
22352
23164
  const accountId = this.accountIdForRecords();
@@ -22375,6 +23187,8 @@ var TilesetCadRenderManager;
22375
23187
  // Time padding in milliseconds (15 seconds).
22376
23188
  // Helps account for desync between client and server.
22377
23189
  paddingMs: 15000,
23190
+ simulation: this.simulation,
23191
+ simulatedOnly: !historic,
22378
23192
  onRecords: (recordsByIds) => {
22379
23193
  var _a;
22380
23194
  const records = recordsByIds[this.rootId];
@@ -22402,6 +23216,7 @@ var TilesetCadRenderManager;
22402
23216
  */
22403
23217
  const getInitialPosses = async () => {
22404
23218
  this.historicPossesLoadingProm = new Promise(async (res) => {
23219
+ var _a;
22405
23220
  try {
22406
23221
  if (this.disposed) {
22407
23222
  res(false);
@@ -22414,11 +23229,16 @@ var TilesetCadRenderManager;
22414
23229
  return;
22415
23230
  }
22416
23231
  // Also request the current position in case its not in the range.
22417
- 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({
22418
23238
  entityId: this.rootId,
22419
23239
  historicPoint: this.viewer.clock.currentTime.toString(),
22420
23240
  api: api
22421
- });
23241
+ })).entity);
22422
23242
  if (this.disposed || !entity) {
22423
23243
  res(false);
22424
23244
  return;
@@ -22498,14 +23318,15 @@ var TilesetCadRenderManager;
22498
23318
  }
22499
23319
  });
22500
23320
  this.viewerDateTimeChangeRemoval = () => {
23321
+ var _a, _b;
22501
23322
  if (this.historicAnimation) {
22502
23323
  this.historicAnimation.stop();
22503
23324
  this.historicAnimation = null;
22504
23325
  }
22505
- if (this.trackEntity) {
22506
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
22507
- this.trackEntity = null;
22508
- }
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;
22509
23330
  if (clockTickRemoval) {
22510
23331
  clockTickRemoval();
22511
23332
  clockTickRemoval = null;
@@ -22513,10 +23334,84 @@ var TilesetCadRenderManager;
22513
23334
  };
22514
23335
  });
22515
23336
  }
22516
- 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) {
22517
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) {
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;
22518
23410
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
22519
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;
22520
23415
  }
22521
23416
  /**
22522
23417
  * Updates the Tileset with latest coordinates.
@@ -23659,11 +24554,11 @@ var DataLabRenderManager;
23659
24554
  this.viewMonitorRemoval = null;
23660
24555
  this.renderQueue = [];
23661
24556
  this.renderQueueInterval = null;
23662
- this.reRenderState = new EntityReRenderMaintainState();
23663
24557
  this.zoomControl = [];
23664
24558
  this.queryLoadId = 0;
23665
24559
  this.renderDataLabEntitiesChain = Promise.resolve();
23666
24560
  const { viewer, item, apiGetter, monitor, visualsRegister } = params;
24561
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
23667
24562
  this.viewer = viewer;
23668
24563
  this.item = item;
23669
24564
  this.apiGetter = apiGetter;
@@ -23760,7 +24655,8 @@ var DataLabRenderManager;
23760
24655
  if (!((_a = data === null || data === void 0 ? void 0 : data.entities) === null || _a === void 0 ? void 0 : _a.length)) {
23761
24656
  return;
23762
24657
  }
23763
- 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));
23764
24660
  });
23765
24661
  }
23766
24662
  unsetGetter() {
@@ -25511,6 +26407,7 @@ var TilesetEntitiesRenderManager;
25511
26407
  this.featurePropCache = new Map();
25512
26408
  this.featurePropsChecked = 0;
25513
26409
  const { viewer, register: visualsManager, getters: apiGetter, item, initQueue, monitor } = params;
26410
+ this.simulation = params.simulation;
25514
26411
  this.monitor = monitor;
25515
26412
  this.viewer = viewer;
25516
26413
  this.getters = apiGetter;
@@ -25611,6 +26508,7 @@ var TilesetEntitiesRenderManager;
25611
26508
  if (Manager.NeedsStyler(this.item)) {
25612
26509
  const api = this.getters.GetBruceApi();
25613
26510
  this.styler.Init({
26511
+ simulation: this.simulation,
25614
26512
  viewer: this.viewer,
25615
26513
  api: api,
25616
26514
  cTileset: this.cTileset,
@@ -25968,6 +26866,7 @@ var TilesetEntitiesRenderManager;
25968
26866
  const needsStyler = Manager.NeedsStyler(this.item);
25969
26867
  if (needsStyler) {
25970
26868
  this.styler.Init({
26869
+ simulation: this.simulation,
25971
26870
  viewer: this.viewer,
25972
26871
  api: this.getters.GetBruceApi(),
25973
26872
  cTileset: this.cTileset,
@@ -26659,6 +27558,7 @@ var TilesetArbRenderManager;
26659
27558
  // LCC object if we're using XGrids to render LCC data.
26660
27559
  this.lccObject = null;
26661
27560
  this.viewer = params.viewer;
27561
+ this.simulation = params.simulation;
26662
27562
  this.getters = params.getters;
26663
27563
  this.visualsManager = params.register;
26664
27564
  // We modify the object due to a lot of backwards compatibility.
@@ -26893,6 +27793,7 @@ var TilesetArbRenderManager;
26893
27793
  this.item.Type == MenuItem.EType.IonTileset) {
26894
27794
  const api = this.getters.GetBruceApi();
26895
27795
  this.styler.Init({
27796
+ simulation: this.simulation,
26896
27797
  viewer: this.viewer,
26897
27798
  api: api,
26898
27799
  cTileset: this.cTileset,
@@ -27234,14 +28135,32 @@ var RelationsRenderManager;
27234
28135
  }
27235
28136
  ;
27236
28137
  constructor(params) {
28138
+ var _a, _b;
27237
28139
  // If true, this menu item is disposed and should not be used.
27238
28140
  // Any rendering will be ignored.
27239
28141
  this.disposed = false;
28142
+ this.simulationRemoval = null;
28143
+ this.FollowsSimulation = true;
27240
28144
  const { apiGetter, item, register, viewer } = params;
27241
28145
  this.apiGetter = apiGetter;
27242
28146
  this.item = item;
27243
28147
  this.register = register;
27244
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;
27245
28164
  }
27246
28165
  /**
27247
28166
  * Starts rendering the menu item.
@@ -27310,10 +28229,13 @@ var RelationsRenderManager;
27310
28229
  return items;
27311
28230
  }
27312
28231
  Dispose() {
28232
+ var _a;
27313
28233
  if (this.disposed) {
27314
28234
  return;
27315
28235
  }
27316
28236
  this.disposed = true;
28237
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28238
+ this.simulationRemoval = null;
27317
28239
  this.register.RemoveRegos({
27318
28240
  menuItemId: this.item.id
27319
28241
  });
@@ -27552,7 +28474,8 @@ var RelationsRenderManager;
27552
28474
  menuItemId: this.item.id,
27553
28475
  relations: relations,
27554
28476
  viewer: this.viewer,
27555
- visualRegister: this.register
28477
+ visualRegister: this.register,
28478
+ simulation: this.simulation
27556
28479
  });
27557
28480
  if (this.disposed) {
27558
28481
  this.register.RemoveRegos({
@@ -27835,9 +28758,12 @@ var LegacyRelationsRenderManager;
27835
28758
  }
27836
28759
  ;
27837
28760
  constructor(params) {
28761
+ var _a, _b;
27838
28762
  // If true, this menu item is disposed and should not be used.
27839
28763
  // Any rendering will be ignored.
27840
28764
  this.disposed = false;
28765
+ this.simulationRemoval = null;
28766
+ this.FollowsSimulation = true;
27841
28767
  // Record of the currently drawn entity IDs.
27842
28768
  // This is to avoid re-queueing already drawn entities when Init is called multiple times.
27843
28769
  this.drawnIds = [];
@@ -27846,6 +28772,21 @@ var LegacyRelationsRenderManager;
27846
28772
  this.item = item;
27847
28773
  this.register = register;
27848
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;
27849
28790
  }
27850
28791
  /**
27851
28792
  * Starts rendering the menu item.
@@ -27888,10 +28829,13 @@ var LegacyRelationsRenderManager;
27888
28829
  this.drawnIds = [].concat(entityIds);
27889
28830
  }
27890
28831
  Dispose() {
28832
+ var _a;
27891
28833
  if (this.disposed) {
27892
28834
  return;
27893
28835
  }
27894
28836
  this.disposed = true;
28837
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28838
+ this.simulationRemoval = null;
27895
28839
  this.register.RemoveRegos({
27896
28840
  menuItemId: this.item.id
27897
28841
  });
@@ -28052,7 +28996,8 @@ var LegacyRelationsRenderManager;
28052
28996
  menuItemId: this.item.id,
28053
28997
  relations: relations,
28054
28998
  viewer: this.viewer,
28055
- visualRegister: this.register
28999
+ visualRegister: this.register,
29000
+ simulation: this.simulation
28056
29001
  });
28057
29002
  if (this.disposed) {
28058
29003
  this.register.RemoveRegos({
@@ -29285,6 +30230,55 @@ var AssemblyRenderManager;
29285
30230
  get ModelSpace() {
29286
30231
  return this.modelSpace;
29287
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
+ }
29288
30282
  constructor(params) {
29289
30283
  this.disposed = false;
29290
30284
  // Cache of the hierarchy so that our scene-tree can reference it.
@@ -29294,19 +30288,41 @@ var AssemblyRenderManager;
29294
30288
  this.hierarchyNodeByEntityId = null;
29295
30289
  this.modelSpace = false;
29296
30290
  this.rootId = null;
29297
- // Where the root sits now, and which way it faces.
29298
- // 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.
29299
30292
  this.rootPos3d = null;
29300
30293
  this.rootHeading = 0;
29301
- // 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.
29302
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();
29303
30311
  // The trail through the root's own movement, which describes the whole Assembly.
29304
- 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;
29305
30320
  // Callback to dispose the link between scene time and the root's historic position.
29306
30321
  this.viewerDateTimeChangeRemoval = null;
29307
30322
  this.historicGatherer = null;
29308
30323
  this.historicAnimation = null;
29309
30324
  const { viewer, register: visualsManager, getters, item } = params;
30325
+ this.simulation = params.simulation;
29310
30326
  this.viewer = viewer;
29311
30327
  this.getters = getters;
29312
30328
  this.item = item;
@@ -29330,10 +30346,10 @@ var AssemblyRenderManager;
29330
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"]);
29331
30347
  }
29332
30348
  /*
29333
- * 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.
29334
30350
  */
29335
- static rootFrame(rootPos3d, headingDegrees) {
29336
- 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));
29337
30353
  return Transforms.headingPitchRollToFixedFrame(rootPos3d, hpr);
29338
30354
  }
29339
30355
  /**
@@ -29356,28 +30372,553 @@ var AssemblyRenderManager;
29356
30372
  return Matrix4.multiplyByPoint(Manager.rootFrame(rootPos3d, headingDegrees), offset, new Cartesian3());
29357
30373
  }
29358
30374
  /**
29359
- * 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.
29360
30376
  * @param params
29361
30377
  */
29362
30378
  ApplyRootPosition(params) {
29363
- var _a, _b;
30379
+ var _a, _b, _c, _d, _e, _f;
29364
30380
  if (this.disposed || !(params === null || params === void 0 ? void 0 : params.pos3d)) {
29365
30381
  return;
29366
30382
  }
29367
30383
  this.rootPos3d = params.pos3d;
29368
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;
29369
30395
  for (const child of this.children.values()) {
29370
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, child.offset);
29371
- 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) {
29372
30654
  continue;
29373
30655
  }
29374
- child.cEntity.position = new ConstantPositionProperty(pos3d);
29375
- const orient = EntityUtils.GetAssemblyOrientation(child.entity, pos3d);
29376
- if (orient) {
29377
- 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;
29378
30690
  }
30691
+ byTime.set(timeMs, key);
29379
30692
  }
29380
- 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;
29381
30922
  }
29382
30923
  Init() {
29383
30924
  this.renderPriority = this.item.renderPriority;
@@ -29385,6 +30926,7 @@ var AssemblyRenderManager;
29385
30926
  this.renderPriority = 1;
29386
30927
  }
29387
30928
  (async () => {
30929
+ var _a, _b;
29388
30930
  if (this.disposed) {
29389
30931
  return;
29390
30932
  }
@@ -29400,6 +30942,8 @@ var AssemblyRenderManager;
29400
30942
  this.hierarchyUpdate.Trigger(hierarchy);
29401
30943
  // Build hierarchy node cache for quick lookups
29402
30944
  this.buildHierarchyNodes(hierarchy);
30945
+ // A set for membership, since searching the list took minutes at 200k nodes.
30946
+ const seenIds = new Set();
29403
30947
  const traverseHierarchy = (node, leavesOnly) => {
29404
30948
  var _a;
29405
30949
  if (node.Children) {
@@ -29408,7 +30952,8 @@ var AssemblyRenderManager;
29408
30952
  }
29409
30953
  }
29410
30954
  const addNode = !leavesOnly || !((_a = node.Children) === null || _a === void 0 ? void 0 : _a.length);
29411
- if (addNode && node.ID && !flatEntityIds.includes(node.ID)) {
30955
+ if (addNode && node.ID && !seenIds.has(node.ID)) {
30956
+ seenIds.add(node.ID);
29412
30957
  flatEntityIds.push(node.ID);
29413
30958
  }
29414
30959
  };
@@ -29427,17 +30972,17 @@ var AssemblyRenderManager;
29427
30972
  }
29428
30973
  }
29429
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;
29430
30976
  if (this.disposed) {
29431
30977
  return;
29432
30978
  }
29433
30979
  else if (flatEntityIds.length === 0) {
29434
30980
  return;
29435
30981
  }
30982
+ // Followed while the parts load: in a large Assembly loading takes far longer than anyone waits.
30983
+ const following = this.viewerDateTimeSub();
29436
30984
  await this.processEntityIds(flatEntityIds);
29437
- if (this.disposed) {
29438
- return;
29439
- }
29440
- this.viewerDateTimeSub();
30985
+ await following;
29441
30986
  }
29442
30987
  catch (e) {
29443
30988
  console.error("AssemblyRenderManager.Manager.Init()", e);
@@ -29464,7 +31009,7 @@ var AssemblyRenderManager;
29464
31009
  if (!batch.length) {
29465
31010
  continue;
29466
31011
  }
29467
- const { entities: fetched } = await Entity$1.GetListByIds({
31012
+ const { entities: apiEntities } = await Entity$1.GetListByIds({
29468
31013
  entityIds: batch,
29469
31014
  api: this.getters.GetBruceApi(),
29470
31015
  expandLODs: true,
@@ -29476,6 +31021,16 @@ var AssemblyRenderManager;
29476
31021
  if (this.disposed) {
29477
31022
  return;
29478
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;
29479
31034
  for (const entity of fetched) {
29480
31035
  // Filter for valid LODs then take the highest quality one (lowest level).
29481
31036
  let lods = entity.Bruce.LOD || [];
@@ -29498,57 +31053,46 @@ var AssemblyRenderManager;
29498
31053
  // Won't bother with vector data for this experiment.
29499
31054
  continue;
29500
31055
  }
29501
- const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29502
- const recordPos3d = EntityUtils.GetPos({
29503
- viewer: this.viewer,
29504
- entity,
29505
- recordHeightRef: recordHeightRef,
29506
- returnHeightRef: HeightReference.NONE,
29507
- allowRendered: false
29508
- });
29509
- 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)) {
29510
31061
  continue;
29511
31062
  }
29512
- const entityId = entity.Bruce.ID;
29513
- if (entityId === this.rootId || !this.rootPos3d) {
29514
- // Nothing has a frame to be relative to until the root has one.
29515
- this.rootPos3d = recordPos3d;
29516
- this.rootHeading = EnsureNumber((_a = entity.Bruce.Transform) === null || _a === void 0 ? void 0 : _a.heading, 0);
29517
- }
29518
- // Held as an offset in the root's frame, so a root move places it again without
29519
- // anything being fetched. Where the root already moved, that is where it goes.
29520
- const offset = Manager.ChildOffsetIn(this.rootPos3d, this.rootHeading, recordPos3d);
29521
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, offset);
29522
- // Optional: a child with no world matrix is still worth drawing, it just has no
29523
- // orientation of its own to apply.
29524
- const orient = EntityUtils.GetAssemblyOrientation(entity, pos3d);
29525
31063
  const cEntity = this.viewer.entities.add({
29526
31064
  model: {
29527
31065
  uri: lod["ClientFile"].URL,
29528
31066
  ...ViewUtils.DefaultLightingProps(),
29529
31067
  heightReference: HeightReference.NONE
29530
- },
29531
- position: pos3d,
29532
- orientation: orient ? new ConstantProperty(orient) : undefined
31068
+ }
29533
31069
  });
29534
31070
  const prior = this.children.get(entityId);
29535
31071
  if (prior && prior.cEntity !== cEntity) {
29536
31072
  RemoveCEntityFromScene(this.viewer, prior.cEntity);
29537
31073
  }
29538
- this.children.set(entityId, {
31074
+ const child = {
29539
31075
  entityId: entityId,
29540
31076
  entity: entity,
29541
- cEntity: cEntity,
29542
- offset: offset
29543
- });
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
+ }
29544
31084
  const geometryRadius = Entity$1.GetValue({
29545
31085
  entity: entity,
29546
31086
  path: ["Bruce", "GeometryRadius"]
29547
31087
  });
29548
31088
  cEntity.model["_radius"] = geometryRadius;
29549
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);
29550
31092
  this.visualsManager.AddRego({
29551
31093
  rego: {
31094
+ moving: child.moving ? true : null,
31095
+ motion: (time) => this.motionAt(entity.Bruce.ID, time),
29552
31096
  canEdit: true,
29553
31097
  entityId: entity.Bruce.ID,
29554
31098
  menuItemId: this.item.id,
@@ -29568,47 +31112,89 @@ var AssemblyRenderManager;
29568
31112
  }
29569
31113
  }
29570
31114
  /*
29571
- * Follows the scene clock and moves the whole Assembly along the root Entity's historic path.
29572
- * 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.
29573
31116
  */
29574
- viewerDateTimeSub() {
29575
- 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)) {
29576
31121
  return;
29577
31122
  }
31123
+ this.followingClock = true;
29578
31124
  const api = this.getters.GetBruceApi();
29579
- 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;
29580
31130
  const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29581
31131
  const toPosses = (records) => {
29582
31132
  return CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, recordHeightRef, HeightReference.NONE, records);
29583
31133
  };
29584
- this.historicGatherer = new HistoricGatherer({
29585
- viewer: this.viewer,
29586
- getApi: () => api,
29587
- getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
29588
- // For clock desync between whatever wrote the record and this client.
29589
- paddingMs: 15000,
29590
- onRecords: (recordsByIds) => {
29591
- const records = recordsByIds[this.rootId];
29592
- if ((records === null || records === void 0 ? void 0 : records.length) && this.historicAnimation) {
29593
- 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.
29594
31154
  }
29595
- }
29596
- });
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()]));
29597
31159
  let gathering = false;
29598
- 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);
29599
31165
  if (this.disposed || gathering) {
29600
31166
  return;
29601
31167
  }
29602
31168
  gathering = true;
29603
31169
  try {
29604
- 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
+ }
29605
31179
  if (this.disposed) {
29606
31180
  return;
29607
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);
29608
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
+ }
29609
31194
  if (!posses.length) {
29610
31195
  return;
29611
31196
  }
31197
+ // Held for the trail alone; where the root is drawn comes from rootAt(), never from its smoothing.
29612
31198
  if (!this.historicAnimation) {
29613
31199
  this.historicAnimation = new CesiumAnimatedProperty.AnimatePositionSeries({
29614
31200
  posses: posses,
@@ -29622,15 +31208,21 @@ var AssemblyRenderManager;
29622
31208
  }
29623
31209
  catch (e) {
29624
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;
29625
31213
  }
29626
31214
  finally {
29627
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
+ }
29628
31220
  }
29629
31221
  };
31222
+ this.regather = gather;
29630
31223
  gather();
29631
31224
  let lastTime = null;
29632
31225
  const tickRemoval = this.viewer.clock.onTick.addEventListener(() => {
29633
- var _a;
29634
31226
  if (this.disposed) {
29635
31227
  return;
29636
31228
  }
@@ -29640,15 +31232,9 @@ var AssemblyRenderManager;
29640
31232
  }
29641
31233
  lastTime = now;
29642
31234
  gather();
29643
- if (!this.historicAnimation) {
29644
- return;
29645
- }
29646
- const pos3d = this.historicAnimation.GetValue();
29647
- if (pos3d) {
29648
- this.ApplyRootPosition({
29649
- pos3d: pos3d,
29650
- heading: (_a = this.historicAnimation.GetHeadingDegrees()) !== null && _a !== void 0 ? _a : this.rootHeading
29651
- });
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();
29652
31238
  }
29653
31239
  });
29654
31240
  this.viewerDateTimeChangeRemoval = () => {
@@ -29657,6 +31243,10 @@ var AssemblyRenderManager;
29657
31243
  (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
29658
31244
  this.historicGatherer = null;
29659
31245
  this.historicAnimation = null;
31246
+ this.regather = null;
31247
+ this.rootSeries = [];
31248
+ this.rootAttitude = [];
31249
+ this.pose = null;
29660
31250
  };
29661
31251
  }
29662
31252
  /*
@@ -29664,7 +31254,7 @@ var AssemblyRenderManager;
29664
31254
  */
29665
31255
  applyRootTrack() {
29666
31256
  var _a;
29667
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
31257
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
29668
31258
  return;
29669
31259
  }
29670
31260
  const trackStyle = EntityRenderEngine.GetTrackStyle({
@@ -29674,41 +31264,35 @@ var AssemblyRenderManager;
29674
31264
  if (!trackStyle) {
29675
31265
  return;
29676
31266
  }
29677
- this.trackEntity = this.viewer.entities.add({
29678
- id: ObjectUtils.UId(15),
29679
- polyline: {
29680
- 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),
29681
- width: trackStyle.width,
29682
- material: trackStyle.color,
29683
- clampToGround: EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
29684
- == HeightReference.CLAMP_TO_GROUND,
29685
- zIndex: 0,
29686
- classificationType: ClassificationType.BOTH
29687
- }
29688
- });
29689
- this.trackEntity._noEdit = true;
29690
- this.trackEntity._noSelect = true;
29691
- CesiumEntityStyler.SetDefaultColor({
29692
- color: trackStyle.color,
29693
- entity: this.trackEntity,
31267
+ this.trail = new HistoricTrail.Trail({
29694
31268
  viewer: this.viewer,
29695
- override: true,
29696
- 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)
29697
31277
  });
29698
31278
  }
29699
31279
  Dispose() {
29700
- var _a;
31280
+ var _a, _b, _c;
29701
31281
  if (this.disposed) {
29702
31282
  return;
29703
31283
  }
29704
31284
  this.disposed = true;
29705
31285
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
29706
31286
  this.viewerDateTimeChangeRemoval = null;
29707
- if (this.trackEntity) {
29708
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
29709
- this.trackEntity = null;
29710
- }
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;
29711
31292
  this.children.clear();
31293
+ this.localPositions.clear();
31294
+ this.jointSeries.clear();
31295
+ this.animatedIds.clear();
29712
31296
  this.visualsManager.RemoveRegos({
29713
31297
  menuItemId: this.item.id
29714
31298
  });
@@ -30166,6 +31750,9 @@ var MenuItemManager;
30166
31750
  this.OnSeriesUpdated.Trigger({ itemId, segments: [] });
30167
31751
  }
30168
31752
  }
31753
+ get Simulation() {
31754
+ return this.simulation;
31755
+ }
30169
31756
  get Monitor() {
30170
31757
  return this.sharedMonitor;
30171
31758
  }
@@ -30191,6 +31778,7 @@ var MenuItemManager;
30191
31778
  this.visualsRegister = visualsRegister;
30192
31779
  this.sharedGetters = new SharedGetters.Cache();
30193
31780
  this.sharedHistoric = new SharedHistoricGetters.Cache();
31781
+ this.simulation = EntitySimulation.Get(this.viewer);
30194
31782
  this.sharedMonitor = new CesiumViewMonitor.Monitor(this.viewer);
30195
31783
  this.tilesetInitQueue = new InitQueueTileset(this.getters);
30196
31784
  }
@@ -30404,7 +31992,8 @@ var MenuItemManager;
30404
31992
  monitor: this.sharedMonitor,
30405
31993
  item: params.item,
30406
31994
  sharedGetters: this.sharedGetters,
30407
- onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment)
31995
+ onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment),
31996
+ simulation: this.simulation
30408
31997
  });
30409
31998
  break;
30410
31999
  case MenuItem.EType.EntitiesLoaded:
@@ -30413,7 +32002,8 @@ var MenuItemManager;
30413
32002
  register: this.visualsRegister,
30414
32003
  apiGetter: params.apiGetter,
30415
32004
  monitor: this.sharedMonitor,
30416
- item: params.item
32005
+ item: params.item,
32006
+ simulation: this.simulation
30417
32007
  });
30418
32008
  break;
30419
32009
  case MenuItem.EType.EntitiesIds:
@@ -30423,7 +32013,8 @@ var MenuItemManager;
30423
32013
  apiGetter: params.apiGetter,
30424
32014
  monitor: this.sharedMonitor,
30425
32015
  item: params.item,
30426
- sharedHistoric: this.sharedHistoric
32016
+ sharedHistoric: this.sharedHistoric,
32017
+ simulation: this.simulation
30427
32018
  });
30428
32019
  break;
30429
32020
  case MenuItem.EType.Entity:
@@ -30432,7 +32023,8 @@ var MenuItemManager;
30432
32023
  register: this.visualsRegister,
30433
32024
  apiGetter: params.apiGetter,
30434
32025
  monitor: this.sharedMonitor,
30435
- item: params.item
32026
+ item: params.item,
32027
+ simulation: this.simulation
30436
32028
  });
30437
32029
  break;
30438
32030
  case MenuItem.EType.CadTileset:
@@ -30442,7 +32034,8 @@ var MenuItemManager;
30442
32034
  getters: this.getters,
30443
32035
  item: params.item,
30444
32036
  modelSpace: params.modelSpace,
30445
- initQueue: this.tilesetInitQueue
32037
+ initQueue: this.tilesetInitQueue,
32038
+ simulation: this.simulation
30446
32039
  });
30447
32040
  break;
30448
32041
  case MenuItem.EType.Assembly:
@@ -30452,6 +32045,7 @@ var MenuItemManager;
30452
32045
  getters: params.getters,
30453
32046
  item: params.item,
30454
32047
  modelSpace: params.modelSpace,
32048
+ simulation: this.simulation
30455
32049
  });
30456
32050
  break;
30457
32051
  case MenuItem.EType.Osm:
@@ -30479,7 +32073,8 @@ var MenuItemManager;
30479
32073
  getters: this.getters,
30480
32074
  monitor: this.sharedMonitor,
30481
32075
  item: params.item,
30482
- initQueue: this.tilesetInitQueue
32076
+ initQueue: this.tilesetInitQueue,
32077
+ simulation: this.simulation
30483
32078
  });
30484
32079
  break;
30485
32080
  case MenuItem.EType.Kml:
@@ -30503,7 +32098,8 @@ var MenuItemManager;
30503
32098
  getters: params.getters,
30504
32099
  item: params.item,
30505
32100
  viewer: this.viewer,
30506
- register: this.visualsRegister
32101
+ register: this.visualsRegister,
32102
+ simulation: this.simulation
30507
32103
  });
30508
32104
  break;
30509
32105
  // Deprecated to support a few demos until new one is phased in.
@@ -30513,7 +32109,8 @@ var MenuItemManager;
30513
32109
  apiGetter: params.apiGetter,
30514
32110
  item: params.item,
30515
32111
  register: this.visualsRegister,
30516
- viewer: this.viewer
32112
+ viewer: this.viewer,
32113
+ simulation: this.simulation
30517
32114
  });
30518
32115
  break;
30519
32116
  case MenuItem.EType.Relationships:
@@ -30521,7 +32118,8 @@ var MenuItemManager;
30521
32118
  apiGetter: params.apiGetter,
30522
32119
  item: params.item,
30523
32120
  register: this.visualsRegister,
30524
- viewer: this.viewer
32121
+ viewer: this.viewer,
32122
+ simulation: this.simulation
30525
32123
  });
30526
32124
  break;
30527
32125
  case MenuItem.EType.GooglePhotoTileset:
@@ -30553,7 +32151,8 @@ var MenuItemManager;
30553
32151
  item: params.item,
30554
32152
  apiGetter: params.apiGetter,
30555
32153
  monitor: this.sharedMonitor,
30556
- visualsRegister: this.visualsRegister
32154
+ visualsRegister: this.visualsRegister,
32155
+ simulation: this.simulation
30557
32156
  });
30558
32157
  break;
30559
32158
  // Any "none" type can be ignored. These are just containers for other items.
@@ -30766,6 +32365,63 @@ var MenuItemManager;
30766
32365
  }
30767
32366
  }
30768
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
+ }
30769
32425
  }
30770
32426
  MenuItemManager.Manager = Manager;
30771
32427
  })(MenuItemManager || (MenuItemManager = {}));
@@ -39325,6 +40981,7 @@ function groupAttributes(paths, attributes) {
39325
40981
  }
39326
40982
  class WidgetInfoView extends Widget.AWidget {
39327
40983
  constructor(params) {
40984
+ var _a;
39328
40985
  super(params);
39329
40986
  this.STYLESHEET_ID = "nextspace-info-view-stylesheet";
39330
40987
  this._generateStyles();
@@ -39334,10 +40991,19 @@ class WidgetInfoView extends Widget.AWidget {
39334
40991
  this._updateInfoView();
39335
40992
  }
39336
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
+ });
39337
41001
  }
39338
41002
  Dispose() {
41003
+ var _a;
39339
41004
  super.Dispose();
39340
41005
  this._updateRemoval();
41006
+ (_a = this._simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
39341
41007
  }
39342
41008
  _generateStyles() {
39343
41009
  if (document.getElementById(this.STYLESHEET_ID)) {
@@ -39799,7 +41465,7 @@ class WidgetInfoView extends Widget.AWidget {
39799
41465
  }
39800
41466
  }
39801
41467
  async _updateInfoView() {
39802
- var _b, _c, _d, _e;
41468
+ var _a, _c, _d, _e, _f;
39803
41469
  const selectedIds = this._manager.VisualsRegister.GetSelected();
39804
41470
  if (selectedIds.length <= 0) {
39805
41471
  this._element.style.display = "none";
@@ -39821,17 +41487,20 @@ class WidgetInfoView extends Widget.AWidget {
39821
41487
  this._summary.innerHTML = "";
39822
41488
  this._imageContainer.style.display = "none";
39823
41489
  const api = this._apiGetters.GetBruceApi();
39824
- 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({
39825
41493
  entityId: firstId,
39826
41494
  api: api,
39827
41495
  migrated: true,
39828
41496
  expandLocation: DISPLAY_ASSEMBLY_INFO
39829
- });
41497
+ })).entity;
41498
+ entity = entity && simulation ? simulation.Resolve(firstId, entity) : entity;
39830
41499
  if (!entity) {
39831
41500
  throw (new Error("Could not get entity."));
39832
41501
  }
39833
41502
  const { entityType } = await EntityType.Get({
39834
- 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"],
39835
41504
  api: api
39836
41505
  });
39837
41506
  let name = Entity$1.CalculateName({
@@ -39854,7 +41523,7 @@ class WidgetInfoView extends Widget.AWidget {
39854
41523
  const defaultImage = images.length ? images[0] : null;
39855
41524
  if (defaultImage) {
39856
41525
  this._imageContainer.style.display = "flex";
39857
- 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({
39858
41527
  fileId: defaultImage["ClientFile.ID"],
39859
41528
  api: api,
39860
41529
  viaCdn: true
@@ -39867,7 +41536,7 @@ class WidgetInfoView extends Widget.AWidget {
39867
41536
  this._summary.appendChild(detailsTitle);
39868
41537
  // TODO: Selection for what mode instead of always doing "all".
39869
41538
  const attributes = gatherAttributes(EMode.ShowAll, entity, entityType);
39870
- 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 : []);
39871
41540
  for (const group of groups) {
39872
41541
  this._generateAttrGroup(entityType, entity, group);
39873
41542
  }
@@ -42464,7 +44133,7 @@ var EntityUtils;
42464
44133
  * @param params
42465
44134
  */
42466
44135
  async function GetLocation(params) {
42467
- var _a, _b, _c, _d;
44136
+ var _a, _b, _c, _d, _e;
42468
44137
  let { api, viewer, samples, visualRegister, minimumAlt, paddingAlt } = params;
42469
44138
  if (!paddingAlt) {
42470
44139
  paddingAlt = 0;
@@ -43012,6 +44681,13 @@ var EntityUtils;
43012
44681
  recordSamples.push(sample);
43013
44682
  }
43014
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
+ }
43015
44691
  // Bulk requesting Entity records.
43016
44692
  try {
43017
44693
  const toSearchIds = recordSamples.filter(x => !x.entity && x.entityId).map(x => x.entityId);
@@ -43032,7 +44708,8 @@ var EntityUtils;
43032
44708
  for (let i = 0; i < recordSamples.length; i++) {
43033
44709
  const sample = recordSamples[i];
43034
44710
  if (!sample.entity && sample.entityId) {
43035
- sample.entity = entitiesMap.get(sample.entityId);
44711
+ const read = entitiesMap.get(sample.entityId);
44712
+ sample.entity = read && simulation ? simulation.Overlay(read) : read;
43036
44713
  }
43037
44714
  }
43038
44715
  }
@@ -43046,6 +44723,11 @@ var EntityUtils;
43046
44723
  // We have to detect whatever is an Assembly Entity and expand it.
43047
44724
  try {
43048
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
+ }
43049
44731
  const assemblyPos = Entity$1.GetValue({
43050
44732
  entity: x.entity,
43051
44733
  path: ["Bruce", "AssemblyPosition"]
@@ -43404,6 +45086,21 @@ var EntityUtils;
43404
45086
  return Quaternion.multiply(combined, _assemblyAxisShift, new Quaternion());
43405
45087
  }
43406
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;
43407
45104
  /**
43408
45105
  * Returns the position of the entity.
43409
45106
  * This is an async variant that checks for related assemblies.
@@ -43413,6 +45110,18 @@ var EntityUtils;
43413
45110
  */
43414
45111
  async function GetPosAsync(params) {
43415
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
+ }
43416
45125
  // We have issues where imported BRZ files have improper expandLocation=true info.
43417
45126
  // As a result we'll double-request the data.
43418
45127
  // If there is a location then we won't bother using expanded information.
@@ -53413,7 +55122,7 @@ var StyleUtils;
53413
55122
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
53414
55123
  })(StyleUtils || (StyleUtils = {}));
53415
55124
 
53416
- const VERSION = "7.6.0";
55125
+ const VERSION = "7.6.2";
53417
55126
  /**
53418
55127
  * Updates the environment instance used by bruce-cesium to one specified.
53419
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.
@@ -53430,5 +55139,5 @@ const getENVIRONMENT = () => {
53430
55139
  return ENVIRONMENT;
53431
55140
  };
53432
55141
 
53433
- 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 };
53434
55143
  //# sourceMappingURL=bruce-cesium.es5.js.map