UNPKG

mapillary-js

Version:

A WebGL interactive street imagery library

337 lines (279 loc) 12.3 kB
import earcut from "earcut"; import polylabel from "polylabel"; import * as martinez from "martinez-polygon-clipping"; import * as THREE from "three"; import { Geometry } from "./Geometry"; import { Transform } from "../../../geo/Transform"; /** * @class VertexGeometry * @abstract * @classdesc Represents a vertex geometry. */ export abstract class VertexGeometry extends Geometry { private _subsampleThreshold: number; /** * Create a vertex geometry. * * @constructor * @ignore */ constructor() { super(); this._subsampleThreshold = 0.005; } /** * Get the 3D coordinates for the vertices of the geometry with possibly * subsampled points along the lines. * * @param {Transform} transform - The transform of the image related to * the geometry. * @returns {Array<Array<number>>} Polygon array of 3D world coordinates * representing the geometry. * @ignore */ public abstract getPoints3d(transform: Transform): number[][]; /** * Get the polygon pole of inaccessibility, the most * distant internal point from the polygon outline. * * @returns {Array<number>} 2D basic coordinates for the pole of inaccessibility. * @ignore */ public abstract getPoleOfInaccessibility2d(): number[]; /** * Get the polygon pole of inaccessibility, the most * distant internal point from the polygon outline. * * @param transform - The transform of the image related to * the geometry. * @returns {Array<number>} 3D world coordinates for the pole of inaccessibility. * @ignore */ public abstract getPoleOfInaccessibility3d(transform: Transform): number[]; /** * Get the coordinates of a vertex from the polygon representation of the geometry. * * @param {number} index - Vertex index. * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex. * @ignore */ public abstract getVertex2d(index: number): number[]; /** * Get a vertex from the polygon representation of the 3D coordinates for the * vertices of the geometry. * * @param {number} index - Vertex index. * @param {Transform} transform - The transform of the image related to the geometry. * @returns {Array<number>} Array representing the 3D world coordinates of the vertex. * @ignore */ public abstract getVertex3d(index: number, transform: Transform): number[]; /** * Get a polygon representation of the 2D basic coordinates for the vertices of the geometry. * * @returns {Array<Array<number>>} Polygon array of 2D basic coordinates representing * the vertices of the geometry. * @ignore */ public abstract getVertices2d(): number[][]; /** * Get a polygon representation of the 3D world coordinates for the vertices of the geometry. * * @param {Transform} transform - The transform of the image related to the geometry. * @returns {Array<Array<number>>} Polygon array of 3D world coordinates representing * the vertices of the geometry. * @ignore */ public abstract getVertices3d(transform: Transform): number[][]; /** * Get a flattend array of the 3D world coordinates for the * triangles filling the geometry. * * @param {Transform} transform - The transform of the image related to the geometry. * @returns {Array<number>} Flattened array of 3D world coordinates of the triangles. * @ignore */ public abstract getTriangles3d(transform: Transform): number[]; /** * Set the value of a vertex in the polygon representation of the geometry. * * @description The polygon is defined to have the first vertex at the * bottom-left corner with the rest of the vertices following in clockwise order. * * @param {number} index - The index of the vertex to be set. * @param {Array<number>} value - The new value of the vertex. * @param {Transform} transform - The transform of the image related to the geometry. * @ignore */ public abstract setVertex2d(index: number, value: number[], transform: Transform): void; /** * Finds the polygon pole of inaccessibility, the most distant internal * point from the polygon outline. * * @param {Array<Array<number>>} points2d - 2d points of outline to triangulate. * @returns {Array<number>} Point of inaccessibility. * @ignore */ protected _getPoleOfInaccessibility2d(points2d: number[][]): number[] { let pole2d: number[] = polylabel([points2d], 3e-2); return pole2d; } protected _project(points2d: number[][], transform: Transform): number[][] { const camera: THREE.Camera = this._createCamera( transform.upVector().toArray(), transform.unprojectSfM([0, 0], 0), transform.unprojectSfM([0, 0], 10)); return this._deunproject( points2d, transform, camera); } protected _subsample(points2d: number[][], threshold: number = this._subsampleThreshold): number[][] { const subsampled: number[][] = []; const length: number = points2d.length; for (let index: number = 0; index < length; index++) { const p1: number[] = points2d[index]; const p2: number[] = points2d[(index + 1) % length]; subsampled.push(p1); const dist: number = Math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2); const subsamples: number = Math.floor(dist / threshold); const coeff: number = 1 / (subsamples + 1); for (let i: number = 1; i <= subsamples; i++) { const alpha: number = i * coeff; const subsample: number[] = [ (1 - alpha) * p1[0] + alpha * p2[0], (1 - alpha) * p1[1] + alpha * p2[1], ]; subsampled.push(subsample); } } return subsampled; } /** * Triangulates a 2d polygon and returns the triangle * representation as a flattened array of 3d points. * * @param {Array<Array<number>>} points2d - 2d points of outline to triangulate. * @param {Array<Array<number>>} points3d - 3d points of outline corresponding to the 2d points. * @param {Array<Array<Array<number>>>} [holes2d] - 2d points of holes to triangulate. * @param {Array<Array<Array<number>>>} [holes3d] - 3d points of holes corresponding to the 2d points. * @returns {Array<number>} Flattened array of 3d points ordered based on the triangles. * @ignore */ protected _triangulate( points2d: number[][], points3d: number[][], holes2d?: number[][][], holes3d?: number[][][]): number[] { let data: number[][][] = [points2d.slice(0, -1)]; for (let hole2d of holes2d != null ? holes2d : []) { data.push(hole2d.slice(0, -1)); } let points: number[][] = points3d.slice(0, -1); for (let hole3d of holes3d != null ? holes3d : []) { points = points.concat(hole3d.slice(0, -1)); } let flattened: { vertices: number[], holes: number[], dimensions: number } = earcut.flatten(data); let indices: number[] = earcut(flattened.vertices, flattened.holes, flattened.dimensions); let triangles: number[] = []; for (let i: number = 0; i < indices.length; ++i) { let point: number[] = points[indices[i]]; triangles.push(point[0]); triangles.push(point[1]); triangles.push(point[2]); } return triangles; } protected _triangulateSpherical( points2d: number[][], holes2d: number[][][], transform: Transform): number[] { const triangles: number[] = []; const epsilon: number = 1e-9; const subareasX: number = 3; const subareasY: number = 3; for (let x: number = 0; x < subareasX; x++) { for (let y: number = 0; y < subareasY; y++) { const epsilonX0: number = x === 0 ? -epsilon : epsilon; const epsilonY0: number = y === 0 ? -epsilon : epsilon; const x0: number = x / subareasX + epsilonX0; const y0: number = y / subareasY + epsilonY0; const x1: number = (x + 1) / subareasX + epsilon; const y1: number = (y + 1) / subareasY + epsilon; const bbox2d: number[][] = [ [x0, y0], [x0, y1], [x1, y1], [x1, y0], [x0, y0], ]; const lookat2d: number[] = [ (2 * x + 1) / (2 * subareasX), (2 * y + 1) / (2 * subareasY), ]; triangles.push(...this._triangulateSubarea(points2d, holes2d, bbox2d, lookat2d, transform)); } } return triangles; } protected _unproject(points2d: number[][], transform: Transform, distance: number = 200): number[][] { return points2d .map( (point: number[]) => { return transform.unprojectBasic(point, distance); }); } private _createCamera(upVector: number[], position: number[], lookAt: number[]): THREE.Camera { const camera: THREE.Camera = new THREE.Camera(); camera.up.copy(new THREE.Vector3().fromArray(upVector)); camera.position.copy(new THREE.Vector3().fromArray(position)); camera.lookAt(new THREE.Vector3().fromArray(lookAt)); camera.updateMatrix(); camera.updateMatrixWorld(true); return camera; } private _deunproject(points2d: number[][], transform: Transform, camera: THREE.Camera): number[][] { return points2d .map( (point2d: number[]): number[] => { const pointWorld: number[] = transform.unprojectBasic(point2d, 10000); const pointCamera: THREE.Vector3 = new THREE.Vector3(pointWorld[0], pointWorld[1], pointWorld[2]) .applyMatrix4(camera.matrixWorldInverse); return [pointCamera.x / pointCamera.z, pointCamera.y / pointCamera.z]; }); } private _triangulateSubarea( points2d: number[][], holes2d: number[][][], bbox2d: number[][], lookat2d: number[], transform: Transform): number[] { const intersections: martinez.MultiPolygon = martinez.intersection([points2d, ...holes2d], [bbox2d]) as martinez.MultiPolygon; if (!intersections) { return []; } const triangles: number[] = []; const threshold: number = this._subsampleThreshold; const camera: THREE.Camera = this._createCamera( transform.upVector().toArray(), transform.unprojectSfM([0, 0], 0), transform.unprojectBasic(lookat2d, 10)); for (const intersection of intersections) { const subsampledPolygon2d: number[][] = this._subsample(intersection[0], threshold); const polygon2d: number[][] = this._deunproject(subsampledPolygon2d, transform, camera); const polygon3d: number[][] = this._unproject(subsampledPolygon2d, transform); const polygonHoles2d: number[][][] = []; const polygonHoles3d: number[][][] = []; for (let i: number = 1; i < intersection.length; i++) { let subsampledHole2d: number[][] = this._subsample(intersection[i], threshold); const hole2d: number[][] = this._deunproject(subsampledHole2d, transform, camera); const hole3d: number[][] = this._unproject(subsampledHole2d, transform); polygonHoles2d.push(hole2d); polygonHoles3d.push(hole3d); } triangles.push(...this._triangulate(polygon2d, polygon3d, polygonHoles2d, polygonHoles3d)); } return triangles; } }