bruce-cesium 7.3.2 → 7.3.4

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 (41) hide show
  1. package/dist/bruce-cesium.es5.js +3730 -3106
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +3722 -3098
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/internal/code-logic-exceptions.js +77 -0
  7. package/dist/lib/internal/code-logic-exceptions.js.map +1 -0
  8. package/dist/lib/rendering/cesium-animated-property.js +210 -34
  9. package/dist/lib/rendering/cesium-animated-property.js.map +1 -1
  10. package/dist/lib/rendering/entity-render-engine-model3d.js +39 -58
  11. package/dist/lib/rendering/entity-render-engine-model3d.js.map +1 -1
  12. package/dist/lib/rendering/entity-render-engine-point.js +43 -64
  13. package/dist/lib/rendering/entity-render-engine-point.js.map +1 -1
  14. package/dist/lib/rendering/entity-render-engine-polygon.js +1 -16
  15. package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
  16. package/dist/lib/rendering/entity-render-engine-polyline.js +5 -18
  17. package/dist/lib/rendering/entity-render-engine-polyline.js.map +1 -1
  18. package/dist/lib/rendering/entity-render-engine.js +67 -0
  19. package/dist/lib/rendering/entity-render-engine.js.map +1 -1
  20. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +26 -15
  21. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  22. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js +265 -14
  23. package/dist/lib/rendering/render-managers/other/assembly-render-manager.js.map +1 -1
  24. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +123 -28
  25. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  26. package/dist/lib/rendering/visual-register-culler.js +9 -120
  27. package/dist/lib/rendering/visual-register-culler.js.map +1 -1
  28. package/dist/lib/utils/drawing-utils.js +122 -0
  29. package/dist/lib/utils/drawing-utils.js.map +1 -1
  30. package/dist/lib/utils/view-utils.js +13 -0
  31. package/dist/lib/utils/view-utils.js.map +1 -1
  32. package/dist/types/bruce-cesium.d.ts +1 -1
  33. package/dist/types/internal/code-logic-exceptions.d.ts +37 -0
  34. package/dist/types/rendering/cesium-animated-property.d.ts +55 -1
  35. package/dist/types/rendering/entity-render-engine.d.ts +33 -1
  36. package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +5 -0
  37. package/dist/types/rendering/render-managers/other/assembly-render-manager.d.ts +42 -0
  38. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +42 -2
  39. package/dist/types/utils/drawing-utils.d.ts +30 -0
  40. package/dist/types/utils/view-utils.d.ts +5 -0
  41. package/package.json +2 -2
@@ -83,7 +83,7 @@ __exportStar(require("./widgets/widget-info-view"), exports);
83
83
  __exportStar(require("./widgets/widget-left-panel"), exports);
84
84
  __exportStar(require("./widgets/widget-nav-compass"), exports);
85
85
  __exportStar(require("./widgets/widget-view-bar"), exports);
