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
@@ -2621,13 +2621,13 @@
2621
2621
  class AnimateTPositionSeries {
2622
2622
  constructor(params) {
2623
2623
  this.lastCalcAttitude = null;
2624
- // Cache for calculated values.
2624
+ // Cache for calculated values: the scene time and set of positions they were worked out for.
2625
2625
  this.lastCalcTime = null;
2626
+ this.lastCalcRevision = -1;
2626
2627
  this.lastCalcPos3d = null;
2627
- this.lastCalcPosApplyOverTime = false;
2628
- this.lastCalcPos3dApplyOverTimeTarget = null;
2629
- this.lastCalcPos3dApplyOverTimeTimeStart = null;
2630
2628
  this.lastCalcHeading = null;
2629
+ // Bumped whenever the positions change, so a still clock re-applies a changed series.
2630
+ this.revision = 0;
2631
2631
  this.removal = null;
2632
2632
  // Track the timeline range for which we have data.
2633
2633
  this.minDate = null;
@@ -2657,6 +2657,7 @@
2657
2657
  if (!newPositions || newPositions.length === 0) {
2658
2658
  return;
2659
2659
  }
2660
+ this.revision++;
2660
2661
  // Add new positions to our map, overwriting any existing positions with the same timestamp
2661
2662
  for (const pos of newPositions) {
2662
2663
  const key = pos.dateTime.toISOString();
@@ -2680,6 +2681,17 @@
2680
2681
  }
2681
2682
  }
2682
2683
  }
2684
+ /**
2685
+ * Replaces every position with these, eg. once some that were simulated have been cleared.
2686
+ * @param positions Positions to animate through from now on.
2687
+ */
2688
+ replacePositions(positions) {
2689
+ this.positionsMap.clear();
2690
+ this.positions = [];
2691
+ this.minDate = null;
2692
+ this.maxDate = null;
2693
+ this.addPositions(positions);
2694
+ }
2683
2695
  /**
2684
2696
  * Get the current date range covered by our positions.
2685
2697
  */
@@ -2727,106 +2739,47 @@
2727
2739
  /*
2728
2740
  * Returns the attitude of the record at an index, or nothing recorded when it names none.
2729
2741
  */
2730
- attitudeAt(index) {
2731
- var _a, _b, _c;
2732
- const pos = index >= 0 ? (_a = this.positions) === null || _a === void 0 ? void 0 : _a[index] : null;
2733
- 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 };
2742
+ /*
2743
+ * Pitch and roll between two positions the short way round; an angle only one names is held.
2744
+ */
2745
+ attitudeBetween(indexLast, indexNext, timeMs) {
2746
+ var _a, _b, _c, _d, _e, _f;
2747
+ const a = indexLast >= 0 ? (_a = this.positions) === null || _a === void 0 ? void 0 : _a[indexLast] : null;
2748
+ const b = indexNext >= 0 ? (_b = this.positions) === null || _b === void 0 ? void 0 : _b[indexNext] : null;
2749
+ const span = a && b ? b.dateTime.getTime() - a.dateTime.getTime() : 0;
2750
+ const t = span > 0 ? (timeMs - a.dateTime.getTime()) / span : 0;
2751
+ const turn = (x, y) => {
2752
+ if (x == null || y == null) {
2753
+ return x !== null && x !== void 0 ? x : null;
2754
+ }
2755
+ return x + (Cesium.Math.negativePiToPi(Cesium.Math.toRadians(y - x)) * 180 / Math.PI) * t;
2756
+ };
2757
+ 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) };
2734
2758
  }
2759
+ /*
2760
+ * Places the series at the scene's time, exactly rather than eased, so what moves with it never lags the clock.
2761
+ */
2735
2762
  update() {
2763
+ var _a;
2736
2764
  if (!this.positions || this.positions.length === 0) {
2737
2765
  return;
2738
2766
  }
2739
- // Get current time.
2740
- let now = this.viewer.scene.lastRenderTime || this.viewer.clock.currentTime;
2741
- const nowTime = Cesium.JulianDate.toDate(now);
2742
- const nowTimeMs = nowTime.getTime();
2743
- const currentRealTimeMs = Date.now();
2744
- // Calculate the ideal position based on current time.
2745
- const targetPosition = this.calculatePosition(nowTimeMs);
2746
- // Initialize if this is the first update.
2747
- if (!this.lastCalcPos3d) {
2748
- this.lastCalcPos3d = targetPosition.pos3d;
2749
- this.lastCalcAttitude = this.attitudeAt(targetPosition.indexLast);
2750
- this.onUpdate(targetPosition.pos3d, this.lastCalcHeading, this.lastCalcAttitude);
2751
- return;
2752
- }
2753
- // Check if target has changed.
2754
- if (!this.lastCalcPos3dApplyOverTimeTarget ||
2755
- !Cesium.Cartesian3.equals(this.lastCalcPos3dApplyOverTimeTarget, targetPosition.pos3d)) {
2756
- // If we were already interpolating, get the current interpolated position as our new starting point.
2757
- if (this.lastCalcPosApplyOverTime && this.lastCalcPos3dApplyOverTimeTimeStart) {
2758
- // Calculate where we are in the current interpolation.
2759
- const currentInterpolatedPos = this.interpolatePos3dOverTime(this.lastCalcPos3dApplyOverTimeTimeStart, currentRealTimeMs);
2760
- // Use this as our new starting position.
2761
- this.lastCalcPos3d = currentInterpolatedPos;
2762
- }
2763
- // Set new target and start time.
2764
- this.lastCalcPos3dApplyOverTimeTarget = targetPosition.pos3d;
2765
- this.lastCalcPos3dApplyOverTimeTimeStart = currentRealTimeMs;
2766
- this.lastCalcPosApplyOverTime = true;
2767
- }
2768
- // Calculate the interpolated position.
2769
- let pos3d;
2770
- if (this.lastCalcPosApplyOverTime) {
2771
- pos3d = this.interpolatePos3dOverTime(this.lastCalcPos3dApplyOverTimeTimeStart, currentRealTimeMs);
2772
- // If we've reached the target, update our base position.
2773
- if (Cesium.Cartesian3.distance(pos3d, this.lastCalcPos3dApplyOverTimeTarget) < 0.01) {
2774
- this.lastCalcPos3d = this.lastCalcPos3dApplyOverTimeTarget;
2775
- this.lastCalcPosApplyOverTime = false;
2776
- }
2767
+ // The clock's time is the frame being drawn; the last render time is the frame before it.
2768
+ const nowTimeMs = Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime();
2769
+ if (nowTimeMs === this.lastCalcTime && this.revision === this.lastCalcRevision) {
2770
+ return;
2777
2771
  }
2778
- else {
2779
- pos3d = this.lastCalcPos3d;
2780
- }
2781
- // Calculate heading.
2782
- if (this.lastCalcPos3dApplyOverTimeTarget) {
2783
- if (this.lastCalcPos3d) {
2784
- const minDistanceThreshold = 0.05;
2785
- const currentToTargetDistance = Cesium.Cartesian3.distance(this.lastCalcPos3d, this.lastCalcPos3dApplyOverTimeTarget);
2786
- if (currentToTargetDistance >= minDistanceThreshold) {
2787
- const lastPos = {
2788
- pos3d: this.lastCalcPos3d,
2789
- dateTime: this.lastCalcPos3dApplyOverTimeTimeStart ? new Date(this.lastCalcPos3dApplyOverTimeTimeStart) : null,
2790
- heading: null
2791
- };
2792
- const nextPos = {
2793
- pos3d: this.lastCalcPos3dApplyOverTimeTarget,
2794
- dateTime: null,
2795
- heading: null
2796
- };
2797
- if (lastPos && nextPos) {
2798
- this.lastCalcHeading = this.calculateHeading(nowTimeMs, lastPos, nextPos);
2799
- }
2800
- }
2801
- }
2772
+ this.lastCalcTime = nowTimeMs;
2773
+ this.lastCalcRevision = this.revision;
2774
+ const target = this.calculatePosition(nowTimeMs);
2775
+ const lastPos = this.positions[target.indexLast];
2776
+ const nextPos = this.positions[target.indexNext];
2777
+ if (lastPos && nextPos) {
2778
+ this.lastCalcHeading = (_a = this.calculateHeading(nowTimeMs, lastPos, nextPos)) !== null && _a !== void 0 ? _a : this.lastCalcHeading;
2802
2779
  }
2803
- else {
2804
- const posses = this.positions;
2805
- const lastPos = posses[targetPosition.indexLast];
2806
- const nextPos = posses[targetPosition.indexNext];
2807
- if (lastPos && nextPos) {
2808
- this.lastCalcHeading = this.calculateHeading(nowTimeMs, lastPos, nextPos);
2809
- }
2810
- }
2811
- // Provide the calculated values to the caller.
2812
- this.lastCalcAttitude = this.attitudeAt(targetPosition.indexLast);
2813
- this.onUpdate(pos3d, this.lastCalcHeading, this.lastCalcAttitude);
2814
- }
2815
- interpolatePos3dOverTime(startTimeMs, currentTimeMs) {
2816
- const lastPos = this.lastCalcPos3d;
2817
- const targetPos = this.lastCalcPos3dApplyOverTimeTarget;
2818
- if (!lastPos || !targetPos) {
2819
- return lastPos ? lastPos : targetPos;
2820
- }
2821
- const interpolationDuration = 200;
2822
- const elapsedTime = currentTimeMs - startTimeMs;
2823
- let progress = Math.min(elapsedTime / interpolationDuration, 1.0);
2824
- // Optional step to make it smoother.
2825
- progress = this.easeInOutQuad(progress);
2826
- return Cesium.Cartesian3.lerp(lastPos, targetPos, progress, new Cesium.Cartesian3());
2827
- }
2828
- easeInOutQuad(t) {
2829
- return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
2780
+ this.lastCalcPos3d = target.pos3d;
2781
+ this.lastCalcAttitude = this.attitudeBetween(target.indexLast, target.indexNext, nowTimeMs);
2782
+ this.onUpdate(target.pos3d, this.lastCalcHeading, this.lastCalcAttitude);
2830
2783
  }
2831
2784
  /**
2832
2785
  * Pre-process headings in the series.
@@ -2852,14 +2805,14 @@
2852
2805
  * Calculate the position at the given time.
2853
2806
  */
2854
2807
  calculatePosition(currentTimeMs) {
2808
+ var _a, _b;
2855
2809
  const posses = this.positions;
2856
2810
  // No positions to interpolate
2857
2811
  if (!posses || posses.length === 0) {
2858
2812
  return {
2859
2813
  pos3d: new Cesium.Cartesian3(),
2860
2814
  indexLast: -1,
2861
- indexNext: -1,
2862
- applyOverTime: false
2815
+ indexNext: -1
2863
2816
  };
2864
2817
  }
2865
2818
  // See if we're before the first position.
@@ -2867,8 +2820,7 @@
2867
2820
  return {
2868
2821
  pos3d: posses[0].pos3d,
2869
2822
  indexLast: 0,
2870
- indexNext: 0,
2871
- applyOverTime: true
2823
+ indexNext: 0
2872
2824
  };
2873
2825
  }
2874
2826
  // See if we're after the last position.
@@ -2877,8 +2829,7 @@
2877
2829
  return {
2878
2830
  pos3d: posses[lastIndex].pos3d,
2879
2831
  indexLast: lastIndex,
2880
- indexNext: lastIndex,
2881
- applyOverTime: true
2832
+ indexNext: lastIndex
2882
2833
  };
2883
2834
  }
2884
2835
  // Binary search to find the closest position.
@@ -2904,20 +2855,25 @@
2904
2855
  return {
2905
2856
  pos3d: last.pos3d,
2906
2857
  indexLast: lastIndex,
2907
- indexNext: lastIndex,
2908
- applyOverTime: true
2858
+ indexNext: lastIndex
2909
2859
  };
2910
2860
  }
2911
2861
  // Interpolate between the two closest positions.
2912
2862
  const lastTime = last.dateTime.getTime();
2913
2863
  const nextTime = next.dateTime.getTime();
2914
2864
  const progress = (currentTimeMs - lastTime) / (nextTime - lastTime);
2915
- const interpolatedPos = Cesium.Cartesian3.lerp(last.pos3d, next.pos3d, progress, new Cesium.Cartesian3());
2865
+ // A straight line between two points on a curved path cuts inside it, so it is lifted back out.
2866
+ const chord = Cesium.Cartesian3.lerp(last.pos3d, next.pos3d, progress, new Cesium.Cartesian3());
2867
+ const chordCarto = Cesium.Cartographic.fromCartesian(chord);
2868
+ const lastHeight = (_a = Cesium.Cartographic.fromCartesian(last.pos3d)) === null || _a === void 0 ? void 0 : _a.height;
2869
+ const nextHeight = (_b = Cesium.Cartographic.fromCartesian(next.pos3d)) === null || _b === void 0 ? void 0 : _b.height;
2870
+ const interpolatedPos = chordCarto && lastHeight != null && nextHeight != null
2871
+ ? Cesium.Cartesian3.fromRadians(chordCarto.longitude, chordCarto.latitude, Cesium.Math.lerp(lastHeight, nextHeight, progress))
2872
+ : chord;
2916
2873
  return {
2917
2874
  pos3d: interpolatedPos,
2918
2875
  indexLast: lastIndex,
2919
- indexNext: nextIndex,
2920
- applyOverTime: false
2876
+ indexNext: nextIndex
2921
2877
  };
2922
2878
  }
