@ifc-lite/viewer 1.21.0 → 1.22.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.
- package/.turbo/turbo-build.log +57 -50
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +10 -0
- package/dist/assets/arrow-fie-E7fe.js +20 -0
- package/dist/assets/ascii-points-source-bTjLVmUX.js +1 -0
- package/dist/assets/{basketViewActivator-Bzw51jhm.js → basketViewActivator-EHAhHlwN.js} +12 -13
- package/dist/assets/bcf-Bhx-K17f.js +281 -0
- package/dist/assets/{browser-C5TFR7sH.js → browser-CVf8ATeW.js} +6 -6
- package/dist/assets/cesium-B4ZIU9jS.js +17742 -0
- package/dist/assets/decode-worker-CYqSjk1n.js +172 -0
- package/dist/assets/e57-source-CQHxE8n3.js +1 -0
- package/dist/assets/emscripten-module.browser-DcFZLAUx.js +1 -0
- package/dist/assets/exporters-KTio0Tdm.js +5723 -0
- package/dist/assets/geometry-controller.worker-Cm2P_EJr.js +7 -0
- package/dist/assets/geometry.worker-DchLBqZ8.js +1 -0
- package/dist/assets/{ids-B7AXEv7h.js → ids-CS7VCFin.js} +5 -5
- package/dist/assets/ifc-lite-C6wEhXa6.js +7 -0
- package/dist/assets/{ifc-lite_bg-DlKs5-yM.wasm → ifc-lite_bg-CSeT3fNI.wasm} +0 -0
- package/dist/assets/{ifc-lite_bg-PqmRe3Ph.wasm → ifc-lite_bg-ns4cSnX2.wasm} +0 -0
- package/dist/assets/{index-DVNSvEMh.js → index-8k9h-ANq.js} +60997 -59926
- package/dist/assets/index-BZC2YaOP.css +1 -0
- package/dist/assets/index-HqAIQkr6.js +22 -0
- package/dist/assets/inline-worker-BpBzlmd6.js +1 -0
- package/dist/assets/las-BW6LIc_j.js +1 -0
- package/dist/assets/las-source-C_IGrgRq.js +1 -0
- package/dist/assets/laz-source-jj3xI5Y4.js +125 -0
- package/dist/assets/maplibre-gl-C4LXKM6c.js +808 -0
- package/dist/assets/{native-bridge-BiD01jI9.js → native-bridge-DNrEhx2R.js} +5 -8
- package/dist/assets/{parser.worker-Bnbrl6gy.js → parser.worker-BcjkIo89.js} +2 -2
- package/dist/assets/pcd-source-Ck0UnVDn.js +3 -0
- package/dist/assets/ply-source-C8jjyzxE.js +4 -0
- package/dist/assets/{exporters-u0sz2Upj.js → sandbox-BSn5MyEJ.js} +11745 -7412
- package/dist/assets/{server-client-DP8fMPY9.js → server-client-D-kU2XAF.js} +4 -4
- package/dist/assets/{three-CDRZThFA.js → three-DwNDHx9-.js} +163 -171
- package/dist/assets/wasm-bridge-Cha08LdC.js +1 -0
- package/dist/assets/{workerHelpers-CBbWSJmd.js → workerHelpers-pUUnk9Wc.js} +1 -1
- package/dist/assets/zip-BJqVbRkU.js +2 -0
- package/dist/index.html +10 -12
- package/package.json +11 -11
- package/src/components/mcp/PlaygroundChat.tsx +90 -52
- package/src/components/viewer/CesiumOverlay.tsx +150 -91
- package/src/components/viewer/CesiumPlacementEditor.tsx +1009 -0
- package/src/components/viewer/ChatPanel.tsx +76 -93
- package/src/components/viewer/EntityContextMenu.tsx +68 -10
- package/src/components/viewer/MainToolbar.tsx +33 -3
- package/src/components/viewer/ViewportContainer.tsx +70 -16
- package/src/components/viewer/ViewportOverlays.tsx +2 -98
- package/src/components/viewer/chat/ByokKeyModal.tsx +338 -0
- package/src/components/viewer/chat/ByokStreamingPill.tsx +62 -0
- package/src/components/viewer/chat/ByokTrustDiagram.tsx +192 -0
- package/src/components/viewer/properties/GeoreferencingPanel.tsx +49 -52
- package/src/components/viewer/properties/ModelMetadataPanel.tsx +55 -44
- package/src/components/viewer/selectionHandlers.ts +7 -1
- package/src/lib/geo/cesium-bridge.ts +86 -50
- package/src/lib/geo/cesium-placement.test.ts +244 -0
- package/src/lib/geo/cesium-placement.ts +231 -0
- package/src/lib/geo/effective-georef.test.ts +74 -1
- package/src/lib/geo/effective-georef.ts +40 -93
- package/src/lib/geo/geo-scale.ts +104 -0
- package/src/lib/geo/reproject.test.ts +130 -0
- package/src/lib/geo/reproject.ts +37 -12
- package/src/lib/geo/terrain-elevation.ts +198 -89
- package/src/lib/lens/adapter.ts +52 -6
- package/src/lib/llm/clipboard-detect.test.ts +150 -0
- package/src/lib/llm/clipboard-detect.ts +90 -0
- package/src/lib/llm/models.ts +28 -0
- package/src/lib/llm/stream-direct.ts +16 -4
- package/src/lib/llm/types.ts +8 -0
- package/src/services/playground-model.ts +55 -0
- package/src/store/index.ts +4 -5
- package/src/store/slices/cesiumSlice.ts +100 -19
- package/src/store.ts +3 -0
- package/dist/assets/arrow-CZ5kQ26f.js +0 -20
- package/dist/assets/bcf-4K724hw0.js +0 -281
- package/dist/assets/cesium-DUOzBlqv.js +0 -17817
- package/dist/assets/decode-worker-t2EGKAxO.js +0 -1708
- package/dist/assets/emscripten-module.browser-CY5t0Vfq.js +0 -1
- package/dist/assets/geometry-controller.worker-NH8pZmrU.js +0 -7
- package/dist/assets/geometry.worker-Bp4rW_R1.js +0 -1
- package/dist/assets/ifc-lite-DfZHk36-.js +0 -7
- package/dist/assets/index-CSWgTe1s.css +0 -1
- package/dist/assets/index-XwKzDuw6.js +0 -22
- package/dist/assets/maplibre-gl-CGLcoNXc.js +0 -811
- package/dist/assets/sandbox-DPD1ROr0.js +0 -9700
- package/dist/assets/wasm-bridge-CErti6zX.js +0 -1
- package/dist/assets/zip-DBEtpeu6.js +0 -12
- package/src/components/viewer/CesiumSettingsDialog.tsx +0 -100
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* CesiumOverlay — renders
|
|
7
|
-
* providing real-world 3D context
|
|
8
|
-
* georeferenced IFC models.
|
|
6
|
+
* CesiumOverlay — renders Google Photorealistic 3D Tiles behind the WebGPU
|
|
7
|
+
* canvas, providing real-world 3D context for georeferenced IFC models.
|
|
9
8
|
*
|
|
10
9
|
* Architecture:
|
|
11
10
|
* - A separate <div> behind the WebGPU <canvas> (z-index layering)
|
|
@@ -26,8 +25,11 @@ import type { MapConversion, ProjectedCRS } from '@ifc-lite/parser';
|
|
|
26
25
|
import type { CoordinateInfo, GeometryResult } from '@ifc-lite/geometry';
|
|
27
26
|
import { getGlobalRenderer } from '@/hooks/useBCF';
|
|
28
27
|
import { createCesiumBridge, type CesiumBridge } from '@/lib/geo/cesium-bridge';
|
|
29
|
-
import {
|
|
30
|
-
|
|
28
|
+
import {
|
|
29
|
+
computeCesiumPlacement,
|
|
30
|
+
shouldPreferOrthometricTerrain,
|
|
31
|
+
} from '@/lib/geo/cesium-placement';
|
|
32
|
+
import { getEffectiveHorizontalScale } from '@/lib/geo/geo-scale';
|
|
31
33
|
|
|
32
34
|
// Lazy-loaded Cesium module and CSS
|
|
33
35
|
let cesiumPromise: Promise<typeof import('cesium')> | null = null;
|
|
@@ -210,6 +212,7 @@ function buildModelMatrix(
|
|
|
210
212
|
|
|
211
213
|
export interface CesiumOverlayProps {
|
|
212
214
|
mapConversion?: MapConversion;
|
|
215
|
+
cameraMapConversion?: MapConversion;
|
|
213
216
|
projectedCRS?: ProjectedCRS;
|
|
214
217
|
coordinateInfo?: CoordinateInfo;
|
|
215
218
|
geometryResult?: GeometryResult | null;
|
|
@@ -223,6 +226,7 @@ export interface CesiumOverlayProps {
|
|
|
223
226
|
|
|
224
227
|
export function CesiumOverlay({
|
|
225
228
|
mapConversion,
|
|
229
|
+
cameraMapConversion,
|
|
226
230
|
projectedCRS,
|
|
227
231
|
coordinateInfo,
|
|
228
232
|
geometryResult,
|
|
@@ -232,6 +236,7 @@ export function CesiumOverlay({
|
|
|
232
236
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
233
237
|
const viewerRef = useRef<InstanceType<typeof import('cesium').Viewer> | null>(null);
|
|
234
238
|
const bridgeRef = useRef<CesiumBridge | null>(null);
|
|
239
|
+
const cameraBridgeRef = useRef<CesiumBridge | null>(null);
|
|
235
240
|
const rafRef = useRef<number | null>(null);
|
|
236
241
|
const [status, setStatus] = useState<'idle' | 'loading' | 'ready' | 'error'>('idle');
|
|
237
242
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -242,9 +247,9 @@ export function CesiumOverlay({
|
|
|
242
247
|
const dataSource = useViewerStore((s) => s.cesiumDataSource);
|
|
243
248
|
const ionToken = useViewerStore((s) => s.cesiumIonToken);
|
|
244
249
|
const terrainEnabled = useViewerStore((s) => s.cesiumTerrainEnabled);
|
|
245
|
-
const
|
|
246
|
-
const terrainHeight = useViewerStore((s) => s.cesiumTerrainHeight);
|
|
250
|
+
const terrainClipY = useViewerStore((s) => s.cesiumTerrainClipY);
|
|
247
251
|
const setCesiumTerrainHeight = useViewerStore((s) => s.setCesiumTerrainHeight);
|
|
252
|
+
const setCesiumTerrainSource = useViewerStore((s) => s.setCesiumTerrainSource);
|
|
248
253
|
const setCesiumTerrainClipY = useViewerStore((s) => s.setCesiumTerrainClipY);
|
|
249
254
|
const setCesiumGlbLoaded = useViewerStore((s) => s.setCesiumGlbLoaded);
|
|
250
255
|
|
|
@@ -341,18 +346,7 @@ export function CesiumOverlay({
|
|
|
341
346
|
scene.globe.showGroundAtmosphere = false;
|
|
342
347
|
scene.backgroundColor = Cesium.Color.TRANSPARENT;
|
|
343
348
|
scene.globe.baseColor = Cesium.Color.TRANSPARENT;
|
|
344
|
-
|
|
345
|
-
// Add imagery
|
|
346
|
-
try {
|
|
347
|
-
const imageryProvider = await Cesium.IonImageryProvider.fromAssetId(2);
|
|
348
|
-
viewer.imageryLayers.addImageryProvider(imageryProvider);
|
|
349
|
-
} catch {
|
|
350
|
-
viewer.imageryLayers.addImageryProvider(
|
|
351
|
-
new Cesium.OpenStreetMapImageryProvider({
|
|
352
|
-
url: 'https://a.tile.openstreetmap.org/',
|
|
353
|
-
})
|
|
354
|
-
);
|
|
355
|
-
}
|
|
349
|
+
scene.globe.show = false;
|
|
356
350
|
|
|
357
351
|
// Add terrain
|
|
358
352
|
if (terrainEnabled && ionToken) {
|
|
@@ -397,7 +391,8 @@ export function CesiumOverlay({
|
|
|
397
391
|
}, [cesiumEnabled, ionToken, terrainEnabled, dataSource]);
|
|
398
392
|
|
|
399
393
|
// ─── Effect 2: Build the coordinate bridge with terrain-aware placement ─
|
|
400
|
-
// Precomputes the model placement (
|
|
394
|
+
// Precomputes the model placement (floored to the visible surface when
|
|
395
|
+
// necessary) BEFORE
|
|
401
396
|
// building the bridge that the GLB and camera will share. This way the
|
|
402
397
|
// model loads into Cesium at its final altitude — no post-load shifting,
|
|
403
398
|
// no camera/model frame divergence, no compensation gymnastics.
|
|
@@ -405,14 +400,15 @@ export function CesiumOverlay({
|
|
|
405
400
|
// Sequence:
|
|
406
401
|
// 1. Build a tentative bridge to recover the model's WGS84 lat/lon.
|
|
407
402
|
// 2. Query terrain at that lat/lon (sync first, async with retry next).
|
|
408
|
-
// 3.
|
|
403
|
+
// 3. Auto-floor the model if its authored base sits below the visible surface.
|
|
409
404
|
// 4. Rebuild the bridge with placementHeight baked into its enuToEcef
|
|
410
405
|
// origin so model matrix and camera frame share a single altitude.
|
|
411
|
-
// 5. Push terrain-derived state (height, clip Y
|
|
406
|
+
// 5. Push terrain-derived state (height, clip Y) and
|
|
412
407
|
// install the bridge.
|
|
413
408
|
useEffect(() => {
|
|
414
409
|
if (status !== 'ready' || !mapConversion || !projectedCRS) {
|
|
415
410
|
bridgeRef.current = null;
|
|
411
|
+
cameraBridgeRef.current = null;
|
|
416
412
|
prevPlacementRef.current = null;
|
|
417
413
|
return;
|
|
418
414
|
}
|
|
@@ -424,12 +420,15 @@ export function CesiumOverlay({
|
|
|
424
420
|
const viewer = viewerRef.current;
|
|
425
421
|
if (!Cesium || !viewer) return;
|
|
426
422
|
|
|
427
|
-
const
|
|
428
|
-
|
|
423
|
+
const cameraConversion = cameraMapConversion ?? mapConversion;
|
|
424
|
+
const usesSeparateCameraBridge = cameraConversion !== mapConversion;
|
|
425
|
+
const cameraTentative = await createCesiumBridge(
|
|
426
|
+
cameraConversion, projectedCRS, coordinateInfo, lengthUnitScale,
|
|
429
427
|
);
|
|
430
428
|
if (cancelled) return;
|
|
431
|
-
if (!
|
|
429
|
+
if (!cameraTentative) {
|
|
432
430
|
bridgeRef.current = null;
|
|
431
|
+
cameraBridgeRef.current = null;
|
|
433
432
|
return;
|
|
434
433
|
}
|
|
435
434
|
|
|
@@ -439,46 +438,81 @@ export function CesiumOverlay({
|
|
|
439
438
|
// Google Photorealistic 3D Tiles (where there's no Cesium terrain
|
|
440
439
|
// provider for getHeight to read). Cached per-session.
|
|
441
440
|
const t0 = performance.now();
|
|
442
|
-
|
|
443
|
-
|
|
441
|
+
const preferOrthometricTerrain = shouldPreferOrthometricTerrain(projectedCRS);
|
|
442
|
+
let terrainSample = null;
|
|
443
|
+
try {
|
|
444
|
+
terrainSample = await cameraTentative.queryTerrainHeight(Cesium, viewer, {
|
|
445
|
+
cacheNamespace: [
|
|
446
|
+
terrainEnabled ? 'terrain' : 'ellipsoid',
|
|
447
|
+
dataSource,
|
|
448
|
+
preferOrthometricTerrain ? 'orthometric' : 'visual-surface',
|
|
449
|
+
].join(':'),
|
|
450
|
+
preferOrthometric: preferOrthometricTerrain,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
444
453
|
catch (err) { console.warn('[CesiumOverlay] terrain query failed:', err); }
|
|
445
454
|
if (cancelled) return;
|
|
446
455
|
const terrainMs = performance.now() - t0;
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
456
|
+
const terrainH = terrainSample?.height ?? null;
|
|
457
|
+
const modelTentative = usesSeparateCameraBridge
|
|
458
|
+
? await createCesiumBridge(mapConversion, projectedCRS, coordinateInfo, lengthUnitScale)
|
|
459
|
+
: cameraTentative;
|
|
460
|
+
if (cancelled) return;
|
|
461
|
+
if (!modelTentative) {
|
|
462
|
+
bridgeRef.current = null;
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
let placement = computeCesiumPlacement({
|
|
466
|
+
coordinateInfo,
|
|
467
|
+
projectedCRS,
|
|
468
|
+
ifcOriginHeight: modelTentative.modelOrigin.height,
|
|
469
|
+
terrainHeight: terrainH,
|
|
470
|
+
storeyElevations,
|
|
471
|
+
});
|
|
472
|
+
const cameraPlacement = usesSeparateCameraBridge
|
|
473
|
+
? computeCesiumPlacement({
|
|
474
|
+
coordinateInfo,
|
|
475
|
+
projectedCRS,
|
|
476
|
+
ifcOriginHeight: cameraTentative.modelOrigin.height,
|
|
477
|
+
terrainHeight: terrainH,
|
|
478
|
+
storeyElevations,
|
|
479
|
+
})
|
|
480
|
+
: placement;
|
|
481
|
+
if (usesSeparateCameraBridge) {
|
|
482
|
+
const mapScale = projectedCRS.mapUnitScale ?? lengthUnitScale;
|
|
483
|
+
const deltaHeightMeters = (
|
|
484
|
+
mapConversion.orthogonalHeight - cameraConversion.orthogonalHeight
|
|
485
|
+
) * mapScale;
|
|
486
|
+
const floorPlacementHeight = terrainH !== null
|
|
487
|
+
? terrainH + cameraPlacement.anchorOffset
|
|
488
|
+
: Number.NEGATIVE_INFINITY;
|
|
489
|
+
placement = {
|
|
490
|
+
...placement,
|
|
491
|
+
placementHeight: Math.max(
|
|
492
|
+
cameraPlacement.placementHeight + deltaHeightMeters,
|
|
493
|
+
floorPlacementHeight,
|
|
494
|
+
),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
466
497
|
|
|
467
498
|
console.debug(
|
|
468
499
|
`[CesiumOverlay] placement decision: terrain=${terrainH?.toFixed(2) ?? 'null'}m`
|
|
469
|
-
+ `
|
|
470
|
-
+ `
|
|
471
|
-
+ `
|
|
500
|
+
+ ` source=${terrainSample?.source ?? 'none'}`
|
|
501
|
+
+ ` ref=${terrainSample?.reference ?? 'none'}`
|
|
502
|
+
+ ` ifcOHeight=${placement.ifcOriginHeight.toFixed(2)}m`
|
|
503
|
+
+ ` anchorY=${placement.clampAnchorY.toFixed(2)}m`
|
|
504
|
+
+ ` (minY=${placement.minY.toFixed(2)}m, ${storeyElevations?.size ?? 0} storeys)`
|
|
505
|
+
+ ` placement=${placement.placementHeight.toFixed(2)}m`
|
|
472
506
|
+ ` (terrain query: ${terrainMs.toFixed(0)}ms)`
|
|
473
507
|
);
|
|
474
508
|
|
|
475
509
|
// Build the final bridge with the placement baked in (or reuse the
|
|
476
510
|
// tentative one when the placement matches its IFC-derived origin).
|
|
477
|
-
let bridge =
|
|
478
|
-
if (Math.abs(placementHeight -
|
|
511
|
+
let bridge = modelTentative;
|
|
512
|
+
if (Math.abs(placement.placementHeight - placement.ifcOriginHeight) > 1e-6) {
|
|
479
513
|
const final = await createCesiumBridge(
|
|
480
514
|
mapConversion, projectedCRS, coordinateInfo, lengthUnitScale,
|
|
481
|
-
placementHeight,
|
|
515
|
+
placement.placementHeight,
|
|
482
516
|
);
|
|
483
517
|
if (cancelled) return;
|
|
484
518
|
if (!final) {
|
|
@@ -488,35 +522,38 @@ export function CesiumOverlay({
|
|
|
488
522
|
bridge = final;
|
|
489
523
|
}
|
|
490
524
|
|
|
491
|
-
if (
|
|
525
|
+
if (terrainSample) {
|
|
492
526
|
setCesiumTerrainHeight(terrainH);
|
|
527
|
+
setCesiumTerrainSource(
|
|
528
|
+
`${terrainSample.source}${terrainSample.reference === 'orthometric' ? ' (orthometric)' : ''}`,
|
|
529
|
+
);
|
|
493
530
|
// terrainClipY stays in viewer-space; it represents the world terrain
|
|
494
|
-
// altitude expressed in the bridge's frame
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
// rather than the basement floor.
|
|
498
|
-
const terrainClipY = clampAnchorY + (terrainH - ifcOHeight);
|
|
499
|
-
setCesiumTerrainClipY(terrainClipY);
|
|
531
|
+
// altitude expressed in the camera bridge's committed frame. Draft
|
|
532
|
+
// placement edits must not move this floor, or the camera will drift.
|
|
533
|
+
setCesiumTerrainClipY(cameraPlacement.terrainClipY);
|
|
500
534
|
} else {
|
|
501
535
|
// Failed re-query (offline, API down) — clear stale store fields so
|
|
502
536
|
// the clip plane doesn't drift relative to the new bridge.
|
|
503
537
|
setCesiumTerrainHeight(null);
|
|
538
|
+
setCesiumTerrainSource(null);
|
|
504
539
|
setCesiumTerrainClipY(null);
|
|
505
540
|
}
|
|
506
541
|
|
|
507
542
|
// World-camera stability: when this rebuild changes the placement
|
|
508
|
-
// altitude (
|
|
543
|
+
// altitude (surface floor or OrthogonalHeight edited), shift the IFC
|
|
509
544
|
// viewer-space camera Y by the inverse delta so the user's WORLD
|
|
510
545
|
// camera ECEF position stays put. Without this, the entire frame
|
|
511
546
|
// translates with the model and edits feel like the camera is
|
|
512
547
|
// moving instead of the model — exactly what the user reported.
|
|
513
548
|
const prevPlacement = prevPlacementRef.current;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
549
|
+
if (!usesSeparateCameraBridge) {
|
|
550
|
+
prevPlacementRef.current = placement.placementHeight;
|
|
551
|
+
}
|
|
552
|
+
if (!usesSeparateCameraBridge && prevPlacement !== null) {
|
|
553
|
+
const dh = placement.placementHeight - prevPlacement;
|
|
517
554
|
// 5 cm threshold — rejects float jitter from cached terrain reads
|
|
518
555
|
// re-flowing through the same effect, while a real placement edit
|
|
519
|
-
//
|
|
556
|
+
// is always far larger.
|
|
520
557
|
if (Math.abs(dh) > 0.05) {
|
|
521
558
|
const renderer = getGlobalRenderer();
|
|
522
559
|
if (renderer) {
|
|
@@ -530,7 +567,18 @@ export function CesiumOverlay({
|
|
|
530
567
|
}
|
|
531
568
|
}
|
|
532
569
|
|
|
570
|
+
let cameraBridge = usesSeparateCameraBridge ? cameraTentative : bridge;
|
|
571
|
+
if (usesSeparateCameraBridge && Math.abs(cameraPlacement.placementHeight - cameraPlacement.ifcOriginHeight) > 1e-6) {
|
|
572
|
+
const finalCamera = await createCesiumBridge(
|
|
573
|
+
cameraConversion, projectedCRS, coordinateInfo, lengthUnitScale,
|
|
574
|
+
cameraPlacement.placementHeight,
|
|
575
|
+
);
|
|
576
|
+
if (cancelled) return;
|
|
577
|
+
cameraBridge = finalCamera ?? cameraTentative;
|
|
578
|
+
}
|
|
579
|
+
|
|
533
580
|
bridgeRef.current = bridge;
|
|
581
|
+
cameraBridgeRef.current = cameraBridge;
|
|
534
582
|
setBridgeVersion((v) => v + 1);
|
|
535
583
|
})();
|
|
536
584
|
|
|
@@ -539,7 +587,20 @@ export function CesiumOverlay({
|
|
|
539
587
|
// owns those (it destroys/recreates the viewer when they change), and
|
|
540
588
|
// listing them here would cause a redundant bridge rebuild while the
|
|
541
589
|
// viewer is being torn down.
|
|
542
|
-
}, [
|
|
590
|
+
}, [
|
|
591
|
+
status,
|
|
592
|
+
mapConversion,
|
|
593
|
+
cameraMapConversion,
|
|
594
|
+
projectedCRS,
|
|
595
|
+
coordinateInfo,
|
|
596
|
+
lengthUnitScale,
|
|
597
|
+
terrainEnabled,
|
|
598
|
+
dataSource,
|
|
599
|
+
storeyElevations,
|
|
600
|
+
setCesiumTerrainHeight,
|
|
601
|
+
setCesiumTerrainSource,
|
|
602
|
+
setCesiumTerrainClipY,
|
|
603
|
+
]);
|
|
543
604
|
|
|
544
605
|
// ─── Effect 2c: Load GLB into Cesium (only when geometry changes) ───────
|
|
545
606
|
// This is the heavy operation — only re-runs when geometry actually changes.
|
|
@@ -630,7 +691,7 @@ export function CesiumOverlay({
|
|
|
630
691
|
}, [status, bridgeVersion, geometryResult]);
|
|
631
692
|
|
|
632
693
|
// ─── Effect 2d: Update model matrix (instant, no reload) ────────────────
|
|
633
|
-
// When terrain
|
|
694
|
+
// When terrain placement or georef changes, just update the
|
|
634
695
|
// existing model's matrix — no GLB re-export, no flicker.
|
|
635
696
|
useEffect(() => {
|
|
636
697
|
const model = cesiumModelRef.current;
|
|
@@ -643,10 +704,8 @@ export function CesiumOverlay({
|
|
|
643
704
|
model.modelMatrix = newMatrix;
|
|
644
705
|
viewer.scene.requestRender();
|
|
645
706
|
// Depend on bridgeVersion so the matrix is rebuilt with the *new* bridge
|
|
646
|
-
// after async createCesiumBridge replaces it. Placement
|
|
647
|
-
//
|
|
648
|
-
// clamp/height changes drive a bridge rebuild instead of a per-frame
|
|
649
|
-
// matrix recomputation here.
|
|
707
|
+
// after async createCesiumBridge replaces it. Placement is baked into
|
|
708
|
+
// bridge.modelOrigin.height by Effect 2.
|
|
650
709
|
}, [mapConversion, projectedCRS, coordinateInfo, lengthUnitScale, bridgeVersion]);
|
|
651
710
|
|
|
652
711
|
// ─── Effect 3: Camera sync loop ─────────────────────────────────────────
|
|
@@ -662,23 +721,32 @@ export function CesiumOverlay({
|
|
|
662
721
|
if (cancelled) return;
|
|
663
722
|
|
|
664
723
|
const bridge = bridgeRef.current;
|
|
724
|
+
const cameraBridge = cameraBridgeRef.current ?? bridge;
|
|
665
725
|
const renderer = getGlobalRenderer();
|
|
666
726
|
const Cesium = cesiumModule;
|
|
667
|
-
if (!viewer || !bridge || !renderer || !Cesium) {
|
|
727
|
+
if (!viewer || !bridge || !cameraBridge || !renderer || !Cesium) {
|
|
668
728
|
rafRef.current = requestAnimationFrame(syncCamera);
|
|
669
729
|
return;
|
|
670
730
|
}
|
|
671
731
|
|
|
672
732
|
const camera = renderer.getCamera();
|
|
673
|
-
|
|
674
|
-
|
|
733
|
+
let camPos = camera.getPosition();
|
|
734
|
+
let camTarget = camera.getTarget();
|
|
675
735
|
const camUp = camera.getUp();
|
|
676
736
|
const fov = camera.getFOV();
|
|
677
737
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
738
|
+
if (terrainClipY !== null) {
|
|
739
|
+
const minCameraY = terrainClipY + 0.05;
|
|
740
|
+
if (camPos.y < minCameraY) {
|
|
741
|
+
const dy = minCameraY - camPos.y;
|
|
742
|
+
camPos = { ...camPos, y: minCameraY };
|
|
743
|
+
camTarget = { ...camTarget, y: camTarget.y + dy };
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// bridge.modelOrigin.height already has the placement baked in, so the
|
|
748
|
+
// camera frame and the model matrix share the same enuToEcef origin altitude.
|
|
749
|
+
cameraBridge.syncCamera(Cesium, viewer, camPos, camTarget, camUp, fov);
|
|
682
750
|
|
|
683
751
|
rafRef.current = requestAnimationFrame(syncCamera);
|
|
684
752
|
}
|
|
@@ -692,7 +760,7 @@ export function CesiumOverlay({
|
|
|
692
760
|
rafRef.current = null;
|
|
693
761
|
}
|
|
694
762
|
};
|
|
695
|
-
}, [status]);
|
|
763
|
+
}, [status, terrainClipY]);
|
|
696
764
|
|
|
697
765
|
if (!cesiumEnabled || !mapConversion || !projectedCRS) {
|
|
698
766
|
return null;
|
|
@@ -721,7 +789,7 @@ export function CesiumOverlay({
|
|
|
721
789
|
}
|
|
722
790
|
|
|
723
791
|
/**
|
|
724
|
-
* Add the
|
|
792
|
+
* Add the Google Photorealistic 3D Tiles layer to the Cesium viewer.
|
|
725
793
|
*/
|
|
726
794
|
async function addDataSourceLayer(
|
|
727
795
|
Cesium: typeof import('cesium'),
|
|
@@ -731,16 +799,11 @@ async function addDataSourceLayer(
|
|
|
731
799
|
) {
|
|
732
800
|
try {
|
|
733
801
|
switch (dataSource) {
|
|
734
|
-
case '
|
|
735
|
-
|
|
736
|
-
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(96188);
|
|
737
|
-
viewer.scene.primitives.add(tileset);
|
|
738
|
-
break;
|
|
739
|
-
}
|
|
740
|
-
case 'google-photorealistic': {
|
|
802
|
+
case 'google-photorealistic':
|
|
803
|
+
default: {
|
|
741
804
|
try {
|
|
742
805
|
const tileset = await Cesium.createGooglePhotorealistic3DTileset();
|
|
743
|
-
|
|
806
|
+
viewer.scene.primitives.add(tileset);
|
|
744
807
|
} catch {
|
|
745
808
|
if (ionToken) {
|
|
746
809
|
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(2275207);
|
|
@@ -749,10 +812,6 @@ async function addDataSourceLayer(
|
|
|
749
812
|
}
|
|
750
813
|
break;
|
|
751
814
|
}
|
|
752
|
-
case 'bing-aerial':
|
|
753
|
-
default:
|
|
754
|
-
// No 3D tileset for Bing — imagery is added separately via imageryLayers
|
|
755
|
-
break;
|
|
756
815
|
}
|
|
757
816
|
} catch (err) {
|
|
758
817
|
console.warn('[CesiumOverlay] Failed to add data source:', dataSource, err);
|