bruce-cesium 7.6.1 → 7.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/bruce-cesium.es5.js +2535 -385
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2524 -382
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +3 -1
  6. package/dist/lib/bruce-cesium.js.map +1 -1
  7. package/dist/lib/rendering/cesium-animated-property.js +63 -107
  8. package/dist/lib/rendering/cesium-animated-property.js.map +1 -1
  9. package/dist/lib/rendering/entity-gatherer.js +11 -3
  10. package/dist/lib/rendering/entity-gatherer.js.map +1 -1
  11. package/dist/lib/rendering/menu-item-creator.js +44 -23
  12. package/dist/lib/rendering/menu-item-creator.js.map +1 -1
  13. package/dist/lib/rendering/menu-item-manager.js +199 -17
  14. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  15. package/dist/lib/rendering/menu-item-styles.js +151 -0
  16. package/dist/lib/rendering/menu-item-styles.js.map +1 -0
  17. package/dist/lib/rendering/relation-render-engine.js +24 -5
  18. package/dist/lib/rendering/relation-render-engine.js.map +1 -1
  19. package/dist/lib/rendering/render-managers/common/entity-label-group.js +13 -9
  20. package/dist/lib/rendering/render-managers/common/entity-label-group.js.map +1 -1
  21. package/dist/lib/rendering/render-managers/common/entity-label.js +68 -7
  22. package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
  23. package/dist/lib/rendering/render-managers/common/entity-rerender-maintain-state.js +44 -5
  24. package/dist/lib/rendering/render-managers/common/entity-rerender-maintain-state.js.map +1 -1
  25. package/dist/lib/rendering/render-managers/common/entity-simulation.js +483 -0
  26. package/dist/lib/rendering/render-managers/common/entity-simulation.js.map +1 -0
  27. package/dist/lib/rendering/render-managers/common/historic-trail.js +123 -0
  28. package/dist/lib/rendering/render-managers/common/historic-trail.js.map +1 -0
  29. package/dist/lib/rendering/render-managers/common/historic-utils.js +17 -6
  30. package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -1
  31. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js +13 -2
  32. package/dist/lib/rendering/render-managers/entities/entities-datalab-render-manager.js.map +1 -1
  33. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +20 -2
  34. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  35. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js +13 -2
  36. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js.map +1 -1
  37. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +66 -5
  38. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  39. package/dist/lib/rendering/render-managers/entities/entity-render-manager.js +11 -1
  40. package/dist/lib/rendering/render-managers/entities/entity-render-manager.js.map +1 -1
  41. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js +745 -106
  42. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js.map +1 -1
  43. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js +23 -1
  44. package/dist/lib/rendering/render-managers/other/legacy-relations-render-manager.js.map +1 -1
  45. package/dist/lib/rendering/render-managers/other/relations-render-manager.js +23 -1
  46. package/dist/lib/rendering/render-managers/other/relations-render-manager.js.map +1 -1
  47. package/dist/lib/rendering/render-managers/render-manager.js.map +1 -1
  48. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +19 -0
  49. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  50. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +145 -40
  51. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  52. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +18 -0
  53. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  54. package/dist/lib/rendering/tileset-styler.js +1 -1
  55. package/dist/lib/rendering/tileset-styler.js.map +1 -1
  56. package/dist/lib/rendering/visual-register-culler.js +2 -0
  57. package/dist/lib/rendering/visual-register-culler.js.map +1 -1
  58. package/dist/lib/rendering/visuals-register.js +89 -20
  59. package/dist/lib/rendering/visuals-register.js.map +1 -1
  60. package/dist/lib/utils/entity-utils.js +43 -2
  61. package/dist/lib/utils/entity-utils.js.map +1 -1
  62. package/dist/lib/widgets/tabs-left-panel/widget-left-panel-tab-menu-items.js +64 -12
  63. package/dist/lib/widgets/tabs-left-panel/widget-left-panel-tab-menu-items.js.map +1 -1
  64. package/dist/lib/widgets/widget-info-view.js +20 -7
  65. package/dist/lib/widgets/widget-info-view.js.map +1 -1
  66. package/dist/types/bruce-cesium.d.ts +3 -1
  67. package/dist/types/rendering/cesium-animated-property.d.ts +8 -6
  68. package/dist/types/rendering/entity-gatherer.d.ts +6 -1
  69. package/dist/types/rendering/menu-item-manager.d.ts +58 -1
  70. package/dist/types/rendering/menu-item-styles.d.ts +30 -0
  71. package/dist/types/rendering/relation-render-engine.d.ts +10 -0
  72. package/dist/types/rendering/render-managers/common/entity-label-group.d.ts +3 -2
  73. package/dist/types/rendering/render-managers/common/entity-label.d.ts +4 -0
  74. package/dist/types/rendering/render-managers/common/entity-rerender-maintain-state.d.ts +20 -0
  75. package/dist/types/rendering/render-managers/common/entity-simulation.d.ts +126 -0
  76. package/dist/types/rendering/render-managers/common/historic-trail.d.ts +30 -0
  77. package/dist/types/rendering/render-managers/common/historic-utils.d.ts +4 -0
  78. package/dist/types/rendering/render-managers/entities/entities-datalab-render-manager.d.ts +7 -1
  79. package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +7 -1
  80. package/dist/types/rendering/render-managers/entities/entities-loaded-render-manager.d.ts +7 -1
  81. package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +10 -1
  82. package/dist/types/rendering/render-managers/entities/entity-render-manager.d.ts +7 -1
  83. package/dist/types/rendering/render-managers/other/assembly-render-manager.d.ts +79 -2
  84. package/dist/types/rendering/render-managers/other/legacy-relations-render-manager.d.ts +5 -0
  85. package/dist/types/rendering/render-managers/other/relations-render-manager.d.ts +5 -0
  86. package/dist/types/rendering/render-managers/render-manager.d.ts +14 -1
  87. package/dist/types/rendering/render-managers/tilesets/tileset-arb-render-manager.d.ts +8 -1
  88. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +16 -2
  89. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +8 -1
  90. package/dist/types/rendering/tileset-styler.d.ts +2 -0
  91. package/dist/types/rendering/visuals-register.d.ts +13 -0
  92. package/dist/types/utils/entity-utils.d.ts +6 -0
  93. package/dist/types/widgets/tabs-left-panel/widget-left-panel-tab-menu-items.d.ts +1 -0
  94. package/dist/types/widgets/widget-info-view.d.ts +1 -0
  95. package/package.json +2 -2
@@ -9,7 +9,7 @@ const js_utils_1 = require("../../../internal/js-utils");
9
9
  const entity_render_engine_1 = require("../../entity-render-engine");
10
10
  const cesium_animated_property_1 = require("../../cesium-animated-property");
11
11
  const historic_utils_1 = require("../common/historic-utils");
12
- const cesium_entity_styler_1 = require("../../../utils/cesium-entity-styler");
12
+ const historic_trail_1 = require("../common/historic-trail");
13
13
  const cesium_utils_1 = require("../../../internal/cesium-utils");
