Skip to content

forge3d.Camera

Two camera controllers that produce CameraSnapshot for the Viewer.


OrbitCamera

OrbitCamera

Spherical-coordinate camera that orbits a target point.

The camera is always looking at target from a point on a sphere of radius distance. azimuth (yaw) rotates around the z-axis; elevation (pitch) tilts above/below the horizon.

Parameters

target : Point the camera looks at, world-frame (x, y, z). distance : Eye-to-target distance in metres. azimuth : Horizontal angle in degrees (0 = +x axis). elevation : Vertical angle in degrees above the horizon (clamped ±89°). fov_deg : Vertical field-of-view in degrees.

Usage::

cam = f3d.OrbitCamera(target=(0, 0, 1), distance=8)
cam.rotate(d_azimuth=45)         # spin 45° around target
cam.zoom(2)                      # move 20% closer (delta > 0 = closer)
snap = cam.to_snapshot()
viewer.set_camera(snap)

position property

position: ndarray

Current world-space eye position.

forward_azimuth property

forward_azimuth: float

Azimuth the camera is looking toward (azimuth + 180°, mod 360).

Useful as a "player forward" heading for camera-relative movement::

yaw_deg = cam.forward_azimuth
fwd = (cos(radians(yaw_deg)), sin(radians(yaw_deg)), 0)

rotate

rotate(d_azimuth: float = 0.0, d_elevation: float = 0.0) -> OrbitCamera

Orbit around the target.

Parameters

d_azimuth : Azimuth delta in degrees. d_elevation : Elevation delta in degrees (clamped to ±89°).

zoom

zoom(delta: float) -> OrbitCamera

Adjust distance.

delta > 0 moves the camera closer; delta < 0 moves it farther. The factor is distance *= (1 - delta * 0.1) so a delta of 1.0 reduces distance by 10%.

Parameters

delta : Zoom speed; typically Input.scroll_delta().

set_distance

set_distance(d: float) -> OrbitCamera

Directly set the eye-to-target distance.

pan

pan(dx: float, dy: float) -> OrbitCamera

Translate the target point in screen space.

Useful for middle-mouse-button panning.

Parameters

dx, dy : Pixel offsets in screen space.

look_at

look_at(target: Any) -> OrbitCamera

Point the camera at a new target without changing distance.

to_snapshot

to_snapshot() -> CameraSnapshot

Build a :class:~forge3d.render.snapshot.CameraSnapshot from the current orbit state — suitable for :meth:Viewer.set_camera.

handle_input

handle_input(inp: Any, dt: float, *, rotate_button: int = 1, mouse_sensitivity: float = 0.25, rotate_key_left: str = 'q', rotate_key_right: str = 'e', pitch_key_up: str = 'r', pitch_key_down: str = 'f', key_deg_per_s: float = 130.0, scroll_zoom: bool = True, min_distance: float = 1.0, max_distance: float = 50.0, min_elevation: float = -89.0, max_elevation: float = 89.0) -> OrbitCamera

Process one frame of keyboard / mouse input and update the camera.

Designed to replace per-game boilerplate camera-rig update logic. Call once per frame before :meth:to_snapshot::

cam.handle_input(inp, dt).follow(player_pos, dt=dt).occlude(world)
viewer.set_camera(cam.to_snapshot())
Parameters

inp : :class:~forge3d.input.Input or :class:~forge3d.input.ScriptedInput. dt : Frame delta-time in seconds. rotate_button : Mouse button held to orbit (default 1 = right). mouse_sensitivity: Degrees per pixel for mouse drag. rotate_key_left : Key to rotate left (default 'q'). rotate_key_right: Key to rotate right (default 'e'). pitch_key_up : Key to pitch up (default 'r'). pitch_key_down : Key to pitch down (default 'f'). key_deg_per_s : Rotation speed in degrees/s for key-driven orbit. scroll_zoom : If True (default), scroll wheel zooms. min_distance : Minimum zoom distance in metres. max_distance : Maximum zoom distance in metres. min_elevation : Elevation clamp lower bound (degrees). max_elevation : Elevation clamp upper bound (degrees).

Returns self for method chaining.

follow

follow(target: Any, head_height: float = 1.2, smooth_hz: float = 10.0, dt: float = 0.0) -> OrbitCamera

Smoothly move the camera target toward target + head_height.

Replaces the common per-frame smooth_target lerp pattern that third-person games need. Call once per frame before :meth:to_snapshot.

Parameters

target : World position to track — usually player.position. head_height : Vertical offset added to target (default 1.2 m). smooth_hz : Smoothing frequency in Hz — higher = snappier. dt : Frame delta-time; pass 0 for instant snap.