2923
2879
  /**
@@ -5481,6 +5437,481 @@
5481
5437
  return result;
5482
5438
  }
5483
5439
 
5440
+ (function (EntitySimulation) {
5441
+ const VIEWER_KEY = "_nextspaceSimulation";
5442
+ /**
5443
+ * The set used when a caller does not name one.
5444
+ */
5445
+ EntitySimulation.DEFAULT_SET = "default";
5446
+ // Record parts replaced whole rather than merged field by field, eg. a matrix or a geometry.
5447
+ const WHOLE_RECORD_PARTS = new Set(["geometry", "boundaries", "assemblyPosition"]);
5448
+ // Record parts that carry Entity data, in the order they are laid over an Entity.
5449
+ const RECORD_PARTS = ["location", "transform", "geometry", "boundaries", "assemblyPosition", "data"];
5450
+ /**
5451
+ * The store for a viewer, made on first use and shared by everything drawing into that scene.
5452
+ */
5453
+ function Get(viewer) {
5454
+ if (!viewer) {
5455
+ return null;
5456
+ }
5457
+ if (!viewer[VIEWER_KEY]) {
5458
+ viewer[VIEWER_KEY] = new Store();
5459
+ }
5460
+ return viewer[VIEWER_KEY];
5461
+ }
5462
+ EntitySimulation.Get = Get;
5463
+ function isPlainObject(value) {
5464
+ return value != null && typeof value === "object" && !Array.isArray(value);
5465
+ }
5466
+ // Entity values replaced whole by a patch, since half of one geometry over another describes neither.
5467
+ const WHOLE_ENTITY_PARTS = new Set(["VectorGeometry", "Boundaries", "AssemblyPosition"]);
5468
+ /**
5469
+ * Returns base with patch laid over it: objects merge key by key, anything else replaces.
5470
+ * @param whole names keys at the top level, or under Bruce, whose value is replaced rather than merged.
5471
+ */
5472
+ function Overlay(base, patch, whole = WHOLE_ENTITY_PARTS) {
5473
+ const lay = (into, over, depth, underBruce) => {
5474
+ if (over === undefined) {
5475
+ return into;
5476
+ }
5477
+ if (!isPlainObject(over) || !isPlainObject(into)) {
5478
+ return over;
5479
+ }
5480
+ const merged = { ...into };
5481
+ for (const key of Object.keys(over)) {
5482
+ const replace = (depth === 0 || underBruce) && whole.has(key);
5483
+ merged[key] = replace ? over[key] : lay(merged[key], over[key], depth + 1, depth === 0 && key === "Bruce");
5484
+ }
5485
+ return merged;
5486
+ };
5487
+ return lay(base, patch, 0, false);
5488
+ }
5489
+ EntitySimulation.Overlay = Overlay;
5490
+ /*
5491
+ * Lays one record's parts over another's, each part replaced whole where a part is one value.
5492
+ */
5493
+ function overlayRecord(base, over) {
5494
+ return Overlay(base, over, WHOLE_RECORD_PARTS);
5495
+ }
5496
+ /**
5497
+ * Each value a record names, as a path into the record. A part replaced whole is one value.
5498
+ */
5499
+ function recordPaths(record) {
5500
+ const paths = [];
5501
+ const walk = (value, path) => {
5502
+ if (isPlainObject(value) && !(path.length === 1 && WHOLE_RECORD_PARTS.has(path[0]))) {
5503
+ for (const key of Object.keys(value)) {
5504
+ walk(value[key], [...path, key]);
5505
+ }
5506
+ return;
5507
+ }
5508
+ if (value !== undefined) {
5509
+ paths.push(path);
5510
+ }
5511
+ };
5512
+ for (const part of RECORD_PARTS) {
5513
+ if ((record === null || record === void 0 ? void 0 : record[part]) != null) {
5514
+ walk(record[part], [part]);
5515
+ }
5516
+ }
5517
+ return paths;
5518
+ }
5519
+ /*
5520
+ * A copy of the record without the values at these paths, dropping objects left empty.
5521
+ */
5522
+ function withoutPaths(record, paths) {
5523
+ const strip = (value, path) => {
5524
+ if (!isPlainObject(value) || !(path[0] in value)) {
5525
+ return value;
5526
+ }
5527
+ const copy = { ...value };
5528
+ if (path.length === 1) {
5529
+ delete copy[path[0]];
5530
+ }
5531
+ else {
5532
+ const inner = strip(copy[path[0]], path.slice(1));
5533
+ if (isPlainObject(inner) && !Object.keys(inner).length) {
5534
+ delete copy[path[0]];
5535
+ }
5536
+ else {
5537
+ copy[path[0]] = inner;
5538
+ }
5539
+ }
5540
+ return copy;
5541
+ };
5542
+ let stripped = record;
5543
+ for (const path of paths) {
5544
+ stripped = strip(stripped, path);
5545
+ }
5546
+ return stripped;
5547
+ }
5548
+ /*
5549
+ * Whether a record still says anything about its Entity.
5550
+ */
5551
+ function hasContent(record) {
5552
+ return RECORD_PARTS.some(part => {
5553
+ const value = record === null || record === void 0 ? void 0 : record[part];
5554
+ return value != null && !(isPlainObject(value) && !Object.keys(value).length);
5555
+ });
5556
+ }
5557
+ function timeOf(record) {
5558
+ return (record === null || record === void 0 ? void 0 : record.dateTime) ? new Date(record.dateTime).getTime() : NaN;
5559
+ }
5560
+ class Store {
5561
+ constructor() {
5562
+ // Insertion order is precedence: a later set wins.
5563
+ this.sets = new Map();
5564
+ this.onChanged = new BModels.BruceEvent();
5565
+ }
5566
+ /**
5567
+ * Triggered after anything is simulated or cleared, naming the Entities affected.
5568
+ */
5569
+ get OnChanged() {
5570
+ return this.onChanged;
5571
+ }
5572
+ /**
5573
+ * Whether anything at all is simulated, so a caller can skip the work when nothing is.
5574
+ */
5575
+ get IsEmpty() {
5576
+ for (const set of this.sets.values()) {
5577
+ if (set.entities.size || set.patches.size || set.records.size) {
5578
+ return false;
5579
+ }
5580
+ }
5581
+ return true;
5582
+ }
5583
+ /**
5584
+ * The names of the sets holding anything, oldest first.
5585
+ */
5586
+ GetSets() {
5587
+ return Array.from(this.sets.keys());
5588
+ }
5589
+ /**
5590
+ * The time each set's records span, oldest set first, for sets holding any records.
5591
+ */
5592
+ GetSetRanges() {
5593
+ const ranges = [];
5594
+ for (const [name, set] of this.sets) {
5595
+ let startMs = Infinity;
5596
+ let endMs = -Infinity;
5597
+ for (const byTime of set.records.values()) {
5598
+ for (const ms of byTime.keys()) {
5599
+ startMs = Math.min(startMs, ms);
5600
+ endMs = Math.max(endMs, ms);
5601
+ }
5602
+ }
5603
+ if (set.records.size) {
5604
+ ranges.push({ set: name, startMs, endMs, entityIds: Array.from(set.records.keys()) });
5605
+ }
5606
+ }
5607
+ return ranges;
5608
+ }
5609
+ setFor(name) {
5610
+ const key = name || EntitySimulation.DEFAULT_SET;
5611
+ let set = this.sets.get(key);
5612
+ if (!set) {
5613
+ set = { entities: new Map(), patches: new Map(), records: new Map() };
5614
+ this.sets.set(key, set);
5615
+ }
5616
+ return set;
5617
+ }
5618
+ /**
5619
+ * Replaces the stand-in for each Entity given, within a set.
5620
+ */
5621
+ SetEntities(entities, set) {
5622
+ this.setBlobs(entities, set, false);
5623
+ }
5624
+ /**
5625
+ * Replaces the patch for each Entity given, within a set. A patch names only the data it changes.
5626
+ */
5627
+ SetPatches(patches, set) {
5628
+ this.setBlobs(patches, set, true);
5629
+ }
5630
+ setBlobs(blobs, set, patch) {
5631
+ var _a;
5632
+ const ids = [];
5633
+ for (const blob of blobs || []) {
5634
+ const id = (_a = blob === null || blob === void 0 ? void 0 : blob.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
5635
+ if (!id) {
5636
+ continue;
5637
+ }
5638
+ const held = this.setFor(set);
5639
+ (patch ? held.patches : held.entities).set(id, blob);
5640
+ ids.push(id);
5641
+ }
5642
+ if (ids.length) {
5643
+ this.onChanged.Trigger({ entityIds: ids, entities: true, records: false, set: set || EntitySimulation.DEFAULT_SET });
5644
+ }
5645
+ }
5646
+ /**
5647
+ * Adds records to each Entity's simulated history within a set, replacing any at the same instant.
5648
+ */
5649
+ SetRecords(records, set) {
5650
+ const ids = new Set();
5651
+ for (const record of records || []) {
5652
+ const id = record === null || record === void 0 ? void 0 : record.entityId;
5653
+ const timeMs = timeOf(record);
5654
+ if (!id || isNaN(timeMs)) {
5655
+ continue;
5656
+ }
5657
+ const held = this.setFor(set);
5658
+ let byTime = held.records.get(id);
5659
+ if (!byTime) {
5660
+ byTime = new Map();
5661
+ held.records.set(id, byTime);
5662
+ }
5663
+ byTime.set(timeMs, record);
5664
+ ids.add(id);
5665
+ }
5666
+ if (ids.size) {
5667
+ this.onChanged.Trigger({ entityIds: Array.from(ids), entities: false, records: true, set: set || EntitySimulation.DEFAULT_SET });
5668
+ }
5669
+ }
5670
+ /**
5671
+ * Forgets what is simulated for these Entities, or for every Entity when none are named.
5672
+ * @param params.set the set to clear. By default every set.
5673
+ * @param params.entities clear the stand-ins and patches. Default true.
5674
+ * @param params.records clear the historic records. Default true.
5675
+ */
5676
+ Clear(params) {
5677
+ var _a, _b;
5678
+ const entities = (params === null || params === void 0 ? void 0 : params.entities) !== false;
5679
+ const records = (params === null || params === void 0 ? void 0 : params.records) !== false;
5680
+ const names = (params === null || params === void 0 ? void 0 : params.set) ? [params.set] : Array.from(this.sets.keys());
5681
+ const cleared = new Set();
5682
+ for (const name of names) {
5683
+ const set = this.sets.get(name);
5684
+ if (!set) {
5685
+ continue;
5686
+ }
5687
+ 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()];
5688
+ for (const id of ids) {
5689
+ const hadEntity = entities && set.entities.delete(id);
5690
+ const hadPatch = entities && set.patches.delete(id);
5691
+ const hadRecords = records && set.records.delete(id);
5692
+ if (hadEntity || hadPatch || hadRecords) {
5693
+ cleared.add(id);
5694
+ }
5695
+ }
5696
+ if (!set.entities.size && !set.patches.size && !set.records.size) {
5697
+ this.sets.delete(name);
5698
+ }
5699
+ }
5700
+ if (cleared.size) {
5701
+ 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 });
5702
+ }
5703
+ }
5704
+ /**
5705
+ * The Entity's stand-in, with any patches laid over it, or null when nothing stands in for it.
5706
+ */
5707
+ GetEntity(entityId) {
5708
+ var _a;
5709
+ let standIn = null;
5710
+ for (const set of this.sets.values()) {
5711
+ standIn = (_a = set.entities.get(entityId)) !== null && _a !== void 0 ? _a : standIn;
5712
+ }
5713
+ return standIn ? this.Overlay(standIn) : null;
5714
+ }
5715
+ /**
5716
+ * Whether a stand-in replaces this Entity, so the API need not be asked for it.
5717
+ */
5718
+ HasEntity(entityId) {
5719
+ for (const set of this.sets.values()) {
5720
+ if (set.entities.has(entityId)) {
5721
+ return true;
5722
+ }
5723
+ }
5724
+ return false;
5725
+ }
5726
+ /**
5727
+ * Whether a stand-in or a patch changes this Entity.
5728
+ */
5729
+ HasBlob(entityId) {
5730
+ for (const set of this.sets.values()) {
5731
+ if (set.entities.has(entityId) || set.patches.has(entityId)) {
5732
+ return true;
5733
+ }
5734
+ }
5735
+ return false;
5736
+ }
5737
+ /**
5738
+ * Returns an Entity with its simulated patches laid over it. The input is never changed.
5739
+ */
5740
+ Overlay(entity) {
5741
+ var _a;
5742
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
5743
+ if (!id) {
5744
+ return entity;
5745
+ }
5746
+ let result = entity;
5747
+ for (const set of this.sets.values()) {
5748
+ const patch = set.patches.get(id);
5749
+ if (patch) {
5750
+ result = Overlay(result, patch);
5751
+ }
5752
+ }
5753
+ return result;
5754
+ }
5755
+ /**
5756
+ * The Entity as simulated: its stand-in if there is one, otherwise what was read with patches laid over it.
5757
+ * @param entityId the Entity.
5758
+ * @param read what the API gave for it, if it was asked.
5759
+ */
5760
+ Resolve(entityId, read) {
5761
+ var _a;
5762
+ return (_a = this.GetEntity(entityId)) !== null && _a !== void 0 ? _a : (read ? this.Overlay(read) : null);
5763
+ }
5764
+ /**
5765
+ * Every Entity with a simulated stand-in or patch.
5766
+ */
5767
+ GetEntityIds() {
5768
+ const ids = new Set();
5769
+ for (const set of this.sets.values()) {
5770
+ set.entities.forEach((_, id) => ids.add(id));
5771
+ set.patches.forEach((_, id) => ids.add(id));
5772
+ }
5773
+ return Array.from(ids);
5774
+ }
5775
+ /**
5776
+ * Every Entity with simulated history.
5777
+ */
5778
+ GetRecordEntityIds() {
5779
+ const ids = new Set();
5780
+ for (const set of this.sets.values()) {
5781
+ set.records.forEach((_, id) => ids.add(id));
5782
+ }
5783
+ return Array.from(ids);
5784
+ }
5785
+ /**
5786
+ * An Entity's simulated history for a historic key, oldest first, every set's record at an instant merged.
5787
+ */
5788
+ GetRecords(entityId, attrKey) {
5789
+ const byTime = new Map();
5790
+ for (const set of this.sets.values()) {
5791
+ for (const [ms, record] of set.records.get(entityId) || []) {
5792
+ if (attrKey && record.attrKey && record.attrKey !== attrKey) {
5793
+ continue;
5794
+ }
5795
+ const held = byTime.get(ms);
5796
+ byTime.set(ms, held ? overlayRecord(held, record) : record);
5797
+ }
5798
+ }
5799
+ return Array.from(byTime.entries()).sort(([a], [b]) => a - b).map(([, record]) => record);
5800
+ }
5801
+ /**
5802
+ * Each value an Entity's simulated history names, with the instant from which it is the simulation's.
5803
+ */
5804
+ ownedFrom(records) {
5805
+ const owned = new Map();
5806
+ for (const record of records) {
5807
+ const ms = timeOf(record);
5808
+ for (const path of recordPaths(record)) {
5809
+ const key = path.join("\u0000");
5810
+ if (!owned.has(key)) {
5811
+ owned.set(key, { path, fromMs: ms });
5812
+ }
5813
+ }
5814
+ }
5815
+ return Array.from(owned.values());
5816
+ }
5817
+ /**
5818
+ * An Entity as it stands at an instant, with each value its simulated records have named by then laid over it.
5819
+ * @param entity the Entity as read for that instant, from the API or a simulated stand-in.
5820
+ * @param timeMs the instant in milliseconds.
5821
+ * @param attrKey the historic attribute key being followed, or null for any.
5822
+ */
5823
+ ApplyAt(entity, timeMs, attrKey) {
5824
+ var _a, _b, _c;
5825
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
5826
+ if (!id || timeMs == null || isNaN(timeMs)) {
5827
+ return entity;
5828
+ }
5829
+ // Laid oldest first, each record replacing only what it names, which leaves every value at its newest.
5830
+ let state = null;
5831
+ let stateMs = -Infinity;
5832
+ for (const record of this.GetRecords(id, attrKey)) {
5833
+ const ms = timeOf(record);
5834
+ if (ms > timeMs) {
5835
+ break;
5836
+ }
5837
+ const parts = {};
5838
+ for (const part of RECORD_PARTS) {
5839
+ if (record[part] != null) {
5840
+ parts[part] = record[part];
5841
+ }
5842
+ }
5843
+ state = state ? overlayRecord(state, parts) : parts;
5844
+ stateMs = ms;
5845
+ }
5846
+ if (!state) {
5847
+ return entity;
5848
+ }
5849
+ const bruce = { ...entity.Bruce };
5850
+ if (state.location) {
5851
+ bruce.Location = Overlay(bruce.Location, state.location);
5852
+ }
5853
+ if (state.transform) {
5854
+ bruce.Transform = Overlay(bruce.Transform, state.transform);
5855
+ }
5856
+ if (state.geometry) {
5857
+ bruce.VectorGeometry = state.geometry;
5858
+ }
5859
+ if (state.boundaries) {
5860
+ bruce.Boundaries = state.boundaries;
5861
+ }
5862
+ if (state.assemblyPosition) {
5863
+ bruce.AssemblyPosition = state.assemblyPosition;
5864
+ }
5865
+ // Says the newest record the Entity now reflects, the way a read at a point does.
5866
+ const outline = entity.Bruce.Outline || [];
5867
+ const readAt = outline.find(x => !!x.DateTime);
5868
+ const readMs = readAt ? new Date(readAt.DateTime).getTime() : -Infinity;
5869
+ const dated = {
5870
+ ...(readAt || { "Source.Name": "Simulation", Editable: false, Kind: BModels.Entity.EOutlineKind.Attribute }),
5871
+ DateTime: new Date(Math.max(readMs, stateMs)).toISOString(),
5872
+ ForeignKey: (_b = readAt === null || readAt === void 0 ? void 0 : readAt.ForeignKey) !== null && _b !== void 0 ? _b : attrKey
5873
+ };
5874
+ bruce.Outline = [dated, ...outline.filter(x => x !== readAt)];
5875
+ const data = state.data ? Overlay({ ...entity, Bruce: undefined }, state.data) : entity;
5876
+ return { ...data, Bruce: Overlay(bruce, (_c = state.data) === null || _c === void 0 ? void 0 : _c.Bruce) };
5877
+ }
5878
+ /**
5879
+ * API records with the simulated ones joined in, for the named Entities or every simulated one.
5880
+ */
5881
+ MergeRecords(recordsByIds, attrKey, entityIds) {
5882
+ if (this.IsEmpty) {
5883
+ return recordsByIds;
5884
+ }
5885
+ const ids = entityIds !== null && entityIds !== void 0 ? entityIds : this.GetRecordEntityIds();
5886
+ let merged = null;
5887
+ for (const id of ids) {
5888
+ const simulated = this.GetRecords(id, attrKey);
5889
+ if (!simulated.length) {
5890
+ continue;
5891
+ }
5892
+ const owned = this.ownedFrom(simulated);
5893
+ const byTime = new Map();
5894
+ for (const record of (recordsByIds === null || recordsByIds === void 0 ? void 0 : recordsByIds[id]) || []) {
5895
+ const ms = timeOf(record);
5896
+ const kept = withoutPaths(record, owned.filter(x => x.fromMs <= ms).map(x => x.path));
5897
+ if (hasContent(kept)) {
5898
+ byTime.set(ms, kept);
5899
+ }
5900
+ }
5901
+ for (const record of simulated) {
5902
+ const ms = timeOf(record);
5903
+ const held = byTime.get(ms);
5904
+ byTime.set(ms, held ? overlayRecord(held, record) : record);
5905
+ }
5906
+ merged = merged !== null && merged !== void 0 ? merged : { ...(recordsByIds || {}) };
5907
+ merged[id] = Array.from(byTime.entries()).sort(([a], [b]) => a - b).map(([, record]) => record);
5908
+ }
5909
+ return merged !== null && merged !== void 0 ? merged : recordsByIds;
5910
+ }
5911
+ }
5912
+ EntitySimulation.Store = Store;
5913
+ })(exports.EntitySimulation || (exports.EntitySimulation = {}));
5914
+
5484
5915
  /**
5485
5916
  * Returns cesium property's value.
5486
5917
  * This will check if it's one that changes over time, or just a fixed value.
@@ -5532,6 +5963,8 @@
5532
5963
  const PHOTO_CACHE_KEY = "_nextspaceLabelPhotos";
5533
5964
  // Clearance around a model's box, as a share of its half-extent on each axis.
5534
5965
  const MODEL_BOX_PADDING = 0.02;
5966
+ // How far a moving model's box may drift before it is moved, in metres.
5967
+ const MODEL_BOX_FOLLOW_TOLERANCE = 0.001;
5535
5968
  // Share of each edge drawn out from its corners while the box is idle. Hovering draws the edges whole.
5536
5969
  const MODEL_BOX_BRACKET = 0.2;
5537
5970
  // Under this many pixels across, a box reads as a smudge and the ring takes over.
@@ -6420,6 +6853,22 @@
6420
6853
  return centre && half && (axes === null || axes === void 0 ? void 0 : axes.every(Boolean)) ? { centre, axes, half } : null;
6421
6854
  }
6422
6855
  EntityLabel.ReadOrientedBox = ReadOrientedBox;
6856
+ /*
6857
+ * Whether a placement turns what it places. Scale alone does not count.
6858
+ */
6859
+ function isRotated(matrix) {
6860
+ for (let column = 0; column < 3; column++) {
6861
+ const axis = [0, 1, 2].map(row => Number(matrix[row][column]));
6862
+ const length = Math.hypot(...axis);
6863
+ if (!(length > 0)) {
6864
+ return false;
6865
+ }
6866
+ if (axis.some((value, row) => Math.abs(value / length - Number(row === column)) > 1e-5)) {
6867
+ return true;
6868
+ }
6869
+ }
6870
+ return false;
6871
+ }
6423
6872
  /*
6424
6873
  * The eight padded world corners of an assembly Entity's geometry box, or null when the record cannot give one.
6425
6874
  *
@@ -6438,6 +6887,10 @@
6438
6887
  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))) {
6439
6888
  return null;
6440
6889
  }
6890
+ // A pure translation can hide a rotation baked into the vertices, which leaves the extents loose.
6891
+ if (!shape && !isRotated(matrix)) {
6892
+ return null;
6893
+ }
6441
6894
  const pivot = ((_c = bruce.GeometryPivot) === null || _c === void 0 ? void 0 : _c.length) >= 3 ? bruce.GeometryPivot.slice(0, 3).map(Number) : [0, 0, 0];
6442
6895
  // Without an oriented box the record's own extents are an axis-aligned box about the pivot.
6443
6896
  const box = shape || { centre: pivot, axes: [[1, 0, 0], [0, 1, 0], [0, 0, 1]], half: half };
@@ -6728,6 +7181,7 @@
6728
7181
  constructor() {
6729
7182
  // Whether a box competes for space comes only from inputs (camera distance, hover, the pointer), never from
6730
7183
  // how its label was laid out, so an animation moving a label cannot feed back into which boxes show.
7184
+ // corners is where the box is drawn this frame: stored, moved by the entry's motion.
6731
7185
  this.entries = new Map();
6732
7186
  this.groups = null;
6733
7187
  // The hovered Entity. Its box is drawn on its own and whole, ahead of every other box.
@@ -6745,7 +7199,7 @@
6745
7199
  Set(id, corners, entityId, inputs) {
6746
7200
  const current = this.entries.get(id);
6747
7201
  // A label rebuilt over the same box keeps its place and how far it has animated.
6748
- if (corners && (current === null || current === void 0 ? void 0 : current.corners) === corners && current.entityId === entityId) {
7202
+ if (corners && (current === null || current === void 0 ? void 0 : current.stored) === corners && current.entityId === entityId) {
6749
7203
  Object.assign(current, inputs);
6750
7204
  return;
6751
7205
  }
@@ -6753,17 +7207,41 @@
6753
7207
  return;
6754
7208
  }
6755
7209
  if (corners) {
6756
- this.entries.set(id, { ...inputs, corners, entityId, active: false, wanted: false });
7210
+ this.entries.set(id, { ...inputs, corners, stored: corners, entityId, active: false, wanted: false });
6757
7211
  }
6758
7212
  else
6759
7213
  this.entries.delete(id);
6760
7214
  this.since.delete(id);
7215
+ this.forget(id);
7216
+ }
7217
+ /*
7218
+ * Drops the groups and outlines a box was in, so they are worked out again from where it is now.
7219
+ */
7220
+ forget(id) {
6761
7221
  this.groups = null;
6762
7222
  for (const [key, group] of this.outlines) {
6763
7223
  if (group.members.includes(id))
6764
7224
  this.outlines.delete(key);
6765
7225
  }
6766
7226
  }
7227
+ /*
7228
+ * Moves each box whose model moved since the last frame, keeping where it has got to in showing.
7229
+ */
7230
+ follow() {
7231
+ var _a;
7232
+ for (const [id, entry] of this.entries) {
7233
+ const motion = (_a = entry.motion) === null || _a === void 0 ? void 0 : _a.call(entry);
7234
+ if (!motion) {
7235
+ continue;
7236
+ }
7237
+ const moved = entry.stored.map(corner => Cesium.Matrix4.multiplyByPoint(motion, corner, new Cesium.Cartesian3()));
7238
+ if (moved.every((corner, i) => Cesium.Cartesian3.equalsEpsilon(corner, entry.corners[i], 0, MODEL_BOX_FOLLOW_TOLERANCE))) {
7239
+ continue;
7240
+ }
7241
+ entry.corners = moved;
7242
+ this.forget(id);
7243
+ }
7244
+ }
6767
7245
  /*
6768
7246
  * The box drawn for a label this frame, or undefined when it has none or lost its place.
6769
7247
  */
@@ -6831,6 +7309,7 @@
6831
7309
  */
6832
7310
  Resolve(viewer, pointer) {
6833
7311
  var _a, _b, _c, _d;
7312
+ this.follow();
6834
7313
  this.frames.clear();
6835
7314
  this.hulls.clear();
6836
7315
  this.drawn = [];
@@ -7253,9 +7732,16 @@
7253
7732
  }
7254
7733
  if (this.api) {
7255
7734
  const rego = this.rego;
7735
+ const simulation = exports.EntitySimulation.Get(this.viewer);
7736
+ let simulated = null;
7737
+ if (simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob(rego.entityId)) {
7738
+ // A stand-in is named as is; a patch is laid over the API's record first.
7739
+ const read = simulation.HasEntity(rego.entityId) ? null : (await BModels.Entity.Get({ api: this.api, entityId: rego.entityId, migrated: true })).entity;
7740
+ simulated = simulation.Resolve(rego.entityId, read);
7741
+ }
7256
7742
  this.rego.name = await BModels.Entity.CalculateNameAsync({
7257
7743
  api: this.api,
7258
- entity: null,
7744
+ entity: simulated,
7259
7745
  entityId: rego.entityId,
7260
7746
  defaultToId: false
7261
7747
  });
@@ -7284,6 +7770,21 @@
7284
7770
  clearTimeout(this.loadTimeout);
7285
7771
  this.loadTimeout = null;
7286
7772
  }
7773
+ /*
7774
+ * Reads the label's Entity again and redraws it, eg. after what is simulated for it changed.
7775
+ */
7776
+ Reload() {
7777
+ if (this.disposed) {
7778
+ return;
7779
+ }
7780
+ this.additionalTexts = this.options.text != null ? [] : null;
7781
+ this.loadedName = false;
7782
+ // The name came from whatever record the visual was drawn from, so it is worked out again too.
7783
+ if (this.rego) {
7784
+ this.rego.name = null;
7785
+ }
7786
+ this.createLabel();
7787
+ }
7287
7788
  /*
7288
7789
  * Rebuilds only when configuration changes; state refreshes must not recreate DOM every frame.
7289
7790
  */
@@ -7514,7 +8015,7 @@
7514
8015
  }
7515
8016
  clearTimeout(this.loadTimeout);
7516
8017
  this.loadTimeout = setTimeout(async () => {
7517
- var _a, _b, _c, _d, _e;
8018
+ var _a, _b, _c, _d, _e, _f;
7518
8019
  if (this.disposed || this.counter != counter) {
7519
8020
  return;
7520
8021
  }
@@ -7541,16 +8042,19 @@
7541
8042
  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;
7542
8043
  // Read for its attributes, and for a photo when the label has no icon of its own.
7543
8044
  if (attributes || !HasIcon(this.options)) {
7544
- let entity = null;
8045
+ // A simulated stand-in is labelled as is, and a patch is laid over the API's.
8046
+ const simulation = exports.EntitySimulation.Get(this.viewer);
8047
+ let entity = (_d = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(this.rego.entityId)) !== null && _d !== void 0 ? _d : null;
7545
8048
  try {
7546
- entity = (await BModels.Entity.Get({
8049
+ entity = entity !== null && entity !== void 0 ? entity : (await BModels.Entity.Get({
7547
8050
  api: this.api,
7548
8051
  entityId: this.rego.entityId,
7549
- 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,
8052
+ 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,
7550
8053
  schemaId: this.rego.schema,
7551
8054
  migrated: true,
7552
8055
  expandAttachments: !HasIcon(this.options)
7553
8056
  })).entity;
8057
+ entity = entity && simulation ? simulation.Resolve(this.rego.entityId, entity) : entity;
7554
8058
  }
7555
8059
  // Entity doesn't exist.
7556
8060
  // Likely a graphic with an ID that points to no record.
@@ -7934,11 +8438,17 @@
7934
8438
  // Past the wait a slow record no longer holds the label back; a box that comes later eases in.
7935
8439
  setTimeout(settle, MODEL_BOX_WAIT);
7936
8440
  this.modelBox.load.then(corners => {
8441
+ var _a, _b;
7937
8442
  if (!corners || this.disposed || this.counter !== counter) {
7938
8443
  return;
7939
8444
  }
7940
8445
  // A record that disagrees with where the model was found describes another placement, such as a moved tileset.
8446
+ // A part known to have moved from its record, eg. a joint turning, is judged where it has moved to.
8447
+ 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);
7941
8448
  const centre = Cesium.Cartesian3.midpoint(corners[0], corners[7], new Cesium.Cartesian3());
8449
+ if (motion) {
8450
+ Cesium.Matrix4.multiplyByPoint(motion, centre, centre);
8451
+ }
7942
8452
  if (Cesium.Cartesian3.distance(centre, found) > Cesium.Cartesian3.distance(corners[0], corners[7]) * 0.75 + 2) {
7943
8453
  return;
7944
8454
  }
@@ -7946,6 +8456,7 @@
7946
8456
  // Let go a little further out than it is taken, so a camera resting at the limit does not flip it.
7947
8457
  let near = false;
7948
8458
  layer.modelBoxes.Set(this.id, corners, rego.entityId, {
8459
+ motion: rego.motion ? () => rego.motion(this.viewer.clock.currentTime) : undefined,
7949
8460
  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); },
7950
8461
  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)); },
7951
8462
  near: () => {
@@ -8152,7 +8663,7 @@
8152
8663
  viewportWidth: this.viewer.canvas.clientWidth,
8153
8664
  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,
8154
8665
  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,
8155
- iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius, modelBox: boxed || undefined, boxBlend,
8666
+ iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius, modelBox: boxed || undefined, boxBlend, follows: !pinned && Boolean(transientPivot || cursorMotion.target),
8156
8667
  // Priority shows the hovered member individually without dragging its cluster along.
8157
8668
  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
8158
8669
  };
@@ -8279,15 +8790,16 @@
8279
8790
  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));
8280
8791
  const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
8281
8792
  // Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
8282
- const targetOuter = iconRing || targetRadius || 2.5;
8283
- const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
8793
+ // A ring around the cursor marks nothing, so a riding label keeps only its leader.
8794
+ const targetOuter = frame.follows ? 0 : iconRing || targetRadius || 2.5;
8795
+ const targetInner = frame.follows ? 0 : iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
8284
8796
  const now = Date.now();
8285
8797
  const duration = (_p = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _p !== void 0 ? _p : 200;
8286
8798
  const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
8287
8799
  let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
8288
8800
  if (!ringAnimation) {
8289
- const outer = iconRing || 2.5;
8290
- const inner = iconRing;
8801
+ const outer = frame.follows ? 0 : iconRing || 2.5;
8802
+ const inner = frame.follows ? 0 : iconRing;
8291
8803
  ringAnimation = { target: outer, targetInner: inner, start: now, outer, inner, fromOuter: outer, fromInner: inner };
8292
8804
  }
8293
8805
  const animateRing = () => {
@@ -8925,9 +9437,9 @@
8925
9437
  * Reads the entities behind items that name one without carrying it, attachments included,
8926
9438
  * so a label's default image needs no second request.
8927
9439
  */
8928
- async function Hydrate(items, api, signal) {
9440
+ async function Hydrate(items, api, signal, simulation) {
8929
9441
  var _a, _b;
8930
- if (!api) {
9442
+ if (!api && !simulation) {
8931
9443
  return items;
8932
9444
  }
8933
9445
  const hydrated = items.slice();
@@ -8938,12 +9450,15 @@
8938
9450
  }
8939
9451
  const chunk = missing.slice(start, start + HYDRATE_AT_ONCE);
8940
9452
  try {
8941
- const { entities } = await BModels.Entity.GetListByIds({
8942
- api, entityIds: chunk.map(at => hydrated[at].entityId), expandAttachments: true, migrated: true
8943
- });
9453
+ // A simulated stand-in is labelled as is, never from the API, and a patch is laid over the API's.
9454
+ const asked = chunk.filter(at => !(simulation === null || simulation === void 0 ? void 0 : simulation.HasEntity(hydrated[at].entityId)));
9455
+ const { entities } = api && asked.length ? await BModels.Entity.GetListByIds({
9456
+ api, entityIds: asked.map(at => hydrated[at].entityId), expandAttachments: true, migrated: true
9457
+ }) : { entities: [] };
8944
9458
  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]; }));
8945
9459
  for (const at of chunk) {
8946
- const entity = found.get(hydrated[at].entityId);
9460
+ const id = hydrated[at].entityId;
9461
+ const entity = simulation ? simulation.Resolve(id, found.get(id)) : found.get(id);
8947
9462
  if (entity) {
8948
9463
  // The API leaves Attachment out when there are none; an empty list records that they were read.
8949
9464
  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 : [] } } };
@@ -8960,7 +9475,7 @@
8960
9475
  /*
8961
9476
  * Uses the existing DataLab grammar, discarding actions and projections that cannot return Entities.
8962
9477
  */
8963
- async function Loader(source, api, signal, hydrate) {
9478
+ async function Loader(source, api, signal, hydrate, simulation) {
8964
9479
  var _a;
8965
9480
  var _b;
8966
9481
  if (source.type === "provider") {
@@ -8975,7 +9490,7 @@
8975
9490
  return async (offset, limit, signal) => {
8976
9491
  const page = items.slice(offset, offset + limit);
8977
9492
  return {
8978
- items: hydrate ? await Hydrate(page, api, signal) : page,
9493
+ items: hydrate ? await Hydrate(page, api, signal, simulation) : page,
8979
9494
  hasMore: offset + limit < items.length
8980
9495
  };
8981
9496
  };
@@ -9117,7 +9632,7 @@
9117
9632
  const items = new Map();
9118
9633
  let truncated = false;
9119
9634
  try {
9120
- const load = await Loader(definition.source, (_g = this.register.ApiGetters) === null || _g === void 0 ? void 0 : _g.GetBruceApi(), controller.signal, NeedsEntityData(definition.options));
9635
+ const load = await Loader(definition.source, (_g = this.register.ApiGetters) === null || _g === void 0 ? void 0 : _g.GetBruceApi(), controller.signal, NeedsEntityData(definition.options), exports.EntitySimulation.Get(this.register.Viewer));
9121
9636
  let offset = 0;
9122
9637
  for (let pageNumber = 0;; pageNumber++) {
9123
9638
  if (controller.signal.aborted || this.register.Disposed) {
@@ -9939,8 +10454,25 @@
9939
10454
  return `${relation["Related.Entity.ID"]}-${relation["Principal.Entity.ID"]}-${relation["Relation.Type.ID"]}`;
9940
10455
  }
9941
10456
  RelationRenderEngine.GetRenderGroupId = GetRenderGroupId;
10457
+ /**
10458
+ * Removes and returns a menu item's drawn relations that end at, or carry data from, any of these Entities.
10459
+ */
10460
+ function TakeRelationsTouching(params) {
10461
+ const { visualRegister, menuItemId } = params;
10462
+ const ids = new Set(params.entityIds);
10463
+ const touched = visualRegister.GetRegos({ menuItemId }).filter((rego) => {
10464
+ const relation = rego.relation;
10465
+ return relation && [relation["Principal.Entity.ID"], relation["Related.Entity.ID"], relation["Data.Entity.ID"]]
10466
+ .some(id => id && ids.has(id));
10467
+ });
10468
+ for (const rego of touched) {
10469
+ visualRegister.RemoveRegos({ relation: rego.relation, entityId: rego.entityId, menuItemId, requestRender: false });
10470
+ }
10471
+ return touched.map(rego => rego.relation);
10472
+ }
10473
+ RelationRenderEngine.TakeRelationsTouching = TakeRelationsTouching;
9942
10474
  async function Render(params) {
9943
- const { apiGetter, viewer, visualRegister, menuItemId, relations } = params;
10475
+ const { apiGetter, viewer, visualRegister, menuItemId, relations, simulation } = params;
9944
10476
  const api = apiGetter.getApi(apiGetter.accountId);
9945
10477
  const rendered = {};
9946
10478
  let neededEntityIdsIds = [];
@@ -9958,13 +10490,15 @@
9958
10490
  }
9959
10491
  // Turn the set unique.
9960
10492
  neededEntityIdsIds = neededEntityIdsIds.filter((v, i, a) => a.indexOf(v) === i);
9961
- // Get all entities in one go.
9962
- let entities = (await BModels.Entity.GetListByIds({
9963
- entityIds: neededEntityIdsIds,
10493
+ // Get all entities in one go, other than the simulated ones.
10494
+ const simulated = neededEntityIdsIds.map(id => simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(id)).filter(x => !!x);
10495
+ const apiIds = neededEntityIdsIds.filter(id => !(simulation === null || simulation === void 0 ? void 0 : simulation.HasEntity(id)));
10496
+ let entities = (apiIds.length ? (await BModels.Entity.GetListByIds({
10497
+ entityIds: apiIds,
9964
10498
  api: apiGetter.getApi(),
9965
10499
  migrated: true,
9966
10500
  maxSearchTimeSec: 60 * 2
9967
- })).entities;
10501
+ })).entities.map(entity => simulation ? simulation.Overlay(entity) : entity) : []).concat(simulated);
9968
10502
  // Create a map for quick reference.
9969
10503
  const entitiesMap = {};
9970
10504
  for (let i = 0; i < entities.length; i++) {
@@ -10458,6 +10992,8 @@
10458
10992
  rego.overrideShow != null ||
10459
10993
  // Being dragged around right now, so its extent is changing faster than we could measure it.
10460
10994
  rego.editing == true ||
10995
+ // Placed by the clock every frame, so no measurement of where it is stays true.
10996
+ rego.moving == true ||
10461
10997
  // Part of a collection we did not create, eg. a geojson data source, which may have its own rules for
10462
10998
  // what is in the scene. Our own per-menu-item data sources are fair game.
10463
10999
  exports.VisualsRegister.IsForeignCollection(rego) ||
@@ -11292,6 +11828,7 @@
11292
11828
  }
11293
11829
  get OnLabelGroupsChanged() { return this.labelGroupsChanged; }
11294
11830
  constructor(params) {
11831
+ var _a, _b;
11295
11832
  this.id = BModels.ObjectUtils.UId();
11296
11833
  this.disposed = false;
11297
11834
  this.rego = {};
@@ -11331,6 +11868,8 @@
11331
11868
  this.menuItemSources = new Map();
11332
11869
  // Set when the data source order no longer matches the zIndexes seen, so it is re-applied once.
11333
11870
  this.sourceOrderDirty = false;
11871
+ // Stops following what is simulated, so labels can read it again when it changes.
11872
+ this.simulationRemoval = null;
11334
11873
  this.labelGroupsChanged = new BModels.BruceEvent();
11335
11874
  this.hoverRingState = {};
11336
11875
  this.retiringHoverLabels = new Map();
@@ -11365,6 +11904,33 @@
11365
11904
  this.cameraCullerDispose = exports.VisualRegisterCuller.Monitor({
11366
11905
  register: this
11367
11906
  });
11907
+ this.simulationRemoval = (_b = (_a = exports.EntitySimulation.Get(this.viewer)) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => this.reloadLabels(change.entityIds))) !== null && _b !== void 0 ? _b : null;
11908
+ }
11909
+ /*
11910
+ * Tells labels to read their Entity again, since they keep what they read when drawn.
11911
+ */
11912
+ reloadLabels(entityIds) {
11913
+ var _a, _b, _c, _d;
11914
+ if (this.disposed) {
11915
+ return;
11916
+ }
11917
+ const groupIds = new Set();
11918
+ for (const entityId of entityIds) {
11919
+ for (const rego of this.GetRegos({ entityId })) {
11920
+ (_a = exports.EntityLabel.GetLabel({ rego })) === null || _a === void 0 ? void 0 : _a.Reload();
11921
+ }
11922
+ if (((_b = this.labelHover) === null || _b === void 0 ? void 0 : _b.entityId) === entityId) {
11923
+ (_c = this.hoverLabel) === null || _c === void 0 ? void 0 : _c.Reload();
11924
+ }
11925
+ const groupId = (_d = this.GetLabelGroupFor(entityId)) === null || _d === void 0 ? void 0 : _d.groupId;
11926
+ if (groupId) {
11927
+ groupIds.add(groupId);
11928
+ }
11929
+ }
11930
+ // A group may have read the Entity in with its items, so the group is read again as a whole.
11931
+ for (const groupId of groupIds) {
11932
+ this.RefreshLabelGroup(groupId).catch((e) => console.error(e));
11933
+ }
11368
11934
  }
11369
11935
  /**
11370
11936
  * Returns the collection a menu item's visuals belong in, creating its data source on first use.
@@ -12267,7 +12833,9 @@
12267
12833
  (_b = this.viewer.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
12268
12834
  }
12269
12835
  Dispose() {
12270
- var _a, _b, _c, _d, _e;
12836
+ var _a, _b, _c, _d, _e, _f;
12837
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
12838
+ this.simulationRemoval = null;
12271
12839
  exports.CesiumEntityStyler.SetDimming(this.viewer, this.id, false);
12272
12840
  this.ClearLabelHover();
12273
12841
  clearTimeout(this.hoverRingReset);
@@ -12277,14 +12845,14 @@
12277
12845
  exports.EntityLabel.Detatch({ label });
12278
12846
  }
12279
12847
  this.retiringHoverLabels.clear();
12280
- (_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.Dispose();
12848
+ (_b = this.labelGroups) === null || _b === void 0 ? void 0 : _b.Dispose();
12281
12849
  this.labelGroupsChanged.Clear();
12282
12850
  for (const regos of Object.values(this.rego)) {
12283
12851
  for (const rego of regos)
12284
12852
  exports.EntityLabel.Detatch({ rego, force: true });
12285
12853
  }
12286
12854
  exports.EntityLabel.SetContainer(this.viewer);
12287
- (_b = this.cameraCullerDispose) === null || _b === void 0 ? void 0 : _b.call(this, {
12855
+ (_c = this.cameraCullerDispose) === null || _c === void 0 ? void 0 : _c.call(this, {
12288
12856
  uncull: false
12289
12857
  });
12290
12858
  this.cameraCullerDispose = null;
@@ -12300,7 +12868,7 @@
12300
12868
  this.disposed = true;
12301
12869
  for (const record of Array.from(this.menuItemSources.values())) {
12302
12870
  try {
12303
- (_d = (_c = this.viewer) === null || _c === void 0 ? void 0 : _c.dataSources) === null || _d === void 0 ? void 0 : _d.remove(record.source, true);
12871
+ (_e = (_d = this.viewer) === null || _d === void 0 ? void 0 : _d.dataSources) === null || _e === void 0 ? void 0 : _e.remove(record.source, true);
12304
12872
  }
12305
12873
  catch (e) {
12306
12874
  console.error(e);
@@ -12309,7 +12877,7 @@
12309
12877
  this.menuItemSources.clear();
12310
12878
  // Hand every tileset's show flag back to its owner, holding whatever the owner last asked for.
12311
12879
  for (const rego of Array.from(this.tilesets.values())) {
12312
- (_e = rego.gate) === null || _e === void 0 ? void 0 : _e.Release();
12880
+ (_f = rego.gate) === null || _f === void 0 ? void 0 : _f.Release();
12313
12881
  }
12314
12882
  this.tilesets.clear();
12315
12883
  }
@@ -12483,21 +13051,7 @@
12483
13051
  changed = true;
12484
13052
  }
12485
13053
  if (editing) {
12486
- const parts = exports.EntityUtils.GatherEntity({
12487
- entity: rego.visual
12488
- });
12489
- for (let k = 0; k < parts.length; k++) {
12490
- const part = parts[k];
12491
- if (!(part instanceof Cesium.Entity)) {
12492
- continue;
12493
- }
12494
- const removalPending = exports.VisualRegisterCuller.CancelQueuedSceneChange(part);
12495
- const wasCulled = part[exports.VisualRegisterCuller.VISUAL_CULL_KEY] == true || removalPending;
12496
- delete part[exports.VisualRegisterCuller.VISUAL_CULL_KEY];
12497
- if (wasCulled && !IsCEntityInScene(this.viewer, part)) {
12498
- AddCEntityToScene(this.viewer, part, rego.collection);
12499
- }
12500
- }
13054
+ this.restoreCulled(rego);
12501
13055
  }
12502
13056
  else {
12503
13057
  exports.VisualRegisterCuller.InvalidateBounds(rego.visual);
@@ -12509,6 +13063,56 @@
12509
13063
  (_b = (_a = this.viewer) === null || _a === void 0 ? void 0 : _a.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
12510
13064
  }
12511
13065
  }
13066
+ /**
13067
+ * Marks visuals as placed by the clock, exempting them from culling until it is turned back off.
13068
+ */
13069
+ SetMoving(params) {
13070
+ const { entityIds, moving, menuItemId } = params;
13071
+ if (!(entityIds === null || entityIds === void 0 ? void 0 : entityIds.length)) {
13072
+ return;
13073
+ }
13074
+ for (let i = 0; i < entityIds.length; i++) {
13075
+ const regos = this.rego[entityIds[i]];
13076
+ if (!regos) {
13077
+ continue;
13078
+ }
13079
+ for (let j = 0; j < regos.length; j++) {
13080
+ const rego = regos[j];
13081
+ if ((menuItemId && rego.menuItemId !== menuItemId) || Boolean(rego.moving) == Boolean(moving)) {
13082
+ continue;
13083
+ }
13084
+ // Null rather than false, so the rego does not carry a key for every visual that ever moved.
13085
+ rego.moving = moving ? true : null;
13086
+ if (moving) {
13087
+ this.restoreCulled(rego);
13088
+ }
13089
+ else {
13090
+ exports.VisualRegisterCuller.InvalidateBounds(rego.visual);
13091
+ exports.VisualRegisterCuller.MarkShouldRecheck(this.viewer);
13092
+ }
13093
+ }
13094
+ }
13095
+ }
13096
+ /*
13097
+ * Puts back whatever the culler took out of a visual that is now exempt from it.
13098
+ */
13099
+ restoreCulled(rego) {
13100
+ const parts = exports.EntityUtils.GatherEntity({
13101
+ entity: rego.visual
13102
+ });
13103
+ for (let k = 0; k < parts.length; k++) {
13104
+ const part = parts[k];
13105
+ if (!(part instanceof Cesium.Entity)) {
13106
+ continue;
13107
+ }
13108
+ const removalPending = exports.VisualRegisterCuller.CancelQueuedSceneChange(part);
13109
+ const wasCulled = part[exports.VisualRegisterCuller.VISUAL_CULL_KEY] == true || removalPending;
13110
+ delete part[exports.VisualRegisterCuller.VISUAL_CULL_KEY];
13111
+ if (wasCulled && !IsCEntityInScene(this.viewer, part)) {
13112
+ AddCEntityToScene(this.viewer, part, rego.collection);
13113
+ }
13114
+ }
13115
+ }
12512
13116
  SetSelected(params) {
12513
13117
  var _a;
12514
13118
  let { entityIds, selected, refreshIfSelected, requestRender, menuItemId, source } = params;
@@ -14001,12 +14605,41 @@
14001
14605
  * Tracks per-entity rerender state for unsaved blob maintenance and stale API response suppression.
14002
14606
  */
14003
14607
  class EntityReRenderMaintainState {
14004
- constructor() {
14608
+ constructor(simulation, pointInTime) {
14609
+ this.simulation = simulation;
14610
+ this.pointInTime = pointInTime;
14005
14611
  this.maintainedEntitiesById = {};
14006
14612
  this.apiRevisionByEntityId = {};
14007
14613
  this.skipApiRunByEntityId = {};
14008
14614
  this.checkRunId = 0;
14009
14615
  }
14616
+ /*
14617
+ * Lays each Entity's simulated record for the current point over it, when the manager reads at one.
14618
+ */
14619
+ atPoint(entities) {
14620
+ var _a;
14621
+ const point = (_a = this.pointInTime) === null || _a === void 0 ? void 0 : _a.call(this);
14622
+ if (!point || !this.simulation || this.simulation.IsEmpty || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
14623
+ return entities;
14624
+ }
14625
+ return entities.map((entity) => this.simulation.ApplyAt(entity, point.timeMs, point.attrKey));
14626
+ }
14627
+ /**
14628
+ * Whether a simulated stand-in replaces this Entity, so nothing from the API may replace what is drawn.
14629
+ */
14630
+ isSimulated(entityId) {
14631
+ var _a;
14632
+ return Boolean(entityId && ((_a = this.simulation) === null || _a === void 0 ? void 0 : _a.HasEntity(entityId)));
14633
+ }
14634
+ /**
14635
+ * Swaps any simulated Entity in a set the API returned for its stand-in, or lays its patch over it.
14636
+ */
14637
+ withSimulated(entities) {
14638
+ if (!this.simulation || this.simulation.IsEmpty || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
14639
+ return entities;
14640
+ }
14641
+ 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; }));
14642
+ }
14010
14643
  setMaintainedEntities(entities) {
14011
14644
  var _a;
14012
14645
  for (let i = 0; i < entities.length; i++) {
@@ -14028,15 +14661,21 @@
14028
14661
  }
14029
14662
  }
14030
14663
  getMaintainedEntities(entityIds) {
14664
+ var _a;
14031
14665
  const entities = [];
14032
14666
  for (let i = 0; i < entityIds.length; i++) {
14033
14667
  const id = entityIds[i];
14034
14668
  if (!id) {
14035
14669
  continue;
14036
14670
  }
14037
- const entity = this.maintainedEntitiesById[id];
14038
- if (entity) {
14039
- entities.push(entity);
14671
+ // A maintained blob is this manager's own unsaved edit, newer than a simulation, so it is drawn as is.
14672
+ const maintained = this.maintainedEntitiesById[id];
14673
+ const simulated = maintained ? null : (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.GetEntity(id);
14674
+ if (maintained) {
14675
+ entities.push(maintained);
14676
+ }
14677
+ else if (simulated) {
14678
+ entities.push(this.atPoint([simulated])[0]);
14040
14679
  }
14041
14680
  }
14042
14681
  return entities;
@@ -14093,7 +14732,7 @@
14093
14732
  }
14094
14733
  filterCurrentApiEntities(params) {
14095
14734
  const { entities, revisions, runId } = params;
14096
- return entities.filter((entity) => {
14735
+ const current = entities.filter((entity) => {
14097
14736
  var _a, _b, _c;
14098
14737
  const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
14099
14738
  if (!id) {
@@ -14102,8 +14741,12 @@
14102
14741
  if (runId != null && this.shouldSkipInRun(id, runId)) {
14103
14742
  return false;
14104
14743
  }
14744
+ if (this.isSimulated(id)) {
14745
+ return false;
14746
+ }
14105
14747
  return ((_b = this.apiRevisionByEntityId[id]) !== null && _b !== void 0 ? _b : 0) === ((_c = revisions[id]) !== null && _c !== void 0 ? _c : 0);
14106
14748
  });
14749
+ return this.atPoint(this.simulation && !this.simulation.IsEmpty ? current.map((entity) => this.simulation.Overlay(entity)) : current);
14107
14750
  }
14108
14751
  }
14109
14752
 
@@ -15539,7 +16182,7 @@
15539
16182
  this.cache = new HistoricRangeCache();
15540
16183
  }
15541
16184
  get Records() {
15542
- return this.cache.GetRecords();
16185
+ return this.withSimulated(this.cache.GetRecords());
15543
16186
  }
15544
16187
  Clear() {
15545
16188
  this.cache.Clear();
@@ -15563,8 +16206,9 @@
15563
16206
  */
15564
16207
  async Gather(entities) {
15565
16208
  var _a;
15566
- if (this.disposed || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
15567
- return { fetched: false, recordsByIds: this.cache.GetRecords() };
16209
+ 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);
16210
+ if (this.disposed || !(entities === null || entities === void 0 ? void 0 : entities.length) || this.params.simulatedOnly) {
16211
+ return { fetched: false, recordsByIds: this.withSimulated(this.cache.GetRecords(), entityIds) };
15568
16212
  }
15569
16213
  const { viewer } = this.params;
15570
16214
  const windowStart = new Date(viewer.clock.startTime.toString()).getTime();
@@ -15584,7 +16228,7 @@
15584
16228
  if (plan.coarse && !this.cache.CoversCoarse(plan.coarse.start, plan.coarse.stop)) {
15585
16229
  const records = await this.request(plan.coarse.start, plan.coarse.stop, entities, plan.coarse.sample);
15586
16230
  if (this.disposed) {
15587
- return { fetched, recordsByIds: this.cache.GetRecords() };
16231
+ return { fetched, recordsByIds: this.withSimulated(this.cache.GetRecords(), entityIds) };
15588
16232
  }
15589
16233
  this.cache.Absorb(plan.coarse.start, this.holdTo(plan.coarse.stop, liveEdge, liveTail), records, true);
15590
16234
  fetched = true;
@@ -15619,7 +16263,17 @@
15619
16263
  }
15620
16264
  }
15621
16265
  }
15622
- return { fetched, recordsByIds: this.cache.GetRecords() };
16266
+ return { fetched, recordsByIds: this.withSimulated(this.cache.GetRecords(), entityIds) };
16267
+ }
16268
+ /*
16269
+ * Joins the simulated records for these Entities, or every simulated Entity, into an answer.
16270
+ */
16271
+ withSimulated(recordsByIds, entityIds) {
16272
+ const simulation = this.params.simulation;
16273
+ if (!simulation || simulation.IsEmpty) {
16274
+ return recordsByIds;
16275
+ }
16276
+ return simulation.MergeRecords(recordsByIds, this.params.getAttrKey(), entityIds);
15623
16277
  }
15624
16278
  /*
15625
16279
  * Returns how much of a range that was just read may be marked as held.
@@ -15654,7 +16308,7 @@
15654
16308
  }).then((res) => {
15655
16309
  const records = res.recordsByIds || {};
15656
16310
  if (!this.disposed && this.params.onRecords) {
15657
- this.params.onRecords(records);
16311
+ this.params.onRecords(this.withSimulated(records, entityIds));
15658
16312
  }
15659
16313
  return records;
15660
16314
  }).finally(() => {
@@ -15726,7 +16380,6 @@
15726
16380
  this.renderQueue = [];
15727
16381
  this.renderQueueInterval = null;
15728
16382
  this.sources = [];
15729
- this.reRenderState = new EntityReRenderMaintainState();
15730
16383
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
15731
16384
  // Many things are not supported when this is enabled.
15732
16385
  this.useGeojson = false;
@@ -15746,6 +16399,14 @@
15746
16399
  */
15747
16400
  this.renderAsIndividualsChain = Promise.resolve();
15748
16401
  const { viewer, apiGetter, monitor, item, register: visualsManager, sharedGetters, onSeriesDiscovered } = params;
16402
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation, () => {
16403
+ // Read at the scene's time only when the item follows history, the same choice its reads make.
16404
+ const bruceEntity = this.item.BruceEntity;
16405
+ if (!(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historic) && !(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historicAttrKey)) {
16406
+ return null;
16407
+ }
16408
+ return { timeMs: Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime(), attrKey: bruceEntity.historicAttrKey };
16409
+ });
15749
16410
  this.viewer = viewer;
15750
16411
  this.sharedGetters = sharedGetters;
15751
16412
  this.monitor = monitor;
@@ -15759,7 +16420,8 @@
15759
16420
  getApi: () => this.apiGetter.getApi(),
15760
16421
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
15761
16422
  // A second either side, to take in records sitting exactly on the boundary.
15762
- paddingMs: 1000
16423
+ paddingMs: 1000,
16424
+ simulation: params.simulation
15763
16425
  });
15764
16426
  this.useGeojson = item.renderAsGeojson == true;
15765
16427
  if (item.enableClustering) {
@@ -15940,8 +16602,9 @@
15940
16602
  return;
15941
16603
  }
15942
16604
  this.updateAllowedTypeIDs(data.entityTypeSources, data.entities);
16605
+ // The getter answers from the API, so a simulated Entity in its answer is swapped for its blob.
15943
16606
  if (isTagItem) {
15944
- this.distributeForRender(data.entities.filter((entity) => {
16607
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities.filter((entity) => {
15945
16608
  let entityTags = entity.Bruce["Layer.ID"];
15946
16609
  if (!entityTags) {
15947
16610
  entityTags = [];
@@ -15949,10 +16612,10 @@
15949
16612
  return entityTags.some((tag) => {
15950
16613
  return tagsToRender.indexOf(tag) !== -1;
15951
16614
  });
15952
- }));
16615
+ })));
15953
16616
  }
15954
16617
  else {
15955
- this.distributeForRender(data.entities);
16618
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities));
15956
16619
  }
15957
16620
  });