14
14
  /**
15
15
  * Menu Item that will render an Assembly.
@@ -32,6 +32,55 @@ var AssemblyRenderManager;
32
32
  get ModelSpace() {
33
33
  return this.modelSpace;
34
34
  }
35
+ /**
36
+ * Every node of the Assembly read so far, so a caller can work out motion in the Assembly's own terms.
37
+ * @param timeMs the scene time for each node's current pose. Default: the clock's.
38
+ * @returns per node its pose within its parent from the API (local) and as drawn at the time (current).
39
+ */
40
+ GetNodes(timeMs) {
41
+ const at = timeMs !== null && timeMs !== void 0 ? timeMs : Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime();
42
+ const rowsOf = (m) => m ? [0, 1, 2, 3].map(row => [0, 1, 2, 3].map(column => m[(column * 4) + row])) : null;
43
+ return Object.values(this.hierarchyNodeByEntityId || {}).map(node => {
44
+ var _a, _b, _c;
45
+ const entity = (_a = this.apiEntities.get(node.entityId)) !== null && _a !== void 0 ? _a : null;
46
+ const rows = (_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition;
47
+ return {
48
+ entityId: node.entityId,
49
+ name: node.name,
50
+ parentId: (_c = node.parentId) !== null && _c !== void 0 ? _c : null,
51
+ local: Manager.MatrixFromRows(rows) ? rows : null,
52
+ current: rowsOf(this.localAt(node.entityId, at)),
53
+ entity
54
+ };
55
+ });
56
+ }
57
+ /**
58
+ * The root's world frame at a time, history and simulation included, eg. to follow or frame the Assembly.
59
+ * @param timeMs the scene time. Default: the clock's.
60
+ * @returns null until the root has been read.
61
+ */
62
+ GetRootFrame(timeMs) {
63
+ const root = this.rootAt(timeMs !== null && timeMs !== void 0 ? timeMs : Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime());
64
+ return root ? Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll) : null;
65
+ }
66
+ /**
67
+ * A part's world frame at a time as its records and its parents place it, size included.
68
+ * @param entityId the part.
69
+ * @param timeMs the scene time. Default: the clock's.
70
+ * @returns null until the root and the part have been read.
71
+ */
72
+ GetPartFrame(entityId, timeMs) {
73
+ const at = timeMs !== null && timeMs !== void 0 ? timeMs : Cesium.JulianDate.toDate(this.viewer.clock.currentTime).getTime();
74
+ const rootFrame = this.GetRootFrame(at);
75
+ const world = entityId === this.rootId ? Cesium.Matrix4.IDENTITY : this.composeWorld(entityId, at);
76
+ if (!rootFrame || !world) {
77
+ return null;
78
+ }
79
+ // Where the part is drawn, so its own ground offset is part of its frame.
80
+ const ground = this.groundOffsets.get(entityId);
81
+ const placed = ground ? Cesium.Matrix4.multiply(Cesium.Matrix4.fromTranslation(ground), world, new Cesium.Matrix4()) : world;
82
+ return Cesium.Matrix4.multiply(rootFrame, placed, new Cesium.Matrix4());
83
+ }
35
84
  constructor(params) {
36
85
  this.disposed = false;
37
86
  // Cache of the hierarchy so that our scene-tree can reference it.
@@ -41,19 +90,41 @@ var AssemblyRenderManager;
41
90
  this.hierarchyNodeByEntityId = null;
42
91
  this.modelSpace = false;
43
92
  this.rootId = null;
44
- // Where the root sits now, and which way it faces.
45
- // Every child is placed through this, so a root move is one value changing rather than a refetch of the whole Assembly.
93
+ // Where the root sits outside its history, and which way it faces in degrees. Children are placed through it.
46
94
  this.rootPos3d = null;
47
95
  this.rootHeading = 0;
48
- // Entity ID -> what was drawn for it and where it sits relative to the root.
96
+ this.rootPitch = 0;
97
+ this.rootRoll = 0;
98
+ // The root's recorded positions and attitudes, oldest first, kept apart since a record may name only one.
99
+ this.rootSeries = [];
100
+ this.rootAttitude = [];
101
+ // Every placement worked out for the scene time last asked about, shared by all parts drawn at it.
102
+ this.pose = null;
103
+ // Entity ID -> what was drawn for it.
49
104
  this.children = new Map();
105
+ // Entity ID -> its live position within its parent, for every node fetched including undrawn groups.
106
+ this.localPositions = new Map();
107
+ // Entity ID -> how much higher the ground under it is than under the root, along the root's up.
108
+ this.groundOffsets = new Map();
109
+ // Entity ID -> its recorded poses within its parent, oldest first.
110
+ this.jointSeries = new Map();
111
+ // Drawn children that can move with the clock, so a tick places only these and not the whole Assembly.
112
+ this.animatedIds = new Set();
50
113
  // The trail through the root's own movement, which describes the whole Assembly.
51
- this.trackEntity = null;
114
+ this.trail = null;
115
+ this.simulationRemoval = null;
116
+ this.FollowsSimulation = true;
117
+ // Entity ID -> the record the API gave, so a cleared simulation falls back to it without a refetch.
118
+ this.apiEntities = new Map();
119
+ // Re-reads history and re-applies it, eg. after simulated records change. Set while the clock is followed.
120
+ this.regather = null;
121
+ this.followingClock = false;
52
122
  // Callback to dispose the link between scene time and the root's historic position.
53
123
  this.viewerDateTimeChangeRemoval = null;
54
124
  this.historicGatherer = null;
55
125
  this.historicAnimation = null;
56
126
  const { viewer, register: visualsManager, getters, item } = params;
127
+ this.simulation = params.simulation;
57
128
  this.viewer = viewer;
58
129
  this.getters = getters;
59
130
  this.item = item;
@@ -77,10 +148,10 @@ var AssemblyRenderManager;
77
148
  return entity_render_engine_1.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"]);
78
149
  }
79
150
  /*
80
- * The root's frame: east-north-up at its position, turned by its heading.
151
+ * The root's frame: east-north-up at its position, turned by its heading, pitch and roll.
81
152
  */
82
- static rootFrame(rootPos3d, headingDegrees) {
83
- const hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(headingDegrees || 0), 0, 0);
153
+ static rootFrame(rootPos3d, headingDegrees, pitchDegrees = 0, rollDegrees = 0) {
154
+ const hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(headingDegrees || 0), Cesium.Math.toRadians(pitchDegrees || 0), Cesium.Math.toRadians(rollDegrees || 0));
84
155
  return Cesium.Transforms.headingPitchRollToFixedFrame(rootPos3d, hpr);
85
156
  }
86
157
  /**
@@ -103,28 +174,553 @@ var AssemblyRenderManager;
103
174
  return Cesium.Matrix4.multiplyByPoint(Manager.rootFrame(rootPos3d, headingDegrees), offset, new Cesium.Cartesian3());
104
175
  }
105
176
  /**
106
- * Moves the whole Assembly to a new root position, without refetching any of its children.
177
+ * Moves where the root sits outside its history, without refetching children; recorded poses still win.
107
178
  * @param params
108
179
  */
