UNPKG

@deck.gl-community/layers

Version:

Add-on layers for deck.gl

285 lines (261 loc) 8.79 kB
// 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