15958
16621
  // Cesium itself handles single-visibility ranges very well.
@@ -15992,6 +16655,10 @@
15992
16655
  // Remove regos directly.
15993
16656
  if (event.action === "D") {
15994
16657
  for (const entityId of affectedIds) {
16658
+ // Deleted in the API, but drawn from its simulation until that is cleared.
16659
+ if (this.reRenderState.isSimulated(entityId)) {
16660
+ continue;
16661
+ }
15995
16662
  this.visualsManager.RemoveRegos({
15996
16663
  entityId,
15997
16664
  menuItemId: this.item.id,
@@ -17115,7 +17782,6 @@
17115
17782
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
17116
17783
  // Many things are not supported when this is enabled.
17117
17784
  this.useGeojson = false;
17118
- this.reRenderState = new EntityReRenderMaintainState();
17119
17785
  /**
17120
17786
  * Our default path.
17121
17787
  * We render each entity individually.
@@ -17125,6 +17791,7 @@
17125
17791
  */
17126
17792
  this.renderAsIndividualsChain = Promise.resolve();
17127
17793
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
17794
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
17128
17795
  this.viewer = viewer;
17129
17796
  this.apiGetter = apiGetter;
17130
17797
  this.monitor = monitor;
@@ -17247,7 +17914,8 @@
17247
17914
  continue;
17248
17915
  }
17249
17916
  }
17250
- this.onGetterUpdate(entities);
17917
+ // The item carries its own Entities, so a simulated one among them is swapped for its blob.
17918
+ this.onGetterUpdate(this.reRenderState.withSimulated(entities));
17251
17919
  });
17252
17920
  this.getter.Start();
17253
17921
  }
@@ -17889,8 +18557,15 @@
17889
18557
  this.renderQueueActive = false;
17890
18558
  this.renderQueue = [];
17891
18559
  this.renderQueueForceFlags = [];
17892
- this.reRenderState = new EntityReRenderMaintainState();
17893
18560
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
18561
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation, () => {
18562
+ // Read at the scene's time only when the item follows history, the same choice its reads make.
18563
+ const bruceEntity = this.item.BruceEntity;
18564
+ if (!(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historic) && !(bruceEntity === null || bruceEntity === void 0 ? void 0 : bruceEntity.historicAttrKey)) {
18565
+ return null;
18566
+ }
18567
+ return { timeMs: Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime(), attrKey: bruceEntity.historicAttrKey };
18568
+ });
17894
18569
  this.sharedHistoric = params.sharedHistoric;
17895
18570
  this.viewer = viewer;
17896
18571
  this.apiGetter = apiGetter;
@@ -17903,7 +18578,8 @@
17903
18578
  getApi: () => this.apiGetter.getApi(),
17904
18579
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
17905
18580
  // Padding either side of a requested range, for clock desync between us and the server.
17906
- paddingMs: 15000
18581
+ paddingMs: 15000,
18582
+ simulation: params.simulation
17907
18583
  });
17908
18584
  if (this.item.enableClustering) {
17909
18585
  this.clustering = new PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
@@ -18361,9 +19037,9 @@
18361
19037
  this.getter = null;
18362
19038
  this.getterSub = null;
18363
19039
  this.disposed = false;
18364
- this.reRenderState = new EntityReRenderMaintainState();
18365
19040
  this.renderEntityChain = Promise.resolve();
18366
19041
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
19042
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
18367
19043
  this.viewer = viewer;
18368
19044
  this.apiGetter = apiGetter;
18369
19045
  this.monitor = monitor;
@@ -18637,6 +19313,122 @@
18637
19313
  ];
18638
19314
  })(CodeLogicExceptions || (CodeLogicExceptions = {}));
18639
19315
 
19316
+ /**
19317
+ * The line drawn through where a moving thing has been, from its history, as a whole path or a fading trail.
19318
+ */
19319
+ var HistoricTrail;
19320
+ (function (HistoricTrail) {
19321
+ const BANDS = 12;
19322
+ const SAMPLES = 96;
19323
+ const WIDTH_FAR = 1;
19324
+ const ALPHA_FAR = 0.05;
19325
+ // Above 1, so width and alpha hold near the head and give way further back.
19326
+ const WIDTH_CURVE = 1.4;
19327
+ const ALPHA_CURVE = 1.25;
19328
+ // How strongly a line is drawn where it passes behind the globe, against its own alpha.
19329
+ const BEHIND_ALPHA = 0.35;
19330
+ /**
19331
+ * The trail length a menu item asks for in its historicTrackSeconds, or null for the whole path.
19332
+ */
19333
+ function GetTrailSeconds(item) {
19334
+ const seconds = Number(item === null || item === void 0 ? void 0 : item.historicTrackSeconds);
19335
+ return seconds > 0 ? seconds : null;
19336
+ }
19337
+ HistoricTrail.GetTrailSeconds = GetTrailSeconds;
19338
+ class Trail {
19339
+ constructor(params) {
19340
+ this.entities = [];
19341
+ this.memoTime = null;
19342
+ this.memo = [];
19343
+ this.viewer = params.viewer;
19344
+ const trailing = params.trailSeconds > 0 && Boolean(params.positionAt);
19345
+ if (!trailing) {
19346
+ this.add(params, params.color, params.width, () => { var _a; return (_a = params.getSeries()) !== null && _a !== void 0 ? _a : []; });
19347
+ return;
19348
+ }
19349
+ for (let band = 0; band < BANDS; band++) {
19350
+ // 0 at the head, 1 at the far end.
19351
+ const age = band / (BANDS - 1);
19352
+ const width = params.width - (params.width - WIDTH_FAR) * Math.pow(age, WIDTH_CURVE);
19353
+ const alpha = params.color.alpha - (params.color.alpha - ALPHA_FAR) * Math.pow(age, ALPHA_CURVE);
19354
+ this.add(params, params.color.withAlpha(alpha), Math.max(WIDTH_FAR, width), (time) => {
19355
+ return this.slice(this.sample(params, time), band);
19356
+ });
19357
+ }
19358
+ }
19359
+ /*
19360
+ * One polyline of the trail, its positions read from the scene's time.
19361
+ */
19362
+ add(params, color, width, positions) {
19363
+ const entity = this.viewer.entities.add({
19364
+ id: BModels.ObjectUtils.UId(15),
19365
+ polyline: {
19366
+ positions: new Cesium.CallbackProperty((time) => positions(time), false),
19367
+ width: width,
19368
+ material: color,
19369
+ clampToGround: params.clampToGround,
19370
+ // A path through the air follows no ellipsoid, so it is drawn as the straight runs it holds.
19371
+ arcType: params.clampToGround ? Cesium.ArcType.GEODESIC : Cesium.ArcType.NONE,
19372
+ // Drawn dimmer behind the globe rather than winking in and out against the terrain along the limb.
19373
+ depthFailMaterial: params.clampToGround ? undefined : color.withAlpha(color.alpha * BEHIND_ALPHA),
19374
+ zIndex: 0,
19375
+ classificationType: params.clampToGround ? Cesium.ClassificationType.BOTH : undefined
19376
+ }
19377
+ });
19378
+ entity._noEdit = true;
19379
+ entity._noSelect = true;
19380
+ exports.CesiumEntityStyler.SetDefaultColor({
19381
+ color: color,
19382
+ entity: entity,
19383
+ viewer: this.viewer,
19384
+ override: true,
19385
+ requestRender: false
19386
+ });
19387
+ this.entities.push(entity);
19388
+ }
19389
+ /*
19390
+ * The trail's positions at a time, newest last, kept for that time so every band reads the same set.
19391
+ */
19392
+ sample(params, time) {
19393
+ var _a;
19394
+ if (this.memoTime && Cesium.JulianDate.equals(this.memoTime, time)) {
19395
+ return this.memo;
19396
+ }
19397
+ this.memoTime = Cesium.JulianDate.clone(time, (_a = this.memoTime) !== null && _a !== void 0 ? _a : new Cesium.JulianDate());
19398
+ const nowMs = Cesium.JulianDate.toDate(time).getTime();
19399
+ const spanMs = params.trailSeconds * 1000;
19400
+ const points = [];
19401
+ for (let i = 0; i <= SAMPLES; i++) {
19402
+ const pos3d = params.positionAt(nowMs - spanMs + spanMs * i / SAMPLES);
19403
+ if (pos3d) {
19404
+ points.push(pos3d);
19405
+ }
19406
+ }
19407
+ this.memo = points;
19408
+ return points;
19409
+ }
19410
+ /*
19411
+ * A band's run of the samples, counted back from the head, sharing its end point with the next band.
19412
+ */
19413
+ slice(points, band) {
19414
+ if (points.length < 2) {
19415
+ return [];
19416
+ }
19417
+ const per = (points.length - 1) / BANDS;
19418
+ const end = points.length - 1 - Math.round(band * per);
19419
+ const start = points.length - 1 - Math.round((band + 1) * per);
19420
+ return start < end ? points.slice(Math.max(0, start), end + 1) : [];
19421
+ }
19422
+ Dispose() {
19423
+ for (const entity of this.entities) {
19424
+ RemoveCEntityFromScene(this.viewer, entity);
19425
+ }
19426
+ this.entities = [];
19427
+ }
19428
+ }
19429
+ HistoricTrail.Trail = Trail;
19430
+ })(HistoricTrail || (HistoricTrail = {}));
19431
+
18640
19432
  class EntityGatherer {
18641
19433
  get OnQueueProgress() {
18642
19434
  if (!this._onQueueProgress) {
@@ -18644,7 +19436,11 @@
18644
19436
  }
18645
19437
  return this._onQueueProgress;
18646
19438
  }
18647
- constructor(api, viewer, emitEntities) {
19439
+ /**
19440
+ * @param simulation what callers simulate in place of API data, used where this gatherer has no override.
19441
+ */
19442
+ constructor(api, viewer, emitEntities, simulation) {
19443
+ this.simulation = simulation;
18648
19444
  this.disposed = false;
18649
19445
  this.expandSources = false;
18650
19446
  this.historic = null;
@@ -19221,15 +20017,19 @@
19221
20017
  }
19222
20018
  }
19223
20019
  }
20020
+ // A historic read is as at the scene's time, and so is any simulated record laid over it.
20021
+ const rTimeMs = new Date(rTime).getTime();
20022
+ const atPoint = (entity) => this.historic && this.simulation ? this.simulation.ApplyAt(entity, rTimeMs) : entity;
19224
20023
  for (const entityId of requestedBatch || []) {
20024
+ // A caller's own override is an unsaved edit, newer than any simulation, so it is used as is.
19225
20025
  const override = this.entityDataOverrides.get(entityId);
19226
20026
  if (override) {
19227
20027
  resolvedEntities.push(override);
19228
20028
  continue;
19229
20029
  }
19230
- const found = foundById.get(entityId);
20030
+ const found = this.simulation ? this.simulation.Resolve(entityId, foundById.get(entityId)) : foundById.get(entityId);
19231
20031
  if (found) {
19232
- resolvedEntities.push(found);
20032
+ resolvedEntities.push(atPoint(found));
19233
20033
  continue;
19234
20034
  }
19235
20035
  resolvedEntities.push(this.createFallbackEntity(entityId));
@@ -19593,7 +20393,7 @@
19593
20393
  this.styleTilesetFeatureFullData(feature, entity, eTags);
19594
20394
  }
19595
20395
  }
19596
- });
20396
+ }, params.simulation);
19597
20397
  // Only Entities Sets bake coordinates into their features, so for any other source this
19598
20398
  // resolves nothing and the gather stays in insertion order.
19599
20399
  this.entityGatherer.SetViewMonitor(this.monitor);
@@ -21117,6 +21917,9 @@
21117
21917
  return this.modelTreeUpdate;
21118
21918
  }
21119
21919
  constructor(params) {
21920
+ this.simulationRemoval = null;
21921
+ // Where the tileset stands by its own record, so a cleared simulation puts it back without a refetch.
21922
+ this.restCoords = null;
21120
21923
  this.disposed = false;
21121
21924
  this.modelSpace = false;
21122
21925
  this.cTileset = null;
@@ -21154,7 +21957,7 @@
21154
21957
  this.historicAnimation = null;
21155
21958
  this.historicGatherer = null;
21156
21959
  // The trail through the root's own movement, which describes the whole assembly.
21157
- this.trackEntity = null;
21960
+ this.trail = null;
21158
21961
  this.historicPossesLoadingProm = null;
21159
21962
  // Queue of loaded in features that we haven't processed yet.
21160
21963
  this.featureQueue = [];
@@ -21168,6 +21971,7 @@
21168
21971
  this.featurePropCache = new Map();
21169
21972
  this.featurePropsChecked = 0;
21170
21973
  const { viewer, register: visualsManager, getters, item, modelSpace, initQueue } = params;
21974
+ this.simulation = params.simulation;
21171
21975
  this.viewer = viewer;
21172
21976
  this.getters = getters;
21173
21977
  this.item = item;
@@ -21332,7 +22136,7 @@
21332
22136
  historicPoint: this.viewer.clock.currentTime.toString(),
21333
22137
  api: api
21334
22138
  });
21335
- coords = coordsFromHistoricEntity(root);
22139
+ coords = coordsFromHistoricEntity(this.atClock(root));
21336
22140
  }
21337
22141
  catch (e) {
21338
22142
  console.error(e);
@@ -21373,6 +22177,10 @@
21373
22177
  this.doDispose();
21374
22178
  return;
21375
22179
  }
22180
+ this.restCoords = coords;
22181
+ if (this.simulatedRootCoords()) {
22182
+ this.applyCoords(tileset, this.simulatedRootCoords());
22183
+ }
21376
22184
  this.viewer.scene.requestRender();
21377
22185
  exports.TilesetRenderEngine.OnTilesetReady(this.cTileset).then(() => {
21378
22186
  var _a, _b, _c;
@@ -21392,6 +22200,7 @@
21392
22200
  }
21393
22201
  this.onCTilesetLoad();
21394
22202
  this.styler.Init({
22203
+ simulation: this.simulation,
21395
22204
  viewer: this.viewer,
21396
22205
  // Default API instead of the account one.
21397
22206
  // Records are driven by the account loading it instead of the source account.
@@ -21407,6 +22216,7 @@
21407
22216
  });
21408
22217
  this.viewer.scene.requestRender();
21409
22218
  // Monitor scene time changes and update the root Entity.
22219
+ this.followSimulation(tileset);
21410
22220
  this.viewerDateTimeSub(tileset);
21411
22221
  }
21412
22222
  catch (e) {
@@ -21462,7 +22272,7 @@
21462
22272
  */
21463
22273
  applyRootTrack() {
21464
22274
  var _a;
21465
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
22275
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
21466
22276
  return;
21467
22277
  }
21468
22278
  const trackStyle = exports.EntityRenderEngine.GetTrackStyle({
@@ -21472,26 +22282,14 @@
21472
22282
  if (!trackStyle) {
21473
22283
  return;
21474
22284
  }
21475
- this.trackEntity = this.viewer.entities.add({
21476
- id: BModels.ObjectUtils.UId(15),
21477
- polyline: {
21478
- positions: new Cesium.CallbackProperty(() => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; }, false),
21479
- width: trackStyle.width,
21480
- material: trackStyle.color,
21481
- clampToGround: exports.EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
21482
- == Cesium.HeightReference.CLAMP_TO_GROUND,
21483
- zIndex: 0,
21484
- classificationType: Cesium.ClassificationType.BOTH
21485
- }
21486
- });
21487
- this.trackEntity._noEdit = true;
21488
- this.trackEntity._noSelect = true;
21489
- exports.CesiumEntityStyler.SetDefaultColor({
21490
- color: trackStyle.color,
21491
- entity: this.trackEntity,
22285
+ // The series has no position at an arbitrary time, so this draws the whole path rather than a trail.
22286
+ this.trail = new HistoricTrail.Trail({
21492
22287
  viewer: this.viewer,
21493
- override: true,
21494
- requestRender: false
22288
+ color: trackStyle.color,
22289
+ width: trackStyle.width,
22290
+ clampToGround: exports.EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
22291
+ == Cesium.HeightReference.CLAMP_TO_GROUND,
22292
+ getSeries: () => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; }
21495
22293
  });
21496
22294
  }
21497
22295
  /**
@@ -21544,7 +22342,7 @@
21544
22342
  }
21545
22343
  // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
21546
22344
  PrepareHandoff() {
21547
- var _a, _b, _c, _d;
22345
+ var _a, _b, _c, _d, _e;
21548
22346
  if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
21549
22347
  return null;
21550
22348
  }
@@ -21562,9 +22360,11 @@
21562
22360
  this.handoffSourceMenuItemId = null;
21563
22361
  // Not transferred, the receiving manager sets up its own fresh copy if needed.
21564
22362
  this.viewerDateTimeDispose();
21565
- (_b = this.styler) === null || _b === void 0 ? void 0 : _b.Dispose();
21566
- (_c = this.tileLoadRemoval) === null || _c === void 0 ? void 0 : _c.call(this);
21567
- (_d = this.tileUnloadRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
22363
+ (_b = this.simulationRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
22364
+ this.simulationRemoval = null;
22365
+ (_c = this.styler) === null || _c === void 0 ? void 0 : _c.Dispose();
22366
+ (_d = this.tileLoadRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
22367
+ (_e = this.tileUnloadRemoval) === null || _e === void 0 ? void 0 : _e.call(this);
21568
22368
  this.tileLoadRemoval = null;
21569
22369
  this.tileUnloadRemoval = null;
21570
22370
  const payload = {
@@ -21585,7 +22385,7 @@
21585
22385
  // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
21586
22386
  // Then re-parents every already-registered rego to this menu item and re-runs styling.
21587
22387
  AdoptHandoff(payload) {
21588
- var _a, _b, _c, _d;
22388
+ var _a, _b, _c, _d, _e;
21589
22389
  this.cTileset = payload.cTileset;
21590
22390
  this.tileset = payload.tileset;
21591
22391
  this.rootId = payload.rootId;
@@ -21616,6 +22416,7 @@
21616
22416
  // Same visuals, same registrations so just re-keyed onto this menu item.
21617
22417
  this.onCTilesetLoad();
21618
22418
  this.styler.Init({
22419
+ simulation: this.simulation,
21619
22420
  viewer: this.viewer,
21620
22421
  api: this.getters.GetBruceApi(),
21621
22422
  cTileset: this.cTileset,
@@ -21649,7 +22450,9 @@
21649
22450
  }
21650
22451
  });
21651
22452
  this.viewer.scene.requestRender();
21652
- if (((_d = this.item.BruceEntity) === null || _d === void 0 ? void 0 : _d.historic) && this.tileset) {
22453
+ if (this.tileset) {
22454
+ this.restCoords = (_d = this.restCoords) !== null && _d !== void 0 ? _d : (_e = this.cTileset) === null || _e === void 0 ? void 0 : _e._bruceCoords;
22455
+ this.followSimulation(this.tileset);
21653
22456
  this.viewerDateTimeSub(this.tileset);
21654
22457
  }
21655
22458
  }
@@ -22237,7 +23040,7 @@
22237
23040
  this.doDispose();
22238
23041
  }
22239
23042
  doDispose() {
22240
- var _a, _b, _c;
23043
+ var _a, _b, _c, _d;
22241
23044
  if (this.featureQueueInterval) {
22242
23045
  clearInterval(this.featureQueueInterval);
22243
23046
  this.featureQueueInterval = null;
@@ -22272,6 +23075,8 @@
22272
23075
  this.handoffSourceMenuItemId = null;
22273
23076
  }
22274
23077
  this.viewerDateTimeDispose();
23078
+ (_d = this.simulationRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
23079
+ this.simulationRemoval = null;
22275
23080
  }
22276
23081
  async ReRender(params) {
22277
23082
  let { entityIds, force, entities, maintain } = params;
@@ -22297,8 +23102,11 @@
22297
23102
  * @param tileset
22298
23103
  */
22299
23104
  viewerDateTimeSub(tileset) {
22300
- var _a;
22301
- if (!((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic) || this.viewerDateTimeChangeRemoval) {
23105
+ var _a, _b;
23106
+ // An item that is not historic still follows the root's simulated records, once there are some.
23107
+ const historic = Boolean((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic);
23108
+ const simulated = Boolean(this.rootId && ((_b = this.simulation) === null || _b === void 0 ? void 0 : _b.GetRecords(this.rootId).length));
23109
+ if ((!historic && !simulated) || this.historicGatherer || this.viewerDateTimeChangeRemoval) {
22302
23110
  return;
22303
23111
  }
22304
23112
  const accountId = this.accountIdForRecords();
@@ -22327,6 +23135,8 @@
22327
23135
  // Time padding in milliseconds (15 seconds).
22328
23136
  // Helps account for desync between client and server.
22329
23137
  paddingMs: 15000,
23138
+ simulation: this.simulation,
23139
+ simulatedOnly: !historic,
22330
23140
  onRecords: (recordsByIds) => {
22331
23141
  var _a;
22332
23142
  const records = recordsByIds[this.rootId];
@@ -22354,6 +23164,7 @@
22354
23164
  */
22355
23165
  const getInitialPosses = async () => {
22356
23166
  this.historicPossesLoadingProm = new Promise(async (res) => {
23167
+ var _a;
22357
23168
  try {
22358
23169
  if (this.disposed) {
22359
23170
  res(false);
@@ -22366,11 +23177,16 @@
22366
23177
  return;
22367
23178
  }
22368
23179
  // Also request the current position in case its not in the range.
22369
- const { entity } = await BModels.Entity.Get({
23180
+ // A simulated root, or an item that is not historic, has no current record to ask the API for.
23181
+ if (!historic || ((_a = this.simulation) === null || _a === void 0 ? void 0 : _a.HasEntity(this.rootId))) {
23182
+ res(positions);
23183
+ return;
23184
+ }
23185
+ const entity = this.atClock((await BModels.Entity.Get({
22370
23186
  entityId: this.rootId,
22371
23187
  historicPoint: this.viewer.clock.currentTime.toString(),
22372
23188
  api: api
22373
- });
23189
+ })).entity);
22374
23190
  if (this.disposed || !entity) {
22375
23191
  res(false);
22376
23192
  return;
@@ -22450,14 +23266,15 @@
22450
23266
  }
22451
23267
  });
22452
23268
  this.viewerDateTimeChangeRemoval = () => {
23269
+ var _a, _b;
22453
23270
  if (this.historicAnimation) {
22454
23271
  this.historicAnimation.stop();
22455
23272
  this.historicAnimation = null;
22456
23273
  }
22457
- if (this.trackEntity) {
22458
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
22459
- this.trackEntity = null;
22460
- }
23274
+ (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
23275
+ this.historicGatherer = null;
23276
+ (_b = this.trail) === null || _b === void 0 ? void 0 : _b.Dispose();
23277
+ this.trail = null;
22461
23278
  if (clockTickRemoval) {
22462
23279
  clockTickRemoval();
22463
23280
  clockTickRemoval = null;
@@ -22465,10 +23282,84 @@
22465
23282
  };
22466
23283
  });
22467
23284
  }
22468
- viewerDateTimeDispose() {
23285
+ /*
23286
+ * Keeps the tileset in line with what is simulated for its root Entity.
23287
+ */
23288
+ followSimulation(tileset) {
23289
+ if (this.simulationRemoval || !this.simulation) {
23290
+ return;
23291
+ }
23292
+ this.simulationRemoval = this.simulation.OnChanged.Subscribe((change) => {
23293
+ var _a;
23294
+ if (this.disposed || !this.rootId || !change.entityIds.includes(this.rootId)) {
23295
+ return;
23296
+ }
23297
+ if (change.records) {
23298
+ this.regatherRoot(tileset);
23299
+ }
23300
+ // A simulated root is where the tileset stands outside its history; recorded positions still win.
23301
+ if (change.entities && !((_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.getDateRange().minDate)) {
23302
+ this.applyRootRest(tileset);
23303
+ }
23304
+ });
23305
+ }
23306
+ /*
23307
+ * Re-reads the root's history after its simulated records changed, following the clock if it was not.
23308
+ */
23309
+ async regatherRoot(tileset) {
22469
23310
  var _a;
23311
+ if (!this.historicGatherer) {
23312
+ this.viewerDateTimeSub(tileset);
23313
+ return;
23314
+ }
23315
+ await this.historicPossesLoadingProm;
23316
+ const { recordsByIds } = await this.historicGatherer.Gather([{ Bruce: { ID: this.rootId } }]);
23317
+ if (this.disposed) {
23318
+ return;
23319
+ }
23320
+ const posses = Manager.HistoricPossesFor({ viewer: this.viewer, item: this.item, records: recordsByIds[this.rootId] || [] });
23321
+ (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.replacePositions(posses);
23322
+ if (!posses.length) {
23323
+ this.applyRootRest(tileset);
23324
+ }
23325
+ }
23326
+ /*
23327
+ * The root as read at the scene's time, with any simulated record at that time laid over it.
23328
+ */
23329
+ atClock(root) {
23330
+ return this.simulation && root
23331
+ ? this.simulation.ApplyAt(root, Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime())
23332
+ : root;
23333
+ }
23334
+ /*
23335
+ * Stands the tileset where its root is outside history: the simulated root, else the root's own record.
23336
+ */
23337
+ applyRootRest(tileset) {
23338
+ var _a;
23339
+ this.applyCoords(tileset, (_a = this.simulatedRootCoords()) !== null && _a !== void 0 ? _a : this.restCoords);
23340
+ }
23341
+ /*
23342
+ * Where a simulated root puts the tileset, or null; a patch only places it when it names a whole location.
23343
+ */
23344
+ simulatedRootCoords() {
23345
+ var _a, _b;
23346
+ const simulation = this.rootId ? this.simulation : null;
23347
+ 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);
23348
+ const location = (_b = root === null || root === void 0 ? void 0 : root.Bruce) === null || _b === void 0 ? void 0 : _b.Location;
23349
+ if ((location === null || location === void 0 ? void 0 : location.latitude) == null || (location === null || location === void 0 ? void 0 : location.longitude) == null) {
23350
+ return null;
23351
+ }
23352
+ const pos3d = Cesium.Cartesian3.fromDegrees(+location.longitude, +location.latitude, EnsureNumber(location.altitude, 0));
23353
+ const transform = root.Bruce.Transform;
23354
+ 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);
23355
+ }
23356
+ viewerDateTimeDispose() {
23357
+ var _a, _b;
22470
23358
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
22471
23359
  this.viewerDateTimeChangeRemoval = null;
23360
+ // Made before the first positions land, so it can outlive a removal that was never set.
23361
+ (_b = this.historicGatherer) === null || _b === void 0 ? void 0 : _b.Dispose();
23362
+ this.historicGatherer = null;
22472
23363
  }
22473
23364
  /**
22474
23365
  * Updates the Tileset with latest coordinates.
@@ -23609,11 +24500,11 @@
23609
24500
  this.viewMonitorRemoval = null;
23610
24501
  this.renderQueue = [];
23611
24502
  this.renderQueueInterval = null;
23612
- this.reRenderState = new EntityReRenderMaintainState();
23613
24503
  this.zoomControl = [];
23614
24504
  this.queryLoadId = 0;
23615
24505
  this.renderDataLabEntitiesChain = Promise.resolve();
23616
24506
  const { viewer, item, apiGetter, monitor, visualsRegister } = params;
24507
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
23617
24508
  this.viewer = viewer;
23618
24509
  this.item = item;
23619
24510
  this.apiGetter = apiGetter;
@@ -23710,7 +24601,8 @@
23710
24601
  if (!((_a = data === null || data === void 0 ? void 0 : data.entities) === null || _a === void 0 ? void 0 : _a.length)) {
23711
24602
  return;
23712
24603
  }
23713
- this.distributeForRender(data.entities);
24604
+ // The getter answers from the API, so a simulated Entity in its answer is swapped for its blob.
24605
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities));
23714
24606
  });
23715
24607
  }
23716
24608
  unsetGetter() {
@@ -25457,6 +26349,7 @@
25457
26349
  this.featurePropCache = new Map();
25458
26350
  this.featurePropsChecked = 0;
25459
26351
  const { viewer, register: visualsManager, getters: apiGetter, item, initQueue, monitor } = params;
26352
+ this.simulation = params.simulation;
25460
26353
  this.monitor = monitor;
25461
26354
  this.viewer = viewer;
25462
26355
  this.getters = apiGetter;
@@ -25557,6 +26450,7 @@
25557
26450
  if (Manager.NeedsStyler(this.item)) {
25558
26451
  const api = this.getters.GetBruceApi();
25559
26452
  this.styler.Init({
26453
+ simulation: this.simulation,
25560
26454
  viewer: this.viewer,
25561
26455
  api: api,
25562
26456
  cTileset: this.cTileset,
@@ -25914,6 +26808,7 @@
25914
26808
  const needsStyler = Manager.NeedsStyler(this.item);
25915
26809
  if (needsStyler) {
25916
26810
  this.styler.Init({
26811
+ simulation: this.simulation,
25917
26812
  viewer: this.viewer,
25918
26813
  api: this.getters.GetBruceApi(),
25919
26814
  cTileset: this.cTileset,
@@ -26598,6 +27493,7 @@
26598
27493
  // LCC object if we're using XGrids to render LCC data.
26599
27494
  this.lccObject = null;
26600
27495
  this.viewer = params.viewer;
27496
+ this.simulation = params.simulation;
26601
27497
  this.getters = params.getters;
26602
27498
  this.visualsManager = params.register;
26603
27499
  // We modify the object due to a lot of backwards compatibility.
@@ -26832,6 +27728,7 @@
26832
27728
  this.item.Type == BModels.MenuItem.EType.IonTileset) {
26833
27729
  const api = this.getters.GetBruceApi();
26834
27730
  this.styler.Init({
27731
+ simulation: this.simulation,
26835
27732
  viewer: this.viewer,
26836
27733
  api: api,
26837
27734
  cTileset: this.cTileset,
@@ -27172,14 +28069,32 @@
27172
28069
  }
27173
28070
  ;
27174
28071
  constructor(params) {
28072
+ var _a, _b;
27175
28073
  // If true, this menu item is disposed and should not be used.
27176
28074
  // Any rendering will be ignored.
27177
28075
  this.disposed = false;
28076
+ this.simulationRemoval = null;
28077
+ this.FollowsSimulation = true;
27178
28078
  const { apiGetter, item, register, viewer } = params;
27179
28079
  this.apiGetter = apiGetter;
27180
28080
  this.item = item;
27181
28081
  this.register = register;
27182
28082
  this.viewer = viewer;
28083
+ this.simulation = params.simulation;
28084
+ // Ends are not this item's own Entities, so it watches the store itself rather than wait to be asked.
28085
+ this.simulationRemoval = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
28086
+ if (this.disposed) {
28087
+ return;
28088
+ }
28089
+ const relations = RelationRenderEngine.TakeRelationsTouching({
28090
+ visualRegister: this.register,
28091
+ menuItemId: this.item.id,
28092
+ entityIds: change.entityIds
28093
+ });
28094
+ if (relations.length) {
28095
+ this.onGetterUpdate(relations);
28096
+ }
28097
+ })) !== null && _b !== void 0 ? _b : null;
27183
28098
  }
27184
28099
  /**
27185
28100
  * Starts rendering the menu item.
@@ -27248,10 +28163,13 @@
27248
28163
  return items;
27249
28164
  }
27250
28165
  Dispose() {
28166
+ var _a;
27251
28167
  if (this.disposed) {
27252
28168
  return;
27253
28169
  }
27254
28170
  this.disposed = true;
28171
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28172
+ this.simulationRemoval = null;
27255
28173
  this.register.RemoveRegos({
27256
28174
  menuItemId: this.item.id
27257
28175
  });
@@ -27490,7 +28408,8 @@
27490
28408
  menuItemId: this.item.id,
27491
28409
  relations: relations,
27492
28410
  viewer: this.viewer,
27493
- visualRegister: this.register
28411
+ visualRegister: this.register,
28412
+ simulation: this.simulation
27494
28413
  });
27495
28414
  if (this.disposed) {
27496
28415
  this.register.RemoveRegos({
@@ -27772,9 +28691,12 @@
27772
28691
  }
27773
28692
  ;
27774
28693
  constructor(params) {
28694
+ var _a, _b;
27775
28695
  // If true, this menu item is disposed and should not be used.
27776
28696
  // Any rendering will be ignored.
27777
28697
  this.disposed = false;
28698
+ this.simulationRemoval = null;
28699
+ this.FollowsSimulation = true;
27778
28700
  // Record of the currently drawn entity IDs.
27779
28701
  // This is to avoid re-queueing already drawn entities when Init is called multiple times.
27780
28702
  this.drawnIds = [];
@@ -27783,6 +28705,21 @@
27783
28705
  this.item = item;
27784
28706
  this.register = register;
27785
28707
  this.viewer = viewer;
28708
+ this.simulation = params.simulation;
28709
+ // Ends are not this item's own Entities, so it watches the store itself rather than wait to be asked.
28710
+ this.simulationRemoval = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
28711
+ if (this.disposed) {
28712
+ return;
28713
+ }
28714
+ const relations = RelationRenderEngine.TakeRelationsTouching({
28715
+ visualRegister: this.register,
28716
+ menuItemId: this.item.id,
28717
+ entityIds: change.entityIds
28718
+ });
28719
+ if (relations.length) {
28720
+ this.onGetterUpdate(relations);
28721
+ }
28722
+ })) !== null && _b !== void 0 ? _b : null;
27786
28723
  }
27787
28724
  /**
27788
28725
  * Starts rendering the menu item.
@@ -27825,10 +28762,13 @@
27825
28762
  this.drawnIds = [].concat(entityIds);
27826
28763
  }
27827
28764
  Dispose() {
28765
+ var _a;
27828
28766
  if (this.disposed) {
27829
28767
  return;
27830
28768
  }
27831
28769
  this.disposed = true;
28770
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28771
+ this.simulationRemoval = null;
27832
28772
  this.register.RemoveRegos({
27833
28773
  menuItemId: this.item.id
27834
28774
  });
@@ -27989,7 +28929,8 @@
27989
28929
  menuItemId: this.item.id,
27990
28930
  relations: relations,
27991
28931
  viewer: this.viewer,
27992
- visualRegister: this.register
28932
+ visualRegister: this.register,
28933
+ simulation: this.simulation
27993
28934
  });
27994
28935
  if (this.disposed) {
27995
28936
  this.register.RemoveRegos({
@@ -29214,6 +30155,55 @@
29214
30155
  get ModelSpace() {
29215
30156
  return this.modelSpace;
29216
30157
  }
30158
+ /**
30159
+ * Every node of the Assembly read so far, so a caller can work out motion in the Assembly's own terms.
30160
+ * @param timeMs the scene time for each node's current pose. Default: the clock's.
30161
+ * @returns per node its pose within its parent from the API (local) and as drawn at the time (current).
30162
+ */
30163
+ GetNodes(timeMs) {
30164
+ const at = timeMs !== null && timeMs !== void 0 ? timeMs : Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime();
30165
+ const rowsOf = (m) => m ? [0, 1, 2, 3].map(row => [0, 1, 2, 3].map(column => m[(column * 4) + row])) : null;
30166
+ return Object.values(this.hierarchyNodeByEntityId || {}).map(node => {
30167
+ var _a, _b, _c;
30168
+ const entity = (_a = this.apiEntities.get(node.entityId)) !== null && _a !== void 0 ? _a : null;
30169
+ const rows = (_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition;
30170
+ return {
30171
+ entityId: node.entityId,
30172
+ name: node.name,
30173
+ parentId: (_c = node.parentId) !== null && _c !== void 0 ? _c : null,
30174
+ local: Manager.MatrixFromRows(rows) ? rows : null,
30175
+ current: rowsOf(this.localAt(node.entityId, at)),
30176
+ entity
30177
+ };
30178
+ });
30179
+ }
30180
+ /**
30181
+ * The root's world frame at a time, history and simulation included, eg. to follow or frame the Assembly.
30182
+ * @param timeMs the scene time. Default: the clock's.
30183
+ * @returns null until the root has been read.
30184
+ */
30185
+ GetRootFrame(timeMs) {
30186
+ const root = this.rootAt(timeMs !== null && timeMs !== void 0 ? timeMs : Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime());
30187
+ return root ? Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll) : null;
30188
+ }
30189
+ /**
30190
+ * A part's world frame at a time as its records and its parents place it, size included.
30191
+ * @param entityId the part.
30192
+ * @param timeMs the scene time. Default: the clock's.
30193
+ * @returns null until the root and the part have been read.
30194
+ */
30195
+ GetPartFrame(entityId, timeMs) {
30196
+ const at = timeMs !== null && timeMs !== void 0 ? timeMs : Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime();
30197
+ const rootFrame = this.GetRootFrame(at);
30198
+ const world = entityId === this.rootId ? Cesium.Matrix4.IDENTITY : this.composeWorld(entityId, at);
30199
+ if (!rootFrame || !world) {
30200
+ return null;
30201
+ }
30202
+ // Where the part is drawn, so its own ground offset is part of its frame.
30203
+ const ground = this.groundOffsets.get(entityId);
30204
+ const placed = ground ? Cesium.Matrix4.multiply(Cesium.Matrix4.fromTranslation(ground), world, new Cesium.Matrix4()) : world;
30205
+ return Cesium.Matrix4.multiply(rootFrame, placed, new Cesium.Matrix4());
30206
+ }
29217
30207
  constructor(params) {
29218
30208
  this.disposed = false;
29219
30209
  // Cache of the hierarchy so that our scene-tree can reference it.
@@ -29223,19 +30213,41 @@
29223
30213
  this.hierarchyNodeByEntityId = null;
29224
30214
  this.modelSpace = false;
29225
30215
  this.rootId = null;
29226
- // Where the root sits now, and which way it faces.
29227
- // Every child is placed through this, so a root move is one value changing rather than a refetch of the whole Assembly.
30216
+ // Where the root sits outside its history, and which way it faces in degrees. Children are placed through it.
29228
30217
  this.rootPos3d = null;
29229
30218
  this.rootHeading = 0;
29230
- // Entity ID -> what was drawn for it and where it sits relative to the root.
30219
+ this.rootPitch = 0;
30220
+ this.rootRoll = 0;
30221
+ // The root's recorded positions and attitudes, oldest first, kept apart since a record may name only one.
30222
+ this.rootSeries = [];
30223
+ this.rootAttitude = [];
30224
+ // Every placement worked out for the scene time last asked about, shared by all parts drawn at it.
30225
+ this.pose = null;
30226
+ // Entity ID -> what was drawn for it.
29231
30227
  this.children = new Map();
30228
+ // Entity ID -> its live position within its parent, for every node fetched including undrawn groups.
30229
+ this.localPositions = new Map();
30230
+ // Entity ID -> how much higher the ground under it is than under the root, along the root's up.
30231
+ this.groundOffsets = new Map();
30232
+ // Entity ID -> its recorded poses within its parent, oldest first.
30233
+ this.jointSeries = new Map();
30234
+ // Drawn children that can move with the clock, so a tick places only these and not the whole Assembly.
30235
+ this.animatedIds = new Set();
29232
30236
  // The trail through the root's own movement, which describes the whole Assembly.
29233
- this.trackEntity = null;
30237
+ this.trail = null;
30238
+ this.simulationRemoval = null;
30239
+ this.FollowsSimulation = true;
30240
+ // Entity ID -> the record the API gave, so a cleared simulation falls back to it without a refetch.
30241
+ this.apiEntities = new Map();
30242
+ // Re-reads history and re-applies it, eg. after simulated records change. Set while the clock is followed.
30243
+ this.regather = null;
30244
+ this.followingClock = false;
29234
30245
  // Callback to dispose the link between scene time and the root's historic position.
29235
30246
  this.viewerDateTimeChangeRemoval = null;
29236
30247
  this.historicGatherer = null;
29237
30248
  this.historicAnimation = null;
29238
30249
  const { viewer, register: visualsManager, getters, item } = params;
30250
+ this.simulation = params.simulation;
29239
30251
  this.viewer = viewer;
29240
30252
  this.getters = getters;
29241
30253
  this.item = item;
@@ -29259,10 +30271,10 @@
29259
30271
  return exports.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"]);
29260
30272
  }
29261
30273
  /*
29262
- * The root's frame: east-north-up at its position, turned by its heading.
30274
+ * The root's frame: east-north-up at its position, turned by its heading, pitch and roll.
29263
30275
  */
29264
- static rootFrame(rootPos3d, headingDegrees) {
29265
- const hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(headingDegrees || 0), 0, 0);
30276
+ static rootFrame(rootPos3d, headingDegrees, pitchDegrees = 0, rollDegrees = 0) {
30277
+ const hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(headingDegrees || 0), Cesium.Math.toRadians(pitchDegrees || 0), Cesium.Math.toRadians(rollDegrees || 0));
29266
30278
  return Cesium.Transforms.headingPitchRollToFixedFrame(rootPos3d, hpr);
29267
30279
  }
29268
30280
  /**
@@ -29285,28 +30297,553 @@
29285
30297
  return Cesium.Matrix4.multiplyByPoint(Manager.rootFrame(rootPos3d, headingDegrees), offset, new Cesium.Cartesian3());
29286
30298
  }
29287
30299
  /**
29288
- * Moves the whole Assembly to a new root position, without refetching any of its children.
30300
+ * Moves where the root sits outside its history, without refetching children; recorded poses still win.
29289
30301
  * @param params
29290
30302
  */
29291
30303
  ApplyRootPosition(params) {
29292
- var _a, _b;
30304
+ var _a, _b, _c, _d, _e, _f;
29293
30305
  if (this.disposed || !(params === null || params === void 0 ? void 0 : params.pos3d)) {
29294
30306
  return;
29295
30307
  }
29296
30308
  this.rootPos3d = params.pos3d;
29297
30309
  this.rootHeading = (_b = (_a = params.heading) !== null && _a !== void 0 ? _a : this.rootHeading) !== null && _b !== void 0 ? _b : 0;
30310
+ this.rootPitch = (_d = (_c = params.pitch) !== null && _c !== void 0 ? _c : this.rootPitch) !== null && _d !== void 0 ? _d : 0;
30311
+ this.rootRoll = (_f = (_e = params.roll) !== null && _e !== void 0 ? _e : this.rootRoll) !== null && _f !== void 0 ? _f : 0;
30312
+ this.repose(true);
30313
+ }
30314
+ /*
30315
+ * Drops every placement worked out so far, after the root or a joint's records change.
30316
+ * @param everyChild re-place the parts that cannot move as well, because where they rest has changed.
30317
+ */
30318
+ repose(everyChild = false) {
30319
+ this.pose = null;
29298
30320
  for (const child of this.children.values()) {
29299
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, child.offset);
29300
- if (!pos3d) {
30321
+ if (everyChild || Boolean(child.moving) != this.canMove(child.entityId)) {
30322
+ this.bindPlacement(child);
30323
+ }
30324
+ }
30325
+ this.viewer.scene.requestRender();
30326
+ }
30327
+ /*
30328
+ * Takes a node's pose outside its history from its record, and for the root where it stands and faces.
30329
+ */
30330
+ absorbNode(entity) {
30331
+ var _a, _b, _c, _d, _e;
30332
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30333
+ const local = Manager.MatrixFromRows((_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition);
30334
+ if (local) {
30335
+ this.localPositions.set(id, local);
30336
+ }
30337
+ // Anchored on the root, which is usually never drawn; everything under it is placed from the hierarchy.
30338
+ if (id === this.rootId || (!this.rootId && !this.rootPos3d)) {
30339
+ const rootPos3d = exports.EntityUtils.GetPos({
30340
+ viewer: this.viewer,
30341
+ entity,
30342
+ recordHeightRef: Manager.GetRecordHeightRef(this.item),
30343
+ returnHeightRef: Cesium.HeightReference.NONE,
30344
+ allowRendered: false
30345
+ });
30346
+ if (rootPos3d) {
30347
+ this.rootPos3d = rootPos3d;
30348
+ this.rootHeading = EnsureNumber((_c = entity.Bruce.Transform) === null || _c === void 0 ? void 0 : _c.heading, 0);
30349
+ this.rootPitch = EnsureNumber((_d = entity.Bruce.Transform) === null || _d === void 0 ? void 0 : _d.pitch, 0);
30350
+ this.rootRoll = EnsureNumber((_e = entity.Bruce.Transform) === null || _e === void 0 ? void 0 : _e.roll, 0);
30351
+ }
30352
+ }
30353
+ }
30354
+ /*
30355
+ * A node's record with its simulation laid over it, keeping what a stand-in leaves out, eg. its model.
30356
+ */
30357
+ withSimulation(entity) {
30358
+ var _a;
30359
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30360
+ if (!this.simulation || !id) {
30361
+ return entity;
30362
+ }
30363
+ const standIn = this.simulation.GetEntity(id);
30364
+ if (!standIn) {
30365
+ return this.simulation.Overlay(entity);
30366
+ }
30367
+ return { ...entity, ...standIn, Bruce: { ...entity.Bruce, ...standIn.Bruce } };
30368
+ }
30369
+ /*
30370
+ * Brings the Assembly in line with a change to what is simulated.
30371
+ */
30372
+ onSimulationChanged(change) {
30373
+ var _a;
30374
+ if (this.disposed || !this.hierarchyNodeByEntityId) {
30375
+ return;
30376
+ }
30377
+ const ids = change.entityIds.filter(id => this.hierarchyNodeByEntityId[id]);
30378
+ if (!ids.length) {
30379
+ return;
30380
+ }
30381
+ if (change.entities) {
30382
+ for (const id of ids) {
30383
+ const api = this.apiEntities.get(id);
30384
+ if (!api) {
30385
+ // Not loaded yet, and it will be drawn from its simulation when it is.
30386
+ continue;
30387
+ }
30388
+ const entity = this.withSimulation(api);
30389
+ // A cleared blob leaves the node's own matrix where the API has none, so it is reset.
30390
+ if (!((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.AssemblyPosition)) {
30391
+ this.localPositions.delete(id);
30392
+ }
30393
+ this.absorbNode(entity);
30394
+ const child = this.children.get(id);
30395
+ if (child) {
30396
+ child.entity = entity;
30397
+ }
30398
+ }
30399
+ this.repose(true);
30400
+ }
30401
+ if (change.records) {
30402
+ if (this.regather) {
30403
+ this.regather(true);
30404
+ }
30405
+ else {
30406
+ this.viewerDateTimeSub();
30407
+ }
30408
+ }
30409
+ }
30410
+ /*
30411
+ * Nodes of this Assembly with simulated history.
30412
+ */
30413
+ simulatedNodeIds() {
30414
+ if (!this.simulation || !this.hierarchyNodeByEntityId) {
30415
+ return [];
30416
+ }
30417
+ return this.simulation.GetRecordEntityIds().filter(id => this.hierarchyNodeByEntityId[id]);
30418
+ }
30419
+ /*
30420
+ * Whether a node can move with time: the root has history, or it or an ancestor does.
30421
+ */
30422
+ canMove(entityId) {
30423
+ return this.rootSeries.length > 0 || this.rootAttitude.length > 0 || this.animatedIds.has(entityId);
30424
+ }
30425
+ /*
30426
+ * Gives a child its position and orientation, as functions of scene time only when it can move.
30427
+ */
30428
+ bindPlacement(child) {
30429
+ const id = child.entityId;
30430
+ const moving = this.canMove(id);
30431
+ if (moving) {
30432
+ if (!child.moving) {
30433
+ child.cEntity.position = new Cesium.CallbackProperty((time, result) => { var _a; return Manager.cloneOrUndefined((_a = this.placementAt(id, time)) === null || _a === void 0 ? void 0 : _a.pos3d, result); }, false);
30434
+ child.cEntity.orientation = new Cesium.CallbackProperty((time, result) => {
30435
+ var _a;
30436
+ const orient = (_a = this.placementAt(id, time)) === null || _a === void 0 ? void 0 : _a.orient;
30437
+ return orient ? Cesium.Quaternion.clone(orient, result) : undefined;
30438
+ }, false);
30439
+ child.cEntity.model.scale = new Cesium.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);
30440
+ }
30441
+ }
30442
+ else {
30443
+ const placed = this.placementAt(id, this.viewer.clock.currentTime);
30444
+ if (placed) {
30445
+ child.cEntity.position = new Cesium.ConstantPositionProperty(placed.pos3d);
30446
+ if (placed.orient) {
30447
+ child.cEntity.orientation = new Cesium.ConstantProperty(placed.orient);
30448
+ }
30449
+ child.cEntity.model.scale = new Cesium.ConstantProperty(placed.scale);
30450
+ }
30451
+ }
30452
+ if (Boolean(child.moving) != moving) {
30453
+ child.moving = moving;
30454
+ this.visualsManager.SetMoving({ moving, entityIds: [id], menuItemId: this.item.id });
30455
+ }
30456
+ }
30457
+ /*
30458
+ * The one scale a model takes from its part's composed matrix, the largest when they are uneven.
30459
+ */
30460
+ static uniformScale(world) {
30461
+ return Cesium.Cartesian3.maximumComponent(Cesium.Matrix4.getScale(world, new Cesium.Cartesian3()));
30462
+ }
30463
+ static cloneOrUndefined(pos3d, result) {
30464
+ return pos3d ? Cesium.Cartesian3.clone(pos3d, result) : undefined;
30465
+ }
30466
+ /*
30467
+ * Where a node draws at a scene time, from one composition shared by every part asked about at that time.
30468
+ */
30469
+ placementAt(entityId, time) {
30470
+ if (this.disposed || !time) {
30471
+ return null;
30472
+ }
30473
+ const timeMs = Cesium.JulianDate.toDate(time).getTime();
30474
+ if (!this.pose || this.pose.timeMs !== timeMs) {
30475
+ const root = this.rootAt(timeMs);
30476
+ this.pose = {
30477
+ timeMs,
30478
+ rootFrame: root ? Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll) : null,
30479
+ worlds: new Map(),
30480
+ placements: new Map()
30481
+ };
30482
+ }
30483
+ const pose = this.pose;
30484
+ if (!pose.rootFrame) {
30485
+ return null;
30486
+ }
30487
+ let placed = pose.placements.get(entityId);
30488
+ if (!placed) {
30489
+ const world = this.composeWorld(entityId, timeMs, pose.worlds);
30490
+ const offset = world
30491
+ ? Cesium.Matrix4.getTranslation(world, new Cesium.Cartesian3())
30492
+ : new Cesium.Cartesian3(0, 0, 0);
30493
+ const ground = this.groundOffsets.get(entityId);
30494
+ if (ground) {
30495
+ Cesium.Cartesian3.add(offset, ground, offset);
30496
+ }
30497
+ placed = {
30498
+ pos3d: Cesium.Matrix4.multiplyByPoint(pose.rootFrame, offset, new Cesium.Cartesian3()),
30499
+ // Without a matrix, eg. the root itself, there is no orientation of its own to apply.
30500
+ orient: world ? exports.EntityUtils.GetAssemblyOrientationInFrame(pose.rootFrame, world) : null,
30501
+ scale: world ? Manager.uniformScale(world) : 1
30502
+ };
30503
+ pose.placements.set(entityId, placed);
30504
+ }
30505
+ return placed;
30506
+ }
30507
+ /*
30508
+ * The root's pose at a time, position and attitude each following their own records as a joint's do.
30509
+ */
30510
+ rootAt(timeMs) {
30511
+ const pos3d = this.rootPositionAt(timeMs);
30512
+ if (!pos3d) {
30513
+ return null;
30514
+ }
30515
+ const live = { heading: this.rootHeading, pitch: this.rootPitch, roll: this.rootRoll };
30516
+ const series = this.rootAttitude;
30517
+ if (!series.length || timeMs < series[0].timeMs) {
30518
+ return { pos3d, ...live };
30519
+ }
30520
+ let i = 0;
30521
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30522
+ i++;
30523
+ }
30524
+ const a = series[i];
30525
+ const b = series[i + 1];
30526
+ const t = b ? (timeMs - a.timeMs) / (b.timeMs - a.timeMs) : 0;
30527
+ const turn = (from, to, fallback) => {
30528
+ var _a, _b, _c;
30529
+ const x = (_a = from !== null && from !== void 0 ? from : fallback) !== null && _a !== void 0 ? _a : 0;
30530
+ const y = (_c = (_b = (b ? to : from)) !== null && _b !== void 0 ? _b : fallback) !== null && _c !== void 0 ? _c : 0;
30531
+ // The short way round, so 350 to 10 degrees turns through north rather than all the way back.
30532
+ return x + (Cesium.Math.negativePiToPi(Cesium.Math.toRadians(y - x)) * 180 / Math.PI) * t;
30533
+ };
30534
+ return {
30535
+ pos3d,
30536
+ heading: turn(a.heading, b === null || b === void 0 ? void 0 : b.heading, live.heading),
30537
+ pitch: turn(a.pitch, b === null || b === void 0 ? void 0 : b.pitch, live.pitch),
30538
+ roll: turn(a.roll, b === null || b === void 0 ? void 0 : b.roll, live.roll)
30539
+ };
30540
+ }
30541
+ /*
30542
+ * Where the root is at a time, from its recorded positions.
30543
+ */
30544
+ rootPositionAt(timeMs) {
30545
+ const series = this.rootSeries;
30546
+ if (!series.length || timeMs < series[0].timeMs) {
30547
+ return this.rootPos3d;
30548
+ }
30549
+ let i = 0;
30550
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30551
+ i++;
30552
+ }
30553
+ const a = series[i];
30554
+ const b = series[i + 1];
30555
+ if (!b) {
30556
+ return a.pos3d;
30557
+ }
30558
+ const t = (timeMs - a.timeMs) / (b.timeMs - a.timeMs);
30559
+ // A straight line between orbit samples cuts up to a kilometre inside the path, so it is lifted back out.
30560
+ const chord = Cesium.Cartesian3.lerp(a.pos3d, b.pos3d, t, new Cesium.Cartesian3());
30561
+ const carto = Cesium.Cartographic.fromCartesian(chord);
30562
+ return carto
30563
+ ? Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, Cesium.Math.lerp(a.height, b.height, t))
30564
+ : chord;
30565
+ }
30566
+ /*
30567
+ * Takes the root's records into its series.
30568
+ * @param replace the records are everything held for the root, so the series becomes exactly them.
30569
+ * @returns whether the series changed.
30570
+ */
30571
+ absorbRootRecords(posses, replace) {
30572
+ if (!(posses === null || posses === void 0 ? void 0 : posses.length) && !(replace && this.rootSeries.length)) {
30573
+ return false;
30574
+ }
30575
+ const byTime = new Map(replace ? [] : this.rootSeries.map(k => [k.timeMs, k]));
30576
+ for (const pos of posses) {
30577
+ const carto = Cesium.Cartographic.fromCartesian(pos.pos3d);
30578
+ if (!carto) {
30579
+ continue;
30580
+ }
30581
+ byTime.set(pos.dateTime.getTime(), {
30582
+ timeMs: pos.dateTime.getTime(),
30583
+ pos3d: pos.pos3d,
30584
+ height: carto.height
30585
+ });
30586
+ }
30587
+ const series = Array.from(byTime.values()).sort((x, y) => x.timeMs - y.timeMs);
30588
+ const changed = (series.length !== this.rootSeries.length ||
30589
+ series.some((k, i) => !Cesium.Cartesian3.equals(k.pos3d, this.rootSeries[i].pos3d)));
30590
+ this.rootSeries = series;
30591
+ return changed;
30592
+ }
30593
+ /*
30594
+ * Takes the angles the root's records name into its attitude series, apart from where it is.
30595
+ * @param replace the records are everything held for the root, as for absorbRootRecords.
30596
+ * @returns whether the series changed.
30597
+ */
30598
+ absorbRootAttitude(records, replace) {
30599
+ const angle = (transform, key) => {
30600
+ var _a;
30601
+ 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)];
30602
+ return value == null || isNaN(+value) ? null : +value;
30603
+ };
30604
+ const byTime = new Map(replace ? [] : this.rootAttitude.map(k => [k.timeMs, k]));
30605
+ for (const record of records || []) {
30606
+ const timeMs = (record === null || record === void 0 ? void 0 : record.dateTime) ? new Date(record.dateTime).getTime() : NaN;
30607
+ const key = {
30608
+ timeMs,
30609
+ heading: angle(record === null || record === void 0 ? void 0 : record.transform, "heading"),
30610
+ pitch: angle(record === null || record === void 0 ? void 0 : record.transform, "pitch"),
30611
+ roll: angle(record === null || record === void 0 ? void 0 : record.transform, "roll")
30612
+ };
30613
+ if (isNaN(timeMs) || (key.heading == null && key.pitch == null && key.roll == null)) {
29301
30614
  continue;
29302
30615
  }
29303
- child.cEntity.position = new Cesium.ConstantPositionProperty(pos3d);
29304
- const orient = exports.EntityUtils.GetAssemblyOrientation(child.entity, pos3d);
29305
- if (orient) {
29306
- child.cEntity.orientation = new Cesium.ConstantProperty(orient);
30616
+ byTime.set(timeMs, key);
30617
+ }
30618
+ const series = Array.from(byTime.values()).sort((x, y) => x.timeMs - y.timeMs);
30619
+ const held = this.rootAttitude;
30620
+ const differs = (k, i) => (k.timeMs !== held[i].timeMs ||
30621
+ k.heading !== held[i].heading ||
30622
+ k.pitch !== held[i].pitch ||
30623
+ k.roll !== held[i].roll);
30624
+ const changed = series.length !== held.length || series.some(differs);
30625
+ this.rootAttitude = series;
30626
+ return changed;
30627
+ }
30628
+ /*
30629
+ * A node's matrix in the root's frame at a time, from each ancestor's pose within its parent.
30630
+ */
30631
+ composeWorld(entityId, timeMs, memo, localOf = (id) => this.localAt(id, timeMs)) {
30632
+ var _a, _b, _c, _d;
30633
+ if (memo === null || memo === void 0 ? void 0 : memo.has(entityId)) {
30634
+ return memo.get(entityId);
30635
+ }
30636
+ let world = null;
30637
+ const local = localOf(entityId);
30638
+ if (local) {
30639
+ const parentId = (_b = (_a = this.hierarchyNodeByEntityId) === null || _a === void 0 ? void 0 : _a[entityId]) === null || _b === void 0 ? void 0 : _b.parentId;
30640
+ const parent = parentId ? this.composeWorld(parentId, timeMs, memo, localOf) : null;
30641
+ world = parent ? Cesium.Matrix4.multiply(parent, local, new Cesium.Matrix4()) : Cesium.Matrix4.clone(local);
30642
+ }
30643
+ else if (entityId !== this.rootId) {
30644
+ // A node whose own position was not fetched falls back to the API's composed matrix.
30645
+ const child = this.children.get(entityId);
30646
+ 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);
30647
+ }
30648
+ memo === null || memo === void 0 ? void 0 : memo.set(entityId, world);
30649
+ return world;
30650
+ }
30651
+ /*
30652
+ * Under a clamped or relative style, lifts a part by how much higher its ground is than the root's.
30653
+ */
30654
+ measureGroundOffset(entity) {
30655
+ var _a;
30656
+ const entityId = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
30657
+ this.groundOffsets.delete(entityId);
30658
+ const root = this.apiEntities.get(this.rootId);
30659
+ if (!entityId ||
30660
+ entityId === this.rootId ||
30661
+ !root ||
30662
+ !this.rootPos3d ||
30663
+ Manager.GetRecordHeightRef(this.item) === Cesium.HeightReference.NONE) {
30664
+ return;
30665
+ }
30666
+ const rootFrame = Manager.rootFrame(this.rootPos3d, this.rootHeading, this.rootPitch, this.rootRoll);
30667
+ const toRoot = Cesium.Matrix4.inverse(rootFrame, new Cesium.Matrix4());
30668
+ const groundUp = (record) => {
30669
+ const pos3d = exports.EntityUtils.GetPos({
30670
+ viewer: this.viewer,
30671
+ entity: record,
30672
+ recordHeightRef: Cesium.HeightReference.CLAMP_TO_GROUND,
30673
+ returnHeightRef: Cesium.HeightReference.NONE,
30674
+ allowRendered: false
30675
+ });
30676
+ return pos3d ? Cesium.Matrix4.multiplyByPoint(toRoot, pos3d, new Cesium.Cartesian3()).z : null;
30677
+ };
30678
+ const part = groundUp(entity);
30679
+ const under = groundUp(root);
30680
+ if (part == null || under == null || Math.abs(part - under) < 1e-6) {
30681
+ return;
30682
+ }
30683
+ this.groundOffsets.set(entityId, new Cesium.Cartesian3(0, 0, part - under));
30684
+ }
30685
+ /*
30686
+ * The rigid Earth-fixed motion from where a part's record places it to where it is drawn at a time.
30687
+ */
30688
+ motionAt(entityId, time) {
30689
+ if (this.disposed || !time || !this.rootPos3d) {
30690
+ return null;
30691
+ }
30692
+ const timeMs = Cesium.JulianDate.toDate(time).getTime();
30693
+ const root = this.rootAt(timeMs);
30694
+ const drawn = this.composeWorld(entityId, timeMs, new Map());
30695
+ const stored = this.composeWorld(entityId, timeMs, new Map(), (id) => {
30696
+ var _a, _b, _c, _d;
30697
+ 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);
30698
+ });
30699
+ if (!root || !drawn || !stored) {
30700
+ return null;
30701
+ }
30702
+ const drawnFixed = Cesium.Matrix4.multiply(Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll), drawn, new Cesium.Matrix4());
30703
+ const storedFixed = Cesium.Matrix4.multiply(Manager.rootFrame(this.rootPos3d, this.rootHeading, this.rootPitch, this.rootRoll), stored, new Cesium.Matrix4());
30704
+ return Cesium.Matrix4.multiply(drawnFixed, Cesium.Matrix4.inverse(storedFixed, new Cesium.Matrix4()), new Cesium.Matrix4());
30705
+ }
30706
+ /*
30707
+ * A node's pose within its parent at a time, interpolated between the records either side.
30708
+ */
30709
+ localAt(entityId, timeMs) {
30710
+ var _a;
30711
+ // The live pose stands before the first record and the last record holds after it, as in an API read.
30712
+ const live = (_a = this.localPositions.get(entityId)) !== null && _a !== void 0 ? _a : null;
30713
+ const series = this.jointSeries.get(entityId);
30714
+ if (!(series === null || series === void 0 ? void 0 : series.length) || timeMs == null || timeMs < series[0].timeMs) {
30715
+ return live;
30716
+ }
30717
+ let i = 0;
30718
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
30719
+ i++;
30720
+ }
30721
+ const a = series[i];
30722
+ const b = series[i + 1];
30723
+ if (!b) {
30724
+ return Cesium.Matrix4.fromTranslationQuaternionRotationScale(a.translation, a.rotation, a.scale);
30725
+ }
30726
+ const t = (timeMs - a.timeMs) / (b.timeMs - a.timeMs);
30727
+ return Cesium.Matrix4.fromTranslationQuaternionRotationScale(Cesium.Cartesian3.lerp(a.translation, b.translation, t, new Cesium.Cartesian3()), Cesium.Quaternion.slerp(a.rotation, b.rotation, t, new Cesium.Quaternion()), Cesium.Cartesian3.lerp(a.scale, b.scale, t, new Cesium.Cartesian3()));
30728
+ }
30729
+ /*
30730
+ * Rebuilds each node's keyframes from the historic records held so far.
30731
+ * @returns whether any node has keyframes.
30732
+ */
30733
+ absorbJointRecords(recordsByIds) {
30734
+ var _a;
30735
+ // Everything held is given, so a node with nothing left, eg. its simulated records cleared, stops moving.
30736
+ for (const entityId of Array.from(this.jointSeries.keys())) {
30737
+ if (!((_a = recordsByIds === null || recordsByIds === void 0 ? void 0 : recordsByIds[entityId]) === null || _a === void 0 ? void 0 : _a.length)) {
30738
+ this.jointSeries.delete(entityId);
30739
+ }
30740
+ }
30741
+ for (const entityId of Object.keys(recordsByIds || {})) {
30742
+ const keyframes = [];
30743
+ for (const record of recordsByIds[entityId] || []) {
30744
+ const matrix = Manager.MatrixFromRows(record.assemblyPosition);
30745
+ const timeMs = new Date(record.dateTime).getTime();
30746
+ if (!matrix || isNaN(timeMs)) {
30747
+ continue;
30748
+ }
30749
+ const scale = Cesium.Matrix4.getScale(matrix, new Cesium.Cartesian3());
30750
+ const rotation = Cesium.Matrix4.getRotation(matrix, new Cesium.Matrix3());
30751
+ keyframes.push({
30752
+ timeMs,
30753
+ translation: Cesium.Matrix4.getTranslation(matrix, new Cesium.Cartesian3()),
30754
+ rotation: Cesium.Quaternion.fromRotationMatrix(rotation),
30755
+ scale
30756
+ });
30757
+ }
30758
+ if (keyframes.length) {
30759
+ keyframes.sort((x, y) => x.timeMs - y.timeMs);
30760
+ this.jointSeries.set(entityId, keyframes);
29307
30761
  }
29308
30762
  }
29309
- this.viewer.scene.requestRender();
30763
+ this.refreshAnimated();
30764
+ return this.jointSeries.size > 0;
30765
+ }
30766
+ /*
30767
+ * Rebuilds which drawn children can move with the clock, by walking each one up to the root.
30768
+ */
30769
+ refreshAnimated() {
30770
+ this.animatedIds.clear();
30771
+ if (!this.jointSeries.size) {
30772
+ return;
30773
+ }
30774
+ for (const entityId of this.children.keys()) {
30775
+ if (this.hasAnimatedAncestry(entityId)) {
30776
+ this.animatedIds.add(entityId);
30777
+ }
30778
+ }
30779
+ }
30780
+ /*
30781
+ * Whether a node or anything above it has keyframes, so the clock can move it.
30782
+ */
30783
+ hasAnimatedAncestry(entityId) {
30784
+ var _a, _b;
30785
+ let at = entityId;
30786
+ while (at && this.jointSeries.size) {
30787
+ if (this.jointSeries.has(at)) {
30788
+ return true;
30789
+ }
30790
+ at = (_b = (_a = this.hierarchyNodeByEntityId) === null || _a === void 0 ? void 0 : _a[at]) === null || _b === void 0 ? void 0 : _b.parentId;
30791
+ }
30792
+ return false;
30793
+ }
30794
+ /*
30795
+ * Which nodes to ask history for.
30796
+ */
30797
+ async historicEntityIds(api) {
30798
+ var _a, _b;
30799
+ // Through the Assembly when there is one, so the hierarchy follows the relation type on its record.
30800
+ const assemblyId = this.item["Assembly.ID"];
30801
+ const root = assemblyId ? { key: "root", AssemblyID: assemblyId } : { key: "root", RootID: this.rootId };
30802
+ const historic = {
30803
+ key: "historic",
30804
+ Historic: { HistoricKey: ((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey) || undefined }
30805
+ };
30806
+ const query = {
30807
+ [BModels.DataLab.EReqKey.Primary]: { Items: [root, historic], Select: { Type: BModels.DataLab.ESelectType.Id } }
30808
+ };
30809
+ const load = 1000;
30810
+ try {
30811
+ const ids = [];
30812
+ for (let skip = 0;; skip += load) {
30813
+ const { items } = await BModels.DataLab.Run({ api, query, skip, load });
30814
+ ids.push(...(items !== null && items !== void 0 ? items : []));
30815
+ if (!items || items.length < load) {
30816
+ return ids;
30817
+ }
30818
+ }
30819
+ }
30820
+ catch (e) {
30821
+ // An API without the historic criterion gets asked about every node, as before it existed.
30822
+ console.warn("AssemblyRenderManager.historicEntityIds()", e);
30823
+ }
30824
+ return Object.keys((_b = this.hierarchyNodeByEntityId) !== null && _b !== void 0 ? _b : { [this.rootId]: null });
30825
+ }
30826
+ /**
30827
+ * Builds a matrix from the row-major rows that Bruce/AssemblyPosition and AssemblyWorldPosition use.
30828
+ * @param rows
30829
+ */
30830
+ static MatrixFromRows(rows) {
30831
+ if (!Array.isArray(rows) || rows.length !== 4 || rows.some(r => !Array.isArray(r) || r.length !== 4)) {
30832
+ return null;
30833
+ }
30834
+ return new Cesium.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]);
30835
+ }
30836
+ /**
30837
+ * The inverse of MatrixFromRows.
30838
+ * @param matrix
30839
+ */
30840
+ static RowsFromMatrix(matrix) {
30841
+ const rows = [];
30842
+ for (let r = 0; r < 4; r++) {
30843
+ const row = Cesium.Matrix4.getRow(matrix, r, new Cesium.Cartesian4());
30844
+ rows.push([row.x, row.y, row.z, row.w]);
30845
+ }
30846
+ return rows;
29310
30847
  }
