@deck.gl-community/layers
Version:
Add-on layers for deck.gl
285 lines (261 loc) • 8.79 kB
JavaScript
// deck.gl-community
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { Layer, picking, project32, UNIT } from '@deck.gl/core';
import { Geometry, Model } from '@luma.gl/engine';
const geometryLayerUniforms = {
name: 'geometryLayer',
vs: `\
uniform geometryLayerUniforms {
float sizeScale;
highp int sizeUnits;
highp int interpolationMode;
} geometryLayer;
`,
fs: `\
uniform geometryLayerUniforms {
float sizeScale;
highp int sizeUnits;
highp int interpolationMode;
} geometryLayer;
`,
uniformTypes: {
sizeScale: 'f32',
sizeUnits: 'i32',
interpolationMode: 'i32'
}
};
const defaultProps = {
sizeUnits: 'common',
sizeScale: { type: 'number', min: 0, value: 1 },
interpolationMode: 'line',
getPickingColor: { type: 'accessor', value: [0, 0, 0] },
getSourcePosition: { type: 'accessor', value: (x) => x.source },
getTargetPosition: { type: 'accessor', value: (x) => x.target },
getPositionRatio: { type: 'accessor', value: 1 },
getSize: { type: 'accessor', value: [1, 1] },
getColor: { type: 'accessor', value: [0, 0, 0, 255] },
getArcHeight: { type: 'accessor', value: 1 },
getArcTilt: { type: 'accessor', value: 0 }
};
const vs = `\
#version 300 es
#define SHADER_NAME geometry-layer-vertex-shader
in vec2 positions;
in vec3 instanceSourcePositions;
in vec3 instanceSourcePositions64Low;
in vec3 instanceTargetPositions;
in vec3 instanceTargetPositions64Low;
in float instanceRatios;
in vec2 instanceSizes;
in vec4 instanceColors;
in float instanceArcHeights;
in float instanceArcTilts;
in vec3 instancePickingColors;
out vec4 vColor;
out vec2 vPosition;
flat out vec2 vPixelSize;
const int GEOMETRY_LINE = 0;
const int GEOMETRY_ARC = 1;
// START ARC LAYER VERTEX
float paraboloid(float distance, float sourceZ, float targetZ, float ratio) {
float deltaZ = targetZ - sourceZ;
float dh = distance * instanceArcHeights;
if (dh == 0.0) {
return sourceZ + deltaZ * ratio;
}
float unitZ = deltaZ / dh;
float p2 = unitZ * unitZ + 1.0;
// sqrt does not deal with negative values, manually flip source and target if delta.z < 0
float dir = step(deltaZ, 0.0);
float z0 = mix(sourceZ, targetZ, dir);
float r = mix(ratio, 1.0 - ratio, dir);
return sqrt(r * (p2 - r)) * dh + z0;
}
vec3 interpolateArc(vec3 source, vec3 target, float ratio) {
float distance = length(source.xy - target.xy);
float z = paraboloid(distance, source.z, target.z, ratio);
float tiltAngle = radians(instanceArcTilts);
vec2 tiltDirection = normalize(target.xy - source.xy);
vec2 tilt = vec2(-tiltDirection.y, tiltDirection.x) * z * sin(tiltAngle);
return vec3(
mix(source.xy, target.xy, ratio) + tilt,
z * cos(tiltAngle)
);
}
// END ARC LAYER VERTEX
vec3 interplolatePath(vec3 source, vec3 target, float ratio) {
if (geometryLayer.interpolationMode == GEOMETRY_ARC) {
return interpolateArc(source, target, ratio);
}
return mix(source, target, ratio);
}
void main(void) {
geometry.worldPosition = instanceSourcePositions;
geometry.worldPositionAlt = instanceTargetPositions;
vPosition = (positions + 1.0) / 2.0;
geometry.uv = vPosition;
vec3 source = project_position(instanceSourcePositions, instanceSourcePositions64Low);
vec3 target = project_position(instanceTargetPositions, instanceTargetPositions64Low);
vec3 curr = interplolatePath(source, target, instanceRatios);
vec2 normal;
if (instanceRatios < 0.01) {
vec3 next = interplolatePath(source, target, instanceRatios + 0.01);
normal = next.xy - curr.xy;
} else {
vec3 prev = interplolatePath(source, target, instanceRatios - 0.01);
normal = curr.xy - prev.xy;
}
vec2 scaledSize = instanceSizes * geometryLayer.sizeScale;
// Anchor the marker at the triangle tip, so ratio 1.0 places the arrowhead on the endpoint.
vec2 markerPosition = vec2((positions.x - 1.0) / 2.0, positions.y / 2.0);
vec2 offset = markerPosition * scaledSize;
float angle = atan(normal.y, normal.x);
float cosA = cos(angle);
float sinA = sin(angle);
offset = vec2(
offset.x * cosA - offset.y * sinA,
offset.x * sinA + offset.y * cosA
);
vec3 offsetCommon = vec3(offset, 0.);
if (geometryLayer.sizeUnits == UNIT_PIXELS) {
offsetCommon.xy = project_pixel_size(offset);
vPixelSize = scaledSize;
} else {
vPixelSize = project_size(scaledSize);
}
geometry.pickingColor = instancePickingColors;
geometry.position = vec4(curr + offsetCommon, 0.1);
gl_Position = project_common_position_to_clipspace(geometry.position);
DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
vColor = vec4(instanceColors.rgb, instanceColors.a * layer.opacity);
DECKGL_FILTER_COLOR(vColor, geometry);
}
`;
const fs = `\
#version 300 es
#define SHADER_NAME geometry-layer-fragment-shader
precision highp float;
in vec4 vColor;
in vec2 vPosition;
flat in vec2 vPixelSize;
out vec4 fragColor;
float smoothedgeSigned(float signedDistance) {
float edgeRadius = fwidth(signedDistance);
return smoothstep(-edgeRadius, edgeRadius, signedDistance);
}
float inTriangle(vec2 bbox, vec2 uv) {
float w = max(bbox.x, 1.0);
float h = max(bbox.y, 1.0);
float d = ((1.0 - abs(1.0 - uv.y * 2.0)) - uv.x) * w;
return smoothedgeSigned(d);
}
void main(void) {
geometry.uv = vPosition;
float inShape = inTriangle(vPixelSize, vPosition);
if (inShape == 0.0) {
discard;
}
fragColor = vColor;
fragColor.a *= inShape;
DECKGL_FILTER_COLOR(fragColor, geometry);
}
`;
/** Renders triangle markers resolved by {@link DependencyArrowLayer}. */
export class GeometryLayer extends Layer {
static defaultProps = defaultProps;
static layerName = 'GeometryLayer';
state = {};
getShaders() {
return super.getShaders({ vs, fs, modules: [project32, picking, geometryLayerUniforms] });
}
initializeState() {
this.getAttributeManager().addInstanced({
instanceSourcePositions: {
size: 3,
type: 'float64',
fp64: this.use64bitPositions(),
transition: true,
accessor: 'getSourcePosition'
},
instanceTargetPositions: {
size: 3,
type: 'float64',
fp64: this.use64bitPositions(),
transition: true,
accessor: 'getTargetPosition'
},
instanceRatios: {
size: 1,
transition: true,
accessor: 'getPositionRatio'
},
instanceArcHeights: {
size: 1,
transition: true,
accessor: 'getArcHeight'
},
instanceArcTilts: {
size: 1,
transition: true,
accessor: 'getArcTilt'
},
instanceSizes: {
size: 2,
transition: true,
accessor: 'getSize'
},
instanceColors: {
size: 4,
transition: true,
type: 'unorm8',
accessor: 'getColor',
defaultValue: [0, 0, 0, 255]
},
instancePickingColors: {
size: 3,
type: 'uint8',
accessor: 'getPickingColor'
}
});
}
updateState(params) {
super.updateState(params);
if (params.changeFlags.extensionsChanged) {
this.state.model?.destroy();
this.state.model = this._getModel();
this.getAttributeManager().invalidateAll();
}
}
draw() {
const model = this.state.model;
if (!model) {
return;
}
const { sizeScale, sizeUnits, interpolationMode } = this.props;
const geometryLayerProps = {
sizeScale,
sizeUnits: UNIT[sizeUnits],
interpolationMode: interpolationMode === 'line' ? 0 : 1
};
model.shaderInputs.setProps({ geometryLayer: geometryLayerProps });
model.draw(this.context.renderPass);
}
_getModel() {
// A square that minimally covers the unit circle.
const positions = [-1, -1, 1, -1, -1, 1, 1, 1];
return new Model(this.context.device, {
...this.getShaders(),
id: this.props.id,
bufferLayout: this.getAttributeManager().getBufferLayouts(),
geometry: new Geometry({
topology: 'triangle-strip',
attributes: {
positions: { size: 2, value: new Float32Array(positions) }
}
}),
isInstanced: true
});
}
}
//# sourceMappingURL=geometry-layer.js.map