diff --git a/apps/editor/public/items/ceiling-fan/model.glb b/apps/editor/public/items/ceiling-fan/model.glb index 093d54dad..b8092f740 100644 Binary files a/apps/editor/public/items/ceiling-fan/model.glb and b/apps/editor/public/items/ceiling-fan/model.glb differ diff --git a/packages/viewer/src/components/viewer/glb-walkthrough-controller.tsx b/packages/viewer/src/components/viewer/glb-walkthrough-controller.tsx index f235f773f..50c835429 100644 --- a/packages/viewer/src/components/viewer/glb-walkthrough-controller.tsx +++ b/packages/viewer/src/components/viewer/glb-walkthrough-controller.tsx @@ -15,6 +15,7 @@ import { Mesh, MeshBasicMaterial, type Object3D, + type PerspectiveCamera, Quaternion, Vector3, } from 'three' @@ -24,6 +25,7 @@ import { useGLTFKTX2 } from '../../hooks/use-gltf-ktx2' import { SCENE_LAYER } from '../../lib/layers' import useViewer from '../../store/use-viewer' import BVHEcctrl, { type BVHEcctrlApi, type MovementInput } from './bvh-ecctrl' +import { WALKTHROUGH_FOV } from './walkthrough-controls' // Eye/capsule geometry mirrors the editor's first-person controller so the // baked walkthrough feels identical. The capsule centre sits below the eye; the @@ -264,6 +266,22 @@ export function GlbWalkthroughController({ url }: { url: string }) { } }, []) + // Widen FOV while walking; the baked walkthrough rides the default 50° orbit + // camera, which feels cramped on foot. Keyed on `camera` so it re-applies if + // the instance swaps (e.g. the ortho→perspective switch above), restoring the + // prior FOV on exit. + useEffect(() => { + const cam = camera as PerspectiveCamera + if (!cam.isPerspectiveCamera) return + const prevFov = cam.fov + cam.fov = WALKTHROUGH_FOV + cam.updateProjectionMatrix() + return () => { + cam.fov = prevFov + cam.updateProjectionMatrix() + } + }, [camera]) + useEffect(() => { worldRef.current = world if (world) { diff --git a/packages/viewer/src/components/viewer/walkthrough-controls.tsx b/packages/viewer/src/components/viewer/walkthrough-controls.tsx index b3d55f288..65c6b69f1 100644 --- a/packages/viewer/src/components/viewer/walkthrough-controls.tsx +++ b/packages/viewer/src/components/viewer/walkthrough-controls.tsx @@ -3,12 +3,18 @@ import { PointerLockControls } from '@react-three/drei' import { useFrame, useThree } from '@react-three/fiber' import { useCallback, useEffect, useRef } from 'react' -import { Vector3 } from 'three' +import { type PerspectiveCamera, Vector3 } from 'three' import useViewer from '../../store/use-viewer' const MOVE_SPEED = 5 const EYE_HEIGHT = 1.6 +// First-person FOV. The orbit camera is 50° (set on the Canvas), which feels +// cramped on foot; ~60° vertical (~90° horizontal at 16:9) restores peripheral +// awareness without wide-angle distortion. Applied only while walking — both +// walkthrough controllers read this and restore the orbit FOV on exit. +export const WALKTHROUGH_FOV = 60 + const _direction = new Vector3() const _forward = new Vector3() const _right = new Vector3() @@ -26,6 +32,20 @@ export const WalkthroughControls = () => { } }, [walkthroughMode, camera]) + // Widen FOV while walking; restore the orbit FOV on exit. + useEffect(() => { + if (!walkthroughMode) return + const cam = camera as PerspectiveCamera + if (!cam.isPerspectiveCamera) return + const prevFov = cam.fov + cam.fov = WALKTHROUGH_FOV + cam.updateProjectionMatrix() + return () => { + cam.fov = prevFov + cam.updateProjectionMatrix() + } + }, [walkthroughMode, camera]) + // Keyboard handlers useEffect(() => { if (!walkthroughMode) return