bruce-cesium 7.6.1 → 7.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/bruce-cesium.es5.js +2031 -343
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2024 -340
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +2 -1
  6. package/dist/lib/bruce-cesium.js.map +1 -1
  7. package/dist/lib/rendering/cesium-animated-property.js +63 -107
  8. package/dist/lib/rendering/cesium-animated-property.js.map +1 -1
  9. package/dist/lib/rendering/entity-gatherer.js +11 -3
  10. package/dist/lib/rendering/entity-gatherer.js.map +1 -1
  11. package/dist/lib/rendering/menu-item-manager.js +83 -10
  12. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  13. package/dist/lib/rendering/relation-render-engine.js +24 -5
  14. package/dist/lib/rendering/relation-render-engine.js.map +1 -1
  15. package/dist/lib/rendering/render-managers/common/entity-label-group.js +13 -9
  16. package/dist/lib/rendering/render-managers/common/entity-label-group.js.map +1 -1
  17. package/dist/lib/rendering/render-managers/common/entity-label.js +68 -7
  18. package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
  19. package/dist/lib/rendering/render-managers/common/entity-rerender-maintain-state.js +44 -5
  20. package/dist/lib/rendering/render-managers/common/entity-rerender-maintain-state.js.map +1 -1
  21. package/dist/lib/rendering/render-managers/common/entity-simulation.js +483 -0
  22. package/dist/lib/rendering/render-managers/common/entity-simulation.js.map +1 -0
  23. package/dist/lib/rendering/render-managers/common/historic-trail.js +123 -0
  24. package/dist/lib/rendering/render-managers/common/historic-trail.js.map +1 -0
  25. package/dist/lib/rendering/render-managers/common/historic-utils.js +17 -6
  26. package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -1
  27. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js +3 -2
  28. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js.map +1 -1
  29. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +10 -2
  30. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  31. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js +3 -2
  32. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js.map +1 -1
  33. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +18 -5
  34. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  35. package/dist/lib/rendering/render-managers/entities/entity-render-manager.js +1 -1
  36. package/dist/lib/rendering/render-managers/entities/entity-render-manager.js.map +1 -1
  37. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js +745 -106
  38. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js.map +1 -1
  39. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js +23 -1
  40. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js.map +1 -1
  41. package/dist/lib/rendering/render-managers/other/relations-render-manager.js +23 -1
  42. package/dist/lib/rendering/render-managers/other/relations-render-manager.js.map +1 -1
  43. package/dist/lib/rendering/render-managers/render-manager.js.map +1 -1
  44. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +2 -0
  45. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  46. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +130 -40
  47. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  48. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +3 -0
  49. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  50. package/dist/lib/rendering/tileset-styler.js +1 -1
  51. package/dist/lib/rendering/tileset-styler.js.map +1 -1
  52. package/dist/lib/rendering/visual-register-culler.js +2 -0
  53. package/dist/lib/rendering/visual-register-culler.js.map +1 -1
  54. package/dist/lib/rendering/visuals-register.js +89 -20
  55. package/dist/lib/rendering/visuals-register.js.map +1 -1
  56. package/dist/lib/utils/entity-utils.js +43 -2
  57. package/dist/lib/utils/entity-utils.js.map +1 -1
  58. package/dist/lib/widgets/widget-info-view.js +20 -7
  59. package/dist/lib/widgets/widget-info-view.js.map +1 -1
  60. package/dist/types/bruce-cesium.d.ts +2 -1
  61. package/dist/types/rendering/cesium-animated-property.d.ts +8 -6
  62. package/dist/types/rendering/entity-gatherer.d.ts +6 -1
  63. package/dist/types/rendering/menu-item-manager.d.ts +33 -1
  64. package/dist/types/rendering/relation-render-engine.d.ts +10 -0
  65. package/dist/types/rendering/render-managers/common/entity-label-group.d.ts +3 -2
  66. package/dist/types/rendering/render-managers/common/entity-label.d.ts +4 -0
  67. package/dist/types/rendering/render-managers/common/entity-rerender-maintain-state.d.ts +20 -0
  68. package/dist/types/rendering/render-managers/common/entity-simulation.d.ts +126 -0
  69. package/dist/types/rendering/render-managers/common/historic-trail.d.ts +30 -0
  70. package/dist/types/rendering/render-managers/common/historic-utils.d.ts +4 -0
  71. package/dist/types/rendering/render-managers/entities/entities-datalab-render-manager.d.ts +2 -0
  72. package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +2 -0
  73. package/dist/types/rendering/render-managers/entities/entities-loaded-render-manager.d.ts +2 -0
  74. package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +2 -0
  75. package/dist/types/rendering/render-managers/entities/entity-render-manager.d.ts +2 -0
  76. package/dist/types/rendering/render-managers/other/assembly-render-manager.d.ts +79 -2
  77. package/dist/types/rendering/render-managers/other/legacy-relations-render-manager.d.ts +5 -0
  78. package/dist/types/rendering/render-managers/other/relations-render-manager.d.ts +5 -0
  79. package/dist/types/rendering/render-managers/render-manager.d.ts +4 -0
  80. package/dist/types/rendering/render-managers/tilesets/tileset-arb-render-manager.d.ts +3 -0
  81. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +11 -1
  82. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +3 -0
  83. package/dist/types/rendering/tileset-styler.d.ts +2 -0
  84. package/dist/types/rendering/visuals-register.d.ts +13 -0
  85. package/dist/types/utils/entity-utils.d.ts +6 -0
  86. package/dist/types/widgets/widget-info-view.d.ts +1 -0
  87. package/package.json +2 -2
@@ -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.
@@ -6748,6 +7181,7 @@
6748
7181
  constructor() {
6749
7182
  // Whether a box competes for space comes only from inputs (camera distance, hover, the pointer), never from
6750
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.
6751
7185
  this.entries = new Map();
6752
7186
  this.groups = null;
6753
7187
  // The hovered Entity. Its box is drawn on its own and whole, ahead of every other box.
@@ -6765,7 +7199,7 @@
6765
7199
  Set(id, corners, entityId, inputs) {
6766
7200
  const current = this.entries.get(id);
6767
7201
  // A label rebuilt over the same box keeps its place and how far it has animated.
6768
- 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) {
6769
7203
  Object.assign(current, inputs);
6770
7204
  return;
6771
7205
  }
@@ -6773,17 +7207,41 @@
6773
7207
  return;
6774
7208
  }
6775
7209
  if (corners) {
6776
- 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 });
6777
7211
  }
6778
7212
  else
6779
7213
  this.entries.delete(id);
6780
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) {
6781
7221
  this.groups = null;
6782
7222
  for (const [key, group] of this.outlines) {
6783
7223
  if (group.members.includes(id))
6784
7224
  this.outlines.delete(key);
6785
7225
  }
6786
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
+ }
6787
7245
  /*
6788
7246
  * The box drawn for a label this frame, or undefined when it has none or lost its place.
6789
7247
  */
@@ -6851,6 +7309,7 @@
6851
7309
  */
6852
7310
  Resolve(viewer, pointer) {
6853
7311
  var _a, _b, _c, _d;
7312
+ this.follow();
6854
7313
  this.frames.clear();
6855
7314
  this.hulls.clear();
6856
7315
  this.drawn = [];
@@ -7273,9 +7732,16 @@
7273
7732
  }
7274
7733
  if (this.api) {
7275
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
+ }
7276
7742
  this.rego.name = await BModels.Entity.CalculateNameAsync({
7277
7743
  api: this.api,
7278
- entity: null,
7744
+ entity: simulated,
7279
7745
  entityId: rego.entityId,
7280
7746
  defaultToId: false
7281
7747
  });
@@ -7304,6 +7770,21 @@
7304
7770
  clearTimeout(this.loadTimeout);
7305
7771
  this.loadTimeout = null;
7306
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
+ }
7307
7788
  /*
7308
7789
  * Rebuilds only when configuration changes; state refreshes must not recreate DOM every frame.
7309
7790
  */
@@ -7534,7 +8015,7 @@
7534
8015
  }
7535
8016
  clearTimeout(this.loadTimeout);
7536
8017
  this.loadTimeout = setTimeout(async () => {
7537
- var _a, _b, _c, _d, _e;
8018
+ var _a, _b, _c, _d, _e, _f;
7538
8019
  if (this.disposed || this.counter != counter) {
7539
8020
  return;
7540
8021
  }
@@ -7561,16 +8042,19 @@
7561
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;
7562
8043
  // Read for its attributes, and for a photo when the label has no icon of its own.
7563
8044
  if (attributes || !HasIcon(this.options)) {
7564
- 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;
7565
8048
  try {
7566
- entity = (await BModels.Entity.Get({
8049
+ entity = entity !== null && entity !== void 0 ? entity : (await BModels.Entity.Get({
7567
8050
  api: this.api,
7568
8051
  entityId: this.rego.entityId,
7569
- 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,
7570
8053
  schemaId: this.rego.schema,
7571
8054
  migrated: true,
7572
8055
  expandAttachments: !HasIcon(this.options)
7573
8056
  })).entity;
8057
+ entity = entity && simulation ? simulation.Resolve(this.rego.entityId, entity) : entity;
7574
8058
  }
7575
8059
  // Entity doesn't exist.
7576
8060
  // Likely a graphic with an ID that points to no record.
@@ -7954,11 +8438,17 @@
7954
8438
  // Past the wait a slow record no longer holds the label back; a box that comes later eases in.
7955
8439
  setTimeout(settle, MODEL_BOX_WAIT);
7956
8440
  this.modelBox.load.then(corners => {
8441
+ var _a, _b;
7957
8442
  if (!corners || this.disposed || this.counter !== counter) {
7958
8443
  return;
7959
8444
  }
7960
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);
7961
8448
  const centre = Cesium.Cartesian3.midpoint(corners[0], corners[7], new Cesium.Cartesian3());
8449
+ if (motion) {
8450
+ Cesium.Matrix4.multiplyByPoint(motion, centre, centre);
8451
+ }
7962
8452
  if (Cesium.Cartesian3.distance(centre, found) > Cesium.Cartesian3.distance(corners[0], corners[7]) * 0.75 + 2) {
7963
8453
  return;
7964
8454
  }
@@ -7966,6 +8456,7 @@
7966
8456
  // Let go a little further out than it is taken, so a camera resting at the limit does not flip it.
7967
8457
  let near = false;
7968
8458
  layer.modelBoxes.Set(this.id, corners, rego.entityId, {
8459
+ motion: rego.motion ? () => rego.motion(this.viewer.clock.currentTime) : undefined,
7969
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); },
7970
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)); },
7971
8462
  near: () => {
@@ -8946,9 +9437,9 @@
8946
9437
  * Reads the entities behind items that name one without carrying it, attachments included,
8947
9438
  * so a label's default image needs no second request.
8948
9439
  */
8949
- async function Hydrate(items, api, signal) {
9440
+ async function Hydrate(items, api, signal, simulation) {
8950
9441
  var _a, _b;
8951
- if (!api) {
9442
+ if (!api && !simulation) {
8952
9443
  return items;
8953
9444
  }
8954
9445
  const hydrated = items.slice();
@@ -8959,12 +9450,15 @@
8959
9450
  }
8960
9451
  const chunk = missing.slice(start, start + HYDRATE_AT_ONCE);
8961
9452
  try {
8962
- const { entities } = await BModels.Entity.GetListByIds({
8963
- api, entityIds: chunk.map(at => hydrated[at].entityId), expandAttachments: true, migrated: true
8964
- });
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: [] };
8965
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]; }));
8966
9459
  for (const at of chunk) {
8967
- 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);
8968
9462
  if (entity) {
8969
9463
  // The API leaves Attachment out when there are none; an empty list records that they were read.
8970
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 : [] } } };
@@ -8981,7 +9475,7 @@
8981
9475
  /*
8982
9476
  * Uses the existing DataLab grammar, discarding actions and projections that cannot return Entities.
8983
9477
  */
8984
- async function Loader(source, api, signal, hydrate) {
9478
+ async function Loader(source, api, signal, hydrate, simulation) {
8985
9479
  var _a;
8986
9480
  var _b;
8987
9481
  if (source.type === "provider") {
@@ -8996,7 +9490,7 @@
8996
9490
  return async (offset, limit, signal) => {
8997
9491
  const page = items.slice(offset, offset + limit);
8998
9492
  return {
8999
- items: hydrate ? await Hydrate(page, api, signal) : page,
9493
+ items: hydrate ? await Hydrate(page, api, signal, simulation) : page,
9000
9494
  hasMore: offset + limit < items.length
9001
9495
  };
9002
9496
  };
@@ -9138,7 +9632,7 @@
9138
9632
  const items = new Map();
9139
9633
  let truncated = false;
9140
9634
  try {
9141
- 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));
9142
9636
  let offset = 0;
9143
9637
  for (let pageNumber = 0;; pageNumber++) {
9144
9638
  if (controller.signal.aborted || this.register.Disposed) {
@@ -9960,8 +10454,25 @@
9960
10454
  return `${relation["Related.Entity.ID"]}-${relation["Principal.Entity.ID"]}-${relation["Relation.Type.ID"]}`;
9961
10455
  }
9962
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;
9963
10474
  async function Render(params) {
9964
- const { apiGetter, viewer, visualRegister, menuItemId, relations } = params;
10475
+ const { apiGetter, viewer, visualRegister, menuItemId, relations, simulation } = params;
9965
10476
  const api = apiGetter.getApi(apiGetter.accountId);
9966
10477
  const rendered = {};
9967
10478
  let neededEntityIdsIds = [];
@@ -9979,13 +10490,15 @@
9979
10490
  }
9980
10491
  // Turn the set unique.
9981
10492
  neededEntityIdsIds = neededEntityIdsIds.filter((v, i, a) => a.indexOf(v) === i);
9982
- // Get all entities in one go.
9983
- let entities = (await BModels.Entity.GetListByIds({
9984
- 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,
9985
10498
  api: apiGetter.getApi(),
9986
10499
  migrated: true,
9987
10500
  maxSearchTimeSec: 60 * 2
9988
- })).entities;
10501
+ })).entities.map(entity => simulation ? simulation.Overlay(entity) : entity) : []).concat(simulated);
9989
10502
  // Create a map for quick reference.
9990
10503
  const entitiesMap = {};
9991
10504
  for (let i = 0; i < entities.length; i++) {
@@ -10479,6 +10992,8 @@
10479
10992
  rego.overrideShow != null ||
10480
10993
  // Being dragged around right now, so its extent is changing faster than we could measure it.
10481
10994
  rego.editing == true ||
10995
+ // Placed by the clock every frame, so no measurement of where it is stays true.
10996
+ rego.moving == true ||
10482
10997
  // Part of a collection we did not create, eg. a geojson data source, which may have its own rules for
10483
10998
  // what is in the scene. Our own per-menu-item data sources are fair game.
10484
10999
  exports.VisualsRegister.IsForeignCollection(rego) ||
@@ -11313,6 +11828,7 @@
11313
11828
  }
11314
11829
  get OnLabelGroupsChanged() { return this.labelGroupsChanged; }
