From 161126bd4cdd04087b8b1103ab275425668bca95 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 25 May 2026 21:26:12 +0800 Subject: [PATCH] fix: match world splat initial camera to reference view --- components/canvas/world3d/World3DViewer.tsx | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/components/canvas/world3d/World3DViewer.tsx b/components/canvas/world3d/World3DViewer.tsx index b1f1613ce..b2db8b16b 100644 --- a/components/canvas/world3d/World3DViewer.tsx +++ b/components/canvas/world3d/World3DViewer.tsx @@ -101,6 +101,7 @@ const DEFAULT_MESH_SETTINGS: Model3DViewerSettings = { }; const WORLDLABS_ASSET_ROTATION: [number, number, number] = [Math.PI, 0, 0]; +const WORLDLABS_REFERENCE_VIEW_AXIS = new THREE.Vector3(0, 0, 1); const EMPTY_MOVEMENT_STATE: MovementState = { forward: false, @@ -320,6 +321,14 @@ function getSplatWorldBounds(splatMesh: SparkSplatMesh) { .applyMatrix4(splatMesh.matrixWorld); } +function getWorldLabsDisplayGroundPlaneOffset(value: unknown) { + if (typeof value !== "number" || !Number.isFinite(value)) return null; + + // World Labs semantics are authored before the SPZ/GLB is rotated into our + // Three.js display coordinates. The X-axis 180deg rotation flips vertical Y. + return -value; +} + function getLoadedSplatCount(splatMesh: SparkSplatMesh) { try { return Math.max( @@ -407,8 +416,12 @@ function positionCameraInsideWorld({ const center = worldBounds.getCenter(new THREE.Vector3()); const size = worldBounds.getSize(new THREE.Vector3()); - const horizontalAxis = + const longestHorizontalAxis = size.z >= size.x ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(1, 0, 0); + const horizontalAxis = + firstPerson && size.z >= 0.8 + ? WORLDLABS_REFERENCE_VIEW_AXIS.clone() + : longestHorizontalAxis; const majorSpan = Math.max(horizontalAxis.z === 1 ? size.z : size.x, 1.4); const clampInsideAxis = ( @@ -1288,10 +1301,9 @@ function WorldSplatScene({ }), [minimalFrame, onLoadProgress, splatBytes, splatFileName, splatUrl] ); - const groundPlaneOffset = - typeof semanticsMetadata?.ground_plane_offset === "number" - ? semanticsMetadata.ground_plane_offset - : null; + const groundPlaneOffset = getWorldLabsDisplayGroundPlaneOffset( + semanticsMetadata?.ground_plane_offset + ); const handleOrbitControlsRef = React.useCallback((controls: any | null) => { orbitControlsRef.current = controls; setControlsMountVersion((version) => version + 1);