29311
30848
  Init() {
29312
30849
  this.renderPriority = this.item.renderPriority;
@@ -29314,6 +30851,7 @@
29314
30851
  this.renderPriority = 1;
29315
30852
  }
29316
30853
  (async () => {
30854
+ var _a, _b;
29317
30855
  if (this.disposed) {
29318
30856
  return;
29319
30857
  }
@@ -29329,6 +30867,8 @@
29329
30867
  this.hierarchyUpdate.Trigger(hierarchy);
29330
30868
  // Build hierarchy node cache for quick lookups
29331
30869
  this.buildHierarchyNodes(hierarchy);
30870
+ // A set for membership, since searching the list took minutes at 200k nodes.
30871
+ const seenIds = new Set();
29332
30872
  const traverseHierarchy = (node, leavesOnly) => {
29333
30873
  var _a;
29334
30874
  if (node.Children) {
@@ -29337,7 +30877,8 @@
29337
30877
  }
29338
30878
  }
29339
30879
  const addNode = !leavesOnly || !((_a = node.Children) === null || _a === void 0 ? void 0 : _a.length);
29340
- if (addNode && node.ID && !flatEntityIds.includes(node.ID)) {
30880
+ if (addNode && node.ID && !seenIds.has(node.ID)) {
30881
+ seenIds.add(node.ID);
29341
30882
  flatEntityIds.push(node.ID);
29342
30883
  }
29343
30884
  };
@@ -29356,17 +30897,17 @@
29356
30897
  }
29357
30898
  }
