@ue-too/board / index / createZoomPanPath
Function: createZoomPanPath()
createZoomPanPath(
from,to,viewPortWidth,options):ZoomPanPath
Defined in: packages/board/src/camera/utils/zoom-pan-path.ts:151
Creates a smooth zoom-and-pan camera path between two views.
Parameters
from
Start view
to
Target view
viewPortWidth
number
Width of the viewport in CSS pixels (camera.viewPortWidth). The visible world width is viewPortWidth / zoomLevel, which sets how far the path zooms out.
options
ZoomPanPathOptions = {}
Returns
The path; sample it with ZoomPanPath.at
Throws
RangeError if viewPortWidth, either zoom level or rho is not a positive finite number, or either position has a coordinate that is not finite
Remarks
Moving the camera and changing its zoom at the same rate looks wrong: the zoom outruns the pan and the target drifts off screen before snapping back. This follows Van Wijk & Nuij, "Smooth and efficient zooming and panning" (2003): on a long trip the camera zooms out, glides across, and zooms back in, so the viewer keeps their bearings. The path is the shortest one in the paper's metric and moves at a constant perceived speed. Rotation is not part of the path.
The function only computes views; drive it with your own frame loop and apply each view to the camera. With CameraMuxWithAnimationAndLock (the Board default), start the flight with initatePanTransition() and initateZoomTransition(), then on each frame report the view through notifyZoomInputAnimation() and notifyPanToAnimationInput() and apply it with the camera rig. When the user pans or zooms, the mux's pan or zoom state machine leaves TRANSITION: stop the flight then, so it doesn't fight them. If the camera is locked on an object (LOCKED_ON_OBJECT), send mux.panStateMachine.happens('unlock') first; initatePanTransition() does nothing in that state. The camera's zoom and position limits still apply: a path that zooms out past the minimum zoom is clamped there.
Example
Fly the camera to a point, handing it back if the user takes over
const board = new Board(canvas);
const mux = board.cameraMux as CameraMuxWithAnimationAndLock;
const rig = board.getCameraRig();
const camera = board.camera;
const path = createZoomPanPath(
{ position: camera.position, zoomLevel: camera.zoomLevel },
{ position: { x: 5000, y: 0 }, zoomLevel: 2 },
camera.viewPortWidth
);
mux.initatePanTransition();
mux.initateZoomTransition();
const duration = 1000;
const startTime = performance.now();
function frame(now: number) {
// The user panned or zoomed: let them have the camera
if (
mux.panStateMachine.currentState !== 'TRANSITION' ||
mux.zoomStateMachine.currentState !== 'TRANSITION'
) {
return;
}
const t = Math.min(1, (now - startTime) / duration);
const view = path.at((1 - Math.cos(Math.PI * t)) / 2);
mux.notifyZoomInputAnimation(view.zoomLevel);
rig.zoomTo(view.zoomLevel);
if (mux.notifyPanToAnimationInput(view.position).allowPassThrough) {
rig.panToWorld(view.position);
}
if (t < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);See the camera-animation example in the ue-too repository for the same flight driven by @ue-too/animate.