11315
11830
  constructor(params) {
11831
+ var _a, _b;
11316
11832
  this.id = BModels.ObjectUtils.UId();
11317
11833
  this.disposed = false;
11318
11834
  this.rego = {};
@@ -11352,6 +11868,8 @@
11352
11868
  this.menuItemSources = new Map();
11353
11869
  // Set when the data source order no longer matches the zIndexes seen, so it is re-applied once.
11354
11870
  this.sourceOrderDirty = false;
11871
+ // Stops following what is simulated, so labels can read it again when it changes.
11872
+ this.simulationRemoval = null;
11355
11873
  this.labelGroupsChanged = new BModels.BruceEvent();
11356
11874
  this.hoverRingState = {};
11357
11875
  this.retiringHoverLabels = new Map();
@@ -11386,6 +11904,33 @@
11386
11904
  this.cameraCullerDispose = exports.VisualRegisterCuller.Monitor({
11387
11905
  register: this
11388
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
+ }
11389
11934
  }
11390
11935
  /**
11391
11936
  * Returns the collection a menu item's visuals belong in, creating its data source on first use.
@@ -12288,7 +12833,9 @@
12288
12833
  (_b = this.viewer.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
12289
12834
  }
12290
12835
  Dispose() {
12291
- 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;
12292
12839
  exports.CesiumEntityStyler.SetDimming(this.viewer, this.id, false);
12293
12840
  this.ClearLabelHover();
12294
12841
  clearTimeout(this.hoverRingReset);
@@ -12298,14 +12845,14 @@
12298
12845
  exports.EntityLabel.Detatch({ label });
12299
12846
  }
12300
12847
  this.retiringHoverLabels.clear();
12301
- (_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.Dispose();
12848
+ (_b = this.labelGroups) === null || _b === void 0 ? void 0 : _b.Dispose();
12302
12849
  this.labelGroupsChanged.Clear();
12303
12850
  for (const regos of Object.values(this.rego)) {
12304
12851
  for (const rego of regos)
12305
12852
  exports.EntityLabel.Detatch({ rego, force: true });
12306
12853
  }
12307
12854
  exports.EntityLabel.SetContainer(this.viewer);
12308
- (_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, {
12309
12856
  uncull: false
12310
12857
  });
12311
12858
  this.cameraCullerDispose = null;
@@ -12321,7 +12868,7 @@
12321
12868
  this.disposed = true;
12322
12869
  for (const record of Array.from(this.menuItemSources.values())) {
12323
12870
  try {
12324
- (_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);
12325
12872
  }
12326
12873
  catch (e) {
12327
12874
  console.error(e);
@@ -12330,7 +12877,7 @@
12330
12877
  this.menuItemSources.clear();
12331
12878
  // Hand every tileset's show flag back to its owner, holding whatever the owner last asked for.
12332
12879
  for (const rego of Array.from(this.tilesets.values())) {
12333
- (_e = rego.gate) === null || _e === void 0 ? void 0 : _e.Release();
12880
+ (_f = rego.gate) === null || _f === void 0 ? void 0 : _f.Release();
12334
12881
  }
12335
12882
  this.tilesets.clear();
12336
12883
  }
@@ -12504,21 +13051,7 @@
12504
13051
  changed = true;
12505
13052
  }
12506
13053
  if (editing) {
12507
- const parts = exports.EntityUtils.GatherEntity({
12508
- entity: rego.visual
12509
- });
12510
- for (let k = 0; k < parts.length; k++) {
12511
- const part = parts[k];
12512
- if (!(part instanceof Cesium.Entity)) {
12513
- continue;
12514
- }
12515
- const removalPending = exports.VisualRegisterCuller.CancelQueuedSceneChange(part);
12516
- const wasCulled = part[exports.VisualRegisterCuller.VISUAL_CULL_KEY] == true || removalPending;
12517
- delete part[exports.VisualRegisterCuller.VISUAL_CULL_KEY];
12518
- if (wasCulled && !IsCEntityInScene(this.viewer, part)) {
12519
- AddCEntityToScene(this.viewer, part, rego.collection);
12520
- }
12521
- }
13054
+ this.restoreCulled(rego);
12522
13055
  }
12523
13056
  else {
12524
13057
  exports.VisualRegisterCuller.InvalidateBounds(rego.visual);
@@ -12530,6 +13063,56 @@
12530
13063
  (_b = (_a = this.viewer) === null || _a === void 0 ? void 0 : _a.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
12531
13064
  }
12532
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
+ }
12533
13116
  SetSelected(params) {
12534
13117
  var _a;
12535
13118
  let { entityIds, selected, refreshIfSelected, requestRender, menuItemId, source } = params;
@@ -14022,12 +14605,41 @@
14022
14605
  * Tracks per-entity rerender state for unsaved blob maintenance and stale API response suppression.
14023
14606
  */
14024
14607
  class EntityReRenderMaintainState {
14025
- constructor() {
14608
+ constructor(simulation, pointInTime) {
14609
+ this.simulation = simulation;
14610
+ this.pointInTime = pointInTime;
14026
14611
  this.maintainedEntitiesById = {};
14027
14612
  this.apiRevisionByEntityId = {};
14028
14613
  this.skipApiRunByEntityId = {};
14029
14614
  this.checkRunId = 0;
14030
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
+ }
14031
14643
  setMaintainedEntities(entities) {
14032
14644
  var _a;
14033
14645
  for (let i = 0; i < entities.length; i++) {
@@ -14049,15 +14661,21 @@
14049
14661
  }
14050
14662
  }
14051
14663
  getMaintainedEntities(entityIds) {
14664
+ var _a;
14052
14665
  const entities = [];
14053
14666
  for (let i = 0; i < entityIds.length; i++) {
14054
14667
  const id = entityIds[i];
14055
14668
  if (!id) {
14056
14669
  continue;
14057
14670
  }
14058
- const entity = this.maintainedEntitiesById[id];
14059
- if (entity) {
14060
- 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]);
14061
14679
  }
14062
14680
  }
14063
14681
  return entities;
@@ -14114,7 +14732,7 @@
14114
14732
  }
14115
14733
  filterCurrentApiEntities(params) {
14116
14734
  const { entities, revisions, runId } = params;
14117
- return entities.filter((entity) => {
14735
+ const current = entities.filter((entity) => {
14118
14736
  var _a, _b, _c;
14119
14737
  const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
14120
14738
  if (!id) {
@@ -14123,8 +14741,12 @@
14123
14741
  if (runId != null && this.shouldSkipInRun(id, runId)) {
14124
14742
  return false;
14125
14743
  }
14744
+ if (this.isSimulated(id)) {
14745
+ return false;
14746
+ }
14126
14747
  return ((_b = this.apiRevisionByEntityId[id]) !== null && _b !== void 0 ? _b : 0) === ((_c = revisions[id]) !== null && _c !== void 0 ? _c : 0);
14127
14748
  });
14749
+ return this.atPoint(this.simulation && !this.simulation.IsEmpty ? current.map((entity) => this.simulation.Overlay(entity)) : current);
14128
14750
  }
14129
14751
  }
14130
14752
 
@@ -15560,7 +16182,7 @@
15560
16182
  this.cache = new HistoricRangeCache();
15561
16183
  }
15562
16184
  get Records() {
15563
- return this.cache.GetRecords();
16185
+ return this.withSimulated(this.cache.GetRecords());
15564
16186
  }
15565
16187
  Clear() {
15566
16188
  this.cache.Clear();
@@ -15584,8 +16206,9 @@
15584
16206
  */
15585
16207
  async Gather(entities) {
15586
16208
  var _a;
15587
- if (this.disposed || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
15588
- 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) };
15589
16212
  }
15590
16213
  const { viewer } = this.params;
15591
16214
  const windowStart = new Date(viewer.clock.startTime.toString()).getTime();
@@ -15605,7 +16228,7 @@
15605
16228
  if (plan.coarse && !this.cache.CoversCoarse(plan.coarse.start, plan.coarse.stop)) {
15606
16229
  const records = await this.request(plan.coarse.start, plan.coarse.stop, entities, plan.coarse.sample);
15607
16230
  if (this.disposed) {
15608
- return { fetched, recordsByIds: this.cache.GetRecords() };
16231
+ return { fetched, recordsByIds: this.withSimulated(this.cache.GetRecords(), entityIds) };
15609
16232
  }
15610
16233
  this.cache.Absorb(plan.coarse.start, this.holdTo(plan.coarse.stop, liveEdge, liveTail), records, true);
15611
16234
  fetched = true;
@@ -15640,7 +16263,17 @@
15640
16263
  }
15641
16264
  }
15642
16265
  }
15643
- 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);
15644
16277
  }
15645
16278
  /*
15646
16279
  * Returns how much of a range that was just read may be marked as held.
@@ -15675,7 +16308,7 @@
15675
16308
  }).then((res) => {
15676
16309
  const records = res.recordsByIds || {};
15677
16310
  if (!this.disposed && this.params.onRecords) {
15678
- this.params.onRecords(records);
16311
+ this.params.onRecords(this.withSimulated(records, entityIds));
15679
16312
  }
15680
16313
  return records;
15681
16314
  }).finally(() => {
@@ -15747,7 +16380,6 @@
15747
16380
  this.renderQueue = [];
15748
16381
  this.renderQueueInterval = null;
15749
16382
  this.sources = [];
15750
- this.reRenderState = new EntityReRenderMaintainState();
15751
16383
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
15752
16384
  // Many things are not supported when this is enabled.
15753
16385
  this.useGeojson = false;
@@ -15767,6 +16399,14 @@
15767
16399
  */
15768
16400
  this.renderAsIndividualsChain = Promise.resolve();
15769
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
+ });
15770
16410
  this.viewer = viewer;
15771
16411
  this.sharedGetters = sharedGetters;
15772
16412
  this.monitor = monitor;
@@ -15780,7 +16420,8 @@
15780
16420
  getApi: () => this.apiGetter.getApi(),
15781
16421
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
15782
16422
  // A second either side, to take in records sitting exactly on the boundary.
15783
- paddingMs: 1000
16423
+ paddingMs: 1000,
16424
+ simulation: params.simulation
15784
16425
  });
15785
16426
  this.useGeojson = item.renderAsGeojson == true;
15786
16427
  if (item.enableClustering) {
@@ -15961,8 +16602,9 @@
15961
16602
  return;
15962
16603
  }
15963
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.
15964
16606
  if (isTagItem) {
15965
- this.distributeForRender(data.entities.filter((entity) => {
16607
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities.filter((entity) => {
15966
16608
  let entityTags = entity.Bruce["Layer.ID"];
15967
16609
  if (!entityTags) {
15968
16610
  entityTags = [];
@@ -15970,10 +16612,10 @@
15970
16612
  return entityTags.some((tag) => {
15971
16613
  return tagsToRender.indexOf(tag) !== -1;
15972
16614
  });
15973
- }));
16615
+ })));
15974
16616
  }
15975
16617
  else {
15976
- this.distributeForRender(data.entities);
16618
+ this.distributeForRender(this.reRenderState.withSimulated(data.entities));
15977
16619
  }
15978
16620
  });
15979
16621
  // Cesium itself handles single-visibility ranges very well.
@@ -16013,6 +16655,10 @@
16013
16655
  // Remove regos directly.
16014
16656
  if (event.action === "D") {
16015
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
+ }
16016
16662
  this.visualsManager.RemoveRegos({
16017
16663
  entityId,
16018
16664
  menuItemId: this.item.id,
@@ -17136,7 +17782,6 @@
17136
17782
  // Highly experimental flag to try improve rendering large sets of polygons and polylines.
17137
17783
  // Many things are not supported when this is enabled.
17138
17784
  this.useGeojson = false;
17139
- this.reRenderState = new EntityReRenderMaintainState();
17140
17785
  /**
17141
17786
  * Our default path.
17142
17787
  * We render each entity individually.
@@ -17146,6 +17791,7 @@
17146
17791
  */
17147
17792
  this.renderAsIndividualsChain = Promise.resolve();
17148
17793
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
17794
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
17149
17795
  this.viewer = viewer;
17150
17796
  this.apiGetter = apiGetter;
17151
17797
  this.monitor = monitor;
@@ -17268,7 +17914,8 @@
17268
17914
  continue;
17269
17915
  }
17270
17916
  }
