Controls Module
src/controls/ houses the input �?camera pipeline for Visionary. It exposes orbit-style controls (default CameraController), an FPS-style controller, pure input helpers, and orbit math utilities. The code mirrors the behaviour of the Rust/Spark stack and plugs directly into the camera module (PerspectiveCamera).
Responsibilities
- Convert DOM events (mouse/keyboard/wheel) into smooth orbiting, panning, and zooming motions.
- Maintain numerically stable basis vectors so the camera never “flips�?near the poles.
- Support alternative control schemes (currently an experimental
FPSController). - Provide stateless helper functions so UI layers can generate custom interactions without touching controller internals.
Components
| Component | Path | Purpose |
|---|---|---|
CameraController |
controller.ts |
Default orbit controller used by the WebGPU viewer. Accumulates input state and updates PerspectiveCamera. |
FPSController |
fps-controller.ts |
WASD + mouse-look controller with inertia, optional ground/fly modes. |
input.ts |
Pure functions that translate keyboard/mouse/scroll deltas into movement vectors (amount, rotation, shift, scroll). |
|
orbit.ts |
Math helpers: orbit basis calculation, logarithmic zoom, panning, rotation with pole protection, lookAtW2C. |
|
base-controller.ts |
Interface (IController) shared by both controllers; defines update, processKeyboard, processMouse, processScroll. |
|
index.ts |
Re-exports controllers, input helpers, and orbit utilities. |
Data Flow
CameraController stores state vectors (rotation, shift, scroll, center, orbit up). Each frame update():
1. Builds orbit basis (calculateOrbitBasis).
2. Applies logarithmic distance scaling for zoom.
3. Applies panning to adjust the orbit center.
4. Applies yaw/pitch/roll with pole protection.
5. Recomputes camera position and rotation quaternion via lookAtW2C.
6. Decays state vectors (applyDecay) for smooth motion.
Integration
- Camera module �?controllers mutate
PerspectiveCamera.positionVandrotationQ. Projection data remains untouched so renderer/preprocess can reuse it. - Renderer/preprocess �?after controllers update the camera, the renderer uploads fresh view/projection matrices and the preprocess step uses the same camera interface.
- UI layer �?
UIController(insrc/app) wires DOM events to controller methods:processKeyboard,processMouse,processScroll, and updatesleftMousePressed/rightMousePressedflags.
Usage Examples
Orbit controller
import { CameraController } from 'src/controls';
const controller = new CameraController(0.2, 0.1);
const camera = PerspectiveCamera.default();
function frame(dt: number) {
controller.update(camera, dt);
renderer.prepareMulti(...); // camera now contains new view/proj
}
Attach events:
canvas.addEventListener('mousedown', (e) => {
if (e.button === 0) controller.leftMousePressed = true;
if (e.button === 2) controller.rightMousePressed = true;
});
canvas.addEventListener('mouseup', () => {
controller.leftMousePressed = controller.rightMousePressed = false;
});
canvas.addEventListener('mousemove', (e) => controller.processMouse(e.movementX, e.movementY));
window.addEventListener('keydown', (e) => controller.processKeyboard(e.code, true));
window.addEventListener('keyup', (e) => controller.processKeyboard(e.code, false));
canvas.addEventListener('wheel', (e) => controller.processScroll(e.deltaY));
FPS controller
import { FPSController } from 'src/controls';
const fps = new FPSController(5.0, 0.002);
function frame(dt: number) {
fps.update(camera, dt);
}
FPSController automatically listens to document-level key events and uses mouse deltas when leftMousePressed is true. Call fps.setFlyMode(false) to restrict movement to the XZ plane.
Orbit math at a glance
- Basis vectors:
forward = normalize(center - cameraPos),yawAxis = project orbitUp onto the plane orthogonal to forward,right = forward × yawAxis. Re-orthogonalised every frame. - Log zoom: distance updates via
exp(log(dist) + scroll * dt * 10 * speed)so zoom feels consistent at any scale. - Pole protection: pitch rotation clamps when
forwardapproachesyawAxiswithin 5°, preventing gimbal lock. - Decay: rotation/shift/scroll values shrink by
pow(0.8, dt * 60)and snap to zero when tiny.
Notes
- All math uses
gl-matrix. Temporary vectors are reused to avoid GC pressure. - Controllers expose
resetUp,resetOrientation, andgetControllerType()for host apps that switch between orbit/FPS modes. - Helper functions (
processKeyboardInput,processMouseInput,processScrollInput,calculateOrbitBasis, etc.) are exported so custom controllers can build on the same primitives.
See Architecture for a deeper dive into the math, and API Reference for method signatures.
Related Docs
- Architecture – Orbit math derivations, controller state machines, and decay tuning.
- API Reference – Controller constructors, input helpers, and exported utility types.
- Camera Module – Describes the
PerspectiveCamerainterface the controllers mutate. - Three Integration – Shows how UI layers wire DOM events into the control system.