86
- exports.VERSION = "7.3.2";
86
+ exports.VERSION = "7.3.4";
87
87
  /**
88
88
  * Updates the environment instance used by bruce-cesium to one specified.
89
89
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CodeLogicExceptions = void 0;
4
+ /**
5
+ * Places where the code is deliberately wrong for one customer and right for everyone else.
6
+ * This allows making fixes while leaving specific customers with unchanged experiences until they are migrated properly.
7
+ */
8
+ var CodeLogicExceptions;
9
+ (function (CodeLogicExceptions) {
10
+ // FNV-1a, run twice from different offsets and joined, so the result is 64 bits rather than 32.
11
+ const FNV_PRIME = 0x01000193;
12
+ const FNV_OFFSETS = [0x811c9dc5, 0x7fffffff];
13
+ function fnv1a(value, offset) {
14
+ let hash = offset;
15
+ for (let i = 0; i < value.length; i++) {
16
+ hash ^= value.charCodeAt(i);
17
+ // Multiply in 32-bit space without losing the high bits to float precision.
18
+ hash = Math.imul(hash, FNV_PRIME) >>> 0;
19
+ }
20
+ return hash >>> 0;
21
+ }
22
+ /**
23
+ * Returns the stored form of an account ID, or null when there is no usable account.
24
+ *
25
+ * Null rather than a hash of the empty string, so that everything without an account does not end
26
+ * up matching everything else without one.
27
+ * @param accountId
28
+ */
29
+ function HashAccountId(accountId) {
30
+ const normalized = (accountId || "").trim().toLowerCase();
31
+ if (!normalized) {
32
+ return null;
33
+ }
34
+ return (FNV_OFFSETS
35
+ .map(offset => fnv1a(normalized, offset).toString(16).padStart(8, "0"))
36
+ .join(""));
37
+ }
38
+ CodeLogicExceptions.HashAccountId = HashAccountId;
39
+ /**
40
+ * Returns whether an exception applies to the account currently being rendered for.
41
+ * Anything unknown is not an exception, so the ordinary path is what runs.
42
+ * @param exception
43
+ * @param accountId
44
+ */
45
+ function AppliesTo(exception, accountId) {
46
+ var _a;
47
+ const hash = HashAccountId(accountId);
48
+ if (!hash || !((_a = exception === null || exception === void 0 ? void 0 : exception.accounts) === null || _a === void 0 ? void 0 : _a.length)) {
49
+ return false;
50
+ }
51
+ return exception.accounts.indexOf(hash) !== -1;
52
+ }
53
+ CodeLogicExceptions.AppliesTo = AppliesTo;
54
+ /**
55
+ * A historic Assembly's root heading is turned through half a circle before it is written into the tileset's UCS.
56
+ */
57
+ CodeLogicExceptions.ASSEMBLY_HISTORIC_HEADING_FLIP = {
58
+ id: "assembly-historic-heading-flip",
59
+ what: "Adds 180 degrees to the root Entity's interpolated heading before the CAD tileset's UCS transform is written, in TilesetCadRenderManager's historic movement path.",
60
+ why: ("The assembly faced backwards along its own track for this customer's demo and nobody "
61
+ + "established whether the fault was in the recorded heading, the source model's axis "
62
+ + "convention, or our own transform. The correction was applied to everyone, which means "
63
+ + "any other account recording a correct heading has been rendered backwards ever since."),
64
+ raisedOn: "2026-08-31",
65
+ removeWhen: ("Someone loads this customer's assembly with the flip removed and confirms which "
66
+ + "of the three is actually wrong. If it is the source model, the fix belongs in the "
67
+ + "model or its import, not here."),
68
+ accounts: ["1185ae5ab0fb0794"]
69
+ };
70
+ /**
71
+ * Every exception, so this list is the one place to look.
72
+ */
73
+ CodeLogicExceptions.ALL = [
74
+ CodeLogicExceptions.ASSEMBLY_HISTORIC_HEADING_FLIP
75
+ ];
76
+ })(CodeLogicExceptions = exports.CodeLogicExceptions || (exports.CodeLogicExceptions = {}));
77
+ //# sourceMappingURL=code-logic-exceptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-logic-exceptions.js","sourceRoot":"","sources":["../../../src/internal/code-logic-exceptions.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,IAAiB,mBAAmB,CA0FnC;AA1FD,WAAiB,mBAAmB;IAgBhC,gGAAgG;IAChG,MAAM,SAAS,GAAG,UAAU,CAAC;IAC7B,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAE7C,SAAS,KAAK,CAAC,KAAa,EAAE,MAAc;QACxC,IAAI,IAAI,GAAG,MAAM,CAAC;QAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACnC,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC5B,4EAA4E;YAC5E,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;SAC3C;QACD,OAAO,IAAI,KAAK,CAAC,CAAC;IACtB,CAAC;IAED;;;;;;OAMG;IACH,SAAgB,aAAa,CAAC,SAAiB;QAC3C,MAAM,UAAU,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC1D,IAAI,CAAC,UAAU,EAAE;YACb,OAAO,IAAI,CAAC;SACf;QACD,OAAO,CACH,WAAW;aACN,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;aACtE,IAAI,CAAC,EAAE,CAAC,CAChB,CAAC;IACN,CAAC;IAVe,iCAAa,gBAU5B,CAAA;IAED;;;;;OAKG;IACH,SAAgB,SAAS,CAAC,SAAqB,EAAE,SAAiB;;QAC9D,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAA,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,QAAQ,0CAAE,MAAM,CAAA,EAAE;YACvC,OAAO,KAAK,CAAC;SAChB;QACD,OAAO,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACnD,CAAC;IANe,6BAAS,YAMxB,CAAA;IAED;;OAEG;IACU,kDAA8B,GAAe;QACtD,EAAE,EAAE,gCAAgC;QACpC,IAAI,EAAE,oKAAoK;QAC1K,GAAG,EAAE,CACD,uFAAuF;cACrF,qFAAqF;cACrF,wFAAwF;cACxF,uFAAuF,CAC5F;QACD,QAAQ,EAAE,YAAY;QACtB,UAAU,EAAE,CACR,kFAAkF;cAChF,oFAAoF;cACpF,gCAAgC,CACrC;QACD,QAAQ,EAAE,CAAC,kBAAkB,CAAC;KACjC,CAAC;IAEF;;OAEG;IACU,uBAAG,GAAiB;QAC7B,oBAAA,8BAA8B;KACjC,CAAC;AACN,CAAC,EA1FgB,mBAAmB,GAAnB,2BAAmB,KAAnB,2BAAmB,QA0FnC"}
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CesiumAnimatedProperty = void 0;
4
- const bruce_models_1 = require("bruce-models");
5
4
  const Cesium = require("cesium");