17271
- 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));
17272
17919
  });
17273
17920
  this.getter.Start();
17274
17921
  }
@@ -17910,8 +18557,15 @@
17910
18557
  this.renderQueueActive = false;
17911
18558
  this.renderQueue = [];
17912
18559
  this.renderQueueForceFlags = [];
17913
- this.reRenderState = new EntityReRenderMaintainState();
17914
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
+ });
17915
18569
  this.sharedHistoric = params.sharedHistoric;
17916
18570
  this.viewer = viewer;
17917
18571
  this.apiGetter = apiGetter;
@@ -17924,7 +18578,8 @@
17924
18578
  getApi: () => this.apiGetter.getApi(),
17925
18579
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
17926
18580
  // Padding either side of a requested range, for clock desync between us and the server.
17927
- paddingMs: 15000
18581
+ paddingMs: 15000,
18582
+ simulation: params.simulation
17928
18583
  });
17929
18584
  if (this.item.enableClustering) {
17930
18585
  this.clustering = new PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
@@ -18382,9 +19037,9 @@
18382
19037
  this.getter = null;
18383
19038
  this.getterSub = null;
18384
19039
  this.disposed = false;
18385
- this.reRenderState = new EntityReRenderMaintainState();
18386
19040
  this.renderEntityChain = Promise.resolve();
18387
19041
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
19042
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
18388
19043
  this.viewer = viewer;
18389
19044
  this.apiGetter = apiGetter;
18390
19045
  this.monitor = monitor;
@@ -18658,6 +19313,122 @@
18658
19313
  ];
18659
19314
  })(CodeLogicExceptions || (CodeLogicExceptions = {}));
18660
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
+
18661
19432
  class EntityGatherer {
18662
19433
  get OnQueueProgress() {
18663
19434
  if (!this._onQueueProgress) {
@@ -18665,7 +19436,11 @@
18665
19436
  }
18666
19437
  return this._onQueueProgress;
18667
19438
  }
18668
- 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;
18669
19444
  this.disposed = false;
18670
19445
  this.expandSources = false;
18671
19446
  this.historic = null;
@@ -19242,15 +20017,19 @@
19242
20017
  }
19243
20018
  }
19244
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;
19245
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.
19246
20025
  const override = this.entityDataOverrides.get(entityId);
19247
20026
  if (override) {
19248
20027
  resolvedEntities.push(override);
19249
20028
  continue;
19250
20029
  }
19251
- const found = foundById.get(entityId);
20030
+ const found = this.simulation ? this.simulation.Resolve(entityId, foundById.get(entityId)) : foundById.get(entityId);
19252
20031
  if (found) {
19253
- resolvedEntities.push(found);
20032
+ resolvedEntities.push(atPoint(found));
19254
20033
  continue;
19255
20034
  }
19256
20035
  resolvedEntities.push(this.createFallbackEntity(entityId));
@@ -19614,7 +20393,7 @@
19614
20393
  this.styleTilesetFeatureFullData(feature, entity, eTags);
19615
20394
  }
19616
20395
  }
19617
- });
20396
+ }, params.simulation);
19618
20397
  // Only Entities Sets bake coordinates into their features, so for any other source this
19619
20398
  // resolves nothing and the gather stays in insertion order.
19620
20399
  this.entityGatherer.SetViewMonitor(this.monitor);
@@ -21138,6 +21917,9 @@
21138
21917
  return this.modelTreeUpdate;
21139
21918
  }
21140
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;
21141
21923
  this.disposed = false;
21142
21924
  this.modelSpace = false;
21143
21925
  this.cTileset = null;
@@ -21175,7 +21957,7 @@
21175
21957
  this.historicAnimation = null;
21176
21958
  this.historicGatherer = null;
21177
21959
  // The trail through the root's own movement, which describes the whole assembly.
21178
- this.trackEntity = null;
21960
+ this.trail = null;
21179
21961
  this.historicPossesLoadingProm = null;
21180
21962
  // Queue of loaded in features that we haven't processed yet.
21181
21963
  this.featureQueue = [];
@@ -21189,6 +21971,7 @@
21189
21971
  this.featurePropCache = new Map();
21190
21972
  this.featurePropsChecked = 0;
21191
21973
  const { viewer, register: visualsManager, getters, item, modelSpace, initQueue } = params;
21974
+ this.simulation = params.simulation;
21192
21975
  this.viewer = viewer;
21193
21976
  this.getters = getters;
21194
21977
  this.item = item;
@@ -21353,7 +22136,7 @@
21353
22136
  historicPoint: this.viewer.clock.currentTime.toString(),
21354
22137
  api: api
21355
22138
  });
21356
- coords = coordsFromHistoricEntity(root);
22139
+ coords = coordsFromHistoricEntity(this.atClock(root));
21357
22140
  }
21358
22141
  catch (e) {
21359
22142
  console.error(e);
@@ -21394,6 +22177,10 @@
21394
22177
  this.doDispose();
21395
22178
  return;
21396
22179
  }
22180
+ this.restCoords = coords;
22181
+ if (this.simulatedRootCoords()) {
22182
+ this.applyCoords(tileset, this.simulatedRootCoords());
22183
+ }
21397
22184
  this.viewer.scene.requestRender();
21398
22185
  exports.TilesetRenderEngine.OnTilesetReady(this.cTileset).then(() => {
21399
22186
  var _a, _b, _c;
@@ -21413,6 +22200,7 @@
21413
22200
  }
21414
22201
  this.onCTilesetLoad();
21415
22202
  this.styler.Init({
22203
+ simulation: this.simulation,
21416
22204
  viewer: this.viewer,
21417
22205
  // Default API instead of the account one.
21418
22206
  // Records are driven by the account loading it instead of the source account.
@@ -21428,6 +22216,7 @@
21428
22216
  });
21429
22217
  this.viewer.scene.requestRender();
21430
22218
  // Monitor scene time changes and update the root Entity.
22219
+ this.followSimulation(tileset);
21431
22220
  this.viewerDateTimeSub(tileset);
21432
22221
  }