109
180
  ApplyRootPosition(params) {
110
- var _a, _b;
181
+ var _a, _b, _c, _d, _e, _f;
111
182
  if (this.disposed || !(params === null || params === void 0 ? void 0 : params.pos3d)) {
112
183
  return;
113
184
  }
114
185
  this.rootPos3d = params.pos3d;
115
186
  this.rootHeading = (_b = (_a = params.heading) !== null && _a !== void 0 ? _a : this.rootHeading) !== null && _b !== void 0 ? _b : 0;
187
+ this.rootPitch = (_d = (_c = params.pitch) !== null && _c !== void 0 ? _c : this.rootPitch) !== null && _d !== void 0 ? _d : 0;
188
+ this.rootRoll = (_f = (_e = params.roll) !== null && _e !== void 0 ? _e : this.rootRoll) !== null && _f !== void 0 ? _f : 0;
189
+ this.repose(true);
190
+ }
191
+ /*
192
+ * Drops every placement worked out so far, after the root or a joint's records change.
193
+ * @param everyChild re-place the parts that cannot move as well, because where they rest has changed.
194
+ */
195
+ repose(everyChild = false) {
196
+ this.pose = null;
116
197
  for (const child of this.children.values()) {
117
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, child.offset);
118
- if (!pos3d) {
198
+ if (everyChild || Boolean(child.moving) != this.canMove(child.entityId)) {
199
+ this.bindPlacement(child);
200
+ }
201
+ }
202
+ this.viewer.scene.requestRender();
203
+ }
204
+ /*
205
+ * Takes a node's pose outside its history from its record, and for the root where it stands and faces.
206
+ */
207
+ absorbNode(entity) {
208
+ var _a, _b, _c, _d, _e;
209
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
210
+ const local = Manager.MatrixFromRows((_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.AssemblyPosition);
211
+ if (local) {
212
+ this.localPositions.set(id, local);
213
+ }
214
+ // Anchored on the root, which is usually never drawn; everything under it is placed from the hierarchy.
215
+ if (id === this.rootId || (!this.rootId && !this.rootPos3d)) {
216
+ const rootPos3d = entity_utils_1.EntityUtils.GetPos({
217
+ viewer: this.viewer,
218
+ entity,
219
+ recordHeightRef: Manager.GetRecordHeightRef(this.item),
220
+ returnHeightRef: Cesium.HeightReference.NONE,
221
+ allowRendered: false
222
+ });
223
+ if (rootPos3d) {
224
+ this.rootPos3d = rootPos3d;
225
+ this.rootHeading = (0, js_utils_1.EnsureNumber)((_c = entity.Bruce.Transform) === null || _c === void 0 ? void 0 : _c.heading, 0);
226
+ this.rootPitch = (0, js_utils_1.EnsureNumber)((_d = entity.Bruce.Transform) === null || _d === void 0 ? void 0 : _d.pitch, 0);
227
+ this.rootRoll = (0, js_utils_1.EnsureNumber)((_e = entity.Bruce.Transform) === null || _e === void 0 ? void 0 : _e.roll, 0);
228
+ }
229
+ }
230
+ }
231
+ /*
232
+ * A node's record with its simulation laid over it, keeping what a stand-in leaves out, eg. its model.
233
+ */
234
+ withSimulation(entity) {
235
+ var _a;
236
+ const id = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
237
+ if (!this.simulation || !id) {
238
+ return entity;
239
+ }
240
+ const standIn = this.simulation.GetEntity(id);
241
+ if (!standIn) {
242
+ return this.simulation.Overlay(entity);
243
+ }
244
+ return { ...entity, ...standIn, Bruce: { ...entity.Bruce, ...standIn.Bruce } };
245
+ }
246
+ /*
247
+ * Brings the Assembly in line with a change to what is simulated.
248
+ */
249
+ onSimulationChanged(change) {
250
+ var _a;
251
+ if (this.disposed || !this.hierarchyNodeByEntityId) {
252
+ return;
253
+ }
254
+ const ids = change.entityIds.filter(id => this.hierarchyNodeByEntityId[id]);
255
+ if (!ids.length) {
256
+ return;
257
+ }
258
+ if (change.entities) {
259
+ for (const id of ids) {
260
+ const api = this.apiEntities.get(id);
261
+ if (!api) {
262
+ // Not loaded yet, and it will be drawn from its simulation when it is.
263
+ continue;
264
+ }
265
+ const entity = this.withSimulation(api);
266
+ // A cleared blob leaves the node's own matrix where the API has none, so it is reset.
267
+ if (!((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.AssemblyPosition)) {
268
+ this.localPositions.delete(id);
269
+ }
270
+ this.absorbNode(entity);
271
+ const child = this.children.get(id);
272
+ if (child) {
273
+ child.entity = entity;
274
+ }
275
+ }
276
+ this.repose(true);
277
+ }
278
+ if (change.records) {
279
+ if (this.regather) {
280
+ this.regather(true);
281
+ }
282
+ else {
283
+ this.viewerDateTimeSub();
284
+ }
285
+ }
286
+ }
287
+ /*
288
+ * Nodes of this Assembly with simulated history.
289
+ */
290
+ simulatedNodeIds() {
291
+ if (!this.simulation || !this.hierarchyNodeByEntityId) {
292
+ return [];
293
+ }
294
+ return this.simulation.GetRecordEntityIds().filter(id => this.hierarchyNodeByEntityId[id]);
295
+ }
296
+ /*
297
+ * Whether a node can move with time: the root has history, or it or an ancestor does.
298
+ */
299
+ canMove(entityId) {
300
+ return this.rootSeries.length > 0 || this.rootAttitude.length > 0 || this.animatedIds.has(entityId);
301
+ }
302
+ /*
303
+ * Gives a child its position and orientation, as functions of scene time only when it can move.
304
+ */
305
+ bindPlacement(child) {
306
+ const id = child.entityId;
307
+ const moving = this.canMove(id);
308
+ if (moving) {
309
+ if (!child.moving) {
310
+ 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);
311
+ child.cEntity.orientation = new Cesium.CallbackProperty((time, result) => {
312
+ var _a;
313
+ const orient = (_a = this.placementAt(id, time)) === null || _a === void 0 ? void 0 : _a.orient;
314
+ return orient ? Cesium.Quaternion.clone(orient, result) : undefined;
315
+ }, false);
316
+ 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);
317
+ }
318
+ }
319
+ else {
320
+ const placed = this.placementAt(id, this.viewer.clock.currentTime);
321
+ if (placed) {
322
+ child.cEntity.position = new Cesium.ConstantPositionProperty(placed.pos3d);
323
+ if (placed.orient) {
324
+ child.cEntity.orientation = new Cesium.ConstantProperty(placed.orient);
325
+ }
326
+ child.cEntity.model.scale = new Cesium.ConstantProperty(placed.scale);
327
+ }
328
+ }
329
+ if (Boolean(child.moving) != moving) {
330
+ child.moving = moving;
331
+ this.visualsManager.SetMoving({ moving, entityIds: [id], menuItemId: this.item.id });
332
+ }
333
+ }
334
+ /*
335
+ * The one scale a model takes from its part's composed matrix, the largest when they are uneven.
336
+ */
337
+ static uniformScale(world) {
338
+ return Cesium.Cartesian3.maximumComponent(Cesium.Matrix4.getScale(world, new Cesium.Cartesian3()));
339
+ }
340
+ static cloneOrUndefined(pos3d, result) {
341
+ return pos3d ? Cesium.Cartesian3.clone(pos3d, result) : undefined;
342
+ }
343
+ /*
344
+ * Where a node draws at a scene time, from one composition shared by every part asked about at that time.
345
+ */
346
+ placementAt(entityId, time) {
347
+ if (this.disposed || !time) {
348
+ return null;
349
+ }
350
+ const timeMs = Cesium.JulianDate.toDate(time).getTime();
351
+ if (!this.pose || this.pose.timeMs !== timeMs) {
352
+ const root = this.rootAt(timeMs);
353
+ this.pose = {
354
+ timeMs,
355
+ rootFrame: root ? Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll) : null,
356
+ worlds: new Map(),
357
+ placements: new Map()
358
+ };
359
+ }
360
+ const pose = this.pose;
361
+ if (!pose.rootFrame) {
362
+ return null;
363
+ }
364
+ let placed = pose.placements.get(entityId);
365
+ if (!placed) {
366
+ const world = this.composeWorld(entityId, timeMs, pose.worlds);
367
+ const offset = world
368
+ ? Cesium.Matrix4.getTranslation(world, new Cesium.Cartesian3())
369
+ : new Cesium.Cartesian3(0, 0, 0);
370
+ const ground = this.groundOffsets.get(entityId);
371
+ if (ground) {
372
+ Cesium.Cartesian3.add(offset, ground, offset);
373
+ }
374
+ placed = {
375
+ pos3d: Cesium.Matrix4.multiplyByPoint(pose.rootFrame, offset, new Cesium.Cartesian3()),
376
+ // Without a matrix, eg. the root itself, there is no orientation of its own to apply.
377
+ orient: world ? entity_utils_1.EntityUtils.GetAssemblyOrientationInFrame(pose.rootFrame, world) : null,
378
+ scale: world ? Manager.uniformScale(world) : 1
379
+ };
380
+ pose.placements.set(entityId, placed);
381
+ }
382
+ return placed;
383
+ }
384
+ /*
385
+ * The root's pose at a time, position and attitude each following their own records as a joint's do.
386
+ */
387
+ rootAt(timeMs) {
388
+ const pos3d = this.rootPositionAt(timeMs);
389
+ if (!pos3d) {
390
+ return null;
391
+ }
392
+ const live = { heading: this.rootHeading, pitch: this.rootPitch, roll: this.rootRoll };
393
+ const series = this.rootAttitude;
394
+ if (!series.length || timeMs < series[0].timeMs) {
395
+ return { pos3d, ...live };
396
+ }
397
+ let i = 0;
398
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
399
+ i++;
400
+ }
401
+ const a = series[i];
402
+ const b = series[i + 1];
403
+ const t = b ? (timeMs - a.timeMs) / (b.timeMs - a.timeMs) : 0;
404
+ const turn = (from, to, fallback) => {
405
+ var _a, _b, _c;
406
+ const x = (_a = from !== null && from !== void 0 ? from : fallback) !== null && _a !== void 0 ? _a : 0;
407
+ const y = (_c = (_b = (b ? to : from)) !== null && _b !== void 0 ? _b : fallback) !== null && _c !== void 0 ? _c : 0;
408
+ // The short way round, so 350 to 10 degrees turns through north rather than all the way back.
409
+ return x + (Cesium.Math.negativePiToPi(Cesium.Math.toRadians(y - x)) * 180 / Math.PI) * t;
410
+ };
411
+ return {
412
+ pos3d,
413
+ heading: turn(a.heading, b === null || b === void 0 ? void 0 : b.heading, live.heading),
414
+ pitch: turn(a.pitch, b === null || b === void 0 ? void 0 : b.pitch, live.pitch),
415
+ roll: turn(a.roll, b === null || b === void 0 ? void 0 : b.roll, live.roll)
416
+ };
417
+ }
418
+ /*
419
+ * Where the root is at a time, from its recorded positions.
420
+ */
421
+ rootPositionAt(timeMs) {
422
+ const series = this.rootSeries;
423
+ if (!series.length || timeMs < series[0].timeMs) {
424
+ return this.rootPos3d;
425
+ }
426
+ let i = 0;
427
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
428
+ i++;
429
+ }
430
+ const a = series[i];
431
+ const b = series[i + 1];
432
+ if (!b) {
433
+ return a.pos3d;
434
+ }
435
+ const t = (timeMs - a.timeMs) / (b.timeMs - a.timeMs);
436
+ // A straight line between orbit samples cuts up to a kilometre inside the path, so it is lifted back out.
437
+ const chord = Cesium.Cartesian3.lerp(a.pos3d, b.pos3d, t, new Cesium.Cartesian3());
438
+ const carto = Cesium.Cartographic.fromCartesian(chord);
439
+ return carto
440
+ ? Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, Cesium.Math.lerp(a.height, b.height, t))
441
+ : chord;
442
+ }
443
+ /*
444
+ * Takes the root's records into its series.
445
+ * @param replace the records are everything held for the root, so the series becomes exactly them.
446
+ * @returns whether the series changed.
447
+ */
448
+ absorbRootRecords(posses, replace) {
449
+ if (!(posses === null || posses === void 0 ? void 0 : posses.length) && !(replace && this.rootSeries.length)) {
450
+ return false;
451
+ }
452
+ const byTime = new Map(replace ? [] : this.rootSeries.map(k => [k.timeMs, k]));
453
+ for (const pos of posses) {
454
+ const carto = Cesium.Cartographic.fromCartesian(pos.pos3d);
455
+ if (!carto) {
119
456
  continue;
120
457
  }
121
- child.cEntity.position = new Cesium.ConstantPositionProperty(pos3d);
122
- const orient = entity_utils_1.EntityUtils.GetAssemblyOrientation(child.entity, pos3d);
123
- if (orient) {
124
- child.cEntity.orientation = new Cesium.ConstantProperty(orient);
458
+ byTime.set(pos.dateTime.getTime(), {
459
+ timeMs: pos.dateTime.getTime(),
460
+ pos3d: pos.pos3d,
461
+ height: carto.height
462
+ });
463
+ }
464
+ const series = Array.from(byTime.values()).sort((x, y) => x.timeMs - y.timeMs);
465
+ const changed = (series.length !== this.rootSeries.length ||
466
+ series.some((k, i) => !Cesium.Cartesian3.equals(k.pos3d, this.rootSeries[i].pos3d)));
467
+ this.rootSeries = series;
468
+ return changed;
469
+ }
470
+ /*
471
+ * Takes the angles the root's records name into its attitude series, apart from where it is.
472
+ * @param replace the records are everything held for the root, as for absorbRootRecords.
473
+ * @returns whether the series changed.
474
+ */
475
+ absorbRootAttitude(records, replace) {
476
+ const angle = (transform, key) => {
477
+ var _a;
478
+ 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)];
479
+ return value == null || isNaN(+value) ? null : +value;
480
+ };
481
+ const byTime = new Map(replace ? [] : this.rootAttitude.map(k => [k.timeMs, k]));
482
+ for (const record of records || []) {
483
+ const timeMs = (record === null || record === void 0 ? void 0 : record.dateTime) ? new Date(record.dateTime).getTime() : NaN;
484
+ const key = {
485
+ timeMs,
486
+ heading: angle(record === null || record === void 0 ? void 0 : record.transform, "heading"),
487
+ pitch: angle(record === null || record === void 0 ? void 0 : record.transform, "pitch"),
488
+ roll: angle(record === null || record === void 0 ? void 0 : record.transform, "roll")
489
+ };
490
+ if (isNaN(timeMs) || (key.heading == null && key.pitch == null && key.roll == null)) {
491
+ continue;
125
492
  }
493
+ byTime.set(timeMs, key);
126
494
  }