5
+ const drawing_utils_1 = require("../utils/drawing-utils");
6
6
  const entity_utils_1 = require("../utils/entity-utils");
7
+ const view_utils_1 = require("../utils/view-utils");
7
8
  function getColor(viewer, obj) {
8
9
  let value = null;
9
10
  if (obj === null || obj === void 0 ? void 0 : obj.getValue) {
@@ -368,6 +369,89 @@ var CesiumAnimatedProperty;
368
369
  }
369
370
  }
370
371
  CesiumAnimatedProperty.AnimateHeading = AnimateHeading;
372
+ // How much watching the trail represents.
373
+ CesiumAnimatedProperty.TRAIL_REAL_SECONDS = 20;
374
+ // A paused clock has no rate, so the trail falls back to this rather than vanishing.
375
+ CesiumAnimatedProperty.TRAIL_MIN_MS = 30 * 1000;
376
+ // And a hard scrub cannot ask for the whole archive.
377
+ CesiumAnimatedProperty.TRAIL_MAX_MS = 60 * 60 * 1000;
378
+ // A dense archive would otherwise build a polyline with more vertices than the scene can afford.
379
+ CesiumAnimatedProperty.TRAIL_MAX_POINTS = 500;
380
+ // And a sparse one would leave almost nothing to see, since the span is measured in time and says
381
+ // nothing about how often the thing being watched reports.
382
+ CesiumAnimatedProperty.TRAIL_MIN_POINTS = 8;
383
+ /**
384
+ * Returns how much scene time a trail should cover, for a clock moving at this rate.
385
+ * @param ratePerSecondMs milliseconds of scene time per real second.
386
+ * @param realSeconds how much watching the trail should represent.
387
+ */
388
+ function TrailSpanFor(ratePerSecondMs, realSeconds = CesiumAnimatedProperty.TRAIL_REAL_SECONDS, minMs = CesiumAnimatedProperty.TRAIL_MIN_MS, maxMs = CesiumAnimatedProperty.TRAIL_MAX_MS) {
389
+ const rate = Math.abs(ratePerSecondMs || 0);
390
+ const span = rate * realSeconds;
391
+ if (!(span > minMs)) {
392
+ return minMs;
393
+ }
394
+ return Math.min(span, maxMs);
395
+ }
396
+ CesiumAnimatedProperty.TrailSpanFor = TrailSpanFor;
397
+ /**
398
+ * Returns the positions a movement trail should run through, for a playhead at this time.
399
+ * @param params
400
+ */
401
+ function TrailPositionsFrom(params) {
402
+ const { positions, nowTimeMs, currentPos } = params;
403
+ if (!positions || positions.length < 2) {
404
+ return [];
405
+ }
406
+ const span = TrailSpanFor(params.ratePerSecondMs);
407
+ const firstMs = positions[0].dateTime.getTime();
408
+ const lastMs = positions[positions.length - 1].dateTime.getTime();
409
+ // A playhead nowhere near the records has no trail. Drawing the nearest leg anyway leaves a
410
+ // stub floating in the scene at a time the thing was not there.
411
+ if (nowTimeMs < firstMs - span || nowTimeMs > lastMs + span) {
412
+ return [];
413
+ }
414
+ // A trail is where the thing has been, so nothing ahead of the playhead belongs in it.
415
+ const behind = [];
416
+ for (const pos of positions) {
417
+ if (pos.pos3d && pos.dateTime.getTime() <= nowTimeMs) {
418
+ behind.push(pos);
419
+ }
420
+ }
421
+ const fromMs = nowTimeMs - span;
422
+ let firstInSpan = behind.length;
423
+ while (firstInSpan > 0 && behind[firstInSpan - 1].dateTime.getTime() >= fromMs) {
424
+ firstInSpan--;
425
+ }
426
+ // Sparse records would otherwise leave the span holding one point, which is not a line.
427
+ const from = Math.max(0, Math.min(firstInSpan, behind.length - CesiumAnimatedProperty.TRAIL_MIN_POINTS));
428
+ let newPosses = behind.slice(from).map(pos => pos.pos3d);
429
+ // The trail has to meet the graphic it trails, which is between two records most of the time.
430
+ if (currentPos && (!newPosses.length
431
+ || !Cesium.Cartesian3.equals(newPosses[newPosses.length - 1], currentPos))) {
432
+ newPosses.push(currentPos);
433
+ }
434
+ return DecimateSeries(newPosses, CesiumAnimatedProperty.TRAIL_MAX_POINTS);
435
+ }
436
+ CesiumAnimatedProperty.TrailPositionsFrom = TrailPositionsFrom;
437
+ /**
438
+ * Thins a run of positions down to at most maxPoints, keeping the first and last.
439
+ * @param posses
440
+ * @param maxPoints
441
+ */
442
+ function DecimateSeries(posses, maxPoints = CesiumAnimatedProperty.TRAIL_MAX_POINTS) {
443
+ if (!posses || posses.length <= maxPoints || maxPoints < 2) {
444
+ return posses;
445
+ }
446
+ const stride = (posses.length - 1) / (maxPoints - 1);
447
+ const out = [];
448
+ for (let i = 0; i < maxPoints - 1; i++) {
449
+ out.push(posses[Math.round(i * stride)]);
450
+ }
451
+ out.push(posses[posses.length - 1]);
452
+ return out;
453
+ }
454
+ CesiumAnimatedProperty.DecimateSeries = DecimateSeries;
371
455
  /**
372
456
  * Animates a position from a set of provided positions in time.
373
457
  * This will return a position across the provided positions based on the current time.
@@ -418,6 +502,20 @@ var CesiumAnimatedProperty;
418
502
  }
419
503
  return Cesium.JulianDate.toDate(viewerTime).getTime();
420
504
  }
505
+ /*
506
+ * Returns how fast scene time is passing, in milliseconds per real second.
507
+ */
508
+ ratePerSecondMs() {
509
+ var _a, _b;
510
+ if (this.useRealTime) {
511
+ return 1000;
512
+ }
513
+ const multiplier = (_b = (_a = this.viewer) === null || _a === void 0 ? void 0 : _a.clock) === null || _b === void 0 ? void 0 : _b.multiplier;
514
+ if (multiplier == null || isNaN(multiplier)) {
515
+ return 1000;
516
+ }
517
+ return Math.abs(multiplier) * 1000;
518
+ }
421
519
  SetUseRealTime(value) {
422
520
  const changed = this.useRealTime !== value;
423
521
  this.useRealTime = value;
@@ -717,27 +815,37 @@ var CesiumAnimatedProperty;
717
815
  this.lastCalcSeriesPos3d = [];
718
816
  return [];
719
817
  }