21433
22222
  catch (e) {
@@ -21483,7 +22272,7 @@
21483
22272
  */
21484
22273
  applyRootTrack() {
21485
22274
  var _a;
21486
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
22275
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
21487
22276
  return;
21488
22277
  }
21489
22278
  const trackStyle = exports.EntityRenderEngine.GetTrackStyle({
@@ -21493,26 +22282,14 @@
21493
22282
  if (!trackStyle) {
21494
22283
  return;
21495
22284
  }
21496
- this.trackEntity = this.viewer.entities.add({
21497
- id: BModels.ObjectUtils.UId(15),
21498
- polyline: {
21499
- 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),
21500
- width: trackStyle.width,
21501
- material: trackStyle.color,
21502
- clampToGround: exports.EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
21503
- == Cesium.HeightReference.CLAMP_TO_GROUND,
21504
- zIndex: 0,
21505
- classificationType: Cesium.ClassificationType.BOTH
21506
- }
21507
- });
21508
- this.trackEntity._noEdit = true;
21509
- this.trackEntity._noSelect = true;
21510
- exports.CesiumEntityStyler.SetDefaultColor({
21511
- color: trackStyle.color,
21512
- 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({
21513
22287
  viewer: this.viewer,
21514
- override: true,
21515
- 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 : []; }
21516
22293
  });
21517
22294
  }
21518
22295
  /**
@@ -21565,7 +22342,7 @@
21565
22342
  }
21566
22343
  // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
21567
22344
  PrepareHandoff() {
21568
- var _a, _b, _c, _d;
22345
+ var _a, _b, _c, _d, _e;
21569
22346
  if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
21570
22347
  return null;
21571
22348
  }
@@ -21583,9 +22360,11 @@
21583
22360
  this.handoffSourceMenuItemId = null;
21584
22361
  // Not transferred, the receiving manager sets up its own fresh copy if needed.
21585
22362
  this.viewerDateTimeDispose();
21586
- (_b = this.styler) === null || _b === void 0 ? void 0 : _b.Dispose();
21587
- (_c = this.tileLoadRemoval) === null || _c === void 0 ? void 0 : _c.call(this);
21588
- (_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);
21589
22368
  this.tileLoadRemoval = null;
21590
22369
  this.tileUnloadRemoval = null;
21591
22370
  const payload = {
@@ -21606,7 +22385,7 @@
21606
22385
  // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
21607
22386
  // Then re-parents every already-registered rego to this menu item and re-runs styling.
21608
22387
  AdoptHandoff(payload) {
21609
- var _a, _b, _c, _d;
22388
+ var _a, _b, _c, _d, _e;
21610
22389
  this.cTileset = payload.cTileset;
21611
22390
  this.tileset = payload.tileset;
21612
22391
  this.rootId = payload.rootId;
@@ -21637,6 +22416,7 @@
21637
22416
  // Same visuals, same registrations so just re-keyed onto this menu item.
21638
22417
  this.onCTilesetLoad();
21639
22418
  this.styler.Init({
22419
+ simulation: this.simulation,
21640
22420
  viewer: this.viewer,
21641
22421
  api: this.getters.GetBruceApi(),
21642
22422
  cTileset: this.cTileset,
@@ -21670,7 +22450,9 @@
21670
22450
  }
21671
22451
  });
21672
22452
  this.viewer.scene.requestRender();
21673
- 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);
21674
22456
  this.viewerDateTimeSub(this.tileset);
21675
22457
  }
21676
22458
  }
@@ -22258,7 +23040,7 @@
22258
23040
  this.doDispose();
22259
23041
  }
22260
23042
  doDispose() {
22261
- var _a, _b, _c;
23043
+ var _a, _b, _c, _d;
22262
23044
  if (this.featureQueueInterval) {
22263
23045
  clearInterval(this.featureQueueInterval);
22264
23046
  this.featureQueueInterval = null;
@@ -22293,6 +23075,8 @@
22293
23075
  this.handoffSourceMenuItemId = null;
22294
23076
  }
22295
23077
  this.viewerDateTimeDispose();
23078
+ (_d = this.simulationRemoval) === null || _d === void 0 ? void 0 : _d.call(this);
23079
+ this.simulationRemoval = null;
22296
23080
  }
22297
23081
  async ReRender(params) {
22298
23082
  let { entityIds, force, entities, maintain } = params;
@@ -22318,8 +23102,11 @@
22318
23102
  * @param tileset
22319
23103
  */
22320
23104
  viewerDateTimeSub(tileset) {
22321
- var _a;
22322
- 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) {
22323
23110
  return;
22324
23111
  }
22325
23112
  const accountId = this.accountIdForRecords();
@@ -22348,6 +23135,8 @@
22348
23135
  // Time padding in milliseconds (15 seconds).
22349
23136
  // Helps account for desync between client and server.
22350
23137
  paddingMs: 15000,
23138
+ simulation: this.simulation,
23139
+ simulatedOnly: !historic,
22351
23140
  onRecords: (recordsByIds) => {
22352
23141
  var _a;
22353
23142
  const records = recordsByIds[this.rootId];
@@ -22375,6 +23164,7 @@
22375
23164
  */
22376
23165
  const getInitialPosses = async () => {
22377
23166
  this.historicPossesLoadingProm = new Promise(async (res) => {
23167
+ var _a;
22378
23168
  try {
22379
23169
  if (this.disposed) {
22380
23170
  res(false);
@@ -22387,11 +23177,16 @@
22387
23177
  return;
22388
23178
  }
22389
23179
  // Also request the current position in case its not in the range.
22390
- 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({
22391
23186
  entityId: this.rootId,
22392
23187
  historicPoint: this.viewer.clock.currentTime.toString(),
22393
23188
  api: api
22394
- });
23189
+ })).entity);
22395
23190
  if (this.disposed || !entity) {
22396
23191
  res(false);
22397
23192
  return;
@@ -22471,14 +23266,15 @@
22471
23266
  }
22472
23267
  });
22473
23268
  this.viewerDateTimeChangeRemoval = () => {
23269
+ var _a, _b;
22474
23270
  if (this.historicAnimation) {
22475
23271
  this.historicAnimation.stop();
22476
23272
  this.historicAnimation = null;
22477
23273
  }
22478
- if (this.trackEntity) {
22479
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
22480
- this.trackEntity = null;
22481
- }
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;
22482
23278
  if (clockTickRemoval) {
22483
23279
  clockTickRemoval();
22484
23280
  clockTickRemoval = null;
@@ -22486,10 +23282,84 @@
22486
23282
  };
22487
23283
  });
22488
23284
  }
22489
- 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) {
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) {
22490
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;
22491
23358
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
22492
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;
22493
23363
  }
22494
23364
  /**
22495
23365
  * Updates the Tileset with latest coordinates.
@@ -23630,11 +24500,11 @@
23630
24500
  this.viewMonitorRemoval = null;
23631
24501
  this.renderQueue = [];
23632
24502
  this.renderQueueInterval = null;
23633
- this.reRenderState = new EntityReRenderMaintainState();
23634
24503
  this.zoomControl = [];
23635
24504
  this.queryLoadId = 0;
23636
24505
  this.renderDataLabEntitiesChain = Promise.resolve();
23637
24506
  const { viewer, item, apiGetter, monitor, visualsRegister } = params;
24507
+ this.reRenderState = new EntityReRenderMaintainState(params.simulation);
23638
24508
  this.viewer = viewer;
23639
24509
  this.item = item;
23640
24510
  this.apiGetter = apiGetter;
@@ -23731,7 +24601,8 @@
23731
24601
  if (!((_a = data === null || data === void 0 ? void 0 : data.entities) === null || _a === void 0 ? void 0 : _a.length)) {
23732
24602
  return;
23733
24603
  }
23734
- 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));
23735
24606
  });
23736
24607
  }
23737
24608
  unsetGetter() {
@@ -25478,6 +26349,7 @@
25478
26349
  this.featurePropCache = new Map();
25479
26350
  this.featurePropsChecked = 0;
25480
26351
  const { viewer, register: visualsManager, getters: apiGetter, item, initQueue, monitor } = params;
26352
+ this.simulation = params.simulation;
25481
26353
  this.monitor = monitor;
25482
26354
  this.viewer = viewer;
25483
26355
  this.getters = apiGetter;
@@ -25578,6 +26450,7 @@
25578
26450
  if (Manager.NeedsStyler(this.item)) {
25579
26451
  const api = this.getters.GetBruceApi();
25580
26452
  this.styler.Init({
26453
+ simulation: this.simulation,
25581
26454
  viewer: this.viewer,
25582
26455
  api: api,
25583
26456
  cTileset: this.cTileset,
@@ -25935,6 +26808,7 @@
25935
26808
  const needsStyler = Manager.NeedsStyler(this.item);
25936
26809
  if (needsStyler) {
25937
26810
  this.styler.Init({
26811
+ simulation: this.simulation,
25938
26812
  viewer: this.viewer,
25939
26813
  api: this.getters.GetBruceApi(),
25940
26814
  cTileset: this.cTileset,
@@ -26619,6 +27493,7 @@
26619
27493
  // LCC object if we're using XGrids to render LCC data.
26620
27494
  this.lccObject = null;
26621
27495
  this.viewer = params.viewer;
27496
+ this.simulation = params.simulation;
26622
27497
  this.getters = params.getters;
26623
27498
  this.visualsManager = params.register;
26624
27499
  // We modify the object due to a lot of backwards compatibility.
@@ -26853,6 +27728,7 @@
26853
27728
  this.item.Type == BModels.MenuItem.EType.IonTileset) {
26854
27729
  const api = this.getters.GetBruceApi();
26855
27730
  this.styler.Init({
27731
+ simulation: this.simulation,
26856
27732
  viewer: this.viewer,
26857
27733
  api: api,
26858
27734
  cTileset: this.cTileset,
@@ -27193,14 +28069,32 @@
27193
28069
  }
27194
28070
  ;
27195
28071
  constructor(params) {
28072
+ var _a, _b;
27196
28073
  // If true, this menu item is disposed and should not be used.
27197
28074
  // Any rendering will be ignored.
27198
28075
  this.disposed = false;
28076
+ this.simulationRemoval = null;
28077
+ this.FollowsSimulation = true;
27199
28078
  const { apiGetter, item, register, viewer } = params;
27200
28079
  this.apiGetter = apiGetter;
27201
28080
  this.item = item;
27202
28081
  this.register = register;
27203
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;
27204
28098
  }
27205
28099
  /**
27206
28100
  * Starts rendering the menu item.
@@ -27269,10 +28163,13 @@
27269
28163
  return items;
27270
28164
  }
27271
28165
  Dispose() {
28166
+ var _a;
27272
28167
  if (this.disposed) {
27273
28168
  return;
27274
28169
  }
27275
28170
  this.disposed = true;
28171
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28172
+ this.simulationRemoval = null;
27276
28173
  this.register.RemoveRegos({
27277
28174
  menuItemId: this.item.id
27278
28175
  });
@@ -27511,7 +28408,8 @@
27511
28408
  menuItemId: this.item.id,
27512
28409
  relations: relations,
27513
28410
  viewer: this.viewer,
27514
- visualRegister: this.register
28411
+ visualRegister: this.register,
28412
+ simulation: this.simulation
27515
28413
  });
27516
28414
  if (this.disposed) {
27517
28415
  this.register.RemoveRegos({
@@ -27793,9 +28691,12 @@
27793
28691
  }
27794
28692
  ;
27795
28693
  constructor(params) {
28694
+ var _a, _b;
27796
28695
  // If true, this menu item is disposed and should not be used.
27797
28696
  // Any rendering will be ignored.
27798
28697
  this.disposed = false;
28698
+ this.simulationRemoval = null;
28699
+ this.FollowsSimulation = true;
27799
28700
  // Record of the currently drawn entity IDs.
27800
28701
  // This is to avoid re-queueing already drawn entities when Init is called multiple times.
27801
28702
  this.drawnIds = [];
@@ -27804,6 +28705,21 @@
27804
28705
  this.item = item;
27805
28706
  this.register = register;
27806
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;
27807
28723
  }
27808
28724
  /**
27809
28725
  * Starts rendering the menu item.
@@ -27846,10 +28762,13 @@
27846
28762
  this.drawnIds = [].concat(entityIds);
27847
28763
  }
27848
28764
  Dispose() {
28765
+ var _a;
27849
28766
  if (this.disposed) {
27850
28767
  return;
27851
28768
  }
27852
28769
  this.disposed = true;
28770
+ (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
28771
+ this.simulationRemoval = null;
27853
28772
  this.register.RemoveRegos({
27854
28773
  menuItemId: this.item.id
27855
28774
  });
@@ -28010,7 +28929,8 @@
28010
28929
  menuItemId: this.item.id,
28011
28930
  relations: relations,
28012
28931
  viewer: this.viewer,
28013
- visualRegister: this.register
28932
+ visualRegister: this.register,
28933
+ simulation: this.simulation
28014
28934
  });
28015
28935
  if (this.disposed) {
28016
28936
  this.register.RemoveRegos({
@@ -29235,6 +30155,55 @@
29235
30155
  get ModelSpace() {
29236
30156
  return this.modelSpace;
29237
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
+ }
29238
30207
  constructor(params) {
29239
30208
  this.disposed = false;
29240
30209
  // Cache of the hierarchy so that our scene-tree can reference it.
@@ -29244,19 +30213,41 @@
29244
30213
  this.hierarchyNodeByEntityId = null;
29245
30214
  this.modelSpace = false;
29246
30215
  this.rootId = null;
29247
- // Where the root sits now, and which way it faces.
29248
- // 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.
29249
30217
  this.rootPos3d = null;
29250
30218
  this.rootHeading = 0;
29251
- // 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.
29252
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();
29253
30236
  // The trail through the root's own movement, which describes the whole Assembly.
29254
- 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;
29255
30245
  // Callback to dispose the link between scene time and the root's historic position.
29256
30246
  this.viewerDateTimeChangeRemoval = null;
29257
30247
  this.historicGatherer = null;
29258
30248
  this.historicAnimation = null;
29259
30249
  const { viewer, register: visualsManager, getters, item } = params;
30250
+ this.simulation = params.simulation;
29260
30251
  this.viewer = viewer;
29261
30252
  this.getters = getters;
29262
30253
  this.item = item;
@@ -29280,10 +30271,10 @@
29280
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"]);
29281
30272
  }
29282
30273
  /*
29283
- * 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.
29284
30275
  */
