Skip to content

Controls Module API Reference

This reference mirrors the TypeScript exports under src/controls/. All math uses gl-matrix data types (vec2, vec3, quat).

Interfaces

IController

interface IController {
  update(camera: PerspectiveCamera, deltaTime: number): void;
  processKeyboard(code: string, pressed: boolean): boolean;
  processMouse(dx: number, dy: number): void;
  processScroll(delta: number): void;
  leftMousePressed: boolean;
  rightMousePressed: boolean;
  userInput: boolean;
  resetOrientation?(): void;
  getControllerType(): 'orbit' | 'fps';
}

Both CameraController (orbit) and FPSController implement this contract so the app can swap controllers at runtime.

Classes

CameraController

Orbit controller used by default.

class CameraController implements IController {
  constructor(speed = 0.2, sensitivity = 0.1);
  center: vec3;
  up: vec3 | null;
  amount: vec3;   // reserved for keyboard motion
  shift: vec2;    // right-click pan accumulator
  rotation: vec3; // yaw/pitch/roll accumulators
  scroll: number; // logarithmic zoom accumulator
  speed: number;
  sensitivity: number;
  leftMousePressed: boolean;
  rightMousePressed: boolean;
  altPressed: boolean;
  userInput: boolean;

  resetUp(u?: vec3): void;
  processKeyboard(code: string, pressed: boolean): boolean;
  processMouse(dx: number, dy: number): void;
  processScroll(delta: number): void;
  update(camera: PerspectiveCamera, deltaTime: number): void;
  resetOrientation(): void;
  getControllerType(): 'orbit';
}
  • resetUp(u) sets the orbit reference up vector (WORLD_UP by default).
  • processKeyboard/processMouse/processScroll call the helpers from input.ts and set userInput = true when handled.
  • update runs the orbit pipeline described in the architecture doc and updates camera.positionV / camera.rotationQ.

FPSController

WASD + mouse-look controller (experimental but exported).

class FPSController implements IController {
  constructor(moveSpeed?, rotateSpeed?, scrollSpeed?, moveInertia?, rotateInertia?);
  moveSpeed: number;
  rotateSpeed: number;
  scrollSpeed: number;
  moveInertia: number;
  rotateInertia: number;
  flyMode: boolean;
  enable: boolean;
  leftMousePressed: boolean;
  rightMousePressed: boolean;
  userInput: boolean;

  processKeyboard(code: string, pressed: boolean): boolean;
  processMouse(dx: number, dy: number): void;
  processScroll(delta: number): void;
  update(camera: PerspectiveCamera, deltaTime: number): void;
  resetOrientation(): void;
  getOrientation(): { yaw: number; pitch: number };
  setOrientation(yaw: number, pitch: number): void;
  setFlyMode(enabled: boolean): void;
  getControllerType(): 'fps';
}

FPSController listens for document-level key events (in its constructor) and keeps yaw/pitch angles plus velocity vectors for inertia. flyMode = false constrains movement to the XZ plane.

Input helpers (input.ts)

processKeyboardInput(code, pressed, amount, rotation, sensitivity)

Updates amount (vec3) and rotation (vec3) arrays in place. Handles KeyW/A/S/D, Space, ShiftLeft, KeyQ, KeyE. Returns true when a key is recognized.

processMouseInput(dx, dy, leftPressed, rightPressed, rotation, shift)

Adds mouse deltas to rotation (left button) or shift (right button). Returns true if any button was active.

processScrollInput(delta)

Scales scroll wheel deltas (default multiplier 3) and returns the value for accumulation.

Orbit math (orbit.ts)

Constants

  • WORLD_UP = vec3(0, 1, 0)
  • MIN_POLE_ANGLE = 5°
  • MIN_DISTANCE = 0.1
  • MAX_DISTANCE = 1000
  • EPS = 1e-6

Functions

lookAtW2C(forwardWorld: vec3, upWorld: vec3): quat;
projectOntoPlaneNormed(v: vec3, n: vec3, fallback: vec3): vec3;
calculateOrbitBasis(cameraPos: vec3, center: vec3, orbitUp: vec3)
  => { forward: vec3, right: vec3, yawAxis: vec3 };
applyDistanceScaling(cameraPos, center, scroll, deltaTime, speed): number;
applyPanning(center, shift, right, yawAxis, deltaTime, speed, distance): void;
applyRotation(forward, right, yawAxis, yaw, pitch, roll)
  => { forward: vec3, right: vec3, yawAxis: vec3 };
applyDecay(rotation: vec3, shift: vec2, scroll: number, deltaTime: number)
  => { rotation: vec3, shift: vec2, scroll: number };

These functions are stateless; controllers pass in temporary vectors and reuse the results.

Usage snippet

import { CameraController } from 'src/controls';
import { PerspectiveCamera } from 'src/camera';

const controller = new CameraController(0.25, 0.12);
const camera = PerspectiveCamera.default();

function frame(dt: number) {
  controller.update(camera, dt);
  renderer.prepareMulti(encoder, queue, [pointCloud], { camera, viewport: [w, h] });
}

Mouse/keyboard wiring (simplified):

window.addEventListener('keydown', (e) => controller.processKeyboard(e.code, true));
window.addEventListener('keyup',   (e) => controller.processKeyboard(e.code, false));
canvas.addEventListener('mousemove', (e) => controller.processMouse(e.movementX, e.movementY));
canvas.addEventListener('wheel', (e) => controller.processScroll(e.deltaY));

See fps-controller.ts for an alternative setup with inertia and 6DoF flight controls.