720
- const totalDuration = this.positions[this.positions.length - 1].dateTime.getTime() - this.positions[0].dateTime.getTime();
721
- const visibilityPercentage = 0.05;
722
- const visibilityDuration = totalDuration * visibilityPercentage;
723
- const newPosses = [];
724
- for (let i = 0; i < this.positions.length; i++) {
725
- const pos = this.positions[i];
726
- if (!pos.pos3d) {
727
- continue;
728
- }
729
- let add = nowDate >= new Date(pos.dateTime.getTime() - visibilityDuration / 2) && nowDate <= new Date(pos.dateTime.getTime() + visibilityDuration / 2);
730
- if (!add && this.lastDesiredPosIndex > -1 && this.lastDesiredNextIndex > -1) {
731
- add = i >= this.lastDesiredPosIndex && i <= this.lastDesiredNextIndex;
732
- }
733
- if (add) {
734
- newPosses.push(pos.pos3d);
735
- }
736
- }
818
+ const newPosses = TrailPositionsFrom({
819
+ positions: this.positions,
820
+ nowTimeMs: nowTimeMs,
821
+ ratePerSecondMs: this.ratePerSecondMs(),
822
+ currentPos: this.currentPos
823
+ });
737
824
  this.lastCalcSeriesTime = nowTimeMs;
738
825
  this.lastCalcSeriesPos3d = newPosses;
739
826
  return newPosses;
740
827
  }
828
+ /**
829
+ * Returns the heading carried by the last record at or before the playhead, in degrees, or
830
+ * null when no record in range names one.
831
+ */
832
+ GetHeadingDegrees() {
833
+ var _a;
834
+ if (!((_a = this.positions) === null || _a === void 0 ? void 0 : _a.length)) {
835
+ return null;
836
+ }
837
+ const nowTimeMs = this.getTimeMs();
838
+ let heading = null;
839
+ for (const pos of this.positions) {
840
+ if (pos.dateTime.getTime() > nowTimeMs) {
841
+ break;
842
+ }
843
+ if (pos.heading != null) {
844
+ heading = pos.heading;
845
+ }
846
+ }
847
+ return heading;
848
+ }
741
849
  GetOrient() {
742
850
  let doUpdate = this.lastCalcOrientTime == null;
743
851
  if (!doUpdate && this.lastCalcOrientTime && (new Date().getTime() - this.lastCalcOrientTime) > 1000 / 30) {
@@ -857,16 +965,16 @@ var CesiumAnimatedProperty;
857
965
  const existingIndex = dateTimeIndexes.get(pos.dateTime.getTime());
858
966
  if (existingIndex >= 0) {
859
967
  this.positions[existingIndex] = pos;
968
+ continue;
860
969
  }
861
- else {
862
- const newPoint = {
863
- pos: pos.pos3d.clone(),
864
- time: pos.dateTime.getTime(),
865
- realTime: Date.now()
866
- };
867
- this.positionHistory.push(newPoint);
868
- dateTimeIndexes.set(pos.dateTime.getTime(), this.positions.length);
869
- }
970
+ // A record at a time not seen before belongs in the series being animated along.
971
+ dateTimeIndexes.set(pos.dateTime.getTime(), this.positions.length);
972
+ this.positions.push(pos);
973
+ this.positionHistory.push({
974
+ pos: pos.pos3d.clone(),
975
+ time: pos.dateTime.getTime(),
976
+ realTime: Date.now()
977
+ });
870
978
  }
871
979
  if (this.positionHistory.length > this.maxHistorySize) {
872
980
  this.positionHistory = this.positionHistory.slice(-this.maxHistorySize);
@@ -905,7 +1013,44 @@ var CesiumAnimatedProperty;
905
1013
  }
906
1014
  AnimatePositionSeries.REAL_TIME_ANIM_DURATION_MS = 500;
907
1015
  CesiumAnimatedProperty.AnimatePositionSeries = AnimatePositionSeries;
1016
+ /*
1017
+ * Reads one transform component, tolerating either spelling, and keeping "not recorded" distinct from a recorded zero.
1018
+ * TODO: see when we actually have varied spelling, might be a result of misusing historic data.
1019
+ */
1020
+ function pick(transform, lower, upper) {
1021
+ var _a;
1022
+ const value = (_a = transform === null || transform === void 0 ? void 0 : transform[lower]) !== null && _a !== void 0 ? _a : transform === null || transform === void 0 ? void 0 : transform[upper];
1023
+ return value == null || isNaN(+value) ? null : +value;
1024
+ }
1025
+ /**
1026
+ * Returns a position at the height its graphic is actually drawn at.
1027
+ * Respecting the height ref means it is pointing the right way when we orient based on movement.
1028
+ * @param viewer
1029
+ * @param pos3d
1030
+ * @param heightRef
1031
+ */
1032
+ function GetDrawnHeightPos3d(viewer, pos3d, heightRef) {
1033
+ if (heightRef != Cesium.HeightReference.CLAMP_TO_GROUND || !pos3d) {
1034
+ return pos3d;
1035
+ }
1036
+ const offset = drawing_utils_1.DrawingUtils.GetTerrainOffset({
1037
+ viewer: viewer,
1038
+ pos3d: pos3d,
1039
+ terrainId: view_utils_1.ViewUtils.GetTerrainCacheId(viewer)
1040
+ });
1041
+ if (isNaN(offset)) {
1042
+ return null;
1043
+ }
1044
+ const carto = Cesium.Cartographic.fromCartesian(pos3d);
1045
+ if (!carto) {
1046
+ return pos3d;
1047
+ }
1048
+ carto.height = offset;
1049
+ return Cesium.Cartographic.toCartesian(carto);
1050
+ }
1051
+ CesiumAnimatedProperty.GetDrawnHeightPos3d = GetDrawnHeightPos3d;
908
1052
  function GetSeriesPossesForHistoricEntity(viewer, dataHeightRef, heightRef, historic) {
1053
+ var _a;
909
1054
  if (!historic || !historic.length) {
910
1055
  return [];
911
1056
  }
@@ -925,7 +1070,7 @@ var CesiumAnimatedProperty;
925
1070
  if (!dateTime || !dateTime.getTime()) {
926
1071
  continue;
927
1072
  }
928
- const pos3d = entity_utils_1.EntityUtils.GetPos({
1073
+ let pos3d = entity_utils_1.EntityUtils.GetPos({
929
1074
  entity: data,
930
1075
  viewer: viewer,
931
1076
  recordHeightRef: dataHeightRef,
@@ -935,13 +1080,18 @@ var CesiumAnimatedProperty;
935
1080
  if (!pos3d || isNaN(pos3d.x) || isNaN(pos3d.y) || isNaN(pos3d.z)) {
936
1081
  continue;
937
1082
  }
1083
+ pos3d = GetDrawnHeightPos3d(viewer, pos3d, heightRef);
1084
+ if (!pos3d) {
1085
+ // Ground not resolved yet. A later pass re-reads the records and will place it.
1086
+ continue;
1087
+ }
1088
+ const transform = (_a = data.Bruce) === null || _a === void 0 ? void 0 : _a.Transform;
938
1089
  series.push({
939
1090
  dateTime,
940
1091
  pos3d: pos3d,
941
- heading: bruce_models_1.Entity.GetValue({
942
- entity: data,
943
- path: ["Bruce", "Transform", "Heading"]
944
- })
1092
+ heading: pick(transform, "heading", "Heading"),
1093
+ pitch: pick(transform, "pitch", "Pitch"),
1094
+ roll: pick(transform, "roll", "Roll")
945
1095
  });
946
1096
  }
947
1097
  return series;
@@ -949,6 +1099,7 @@ var CesiumAnimatedProperty;
949
1099
  CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity = GetSeriesPossesForHistoricEntity;
950
1100
  class AnimateTPositionSeries {
951
1101
  constructor(params) {
1102
+ this.lastCalcAttitude = null;
952
1103
  // Cache for calculated values.
953
1104
  this.lastCalcTime = null;
954
1105
  this.lastCalcPos3d = null;
@@ -1017,6 +1168,21 @@ var CesiumAnimatedProperty;
1017
1168
  maxDate: this.maxDate
1018
1169
  };
1019
1170
  }
1171
+ /**
1172
+ * Returns the positions a movement trail through this series should run through.
1173
+ * The same window an Entity's own trail uses, so a root trail matches one drawn per Entity.
1174
+ */
1175
+ GetSeries() {
1176
+ var _a, _b;
1177
+ const viewerTime = this.viewer.scene.lastRenderTime || this.viewer.clock.currentTime;
1178
+ const multiplier = (_b = (_a = this.viewer) === null || _a === void 0 ? void 0 : _a.clock) === null || _b === void 0 ? void 0 : _b.multiplier;
1179
+ return TrailPositionsFrom({
1180
+ positions: this.positions,
1181
+ nowTimeMs: Cesium.JulianDate.toDate(viewerTime).getTime(),
1182
+ ratePerSecondMs: multiplier == null || isNaN(multiplier) ? 1000 : Math.abs(multiplier) * 1000,
1183
+ currentPos: this.lastCalcPos3d
1184
+ });
1185
+ }
1020
1186
  /**
1021
1187
  * Trigger the onRangeChange callback to request more data.
1022
1188
  */
@@ -1037,6 +1203,14 @@ var CesiumAnimatedProperty;
1037
1203
  }
1038
1204
  }
1039
1205
  }
1206
+ /*
1207
+ * Returns the attitude of the record at an index, or nothing recorded when it names none.
1208
+ */
1209
+ attitudeAt(index) {
1210
+ var _a, _b, _c;
1211
+ const pos = index >= 0 ? (_a = this.positions) === null || _a === void 0 ? void 0 : _a[index] : null;
1212
+ 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 };
1213
+ }
1040
1214
  update() {
1041
1215
  if (!this.positions || this.positions.length === 0) {
1042
1216
  return;
@@ -1051,7 +1225,8 @@ var CesiumAnimatedProperty;
1051
1225
  // Initialize if this is the first update.
1052
1226
  if (!this.lastCalcPos3d) {
1053
1227
  this.lastCalcPos3d = targetPosition.pos3d;
1054
- this.onUpdate(targetPosition.pos3d, this.lastCalcHeading);
1228
+ this.lastCalcAttitude = this.attitudeAt(targetPosition.indexLast);
1229
+ this.onUpdate(targetPosition.pos3d, this.lastCalcHeading, this.lastCalcAttitude);
1055
1230
  return;
1056
1231
  }
1057
1232
  // Check if target has changed.
@@ -1113,7 +1288,8 @@ var CesiumAnimatedProperty;
1113
1288
  }
1114
1289
  }
1115
1290
  // Provide the calculated values to the caller.
1116
- this.onUpdate(pos3d, this.lastCalcHeading);
1291
+ this.lastCalcAttitude = this.attitudeAt(targetPosition.indexLast);
1292
+ this.onUpdate(pos3d, this.lastCalcHeading, this.lastCalcAttitude);
1117
1293
  }
1118
1294
  interpolatePos3dOverTime(startTimeMs, currentTimeMs) {
1119
1295
  const lastPos = this.lastCalcPos3d;