mapillary-js
Version:
A WebGL interactive street imagery library
575 lines (486 loc) • 20.6 kB
text/typescript
import * as THREE from "three";
import { Transform } from "./Transform";
/**
* @class ViewportCoords
*
* @classdesc Provides methods for calculating 2D coordinate conversions
* as well as 3D projection and unprojection.
*
* Basic coordinates are 2D coordinates on the [0, 1] interval and
* have the origin point, (0, 0), at the top left corner and the
* maximum value, (1, 1), at the bottom right corner of the original
* image.
*
* Viewport coordinates are 2D coordinates on the [-1, 1] interval and
* have the origin point in the center. The bottom left corner point is
* (-1, -1) and the top right corner point is (1, 1).
*
* Canvas coordiantes are 2D pixel coordinates on the [0, canvasWidth] and
* [0, canvasHeight] intervals. The origin point (0, 0) is in the top left
* corner and the maximum value is (canvasWidth, canvasHeight) is in the
* bottom right corner.
*
* 3D coordinates are in the topocentric world reference frame.
*/
export class ViewportCoords {
private _unprojectDepth: number = 200;
/**
* Convert basic coordinates to canvas coordinates.
*
* @description Transform origin and camera position needs to be the
* equal for reliable return value.
*
* @param {number} basicX - Basic X coordinate.
* @param {number} basicY - Basic Y coordinate.
* @param {HTMLElement} container - The viewer container.
* @param {Transform} transform - Transform of the image to unproject from.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D canvas coordinates.
*/
public basicToCanvas(
basicX: number,
basicY: number,
container: { offsetHeight: number, offsetWidth: number },
transform: Transform,
camera: THREE.Camera):
number[] {
const point3d: number[] = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
const canvas: number[] = this.projectToCanvas(point3d, container, camera);
return canvas;
}
/**
* Convert basic coordinates to canvas coordinates safely. If 3D point is
* behind camera null will be returned.
*
* @description Transform origin and camera position needs to be the
* equal for reliable return value.
*
* @param {number} basicX - Basic X coordinate.
* @param {number} basicY - Basic Y coordinate.
* @param {HTMLElement} container - The viewer container.
* @param {Transform} transform - Transform of the image to unproject from.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D canvas coordinates if the basic point represents a 3D point
* in front of the camera, otherwise null.
*/
public basicToCanvasSafe(
basicX: number,
basicY: number,
container: { offsetHeight: number, offsetWidth: number },
transform: Transform,
camera: THREE.Camera):
number[] {
const viewport: number[] = this.basicToViewportSafe(basicX, basicY, transform, camera);
if (viewport === null) {
return null;
}
const canvas: number[] = this.viewportToCanvas(viewport[0], viewport[1], container);
return canvas;
}
/**
* Convert basic coordinates to viewport coordinates.
*
* @description Transform origin and camera position needs to be the
* equal for reliable return value.
*
* @param {number} basicX - Basic X coordinate.
* @param {number} basicY - Basic Y coordinate.
* @param {Transform} transform - Transform of the image to unproject from.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D viewport coordinates.
*/
public basicToViewport(
basicX: number,
basicY: number,
transform: Transform,
camera: THREE.Camera):
number[] {
const point3d: number[] = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
const viewport: number[] = this.projectToViewport(point3d, camera);
return viewport;
}
/**
* Convert basic coordinates to viewport coordinates safely. If 3D point is
* behind camera null will be returned.
*
* @description Transform origin and camera position needs to be the
* equal for reliable return value.
*
* @param {number} basicX - Basic X coordinate.
* @param {number} basicY - Basic Y coordinate.
* @param {Transform} transform - Transform of the image to unproject from.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D viewport coordinates.
*/
public basicToViewportSafe(
basicX: number,
basicY: number,
transform: Transform,
camera: THREE.Camera):
number[] {
const point3d: number[] = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
const pointCamera: number[] = this.worldToCamera(point3d, camera);
if (pointCamera[2] > 0) {
return null;
}
const viewport: number[] = this.projectToViewport(point3d, camera);
return viewport;
}
/**
* Convert camera 3D coordinates to viewport coordinates.
*
* @param {number} pointCamera - 3D point in camera coordinate system.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D viewport coordinates.
*/
public cameraToViewport(
pointCamera: number[],
camera: THREE.Camera):
number[] {
const viewport: THREE.Vector3 =
new THREE.Vector3().fromArray(pointCamera)
.applyMatrix4(camera.projectionMatrix);
return [viewport.x, viewport.y];
}
/**
* Get canvas pixel position from event.
*
* @param {Event} event - Event containing clientX and clientY properties.
* @param {HTMLElement} element - HTML element.
* @returns {Array<number>} 2D canvas coordinates.
*/
public canvasPosition(event: { clientX: number, clientY: number }, element: HTMLElement): number[] {
const clientRect: ClientRect = element.getBoundingClientRect();
const canvasX: number = event.clientX - clientRect.left - element.clientLeft;
const canvasY: number = event.clientY - clientRect.top - element.clientTop;
return [canvasX, canvasY];
}
/**
* Convert canvas coordinates to basic coordinates.
*
* @description Transform origin and camera position needs to be the
* equal for reliable return value.
*
* @param {number} canvasX - Canvas X coordinate.
* @param {number} canvasY - Canvas Y coordinate.
* @param {HTMLElement} container - The viewer container.
* @param {Transform} transform - Transform of the image to unproject from.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D basic coordinates.
*/
public canvasToBasic(
canvasX: number,
canvasY: number,
container: { offsetHeight: number, offsetWidth: number },
transform: Transform,
camera: THREE.Camera):
number[] {
const point3d: number[] =
this.unprojectFromCanvas(canvasX, canvasY, container, camera)
.toArray();
const basic: number[] = transform.projectBasic(point3d);
return basic;
}
/**
* Convert canvas coordinates to viewport coordinates.
*
* @param {number} canvasX - Canvas X coordinate.
* @param {number} canvasY - Canvas Y coordinate.
* @param {HTMLElement} container - The viewer container.
* @returns {Array<number>} 2D viewport coordinates.
*/
public canvasToViewport(
canvasX: number,
canvasY: number,
container: { offsetHeight: number, offsetWidth: number }):
number[] {
const [canvasWidth, canvasHeight]: number[] = this.containerToCanvas(container);
const viewportX: number = 2 * canvasX / canvasWidth - 1;
const viewportY: number = 1 - 2 * canvasY / canvasHeight;
return [viewportX, viewportY];
}
/**
* Determines the width and height of the container in canvas coordinates.
*
* @param {HTMLElement} container - The viewer container.
* @returns {Array<number>} 2D canvas coordinates.
*/
public containerToCanvas(container: { offsetHeight: number, offsetWidth: number }): number[] {
return [container.offsetWidth, container.offsetHeight];
}
/**
* Determine basic distances from image to canvas corners.
*
* @description Transform origin and camera position needs to be the
* equal for reliable return value.
*
* Determines the smallest basic distance for every side of the canvas.
*
* @param {Transform} transform - Transform of the image to unproject from.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} Array of basic distances as [top, right, bottom, left].
*/
public getBasicDistances(
transform: Transform,
camera: THREE.Camera):
number[] {
const topLeftBasic: number[] = this.viewportToBasic(-1, 1, transform, camera);
const topRightBasic: number[] = this.viewportToBasic(1, 1, transform, camera);
const bottomRightBasic: number[] = this.viewportToBasic(1, -1, transform, camera);
const bottomLeftBasic: number[] = this.viewportToBasic(-1, -1, transform, camera);
let topBasicDistance: number = 0;
let rightBasicDistance: number = 0;
let bottomBasicDistance: number = 0;
let leftBasicDistance: number = 0;
if (topLeftBasic[1] < 0 && topRightBasic[1] < 0) {
topBasicDistance = topLeftBasic[1] > topRightBasic[1] ?
-topLeftBasic[1] :
-topRightBasic[1];
}
if (topRightBasic[0] > 1 && bottomRightBasic[0] > 1) {
rightBasicDistance = topRightBasic[0] < bottomRightBasic[0] ?
topRightBasic[0] - 1 :
bottomRightBasic[0] - 1;
}
if (bottomRightBasic[1] > 1 && bottomLeftBasic[1] > 1) {
bottomBasicDistance = bottomRightBasic[1] < bottomLeftBasic[1] ?
bottomRightBasic[1] - 1 :
bottomLeftBasic[1] - 1;
}
if (bottomLeftBasic[0] < 0 && topLeftBasic[0] < 0) {
leftBasicDistance = bottomLeftBasic[0] > topLeftBasic[0] ?
-bottomLeftBasic[0] :
-topLeftBasic[0];
}
return [topBasicDistance, rightBasicDistance, bottomBasicDistance, leftBasicDistance];
}
/**
* Determine pixel distances from image to canvas corners.
*
* @description Transform origin and camera position needs to be the
* equal for reliable return value.
*
* Determines the smallest pixel distance for every side of the canvas.
*
* @param {HTMLElement} container - The viewer container.
* @param {Transform} transform - Transform of the image to unproject from.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} Array of pixel distances as [top, right, bottom, left].
*/
public getPixelDistances(
container: { offsetHeight: number, offsetWidth: number },
transform: Transform,
camera: THREE.Camera):
number[] {
const topLeftBasic: number[] = this.viewportToBasic(-1, 1, transform, camera);
const topRightBasic: number[] = this.viewportToBasic(1, 1, transform, camera);
const bottomRightBasic: number[] = this.viewportToBasic(1, -1, transform, camera);
const bottomLeftBasic: number[] = this.viewportToBasic(-1, -1, transform, camera);
let topPixelDistance: number = 0;
let rightPixelDistance: number = 0;
let bottomPixelDistance: number = 0;
let leftPixelDistance: number = 0;
const [canvasWidth, canvasHeight]: number[] = this.containerToCanvas(container);
if (topLeftBasic[1] < 0 && topRightBasic[1] < 0) {
const basicX: number = topLeftBasic[1] > topRightBasic[1] ?
topLeftBasic[0] :
topRightBasic[0];
const canvas: number[] = this.basicToCanvas(basicX, 0, container, transform, camera);
topPixelDistance = canvas[1] > 0 ? canvas[1] : 0;
}
if (topRightBasic[0] > 1 && bottomRightBasic[0] > 1) {
const basicY: number = topRightBasic[0] < bottomRightBasic[0] ?
topRightBasic[1] :
bottomRightBasic[1];
const canvas: number[] = this.basicToCanvas(1, basicY, container, transform, camera);
rightPixelDistance = canvas[0] < canvasWidth ? canvasWidth - canvas[0] : 0;
}
if (bottomRightBasic[1] > 1 && bottomLeftBasic[1] > 1) {
const basicX: number = bottomRightBasic[1] < bottomLeftBasic[1] ?
bottomRightBasic[0] :
bottomLeftBasic[0];
const canvas: number[] = this.basicToCanvas(basicX, 1, container, transform, camera);
bottomPixelDistance = canvas[1] < canvasHeight ? canvasHeight - canvas[1] : 0;
}
if (bottomLeftBasic[0] < 0 && topLeftBasic[0] < 0) {
const basicY: number = bottomLeftBasic[0] > topLeftBasic[0] ?
bottomLeftBasic[1] :
topLeftBasic[1];
const canvas: number[] = this.basicToCanvas(0, basicY, container, transform, camera);
leftPixelDistance = canvas[0] > 0 ? canvas[0] : 0;
}
return [topPixelDistance, rightPixelDistance, bottomPixelDistance, leftPixelDistance];
}
/**
* Determine if an event occured inside an element.
*
* @param {Event} event - Event containing clientX and clientY properties.
* @param {HTMLElement} element - HTML element.
* @returns {boolean} Value indicating if the event occured inside the element or not.
*/
public insideElement(event: { clientX: number, clientY: number }, element: HTMLElement): boolean {
const clientRect: ClientRect = element.getBoundingClientRect();
const minX: number = clientRect.left + element.clientLeft;
const maxX: number = minX + element.clientWidth;
const minY: number = clientRect.top + element.clientTop;
const maxY: number = minY + element.clientHeight;
return event.clientX > minX &&
event.clientX < maxX &&
event.clientY > minY &&
event.clientY < maxY;
}
/**
* Project 3D world coordinates to canvas coordinates.
*
* @param {Array<number>} point3D - 3D world coordinates.
* @param {HTMLElement} container - The viewer container.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D canvas coordinates.
*/
public projectToCanvas(
point3d: number[],
container: { offsetHeight: number, offsetWidth: number },
camera: THREE.Camera):
number[] {
const viewport: number[] = this.projectToViewport(point3d, camera);
const canvas: number[] =
this.viewportToCanvas(viewport[0], viewport[1], container);
return canvas;
}
/**
* Project 3D world coordinates to canvas coordinates safely. If 3D
* point is behind camera null will be returned.
*
* @param {Array<number>} point3D - 3D world coordinates.
* @param {HTMLElement} container - The viewer container.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D canvas coordinates.
*/
public projectToCanvasSafe(
point3d: number[],
container: { offsetHeight: number, offsetWidth: number },
camera: THREE.Camera):
number[] {
const pointCamera: number[] = this.worldToCamera(point3d, camera);
if (pointCamera[2] > 0) {
return null;
}
const viewport: number[] = this.projectToViewport(point3d, camera);
const canvas: number[] =
this.viewportToCanvas(viewport[0], viewport[1], container);
return canvas;
}
/**
* Project 3D world coordinates to viewport coordinates.
*
* @param {Array<number>} point3D - 3D world coordinates.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D viewport coordinates.
*/
public projectToViewport(
point3d: number[],
camera: THREE.Camera):
number[] {
const viewport: THREE.Vector3 =
new THREE.Vector3(point3d[0], point3d[1], point3d[2])
.project(camera);
return [viewport.x, viewport.y];
}
/**
* Uproject canvas coordinates to 3D world coordinates.
*
* @param {number} canvasX - Canvas X coordinate.
* @param {number} canvasY - Canvas Y coordinate.
* @param {HTMLElement} container - The viewer container.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 3D world coordinates.
*/
public unprojectFromCanvas(
canvasX: number,
canvasY: number,
container: { offsetHeight: number, offsetWidth: number },
camera: THREE.Camera):
THREE.Vector3 {
const viewport: number[] =
this.canvasToViewport(canvasX, canvasY, container);
const point3d: THREE.Vector3 =
this.unprojectFromViewport(viewport[0], viewport[1], camera);
return point3d;
}
/**
* Unproject viewport coordinates to 3D world coordinates.
*
* @param {number} viewportX - Viewport X coordinate.
* @param {number} viewportY - Viewport Y coordinate.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 3D world coordinates.
*/
public unprojectFromViewport(
viewportX: number,
viewportY: number,
camera: THREE.Camera):
THREE.Vector3 {
const point3d: THREE.Vector3 =
new THREE.Vector3(viewportX, viewportY, 1)
.unproject(camera);
return point3d;
}
/**
* Convert viewport coordinates to basic coordinates.
*
* @description Transform origin and camera position needs to be the
* equal for reliable return value.
*
* @param {number} viewportX - Viewport X coordinate.
* @param {number} viewportY - Viewport Y coordinate.
* @param {Transform} transform - Transform of the image to unproject from.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 2D basic coordinates.
*/
public viewportToBasic(
viewportX: number,
viewportY: number,
transform: Transform,
camera: THREE.Camera):
number[] {
const point3d: number[] =
new THREE.Vector3(viewportX, viewportY, 1)
.unproject(camera)
.toArray();
const basic: number[] = transform.projectBasic(point3d);
return basic;
}
/**
* Convert viewport coordinates to canvas coordinates.
*
* @param {number} viewportX - Viewport X coordinate.
* @param {number} viewportY - Viewport Y coordinate.
* @param {HTMLElement} container - The viewer container.
* @returns {Array<number>} 2D canvas coordinates.
*/
public viewportToCanvas(
viewportX: number,
viewportY: number,
container: { offsetHeight: number, offsetWidth: number }):
number[] {
const [canvasWidth, canvasHeight]: number[] = this.containerToCanvas(container);
const canvasX: number = canvasWidth * (viewportX + 1) / 2;
const canvasY: number = -canvasHeight * (viewportY - 1) / 2;
return [canvasX, canvasY];
}
/**
* Convert 3D world coordinates to 3D camera coordinates.
*
* @param {number} point3D - 3D point in world coordinate system.
* @param {THREE.Camera} camera - Camera used in rendering.
* @returns {Array<number>} 3D camera coordinates.
*/
public worldToCamera(
point3d: number[],
camera: THREE.Camera): number[] {
const pointCamera: THREE.Vector3 =
new THREE.Vector3(point3d[0], point3d[1], point3d[2])
.applyMatrix4(camera.matrixWorldInverse);
return pointCamera.toArray();
}
}