29358
30899
  this.rootId = rootId;
30900
+ 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;
29359
30901
  if (this.disposed) {
29360
30902
  return;
29361
30903
  }
29362
30904
  else if (flatEntityIds.length === 0) {
29363
30905
  return;
29364
30906
  }
30907
+ // Followed while the parts load: in a large Assembly loading takes far longer than anyone waits.
30908
+ const following = this.viewerDateTimeSub();
29365
30909
  await this.processEntityIds(flatEntityIds);
29366
- if (this.disposed) {
29367
- return;
29368
- }
29369
- this.viewerDateTimeSub();
30910
+ await following;
29370
30911
  }
29371
30912
  catch (e) {
29372
30913
  console.error("AssemblyRenderManager.Manager.Init()", e);
@@ -29393,7 +30934,7 @@
29393
30934
  if (!batch.length) {
29394
30935
  continue;
29395
30936
  }
29396
- const { entities: fetched } = await BModels.Entity.GetListByIds({
30937
+ const { entities: apiEntities } = await BModels.Entity.GetListByIds({
29397
30938
  entityIds: batch,
29398
30939
  api: this.getters.GetBruceApi(),
29399
30940
  expandLODs: true,
@@ -29405,6 +30946,16 @@
29405
30946
  if (this.disposed) {
29406
30947
  return;
29407
30948
  }
30949
+ const fetched = apiEntities.map((entity) => {
30950
+ var _a;
30951
+ this.apiEntities.set((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID, entity);
30952
+ return this.withSimulation(entity);
30953
+ });
30954
+ for (const entity of fetched) {
30955
+ this.absorbNode(entity);
30956
+ }
30957
+ // Placements already worked out did not know about this batch's positions.
30958
+ this.pose = null;
29408
30959
  for (const entity of fetched) {
29409
30960
  // Filter for valid LODs then take the highest quality one (lowest level).
29410
30961
  let lods = entity.Bruce.LOD || [];
@@ -29427,57 +30978,46 @@
29427
30978
  // Won't bother with vector data for this experiment.
29428
30979
  continue;
29429
30980
  }
29430
- const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29431
- const recordPos3d = exports.EntityUtils.GetPos({
29432
- viewer: this.viewer,
29433
- entity,
29434
- recordHeightRef: recordHeightRef,
29435
- returnHeightRef: Cesium.HeightReference.NONE,
29436
- allowRendered: false
29437
- });
29438
- if (!recordPos3d) {
30981
+ const entityId = entity.Bruce.ID;
30982
+ this.measureGroundOffset(entity);
30983
+ // A placement cached before the offset was known would leave the part off its ground.
30984
+ (_a = this.pose) === null || _a === void 0 ? void 0 : _a.placements.delete(entityId);
30985
+ if (!this.canMove(entityId) && !this.placementAt(entityId, this.viewer.clock.currentTime)) {
29439
30986
  continue;
29440
30987
  }
29441
- const entityId = entity.Bruce.ID;
29442
- if (entityId === this.rootId || !this.rootPos3d) {
29443
- // Nothing has a frame to be relative to until the root has one.
29444
- this.rootPos3d = recordPos3d;
29445
- this.rootHeading = EnsureNumber((_a = entity.Bruce.Transform) === null || _a === void 0 ? void 0 : _a.heading, 0);
29446
- }
29447
- // Held as an offset in the root's frame, so a root move places it again without
29448
- // anything being fetched. Where the root already moved, that is where it goes.
29449
- const offset = Manager.ChildOffsetIn(this.rootPos3d, this.rootHeading, recordPos3d);
29450
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, offset);
29451
- // Optional: a child with no world matrix is still worth drawing, it just has no
29452
- // orientation of its own to apply.
29453
- const orient = exports.EntityUtils.GetAssemblyOrientation(entity, pos3d);
29454
30988
  const cEntity = this.viewer.entities.add({
29455
30989
  model: {
29456
30990
  uri: lod["ClientFile"].URL,
29457
30991
  ...exports.ViewUtils.DefaultLightingProps(),
29458
30992
  heightReference: Cesium.HeightReference.NONE
29459
- },
29460
- position: pos3d,
29461
- orientation: orient ? new Cesium.ConstantProperty(orient) : undefined
30993
+ }
29462
30994
  });
29463
30995
  const prior = this.children.get(entityId);
29464
30996
  if (prior && prior.cEntity !== cEntity) {
29465
30997
  RemoveCEntityFromScene(this.viewer, prior.cEntity);
29466
30998
  }
29467
- this.children.set(entityId, {
30999
+ const child = {
29468
31000
  entityId: entityId,
29469
31001
  entity: entity,
29470
- cEntity: cEntity,
29471
- offset: offset
29472
- });
31002
+ cEntity: cEntity
31003
+ };
31004
+ this.children.set(entityId, child);
31005
+ // Joins the animated set by its own ancestry, so a late part never re-walks the rest.
31006
+ if (this.hasAnimatedAncestry(entityId)) {
31007
+ this.animatedIds.add(entityId);
31008
+ }
29473
31009
  const geometryRadius = BModels.Entity.GetValue({
29474
31010
  entity: entity,
29475
31011
  path: ["Bruce", "GeometryRadius"]
29476
31012
  });
29477
31013
  cEntity.model["_radius"] = geometryRadius;
29478
31014
  cEntity.model["_radiusLoaded"] = true;
31015
+ // Placed before it is registered, so the register never measures a visual with no position.
31016
+ this.bindPlacement(child);
29479
31017
  this.visualsManager.AddRego({
29480
31018
  rego: {
31019
+ moving: child.moving ? true : null,
31020
+ motion: (time) => this.motionAt(entity.Bruce.ID, time),
29481
31021
  canEdit: true,
29482
31022
  entityId: entity.Bruce.ID,
29483
31023
  menuItemId: this.item.id,
@@ -29497,47 +31037,89 @@
29497
31037
  }
29498
31038
  }
29499
31039
  /*
29500
- * Follows the scene clock and moves the whole Assembly along the root Entity's historic path.
29501
- * Only the root is fetched: everything under it is placed through ApplyRootPosition.
31040
+ * Follows the scene clock, moving the Assembly along the root's history and each joint along its own.
29502
31041
  */
29503
- viewerDateTimeSub() {
29504
- if (!Manager.IsHistoric(this.item) || this.viewerDateTimeChangeRemoval || !this.rootId) {
31042
+ async viewerDateTimeSub() {
31043
+ var _a;
31044
+ const historic = Manager.IsHistoric(this.item);
31045
+ if (this.followingClock || !this.rootId || (!historic && !this.simulatedNodeIds().length)) {
29505
31046
  return;
29506
31047
  }
31048
+ this.followingClock = true;
29507
31049
  const api = this.getters.GetBruceApi();
29508
- const rootEntity = { Bruce: { ID: this.rootId } };
31050
+ const historicIds = historic ? await this.historicEntityIds(api) : [];
31051
+ if (this.disposed) {
31052
+ return;
31053
+ }
31054
+ const attrKey = (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey;
29509
31055
  const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29510
31056
  const toPosses = (records) => {
29511
31057
  return exports.CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, recordHeightRef, Cesium.HeightReference.NONE, records);
29512
31058
  };
29513
- this.historicGatherer = new HistoricGatherer({
29514
- viewer: this.viewer,
29515
- getApi: () => api,
29516
- getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
29517
- // For clock desync between whatever wrote the record and this client.
29518
- paddingMs: 15000,
29519
- onRecords: (recordsByIds) => {
29520
- const records = recordsByIds[this.rootId];
29521
- if ((records === null || records === void 0 ? void 0 : records.length) && this.historicAnimation) {
29522
- this.historicAnimation.SupplementSeries(toPosses(records));
31059
+ if (historicIds.length) {
31060
+ this.historicGatherer = new HistoricGatherer({
31061
+ viewer: this.viewer,
31062
+ getApi: () => api,
31063
+ getAttrKey: () => attrKey,
31064
+ // For clock desync between whatever wrote the record and this client.
31065
+ paddingMs: 15000,
31066
+ simulation: this.simulation,
31067
+ onRecords: (recordsByIds) => {
31068
+ var _a;
31069
+ const records = recordsByIds[this.rootId];
31070
+ if (records === null || records === void 0 ? void 0 : records.length) {
31071
+ const posses = toPosses(records);
31072
+ (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.SupplementSeries(posses);
31073
+ const moved = this.absorbRootRecords(posses, false);
31074
+ if (this.absorbRootAttitude(records, false) || moved) {
31075
+ this.repose();
31076
+ }
31077
+ }
31078
+ // Joint records go in together once the gather lands, so parents never draw apart from children.
29523
31079
  }
29524
- }
29525
- });
31080
+ });
31081
+ }
31082
+ // The nodes with history: what the API has records for, and whatever has been simulated since.
31083
+ const nodes = () => Array.from(new Set([...historicIds, ...this.simulatedNodeIds()]));
29526
31084
  let gathering = false;
29527
- const gather = async () => {
31085
+ // Set when what is held changed without a fetch, eg. a simulated record arriving.
31086
+ let stale = true;
31087
+ const gather = async (force) => {
31088
+ var _a, _b;
31089
+ stale = stale || Boolean(force);
29528
31090
  if (this.disposed || gathering) {
29529
31091
  return;
29530
31092
  }
29531
31093
  gathering = true;
29532
31094
  try {
29533
- const { recordsByIds } = await this.historicGatherer.Gather([rootEntity]);
31095
+ const ids = nodes();
31096
+ let fetched = false;
31097
+ let recordsByIds;
31098
+ if (this.historicGatherer) {
31099
+ ({ fetched, recordsByIds } = await this.historicGatherer.Gather(ids.map(id => ({ Bruce: { ID: id } }))));
31100
+ }
31101
+ else {
31102
+ recordsByIds = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.MergeRecords({}, attrKey, ids)) !== null && _b !== void 0 ? _b : {};
31103
+ }
29534
31104
  if (this.disposed) {
29535
31105
  return;
29536
31106
  }
31107
+ // A gather served from what is held changes nothing and runs every tick, so it stops here.
31108
+ if (!fetched && !stale && this.jointSeries.size) {
31109
+ return;
31110
+ }
31111
+ stale = false;
31112
+ let changed = this.absorbJointRecords(recordsByIds);
29537
31113
  const posses = toPosses(recordsByIds[this.rootId] || []);
31114
+ changed = this.absorbRootRecords(posses, true) || changed;
31115
+ changed = this.absorbRootAttitude(recordsByIds[this.rootId] || [], true) || changed;
31116
+ if (changed) {
31117
+ this.repose();
31118
+ }
29538
31119
  if (!posses.length) {
29539
31120
  return;
29540
31121
  }
31122
+ // Held for the trail alone; where the root is drawn comes from rootAt(), never from its smoothing.
29541
31123
  if (!this.historicAnimation) {
29542
31124
  this.historicAnimation = new exports.CesiumAnimatedProperty.AnimatePositionSeries({
29543
31125
  posses: posses,
@@ -29551,15 +31133,21 @@
29551
31133
  }
29552
31134
  catch (e) {
29553
31135
  console.error("AssemblyRenderManager.viewerDateTimeSub()", e);
31136
+ // A gather that throws would throw again straight away, so the retry waits for the next tick.
31137
+ stale = false;
29554
31138
  }
29555
31139
  finally {
29556
31140
  gathering = false;
31141
+ // A change that landed mid-gather is not left for a tick that may never come on a paused clock.
31142
+ if (stale && !this.disposed) {
31143
+ gather();
31144
+ }
29557
31145
  }
29558
31146
  };
31147
+ this.regather = gather;
29559
31148
  gather();
29560
31149
  let lastTime = null;
29561
31150
  const tickRemoval = this.viewer.clock.onTick.addEventListener(() => {
29562
- var _a;
29563
31151
  if (this.disposed) {
29564
31152
  return;
29565
31153
  }
@@ -29569,15 +31157,9 @@
29569
31157
  }
29570
31158
  lastTime = now;
29571
31159
  gather();
29572
- if (!this.historicAnimation) {
29573
- return;
29574
- }
29575
- const pos3d = this.historicAnimation.GetValue();
29576
- if (pos3d) {
29577
- this.ApplyRootPosition({
29578
- pos3d: pos3d,
29579
- heading: (_a = this.historicAnimation.GetHeadingDegrees()) !== null && _a !== void 0 ? _a : this.rootHeading
29580
- });
31160
+ // Moving parts read the clock themselves when drawn, so the scene only needs a new frame.
31161
+ if (this.rootSeries.length || this.rootAttitude.length || this.animatedIds.size) {
31162
+ this.viewer.scene.requestRender();
29581
31163
  }
29582
31164
  });
29583
31165
  this.viewerDateTimeChangeRemoval = () => {
@@ -29586,6 +31168,10 @@
29586
31168
  (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
29587
31169
  this.historicGatherer = null;
29588
31170
  this.historicAnimation = null;
31171
+ this.regather = null;
31172
+ this.rootSeries = [];
31173
+ this.rootAttitude = [];
31174
+ this.pose = null;
29589
31175
  };
29590
31176
  }
29591
31177
  /*
@@ -29593,7 +31179,7 @@
29593
31179
  */
29594
31180
  applyRootTrack() {
29595
31181
  var _a;
29596
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
31182
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
29597
31183
  return;
29598
31184
  }
29599
31185
  const trackStyle = exports.EntityRenderEngine.GetTrackStyle({
@@ -29603,41 +31189,35 @@
29603
31189
  if (!trackStyle) {
29604
31190
  return;
29605
31191
  }
29606
- this.trackEntity = this.viewer.entities.add({
29607
- id: BModels.ObjectUtils.UId(15),
29608
- polyline: {
29609
- positions: new Cesium.CallbackProperty(() => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; }, false),
29610
- width: trackStyle.width,
29611
- material: trackStyle.color,
29612
- clampToGround: exports.EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
29613
- == Cesium.HeightReference.CLAMP_TO_GROUND,
29614
- zIndex: 0,
29615
- classificationType: Cesium.ClassificationType.BOTH
29616
- }
29617
- });
29618
- this.trackEntity._noEdit = true;
29619
- this.trackEntity._noSelect = true;
29620
- exports.CesiumEntityStyler.SetDefaultColor({
29621
- color: trackStyle.color,
29622
- entity: this.trackEntity,
31192
+ this.trail = new HistoricTrail.Trail({
29623
31193
  viewer: this.viewer,
29624
- override: true,
29625
- requestRender: false
31194
+ color: trackStyle.color,
31195
+ width: trackStyle.width,
31196
+ clampToGround: exports.EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
31197
+ == Cesium.HeightReference.CLAMP_TO_GROUND,
31198
+ getSeries: () => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; },
31199
+ trailSeconds: HistoricTrail.GetTrailSeconds(this.item),
31200
+ // Where the root is drawn, so the trail ends on the model; nothing before the first record held.
31201
+ positionAt: (timeMs) => (this.rootSeries.length && timeMs >= this.rootSeries[0].timeMs ? this.rootPositionAt(timeMs) : null)
29626
31202
  });
29627
31203
  }
29628
31204
  Dispose() {
29629
- var _a;
31205
+ var _a, _b, _c;
29630
31206
  if (this.disposed) {
29631
31207
  return;
29632
31208
  }
29633
31209
  this.disposed = true;
29634
31210
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
29635
31211
  this.viewerDateTimeChangeRemoval = null;
29636
- if (this.trackEntity) {
29637
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
29638
- this.trackEntity = null;
29639
- }
31212
+ (_b = this.simulationRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
31213
+ this.simulationRemoval = null;
31214
+ this.apiEntities.clear();
31215
+ (_c = this.trail) === null || _c === void 0 ? void 0 : _c.Dispose();
31216
+ this.trail = null;
29640
31217
  this.children.clear();
31218
+ this.localPositions.clear();
31219
+ this.jointSeries.clear();
31220
+ this.animatedIds.clear();
29641
31221
  this.visualsManager.RemoveRegos({
29642
31222
  menuItemId: this.item.id
29643
31223
  });
@@ -30091,6 +31671,9 @@
30091
31671
  this.OnSeriesUpdated.Trigger({ itemId, segments: [] });
30092
31672
  }
30093
31673
  }
31674
+ get Simulation() {
31675
+ return this.simulation;
31676
+ }
30094
31677
  get Monitor() {
30095
31678
  return this.sharedMonitor;
30096
31679
  }
@@ -30116,6 +31699,7 @@
30116
31699
  this.visualsRegister = visualsRegister;
30117
31700
  this.sharedGetters = new exports.SharedGetters.Cache();
30118
31701
  this.sharedHistoric = new SharedHistoricGetters.Cache();
31702
+ this.simulation = exports.EntitySimulation.Get(this.viewer);
30119
31703
  this.sharedMonitor = new exports.CesiumViewMonitor.Monitor(this.viewer);
30120
31704
  this.tilesetInitQueue = new InitQueueTileset(this.getters);
30121
31705
  }
@@ -30329,7 +31913,8 @@
30329
31913
  monitor: this.sharedMonitor,
30330
31914
  item: params.item,
30331
31915
  sharedGetters: this.sharedGetters,
30332
- onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment)
31916
+ onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment),
31917
+ simulation: this.simulation
30333
31918
  });
30334
31919
  break;
30335
31920
  case BModels.MenuItem.EType.EntitiesLoaded:
@@ -30338,7 +31923,8 @@
30338
31923
  register: this.visualsRegister,
30339
31924
  apiGetter: params.apiGetter,
30340
31925
  monitor: this.sharedMonitor,
30341
- item: params.item
31926
+ item: params.item,
31927
+ simulation: this.simulation
30342
31928
  });
30343
31929
  break;
30344
31930
  case BModels.MenuItem.EType.EntitiesIds:
@@ -30348,7 +31934,8 @@
30348
31934
  apiGetter: params.apiGetter,
30349
31935
  monitor: this.sharedMonitor,
30350
31936
  item: params.item,
30351
- sharedHistoric: this.sharedHistoric
31937
+ sharedHistoric: this.sharedHistoric,
31938
+ simulation: this.simulation
30352
31939
  });
30353
31940
  break;
30354
31941
  case BModels.MenuItem.EType.Entity:
@@ -30357,7 +31944,8 @@
30357
31944
  register: this.visualsRegister,
30358
31945
  apiGetter: params.apiGetter,
30359
31946
  monitor: this.sharedMonitor,
30360
- item: params.item
31947
+ item: params.item,
31948
+ simulation: this.simulation
30361
31949
  });
30362
31950
  break;
30363
31951
  case BModels.MenuItem.EType.CadTileset:
@@ -30367,7 +31955,8 @@
30367
31955
  getters: this.getters,
30368
31956
  item: params.item,
30369
31957
  modelSpace: params.modelSpace,
30370
- initQueue: this.tilesetInitQueue
31958
+ initQueue: this.tilesetInitQueue,
31959
+ simulation: this.simulation
30371
31960
  });
30372
31961
  break;
30373
31962
  case BModels.MenuItem.EType.Assembly:
@@ -30377,6 +31966,7 @@
30377
31966
  getters: params.getters,
30378
31967
  item: params.item,
30379
31968
  modelSpace: params.modelSpace,
31969
+ simulation: this.simulation
30380
31970
  });
