OrbitFollow

Lets the user orbit and zoom around a target.

Puts the camera on a sphere around the target, or on a surface through three rings. Three InputAxis pick the spot: horizontal goes around the target and vertical up and down, both in degrees, and radial zooms. They can be driven by the mouse, touch, the wheel and pinches through an input controller, or from code. Pair it with an Aim that looks at the same target, like HardLookAt, for an orbit or third person camera.

The camera sits radius × exp(radial) away from the target. radial is the natural log of the zoom: 0 is radius, ln 2 twice as far, ln 0.5 half as far. That way each step of the wheel zooms by the same ratio near and far, and a pinch zooms exactly as much as the fingers spread.

To start from a specific spot, pass a position to the camera's initialState. horizontal and vertical are set from its direction around the target, radial keeps its value. Later on, setFromPosition(position) and setFromRotation(rotation) on the body ease the camera there with the axes' damping. On OrbitFollowBodyThree they also take a Vector3 and a Quaternion.

Settings

SettingDescriptionDefault
targetWhat to orbit. See Targets.
orbitStyle'sphere', or 'threeRing' for a surface through three rings.'sphere'
radiusDistance from the target while radial is 0, for 'sphere'. Changes ease in with the radial axis' damping.10
orbitsThe top, center and bottom ring for 'threeRing', each a radius and a height above the target.see Three rings
splineCurvatureHow strongly the 'threeRing' surface curves between the rings, from 0 to 1.0
targetOffsetCenter of the orbit relative to the target, turned by bindingMode.[0, 0, 0]
bindingModeHow the orbit turns with the target. See Follow.'worldSpace'
dampingSeconds to catch up with the target. See Damping.0
rotationDampingSeconds to turn with the target, for every bindingMode but worldSpace.0
maxSpeedMaximum catch-up speed, in units per second.Infinity
recenteringTargetWhat horizontal's center is measured from when it recenters: 'trackingTarget' from behind the target, 'axisCenter' from the bindingMode frame.'trackingTarget'
horizontal, vertical, radialSettings for each axis, like range, damping or recentering. Later on, change them on body.horizontal and the others.see Axes
autoNormalizeReact only. Keep horizontal inside its range after each drag. autoNormalize on the axis elsewhere.false
debugReact only. Draws the orbit in the scene. See Debugging.false

Axes

AxisRangeCenterWraps
horizontal[-180, 180]0, the camera on the target's +Z sideyes
vertical[-90, 90]0, level with the targetno
radial[ln 0.5, ln 2]0no

Every axis starts at its center. Dragging right with a positive gain turns the view right, the same way as PanTilt.

Axes with the same recentering.time recenter together: input on one of them restarts the wait on the others. With recenteringTarget: 'trackingTarget', the default, horizontal recenters to its center measured from behind the target's forward, -Z, whatever the bindingMode: 0 is right behind it, 180 in front of it.

Three rings

With orbitStyle: 'threeRing', vertical runs from the bottom ring at the start of its range, through center, to the top ring at its end, and radial scales the whole surface. The rings let the camera come closer when it looks from above or below.

By default all three rings have a radius of 10, at heights -10, 0 and 10: a plain cylinder whose center ring is where the sphere starts.

Damping

damping makes the camera lag behind a moving target. Going around the orbit is never delayed by it, the camera turns around the target right away. To smooth the input itself, set damping on the axes.

An Aim with damping, like RotationComposer, also follows the turns of the orbit without its damping, so the target stays in place on screen while the user drags.

Usage

import { HardLookAtAim, OrbitFollowBody, targetPose } from '@pmndrs/klipp';
import { InputControllerDom } from '@pmndrs/klipp/dom';

const player = targetPose.create(); // keep player.position up to date

const body = new OrbitFollowBody(player, { radius: 8, damping: 0.3, radial: { damping: 0.15 } });
camera.body = body;
camera.aim = new HardLookAtAim(player);

const orbit = { axes: { x: body.horizontal, y: body.vertical }, gain: 0.3 };
const controller = new InputControllerDom({
  mouseButtons: { left: orbit },
  touches: { one: orbit },
  wheel: { axis: body.radial, gain: 0.001, invert: true },
  pinch: { axis: body.radial, invert: true },
});
klipp.whileOnScreen(camera, () => {
  controller.connect(canvas);
  return () => controller.disconnect();
});
klipp.registerUpdate(() => controller.update());

Scrolling up and spreading a pinch are positive, while zooming in makes radial smaller, hence invert.

Recentering

const recentering = { enabled: true, wait: 1, time: 0.5 };
body.horizontal.recentering = recentering;
body.vertical.recentering = recentering;