bruce-cesium 7.7.4 → 7.7.5

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.
@@ -28110,10 +28110,13 @@
28110
28110
  })(exports.DataSourceStaticKmlManager || (exports.DataSourceStaticKmlManager = {}));
28111
28111
 
28112
28112
  (function (XGridsRenderEngine) {
28113
+ /*
28114
+ * Loads a scan with optional SDK tuning and per-viewer budget control.
28115
+ */
28113
28116
  function Render(params) {
28114
- var _a, _b, _c;
28117
+ var _a, _b, _c, _d;
28115
28118
  const { api, viewer, tileset } = params;
28116
- if (!LCCRender) {
28119
+ if (typeof LCCRender === "undefined" || typeof (LCCRender === null || LCCRender === void 0 ? void 0 : LCCRender.load) !== "function") {
28117
28120
  console.warn("[XGridsRenderEngine] Render() LCCRender library not available.");
28118
28121
  return null;
28119
28122
  }
@@ -28156,6 +28159,46 @@
28156
28159
  if (loadUrl.indexOf("?") !== -1) {
28157
28160
  loadUrl = loadUrl.split("?")[0];
28158
28161
  }
28162
+ let maxSplats = 6000000;
28163
+ /*
28164
+ * Configures optional LOD capabilities once metadata or initial geometry is ready.
28165
+ */
28166
+ const configure = (object, ready) => {
28167
+ var _a, _b, _c;
28168
+ if (!object || object["_nextspaceLodRemoved"] || object["_nextspaceLodConfigured"] || ((_a = viewer.isDestroyed) === null || _a === void 0 ? void 0 : _a.call(viewer))) {
28169
+ return;
28170
+ }
28171
+ try {
28172
+ if (typeof object.setMaxSplats !== "function") {
28173
+ return;
28174
+ }
28175
+ const lods = typeof object.getLodInfos === "function" ? object.getLodInfos() : undefined;
28176
+ const adaptive = (LCCRender.nextspaceAdaptiveLod === 1 &&
28177
+ typeof object.setNextspaceMetadataCallback === "function" &&
28178
+ Array.isArray(lods) &&
28179
+ typeof object.setVisible === "function" &&
28180
+ typeof object.setLodAutoLevelUp === "function");
28181
+ // Unpatched SDKs still need the legacy clamp to avoid their fixed LOD shortcut.
28182
+ const budget = !adaptive && (lods === null || lods === void 0 ? void 0 : lods[0]) > maxSplats && (lods === null || lods === void 0 ? void 0 : lods[1]) > 0
28183
+ ? Math.min(maxSplats, lods[1]) : maxSplats;
28184
+ object.setMaxSplats(budget);
28185
+ // A requested fly-to needs its initial data before off-screen culling can take over.
28186
+ if (adaptive &&
28187
+ (!params.flyTo || ready) &&
28188
+ ((_b = viewer.scene.preUpdate) === null || _b === void 0 ? void 0 : _b.addEventListener) &&
28189
+ ((_c = viewer.scene.postRender) === null || _c === void 0 ? void 0 : _c.addEventListener)) {
28190
+ const controller = viewer["_nextspaceXGridsLod"] ||
28191
+ (viewer["_nextspaceXGridsLod"] = new AdaptiveLod(viewer));
28192
+ controller.Add(object, maxSplats);
28193
+ controller.Update();
28194
+ }
28195
+ object["_nextspaceLodConfigured"] = !params.flyTo || ready;
28196
+ }
28197
+ catch (error) {
28198
+ // Optional SDK tuning must not abort its metadata parsing or the Cesium render loop.
28199
+ console.warn("[XGridsRenderEngine] Optional LOD setup unavailable.", error);
28200
+ }
28201
+ };
28159
28202
  const obj = LCCRender.load({
28160
28203
  camera: viewer.camera,
28161
28204
  scene: viewer.scene,
@@ -28169,22 +28212,19 @@
28169
28212
  // When true it makes the new pieces fade-in.
28170
28213
  // The look is similar to sectioning in cesium. Might be a nice setting.
28171
28214
  useLoadingEffect: false
28172
- }, async () => {
28173
- var _a, _b;
28174
- // Array index is the LOD level, ie: index 0 = LOD 0.
28175
- const lods = obj.getLodInfos();
28176
- const maxSplats = 6000000;
28177
- // LCC 0.5.4 fixes the LOD when the budget exceeds LOD 1, leaving distant splats sparse.
28178
- const lodBudget = (lods === null || lods === void 0 ? void 0 : lods[0]) > maxSplats && (lods === null || lods === void 0 ? void 0 : lods[1]) > 0
28179
- ? Math.min(maxSplats, lods[1])
28180
- : maxSplats;
28181
- obj.setMaxSplats(lodBudget);
28215
+ }, async (loaded) => {
28216
+ var _a, _b, _c;
28217
+ const object = loaded || obj;
28218
+ if (!object || object["_nextspaceLodRemoved"] || ((_a = viewer.isDestroyed) === null || _a === void 0 ? void 0 : _a.call(viewer))) {
28219
+ return;
28220
+ }
28221
+ configure(object, true);
28182
28222
  // Auto fly to.
28183
28223
  if (params.flyTo) {
28184
- const pos3d = (_b = (_a = obj === null || obj === void 0 ? void 0 : obj.getBounds) === null || _a === void 0 ? void 0 : _a.call(obj)) === null || _b === void 0 ? void 0 : _b.origin;
28224
+ const pos3d = typeof object.getBounds === "function" ? (_b = object.getBounds()) === null || _b === void 0 ? void 0 : _b.origin : undefined;
28185
28225
  if (pos3d && BModels.Cartes.ValidateCartes3(pos3d)) {
28186
28226
  const altered = await exports.DrawingUtils.RaisePos3d(viewer, pos3d, 300);
28187
- if (altered && BModels.Cartes.ValidateCartes3(altered)) {
28227
+ if (!object["_nextspaceLodRemoved"] && !((_c = viewer.isDestroyed) === null || _c === void 0 ? void 0 : _c.call(viewer)) && altered && BModels.Cartes.ValidateCartes3(altered)) {
28188
28228
  viewer.scene.camera.flyTo({
28189
28229
  destination: altered,
28190
28230
  orientation: {
@@ -28199,13 +28239,32 @@
28199
28239
  }
28200
28240
  }
28201
28241
  });
28242
+ if (!obj) {
28243
+ return null;
28244
+ }
28245
+ try {
28246
+ const sdkBudget = typeof obj.getCurrentConfig === "function" ? (_d = obj.getCurrentConfig()) === null || _d === void 0 ? void 0 : _d.maxLoadSplatCount : undefined;
28247
+ // Preserve the SDK's mobile and weak-GPU limits before applying any temporary startup cap.
28248
+ maxSplats = Number.isFinite(sdkBudget) && sdkBudget > 0 && sdkBudget <= 2000000 ? sdkBudget : 6000000;
28249
+ if (LCCRender.nextspaceMetadataReady === 1 &&
28250
+ typeof obj.setNextspaceMetadataCallback === "function" &&
28251
+ typeof obj.setMaxSplats === "function") {
28252
+ obj.setNextspaceMetadataCallback((object) => configure(object, false));
28253
+ obj.setMaxSplats(Math.min(maxSplats, 1000000));
28254
+ }
28255
+ }
28256
+ catch (error) {
28257
+ console.warn("[XGridsRenderEngine] Optional startup LOD setup unavailable.", error);
28258
+ }
28202
28259
  if (typeof (obj === null || obj === void 0 ? void 0 : obj.setRenderState) === "function") {
28203
28260
  // Splats must respect nearer meshes already drawn in the same Cesium pass.
28204
28261
  obj.setRenderState({
28205
28262
  depthTest: {
28206
28263
  enabled: true,
28207
28264
  func: Cesium.WebGLConstants.LEQUAL
28208
- }
28265
+ },
28266
+ // Transparent Gaussian edges must not occlude the splats blended behind them.
28267
+ depthMask: false
28209
28268
  });
28210
28269
  }
28211
28270
  if (typeof (obj === null || obj === void 0 ? void 0 : obj.setVisible) === "function") {
@@ -28213,9 +28272,13 @@
28213
28272
  }
28214
28273
  exports.TilesetRenderEngine.WatchDateRange(viewer, obj, tileset);
28215
28274
  // Highest LOD.
28216
- obj.setStartLod(0);
28275
+ if (typeof obj.setStartLod === "function") {
28276
+ obj.setStartLod(0);
28277
+ }
28217
28278
  // Max distance for rendering.
28218
- obj.setMaxDistance(800);
28279
+ if (typeof obj.setMaxDistance === "function") {
28280
+ obj.setMaxDistance(800);
28281
+ }
28219
28282
  // Adjust LODs based on camera distance.
28220
28283
  if (typeof obj.setLodAutoLevelUp === "function") {
28221
28284
  obj.setLodAutoLevelUp(true);
@@ -28223,18 +28286,299 @@
28223
28286
  return obj;
28224
28287
  }
28225
28288
  XGridsRenderEngine.Render = Render;
28289
+ /*
28290
+ * Detaches optional tuning before unloading the scan.
28291
+ */
28226
28292
  function Remove(viewer, renderObject) {
28293
+ var _a;
28227
28294
  if (!renderObject) {
28228
28295
  return;
28229
28296
  }
28230
- if (!LCCRender) {
28297
+ renderObject["_nextspaceLodRemoved"] = true;
28298
+ delete renderObject.nextspaceOnMetadata;
28299
+ (_a = viewer === null || viewer === void 0 ? void 0 : viewer["_nextspaceXGridsLod"]) === null || _a === void 0 ? void 0 : _a.Remove(renderObject);
28300
+ exports.TilesetRenderEngine.UnwatchDateRange(viewer, renderObject);
28301
+ if (typeof LCCRender === "undefined" || typeof (LCCRender === null || LCCRender === void 0 ? void 0 : LCCRender.unload) !== "function") {
28231
28302
  console.warn("[XGridsRenderEngine] Remove() LCCRender library not available.");
28232
28303
  return;
28233
28304
  }
28234
- exports.TilesetRenderEngine.UnwatchDateRange(viewer, renderObject);
28235
28305
  LCCRender.unload(renderObject);
28236
28306
  }
28237
28307
  XGridsRenderEngine.Remove = Remove;
28308
+ /*
28309
+ * Shares a bounded splat budget across visible scans without overriding layer or timeline visibility.
28310
+ */
28311
+ class AdaptiveLod {
28312
+ /*
28313
+ * Observes scene updates and rendered frames for this viewer only.
28314
+ */
28315
+ constructor(viewer) {
28316
+ this.viewer = viewer;
28317
+ this.entries = [];
28318
+ this.removals = [];
28319
+ this.lastUpdate = -Infinity;
28320
+ this.lastAllocation = -Infinity;
28321
+ this.lastFrame = 0;
28322
+ this.lastMove = 0;
28323
+ this.slowSince = 0;
28324
+ this.fastSince = 0;
28325
+ this.lastChange = -Infinity;
28326
+ this.scale = 1;
28327
+ this.position = new Cesium.Cartesian3();
28328
+ this.direction = new Cesium.Cartesian3();
28329
+ this.havePose = false;
28330
+ this.removals.push(viewer.scene.preUpdate.addEventListener(() => {
28331
+ try {
28332
+ this.Update();
28333
+ }
28334
+ catch (error) {
28335
+ console.warn("[XGridsRenderEngine] Optional LOD controller disabled.", error);
28336
+ this.Dispose();
28337
+ }
28338
+ }));
28339
+ this.removals.push(viewer.scene.postRender.addEventListener(() => {
28340
+ const camera = viewer.scene.camera;
28341
+ const moving = this.havePose && (Cesium.Cartesian3.distanceSquared(camera.positionWC, this.position) > 0.000001 ||
28342
+ Cesium.Cartesian3.distanceSquared(camera.directionWC, this.direction) > 0.00000001);
28343
+ Cesium.Cartesian3.clone(camera.positionWC, this.position);
28344
+ Cesium.Cartesian3.clone(camera.directionWC, this.direction);
28345
+ this.havePose = true;
28346
+ this.ObserveFrame(performance.now(), moving);
28347
+ }));
28348
+ }
28349
+ /*
28350
+ * Registers a scan while preserving the caller's visibility intent.
28351
+ */
28352
+ Add(object, maxBudget) {
28353
+ var _a, _b, _c, _d;
28354
+ if (this.entries.some(entry => entry.object === object)) {
28355
+ return;
28356
+ }
28357
+ if (typeof object.setMaxSplats !== "function") {
28358
+ return;
28359
+ }
28360
+ const lods = typeof object.getLodInfos === "function" ? object.getLodInfos() : undefined;
28361
+ const finest = lods === null || lods === void 0 ? void 0 : lods[0];
28362
+ const coarsest = (lods === null || lods === void 0 ? void 0 : lods.length) > 1 ? lods[lods.length - 1] : 0;
28363
+ const entry = {
28364
+ object, maxBudget,
28365
+ capacity: Number.isFinite(finest) && finest > 0 ? Math.min(maxBudget, finest) : maxBudget,
28366
+ minimum: Number.isFinite(coarsest) && coarsest > 0 ? Math.min(maxBudget, coarsest) : 0,
28367
+ wantedVisible: ((_b = (_a = object.getCurrentConfig) === null || _a === void 0 ? void 0 : _a.call(object)) === null || _b === void 0 ? void 0 : _b.visible) !== false,
28368
+ visible: ((_d = (_c = object.getCurrentConfig) === null || _c === void 0 ? void 0 : _c.call(object)) === null || _d === void 0 ? void 0 : _d.visible) !== false,
28369
+ culled: false, setVisible: object.setVisible, budget: -1, nodeBudget: -1,
28370
+ radiusPixels: Infinity, surfaceDistance: Infinity, weight: 1, near: false
28371
+ };
28372
+ if (typeof entry.setVisible === "function") {
28373
+ object.setVisible = (visible) => {
28374
+ entry.wantedVisible = !!visible;
28375
+ this.applyVisibility(entry);
28376
+ this.lastUpdate = -Infinity;
28377
+ this.lastAllocation = -Infinity;
28378
+ };
28379
+ }
28380
+ this.entries.push(entry);
28381
+ this.lastMove = performance.now();
28382
+ this.lastUpdate = -Infinity;
28383
+ this.lastAllocation = -Infinity;
28384
+ }
28385
+ /*
28386
+ * Combines explicit visibility with temporary camera culling.
28387
+ */
28388
+ applyVisibility(entry) {
28389
+ const visible = entry.wantedVisible && !entry.culled;
28390
+ if (entry.visible !== visible && typeof entry.setVisible === "function") {
28391
+ entry.visible = visible;
28392
+ entry.setVisible.call(entry.object, visible);
28393
+ this.viewer.scene.requestRender();
28394
+ }
28395
+ }
28396
+ /*
28397
+ * Render pressure uses elapsed wall time because a paused timeline must still adapt.
28398
+ */
28399
+ ObserveFrame(now, moving) {
28400
+ const gap = now - this.lastFrame;
28401
+ this.lastFrame = now;
28402
+ if (moving) {
28403
+ this.lastMove = now;
28404
+ }
28405
+ if (!moving || gap <= 0 || gap > 1000) {
28406
+ this.slowSince = this.fastSince = 0;
28407
+ return;
28408
+ }
28409
+ this.slowSince = gap > 40 ? this.slowSince || now : 0;
28410
+ this.fastSince = gap < 22 ? this.fastSince || now : 0;
28411
+ if (now - this.lastChange < 4000) {
28412
+ return;
28413
+ }
28414
+ if (this.slowSince && now - this.slowSince >= 1500 && this.scale > 0.25) {
28415
+ this.scale = Math.max(0.25, this.scale * 0.75);
28416
+ this.lastChange = now;
28417
+ this.slowSince = this.fastSince = 0;
28418
+ this.lastUpdate = -Infinity;
28419
+ }
28420
+ else if (this.fastSince && now - this.fastSince >= 6000 && this.scale < 1) {
28421
+ this.scale = Math.min(1, this.scale + 0.125);
28422
+ this.lastChange = now;
28423
+ this.slowSince = this.fastSince = 0;
28424
+ this.lastUpdate = -Infinity;
28425
+ }
28426
+ }
28427
+ /*
28428
+ * Estimates visible screen coverage and distance, failing open for unknown bounds.
28429
+ */
28430
+ measure(entry) {
28431
+ var _a;
28432
+ entry.weight = 1;
28433
+ entry.radiusPixels = entry.surfaceDistance = Infinity;
28434
+ try {
28435
+ const object = entry.object;
28436
+ const bounds = (_a = object.getBounds) === null || _a === void 0 ? void 0 : _a.call(object);
28437
+ const matrix = object.modelMatrix;
28438
+ const camera = this.viewer.scene.camera;
28439
+ const width = this.viewer.scene.drawingBufferWidth;
28440
+ const height = this.viewer.scene.drawingBufferHeight;
28441
+ if (!(bounds === null || bounds === void 0 ? void 0 : bounds.min) || !(bounds === null || bounds === void 0 ? void 0 : bounds.max) || !matrix || !(width > 0 && height > 0)) {
28442
+ entry.culled = false;
28443
+ return;
28444
+ }
28445
+ const center = Cesium.Cartesian3.midpoint(bounds.min, bounds.max, new Cesium.Cartesian3());
28446
+ Cesium.Matrix4.multiplyByPoint(matrix, center, center);
28447
+ const scale = Cesium.Matrix4.getMaximumScale(matrix);
28448
+ const radius = Cesium.Cartesian3.distance(bounds.min, bounds.max) * scale * 0.55 + 1;
28449
+ const distance = Cesium.Cartesian3.distance(camera.positionWC, center);
28450
+ const pixel = camera.frustum.getPixelDimensions(width, height, Math.max(1, distance), 1, new Cesium.Cartesian2());
28451
+ const pixels = radius / Math.max(pixel.x, pixel.y);
28452
+ if (!Number.isFinite(pixels) || !(radius > 0)) {
28453
+ entry.culled = false;
28454
+ return;
28455
+ }
28456
+ const sphere = new Cesium.BoundingSphere(center, radius);
28457
+ const frustum = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
28458
+ const outside = frustum.computeVisibility(sphere) === Cesium.Intersect.OUTSIDE;
28459
+ entry.culled = outside || pixels < (entry.culled ? 1.5 : 0.75);
28460
+ entry.radiusPixels = pixels;
28461
+ entry.weight = Math.pow(Math.min(1, Math.max(0.05, pixels / height)), 2);
28462
+ const local = Cesium.Matrix4.multiplyByPoint(Cesium.Matrix4.inverse(matrix, new Cesium.Matrix4()), camera.positionWC, new Cesium.Cartesian3());
28463
+ const nearest = new Cesium.Cartesian3(Cesium.Math.clamp(local.x, bounds.min.x, bounds.max.x), Cesium.Math.clamp(local.y, bounds.min.y, bounds.max.y), Cesium.Math.clamp(local.z, bounds.min.z, bounds.max.z));
28464
+ entry.surfaceDistance = Cesium.Cartesian3.distance(local, nearest) * scale;
28465
+ }
28466
+ catch {
28467
+ // Unknown bounds or a changed optional SDK must fail open instead of hiding the scan.
28468
+ entry.culled = false;
28469
+ }
28470
+ }
28471
+ /*
28472
+ * Allocates the shared hardware budget across visible scans with hysteresis.
28473
+ */
28474
+ Update(now = performance.now()) {
28475
+ var _a, _b;
28476
+ if ((_b = (_a = this.viewer).isDestroyed) === null || _b === void 0 ? void 0 : _b.call(_a)) {
28477
+ this.Dispose();
28478
+ return;
28479
+ }
28480
+ if (now - this.lastUpdate < 250) {
28481
+ return;
28482
+ }
28483
+ this.lastUpdate = now;
28484
+ // A still view can recover detail gradually without treating missing frames as a slow GPU.
28485
+ if (now - this.lastMove > 3000 && now - this.lastChange > 6000 && this.scale < 1) {
28486
+ this.scale = Math.min(1, this.scale + 0.125);
28487
+ this.lastChange = now;
28488
+ }
28489
+ for (const entry of this.entries) {
28490
+ this.measure(entry);
28491
+ this.applyVisibility(entry);
28492
+ }
28493
+ const visible = this.entries.filter(entry => entry.wantedVisible && !entry.culled);
28494
+ if (!visible.length || now - this.lastAllocation < 2000) {
28495
+ return;
28496
+ }
28497
+ this.lastAllocation = now;
28498
+ const maximum = Math.max(...visible.map(entry => entry.maxBudget));
28499
+ const minimum = visible.reduce((sum, entry) => sum + Math.min(entry.capacity, entry.minimum), 0);
28500
+ // Preserve authored coarse coverage when it fits; dropping below a whole tile can erase a scan.
28501
+ const reserve = minimum <= maximum;
28502
+ let remaining = Math.max(maximum * this.scale, reserve ? minimum : 0);
28503
+ const allocations = new Map();
28504
+ visible.forEach(entry => {
28505
+ const floor = reserve ? Math.min(entry.capacity, entry.minimum) : 0;
28506
+ allocations.set(entry, floor);
28507
+ remaining -= floor;
28508
+ });
28509
+ let pending = visible.filter(entry => allocations.get(entry) < entry.capacity);
28510
+ while (pending.length) {
28511
+ const totalWeight = pending.reduce((sum, entry) => sum + entry.weight, 0);
28512
+ const capped = pending.filter(entry => remaining * entry.weight / totalWeight >= entry.capacity - allocations.get(entry));
28513
+ if (!capped.length) {
28514
+ pending.forEach(entry => allocations.set(entry, allocations.get(entry) + remaining * entry.weight / totalWeight));
28515
+ break;
28516
+ }
28517
+ capped.forEach(entry => {
28518
+ remaining -= entry.capacity - allocations.get(entry);
28519
+ allocations.set(entry, entry.capacity);
28520
+ });
28521
+ pending = pending.filter(entry => allocations.get(entry) < entry.capacity);
28522
+ }
28523
+ for (const entry of visible) {
28524
+ const allocation = allocations.get(entry);
28525
+ const step = allocation < 250000 ? 10000 : 250000;
28526
+ const budget = allocation >= entry.capacity ? entry.capacity : Math.max(1, reserve ? entry.minimum : 0, Math.floor(allocation / step) * step);
28527
+ const near = entry.surfaceDistance < (entry.near ? 20 : 12) && entry.radiusPixels > (entry.near ? 80 : 120);
28528
+ entry.near = near;
28529
+ const ceiling = entry.maxBudget <= 2000000 ? entry.maxBudget / 2 : near ? 4000000 : 2000000;
28530
+ const nodeBudget = Math.max(1, Math.min(ceiling, entry.capacity <= ceiling ? budget : budget * (near ? 2 / 3 : 1 / 3)));
28531
+ if (entry.budget !== budget) {
28532
+ entry.object.setMaxSplats(budget);
28533
+ entry.budget = budget;
28534
+ this.viewer.scene.requestRender();
28535
+ }
28536
+ if (entry.nodeBudget !== nodeBudget && typeof entry.object.setMaxNodeSplats === "function") {
28537
+ entry.object.setMaxNodeSplats(nodeBudget);
28538
+ entry.nodeBudget = nodeBudget;
28539
+ this.viewer.scene.requestRender();
28540
+ }
28541
+ }
28542
+ }
28543
+ /*
28544
+ * Restores the scan's visibility method and releases unused listeners.
28545
+ */
28546
+ Remove(object) {
28547
+ const entry = this.entries.find(entry => entry.object === object);
28548
+ if (entry) {
28549
+ object.setVisible = entry.setVisible;
28550
+ this.entries = this.entries.filter(other => other !== entry);
28551
+ this.lastUpdate = -Infinity;
28552
+ this.lastAllocation = -Infinity;
28553
+ }
28554
+ if (!this.entries.length) {
28555
+ this.Dispose();
28556
+ }
28557
+ }
28558
+ /*
28559
+ * Restores visibility intent and releases all controller listeners.
28560
+ */
28561
+ Dispose() {
28562
+ this.removals.forEach(remove => remove());
28563
+ this.removals = [];
28564
+ this.entries.forEach(entry => {
28565
+ entry.object.setVisible = entry.setVisible;
28566
+ try {
28567
+ if (entry.visible !== entry.wantedVisible && typeof entry.setVisible === "function") {
28568
+ entry.setVisible.call(entry.object, entry.wantedVisible);
28569
+ }
28570
+ }
28571
+ catch {
28572
+ // A disposed or replaced optional SDK must not break viewer cleanup.
28573
+ }
28574
+ });
28575
+ this.entries = [];
28576
+ if (this.viewer["_nextspaceXGridsLod"] === this) {
28577
+ delete this.viewer["_nextspaceXGridsLod"];
28578
+ }
28579
+ }
28580
+ }
28581
+ XGridsRenderEngine.AdaptiveLod = AdaptiveLod;
28238
28582
  /**
28239
28583
  * Applies position, rotation, and scale to a live XGrids render object.
28240
28584
  */
@@ -49481,88 +49825,103 @@
49481
49825
  });
49482
49826
  cEntity.show = true;
49483
49827
  }
49484
- const fileRadiusKey = `model3d_${BModels.UrlUtils.RemoveSignature(params.lodUrl)}_${scale * styleScale}_radius`;
49485
- let heightProm = _fileRadiusCache.Get(fileRadiusKey);
49486
- if (!heightProm) {
49487
- heightProm = new Promise(async (res) => {
49488
- try {
49489
- // We'll have an async secondary model created for calculating boundaries.
49490
- // Once calculated we remove it.
49491
- const modelPrim = await getModelEntity(params.lodUrl, scale * styleScale);
49492
- const onReady = () => {
49493
- readyEventRemoval === null || readyEventRemoval === void 0 ? void 0 : readyEventRemoval();
49494
- readyEventRemoval = null;
49495
- const kill = () => {
49496
- if (params.viewer && !params.viewer.isDestroyed() && params.viewer.scene.primitives.contains(modelPrim)) {
49497
- params.viewer.scene.primitives.remove(modelPrim);
49828
+ const model = cEntity.model;
49829
+ model._radiusLoaded = false;
49830
+ /*
49831
+ * Retains bounds measurement for hosts that cannot expose the displayed model.
49832
+ */
49833
+ const measureTemporaryModelBounds = () => {
49834
+ const fileRadiusKey = `model3d_${BModels.UrlUtils.RemoveSignature(params.lodUrl)}_${scale * styleScale}_radius`;
49835
+ let heightProm = _fileRadiusCache.Get(fileRadiusKey);
49836
+ if (!heightProm) {
49837
+ heightProm = new Promise(async (res) => {
49838
+ try {
49839
+ // The fallback needs a temporary primitive until its bounds become available.
49840
+ const modelPrim = await getModelEntity(params.lodUrl, scale * styleScale);
49841
+ /*
49842
+ * Copies bounds before releasing the temporary primitive.
49843
+ */
49844
+ const onReady = () => {
49845
+ readyEventRemoval === null || readyEventRemoval === void 0 ? void 0 : readyEventRemoval();
49846
+ readyEventRemoval = null;
49847
+ /*
49848
+ * Releases the temporary primitive even if the viewer was destroyed.
49849
+ */
49850
+ const kill = () => {
49851
+ if (params.viewer && !params.viewer.isDestroyed() && params.viewer.scene.primitives.contains(modelPrim)) {
49852
+ params.viewer.scene.primitives.remove(modelPrim);
49853
+ }
49854
+ if (!modelPrim.isDestroyed()) {
49855
+ modelPrim.destroy();
49856
+ }
49857
+ };
49858
+ if (!cEntity || !(modelPrim === null || modelPrim === void 0 ? void 0 : modelPrim.boundingSphere)) {
49859
+ kill();
49860
+ res(null);
49861
+ return;
49498
49862
  }
49499
- if (!modelPrim.isDestroyed()) {
49500
- modelPrim.destroy();
49863
+ let radius = modelPrim.boundingSphere.radius;
49864
+ let centerOffset = modelPrim.boundingSphere.center;
49865
+ const bounds = {
49866
+ center: centerOffset ? Cesium.Cartesian3.clone(centerOffset) : Cesium.Cartesian3.ZERO.clone(),
49867
+ radius: modelPrim.boundingSphere.radius
49868
+ };
49869
+ if (centerOffset) {
49870
+ // Include the offset so origin-based culling contains the full model.
49871
+ const magnitude = Cesium.Cartesian3.magnitude(centerOffset);
49872
+ radius += magnitude;
49873
+ // Preserve extra culling padding for models with large origin offsets.
49874
+ if (magnitude > 100) {
49875
+ radius += 100;
49876
+ }
49501
49877
  }
49502
- };
49503
- if (!cEntity || !(modelPrim === null || modelPrim === void 0 ? void 0 : modelPrim.boundingSphere)) {
49504
49878
  kill();
49505
- res(null);
49506
- return;
49507
- }
49508
- let radius = modelPrim.boundingSphere.radius;
49509
- let centerOffset = modelPrim.boundingSphere.center;
49510
- const bounds = {
49511
- center: centerOffset ? Cesium.Cartesian3.clone(centerOffset) : Cesium.Cartesian3.ZERO.clone(),
49512
- radius: modelPrim.boundingSphere.radius
49879
+ res({ radius, bounds });
49513
49880
  };
49514
- if (centerOffset) {
49515
- // Add the center offset to the radius because I am not good at math.
49516
- const magnitude = Cesium.Cartesian3.magnitude(centerOffset);
49517
- radius += magnitude;
49518
- // Extra padding if the model is super not centered.
49519
- // I saw some strange boundaries for some bad models.
49520
- if (magnitude > 100) {
49521
- radius += 100;
49522
- }
49881
+ let readyEventRemoval = null;
49882
+ // Prefer the ready event when the host exposes it.
49883
+ if (modelPrim["readyEvent"]) {
49884
+ modelPrim["readyEvent"].addEventListener(onReady);
49885
+ }
49886
+ // Retain compatibility with promise-based hosts.
49887
+ else if (modelPrim["readyPromise"]) {
49888
+ modelPrim["readyPromise"].then(() => {
49889
+ onReady();
49890
+ }).catch((e) => {
49891
+ console.error(e);
49892
+ res(null);
49893
+ });
49523
49894
  }
49524
- kill();
49525
- res({ radius, bounds });
49526
- };
49527
- let readyEventRemoval = null;
49528
- // Newer Cesium versions have readyEvent. They don't use readyPromise.
49529
- if (modelPrim["readyEvent"]) {
49530
- modelPrim["readyEvent"].addEventListener(onReady);
49531
- }
49532
- // Older Cesium versions don't have readyEvent, they use readyPromise.
49533
- else if (modelPrim["readyPromise"]) {
49534
- modelPrim["readyPromise"].then(() => {
49535
- onReady();
49536
- }).catch((e) => {
49537
- console.error(e);
49538
- res(null);
49539
- });
49895
+ params.viewer.scene.primitives.add(modelPrim);
49896
+ }
49897
+ catch (e) {
49898
+ console.error(e);
49899
+ res(null);
49900
+ }
49901
+ });
49902
+ _fileRadiusCache.Set(fileRadiusKey, heightProm);
49903
+ }
49904
+ heightProm.then((measured) => {
49905
+ if (model) {
49906
+ model._radius = measured ? measured.radius : null;
49907
+ model._bounds = measured ? measured.bounds : null;
49908
+ model._radiusLoaded = true;
49909
+ if (params.viewer && params.viewer.scene && !params.viewer.isDestroyed()) {
49910
+ exports.VisualRegisterCuller.MarkShouldRecheck(params.viewer);
49540
49911
  }
49541
- params.viewer.scene.primitives.add(modelPrim);
49542
49912
  }
49543
- catch (e) {
49544
- console.error(e);
49545
- res(null);
49913
+ // Rough estimate on when the model is ready in the scene.
49914
+ if (animateScale) {
49915
+ animateScale.Play();
49546
49916
  }
49547
49917
  });
49548
- _fileRadiusCache.Set(fileRadiusKey, heightProm);
49918
+ };
49919
+ if (Cesium.Model.fromGltfAsync) {
49920
+ trackRenderedModelBounds(params.viewer, cEntity, model, scale * styleScale, measureTemporaryModelBounds, animateScale);
49921
+ }
49922
+ else {
49923
+ measureTemporaryModelBounds();
49549
49924
  }
49550
- heightProm.then((measured) => {
49551
- if (model) {
49552
- model._radius = measured ? measured.radius : null;
49553
- model._bounds = measured ? measured.bounds : null;
49554
- model._radiusLoaded = true;
49555
- if (params.viewer && params.viewer.scene && !params.viewer.isDestroyed()) {
49556
- exports.VisualRegisterCuller.MarkShouldRecheck(params.viewer);
49557
- }
49558
- }
49559
- // Rough estimate on when the model is ready in the scene.
49560
- if (animateScale) {
49561
- animateScale.Play();
49562
- }
49563
- });
49564
- const model = cEntity.model;
49565
- model._radiusLoaded = false;
49566
49925
  model._clientFileId = params.lodClientFileId;
49567
49926
  model._entityScale = {
49568
49927
  record: scale,
@@ -49770,6 +50129,88 @@
49770
50129
  }
49771
50130
  EntityRenderEngineModel3d.RenderGroup = RenderGroup;
49772
50131
  })(exports.EntityRenderEngineModel3d || (exports.EntityRenderEngineModel3d = {}));
50132
+ /*
50133
+ * Measures bounds from Cesium's displayed model so loading a visual does not also load a temporary model.
50134
+ */
50135
+ function trackRenderedModelBounds(viewer, entity, model, targetScale, fallback, animateScale) {
50136
+ const key = "_nextspaceRenderedModelBoundsMonitor";
50137
+ let monitor = viewer[key];
50138
+ if (!monitor) {
50139
+ monitor = { pending: new Map() };
50140
+ viewer[key] = monitor;
50141
+ }
50142
+ monitor.pending.set(entity.id, { entity, model, targetScale, fallback, animateScale });
50143
+ if (monitor.removeListener) {
50144
+ return;
50145
+ }
50146
+ monitor.removeListener = viewer.scene.postRender.addEventListener(() => {
50147
+ var _a, _b, _c, _d, _e, _f;
50148
+ if (viewer.isDestroyed()) {
50149
+ monitor.pending.clear();
50150
+ }
50151
+ else {
50152
+ const primitives = viewer.scene.primitives;
50153
+ for (let i = 0; i < primitives.length && monitor.pending.size; i++) {
50154
+ const primitive = primitives.get(i);
50155
+ if (!(primitive instanceof Cesium.Model) || !primitive.ready) {
50156
+ continue;
50157
+ }
50158
+ const task = monitor.pending.get((_a = primitive.id) === null || _a === void 0 ? void 0 : _a.id);
50159
+ if (!task || primitive.id !== task.entity || task.entity.model !== task.model) {
50160
+ continue;
50161
+ }
50162
+ try {
50163
+ const sphere = primitive.boundingSphere;
50164
+ const matrix = primitive.heightReference == null || primitive.heightReference === Cesium.HeightReference.NONE
50165
+ ? primitive.modelMatrix
50166
+ : primitive._clampedModelMatrix;
50167
+ if (!sphere || !matrix ||
50168
+ !Number.isFinite(sphere.radius) || sphere.radius < 0 ||
50169
+ !Number.isFinite((_b = sphere.center) === null || _b === void 0 ? void 0 : _b.x) ||
50170
+ !Number.isFinite((_c = sphere.center) === null || _c === void 0 ? void 0 : _c.y) ||
50171
+ !Number.isFinite((_d = sphere.center) === null || _d === void 0 ? void 0 : _d.z)) {
50172
+ throw new Error("Displayed model bounds unavailable");
50173
+ }
50174
+ const inverse = Cesium.Matrix4.inverseTransformation(matrix, new Cesium.Matrix4());
50175
+ const center = Cesium.Matrix4.multiplyByPoint(inverse, sphere.center, new Cesium.Cartesian3());
50176
+ const scaleRatio = task.targetScale / primitive.scale;
50177
+ Cesium.Cartesian3.multiplyByScalar(center, scaleRatio, center);
50178
+ const radius = sphere.radius * scaleRatio;
50179
+ const magnitude = Cesium.Cartesian3.magnitude(center);
50180
+ if (!Number.isFinite(scaleRatio) || scaleRatio <= 0 || !Number.isFinite(magnitude) || !Number.isFinite(radius)) {
50181
+ throw new Error("Displayed model bounds transform invalid");
50182
+ }
50183
+ task.model._bounds = { center, radius };
50184
+ task.model._radius = radius + magnitude + (magnitude > 100 ? 100 : 0);
50185
+ task.model._radiusLoaded = true;
50186
+ (_e = task.animateScale) === null || _e === void 0 ? void 0 : _e.Play();
50187
+ exports.VisualRegisterCuller.MarkShouldRecheck(viewer);
50188
+ }
50189
+ catch (e) {
50190
+ task.fallback();
50191
+ }
50192
+ monitor.pending.delete(task.entity.id);
50193
+ }
50194
+ for (const [id, task] of monitor.pending) {
50195
+ const modelShown = GetCValue(viewer, task.model.show) !== false;
50196
+ if (!task.entity.isShowing || !modelShown) {
50197
+ task.visibleAt = null;
50198
+ }
50199
+ else if (task.visibleAt == null) {
50200
+ task.visibleAt = performance.now();
50201
+ }
50202
+ else if (performance.now() - task.visibleAt > 60000) {
50203
+ task.fallback();
50204
+ monitor.pending.delete(id);
50205
+ }
50206
+ }
50207
+ }
50208
+ if (!monitor.pending.size) {
50209
+ (_f = monitor.removeListener) === null || _f === void 0 ? void 0 : _f.call(monitor);
50210
+ monitor.removeListener = null;
50211
+ }
50212
+ });
50213
+ }
49773
50214
  async function getName$1(api, entity, offline) {
49774
50215
  var _a;
49775
50216
  const ownName = GetUsableEntityName(entity);
@@ -57122,7 +57563,7 @@ void main() {
57122
57563
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
57123
57564
  })(exports.StyleUtils || (exports.StyleUtils = {}));
57124
57565
 
57125
- const VERSION = "7.7.4";
57566
+ const VERSION = "7.7.5";
57126
57567
  /**
57127
57568
  * Updates the environment instance used by bruce-cesium to one specified.
57128
57569
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.