29285
- static rootFrame(rootPos3d, headingDegrees) {
29286
- 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));
29287
30278
  return Cesium.Transforms.headingPitchRollToFixedFrame(rootPos3d, hpr);
29288
30279
  }
29289
30280
  /**
@@ -29306,28 +30297,553 @@
29306
30297
  return Cesium.Matrix4.multiplyByPoint(Manager.rootFrame(rootPos3d, headingDegrees), offset, new Cesium.Cartesian3());
29307
30298
  }
29308
30299
  /**
29309
- * 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.
29310
30301
  * @param params
29311
30302
  */
29312
30303
  ApplyRootPosition(params) {
29313
- var _a, _b;
30304
+ var _a, _b, _c, _d, _e, _f;
29314
30305
  if (this.disposed || !(params === null || params === void 0 ? void 0 : params.pos3d)) {
29315
30306
  return;
29316
30307
  }
29317
30308
  this.rootPos3d = params.pos3d;
29318
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;
29319
30320
  for (const child of this.children.values()) {
29320
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, child.offset);
29321
- 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) {
29322
30579
  continue;
29323
30580
  }
29324
- child.cEntity.position = new Cesium.ConstantPositionProperty(pos3d);
29325
- const orient = exports.EntityUtils.GetAssemblyOrientation(child.entity, pos3d);
29326
- if (orient) {
29327
- child.cEntity.orientation = new Cesium.ConstantProperty(orient);
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)) {
30614
+ continue;
29328
30615
  }
30616
+ byTime.set(timeMs, key);
29329
30617
  }
29330
- this.viewer.scene.requestRender();
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);
30761
+ }
30762
+ }
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;
29331
30847
  }
29332
30848
  Init() {
29333
30849
  this.renderPriority = this.item.renderPriority;
@@ -29335,6 +30851,7 @@
29335
30851
  this.renderPriority = 1;
29336
30852
  }
29337
30853
  (async () => {
30854
+ var _a, _b;
29338
30855
  if (this.disposed) {
29339
30856
  return;
29340
30857
  }
@@ -29350,6 +30867,8 @@
29350
30867
  this.hierarchyUpdate.Trigger(hierarchy);
29351
30868
  // Build hierarchy node cache for quick lookups
29352
30869
  this.buildHierarchyNodes(hierarchy);
30870
+ // A set for membership, since searching the list took minutes at 200k nodes.
30871
+ const seenIds = new Set();
29353
30872
  const traverseHierarchy = (node, leavesOnly) => {
29354
30873
  var _a;
29355
30874
  if (node.Children) {
@@ -29358,7 +30877,8 @@
29358
30877
  }
29359
30878
  }
29360
30879
  const addNode = !leavesOnly || !((_a = node.Children) === null || _a === void 0 ? void 0 : _a.length);
29361
- if (addNode && node.ID && !flatEntityIds.includes(node.ID)) {
30880
+ if (addNode && node.ID && !seenIds.has(node.ID)) {
30881
+ seenIds.add(node.ID);
29362
30882
  flatEntityIds.push(node.ID);
29363
30883
  }
29364
30884
  };
@@ -29377,17 +30897,17 @@
29377
30897
  }
29378
30898
  }
29379
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;
29380
30901
  if (this.disposed) {
29381
30902
  return;
29382
30903
  }
29383
30904
  else if (flatEntityIds.length === 0) {
29384
30905
  return;
29385
30906
  }
30907
+ // Followed while the parts load: in a large Assembly loading takes far longer than anyone waits.
30908
+ const following = this.viewerDateTimeSub();
29386
30909
  await this.processEntityIds(flatEntityIds);
29387
- if (this.disposed) {
29388
- return;
29389
- }
29390
- this.viewerDateTimeSub();
30910
+ await following;
29391
30911
  }
29392
30912
  catch (e) {
29393
30913
  console.error("AssemblyRenderManager.Manager.Init()", e);
@@ -29414,7 +30934,7 @@
29414
30934
  if (!batch.length) {
29415
30935
  continue;
29416
30936
  }
29417
- const { entities: fetched } = await BModels.Entity.GetListByIds({
30937
+ const { entities: apiEntities } = await BModels.Entity.GetListByIds({
29418
30938
  entityIds: batch,
29419
30939
  api: this.getters.GetBruceApi(),
29420
30940
  expandLODs: true,
@@ -29426,6 +30946,16 @@
29426
30946
  if (this.disposed) {
29427
30947
  return;
29428
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;
29429
30959
  for (const entity of fetched) {
29430
30960
  // Filter for valid LODs then take the highest quality one (lowest level).
29431
30961
  let lods = entity.Bruce.LOD || [];
@@ -29448,57 +30978,46 @@
29448
30978
  // Won't bother with vector data for this experiment.
29449
30979
  continue;
29450
30980
  }
29451
- const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29452
- const recordPos3d = exports.EntityUtils.GetPos({
29453
- viewer: this.viewer,
29454
- entity,
29455
- recordHeightRef: recordHeightRef,
29456
- returnHeightRef: Cesium.HeightReference.NONE,
29457
- allowRendered: false
29458
- });
29459
- 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)) {
29460
30986
  continue;
29461
30987
  }
29462
- const entityId = entity.Bruce.ID;
29463
- if (entityId === this.rootId || !this.rootPos3d) {
29464
- // Nothing has a frame to be relative to until the root has one.
29465
- this.rootPos3d = recordPos3d;
29466
- this.rootHeading = EnsureNumber((_a = entity.Bruce.Transform) === null || _a === void 0 ? void 0 : _a.heading, 0);
29467
- }
29468
- // Held as an offset in the root's frame, so a root move places it again without
29469
- // anything being fetched. Where the root already moved, that is where it goes.
29470
- const offset = Manager.ChildOffsetIn(this.rootPos3d, this.rootHeading, recordPos3d);
29471
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, offset);
29472
- // Optional: a child with no world matrix is still worth drawing, it just has no
29473
- // orientation of its own to apply.
29474
- const orient = exports.EntityUtils.GetAssemblyOrientation(entity, pos3d);
29475
30988
  const cEntity = this.viewer.entities.add({
29476
30989
  model: {
29477
30990
  uri: lod["ClientFile"].URL,
29478
30991
  ...exports.ViewUtils.DefaultLightingProps(),
29479
30992
  heightReference: Cesium.HeightReference.NONE
29480
- },
29481
- position: pos3d,
29482
- orientation: orient ? new Cesium.ConstantProperty(orient) : undefined
30993
+ }
29483
30994
  });
29484
30995
  const prior = this.children.get(entityId);
29485
30996
  if (prior && prior.cEntity !== cEntity) {
29486
30997
  RemoveCEntityFromScene(this.viewer, prior.cEntity);
29487
30998
  }
29488
- this.children.set(entityId, {
30999
+ const child = {
29489
31000
  entityId: entityId,
29490
31001
  entity: entity,
29491
- cEntity: cEntity,
29492
- offset: offset
29493
- });
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
+ }
29494
31009
  const geometryRadius = BModels.Entity.GetValue({
29495
31010
  entity: entity,
29496
31011
  path: ["Bruce", "GeometryRadius"]
29497
31012
  });
29498
31013
  cEntity.model["_radius"] = geometryRadius;
29499
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);
29500
31017
  this.visualsManager.AddRego({
29501
31018
  rego: {
31019
+ moving: child.moving ? true : null,
31020
+ motion: (time) => this.motionAt(entity.Bruce.ID, time),
29502
31021
  canEdit: true,
29503
31022
  entityId: entity.Bruce.ID,
29504
31023
  menuItemId: this.item.id,
@@ -29518,47 +31037,89 @@
29518
31037
  }
29519
31038
  }
29520
31039
  /*
29521
- * Follows the scene clock and moves the whole Assembly along the root Entity's historic path.
29522
- * 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.
29523
31041
  */
29524
- viewerDateTimeSub() {
29525
- 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)) {
29526
31046
  return;
29527
31047
  }
31048
+ this.followingClock = true;
29528
31049
  const api = this.getters.GetBruceApi();
29529
- 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;
29530
31055
  const recordHeightRef = Manager.GetRecordHeightRef(this.item);
29531
31056
  const toPosses = (records) => {
29532
31057
  return exports.CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, recordHeightRef, Cesium.HeightReference.NONE, records);
29533
31058
  };
