modules/layers/src/solid-polygon-layer/solid-polygon-layer.js (295 lines of code) (raw):
// 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.
import {Layer, project32, gouraudLighting, picking, COORDINATE_SYSTEM} from '@deck.gl/core';
import GL from '@luma.gl/constants';
import {Model, Geometry, hasFeatures, FEATURES} from '@luma.gl/core';
// Polygon geometry generation is managed by the polygon tesselator
import PolygonTesselator from './polygon-tesselator';
import vsTop from './solid-polygon-layer-vertex-top.glsl';
import vsSide from './solid-polygon-layer-vertex-side.glsl';
import fs from './solid-polygon-layer-fragment.glsl';
const DEFAULT_COLOR = [0, 0, 0, 255];
const defaultProps = {
filled: true,
// Whether to extrude
extruded: false,
// Whether to draw a GL.LINES wireframe of the polygon
wireframe: false,
_normalize: true,
// elevation multiplier
elevationScale: {type: 'number', min: 0, value: 1},
// Accessor for polygon geometry
getPolygon: {type: 'accessor', value: f => f.polygon},
// Accessor for extrusion height
getElevation: {type: 'accessor', value: 1000},
// Accessor for colors
getFillColor: {type: 'accessor', value: DEFAULT_COLOR},
getLineColor: {type: 'accessor', value: DEFAULT_COLOR},
// Optional settings for 'lighting' shader module
material: true
};
const ATTRIBUTE_TRANSITION = {
enter: (value, chunk) => {
return chunk.length ? chunk.subarray(chunk.length - value.length) : value;
}
};
export default class SolidPolygonLayer extends Layer {
getShaders(vs) {
return super.getShaders({
vs,
fs,
defines: {},
modules: [project32, gouraudLighting, picking]
});
}
get wrapLongitude() {
return false;
}
initializeState() {
const {gl, viewport} = this.context;
let {coordinateSystem} = this.props;
if (viewport.isGeospatial && coordinateSystem === COORDINATE_SYSTEM.DEFAULT) {
coordinateSystem = COORDINATE_SYSTEM.LNGLAT;
}
this.setState({
numInstances: 0,
polygonTesselator: new PolygonTesselator({
// Lnglat coordinates are usually projected non-linearly, which affects tesselation results
// Provide a preproject function if the coordinates are in lnglat
preproject: coordinateSystem === COORDINATE_SYSTEM.LNGLAT && viewport.projectFlat,
fp64: this.use64bitPositions(),
IndexType: !gl || hasFeatures(gl, FEATURES.ELEMENT_INDEX_UINT32) ? Uint32Array : Uint16Array
})
});
const attributeManager = this.getAttributeManager();
const noAlloc = true;
attributeManager.remove(['instancePickingColors']);
/* eslint-disable max-len */
attributeManager.add({
indices: {size: 1, isIndexed: true, update: this.calculateIndices, noAlloc},
positions: {
size: 3,
type: GL.DOUBLE,
fp64: this.use64bitPositions(),
transition: ATTRIBUTE_TRANSITION,
accessor: 'getPolygon',
update: this.calculatePositions,
noAlloc,
shaderAttributes: {
positions: {
vertexOffset: 0,
divisor: 0
},
instancePositions: {
vertexOffset: 0,
divisor: 1
},
nextPositions: {
vertexOffset: 1,
divisor: 1
}
}
},
vertexValid: {
size: 1,
divisor: 1,
type: GL.UNSIGNED_BYTE,
update: this.calculateVertexValid,
noAlloc
},
elevations: {
size: 1,
transition: ATTRIBUTE_TRANSITION,
accessor: 'getElevation',
shaderAttributes: {
elevations: {
divisor: 0
},
instanceElevations: {
divisor: 1
}
}
},
fillColors: {
alias: 'colors',
size: this.props.colorFormat.length,
type: GL.UNSIGNED_BYTE,
normalized: true,
transition: ATTRIBUTE_TRANSITION,
accessor: 'getFillColor',
defaultValue: DEFAULT_COLOR,
shaderAttributes: {
fillColors: {
divisor: 0
},
instanceFillColors: {
divisor: 1
}
}
},
lineColors: {
alias: 'colors',
size: this.props.colorFormat.length,
type: GL.UNSIGNED_BYTE,
normalized: true,
transition: ATTRIBUTE_TRANSITION,
accessor: 'getLineColor',
defaultValue: DEFAULT_COLOR,
shaderAttributes: {
lineColors: {
divisor: 0
},
instanceLineColors: {
divisor: 1
}
}
},
pickingColors: {
size: 3,
type: GL.UNSIGNED_BYTE,
accessor: (object, {index, target: value}) =>
this.encodePickingColor(object && object.__source ? object.__source.index : index, value),
shaderAttributes: {
pickingColors: {
divisor: 0
},
instancePickingColors: {
divisor: 1
}
}
}
});
/* eslint-enable max-len */
}
getPickingInfo(params) {
const info = super.getPickingInfo(params);
const {index} = info;
const {data} = this.props;
if (data[0] && data[0].__source) {
// data is wrapped
info.object = data.find(d => d.__source.index === index);
}
return info;
}
draw({uniforms}) {
const {extruded, filled, wireframe, elevationScale} = this.props;
const {topModel, sideModel, polygonTesselator} = this.state;
const renderUniforms = Object.assign({}, uniforms, {
extruded: Boolean(extruded),
elevationScale
});
// Note: the order is important
if (sideModel) {
sideModel.setInstanceCount(polygonTesselator.instanceCount - 1);
sideModel.setUniforms(renderUniforms);
if (wireframe) {
sideModel.setDrawMode(GL.LINE_STRIP);
sideModel.setUniforms({isWireframe: true}).draw();
}
if (filled) {
sideModel.setDrawMode(GL.TRIANGLE_FAN);
sideModel.setUniforms({isWireframe: false}).draw();
}
}
if (topModel) {
topModel.setVertexCount(polygonTesselator.vertexCount);
topModel.setUniforms(renderUniforms).draw();
}
}
updateState(updateParams) {
super.updateState(updateParams);
this.updateGeometry(updateParams);
const {props, oldProps, changeFlags} = updateParams;
const attributeManager = this.getAttributeManager();
const regenerateModels =
changeFlags.extensionsChanged ||
props.filled !== oldProps.filled ||
props.extruded !== oldProps.extruded;
if (regenerateModels) {
if (this.state.models) {
this.state.models.forEach(model => model.delete());
}
this.setState(this._getModels(this.context.gl));
attributeManager.invalidateAll();
}
}
updateGeometry({props, oldProps, changeFlags}) {
const geometryConfigChanged =
changeFlags.dataChanged ||
(changeFlags.updateTriggersChanged &&
(changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getPolygon));
// When the geometry config or the data is changed,
// tessellator needs to be invoked
if (geometryConfigChanged) {
const {polygonTesselator} = this.state;
const buffers = props.data.attributes || {};
polygonTesselator.updateGeometry({
data: props.data,
normalize: props._normalize,
geometryBuffer: buffers.getPolygon,
buffers,
getGeometry: props.getPolygon,
positionFormat: props.positionFormat,
wrapLongitude: props.wrapLongitude,
// TODO - move the flag out of the viewport
resolution: this.context.viewport.resolution,
fp64: this.use64bitPositions(),
dataChanged: changeFlags.dataChanged
});
this.setState({
numInstances: polygonTesselator.instanceCount,
startIndices: polygonTesselator.vertexStarts
});
if (!changeFlags.dataChanged) {
// Base `layer.updateState` only invalidates all attributes on data change
// Cover the rest of the scenarios here
this.getAttributeManager().invalidateAll();
}
}
}
_getModels(gl) {
const {id, filled, extruded} = this.props;
let topModel;
let sideModel;
if (filled) {
const shaders = this.getShaders(vsTop);
shaders.defines.NON_INSTANCED_MODEL = 1;
topModel = new Model(
gl,
Object.assign({}, shaders, {
id: `${id}-top`,
drawMode: GL.TRIANGLES,
attributes: {
vertexPositions: new Float32Array([0, 1])
},
uniforms: {
isWireframe: false,
isSideVertex: false
},
vertexCount: 0,
isIndexed: true
})
);
}
if (extruded) {
sideModel = new Model(
gl,
Object.assign({}, this.getShaders(vsSide), {
id: `${id}-side`,
geometry: new Geometry({
drawMode: GL.LINES,
vertexCount: 4,
attributes: {
// top right - top left - bootom left - bottom right
vertexPositions: {
size: 2,
value: new Float32Array([1, 0, 0, 0, 0, 1, 1, 1])
}
}
}),
instanceCount: 0,
isInstanced: 1
})
);
sideModel.userData.excludeAttributes = {indices: true};
}
return {
models: [sideModel, topModel].filter(Boolean),
topModel,
sideModel
};
}
calculateIndices(attribute) {
const {polygonTesselator} = this.state;
attribute.startIndices = polygonTesselator.indexStarts;
attribute.value = polygonTesselator.get('indices');
}
calculatePositions(attribute) {
const {polygonTesselator} = this.state;
attribute.startIndices = polygonTesselator.vertexStarts;
attribute.value = polygonTesselator.get('positions');
}
calculateVertexValid(attribute) {
attribute.value = this.state.polygonTesselator.get('vertexValid');
}
}
SolidPolygonLayer.layerName = 'SolidPolygonLayer';
SolidPolygonLayer.defaultProps = defaultProps;