Returns self for method chaining::

cam.follow(player.position, dt=dt).occlude(world)
viewer.set_camera(cam.to_snapshot())

occlude

occlude(world: Any, terrain_sampler: Any = None, min_distance: float = 1.6, layer_mask: int = 1, terrain_steps: int = 16, terrain_clearance: float = 0.35) -> OrbitCamera

Pull the camera distance inward to avoid geometry occlusion.

Raycasts against physics bodies and (optionally) marches the eye ray against a terrain height sampler. Sets self.distance to the safe distance and returns self for chaining.

Parameters

world : :class:~forge3d.facade.World to raycast against. terrain_sampler : Callable (x, y) → z for terrain height (e.g. heightfield.height_at or your own function). Pass None to skip terrain marching — when the world has heightfields and raycasts already hit them this is usually not needed. min_distance : Minimum eye-to-target distance (m). layer_mask : Collision layers included in the occlusion raycast. terrain_steps : Number of steps for the terrain march. terrain_clearance: Camera is pulled in when closer than this to terrain.

Returns self::

cam.follow(player.position, dt=dt).occlude(world)
viewer.set_camera(cam.to_snapshot())

FollowCamera

FollowCamera

Camera that smoothly tracks a :class:~forge3d.facade.Body.

The camera sits at body.position + offset and looks at body.position. Supports world-frame and body-local-frame offsets, and uses dt-corrected exponential smoothing (FPS-independent).

Parameters

body : The :class:Body to follow. offset : Camera offset from the body in frame coordinates. frame : "world" (default) — offset is world-aligned; stays fixed regardless of body rotation. "local" — offset is body-local; camera always sits behind the body as it turns (ideal for vehicles). smoothing_hz : Smoothing speed in Hz (reciprocal of time constant τ). Higher = snappier. Default 6 ≈ 0.17 s settling time. Pass smoothing_hz=None for legacy per-frame alpha. alpha : Legacy per-frame lerp factor (ignored when smoothing_hz is set). Kept for backward compatibility. fov_deg : Vertical field-of-view in degrees.

Examples

World-frame offset (original behaviour)

cam = f3d.FollowCamera(ball, offset=(0, -8, 4))

Body-local offset — always behind the car

cam = f3d.FollowCamera(car, offset=(-8, 0, 3), frame="local", ... smoothing_hz=8)

Usage (per-frame)::

viewer.set_camera(cam.to_snapshot(dt=viewer.dt))

to_snapshot

to_snapshot(dt: float | None = None) -> CameraSnapshot

Update smoothed position and return a :class:CameraSnapshot.

Parameters

dt : Elapsed time since last call (seconds). When provided, uses dt-corrected exponential smoothing; otherwise falls back to the legacy per-frame alpha factor.


Usage examples

OrbitCamera — orbit around a target

cam = f3d.OrbitCamera(target=(0, 0, 1), distance=10, elevation=30)

while viewer.is_open:
    inp = viewer.input
    if inp.mouse_button(1):                     # right-drag to orbit
        dx, dy = inp.mouse_delta()
        cam.rotate(d_azimuth=dx * 0.4, d_elevation=-dy * 0.4)
    cam.zoom(inp.scroll_delta() * 0.5)
    viewer.set_camera(cam.to_snapshot())
    world.step()
    viewer.draw()

FollowCamera — world-frame offset (default)

ball = world.add_sphere(radius=0.5, position=(0,0,3), mass=1)

cam = f3d.FollowCamera(ball, offset=(0, -8, 4), smoothing_hz=6)

while viewer.is_open:
    world.step()
    viewer.set_camera(cam.to_snapshot(dt=viewer.dt))
    viewer.draw()

FollowCamera — local frame (vehicle camera) (v1.1.0)

car = world.add_box(size=(2, 1, 0.5), position=(0, 0, 0.3), mass=20)

# Camera always sits 10 m behind and 3 m above the car,
# regardless of which direction the car is facing.
cam = f3d.FollowCamera(
    car,
    offset=(-10, 0, 3),   # local-frame: X=back, Z=up
    frame="local",
    smoothing_hz=8.0,      # snappier (default: 6.0)
    fov_deg=60.0,
)

while viewer.is_open:
    world.step()
    viewer.set_camera(cam.to_snapshot(dt=viewer.dt))
    viewer.draw()

Smoothing comparison

Parameter Meaning Best for
frame="world" Offset stays world-aligned Ball, spacecraft, top-down
frame="local" Offset rotates with the body Car, plane, FPS
smoothing_hz=2 Slow, cinematic lag Cutscenes
smoothing_hz=6 Default, natural Most games
smoothing_hz=20 Near-instant snap Action games