Skip to content

PersonViewPlugin

PersonViewPlugin is a keyboard-driven first-/third-person view controller. It drives a virtual position on the globe and runs a chase camera (TPV) or first-person camera (FPV) that follows it. Optionally, you can attach a GLTF character: the plugin will load it, drive its position and heading, and cross-fade between an idle and a dash animation clip.

The character is optional. When omitted, the plugin still drives the virtual position and the camera follows it — useful as a pure person-view camera controller for scenes that already have their own avatar or for empty-world fly-throughs.

The plugin broadcasts its position, heading, speed, and current view mode on every frame, making it easy to build HUDs and other UI on top.

import ThreeView from "@navaramap/three";
import { DefaultPlugin } from "@navaramap/three_default_plugin";
import { PersonViewPlugin } from "@navaramap/three_plugins";
const view = new ThreeView({ container, animation: true });
const defaultPlugin = new DefaultPlugin();
const personView = new PersonViewPlugin({
character: {
modelUrl: "/glTF/bird/scene.gltf",
animation: {
idleClip: "Gliding",
dashClip: "Flapping",
speed: 1.0,
crossfadeDuration: 0.3,
},
modelRotationOffset: { x: -Math.PI / 2, y: 0, z: Math.PI },
},
startLat: 35.6812,
startLng: 139.7671,
startHeight: 500,
});
view.addPlugin(defaultPlugin);
view.addPlugin(personView);
await view.init();
// Start the per-frame loop after initialization
personView.start();
// Subscribe to state updates
const unsub = personView.onStateChange((state) => {
console.log(state.lat, state.lng, state.alt, state.heading, state.mode);
});
// Teleport to a new position
personView.teleport({ lng: 139.77, lat: 35.68, alt: 300 });
// Toggle between third-person and first-person view
personView.toggleViewMode();
// Cleanup
unsub();
personView.dispose();
KeyAction
W / SForward / backward
A / DTurn left / right
Arrow Up / SpaceAscend
Arrow Down / CtrlDescend
ShiftDash (2.5x speed; switches to dashClip)
Alt (hold)Free-orbit camera (TPV) / free-look (FPV)
VToggle TPV / FPV

All bindings can be remapped via the keys option (see KeyBindings).

Keyboard input is automatically suppressed when focus is on <input>, <textarea>, or contenteditable elements. You can also set personView.movementSuppressed = true to temporarily disable all movement keys — for example, while a modal dialog is open.

The camera operates in one of two modes:

  • TPV (third-person view) — Chase camera positioned behind and above the character, smoothly interpolating its heading toward the character’s heading.
  • FPV (first-person view) — Camera placed at the character’s eye, looking forward along the heading. The character mesh is hidden by default in FPV (configurable via character.hideModelInFpv).

Press V (or call toggleViewMode()) to switch. Hold Alt to take manual control of the camera: in TPV the camera orbits around the character; in FPV the camera stays planted at the eye and mouse drag rotates the view direction in place (free-look). Set allowCameraControl: true in the config to make the camera always free without needing Alt.

After releasing Alt, the camera keeps the orientation you left it at — useful for dwelling at a custom angle. The free-camera state stays active until you press any movement key (forward/backward/turn/ascend/descend), at which point the camera snaps back to its default chase or FPV position. Dash and V do not exit the free-camera state.

new PersonViewPlugin(config?: PersonViewConfig)
PropertyTypeDefaultDescription
characterCharacterConfig(none)Optional character. When omitted, the plugin runs as a pure camera controller.
allowCameraControlbooleanfalseWhen true, the camera is always free (no Alt-hold required).
initialView"tpv" | "fpv""tpv"Initial view mode.
moveSpeednumber50Forward/backward speed in m/s.
rotationSpeednumber3Turning speed in deg/frame.
altSpeednumber30Altitude change speed in m/s.
minAltnumber50Minimum altitude in meters.
maxAltnumber5000Maximum altitude in meters.
cameraDistancenumber50Chase camera distance (TPV) in meters.
cameraPitchnumber0Downward TPV camera pitch in radians (orbits up and over the model).
cameraLerpSpeednumber3Camera heading interpolation speed.
fpvForwardOffsetnumber0Forward offset (m) applied to the FPV eye position.
fpvHeightOffsetnumber1Eye-line height offset (m): the FPV eye height, and the shared eye-line height the TPV camera orbits around.
fpvPitchnumber0Downward FPV camera pitch in radians (tilts the view down in place).
startLatnumber35.6812Starting latitude in degrees.
startLngnumber139.7671Starting longitude in degrees.
startHeightnumber500Starting altitude in meters.
startHeadingnumberMath.PI * 1.3Starting heading in radians (0 = north).
keysKeyBindingsdefaultsKeyboard bindings — see KeyBindings.
PropertyTypeDefaultDescription
modelUrlstring(required)URL of the GLTF model to load.
animationAnimationConfig(required)Animation clip configuration.
modelRotationOffsetModelRotationOffset{ x: 0, y: 0, z: 0 }Rotation offset to correct the model’s default orientation.
modelScalenumber3Uniform scale multiplier for the model.
hideModelInFpvbooleantrueHide the model while the camera is in FPV.
castShadowbooleanfalseWhether the character casts shadows.
receiveShadowbooleanfalseWhether the character receives shadows.
PropertyTypeDescription
idleClipstringClip played while the model is idle (no movement keys held).
walkClipstring?Clip played while the model is moving without dashing. Omit to keep idleClip running — useful for idle+dash-only models.
dashClipstringClip played while the model is dashing (dash key held).
speednumberPlayback speed multiplier.
crossfadeDurationnumberDuration in seconds for cross-fade transitions between clips.
PropertyTypeDescription
xnumberRotation offset around the X axis (radians).
ynumberRotation offset around the Y axis (radians).
znumberRotation offset around the Z axis (radians).

