bruce-cesium 7.7.3 → 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,24 +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
- //console.log("xgrids-getLodInfos()", lods);
28177
- const HARD_MIN = 1000000;
28178
- const HARD_MAX = 20000000;
28179
- const maxSplats = Math.max(lods ? lods[0] || 0 : 0, HARD_MIN);
28180
- obj.setMaxSplats(Math.min(HARD_MAX, maxSplats));
28181
- if (maxSplats >= HARD_MAX) {
28182
- console.warn("Reduced splat quality due to the number of splats being too large. Was: \"" + maxSplats + "\", now is: \"" + HARD_MAX + "\".");
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;
28183
28220
  }
28221
+ configure(object, true);
28184
28222
  // Auto fly to.
28185
28223
  if (params.flyTo) {
28186
- 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;
28187
28225
  if (pos3d && BModels.Cartes.ValidateCartes3(pos3d)) {
28188
28226
  const altered = await exports.DrawingUtils.RaisePos3d(viewer, pos3d, 300);
28189
- 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)) {
28190
28228
  viewer.scene.camera.flyTo({
28191
28229
  destination: altered,
28192
28230
  orientation: {
@@ -28201,14 +28239,46 @@
28201
28239
  }
28202
28240
  }
28203
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
+ }
28259
+ if (typeof (obj === null || obj === void 0 ? void 0 : obj.setRenderState) === "function") {
28260
+ // Splats must respect nearer meshes already drawn in the same Cesium pass.
28261
+ obj.setRenderState({
28262
+ depthTest: {
28263
+ enabled: true,
28264
+ func: Cesium.WebGLConstants.LEQUAL
28265
+ },
28266
+ // Transparent Gaussian edges must not occlude the splats blended behind them.
28267
+ depthMask: false
28268
+ });
28269
+ }
28204
28270
  if (typeof (obj === null || obj === void 0 ? void 0 : obj.setVisible) === "function") {
28205
28271
  obj.setVisible(true);
28206
28272
  }
28207
28273
  exports.TilesetRenderEngine.WatchDateRange(viewer, obj, tileset);
28208
28274
  // Highest LOD.
28209
- obj.setStartLod(0);
28275
+ if (typeof obj.setStartLod === "function") {
28276
+ obj.setStartLod(0);
28277
+ }
28210
28278
  // Max distance for rendering.
28211
- obj.setMaxDistance(800);
28279
+ if (typeof obj.setMaxDistance === "function") {
28280
+ obj.setMaxDistance(800);
28281
+ }
28212
28282
  // Adjust LODs based on camera distance.
28213
28283
  if (typeof obj.setLodAutoLevelUp === "function") {
28214
28284
  obj.setLodAutoLevelUp(true);
@@ -28216,18 +28286,299 @@
28216
28286
  return obj;
28217
28287
  }
28218
28288
  XGridsRenderEngine.Render = Render;
28289
+ /*
28290
+ * Detaches optional tuning before unloading the scan.
28291
+ */
28219
28292
  function Remove(viewer, renderObject) {
28293
+ var _a;
28220
28294
  if (!renderObject) {
28221
28295
  return;
28222
28296
  }
28223
- 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") {
28224
28302
  console.warn("[XGridsRenderEngine] Remove() LCCRender library not available.");
28225
28303
  return;
28226
28304
  }
28227
- exports.TilesetRenderEngine.UnwatchDateRange(viewer, renderObject);
28228
28305
  LCCRender.unload(renderObject);
28229
28306
  }
28230
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;
28231
28582
  /**
28232
28583
  * Applies position, rotation, and scale to a live XGrids render object.
28233
28584
  */
@@ -49474,88 +49825,103 @@
49474
49825
  });
49475
49826
  cEntity.show = true;
49476
49827
  }
49477
- const fileRadiusKey = `model3d_${BModels.UrlUtils.RemoveSignature(params.lodUrl)}_${scale * styleScale}_radius`;
49478
- let heightProm = _fileRadiusCache.Get(fileRadiusKey);
49479
- if (!heightProm) {
49480
- heightProm = new Promise(async (res) => {
49481
- try {
49482
- // We'll have an async secondary model created for calculating boundaries.
49483
- // Once calculated we remove it.
49484
- const modelPrim = await getModelEntity(params.lodUrl, scale * styleScale);
49485
- const onReady = () => {
49486
- readyEventRemoval === null || readyEventRemoval === void 0 ? void 0 : readyEventRemoval();
49487
- readyEventRemoval = null;
49488
- const kill = () => {
49489
- if (params.viewer && !params.viewer.isDestroyed() && params.viewer.scene.primitives.contains(modelPrim)) {
49490
- 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;
49491
49862
  }
49492
- if (!modelPrim.isDestroyed()) {
49493
- 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
+ }
49494
49877
  }
49495
- };
49496
- if (!cEntity || !(modelPrim === null || modelPrim === void 0 ? void 0 : modelPrim.boundingSphere)) {
49497
49878
  kill();
49498
- res(null);
49499
- return;
49500
- }
49501
- let radius = modelPrim.boundingSphere.radius;
49502
- let centerOffset = modelPrim.boundingSphere.center;
49503
- const bounds = {
49504
- center: centerOffset ? Cesium.Cartesian3.clone(centerOffset) : Cesium.Cartesian3.ZERO.clone(),
49505
- radius: modelPrim.boundingSphere.radius
49879
+ res({ radius, bounds });
49506
49880
  };
49507
- if (centerOffset) {
49508
- // Add the center offset to the radius because I am not good at math.
49509
- const magnitude = Cesium.Cartesian3.magnitude(centerOffset);
49510
- radius += magnitude;
49511
- // Extra padding if the model is super not centered.
49512
- // I saw some strange boundaries for some bad models.
49513
- if (magnitude > 100) {
49514
- radius += 100;
49515
- }
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
+ });
49516
49894
  }
49517
- kill();
49518
- res({ radius, bounds });
49519
- };
49520
- let readyEventRemoval = null;
49521
- // Newer Cesium versions have readyEvent. They don't use readyPromise.
49522
- if (modelPrim["readyEvent"]) {
49523
- modelPrim["readyEvent"].addEventListener(onReady);
49524
- }
49525
- // Older Cesium versions don't have readyEvent, they use readyPromise.
49526
- else if (modelPrim["readyPromise"]) {
49527
- modelPrim["readyPromise"].then(() => {
49528
- onReady();
49529
- }).catch((e) => {
49530
- console.error(e);
49531
- res(null);
49532
- });
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);
49533
49911
  }
49534
- params.viewer.scene.primitives.add(modelPrim);
49535
49912
  }
49536
- catch (e) {
49537
- console.error(e);
49538
- res(null);
49913
+ // Rough estimate on when the model is ready in the scene.
49914
+ if (animateScale) {
49915
+ animateScale.Play();
49539
49916
  }
49540
49917
  });
49541
- _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();
49542
49924
  }
49543
- heightProm.then((measured) => {
49544
- if (model) {
49545
- model._radius = measured ? measured.radius : null;
49546
- model._bounds = measured ? measured.bounds : null;
49547
- model._radiusLoaded = true;
49548
- if (params.viewer && params.viewer.scene && !params.viewer.isDestroyed()) {
49549
- exports.VisualRegisterCuller.MarkShouldRecheck(params.viewer);
49550
- }
49551
- }
49552
- // Rough estimate on when the model is ready in the scene.
49553
- if (animateScale) {
49554
- animateScale.Play();
49555
- }
49556
- });
49557
- const model = cEntity.model;
49558
- model._radiusLoaded = false;
49559
49925
  model._clientFileId = params.lodClientFileId;
49560
49926
  model._entityScale = {
49561
49927
  record: scale,
@@ -49763,6 +50129,88 @@
49763
50129
  }
49764
50130
  EntityRenderEngineModel3d.RenderGroup = RenderGroup;
49765
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
+ }
49766
50214
  async function getName$1(api, entity, offline) {
49767
50215
  var _a;
49768
50216
  const ownName = GetUsableEntityName(entity);
@@ -57115,7 +57563,7 @@ void main() {
57115
57563
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
57116
57564
  })(exports.StyleUtils || (exports.StyleUtils = {}));
57117
57565
 
57118
- const VERSION = "7.7.3";
57566
+ const VERSION = "7.7.5";
57119
57567
  /**
57120
57568
  * Updates the environment instance used by bruce-cesium to one specified.
57121
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.