127
- this.viewer.scene.requestRender();
495
+ const series = Array.from(byTime.values()).sort((x, y) => x.timeMs - y.timeMs);
496
+ const held = this.rootAttitude;
497
+ const differs = (k, i) => (k.timeMs !== held[i].timeMs ||
498
+ k.heading !== held[i].heading ||
499
+ k.pitch !== held[i].pitch ||
500
+ k.roll !== held[i].roll);
501
+ const changed = series.length !== held.length || series.some(differs);
502
+ this.rootAttitude = series;
503
+ return changed;
504
+ }
505
+ /*
506
+ * A node's matrix in the root's frame at a time, from each ancestor's pose within its parent.
507
+ */
508
+ composeWorld(entityId, timeMs, memo, localOf = (id) => this.localAt(id, timeMs)) {
509
+ var _a, _b, _c, _d;
510
+ if (memo === null || memo === void 0 ? void 0 : memo.has(entityId)) {
511
+ return memo.get(entityId);
512
+ }
513
+ let world = null;
514
+ const local = localOf(entityId);
515
+ if (local) {
516
+ const parentId = (_b = (_a = this.hierarchyNodeByEntityId) === null || _a === void 0 ? void 0 : _a[entityId]) === null || _b === void 0 ? void 0 : _b.parentId;
517
+ const parent = parentId ? this.composeWorld(parentId, timeMs, memo, localOf) : null;
518
+ world = parent ? Cesium.Matrix4.multiply(parent, local, new Cesium.Matrix4()) : Cesium.Matrix4.clone(local);
519
+ }
520
+ else if (entityId !== this.rootId) {
521
+ // A node whose own position was not fetched falls back to the API's composed matrix.
522
+ const child = this.children.get(entityId);
523
+ 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);
524
+ }
525
+ memo === null || memo === void 0 ? void 0 : memo.set(entityId, world);
526
+ return world;
527
+ }
528
+ /*
529
+ * Under a clamped or relative style, lifts a part by how much higher its ground is than the root's.
530
+ */
531
+ measureGroundOffset(entity) {
532
+ var _a;
533
+ const entityId = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
534
+ this.groundOffsets.delete(entityId);
535
+ const root = this.apiEntities.get(this.rootId);
536
+ if (!entityId ||
537
+ entityId === this.rootId ||
538
+ !root ||
539
+ !this.rootPos3d ||
540
+ Manager.GetRecordHeightRef(this.item) === Cesium.HeightReference.NONE) {
541
+ return;
542
+ }
543
+ const rootFrame = Manager.rootFrame(this.rootPos3d, this.rootHeading, this.rootPitch, this.rootRoll);
544
+ const toRoot = Cesium.Matrix4.inverse(rootFrame, new Cesium.Matrix4());
545
+ const groundUp = (record) => {
546
+ const pos3d = entity_utils_1.EntityUtils.GetPos({
547
+ viewer: this.viewer,
548
+ entity: record,
549
+ recordHeightRef: Cesium.HeightReference.CLAMP_TO_GROUND,
550
+ returnHeightRef: Cesium.HeightReference.NONE,
551
+ allowRendered: false
552
+ });
553
+ return pos3d ? Cesium.Matrix4.multiplyByPoint(toRoot, pos3d, new Cesium.Cartesian3()).z : null;
554
+ };
555
+ const part = groundUp(entity);
556
+ const under = groundUp(root);
557
+ if (part == null || under == null || Math.abs(part - under) < 1e-6) {
558
+ return;
559
+ }
560
+ this.groundOffsets.set(entityId, new Cesium.Cartesian3(0, 0, part - under));
561
+ }
562
+ /*
563
+ * The rigid Earth-fixed motion from where a part's record places it to where it is drawn at a time.
564
+ */
565
+ motionAt(entityId, time) {
566
+ if (this.disposed || !time || !this.rootPos3d) {
567
+ return null;
568
+ }
569
+ const timeMs = Cesium.JulianDate.toDate(time).getTime();
570
+ const root = this.rootAt(timeMs);
571
+ const drawn = this.composeWorld(entityId, timeMs, new Map());
572
+ const stored = this.composeWorld(entityId, timeMs, new Map(), (id) => {
573
+ var _a, _b, _c, _d;
574
+ 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);
575
+ });
576
+ if (!root || !drawn || !stored) {
577
+ return null;
578
+ }
579
+ const drawnFixed = Cesium.Matrix4.multiply(Manager.rootFrame(root.pos3d, root.heading, root.pitch, root.roll), drawn, new Cesium.Matrix4());
580
+ const storedFixed = Cesium.Matrix4.multiply(Manager.rootFrame(this.rootPos3d, this.rootHeading, this.rootPitch, this.rootRoll), stored, new Cesium.Matrix4());
581
+ return Cesium.Matrix4.multiply(drawnFixed, Cesium.Matrix4.inverse(storedFixed, new Cesium.Matrix4()), new Cesium.Matrix4());
582
+ }
583
+ /*
584
+ * A node's pose within its parent at a time, interpolated between the records either side.
585
+ */
586
+ localAt(entityId, timeMs) {
587
+ var _a;
588
+ // The live pose stands before the first record and the last record holds after it, as in an API read.
589
+ const live = (_a = this.localPositions.get(entityId)) !== null && _a !== void 0 ? _a : null;
590
+ const series = this.jointSeries.get(entityId);
591
+ if (!(series === null || series === void 0 ? void 0 : series.length) || timeMs == null || timeMs < series[0].timeMs) {
592
+ return live;
593
+ }
594
+ let i = 0;
595
+ while (i < series.length - 1 && series[i + 1].timeMs <= timeMs) {
596
+ i++;
597
+ }
598
+ const a = series[i];
599
+ const b = series[i + 1];
600
+ if (!b) {
601
+ return Cesium.Matrix4.fromTranslationQuaternionRotationScale(a.translation, a.rotation, a.scale);
602
+ }
603
+ const t = (timeMs - a.timeMs) / (b.timeMs - a.timeMs);
604
+ 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()));
605
+ }
606
+ /*
607
+ * Rebuilds each node's keyframes from the historic records held so far.
608
+ * @returns whether any node has keyframes.
609
+ */
610
+ absorbJointRecords(recordsByIds) {
611
+ var _a;
612
+ // Everything held is given, so a node with nothing left, eg. its simulated records cleared, stops moving.
613
+ for (const entityId of Array.from(this.jointSeries.keys())) {
614
+ if (!((_a = recordsByIds === null || recordsByIds === void 0 ? void 0 : recordsByIds[entityId]) === null || _a === void 0 ? void 0 : _a.length)) {
615
+ this.jointSeries.delete(entityId);
616
+ }
617
+ }
618
+ for (const entityId of Object.keys(recordsByIds || {})) {
619
+ const keyframes = [];
620
+ for (const record of recordsByIds[entityId] || []) {
621
+ const matrix = Manager.MatrixFromRows(record.assemblyPosition);
622
+ const timeMs = new Date(record.dateTime).getTime();
623
+ if (!matrix || isNaN(timeMs)) {
624
+ continue;
625
+ }
626
+ const scale = Cesium.Matrix4.getScale(matrix, new Cesium.Cartesian3());
627
+ const rotation = Cesium.Matrix4.getRotation(matrix, new Cesium.Matrix3());
628
+ keyframes.push({
629
+ timeMs,
630
+ translation: Cesium.Matrix4.getTranslation(matrix, new Cesium.Cartesian3()),
631
+ rotation: Cesium.Quaternion.fromRotationMatrix(rotation),
632
+ scale
633
+ });
634
+ }
635
+ if (keyframes.length) {
636
+ keyframes.sort((x, y) => x.timeMs - y.timeMs);
637
+ this.jointSeries.set(entityId, keyframes);
638
+ }
639
+ }
640
+ this.refreshAnimated();
641
+ return this.jointSeries.size > 0;
642
+ }
643
+ /*
644
+ * Rebuilds which drawn children can move with the clock, by walking each one up to the root.
645
+ */
646
+ refreshAnimated() {
647
+ this.animatedIds.clear();
648
+ if (!this.jointSeries.size) {
649
+ return;
650
+ }
651
+ for (const entityId of this.children.keys()) {
652
+ if (this.hasAnimatedAncestry(entityId)) {
653
+ this.animatedIds.add(entityId);
654
+ }
655
+ }
656
+ }
657
+ /*
658
+ * Whether a node or anything above it has keyframes, so the clock can move it.
659
+ */
660
+ hasAnimatedAncestry(entityId) {
661
+ var _a, _b;
662
+ let at = entityId;
663
+ while (at && this.jointSeries.size) {
664
+ if (this.jointSeries.has(at)) {
665
+ return true;
666
+ }
667
+ at = (_b = (_a = this.hierarchyNodeByEntityId) === null || _a === void 0 ? void 0 : _a[at]) === null || _b === void 0 ? void 0 : _b.parentId;
668
+ }
669
+ return false;
670
+ }
671
+ /*
672
+ * Which nodes to ask history for.
673
+ */
674
+ async historicEntityIds(api) {
675
+ var _a, _b;
676
+ // Through the Assembly when there is one, so the hierarchy follows the relation type on its record.
677
+ const assemblyId = this.item["Assembly.ID"];
678
+ const root = assemblyId ? { key: "root", AssemblyID: assemblyId } : { key: "root", RootID: this.rootId };
679
+ const historic = {
680
+ key: "historic",
681
+ Historic: { HistoricKey: ((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey) || undefined }
682
+ };
683
+ const query = {
684
+ [bruce_models_1.DataLab.EReqKey.Primary]: { Items: [root, historic], Select: { Type: bruce_models_1.DataLab.ESelectType.Id } }
685
+ };
686
+ const load = 1000;
687
+ try {
688
+ const ids = [];
689
+ for (let skip = 0;; skip += load) {
690
+ const { items } = await bruce_models_1.DataLab.Run({ api, query, skip, load });
691
+ ids.push(...(items !== null && items !== void 0 ? items : []));
692
+ if (!items || items.length < load) {
693
+ return ids;
694
+ }
695
+ }
696
+ }
697
+ catch (e) {
698
+ // An API without the historic criterion gets asked about every node, as before it existed.
699
+ console.warn("AssemblyRenderManager.historicEntityIds()", e);
700
+ }
701
+ return Object.keys((_b = this.hierarchyNodeByEntityId) !== null && _b !== void 0 ? _b : { [this.rootId]: null });
702
+ }
703
+ /**
704
+ * Builds a matrix from the row-major rows that Bruce/AssemblyPosition and AssemblyWorldPosition use.
705
+ * @param rows
706
+ */
707
+ static MatrixFromRows(rows) {
708
+ if (!Array.isArray(rows) || rows.length !== 4 || rows.some(r => !Array.isArray(r) || r.length !== 4)) {
709
+ return null;
710
+ }
711
+ 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]);
712
+ }
713
+ /**
714
+ * The inverse of MatrixFromRows.
715
+ * @param matrix
716
+ */
717
+ static RowsFromMatrix(matrix) {
718
+ const rows = [];
719
+ for (let r = 0; r < 4; r++) {
720
+ const row = Cesium.Matrix4.getRow(matrix, r, new Cesium.Cartesian4());
721
+ rows.push([row.x, row.y, row.z, row.w]);
722
+ }
723
+ return rows;
128
724
  }
