examples/website/plot/plot-layer/surface-layer.js (177 lines of code) (raw):
import {Layer, picking} from '@deck.gl/core';
import GL from '@luma.gl/constants';
import {Model} from '@luma.gl/core';
import surfaceVertex from './surface-vertex.glsl';
import fragmentShader from './fragment.glsl';
const DEFAULT_COLOR = [0, 0, 0, 255];
const defaultProps = {
data: [],
getPosition: () => [0, 0, 0],
getColor: () => DEFAULT_COLOR,
xScale: null,
yScale: null,
zScale: null,
uCount: 100,
vCount: 100,
lightStrength: 0.1
};
/*
* @classdesc
* A layer that plots a surface based on a z=f(x,y) equation.
*
* @class
* @param {Object} [props]
* @param {Function} [props.getPosition] - method called to get [x, y, z] from (u,v) values
* @param {Function} [props.getColor] - method called to get color from (x,y,z)
returns [r,g,b,a].
* @param {d3.scale} [props.xScale] - a d3 scale for the x axis
* @param {d3.scale} [props.yScale] - a d3 scale for the y axis
* @param {d3.scale} [props.zScale] - a d3 scale for the z axis
* @param {Integer} [props.uCount] - number of samples within x range
* @param {Integer} [props.vCount] - number of samples within y range
* @param {Number} [props.lightStrength] - front light strength
*/
export default class SurfaceLayer extends Layer {
initializeState() {
const {gl} = this.context;
const attributeManager = this.getAttributeManager();
const noAlloc = true;
/* eslint-disable max-len */
attributeManager.add({
indices: {size: 1, isIndexed: true, update: this.calculateIndices, noAlloc},
positions: {size: 4, accessor: 'getPosition', update: this.calculatePositions, noAlloc},
colors: {
size: 4,
accessor: ['getPosition', 'getColor'],
type: GL.UNSIGNED_BYTE,
update: this.calculateColors,
noAlloc
},
pickingColors: {size: 3, type: GL.UNSIGNED_BYTE, update: this.calculatePickingColors, noAlloc}
});
/* eslint-enable max-len */
gl.getExtension('OES_element_index_uint');
this.setState({
model: this.getModel(gl)
});
}
updateState({oldProps, props, changeFlags}) {
if (changeFlags.propsChanged) {
const {uCount, vCount} = props;
if (oldProps.uCount !== uCount || oldProps.vCount !== vCount) {
this.setState({
vertexCount: uCount * vCount
});
this.getAttributeManager().invalidateAll();
}
}
}
getModel(gl) {
// 3d surface
return new Model(gl, {
id: `${this.props.id}-surface`,
vs: surfaceVertex,
fs: fragmentShader,
modules: [picking],
drawMode: GL.TRIANGLES,
vertexCount: 0,
isIndexed: true
});
}
draw({uniforms}) {
const {lightStrength} = this.props;
this.state.model
.setUniforms(
Object.assign({}, uniforms, {
lightStrength
})
)
.draw();
}
/*
* y 1
* ^
* |
* |
* |
* 0--------> 1
* x
*/
encodePickingColor(i) {
const {uCount, vCount} = this.props;
const xIndex = i % uCount;
const yIndex = (i - xIndex) / uCount;
return [(xIndex / (uCount - 1)) * 255, (yIndex / (vCount - 1)) * 255, 1];
}
decodePickingColor([r, g, b]) {
if (b === 0) {
return -1;
}
return [r / 255, g / 255];
}
getPickingInfo(opts) {
const {info} = opts;
if (info && info.index !== -1) {
const [u, v] = info.index;
const {getPosition} = this.props;
info.sample = getPosition(u, v);
}
return info;
}
calculateIndices(attribute) {
const {uCount, vCount} = this.props;
// # of squares = (nx - 1) * (ny - 1)
// # of triangles = squares * 2
// # of indices = triangles * 3
const indicesCount = (uCount - 1) * (vCount - 1) * 2 * 3;
const indices = new Uint32Array(indicesCount);
let i = 0;
for (let xIndex = 0; xIndex < uCount - 1; xIndex++) {
for (let yIndex = 0; yIndex < vCount - 1; yIndex++) {
/*
* i0 i1
* +--.+---
* | / |
* +'--+---
* | |
* i2 i3
*/
const i0 = yIndex * uCount + xIndex;
const i1 = i0 + 1;
const i2 = i0 + uCount;
const i3 = i2 + 1;
indices[i++] = i0;
indices[i++] = i2;
indices[i++] = i1;
indices[i++] = i1;
indices[i++] = i2;
indices[i++] = i3;
}
}
attribute.value = indices;
this.state.model.setVertexCount(indicesCount);
}
// the fourth component is a flag for invalid z (NaN or Infinity)
/* eslint-disable max-statements */
calculatePositions(attribute) {
const {vertexCount} = this.state;
const {uCount, vCount, getPosition, xScale, yScale, zScale} = this.props;
const value = new Float32Array(vertexCount * attribute.size);
let i = 0;
for (let vIndex = 0; vIndex < vCount; vIndex++) {
for (let uIndex = 0; uIndex < uCount; uIndex++) {
const u = uIndex / (uCount - 1);
const v = vIndex / (vCount - 1);
const [x, y, z] = getPosition(u, v);
const isXFinite = isFinite(x);
const isYFinite = isFinite(y);
const isZFinite = isFinite(z);
// swap z and y: y is up in the default viewport
value[i++] = isXFinite ? xScale(x) : xScale.range()[0];
value[i++] = isZFinite ? zScale(z) : zScale.range()[0];
value[i++] = isYFinite ? yScale(y) : yScale.range()[0];
value[i++] = isXFinite && isYFinite && isZFinite ? 0 : 1;
}
}
attribute.value = value;
}
/* eslint-enable max-statements */
calculateColors(attribute) {
const {vertexCount} = this.state;
const attributeManager = this.getAttributeManager();
// reuse the calculated [x, y, z] in positions
const positions = attributeManager.attributes.positions.value;
const value = new Uint8ClampedArray(vertexCount * attribute.size);
// Support constant colors
let {getColor} = this.props;
getColor = typeof getColor === 'function' ? getColor : () => getColor;
for (let i = 0; i < vertexCount; i++) {
const index = i * 4;
const color = getColor(positions[index], positions[index + 2], positions[index + 1]);
value[i * 4] = color[0];
value[i * 4 + 1] = color[1];
value[i * 4 + 2] = color[2];
value[i * 4 + 3] = isNaN(color[3]) ? 255 : color[3];
}
attribute.value = value;
}
calculatePickingColors(attribute) {
const {vertexCount} = this.state;
const value = new Uint8ClampedArray(vertexCount * attribute.size);
for (let i = 0; i < vertexCount; i++) {
const pickingColor = this.encodePickingColor(i);
value[i * 3] = pickingColor[0];
value[i * 3 + 1] = pickingColor[1];
value[i * 3 + 2] = pickingColor[2];
}
attribute.value = value;
}
}
SurfaceLayer.layerName = 'SurfaceLayer';
SurfaceLayer.defaultProps = defaultProps;