@deck.gl/core
Version:
deck.gl core library
151 lines (129 loc) • 4.95 kB
text/typescript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import Viewport from '../viewports/viewport';
import {Matrix4} from '@math.gl/core';
import {pixelsToWorld, fovyToAltitude} from '@math.gl/web-mercator';
const DEGREES_TO_RADIANS = Math.PI / 180;
function getViewMatrix({
height,
focalDistance,
orbitAxis,
rotationX,
rotationOrbit,
zoom
}: {
height: number;
focalDistance: number;
orbitAxis: 'Y' | 'Z';
rotationX: number;
rotationOrbit: number;
zoom: number;
}): Matrix4 {
// We position the camera so that one common space unit (world space unit scaled by zoom)
// at the target maps to one screen pixel.
// This is a similar technique to that used in web mercator projection
// By doing so we are able to convert between common space and screen space sizes efficiently
// in the vertex shader.
const up = orbitAxis === 'Z' ? [0, 0, 1] : [0, 1, 0];
const eye = orbitAxis === 'Z' ? [0, -focalDistance, 0] : [0, 0, focalDistance];
const viewMatrix = new Matrix4().lookAt({eye, up});
viewMatrix.rotateX(rotationX * DEGREES_TO_RADIANS);
if (orbitAxis === 'Z') {
viewMatrix.rotateZ(rotationOrbit * DEGREES_TO_RADIANS);
} else {
viewMatrix.rotateY(rotationOrbit * DEGREES_TO_RADIANS);
}
// When height increases, we need to increase the distance from the camera to the target to
// keep the 1:1 mapping. However, this also changes the projected depth of each position by
// moving them further away between the near/far plane.
// Without modifying the default near/far planes, we instead scale down the common space to
// remove the distortion to the depth field.
const projectionScale = Math.pow(2, zoom) / height;
viewMatrix.scale(projectionScale);
return viewMatrix;
}
export type OrbitViewportOptions = {
/** Name of the viewport */
id?: string;
/** Left offset from the canvas edge, in pixels */
x?: number;
/** Top offset from the canvas edge, in pixels */
y?: number;
/** Viewport width in pixels */
width?: number;
/** Viewport height in pixels */
height?: number;
/** Axis with 360 degrees rotating freedom, either `'Y'` or `'Z'`, default to `'Z'`. */
orbitAxis?: 'Y' | 'Z';
/** The world position at the center of the viewport. Default `[0, 0, 0]`. */
target?: [number, number, number];
/** The zoom level of the viewport. `zoom: 0` maps one unit distance to one pixel on screen, and increasing `zoom` by `1` scales the same object to twice as large. Default `0`. */
zoom?: number;
/** Rotating angle around orbit axis. Default `0`. */
rotationOrbit?: number;
/** Rotating angle around orbit axis. Default `0`. */
rotationX?: number;
/** Custom projection matrix */
projectionMatrix?: number[];
/** Field of view covered by camera, in the perspective case. In degrees. Default `50`. */
fovy?: number;
/** Distance of near clipping plane. Default `0.1`. */
near?: number;
/** Distance of far clipping plane. Default `1000`. */
far?: number;
/** Whether to create an orthographic or perspective projection matrix. Default is `false` (perspective projection). */
orthographic?: boolean;
};
export default class OrbitViewport extends Viewport {
projectedCenter: number[];
constructor(props: OrbitViewportOptions) {
const {
height,
projectionMatrix,
fovy = 50, // For setting camera position
orbitAxis = 'Z', // Orbit axis with 360 degrees rotating freedom, can only be 'Y' or 'Z'
target = [0, 0, 0], // Which point is camera looking at, default origin
rotationX = 0, // Rotating angle around X axis
rotationOrbit = 0, // Rotating angle around orbit axis
zoom = 0
} = props;
const focalDistance = projectionMatrix ? projectionMatrix[5] / 2 : fovyToAltitude(fovy);
super({
...props,
// in case viewState contains longitude/latitude values,
// make sure that the base Viewport class does not treat this as a geospatial viewport
longitude: undefined,
viewMatrix: getViewMatrix({
height: height || 1,
focalDistance,
orbitAxis,
rotationX,
rotationOrbit,
zoom
}),
fovy,
focalDistance,
position: target,
zoom
});
this.projectedCenter = this.project(this.center);
}
unproject(xyz: number[], {topLeft = true}: {topLeft?: boolean} = {}): [number, number, number] {
const [x, y, z = this.projectedCenter[2]] = xyz;
const y2 = topLeft ? y : this.height - y;
const [X, Y, Z] = pixelsToWorld([x, y2, z], this.pixelUnprojectionMatrix);
return [X, Y, Z];
}
panByPosition(coords: number[], pixel: number[]): OrbitViewportOptions {
const p0 = this.project(coords);
const nextCenter = [
this.width / 2 + p0[0] - pixel[0],
this.height / 2 + p0[1] - pixel[1],
this.projectedCenter[2]
];
return {
target: this.unproject(nextCenter)
};
}
}