29534
- this.historicGatherer = new HistoricGatherer({
29535
- viewer: this.viewer,
29536
- getApi: () => api,
29537
- getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
29538
- // For clock desync between whatever wrote the record and this client.
29539
- paddingMs: 15000,
29540
- onRecords: (recordsByIds) => {
29541
- const records = recordsByIds[this.rootId];
29542
- if ((records === null || records === void 0 ? void 0 : records.length) && this.historicAnimation) {
29543
- 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.
29544
31079
  }
29545
- }
29546
- });
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()]));
29547
31084
  let gathering = false;
29548
- 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);
29549
31090
  if (this.disposed || gathering) {
29550
31091
  return;
29551
31092
  }
29552
31093
  gathering = true;
29553
31094
  try {
29554
- 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
+ }
29555
31104
  if (this.disposed) {
29556
31105
  return;
29557
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);
29558
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
+ }
29559
31119
  if (!posses.length) {
29560
31120
  return;
29561
31121
  }
31122
+ // Held for the trail alone; where the root is drawn comes from rootAt(), never from its smoothing.
29562
31123
  if (!this.historicAnimation) {
29563
31124
  this.historicAnimation = new exports.CesiumAnimatedProperty.AnimatePositionSeries({
29564
31125
  posses: posses,
@@ -29572,15 +31133,21 @@
29572
31133
  }
29573
31134
  catch (e) {
29574
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;
29575
31138
  }
29576
31139
  finally {
29577
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
+ }
29578
31145
  }
29579
31146
  };
31147
+ this.regather = gather;
29580
31148
  gather();
29581
31149
  let lastTime = null;
29582
31150
  const tickRemoval = this.viewer.clock.onTick.addEventListener(() => {
29583
- var _a;
29584
31151
  if (this.disposed) {
29585
31152
  return;
29586
31153
  }
@@ -29590,15 +31157,9 @@
29590
31157
  }
29591
31158
  lastTime = now;
29592
31159
  gather();
29593
- if (!this.historicAnimation) {
29594
- return;
29595
- }
29596
- const pos3d = this.historicAnimation.GetValue();
29597
- if (pos3d) {
29598
- this.ApplyRootPosition({
29599
- pos3d: pos3d,
29600
- heading: (_a = this.historicAnimation.GetHeadingDegrees()) !== null && _a !== void 0 ? _a : this.rootHeading
29601
- });
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();
29602
31163
  }
29603
31164
  });
29604
31165
  this.viewerDateTimeChangeRemoval = () => {
@@ -29607,6 +31168,10 @@
29607
31168
  (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
29608
31169
  this.historicGatherer = null;
29609
31170
  this.historicAnimation = null;
31171
+ this.regather = null;
31172
+ this.rootSeries = [];
31173
+ this.rootAttitude = [];
31174
+ this.pose = null;
29610
31175
  };
29611
31176
  }
29612
31177
  /*
@@ -29614,7 +31179,7 @@
29614
31179
  */
29615
31180
  applyRootTrack() {
29616
31181
  var _a;
29617
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
31182
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
29618
31183
  return;
29619
31184
  }
29620
31185
  const trackStyle = exports.EntityRenderEngine.GetTrackStyle({
@@ -29624,41 +31189,35 @@
29624
31189
  if (!trackStyle) {
29625
31190
  return;
29626
31191
  }
29627
- this.trackEntity = this.viewer.entities.add({
29628
- id: BModels.ObjectUtils.UId(15),
29629
- polyline: {
29630
- 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),
29631
- width: trackStyle.width,
29632
- material: trackStyle.color,
29633
- clampToGround: exports.EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
29634
- == Cesium.HeightReference.CLAMP_TO_GROUND,
29635
- zIndex: 0,
29636
- classificationType: Cesium.ClassificationType.BOTH
29637
- }
29638
- });
29639
- this.trackEntity._noEdit = true;
29640
- this.trackEntity._noSelect = true;
29641
- exports.CesiumEntityStyler.SetDefaultColor({
29642
- color: trackStyle.color,
29643
- entity: this.trackEntity,
31192
+ this.trail = new HistoricTrail.Trail({
29644
31193
  viewer: this.viewer,
29645
- override: true,
29646
- 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)
29647
31202
  });
29648
31203
  }
29649
31204
  Dispose() {
29650
- var _a;
31205
+ var _a, _b, _c;
29651
31206
  if (this.disposed) {
29652
31207
  return;
29653
31208
  }
29654
31209
  this.disposed = true;
29655
31210
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
29656
31211
  this.viewerDateTimeChangeRemoval = null;
29657
- if (this.trackEntity) {
29658
- RemoveCEntityFromScene(this.viewer, this.trackEntity);
29659
- this.trackEntity = null;
29660
- }
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;
29661
31217
  this.children.clear();
31218
+ this.localPositions.clear();
31219
+ this.jointSeries.clear();
31220
+ this.animatedIds.clear();
29662
31221
  this.visualsManager.RemoveRegos({
29663
31222
  menuItemId: this.item.id
29664
31223
  });
@@ -30112,6 +31671,9 @@
30112
31671
  this.OnSeriesUpdated.Trigger({ itemId, segments: [] });
30113
31672
  }
30114
31673
  }
31674
+ get Simulation() {
31675
+ return this.simulation;
31676
+ }
30115
31677
  get Monitor() {
30116
31678
  return this.sharedMonitor;
30117
31679
  }
@@ -30137,6 +31699,7 @@
30137
31699
  this.visualsRegister = visualsRegister;
30138
31700
  this.sharedGetters = new exports.SharedGetters.Cache();
30139
31701
  this.sharedHistoric = new SharedHistoricGetters.Cache();
31702
+ this.simulation = exports.EntitySimulation.Get(this.viewer);
30140
31703
  this.sharedMonitor = new exports.CesiumViewMonitor.Monitor(this.viewer);
30141
31704
  this.tilesetInitQueue = new InitQueueTileset(this.getters);
30142
31705
  }
@@ -30350,7 +31913,8 @@
30350
31913
  monitor: this.sharedMonitor,
30351
31914
  item: params.item,
30352
31915
  sharedGetters: this.sharedGetters,
30353
- onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment)
31916
+ onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment),
31917
+ simulation: this.simulation
30354
31918
  });
30355
31919
  break;
30356
31920
  case BModels.MenuItem.EType.EntitiesLoaded:
@@ -30359,7 +31923,8 @@
30359
31923
  register: this.visualsRegister,
30360
31924
  apiGetter: params.apiGetter,
30361
31925
  monitor: this.sharedMonitor,
30362
- item: params.item
31926
+ item: params.item,
31927
+ simulation: this.simulation
30363
31928
  });
30364
31929
  break;
30365
31930
  case BModels.MenuItem.EType.EntitiesIds:
@@ -30369,7 +31934,8 @@
30369
31934
  apiGetter: params.apiGetter,
30370
31935
  monitor: this.sharedMonitor,
30371
31936
  item: params.item,
30372
- sharedHistoric: this.sharedHistoric
31937
+ sharedHistoric: this.sharedHistoric,
31938
+ simulation: this.simulation
30373
31939
  });
30374
31940
  break;
30375
31941
  case BModels.MenuItem.EType.Entity:
@@ -30378,7 +31944,8 @@
30378
31944
  register: this.visualsRegister,
