Skip to content

feat(viewer): configurable walkthrough FOV with slider overlay#240

Open
b9llach wants to merge 1 commit intopascalorg:mainfrom
b9llach:feat/walkthrough-fov
Open

feat(viewer): configurable walkthrough FOV with slider overlay#240
b9llach wants to merge 1 commit intopascalorg:mainfrom
b9llach:feat/walkthrough-fov

Conversation

@b9llach
Copy link
Copy Markdown
Contributor

@b9llach b9llach commented Apr 15, 2026

Problem

packages/viewer/src/components/viewer/viewer-camera.tsx hardcodes fov={50} on the perspective camera. That framing is photogenic for outside-looking-in orbit views, but feels telephoto once the user enters walkthrough mode — standing inside a typical room the scene reads as a narrow viewport instead of a walkable space, and peripheral context disappears as soon as you start moving with WASD. There was no way to adjust it without editing the source.

Fix

Add a walkthroughFov setting on useViewer (default 85°, clamped 50–110° by the setter, persisted under the existing viewer-preferences key). ViewerCamera reads it and swaps the perspective camera's FOV while walkthroughMode is true, reverting to the 50° orbit framing when walkthrough exits.

85° is in the standard FPS range (Quake/CS ≈ 90°, Valorant 103°) without the near-wall fisheye distortion wider values produce. The clamp in the setter keeps values from drifting into sniper-scope territory on the low end or unusable fisheye on the high end.

A new <WalkthroughFovSlider /> component exported from @pascal-app/viewer provides runtime adjustment:

  • Self-gates on walkthroughMode (returns null otherwise), so consumers can mount it unconditionally alongside their walkthrough UI without their own visibility checks
  • Plain inline styles with a violet-500 accent to match the editor palette — no Tailwind / shadcn / design-token dependencies, because the viewer package has to stay editor-agnostic and work in any host app
  • Renders bottom-right by default, overridable via className

The editor's FirstPersonOverlay now mounts the slider alongside the existing crosshair and controls hint.

Shared walkthrough state

One small coupling: setFirstPersonMode in packages/editor/src/store/use-editor.tsx now mirrors its flag into useViewer.walkthroughMode. The editor and viewer each had their own walkthrough state — isFirstPersonMode in useEditor, walkthroughMode in useViewer — and the desktop editor's walkthrough button only flipped the editor-side flag. Without the mirror, the FOV conditional in viewer-camera.tsx never tripped on `/edit/[id]` because walkthroughMode stayed false the whole time.

Safe because custom-camera-controls.tsx already early-returns on isFirstPersonMode before it would mount WalkthroughControls, so there's no pointer-lock conflict between FirstPersonControls and WalkthroughControls.

Scope

  • Perspective camera + walkthrough: new behaviour, wider FOV and a slider.
  • Perspective camera + orbit: unchanged (still 50°).
  • Orthographic camera: unchanged — ortho has no FOV concept.
  • Read-only /viewer/[id] path: the slider is opt-in via the exported component, so consumers that don't mount it see no visual or behavioural difference.

How to test

  1. bun dev, open `/edit/[id]` on an existing scene.
  2. Click the desktop editor's walkthrough toggle: the FOV should widen to 85° and the slider should appear bottom-right.
  3. Drag the slider between 50° and 110°: the camera FOV should update live.
  4. Exit walkthrough: FOV reverts to 50° orbit framing.
  5. Reload the page: last-used FOV persists (stored in `localStorage` under `viewer-preferences`).
  6. Try setting the FOV below 50 or above 110 programmatically: the setter clamps silently.
  7. bun check, bun check-types, bun run build all clean on the touched packages.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (bun check passes on the touched files — verified via biome check at @biomejs/biome@^2.4.6)
  • I've updated relevant documentation (N/A — no docs affected)
  • This PR targets the main branch

@b9llach b9llach force-pushed the feat/walkthrough-fov branch from 9d26100 to 629dfd9 Compare April 15, 2026 16:32
@b9llach b9llach marked this pull request as ready for review April 15, 2026 16:35
reneruano95 pushed a commit to reneruano95/editor that referenced this pull request Apr 15, 2026
`packages/viewer/src/components/viewer/viewer-camera.tsx` hardcoded
`fov={50}` on the perspective camera. That framing is photogenic for
outside-looking-in orbit views, but feels telephoto once the user
enters walkthrough mode — standing inside a typical room the
scene reads as a narrow viewport instead of a walkable space, and
peripheral context disappears as soon as you start moving with WASD.

This adds a `walkthroughFov` setting to `useViewer` (default 85°,
clamped 50–110° by the setter, persisted under the existing
`viewer-preferences` key). `ViewerCamera` reads it and swaps the
perspective camera's FOV while `walkthroughMode` is true, reverting
to the 50° orbit framing when walkthrough exits. 85° is in the
standard FPS range (Quake/CS ≈ 90°, Valorant 103°) without the
near-wall fisheye distortion wider values produce. The clamp in the
setter keeps values from drifting into sniper-scope territory on the
low end or unusable fisheye on the high end.

A new `<WalkthroughFovSlider />` component exported from
`@pascal-app/viewer` provides runtime adjustment. It self-gates on
`walkthroughMode` (returns `null` otherwise) so consumers can mount
it unconditionally alongside their walkthrough UI. Styling is plain
inline CSS with a violet-500 accent so it works in any host app
without pulling in Tailwind / shadcn / design-token dependencies —
the viewer package has to stay editor-agnostic. Renders bottom-right
by default, overridable via `className`.

The editor's `FirstPersonOverlay` now mounts the slider alongside
the existing crosshair and controls hint.

One small coupling: `setFirstPersonMode` in
`packages/editor/src/store/use-editor.tsx` now mirrors its flag into
`useViewer.walkthroughMode`. The editor and viewer each had their
own walkthrough state — `isFirstPersonMode` in `useEditor`,
`walkthroughMode` in `useViewer` — and the desktop editor's
"walkthrough" button only flipped the editor-side flag. Without the
mirror, the FOV conditional in `viewer-camera.tsx` never tripped on
`/edit/[id]` because `walkthroughMode` stayed false the whole time.
Safe because `custom-camera-controls.tsx` already early-returns on
`isFirstPersonMode` before it would mount `WalkthroughControls`, so
there's no pointer-lock conflict between `FirstPersonControls` and
`WalkthroughControls`.

Caveats:
- Orthographic camera mode is unaffected — ortho has no fov concept.
- The read-only `/viewer/[id]` path is unchanged; the slider is
  opt-in via the exported component, so consumers that don't mount
  it see no visual or behavioural difference.
@b9llach b9llach force-pushed the feat/walkthrough-fov branch from 629dfd9 to dd28b9c Compare April 16, 2026 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant