From 57b72922690013aa045417e3d5736d79774526fa Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 30 Apr 2026 17:38:13 +0800 Subject: [PATCH] fix: improve world viewer camera targeting --- components/canvas/world3d/World3DViewer.tsx | 25 +++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/components/canvas/world3d/World3DViewer.tsx b/components/canvas/world3d/World3DViewer.tsx index b78ae4408..1492bb5e0 100644 --- a/components/canvas/world3d/World3DViewer.tsx +++ b/components/canvas/world3d/World3DViewer.tsx @@ -286,11 +286,13 @@ function positionCameraInsideWorld({ controls, splatMesh, groundPlaneOffset, + firstPerson = false, }: { camera: THREE.Camera; controls: { target?: THREE.Vector3; update?: () => void } | null; splatMesh: SparkSplatMesh; groundPlaneOffset: number | null; + firstPerson?: boolean; }): WorldCameraPose | null { if (!(camera instanceof THREE.PerspectiveCamera)) return null; @@ -364,28 +366,30 @@ function positionCameraInsideWorld({ lookDirection.copy(horizontalAxis).multiplyScalar(-1); } lookDirection.normalize(); - const targetPosition = cameraPosition + const nearTargetPosition = cameraPosition .clone() .addScaledVector(lookDirection, THREE.MathUtils.clamp(majorSpan * 0.24, 0.75, 2.4)); - targetPosition.y = THREE.MathUtils.clamp( + nearTargetPosition.y = THREE.MathUtils.clamp( eyeY - THREE.MathUtils.clamp(size.y * 0.025, 0.04, 0.18), minEyeY <= maxEyeY ? minEyeY : worldBounds.min.y, minEyeY <= maxEyeY ? maxEyeY : worldBounds.max.y ); - targetPosition.x = clampInsideAxis( - targetPosition.x, + nearTargetPosition.x = clampInsideAxis( + nearTargetPosition.x, worldBounds.min.x, worldBounds.max.x, Math.max(size.x * 0.08, 0.18), center.x ); - targetPosition.z = clampInsideAxis( - targetPosition.z, + nearTargetPosition.z = clampInsideAxis( + nearTargetPosition.z, worldBounds.min.z, worldBounds.max.z, Math.max(size.z * 0.08, 0.18), center.z ); + const orbitTargetPosition = anchor.clone(); + const targetPosition = firstPerson ? nearTargetPosition : orbitTargetPosition; camera.position.copy(cameraPosition); camera.lookAt(targetPosition); @@ -879,6 +883,7 @@ function WorldSplatScene({ lastCameraPoseRef.current = positionCameraInsideWorld({ camera, controls: orbitControlsRef.current, + firstPerson, groundPlaneOffset, splatMesh, }); @@ -912,7 +917,7 @@ function WorldSplatScene({ } splatMesh.dispose(); }; - }, [camera, groundPlaneOffset, onLoadStateChange, sparkRenderer, splatMesh]); + }, [camera, firstPerson, groundPlaneOffset, onLoadStateChange, sparkRenderer, splatMesh]); useEffect(() => { if (!hasAppliedInitialViewRef.current) return; @@ -921,10 +926,11 @@ function WorldSplatScene({ lastCameraPoseRef.current = positionCameraInsideWorld({ camera, controls: orbitControlsRef.current, + firstPerson, groundPlaneOffset, splatMesh, }); - }, [camera, groundPlaneOffset, resetViewToken, splatMesh]); + }, [camera, firstPerson, groundPlaneOffset, resetViewToken, splatMesh]); useEffect(() => { if (!hasAppliedInitialViewRef.current) { @@ -932,10 +938,11 @@ function WorldSplatScene({ return; } - if (!lastFirstPersonRef.current && firstPerson) { + if (lastFirstPersonRef.current !== firstPerson) { lastCameraPoseRef.current = positionCameraInsideWorld({ camera, controls: orbitControlsRef.current, + firstPerson, groundPlaneOffset, splatMesh, });