bruce-cesium 6.7.9 → 6.8.0

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 (25) hide show
  1. package/dist/bruce-cesium.es5.js +743 -157
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +743 -157
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/rendering/menu-item-manager.js +75 -6
  7. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  8. package/dist/lib/rendering/render-managers/render-manager.js.map +1 -1
  9. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +1 -1
  10. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  11. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +147 -3
  12. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  13. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +151 -3
  14. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  15. package/dist/lib/rendering/view-render-engine.js +8 -2
  16. package/dist/lib/rendering/view-render-engine.js.map +1 -1
  17. package/dist/lib/rendering/visuals-register.js +360 -141
  18. package/dist/lib/rendering/visuals-register.js.map +1 -1
  19. package/dist/types/bruce-cesium.d.ts +1 -1
  20. package/dist/types/rendering/menu-item-manager.d.ts +6 -0
  21. package/dist/types/rendering/render-managers/render-manager.d.ts +30 -0
  22. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +10 -1
  23. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +10 -1
  24. package/dist/types/rendering/visuals-register.d.ts +39 -3
  25. package/package.json +8 -2
@@ -368,6 +368,18 @@ var VisualsRegister;
368
368
  this.id = bruce_models_1.ObjectUtils.UId();
369
369
  this.disposed = false;
370
370
  this.rego = {};
371
+ // Menu Item ID -> Set of Entity IDs registered under that menu item, for O(1) lookup.
372
+ this.entityIdsByMenuItem = {};
373
+ // Caches GetIsIsolatedAny(). `null` means "unknown, recompute".
374
+ // Invalidated on any write that could affect isolation.
375
+ this.isolatedAnyCache = null;
376
+ // Lazy OverrideStates() bookkeeping. See cancelLazyOverride()/startLazyOverride().
377
+ this.lazyOverrideToken = 0;
378
+ this.lazyOverrideActive = false;
379
+ // Entity ids a direct (non-lazy) write touched while a lazy apply was in-flight-
380
+ // skipped by the applier so a live user action always wins.
381
+ this.lazyOverrideExclusions = new Set();
382
+ this.lazyOverrideInterval = null;
371
383
  this.onUpdate = null;
372
384
  // Entity ID -> Menu Item ID -> State.
373
385
  // When no Menu Item ID is set, then the second key is a blank string.
@@ -375,6 +387,8 @@ var VisualsRegister;
375
387
  this.states = {};
376
388
  // Queue of Entity updates to process in batches to avoid overloading the render loop.
377
389
  this.updateQueue = [];
390
+ // Mirrors updateQueue for O(1) "already queued" checks.
391
+ this.updateQueueIds = new Set();
378
392
  // Settings related to those Entities in the queue.
379
393
  // This lets us modify the update without re-adding the item to the queue if it's already in it.
380
394
  this.updateQueueSettings = {};
@@ -385,6 +399,42 @@ var VisualsRegister;
385
399
  register: this
386
400
  });
387
401
  }