30381
31971
  break;
30382
31972
  case BModels.MenuItem.EType.Osm:
@@ -30404,7 +31994,8 @@
30404
31994
  getters: this.getters,
30405
31995
  monitor: this.sharedMonitor,
30406
31996
  item: params.item,
30407
- initQueue: this.tilesetInitQueue
31997
+ initQueue: this.tilesetInitQueue,
31998
+ simulation: this.simulation
30408
31999
  });
30409
32000
  break;
30410
32001
  case BModels.MenuItem.EType.Kml:
@@ -30428,7 +32019,8 @@
30428
32019
  getters: params.getters,
30429
32020
  item: params.item,
30430
32021
  viewer: this.viewer,
30431
- register: this.visualsRegister
32022
+ register: this.visualsRegister,
32023
+ simulation: this.simulation
30432
32024
  });
30433
32025
  break;
30434
32026
  // Deprecated to support a few demos until new one is phased in.
@@ -30438,7 +32030,8 @@
30438
32030
  apiGetter: params.apiGetter,
30439
32031
  item: params.item,
30440
32032
  register: this.visualsRegister,
30441
- viewer: this.viewer
32033
+ viewer: this.viewer,
32034
+ simulation: this.simulation
30442
32035
  });
30443
32036
  break;
30444
32037
  case BModels.MenuItem.EType.Relationships:
@@ -30446,7 +32039,8 @@
30446
32039
  apiGetter: params.apiGetter,
30447
32040
  item: params.item,
30448
32041
  register: this.visualsRegister,
30449
- viewer: this.viewer
32042
+ viewer: this.viewer,
32043
+ simulation: this.simulation
30450
32044
  });
30451
32045
  break;
30452
32046
  case BModels.MenuItem.EType.GooglePhotoTileset:
@@ -30478,7 +32072,8 @@
30478
32072
  item: params.item,
30479
32073
  apiGetter: params.apiGetter,
30480
32074
  monitor: this.sharedMonitor,
30481
- visualsRegister: this.visualsRegister
32075
+ visualsRegister: this.visualsRegister,
32076
+ simulation: this.simulation
30482
32077
  });
30483
32078
  break;
30484
32079
  // Any "none" type can be ignored. These are just containers for other items.
@@ -30691,6 +32286,63 @@
30691
32286
  }
30692
32287
  }
30693
32288
  }
32289
+ /**
32290
+ * Draws Entities from data given here as well as, or instead of, the API's, until ClearSimulation is called.
32291
+ * @param params.set the simulation this belongs to, so it can be cleared apart from others. Default "default".
32292
+ * @param params.entities stand-ins that replace each Entity's record; API data for them is ignored.
32293
+ * @param params.patches partial Entities laid over each Entity; live API data shows where a patch is silent.
32294
+ * @param params.records historic records naming entityId, dateTime and only the data they change.
32295
+ * @param params.menuItemIds the items to redraw. By default every item already drawing one of the Entities.
32296
+ */
32297
+ async Simulate(params) {
32298
+ const { set, entities, patches, records, menuItemIds } = params;
32299
+ this.simulation.SetEntities(entities, set);
32300
+ this.simulation.SetPatches(patches, set);
32301
+ this.simulation.SetRecords(records, set);
32302
+ const entityIds = [
32303
+ ...(entities || []).map(x => { var _a; return (_a = x === null || x === void 0 ? void 0 : x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }),
32304
+ ...(patches || []).map(x => { var _a; return (_a = x === null || x === void 0 ? void 0 : x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }),
32305
+ ...(records || []).map(x => x === null || x === void 0 ? void 0 : x.entityId)
32306
+ ];
32307
+ await this.reRenderSimulated(entityIds, menuItemIds);
32308
+ }
32309
+ /**
32310
+ * Stops simulating these Entities, or every Entity when none are named, and redraws them.
32311
+ * @param params.set the simulation to clear. By default every one.
32312
+ * @param params.entities clear the stand-ins and patches. Default true.
32313
+ * @param params.records clear the historic records. Default true.
32314
+ */
32315
+ async ClearSimulation(params) {
32316
+ var _a;
32317
+ const entityIds = (_a = params === null || params === void 0 ? void 0 : params.entityIds) !== null && _a !== void 0 ? _a : [
32318
+ ...this.simulation.GetEntityIds(),
32319
+ ...this.simulation.GetRecordEntityIds()
32320
+ ];
32321
+ 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 });
32322
+ await this.reRenderSimulated(entityIds, params === null || params === void 0 ? void 0 : params.menuItemIds);
32323
+ }
32324
+ /*
32325
+ * Redraws simulated Entities only in the items holding a visual for them, unless the caller names the items.
32326
+ */
32327
+ async reRenderSimulated(entityIds, menuItemIds) {
32328
+ const ids = Array.from(new Set(entityIds.filter(x => !!x)));
32329
+ if (!ids.length) {
32330
+ return;
32331
+ }
32332
+ const items = (menuItemIds === null || menuItemIds === void 0 ? void 0 : menuItemIds.length) ? this.items.filter(x => menuItemIds.includes(x.id)) : this.items;
32333
+ for (const item of items) {
32334
+ // Some managers follow the store themselves, and redrawing them here would only repeat that.
32335
+ if (!item.renderManager || item.renderManager.FollowsSimulation) {
32336
+ continue;
32337
+ }
32338
+ const drawn = (menuItemIds === null || menuItemIds === void 0 ? void 0 : menuItemIds.length)
32339
+ ? ids
32340
+ : ids.filter(id => this.visualsRegister.GetRego({ entityId: id, menuItemId: item.id }));
32341
+ if (drawn.length) {
32342
+ await item.renderManager.ReRender({ entityIds: drawn, force: true });
32343
+ }
32344
+ }
32345
+ }
30694
32346
  }
30695
32347
  MenuItemManager.Manager = Manager;
30696
32348
  })(exports.MenuItemManager || (exports.MenuItemManager = {}));
@@ -39227,6 +40879,7 @@
39227
40879
  }
39228
40880
  class WidgetInfoView extends exports.Widget.AWidget {
39229
40881
  constructor(params) {
40882
+ var _a;
39230
40883
  super(params);
39231
40884
  this.STYLESHEET_ID = "nextspace-info-view-stylesheet";
39232
40885
  this._generateStyles();
@@ -39236,10 +40889,19 @@
39236
40889
  this._updateInfoView();
39237
40890
  }
39238
40891
  });
40892
+ // The selected Entity is read once per selection, so a change to its simulation reads it again.
40893
+ this._simulationRemoval = (_a = this._manager.Simulation) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => {
40894
+ if (this._lastSelectedId && change.entityIds.includes(this._lastSelectedId)) {
40895
+ this._lastSelectedId = null;
40896
+ this._updateInfoView();
40897
+ }
40898
+ });
39239
40899
  }
39240
40900
  Dispose() {
40901
+ var _a;
39241
40902
  super.Dispose();
39242
40903
  this._updateRemoval();
40904
+ (_a = this._simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
39243
40905
  }
39244
40906
  _generateStyles() {
39245
40907
  if (document.getElementById(this.STYLESHEET_ID)) {
@@ -39701,7 +41363,7 @@
39701
41363
  }
39702
41364
  }
39703
41365
  async _updateInfoView() {
39704
- var _b, _c, _d, _e;
41366
+ var _a, _c, _d, _e, _f;
39705
41367
  const selectedIds = this._manager.VisualsRegister.GetSelected();
39706
41368
  if (selectedIds.length <= 0) {
39707
41369
  this._element.style.display = "none";
@@ -39723,17 +41385,20 @@
39723
41385
  this._summary.innerHTML = "";
39724
41386
  this._imageContainer.style.display = "none";
39725
41387
  const api = this._apiGetters.GetBruceApi();
39726
- const { entity } = await BModels.Entity.Get({
41388
+ // A simulated Entity is shown as simulated: a stand-in as is, a patch over what the API has.
41389
+ const simulation = this._manager.Simulation;
41390
+ let entity = (_a = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(firstId)) !== null && _a !== void 0 ? _a : (await BModels.Entity.Get({
39727
41391
  entityId: firstId,
39728
41392
  api: api,
39729
41393
  migrated: true,
39730
41394
  expandLocation: DISPLAY_ASSEMBLY_INFO
39731
- });
41395
+ })).entity;
41396
+ entity = entity && simulation ? simulation.Resolve(firstId, entity) : entity;
39732
41397
  if (!entity) {
39733
41398
  throw (new Error("Could not get entity."));
39734
41399
  }
39735
41400
  const { entityType } = await BModels.EntityType.Get({
39736
- entityTypeId: (_b = entity.Bruce) === null || _b === void 0 ? void 0 : _b["EntityType.ID"],
41401
+ entityTypeId: (_c = entity.Bruce) === null || _c === void 0 ? void 0 : _c["EntityType.ID"],
39737
41402
  api: api
39738
41403
  });
39739
41404
  let name = BModels.Entity.CalculateName({
@@ -39756,7 +41421,7 @@
39756
41421
  const defaultImage = images.length ? images[0] : null;
39757
41422
  if (defaultImage) {
39758
41423
  this._imageContainer.style.display = "flex";
39759
- const url = (_c = defaultImage.URL) !== null && _c !== void 0 ? _c : BModels.ClientFile.GetUrl({
41424
+ const url = (_d = defaultImage.URL) !== null && _d !== void 0 ? _d : BModels.ClientFile.GetUrl({
39760
41425
  fileId: defaultImage["ClientFile.ID"],
39761
41426
  api: api,
39762
41427
  viaCdn: true
@@ -39769,7 +41434,7 @@
39769
41434
  this._summary.appendChild(detailsTitle);
39770
41435
  // TODO: Selection for what mode instead of always doing "all".
39771
41436
  const attributes = gatherAttributes(EMode.ShowAll, entity, entityType);
39772
- const groups = groupAttributes(attributes, (_e = (_d = entityType.DataSchema) === null || _d === void 0 ? void 0 : _d.Structure) !== null && _e !== void 0 ? _e : []);
41437
+ const groups = groupAttributes(attributes, (_f = (_e = entityType.DataSchema) === null || _e === void 0 ? void 0 : _e.Structure) !== null && _f !== void 0 ? _f : []);
39773
41438
  for (const group of groups) {
39774
41439
  this._generateAttrGroup(entityType, entity, group);
39775
41440
  }
@@ -42363,7 +44028,7 @@
42363
44028
  * @param params
42364
44029
  */
42365
44030
  async function GetLocation(params) {
42366
- var _a, _b, _c, _d;
44031
+ var _a, _b, _c, _d, _e;
42367
44032
  let { api, viewer, samples, visualRegister, minimumAlt, paddingAlt } = params;
42368
44033
  if (!paddingAlt) {
42369
44034
  paddingAlt = 0;
@@ -42911,6 +44576,13 @@
42911
44576
  recordSamples.push(sample);
42912
44577
  }
42913
44578
  }
44579
+ // A simulated Entity is where its blob says, never where the API has it.
44580
+ const simulation = exports.EntitySimulation.Get(viewer);
44581
+ for (const sample of recordSamples) {
44582
+ if (!sample.entity && sample.entityId) {
44583
+ sample.entity = (_e = simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(sample.entityId)) !== null && _e !== void 0 ? _e : sample.entity;
44584
+ }
44585
+ }
42914
44586
  // Bulk requesting Entity records.
42915
44587
  try {
42916
44588
  const toSearchIds = recordSamples.filter(x => !x.entity && x.entityId).map(x => x.entityId);
@@ -42931,7 +44603,8 @@
42931
44603
  for (let i = 0; i < recordSamples.length; i++) {
42932
44604
  const sample = recordSamples[i];
42933
44605
  if (!sample.entity && sample.entityId) {
42934
- sample.entity = entitiesMap.get(sample.entityId);
44606
+ const read = entitiesMap.get(sample.entityId);
44607
+ sample.entity = read && simulation ? simulation.Overlay(read) : read;
42935
44608
  }
42936
44609
  }
42937
44610
  }
@@ -42945,6 +44618,11 @@
42945
44618
  // We have to detect whatever is an Assembly Entity and expand it.
42946
44619
  try {
42947
44620
  const needsExpansion = recordSamples.filter(x => {
44621
+ var _a;
44622
+ // Expanding reads the API's record, which would replace the stand-in or undo the patch.
44623
+ if (simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob((_a = x.entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID)) {
44624
+ return false;
44625
+ }
42948
44626
  const assemblyPos = BModels.Entity.GetValue({
42949
44627
  entity: x.entity,
42950
44628
  path: ["Bruce", "AssemblyPosition"]
@@ -43303,6 +44981,21 @@
43303
44981
  return Cesium.Quaternion.multiply(combined, _assemblyAxisShift, new Cesium.Quaternion());
43304
44982
  }
43305
44983
  EntityUtils.GetAssemblyOrientation = GetAssemblyOrientation;
44984
+ /**
44985
+ * Returns the orientation a part's model is drawn with, for its world matrix within the root's frame.
44986
+ * @param rootFrame the root's frame in the world, eg. from Transforms.headingPitchRollToFixedFrame.
44987
+ * @param world the part's matrix within the root's frame.
44988
+ */
44989
+ function GetAssemblyOrientationInFrame(rootFrame, world) {
44990
+ if (!rootFrame || !world) {
44991
+ return null;
44992
+ }
44993
+ const combined = Cesium.Matrix4.multiply(rootFrame, world, new Cesium.Matrix4());
44994
+ const rotation = Cesium.Matrix4.getRotation(combined, new Cesium.Matrix3());
44995
+ const q = Cesium.Quaternion.fromRotationMatrix(rotation);
44996
+ return Cesium.Quaternion.multiply(q, _assemblyAxisShift, new Cesium.Quaternion());
44997
+ }
44998
+ EntityUtils.GetAssemblyOrientationInFrame = GetAssemblyOrientationInFrame;
43306
44999
  /**
43307
45000
  * Returns the position of the entity.
43308
45001
  * This is an async variant that checks for related assemblies.
@@ -43312,6 +45005,18 @@
43312
45005
  */
43313
45006
  async function GetPosAsync(params) {
43314
45007
  let { viewer, entityId, entity, visualRegister, returnHeightRef, recordHeightRef, api } = params;
45008
+ // A simulated Entity is where its stand-in says, or where its patch puts the API's record.
45009
+ const simulation = exports.EntitySimulation.Get(viewer);
45010
+ entity = entity !== null && entity !== void 0 ? entity : simulation === null || simulation === void 0 ? void 0 : simulation.GetEntity(entityId);
45011
+ if (!entity && entityId && (simulation === null || simulation === void 0 ? void 0 : simulation.HasBlob(entityId))) {
45012
+ try {
45013
+ const read = (await BModels.Entity.Get({ api, entityId, expandLocation: false, migrated: true })).entity;
45014
+ entity = read ? simulation.Overlay(read) : null;
45015
+ }
45016
+ catch (e) {
45017
+ console.error(e);
45018
+ }
45019
+ }
43315
45020
  // We have issues where imported BRZ files have improper expandLocation=true info.
43316
45021
  // As a result we'll double-request the data.
43317
45022
  // If there is a location then we won't bother using expanded information.
@@ -53273,7 +54978,7 @@ void main() {
53273
54978
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
53274
54979
  })(exports.StyleUtils || (exports.StyleUtils = {}));
53275
54980
 
53276
- const VERSION = "7.6.0";
54981
+ const VERSION = "7.6.2";
53277
54982
  /**
53278
54983
  * Updates the environment instance used by bruce-cesium to one specified.
53279
54984
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.