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)
forward_azimuth
property
¶
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 ¶
Orbit around the target.
Parameters¶
d_azimuth : Azimuth delta in degrees. d_elevation : Elevation delta in degrees (clamped to ±89°).
zoom ¶
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().
pan ¶
Translate the target point in screen space.
Useful for middle-mouse-button panning.
Parameters¶
dx, dy : Pixel offsets in screen space.
look_at ¶
Point the camera at a new target without changing distance.
to_snapshot ¶
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))
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 |