402
+ indexMenuItemEntity(menuItemId, entityId) {
403
+ if (!menuItemId) {
404
+ return;
405
+ }
406
+ let set = this.entityIdsByMenuItem[menuItemId];
407
+ if (!set) {
408
+ set = this.entityIdsByMenuItem[menuItemId] = new Set();
409
+ }
410
+ set.add(entityId);
411
+ }
412
+ // Removes entityId from the menu item index only if it no longer has any rego
413
+ // registered under that menu item. Safe to call speculatively.
414
+ unindexMenuItemEntityIfAbsent(menuItemId, entityId) {
415
+ var _a;
416
+ if (!menuItemId) {
417
+ return;
418
+ }
419
+ const set = this.entityIdsByMenuItem[menuItemId];
420
+ if (!set) {
421
+ return;
422
+ }
423
+ const stillPresent = (_a = this.rego[entityId]) === null || _a === void 0 ? void 0 : _a.some(r => r.menuItemId === menuItemId);
424
+ if (!stillPresent) {
425
+ set.delete(entityId);
426
+ if (!set.size) {
427
+ delete this.entityIdsByMenuItem[menuItemId];
428
+ }
429
+ }
430
+ }
431
+ // Records that `entityId` was just written to directly, outside of any lazy
432
+ // OverrideStates() apply. No-op unless a lazy apply is currently in-flight.
433
+ markLazyExclusion(entityId) {
434
+ if (this.lazyOverrideActive) {
435
+ this.lazyOverrideExclusions.add(entityId);
436
+ }
437
+ }
388
438
  queueUpdate(update) {
389
439
  if (this.Disposed) {
390
440
  return;
@@ -417,7 +467,8 @@ var VisualsRegister;
417
467
  }
418
468
  this.updateQueueSettings[update.entityId] = settings;
419
469
  // Queue ID if it's not already in the queue.
420
- if (!this.updateQueue.includes(update.entityId)) {
470
+ if (!this.updateQueueIds.has(update.entityId)) {
471
+ this.updateQueueIds.add(update.entityId);
421
472
  this.updateQueue.push(update.entityId);
422
473
  this.pingUpdateQueue();
423
474
  }
@@ -438,6 +489,7 @@ var VisualsRegister;
438
489
  const cache = {};
439
490
  for (let i = 0; i < batch.length; i++) {
440
491
  const entityId = batch[i];
492
+ this.updateQueueIds.delete(entityId);
441
493
  // Check if still registered.
442
494
  if (entityId && (!this.rego[entityId] || !this.rego[entityId].length)) {
443
495
  continue;
@@ -550,6 +602,8 @@ var VisualsRegister;
550
602
  SetStates(states, source) {
551
603
  // Array of changed Entity IDs so we know what to refresh/notify at the end.
552
604
  const entityIds = [];
605
+ // Mirrors entityIds for O(1) "already recorded" checks.
606
+ const entityIdsSeen = new Set();
553
607
  // If we need to update all Entities.
554
608
  // This is usually for isolation changes.
555
609
  let updateAll = false;
@@ -582,8 +636,9 @@ var VisualsRegister;
582
636
  };
583
637
  const doUpdate = (state) => {
584
638
  const eChanged = this.SetState(state, false, source);
585
- if (eChanged && !entityIds.includes(state.entityId)) {
639
+ if (eChanged && !entityIdsSeen.has(state.entityId)) {
586
640
  updateRefreshMarkers(state);
641
+ entityIdsSeen.add(state.entityId);
587
642
  entityIds.push(state.entityId);
588
643
  changed = true;
589
644
  }
@@ -618,7 +673,7 @@ var VisualsRegister;
618
673
  for (let i = 0; i < keys.length; i++) {
619
674
  const key = keys[i];
620
675
  const state = states[key];
621
- if (entityIds.includes(state.entityId)) {
676
+ if (entityIdsSeen.has(state.entityId)) {
622
677
  const update = {
623
678
  type: EVisualUpdateType.Update,
624
679
  entityId: state.entityId
@@ -648,12 +703,18 @@ var VisualsRegister;
648
703
  * Sets the provided settings for a given Entity's state.
649
704
  * @param params
650
705
  */
651
- setStateValues(values) {
706
+ setStateValues(values, fromLazyApplier = false) {
652
707
  const { entityId, menuItemId } = values;
653
708
  const keys = Object.keys(values).filter(k => k !== "entityId" && k !== "menuItemId");
654
709
  if (!keys.length) {
655
710
  return false;
656
711
  }
712
+ if (!fromLazyApplier) {
713
+ this.markLazyExclusion(entityId);
714
+ }
715
+ if (keys.includes("isolated")) {
716
+ this.isolatedAnyCache = null;
717
+ }
657
718
  let changed = false;
658
719
  const eStates = this.states[entityId] || {};
659
720
  this.states[entityId] = eStates;
@@ -824,18 +885,68 @@ var VisualsRegister;
824
885
  }
825
886
  return false;
826
887
  }
888
+ buildClearDiffItem(state) {
889
+ const clearState = {
890
+ entityId: state.entityId,
891
+ menuItemId: state.menuItemId
892
+ };
893
+ const refresh = {};
894
+ // For each property that exists, set it to null to clear it.
895
+ // This will cause setStateValues to delete the property.
896
+ if (state.highlighted != null) {
897
+ clearState.highlighted = null;
898
+ refresh.highlighted = true;
899
+ }
900
+ if (state.selected != null) {
901
+ clearState.selected = null;
902
+ refresh.selected = true;
903
+ }
904
+ if (state.opacity != null) {
905
+ clearState.opacity = null;
906
+ refresh.opacity = true;
907
+ }
908
+ if (state.labelled != null) {
909
+ clearState.labelled = null;
910
+ }
911
+ if (state.hidden != null) {
912
+ clearState.hidden = null;
913
+ }
914
+ if (state.isolated != null) {
915
+ clearState.isolated = null;
916
+ }
917
+ return { clearState, refresh };
918
+ }
919
+ /**
920
+ * Refresh flags needed for an "add"/"update" state - the new value is applied as-is,
921
+ * this just says which visual properties changed.
922
+ */
923
+ buildSetDiffRefresh(state) {
924
+ const refresh = {};
925
+ if (state.hasOwnProperty("highlighted")) {
926
+ refresh.highlighted = true;
927
+ }
928
+ if (state.hasOwnProperty("selected")) {
929
+ refresh.selected = true;
930
+ }
931
+ if (state.hasOwnProperty("opacity")) {
932
+ refresh.opacity = true;
933
+ }
934
+ return refresh;
935
+ }
827
936
  /**
828
937
  * Applies a set of new states to override the existing ones using differential updates.
829
- * This is more efficient than clearing and re-applying all states.
830
- * @param states
831
938
  */
832
- OverrideStates(states) {
833
- // Calculate what needs to change.
939
+ OverrideStates(states, options) {
940
+ this.cancelLazyOverride();
834
941
  const diff = this.calculateStateDifference(states);
835
942
  // If nothing changed, exit early.
836
943
  if (diff.toAdd.length === 0 && diff.toRemove.length === 0 && diff.toUpdate.length === 0) {
837
944
  return;
838
945
  }
946
+ if (options === null || options === void 0 ? void 0 : options.lazy) {
947
+ this.startLazyOverride(diff);
948
+ return;
949
+ }
839
950
  // Track which visual properties need refreshing per entity.
840
951
  const refreshMap = new Map();
841
952
  const updateRefreshForEntity = (entityId, props) => {
@@ -846,76 +957,16 @@ var VisualsRegister;
846
957
  opacity: existing.opacity || props.opacity || false
847
958
  });
848
959
  };
849
- // Process removals.
850
960
  for (const state of diff.toRemove) {
851
- // Create a state with inverted/cleared values.
852
- const clearState = {
853
- entityId: state.entityId,
854
- menuItemId: state.menuItemId
855
- };
856
- // For each property that exists, set it to null to clear it.
857
- // This will cause setStateValues to delete the property.
858
- if (state.highlighted != null) {
859
- clearState.highlighted = null;
860
- updateRefreshForEntity(state.entityId, {
861
- highlighted: true
862
- });
863
- }
864
- if (state.selected != null) {
865
- clearState.selected = null;
866
- updateRefreshForEntity(state.entityId, {
867
- selected: true
868
- });
869
- }
870
- if (state.opacity != null) {
871
- clearState.opacity = null;
872
- updateRefreshForEntity(state.entityId, {
873
- opacity: true
874
- });
875
- }
876
- if (state.labelled != null) {
877
- clearState.labelled = null;
878
- updateRefreshForEntity(state.entityId, {});
879
- }
880
- if (state.hidden != null) {
881
- clearState.hidden = null;
882
- updateRefreshForEntity(state.entityId, {});
883
- }
884
- if (state.isolated != null) {
885
- clearState.isolated = null;
886
- updateRefreshForEntity(state.entityId, {});
887
- }
961
+ const { clearState, refresh } = this.buildClearDiffItem(state);
962
+ updateRefreshForEntity(state.entityId, refresh);
888
963
  this.setStateValues(clearState);
889
964
  }
890
- // Process additions and updates.
891
965
  const statesToSet = [...diff.toAdd, ...diff.toUpdate];
892
966
  for (const state of statesToSet) {
893
- // Determine what needs visual refresh based on what's changing.
894
- if (state.hasOwnProperty("highlighted")) {
895
- updateRefreshForEntity(state.entityId, {
896
- highlighted: true
897
- });
898
- }
899
- if (state.hasOwnProperty("selected")) {
900
- updateRefreshForEntity(state.entityId, {
901
- selected: true
902
- });
903
- }
904
- if (state.hasOwnProperty("opacity")) {
905
- updateRefreshForEntity(state.entityId, {
906
- opacity: true
907
- });
908
- }
909
- if (state.hasOwnProperty("labelled")) {
910
- updateRefreshForEntity(state.entityId, {});
911
- }
912
- if (state.hasOwnProperty("hidden") || state.hasOwnProperty("isolated")) {
913
- updateRefreshForEntity(state.entityId, {});
914
- }
967
+ updateRefreshForEntity(state.entityId, this.buildSetDiffRefresh(state));
915
968
  this.setStateValues(state);
916
969
  }
917
- // Queue updates for all affected entities.
918
- // TODO: if a massive list we should async batch this.
919
970
  for (const entityId of diff.affectedEntityIds) {
920
971
  const refresh = refreshMap.get(entityId);
921
972
  this.queueUpdate({
@@ -925,6 +976,77 @@ var VisualsRegister;
925
976
  }
926
977
  this.viewer.scene.requestRender();
927
978
  }
979
+ /**
980
+ * Cancels any in-flight lazy OverrideStates() apply.
981
+ */
982
+ cancelLazyOverride() {
983
+ if (!this.lazyOverrideActive) {
984
+ return;
985
+ }
986
+ this.lazyOverrideToken++;
987
+ this.lazyOverrideActive = false;
988
+ this.lazyOverrideExclusions.clear();
989
+ if (this.lazyOverrideInterval) {
990
+ clearInterval(this.lazyOverrideInterval);
991
+ this.lazyOverrideInterval = null;
992
+ }
993
+ }
994
+ /**
995
+ * Starts (or restarts) a chunked, cancellable apply of a previously-computed diff.
996
+ * See OverrideStates() for the cancellation/priority semantics.
997
+ */
998
+ startLazyOverride(diff) {
999
+ const BATCH_SIZE = 2000;
1000
+ const queue = [];
1001
+ for (const state of diff.toRemove) {
1002
+ queue.push({ state, isRemoval: true });
1003
+ }
1004
+ for (const state of diff.toAdd) {
1005
+ queue.push({ state, isRemoval: false });
1006
+ }
1007
+ for (const state of diff.toUpdate) {
1008
+ queue.push({ state, isRemoval: false });
1009
+ }
1010
+ this.lazyOverrideToken++;
1011
+ const token = this.lazyOverrideToken;
1012
+ this.lazyOverrideActive = true;
1013
+ this.lazyOverrideExclusions.clear();
1014
+ let index = 0;
1015
+ this.lazyOverrideInterval = setInterval(() => {
1016
+ // A newer lazy call (or a sync OverrideStates()) superseded this run.
1017
+ if (token !== this.lazyOverrideToken || this.Disposed) {
1018
+ clearInterval(this.lazyOverrideInterval);
1019
+ this.lazyOverrideInterval = null;
1020
+ return;
1021
+ }
1022
+ const end = Math.min(index + BATCH_SIZE, queue.length);
1023
+ for (; index < end; index++) {
1024
+ const { state, isRemoval } = queue[index];
1025
+ // A direct write already claimed this entity, the bookmark's still-pending
1026
+ // value for it is dropped for the rest of this run.
1027
+ if (this.lazyOverrideExclusions.has(state.entityId)) {
1028
+ continue;
1029
+ }
1030
+ if (isRemoval) {
1031
+ const { clearState, refresh } = this.buildClearDiffItem(state);
1032
+ this.setStateValues(clearState, true);
1033
+ this.queueUpdate({ entityId: state.entityId, refresh });
1034
+ }
1035
+ else {
1036
+ const refresh = this.buildSetDiffRefresh(state);
1037
+ this.setStateValues(state, true);
1038
+ this.queueUpdate({ entityId: state.entityId, refresh });
1039
+ }
1040
+ }
1041
+ this.viewer.scene.requestRender();
1042
+ if (index >= queue.length) {
1043
+ clearInterval(this.lazyOverrideInterval);
1044
+ this.lazyOverrideInterval = null;
1045
+ this.lazyOverrideActive = false;
1046
+ this.lazyOverrideExclusions.clear();
1047
+ }
1048
+ }, 10);
1049
+ }
928
1050
  /**
929
1051
  * Returns all states with the first detected Menu Item ID settings included.
930
1052
  * This is a utility function for returning settings for apps that don't support the newer state settings.
@@ -940,9 +1062,8 @@ var VisualsRegister;
940
1062
  if (!eStates) {
941
1063
  continue;
942
1064
  }
943
- if (!entityIds.includes(entityId)) {
944
- entityIds.push(entityId);
945
- }
1065
+ // Keys of this.states are already unique, no dedupe check needed.
1066
+ entityIds.push(entityId);
946
1067
  const mKeys = Object.keys(eStates);
947
1068
  for (let i = 0; i < mKeys.length; i++) {
948
1069
  const menuItemId = mKeys[i];
@@ -1008,6 +1129,9 @@ var VisualsRegister;
1008
1129
  this.cameraCullerDispose = null;
1009
1130
  clearInterval(this.updateQueueInterval);
1010
1131
  this.updateQueueInterval = null;
1132
+ clearInterval(this.lazyOverrideInterval);
1133
+ this.lazyOverrideInterval = null;
1134
+ this.lazyOverrideActive = false;
1011
1135
  this.disposed = true;
1012
1136
  }
1013
1137
  ForceUpdate(params) {
@@ -1079,12 +1203,15 @@ var VisualsRegister;
1079
1203
  ClearLabelled(params) {
1080
1204
  const cache = {};
1081
1205
  const toUpdateIds = [];
1206
+ const toUpdateIdsSeen = new Set();
1082
1207
  this.ForEachState((state) => {
1083
1208
  if (state.labelled == null || state.labelled === undefined) {
1084
1209
  return;
1085
1210
  }
1211
+ this.markLazyExclusion(state.entityId);
1086
1212
  delete state.labelled;
1087
- if (!toUpdateIds.includes(state.entityId)) {
1213
+ if (!toUpdateIdsSeen.has(state.entityId)) {
1214
+ toUpdateIdsSeen.add(state.entityId);
1088
1215
  toUpdateIds.push(state.entityId);
1089
1216
  }
1090
1217
  });
@@ -1212,11 +1339,12 @@ var VisualsRegister;
1212
1339
  }
1213
1340
  ClearSelected(params) {
1214
1341
  var _a;
1215
- const cleared = [];
1342
+ const cleared = new Set();
1216
1343
  this.ForEachState((state) => {
1217
1344
  if (state.selected == null || state.selected === undefined) {
1218
1345
  return;
1219
1346
  }
1347
+ this.markLazyExclusion(state.entityId);
1220
1348
  delete state.selected;
1221
1349
  const regos = this.GetRegos({
1222
1350
  entityId: state.entityId,
@@ -1224,13 +1352,13 @@ var VisualsRegister;
1224
1352
  });
1225
1353
  for (let i = 0; i < regos.length; i++) {
1226
1354
  const rego = regos[i];
1227
- if ((rego === null || rego === void 0 ? void 0 : rego.visual) && !cleared.includes(rego)) {
1355
+ if ((rego === null || rego === void 0 ? void 0 : rego.visual) && !cleared.has(rego)) {
1228
1356
  cesium_entity_styler_1.CesiumEntityStyler.Deselect({
1229
1357
  entity: rego.visual,
1230
1358
  viewer: this.viewer,
1231
1359
  requestRender: false
1232
1360
  });
1233
- cleared.push(rego);
1361
+ cleared.add(rego);
1234
1362
  }
1235
1363
  }
1236
1364
  });
@@ -1343,10 +1471,13 @@ var VisualsRegister;
1343
1471
  var _a;
1344
1472
  // Null all states.
1345
1473
  let entityIds = [];
1474
+ const entityIdsSeen = new Set();
1346
1475
  this.ForEachState((state) => {
1347
1476
  if (state.highlighted) {
1477
+ this.markLazyExclusion(state.entityId);
1348
1478
  delete state.highlighted;
1349
- if (!entityIds.includes(state.entityId)) {
1479
+ if (!entityIdsSeen.has(state.entityId)) {
1480
+ entityIdsSeen.add(state.entityId);
1350
1481
  entityIds.push(state.entityId);
1351
1482
  }
1352
1483
  }
@@ -1435,7 +1566,10 @@ var VisualsRegister;
1435
1566
  return state.isolated === true;
1436
1567
  }
1437
1568
  GetIsIsolatedAny() {
1438
- return Object.values(this.states).some(state => state && Object.values(state).some(item => item.isolated));
1569
+ if (this.isolatedAnyCache == null) {
1570
+ this.isolatedAnyCache = Object.values(this.states).some(state => state && Object.values(state).some(item => item.isolated));
1571
+ }
1572
+ return this.isolatedAnyCache;
1439
1573
  }
1440
1574
  /**
1441
1575
  * @deprecated Use GetStates() or GetState().
@@ -1456,10 +1590,15 @@ var VisualsRegister;
1456
1590
  this.ForEachState((state) => {
1457
1591
  if (state.isolated) {
1458
1592
  changed = true;
1593
+ this.markLazyExclusion(state.entityId);
1459
1594
  }
1460
1595
  delete state.isolated;
1461
1596
  });
1462
- if (changed && params.doUpdate !== false) {
1597
+ if (changed) {
1598
+ // Mutated `isolated` directly rather than through setStateValues().
1599
+ this.isolatedAnyCache = false;
1600
+ }
1601
+ if (changed && (params === null || params === void 0 ? void 0 : params.doUpdate) !== false) {
1463
1602
  this.updateAllEntities({
1464
1603
  requestRender: params === null || params === void 0 ? void 0 : params.requestRender,
1465
1604
  refresh: true
@@ -1510,10 +1649,11 @@ var VisualsRegister;
1510
1649
  this.ForEachState((state) => {
1511
1650
  if (state.hidden) {
1512
1651
  changed = true;
1652
+ this.markLazyExclusion(state.entityId);
1513
1653
  }
1514
1654
  delete state.hidden;
1515
1655
  });
1516
- if (changed && params.doUpdate !== false) {
1656
+ if (changed && (params === null || params === void 0 ? void 0 : params.doUpdate) !== false) {
1517
1657
  this.updateAllEntities({
1518
1658
  requestRender: params === null || params === void 0 ? void 0 : params.requestRender,
1519
1659
  refresh: true
@@ -1549,6 +1689,7 @@ var VisualsRegister;
1549
1689
  const entityRegos = (_a = this.rego[entityId]) !== null && _a !== void 0 ? _a : [];
1550
1690
  entityRegos.push(rego);
1551
1691
  this.rego[entityId] = entityRegos;
1692
+ this.indexMenuItemEntity(rego.menuItemId, entityId);
1552
1693
  // Mark the visual as part of this register so selection works.
1553
1694
  markEntity(this, rego, rego.visual, false);
1554
1695
  // Run any updates on the visual based on the calculated state.
@@ -1572,6 +1713,74 @@ var VisualsRegister;
1572
1713
  this.viewer.scene.requestRender();
1573
1714
  }
1574
1715
  }
1716
+ /**
1717
+ * Re-parents every rego registered under one menu item id to a different menu item id.
1718
+ */
1719
+ ReassignMenuItem(params) {
1720
+ var _a, _b;
1721
+ const { fromMenuItemId, toMenuItemId, toMenuItemType, toPriority, requestRender, source } = params;
1722
+ if (!fromMenuItemId || !toMenuItemId || fromMenuItemId === toMenuItemId) {
1723
+ return;
1724
+ }
1725
+ const entityIds = this.entityIdsByMenuItem[fromMenuItemId];
1726
+ if (!entityIds) {
1727
+ return;
1728
+ }
1729
+ // Snapshot: unindexMenuItemEntityIfAbsent() below mutates this same set.
1730
+ for (const entityId of Array.from(entityIds)) {
1731
+ const regos = this.rego[entityId];
1732
+ if (!regos) {
1733
+ continue;
1734
+ }
1735
+ for (const rego of regos) {
1736
+ if (rego.menuItemId !== fromMenuItemId) {
1737
+ continue;
1738
+ }
1739
+ if (source !== EUpdateSource.TREE_CASCADE) {
1740
+ (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger({
1741
+ type: EVisualUpdateType.Remove,
1742
+ entityId,
1743
+ rego: { ...rego, menuItemId: fromMenuItemId },
1744
+ source
1745
+ });
1746
+ }
1747
+ rego.menuItemId = toMenuItemId;
1748
+ rego.menuItemType = toMenuItemType;
1749
+ if (toPriority != null) {
1750
+ rego.priority = toPriority;
1751
+ }
1752
+ if (source !== EUpdateSource.TREE_CASCADE) {
1753
+ (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({
1754
+ type: EVisualUpdateType.Add,
1755
+ entityId,
1756
+ rego,
1757
+ source
1758
+ });
1759
+ }
1760
+ }
1761
+ const eStates = this.states[entityId];
1762
+ const movingState = eStates === null || eStates === void 0 ? void 0 : eStates[fromMenuItemId];
1763
+ if (movingState) {
1764
+ delete eStates[fromMenuItemId];
1765
+ const existingTargetState = eStates[toMenuItemId];
1766
+ eStates[toMenuItemId] = {
1767
+ ...existingTargetState,
1768
+ ...movingState,
1769
+ entityId,
1770
+ menuItemId: toMenuItemId
1771
+ };
1772
+ }
1773
+ this.unindexMenuItemEntityIfAbsent(fromMenuItemId, entityId);
1774
+ this.indexMenuItemEntity(toMenuItemId, entityId);
1775
+ this.queueUpdate({
1776
+ entityId,
1777
+ refresh: true
1778
+ });
1779
+ }
1780
+ if (requestRender != false) {
1781
+ this.viewer.scene.requestRender();
1782
+ }
1783
+ }
1575
1784
  /**
1576
1785
  * Locates a visual corresponding to a given entity id.
1577
1786
  * If no menu item id is provided, then it will pick the "best" one.
@@ -1682,7 +1891,10 @@ var VisualsRegister;
1682
1891
  // TODO: refactor.
1683
1892
  // Currently this was made by merging two functions.
1684
1893
  if (menuItemId && !entityId) {
1685
- for (const entityId in this.rego) {
1894
+ // Entities registered under this menu item, keyed via entityIdsByMenuItem.
1895
+ // Snapshot to an array since removals below mutate the backing set.
1896
+ const entityIds = this.entityIdsByMenuItem[menuItemId] ? Array.from(this.entityIdsByMenuItem[menuItemId]) : [];
1897
+ for (const entityId of entityIds) {
1686
1898
  const entityRegos = this.rego[entityId];
1687
1899
  if (entityRegos) {
1688
1900
  const rego = entityRegos.find(r => r.menuItemId === menuItemId);
@@ -1704,6 +1916,7 @@ var VisualsRegister;
1704
1916
  const doesInclude = this.rego[entityId].find(r => r.menuItemId === menuItemId);
1705
1917
  if (doesInclude) {
1706
1918
  this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
1919
+ this.unindexMenuItemEntityIfAbsent(menuItemId, entityId);
1707
1920
  }
1708
1921
  const update = {
1709
1922
  type: EVisualUpdateType.Remove,
@@ -1756,6 +1969,7 @@ var VisualsRegister;
1756
1969
  (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger(update);
1757
1970
  }
1758
1971
  this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
1972
+ this.unindexMenuItemEntityIfAbsent(menuItemId, entityId);
1759
1973
  if (doUpdate && menuItemId) {
1760
1974
  this.queueUpdate({
1761
1975
  entityId: entityId,
@@ -1778,7 +1992,11 @@ var VisualsRegister;
1778
1992
  });
1779
1993
  removeEntity(this.viewer, rego.visual);
1780
1994
  rego.visual = null;
1781
- this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
1995
+ this.rego[entityId] = entityRegos.filter(r => r !== rego);
1996
+ if (!this.rego[entityId].length) {
1997
+ delete this.rego[entityId];
1998
+ }
1999
+ this.unindexMenuItemEntityIfAbsent(rego.menuItemId, entityId);
1782
2000
  const update = {
1783
2001
  type: EVisualUpdateType.Remove,
1784
2002
  entityId: rego.entityId,
@@ -1790,7 +2008,7 @@ var VisualsRegister;
1790
2008
  if (source !== EUpdateSource.TREE_CASCADE) {
1791
2009
  (_c = this.onUpdate) === null || _c === void 0 ? void 0 : _c.Trigger(update);
1792
2010
  }
1793
- if (doUpdate && menuItemId) {
2011
+ if (doUpdate) {
1794
2012
  this.queueUpdate({
1795
2013
  entityId: entityId,
1796
2014
  refresh: false
@@ -1807,68 +2025,68 @@ var VisualsRegister;
1807
2025
  * @param params
1808
2026
  */
1809
2027
  RemoveRegosByVisuals(params) {
1810
- var _a, _b, _c;
2028
+ var _a, _b;
1811
2029
  const { doRemove = true, requestRender = true, source, doUpdate, menuItemId } = params;
1812
- const entityIds = Object.keys(this.rego);
1813
- const removedEntityIds = [];
1814
- for (const entityId of entityIds) {
1815
- const regos = this.rego[entityId];
1816
- if (regos && regos.length) {
1817
- for (const rego of regos) {
1818
- if (params.visuals.includes(rego.visual)) {
1819
- entity_label_1.EntityLabel.Detatch({
1820
- rego
1821
- });
1822
- if (doRemove != false) {
1823
- removeEntity(this.viewer, rego.visual);
1824
- }
1825
- rego.visual = null;
1826
- this.rego[entityId] = regos.filter(r => r.visual !== rego.visual);
1827
- const update = {
1828
- type: EVisualUpdateType.Remove,
1829
- entityId: rego.entityId,
1830
- rego: rego
1831
- };
1832
- if (source) {
1833
- update.source = source;
1834
- }
1835
- if (source !== EUpdateSource.TREE_CASCADE) {
1836
- (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(update);
1837
- }
1838
- if (doUpdate && menuItemId) {
1839
- this.queueUpdate({
1840
- entityId: entityId,
1841
- refresh: false
1842
- });
1843
- }
1844
- if (!removedEntityIds.includes(rego.entityId)) {
1845
- removedEntityIds.push(rego.entityId);
1846
- }
1847
- }
1848
- // Check siblings.
1849
- else if ((_b = rego.visual) === null || _b === void 0 ? void 0 : _b["_siblingGraphics"]) {
1850
- let siblings = rego.visual["_siblingGraphics"];
1851
- if (doRemove != false) {
1852
- for (const sibling of siblings) {
1853
- if (params.visuals.includes(sibling)) {
1854
- removeEntity(this.viewer, sibling);
1855
- }
1856
- }
1857
- }
1858
- siblings = siblings.filter(s => !params.visuals.includes(s));
1859
- rego.visual["_siblingGraphics"] = siblings;
1860
- }
2030
+ const removedEntityIds = new Set();
2031
+ for (const visual of params.visuals) {
2032
+ const visExt = visual;
2033
+ const rego = (visExt === null || visExt === void 0 ? void 0 : visExt._register) === this ? visExt._rego : null;
2034
+ if (!rego || !rego.visual) {
2035
+ continue;
2036
+ }
2037
+ const entityId = rego.entityId;
2038
+ const entityRegos = this.rego[entityId];
2039
+ if (!entityRegos) {
2040
+ continue;
2041
+ }
2042
+ if (rego.visual === visual) {
2043
+ // The primary graphic for this rego - remove the rego entirely.
2044
+ entity_label_1.EntityLabel.Detatch({
2045
+ rego
2046
+ });
2047
+ if (doRemove != false) {
2048
+ removeEntity(this.viewer, rego.visual);
1861
2049
  }
2050
+ rego.visual = null;
2051
+ this.rego[entityId] = entityRegos.filter(r => r !== rego);
2052
+ if (!this.rego[entityId].length) {
2053
+ delete this.rego[entityId];
2054
+ }
2055
+ this.unindexMenuItemEntityIfAbsent(rego.menuItemId, entityId);
2056
+ const update = {
2057
+ type: EVisualUpdateType.Remove,
2058
+ entityId: rego.entityId,
2059
+ rego: rego
2060
+ };
2061
+ if (source) {
2062
+ update.source = source;
2063
+ }
2064
+ if (source !== EUpdateSource.TREE_CASCADE) {
2065
+ (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(update);
2066
+ }
2067
+ if (doUpdate && menuItemId) {
2068
+ this.queueUpdate({
2069
+ entityId: entityId,
2070
+ refresh: false
2071
+ });
2072
+ }
2073
+ removedEntityIds.add(entityId);
1862
2074
  }
1863
- if (!((_c = this.rego[entityId]) === null || _c === void 0 ? void 0 : _c.length)) {
1864
- delete this.rego[entityId];
2075
+ else {
2076
+ const siblings = (_b = rego.visual) === null || _b === void 0 ? void 0 : _b["_siblingGraphics"];
2077
+ if (siblings === null || siblings === void 0 ? void 0 : siblings.length) {
2078
+ if (doRemove != false && siblings.includes(visual)) {
2079
+ removeEntity(this.viewer, visual);
2080
+ }
2081
+ rego.visual["_siblingGraphics"] = siblings.filter(s => s !== visual);
2082
+ }
1865
2083
  }
1866
2084
  }
1867
2085
  if (requestRender) {
1868
2086
  this.viewer.scene.requestRender();
1869
2087
  }
1870
2088
  return {
1871
- removedEntityIds
2089
+ removedEntityIds: Array.from(removedEntityIds)
1872
2090
  };
1873
2091
  }
1874
2092
  /**
@@ -2024,16 +2242,17 @@ var VisualsRegister;
2024
2242
  return (totalOpacity && totalTicks) ? (totalOpacity / totalTicks) : totalOpacity != null && typeof totalOpacity == "number" ? totalOpacity : 1;
2025
2243
  }
2026
2244
  ClearOpacity(params) {
2027
- const clearedIds = [];
2245
+ const clearedIds = new Set();
2028
2246
  this.ForEachState((state) => {
2029
- if (!clearedIds.includes(state.entityId) && state.opacity != null) {
2247
+ if (!clearedIds.has(state.entityId) && state.opacity != null) {
2248
+ this.markLazyExclusion(state.entityId);
2030
2249
  this.queueUpdate({
2031
2250
  entityId: state.entityId,
2032
2251
  refresh: {
2033
2252
  opacity: true
2034
2253
  }
2035
2254
  });
2036
- clearedIds.push(state.entityId);
2255
+ clearedIds.add(state.entityId);
2037
2256
  }
2038
2257
  delete state.opacity;
2039
2258
  });