Each entry takes an array of KeyboardEvent.code values (e.g. ["KeyW"], ["ArrowUp", "ControlLeft"]) so multiple keys can trigger the same action.

PropertyTypeDefaultDescription
forwardstring[]["KeyW"]Move forward.
backwardstring[]["KeyS"]Move backward.
turnLeftstring[]["KeyA"]Turn left.
turnRightstring[]["KeyD"]Turn right.
ascendstring[]["ArrowUp", "Space"]Climb.
descendstring[]["ArrowDown", "ControlLeft", "ControlRight"]Descend.
dashstring[]["ShiftLeft", "ShiftRight"]Hold to dash.
orbitCamerastring[]["AltLeft", "AltRight"]Hold to enable free camera (orbit in TPV / free-look in FPV). After release, the camera keeps its orientation until a movement key is pressed.
toggleViewstring[]["KeyV"]Toggle TPV / FPV.
start(): void

Loads the GLTF model (when configured) and starts the per-frame update loop. Must be called after view.init() completes.

teleport(options: {
lng: number;
lat: number;
alt: number;
heading?: number;
}): void

Instantly moves to a new geographic position. When heading is omitted, the current camera heading is kept. To rotate in place without moving, use setHeading(); for the camera pitch, use setCameraPitch() / setFpvPitch().

FieldTypeDescription
lngnumberLongitude in degrees.
latnumberLatitude in degrees.
altnumberAltitude in meters.
headingnumber | undefinedOptional heading in radians (0 = north, increasing clockwise).
setHeading(radians: number): void
getHeading(): number

Rotates the character to the given heading in radians (0 = north, increasing clockwise) without changing position. The chase camera snaps to match; in free-camera mode only the model rotates. getHeading() returns the current heading.

setCameraPitch(radians) / setFpvPitch(radians)

Section titled “setCameraPitch(radians) / setFpvPitch(radians)”
setCameraPitch(radians: number): void
getCameraPitch(): number
setFpvPitch(radians: number): void
getFpvPitch(): number

Set the downward camera pitch in radians, taking effect immediately for the chase / locked camera. setCameraPitch controls the TPV pitch (orbits the camera up and over the model), while setFpvPitch controls the FPV pitch (tilts the view down in place). The matching getters return the current values.

setFpvHeightOffset(meters) / getFpvHeightOffset()

Section titled “setFpvHeightOffset(meters) / getFpvHeightOffset()”
setFpvHeightOffset(meters: number): void
getFpvHeightOffset(): number

Set the eye-line height offset in meters, taking effect immediately for the chase / locked camera. It is the FPV eye height and the shared eye-line height the TPV camera orbits around and aims at. getFpvHeightOffset() returns the current value.

setViewMode(mode: "tpv" | "fpv"): void
toggleViewMode(): void

Switch the camera between third-person and first-person view.

setAllowCameraControl(value: boolean): void

Enable or disable always-free camera at runtime. The Alt-hold behavior still works regardless.

getState(): PersonViewState

Returns the current view state.

onStateChange(fn: (state: PersonViewState) => void): () => void

Subscribes to state updates emitted on every animation frame. Returns an unsubscribe function.

dispose(): void

Stops the animation loop, removes keyboard listeners, and deletes the character (when one was configured).

The state object emitted by onStateChange():

PropertyTypeDescription
lngnumberCurrent longitude in degrees.
latnumberCurrent latitude in degrees.
altnumberCurrent altitude in meters.
headingnumberCurrent heading in radians (0 = north, increasing clockwise).
speednumberConfigured movement speed in m/s (moveSpeed, multiplied by 2.5 while dashing).
animationStatestring | nullName of the currently playing clip; null when no character.
mode"tpv" | "fpv"Current view mode.