129
725
  Init() {
130
726
  this.renderPriority = this.item.renderPriority;
@@ -132,6 +728,7 @@ var AssemblyRenderManager;
132
728
  this.renderPriority = 1;
133
729
  }
134
730
  (async () => {
731
+ var _a, _b;
135
732
  if (this.disposed) {
136
733
  return;
137
734
  }
@@ -147,6 +744,8 @@ var AssemblyRenderManager;
147
744
  this.hierarchyUpdate.Trigger(hierarchy);
148
745
  // Build hierarchy node cache for quick lookups
149
746
  this.buildHierarchyNodes(hierarchy);
747
+ // A set for membership, since searching the list took minutes at 200k nodes.
748
+ const seenIds = new Set();
150
749
  const traverseHierarchy = (node, leavesOnly) => {
151
750
  var _a;
152
751
  if (node.Children) {
@@ -155,7 +754,8 @@ var AssemblyRenderManager;
155
754
  }
156
755
  }
157
756
  const addNode = !leavesOnly || !((_a = node.Children) === null || _a === void 0 ? void 0 : _a.length);
158
- if (addNode && node.ID && !flatEntityIds.includes(node.ID)) {
757
+ if (addNode && node.ID && !seenIds.has(node.ID)) {
758
+ seenIds.add(node.ID);
159
759
  flatEntityIds.push(node.ID);
160
760
  }
161
761
  };
@@ -174,17 +774,17 @@ var AssemblyRenderManager;
174
774
  }
175
775
  }
176
776
  this.rootId = rootId;
777
+ 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;
177
778
  if (this.disposed) {
178
779
  return;
179
780
  }
180
781
  else if (flatEntityIds.length === 0) {
181
782
  return;
182
783
  }
784
+ // Followed while the parts load: in a large Assembly loading takes far longer than anyone waits.
785
+ const following = this.viewerDateTimeSub();
183
786
  await this.processEntityIds(flatEntityIds);
184
- if (this.disposed) {
185
- return;
186
- }
187
- this.viewerDateTimeSub();
787
+ await following;
188
788
  }
189
789
  catch (e) {
190
790
  console.error("AssemblyRenderManager.Manager.Init()", e);
@@ -216,7 +816,7 @@ var AssemblyRenderManager;
216
816
  if (!batch.length) {
217
817
  continue;
218
818
  }
219
- const { entities: fetched } = await bruce_models_1.Entity.GetListByIds({
819
+ const { entities: apiEntities } = await bruce_models_1.Entity.GetListByIds({
220
820
  entityIds: batch,
221
821
  api: this.getters.GetBruceApi(),
222
822
  expandLODs: true,
@@ -228,6 +828,16 @@ var AssemblyRenderManager;
228
828
  if (this.disposed) {
229
829
  return;
230
830
  }
831
+ const fetched = apiEntities.map((entity) => {
832
+ var _a;
833
+ this.apiEntities.set((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID, entity);
834
+ return this.withSimulation(entity);
835
+ });
836
+ for (const entity of fetched) {
837
+ this.absorbNode(entity);
838
+ }
839
+ // Placements already worked out did not know about this batch's positions.
840
+ this.pose = null;
231
841
  for (const entity of fetched) {
232
842
  // Filter for valid LODs then take the highest quality one (lowest level).
233
843
  let lods = entity.Bruce.LOD || [];
@@ -250,57 +860,46 @@ var AssemblyRenderManager;
250
860
  // Won't bother with vector data for this experiment.
251
861
  continue;
252
862
  }
253
- const recordHeightRef = Manager.GetRecordHeightRef(this.item);
254
- const recordPos3d = entity_utils_1.EntityUtils.GetPos({
255
- viewer: this.viewer,
256
- entity,
257
- recordHeightRef: recordHeightRef,
258
- returnHeightRef: Cesium.HeightReference.NONE,
259
- allowRendered: false
260
- });
261
- if (!recordPos3d) {
262
- continue;
263
- }
264
863
  const entityId = entity.Bruce.ID;
265
- if (entityId === this.rootId || !this.rootPos3d) {
266
- // Nothing has a frame to be relative to until the root has one.
267
- this.rootPos3d = recordPos3d;
268
- this.rootHeading = (0, js_utils_1.EnsureNumber)((_a = entity.Bruce.Transform) === null || _a === void 0 ? void 0 : _a.heading, 0);
864
+ this.measureGroundOffset(entity);
865
+ // A placement cached before the offset was known would leave the part off its ground.
866
+ (_a = this.pose) === null || _a === void 0 ? void 0 : _a.placements.delete(entityId);
867
+ if (!this.canMove(entityId) && !this.placementAt(entityId, this.viewer.clock.currentTime)) {
868
+ continue;
269
869
  }
270
- // Held as an offset in the root's frame, so a root move places it again without
271
- // anything being fetched. Where the root already moved, that is where it goes.
272
- const offset = Manager.ChildOffsetIn(this.rootPos3d, this.rootHeading, recordPos3d);
273
- const pos3d = Manager.ChildPositionFrom(this.rootPos3d, this.rootHeading, offset);
274
- // Optional: a child with no world matrix is still worth drawing, it just has no
275
- // orientation of its own to apply.
276
- const orient = entity_utils_1.EntityUtils.GetAssemblyOrientation(entity, pos3d);
277
870
  const cEntity = this.viewer.entities.add({
278
871
  model: {
279
872
  uri: lod["ClientFile"].URL,
280
873
  ...view_utils_1.ViewUtils.DefaultLightingProps(),
281
874
  heightReference: Cesium.HeightReference.NONE
282
- },
283
- position: pos3d,
284
- orientation: orient ? new Cesium.ConstantProperty(orient) : undefined
875
+ }
285
876
  });
286
877
  const prior = this.children.get(entityId);
287
878
  if (prior && prior.cEntity !== cEntity) {
288
879
  (0, cesium_utils_1.RemoveCEntityFromScene)(this.viewer, prior.cEntity);
289
880
  }
290
- this.children.set(entityId, {
881
+ const child = {
291
882
  entityId: entityId,
292
883
  entity: entity,
293
- cEntity: cEntity,
294
- offset: offset
295
- });
884
+ cEntity: cEntity
885
+ };
886
+ this.children.set(entityId, child);
887
+ // Joins the animated set by its own ancestry, so a late part never re-walks the rest.
888
+ if (this.hasAnimatedAncestry(entityId)) {
889
+ this.animatedIds.add(entityId);
890
+ }
296
891
  const geometryRadius = bruce_models_1.Entity.GetValue({
297
892
  entity: entity,
298
893
  path: ["Bruce", "GeometryRadius"]
299
894
  });
300
895
  cEntity.model["_radius"] = geometryRadius;
301
896
  cEntity.model["_radiusLoaded"] = true;
897
+ // Placed before it is registered, so the register never measures a visual with no position.
898
+ this.bindPlacement(child);
302
899
  this.visualsManager.AddRego({
303
900
  rego: {
901
+ moving: child.moving ? true : null,
902
+ motion: (time) => this.motionAt(entity.Bruce.ID, time),
304
903
  canEdit: true,
305
904
  entityId: entity.Bruce.ID,
306
905
  menuItemId: this.item.id,
@@ -320,47 +919,89 @@ var AssemblyRenderManager;
320
919
  }
321
920
  }
322
921
  /*
323
- * Follows the scene clock and moves the whole Assembly along the root Entity's historic path.
324
- * Only the root is fetched: everything under it is placed through ApplyRootPosition.
922
+ * Follows the scene clock, moving the Assembly along the root's history and each joint along its own.
325
923
  */
326
- viewerDateTimeSub() {
327
- if (!Manager.IsHistoric(this.item) || this.viewerDateTimeChangeRemoval || !this.rootId) {
924
+ async viewerDateTimeSub() {
925
+ var _a;
926
+ const historic = Manager.IsHistoric(this.item);
927
+ if (this.followingClock || !this.rootId || (!historic && !this.simulatedNodeIds().length)) {
328
928
  return;
329
929
  }
930
+ this.followingClock = true;
330
931
  const api = this.getters.GetBruceApi();
331
- const rootEntity = { Bruce: { ID: this.rootId } };
932
+ const historicIds = historic ? await this.historicEntityIds(api) : [];
933
+ if (this.disposed) {
934
+ return;
935
+ }
936
+ const attrKey = (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey;
332
937
  const recordHeightRef = Manager.GetRecordHeightRef(this.item);
333
938
  const toPosses = (records) => {
334
939
  return cesium_animated_property_1.CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, recordHeightRef, Cesium.HeightReference.NONE, records);
335
940
  };
336
- this.historicGatherer = new historic_utils_1.HistoricGatherer({
337
- viewer: this.viewer,
338
- getApi: () => api,
339
- getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
340
- // For clock desync between whatever wrote the record and this client.
341
- paddingMs: 15000,
342
- onRecords: (recordsByIds) => {
343
- const records = recordsByIds[this.rootId];
344
- if ((records === null || records === void 0 ? void 0 : records.length) && this.historicAnimation) {
345
- this.historicAnimation.SupplementSeries(toPosses(records));
941
+ if (historicIds.length) {
942
+ this.historicGatherer = new historic_utils_1.HistoricGatherer({
943
+ viewer: this.viewer,
944
+ getApi: () => api,
945
+ getAttrKey: () => attrKey,
946
+ // For clock desync between whatever wrote the record and this client.
947
+ paddingMs: 15000,
948
+ simulation: this.simulation,
949
+ onRecords: (recordsByIds) => {
950
+ var _a;
951
+ const records = recordsByIds[this.rootId];
952
+ if (records === null || records === void 0 ? void 0 : records.length) {
953
+ const posses = toPosses(records);
954
+ (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.SupplementSeries(posses);
955
+ const moved = this.absorbRootRecords(posses, false);
956
+ if (this.absorbRootAttitude(records, false) || moved) {
957
+ this.repose();
958
+ }
959
+ }
960
+ // Joint records go in together once the gather lands, so parents never draw apart from children.
346
961
  }
347
- }
348
- });
962
+ });
963
+ }
964
+ // The nodes with history: what the API has records for, and whatever has been simulated since.
965
+ const nodes = () => Array.from(new Set([...historicIds, ...this.simulatedNodeIds()]));
349
966
  let gathering = false;
350
- const gather = async () => {
967
+ // Set when what is held changed without a fetch, eg. a simulated record arriving.
968
+ let stale = true;
969
+ const gather = async (force) => {
970
+ var _a, _b;
971
+ stale = stale || Boolean(force);
351
972
  if (this.disposed || gathering) {
352
973
  return;
353
974
  }
354
975
  gathering = true;
355
976
  try {
356
- const { recordsByIds } = await this.historicGatherer.Gather([rootEntity]);
977
+ const ids = nodes();
978
+ let fetched = false;
979
+ let recordsByIds;
980
+ if (this.historicGatherer) {
981
+ ({ fetched, recordsByIds } = await this.historicGatherer.Gather(ids.map(id => ({ Bruce: { ID: id } }))));
982
+ }
983
+ else {
984
+ recordsByIds = (_b = (_a = this.simulation) === null || _a === void 0 ? void 0 : _a.MergeRecords({}, attrKey, ids)) !== null && _b !== void 0 ? _b : {};
985
+ }
357
986
  if (this.disposed) {
358
987
  return;
359
988
  }
989
+ // A gather served from what is held changes nothing and runs every tick, so it stops here.
990
+ if (!fetched && !stale && this.jointSeries.size) {
991
+ return;
992
+ }
993
+ stale = false;
994
+ let changed = this.absorbJointRecords(recordsByIds);
360
995
  const posses = toPosses(recordsByIds[this.rootId] || []);
996
+ changed = this.absorbRootRecords(posses, true) || changed;
997
+ changed = this.absorbRootAttitude(recordsByIds[this.rootId] || [], true) || changed;
998
+ if (changed) {
999
+ this.repose();
1000
+ }
361
1001
  if (!posses.length) {
362
1002
  return;
363
1003
  }
1004
+ // Held for the trail alone; where the root is drawn comes from rootAt(), never from its smoothing.
364
1005
  if (!this.historicAnimation) {
365
1006
  this.historicAnimation = new cesium_animated_property_1.CesiumAnimatedProperty.AnimatePositionSeries({
366
1007
  posses: posses,
@@ -374,15 +1015,21 @@ var AssemblyRenderManager;
374
1015
  }
375
1016
  catch (e) {
376
1017
  console.error("AssemblyRenderManager.viewerDateTimeSub()", e);
1018
+ // A gather that throws would throw again straight away, so the retry waits for the next tick.
1019
+ stale = false;
377
1020
  }
378
1021
  finally {
379
1022
  gathering = false;
1023
+ // A change that landed mid-gather is not left for a tick that may never come on a paused clock.
1024
+ if (stale && !this.disposed) {
1025
+ gather();
1026
+ }
380
1027
  }
381
1028
  };
1029
+ this.regather = gather;
382
1030
  gather();
383
1031
  let lastTime = null;
384
1032
  const tickRemoval = this.viewer.clock.onTick.addEventListener(() => {
385
- var _a;
386
1033
  if (this.disposed) {
387
1034
  return;
388
1035
  }
@@ -392,15 +1039,9 @@ var AssemblyRenderManager;
392
1039
  }
393
1040
  lastTime = now;
394
1041
  gather();
395
- if (!this.historicAnimation) {
396
- return;
397
- }
398
- const pos3d = this.historicAnimation.GetValue();
399
- if (pos3d) {
400
- this.ApplyRootPosition({
401
- pos3d: pos3d,
402
- heading: (_a = this.historicAnimation.GetHeadingDegrees()) !== null && _a !== void 0 ? _a : this.rootHeading
403
- });
1042
+ // Moving parts read the clock themselves when drawn, so the scene only needs a new frame.
1043
+ if (this.rootSeries.length || this.rootAttitude.length || this.animatedIds.size) {
1044
+ this.viewer.scene.requestRender();
404
1045
  }
405
1046
  });
406
1047
  this.viewerDateTimeChangeRemoval = () => {
@@ -409,6 +1050,10 @@ var AssemblyRenderManager;
409
1050
  (_a = this.historicGatherer) === null || _a === void 0 ? void 0 : _a.Dispose();
410
1051
  this.historicGatherer = null;
411
1052
  this.historicAnimation = null;
1053
+ this.regather = null;
1054
+ this.rootSeries = [];
1055
+ this.rootAttitude = [];
1056
+ this.pose = null;
412
1057
  };
413
1058
  }
414
1059
  /*
@@ -416,7 +1061,7 @@ var AssemblyRenderManager;
416
1061
  */
417
1062
  applyRootTrack() {
418
1063
  var _a;
419
- if (!this.item.historicDrawTrack || !this.historicAnimation || this.trackEntity) {
1064
+ if (!this.item.historicDrawTrack || !this.historicAnimation || this.trail) {
420
1065
  return;
421
1066
  }
422
1067
  const trackStyle = entity_render_engine_1.EntityRenderEngine.GetTrackStyle({
@@ -426,41 +1071,35 @@ var AssemblyRenderManager;
426
1071
  if (!trackStyle) {
427
1072
  return;
428
1073
  }
429
- this.trackEntity = this.viewer.entities.add({
430
- id: bruce_models_1.ObjectUtils.UId(15),
431
- polyline: {
432
- 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),
433
- width: trackStyle.width,
434
- material: trackStyle.color,
435
- clampToGround: entity_render_engine_1.EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
436
- == Cesium.HeightReference.CLAMP_TO_GROUND,
437
- zIndex: 0,
438
- classificationType: Cesium.ClassificationType.BOTH
439
- }
440
- });
441
- this.trackEntity._noEdit = true;
442
- this.trackEntity._noSelect = true;
443
- cesium_entity_styler_1.CesiumEntityStyler.SetDefaultColor({
444
- color: trackStyle.color,
445
- entity: this.trackEntity,
1074
+ this.trail = new historic_trail_1.HistoricTrail.Trail({
446
1075
  viewer: this.viewer,
447
- override: true,
448
- requestRender: false
1076
+ color: trackStyle.color,
1077
+ width: trackStyle.width,
1078
+ clampToGround: entity_render_engine_1.EntityRenderEngine.GetHeightRefPolyline(Manager.GetRecordHeightRef(this.item))
1079
+ == Cesium.HeightReference.CLAMP_TO_GROUND,
1080
+ getSeries: () => { var _a, _b; return (_b = (_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.GetSeries()) !== null && _b !== void 0 ? _b : []; },
1081
+ trailSeconds: historic_trail_1.HistoricTrail.GetTrailSeconds(this.item),
1082
+ // Where the root is drawn, so the trail ends on the model; nothing before the first record held.
1083
+ positionAt: (timeMs) => (this.rootSeries.length && timeMs >= this.rootSeries[0].timeMs ? this.rootPositionAt(timeMs) : null)
449
1084
  });
450
1085
  }
451
1086
  Dispose() {
452
- var _a;
1087
+ var _a, _b, _c;
453
1088
  if (this.disposed) {
454
1089
  return;
455
1090
  }
456
1091
  this.disposed = true;
457
1092
  (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
458
1093
  this.viewerDateTimeChangeRemoval = null;
459
- if (this.trackEntity) {
460
- (0, cesium_utils_1.RemoveCEntityFromScene)(this.viewer, this.trackEntity);
461
- this.trackEntity = null;
462
- }
1094
+ (_b = this.simulationRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
1095
+ this.simulationRemoval = null;
1096
+ this.apiEntities.clear();
1097
+ (_c = this.trail) === null || _c === void 0 ? void 0 : _c.Dispose();
1098
+ this.trail = null;
463
1099
  this.children.clear();
1100
+ this.localPositions.clear();
1101
+ this.jointSeries.clear();
1102
+ this.animatedIds.clear();
464
1103
  this.visualsManager.RemoveRegos({
465
1104
  menuItemId: this.item.id
466
1105
  });