Home / Modules / Camera / PoseFollowCameraRig

PoseFollowCameraRig

Pure logic

Follows a target position and targetFrame with a pose-relative offset and pose-relative look target so the view moves and turns with the target.

CategoryCamera
Dependency tierPure logic
Related modulesnone
Demo scenecamera-gallery · Camera rigs ↓
Take just this module
modules/camera/PoseFollowCameraRig.js

Copy it together with its internal dependencies, preserving the relative directory structure.

Camera rigs

three

Auto-driving showcase — no controls needed.

Source

import { BaseCameraRig, CAMERA_HEIGHT_SOURCES, CAMERA_ROTATION_MODES } from './BaseCameraRig.js';
import { toVec3 } from '../math/Vector3Utils.js';
import { DEFAULT_WORLD_BASIS } from '../math/WorldBasis.js';

export class PoseFollowCameraRig extends BaseCameraRig {
  constructor({
    cameraOffset,
    lookAtOffset = { forward: 1, up: 0, right: 0 },
    speedCameraOffset = { forward: 0, up: 0, right: 0 },
    speedLookAtOffset = { forward: 0, up: 0, right: 0 },
    heightVectorSource = CAMERA_HEIGHT_SOURCES.frameUp,
    lookHeightVectorSource = CAMERA_HEIGHT_SOURCES.frameUp,
    positionLag = 0,
    lookLag = 0,
    frameLag = 0,
    rotationMode = CAMERA_ROTATION_MODES.lookAt,
    basis = DEFAULT_WORLD_BASIS,
  }) {
    super({ basis, rotationMode });
    Object.assign(this, {
      cameraOffset,
      lookAtOffset,
      speedCameraOffset,
      speedLookAtOffset,
      heightVectorSource,
      lookHeightVectorSource,
      positionLag,
      lookLag,
      frameLag,
    });
  }

  step({
    targetPosition,
    targetFrame,
    targetSpeed = 0,
    snapToTarget = false,
    deltaSeconds = 1 / 60,
    camera = null,
  }) {
    const frame = this.resolveTargetFrame(targetFrame);
    const focusPosition = toVec3(targetPosition);
    const speed = Math.max(0, targetSpeed ?? 0);
    const heightVector = this.vectorFromSource(this.heightVectorSource, frame);
    const lookHeightVector = this.vectorFromSource(this.lookHeightVectorSource, frame);

    const cameraOffset = this.offsetForSpeed(this.cameraOffset, this.speedCameraOffset, speed);
    const lookAtOffset = this.offsetForSpeed(this.lookAtOffset, this.speedLookAtOffset, speed);

    const desiredPosition = focusPosition.clone()
      .addScaledVector(frame.forward, cameraOffset.forward)
      .addScaledVector(frame.right, cameraOffset.right)
      .addScaledVector(heightVector, cameraOffset.up);
    const desiredLookAt = focusPosition.clone()
      .addScaledVector(frame.forward, lookAtOffset.forward)
      .addScaledVector(frame.right, lookAtOffset.right)
      .addScaledVector(lookHeightVector, lookAtOffset.up);

    this.smoothVector(this.position, desiredPosition, this.positionLag, deltaSeconds, snapToTarget);
    this.smoothVector(this.lookAt, desiredLookAt, this.lookLag, deltaSeconds, snapToTarget);

    const desiredForward = frame.forward.clone();
    const desiredUp = frame.up.clone();
    this.smoothVector(this.forward, desiredForward, this.frameLag, deltaSeconds, snapToTarget).normalize();
    this.smoothVector(this.up, desiredUp, this.frameLag, deltaSeconds, snapToTarget).normalize();

    if (this.rotationMode === CAMERA_ROTATION_MODES.frame) {
      this.setFramePose({
        position: this.position,
        forward: this.forward,
        up: this.up,
      });
    } else if(this.rotationMode == CAMERA_ROTATION_MODES.lookAt) {
      this.setLookAtPose({
        position: this.position,
        lookAt: this.lookAt,
        up: frame.up,
      });
    }

    const pose = this.getPose();
    this.applyToCamera(camera, pose);
    return pose;
  }

  offsetForSpeed(offset, speedOffset, speed) {
    return {
      forward: offset.forward + speedOffset.forward * speed,
      up: offset.up + speedOffset.up * speed,
      right: offset.right + speedOffset.right * speed,
    };
  }
}