30379
31945
  apiGetter: params.apiGetter,
30380
31946
  monitor: this.sharedMonitor,
30381
- item: params.item
31947
+ item: params.item,
31948
+ simulation: this.simulation
30382
31949
  });
30383
31950
  break;
30384
31951
  case BModels.MenuItem.EType.CadTileset:
@@ -30388,7 +31955,8 @@
30388
31955
  getters: this.getters,
30389
31956
  item: params.item,
30390
31957
  modelSpace: params.modelSpace,
30391
- initQueue: this.tilesetInitQueue
31958
+ initQueue: this.tilesetInitQueue,
31959
+ simulation: this.simulation
30392
31960
  });
30393
31961
  break;
30394
31962
  case BModels.MenuItem.EType.Assembly:
@@ -30398,6 +31966,7 @@
30398
31966
  getters: params.getters,
30399
31967
  item: params.item,
30400
31968
  modelSpace: params.modelSpace,
31969
+ simulation: this.simulation
30401
31970
  });
30402
31971
  break;
30403
31972
  case BModels.MenuItem.EType.Osm:
@@ -30425,7 +31994,8 @@
30425
31994
  getters: this.getters,
30426
31995
  monitor: this.sharedMonitor,
30427
31996
  item: params.item,
30428
- initQueue: this.tilesetInitQueue
31997
+ initQueue: this.tilesetInitQueue,
31998
+ simulation: this.simulation
30429
31999
  });
30430
32000
  break;
30431
32001
  case BModels.MenuItem.EType.Kml:
@@ -30449,7 +32019,8 @@
30449
32019
  getters: params.getters,
30450
32020
  item: params.item,
30451
32021
  viewer: this.viewer,
30452
- register: this.visualsRegister
32022
+ register: this.visualsRegister,
32023
+ simulation: this.simulation
30453
32024
  });
30454
32025
  break;
30455
32026
  // Deprecated to support a few demos until new one is phased in.
@@ -30459,7 +32030,8 @@
30459
32030
  apiGetter: params.apiGetter,
30460
32031
  item: params.item,
30461
32032
  register: this.visualsRegister,
30462
- viewer: this.viewer
32033
+ viewer: this.viewer,
32034
+ simulation: this.simulation
30463
32035
  });
30464
32036
  break;
30465
32037
  case BModels.MenuItem.EType.Relationships:
@@ -30467,7 +32039,8 @@
30467
32039
  apiGetter: params.apiGetter,
30468
32040
  item: params.item,
30469
32041
  register: this.visualsRegister,
30470
- viewer: this.viewer
32042
+ viewer: this.viewer,
32043
+ simulation: this.simulation
30471
32044
  });
30472
32045
  break;
30473
32046
  case BModels.MenuItem.EType.GooglePhotoTileset:
@@ -30499,7 +32072,8 @@
30499
32072
  item: params.item,
30500
32073
  apiGetter: params.apiGetter,
30501
32074
  monitor: this.sharedMonitor,
30502
- visualsRegister: this.visualsRegister
32075
+ visualsRegister: this.visualsRegister,
32076
+ simulation: this.simulation
30503
32077
  });
30504
32078
  break;
30505
32079
  // Any "none" type can be ignored. These are just containers for other items.
@@ -30712,6 +32286,63 @@
30712
32286
  }
30713
32287
  }
30714
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
+ }
30715
32346
  }
30716
32347
  MenuItemManager.Manager = Manager;
30717
32348
  })(exports.MenuItemManager || (exports.MenuItemManager = {}));
@@ -39248,6 +40879,7 @@
39248
40879
  }
39249
40880
  class WidgetInfoView extends exports.Widget.AWidget {
39250
40881
  constructor(params) {
40882
+ var _a;
39251
40883
  super(params);
39252
40884
  this.STYLESHEET_ID = "nextspace-info-view-stylesheet";
39253
40885
  this._generateStyles();
@@ -39257,10 +40889,19 @@
39257
40889
  this._updateInfoView();
39258
40890
  }
39259
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
+ });
39260
40899
  }
39261
40900
  Dispose() {
40901
+ var _a;
39262
40902
  super.Dispose();
39263
40903
  this._updateRemoval();
40904
+ (_a = this._simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
39264
40905
  }
39265
40906
  _generateStyles() {
39266
40907
  if (document.getElementById(this.STYLESHEET_ID)) {
@@ -39722,7 +41363,7 @@
39722
41363
  }
39723
41364
  }
39724
41365
  async _updateInfoView() {
39725
- var _b, _c, _d, _e;
41366
+ var _a, _c, _d, _e, _f;
39726
41367
  const selectedIds = this._manager.VisualsRegister.GetSelected();
39727
41368
  if (selectedIds.length <= 0) {
39728
41369
  this._element.style.display = "none";
@@ -39744,17 +41385,20 @@
39744
41385
  this._summary.innerHTML = "";
39745
41386
  this._imageContainer.style.display = "none";
39746
41387
  const api = this._apiGetters.GetBruceApi();
39747
- 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({
39748
41391
  entityId: firstId,
39749
41392
  api: api,
39750
41393
  migrated: true,
39751
41394
  expandLocation: DISPLAY_ASSEMBLY_INFO
39752
- });
41395
+ })).entity;
41396
+ entity = entity && simulation ? simulation.Resolve(firstId, entity) : entity;
39753
41397
  if (!entity) {
39754
41398
  throw (new Error("Could not get entity."));
39755
41399
  }
39756
41400
  const { entityType } = await BModels.EntityType.Get({
39757
- 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"],
39758
41402
  api: api
39759
41403
  });
39760
41404
  let name = BModels.Entity.CalculateName({
@@ -39777,7 +41421,7 @@
39777
41421
  const defaultImage = images.length ? images[0] : null;
39778
41422
  if (defaultImage) {
39779
41423
  this._imageContainer.style.display = "flex";
39780
- 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({
39781
41425
  fileId: defaultImage["ClientFile.ID"],
39782
41426
  api: api,
39783
41427
  viaCdn: true
@@ -39790,7 +41434,7 @@
39790
41434
  this._summary.appendChild(detailsTitle);
39791
41435
  // TODO: Selection for what mode instead of always doing "all".
39792
41436
  const attributes = gatherAttributes(EMode.ShowAll, entity, entityType);
39793
- 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 : []);
39794
41438
  for (const group of groups) {
39795
41439
  this._generateAttrGroup(entityType, entity, group);
39796
41440
  }
@@ -42384,7 +44028,7 @@
42384
44028
  * @param params
42385
44029
  */
42386
44030
  async function GetLocation(params) {
42387
- var _a, _b, _c, _d;
44031
+ var _a, _b, _c, _d, _e;
42388
44032
  let { api, viewer, samples, visualRegister, minimumAlt, paddingAlt } = params;
42389
44033
  if (!paddingAlt) {
42390
44034
  paddingAlt = 0;
@@ -42932,6 +44576,13 @@
42932
44576
  recordSamples.push(sample);
42933
44577
  }
42934
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
+ }
42935
44586
  // Bulk requesting Entity records.
42936
44587
  try {
42937
44588
  const toSearchIds = recordSamples.filter(x => !x.entity && x.entityId).map(x => x.entityId);
@@ -42952,7 +44603,8 @@
42952
44603
  for (let i = 0; i < recordSamples.length; i++) {
42953
44604
  const sample = recordSamples[i];
42954
44605
  if (!sample.entity && sample.entityId) {
42955
- sample.entity = entitiesMap.get(sample.entityId);
44606
+ const read = entitiesMap.get(sample.entityId);
44607
+ sample.entity = read && simulation ? simulation.Overlay(read) : read;
42956
44608
  }
42957
44609
  }
42958
44610
  }
@@ -42966,6 +44618,11 @@
42966
44618
  // We have to detect whatever is an Assembly Entity and expand it.
42967
44619
  try {
42968
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
+ }
42969
44626
  const assemblyPos = BModels.Entity.GetValue({
42970
44627
  entity: x.entity,
42971
44628
  path: ["Bruce", "AssemblyPosition"]
@@ -43324,6 +44981,21 @@
43324
44981
  return Cesium.Quaternion.multiply(combined, _assemblyAxisShift, new Cesium.Quaternion());
43325
44982
  }
43326
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;
43327
44999
  /**
43328
45000
  * Returns the position of the entity.
43329
45001
  * This is an async variant that checks for related assemblies.
@@ -43333,6 +45005,18 @@
43333
45005
  */
43334
45006
  async function GetPosAsync(params) {
43335
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
+ }
43336
45020
  // We have issues where imported BRZ files have improper expandLocation=true info.
43337
45021
  // As a result we'll double-request the data.
43338
45022
  // If there is a location then we won't bother using expanded information.
@@ -53294,7 +54978,7 @@ void main() {
53294
54978
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
53295
54979
  })(exports.StyleUtils || (exports.StyleUtils = {}));
53296
54980
 
53297
- const VERSION = "7.6.1";
54981
+ const VERSION = "7.6.2";
53298
54982
  /**
53299
54983
  * Updates the environment instance used by bruce-cesium to one specified.
53300
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.