@deck.gl/layers
Version:
deck.gl core layers
188 lines (187 loc) • 8.2 kB
JavaScript
// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Handles tesselation of polygons with holes
// - 2D surfaces
// - 2D outlines
// - 3D surfaces (top and sides only)
// - 3D wireframes (not yet)
import * as Polygon from './polygon';
import { Tesselator } from '@deck.gl/core';
import { cutPolygonByGrid, cutPolygonByMercatorBounds } from '@math.gl/polygon';
// This class is set up to allow querying one attribute at a time
// the way the AttributeManager expects it
export default class PolygonTesselator extends Tesselator {
constructor(opts) {
const { fp64, IndexType = Uint32Array } = opts;
super({
...opts,
attributes: {
positions: { size: 3, type: fp64 ? Float64Array : Float32Array },
vertexValid: { type: Uint8ClampedArray, size: 1 },
indices: { type: IndexType, size: 1 }
}
});
}
/** Get attribute by name */
get(attributeName) {
const { attributes } = this;
if (attributeName === 'indices') {
return attributes.indices && attributes.indices.subarray(0, this.vertexCount);
}
return attributes[attributeName];
}
/** Override base Tesselator method */
updateGeometry(opts) {
super.updateGeometry(opts);
const externalIndices = this.buffers.indices;
if (externalIndices) {
// @ts-ignore (2339) value is not defined on TypedArray (fall through)
this.vertexCount = (externalIndices.value || externalIndices).length;
}
else if (this.data && !this.getGeometry) {
throw new Error('missing indices buffer');
}
}
/** Implement base Tesselator interface */
normalizeGeometry(polygon) {
if (this.normalize) {
const normalizedPolygon = Polygon.normalize(polygon, this.positionSize);
if (this.opts.resolution) {
return cutPolygonByGrid(Polygon.getPositions(normalizedPolygon), Polygon.getHoleIndices(normalizedPolygon), {
size: this.positionSize,
gridResolution: this.opts.resolution,
edgeTypes: true
});
}
if (this.opts.wrapLongitude) {
return cutPolygonByMercatorBounds(Polygon.getPositions(normalizedPolygon), Polygon.getHoleIndices(normalizedPolygon), {
size: this.positionSize,
maxLatitude: 86,
edgeTypes: true
});
}
return normalizedPolygon;
}
// normalize is explicitly set to false, assume that user passed in already normalized polygons
return polygon;
}
/** Implement base Tesselator interface */
getGeometrySize(polygon) {
if (isCut(polygon)) {
let size = 0;
for (const subPolygon of polygon) {
size += this.getGeometrySize(subPolygon);
}
return size;
}
return Polygon.getPositions(polygon).length / this.positionSize;
}
/** Override base Tesselator method */
getGeometryFromBuffer(buffer) {
if (this.normalize || !this.buffers.indices) {
return super.getGeometryFromBuffer(buffer);
}
// we don't need to read the positions if no normalization/tesselation
return null;
}
/** Implement base Tesselator interface */
updateGeometryAttributes(polygon, context) {
if (polygon && isCut(polygon)) {
for (const subPolygon of polygon) {
const geometrySize = this.getGeometrySize(subPolygon);
context.geometrySize = geometrySize;
this.updateGeometryAttributes(subPolygon, context);
context.vertexStart += geometrySize;
context.indexStart = this.indexStarts[context.geometryIndex + 1];
}
}
else {
this._updateIndices(polygon, context);
this._updatePositions(polygon, context);
this._updateVertexValid(polygon, context);
}
}
// Flatten the indices array
_updateIndices(polygon, { geometryIndex, vertexStart: offset, indexStart }) {
const { attributes, indexStarts, typedArrayManager } = this;
let target = attributes.indices;
if (!target || !polygon) {
return;
}
let i = indexStart;
// 1. get triangulated indices for the internal areas
const indices = Polygon.getSurfaceIndices(polygon, this.positionSize, this.opts.preproject);
// make sure the buffer is large enough
target = typedArrayManager.allocate(target, indexStart + indices.length, {
copy: true
});
// 2. offset each index by the number of indices in previous polygons
for (let j = 0; j < indices.length; j++) {
target[i++] = indices[j] + offset;
}
indexStarts[geometryIndex + 1] = indexStart + indices.length;
attributes.indices = target;
}
// Flatten out all the vertices of all the sub subPolygons
_updatePositions(polygon, { vertexStart, geometrySize }) {
const { attributes: { positions }, positionSize } = this;
if (!positions || !polygon) {
return;
}
const polygonPositions = Polygon.getPositions(polygon);
for (let i = vertexStart, j = 0; j < geometrySize; i++, j++) {
const x = polygonPositions[j * positionSize];
const y = polygonPositions[j * positionSize + 1];
const z = positionSize > 2 ? polygonPositions[j * positionSize + 2] : 0;
positions[i * 3] = x;
positions[i * 3 + 1] = y;
positions[i * 3 + 2] = z;
}
}
_updateVertexValid(polygon, { vertexStart, geometrySize }) {
const { positionSize } = this;
const vertexValid = this.attributes.vertexValid;
const holeIndices = polygon && Polygon.getHoleIndices(polygon);
/* We are reusing the some buffer for `nextPositions` by offseting one vertex
* to the left. As a result,
* the last vertex of each ring overlaps with the first vertex of the next ring.
* `vertexValid` is used to mark the end of each ring so we don't draw these
* segments:
positions A0 A1 A2 A3 A4 B0 B1 B2 C0 ...
nextPositions A1 A2 A3 A4 B0 B1 B2 C0 C1 ...
vertexValid 1 1 1 1 0 1 1 0 1 ...
*/
if (polygon && polygon.edgeTypes) {
vertexValid.set(polygon.edgeTypes, vertexStart);
}
else {
vertexValid.fill(1, vertexStart, vertexStart + geometrySize);
}
if (holeIndices) {
for (let j = 0; j < holeIndices.length; j++) {
vertexValid[vertexStart + holeIndices[j] / positionSize - 1] = 0;
}
}
vertexValid[vertexStart + geometrySize - 1] = 0;
}
}
function isCut(polygon) {
return Array.isArray(polygon) && polygon.length > 0 && !Number.isFinite(polygon[0]);
}