/** * Camera controller. * * Cinematic semantics on purpose: `flyTo` follows the Van Wijk and Nuij (2003) * smooth-and-efficient zoom-and-pan path, which arcs out to a wider zoom during * long moves. Linear interpolation of centre and scale feels wrong because * perceived motion is logarithmic in scale, and that difference is what * separates a camera that reads as cinematic from one that reads as a slideshow. * * Every move is **interruptible and retargeting**: a new move starts from the * current interpolated state rather than queueing or snapping, because in a * scroll-driven story the reader can outrun the animation at any moment. * * On an azimuthal projection a move to a `center` is a **rotation**, not a pan. * The camera is a screen-space transform and cannot reach the far side of a * globe by any amount of translating, so where `viewport.supportsRecentre()` * says so, the same `flyTo({ center })` turns the sphere instead. See * `_resolveMove`. * * @module geo/Camera */ import type { EasingFn } from '../utils/easing'; import type { Viewport } from './Viewport'; import type { CameraState, LonLat, Padding, ScreenPoint, WorldBounds } from '../types'; /** * Everything a camera move can target. Mutually exclusive in practice: `bounds` * wins, then `center`, then a bare `zoom`, then raw `k`/`x`/`y`. */ export interface CameraTarget { /** Geographic centre. */ center?: LonLat; zoom?: number; /** World-space box to frame. */ bounds?: WorldBounds; padding?: Padding; maxZoom?: number; /** Raw camera scale. Advanced; prefer `zoom`. */ k?: number; /** Raw camera translate. Advanced. */ x?: number; y?: number; } export interface TransitionOptions { duration?: number; ease?: string | EasingFn; } export interface CameraOptions { minZoom?: number; maxZoom?: number; /** Divides the Van Wijk natural duration. Higher is faster. */ speed?: number; /** Van Wijk rho. Higher arcs out further; 0 disables the arc. */ curve?: number; } /** An interpolator carrying its own natural duration, in ms. */ export type ZoomInterpolator = ((t: number) => [number, number, number]) & { duration: number; }; /** * The unhurried duration for turning from one place to another, before `speed` * and the floor are applied. Exported because it is the one number in a globe * move a caller might reasonably want to reason about, and because a duration * curve is far easier to pin in a test than a wall clock. */ export declare function rotationDuration(from: LonLat, to: LonLat): number; /** * Van Wijk and Nuij (2003) zoom-and-pan interpolator. * * Operates on `[cx, cy, w]` triples where `cx,cy` is the world-space point at * the viewport centre and `w` is the world-space width visible in the viewport. * Returns an interpolator carrying its own natural `duration` in ms, which is * how a long move automatically takes longer than a short one. * * @param rho Curvature. Higher arcs out further; 0 disables the arc. */ export declare function interpolateZoom(p0: [number, number, number], p1: [number, number, number], rho?: number): ZoomInterpolator; export declare class Camera { readonly viewport: Viewport; readonly onChange: () => void; readonly onRotate: () => void; options: Required; private _raf; private _resolve; constructor({ viewport, onChange, onRotate, options, }: { viewport: Viewport; /** Called on every frame, after the camera has been mutated. */ onChange: () => void; /** * Called after the sphere has been turned, which unlike a camera change * invalidates every projected coordinate. Optional: a camera with no host * to redraw still computes the right rotation, it just draws nothing. */ onRotate?: () => void; options?: CameraOptions; }); get state(): CameraState; get animating(): boolean; /** * Stop any in-flight move, leaving the camera wherever it is. Called by every * new move so transitions retarget instead of fighting. * */ stop(): void; /** * Apply a camera state immediately, clamped to the zoom range. * */ set(next: Partial): void; /** * Turn the sphere and tell the host to reproject. * * Separate from `set` because the two are different kinds of change: a camera * write is a transform on already-projected geometry, while this invalidates * the geometry itself. */ private _rotate; /** * Jump with no animation. * */ jumpTo(target: CameraTarget): void; /** * Animate with a fixed duration and easing. Use for short, mechanical moves * (a legend filter re-fit, a drilldown) where an arc would be theatrical. * * Fixed is the contract, rotation included: unlike `flyTo` this does not * stretch its duration to suit a half-turn, because a caller who asked for * 400 ms asked for 400 ms. */ easeTo(target: CameraTarget & TransitionOptions): Promise; /** * Animate along a Van Wijk zoom-and-pan path. Use for narrative moves. * * Duration is derived from the path length unless overridden, so crossing a * continent takes longer than nudging to a neighbouring county without the * author having to hand-tune anything. On a globe the same is true of the * turn: the angular distance between the place facing the viewer now and the * one that will be sets the pace, and whichever of the two motions needs * longer decides, so the zoom and the rotation land together. * */ flyTo(target: CameraTarget & TransitionOptions & { speed?: number; curve?: number; }): Promise; /** * Frame a world-space bounding box (padding-aware). * */ fitBounds(bounds: WorldBounds, options?: { padding?: Padding; maxZoom?: number; duration?: number; transition?: 'fly' | 'ease' | 'jump'; }): Promise; /** * Zoom by a factor about a fixed screen point, so the geography under the * cursor stays under the cursor. This is the single detail that makes wheel * zoom feel correct. * */ zoomAbout(factor: number, screenPoint: ScreenPoint): void; panBy(dx: number, dy: number): void; /** * Resolve a target into where the sphere ends up and where the camera ends up. * * The whole of the pan-or-rotate decision lives here, and it is one question: * does re-centring this projection mean turning the sphere? On anything laid * out flat the answer is no, `rotation` comes back null, and every number * below is what it has always been. On an azimuthal one a `center` becomes a * rotation, and the camera state is then resolved **against the sphere in its * destination orientation** rather than its current one. That second part is * what stops the move counting the same distance twice: measured against the * old sphere, the target still reads as being off on the far side, and the * camera would dutifully pan the screen there on top of a rotation that had * already brought it home. */ private _resolveMove; /** The destination rotation, or null when this move does not turn the sphere. */ private _rotationFor; /** * A stepper that slerps from the current rotation to `to`, or null if there is * nothing to turn. * * The starting orientation is captured here, at call time, which is what makes * a rotation interruptible on the same terms as everything else: a second * `flyTo` arriving mid-turn reads the half-turned sphere as its origin and * takes the short way from *there*, rather than snapping back or queueing. */ private _turn; /** * Resolve the many accepted target shapes into a concrete camera state. * */ private _resolveTarget; private _run; } //# sourceMappingURL=Camera.d.ts.map