deck.gl icon indicating copy to clipboard operation
deck.gl copied to clipboard

[Feat] Support dashed lines in ArcLayer

Open dbrnz opened this issue 1 year ago • 4 comments

Target Use Case

We use MapLibre to show cartoon maps of anatomical features on which neuron paths are drawn, using both plain and dashed coloured lines. The resulting map looks cluttered when there is a large number of neuron paths -- going into 3D, using Deck's ArcLayer, simply works (thanks!), nicely separating the paths. We just now need a layer with arcs drawn as dashed lines...

Proposal

Is this a case of extending the path-styles extension to ArcLayer? Or rather, what limits this extension to PathLayer?

dbrnz avatar Jan 23 '24 07:01 dbrnz

While the ArcLayer doesn't support the PathExtension, you could generate arcs in JS with the PathLayer and use the PathExtension. Here's a codepen demonstrating this concept. While it won't be as performant as the shader-based arcs, the shape can be customized and its dashed.

chrisgervang avatar Feb 13 '24 21:02 chrisgervang

Thanks!

I've now got dashed lines in a custom ArcLayer (by creating TRIANGLES instead of a TRIANGLE_STRIP and patching the shaders). Quite happy to show how I've done this but it won't be for a week or three as am about to go on leave -- would you like a PR or have code pasted here?

dbrnz avatar Feb 13 '24 22:02 dbrnz

Oh cool. I'd love to see your solution, whatever's convenient for you.

chrisgervang avatar Feb 13 '24 23:02 chrisgervang

Here's the relevant bits:

//==============================================================================

const transparencyCheck = '|| length(vColor) == 0.0'

class ArcMapLayer extends ArcLayer
{
    static layerName = 'ArcMapLayer'

    constructor(...args)
    {
        super(...args)
    }

    getShaders()
    //==========
    {
        const shaders = super.getShaders()
        shaders.fs = `#version 300 es\n${shaders.fs}`
                     .replace('isValid == 0.0', `isValid == 0.0 ${transparencyCheck}`)
        shaders.vs = `#version 300 es\n${shaders.vs}`
        return shaders
    }

    redraw()
    //======
    {
        this.internalState.changeFlags.dataChanged = true
        this.setNeedsUpdate()
    }
}

//==============================================================================

const makeDashedTriangles = `  float alpha = floor(fract(float(gl_VertexID)/12.0)+0.5);
  if (vColor.a != 0.0) vColor.a *= alpha;
`

class ArcDashedLayer extends ArcMapLayer
{
    static layerName = 'ArcDashedLayer'

    constructor(...args)
    {
        super(...args)
    }

    getShaders()
    //==========
    {
        const shaders = super.getShaders()
        shaders.vs = shaders.vs.replace('DECKGL_FILTER_COLOR(', `${makeDashedTriangles}\n  DECKGL_FILTER_COLOR(`)
        return shaders
    }

    _getModel(gl)
    //===========
    {
        const {numSegments} = this.props
        let positions = []
        for (let i = 0; i < numSegments; i++) {
            positions = positions.concat([i,  1, 0, i,  -1, 0, i+1,  1, 0,
                                          i, -1, 0, i+1, 1, 0, i+1, -1, 0])
        }
        const model = new Model(gl, {
            ...this.getShaders(),
            id: this.props.id,
            geometry: new Geometry({
                drawMode: GL.TRIANGLES,
                attributes: {
                    positions: new Float32Array(positions)
                }
            }),
            isInstanced: true,
        })
        model.setUniforms({numSegments: numSegments})
        return model
    }
}

//==============================================================================

export class Paths3DLayer
{
    #arcLayers = new Map()

    #layerOptions(pathType)
    //=====================
    {
        const pathData = [...this.#pathData.values()]
                                 .filter(ann => (this.#knownTypes.includes(ann.kind) && (ann.kind === pathType)
                                             || !this.#knownTypes.includes(ann.kind) && (pathType === 'other')))
        return {
            id: `arc-${pathType}`,
            data: pathData,
            pickable: true,
            autoHighlight: true,
            numSegments: 400,
            // Styles
            getSourcePosition: f => f.pathStartPosition,
            getTargetPosition: f => f.pathEndPosition,
            getSourceColor: this.#pathColour.bind(this),
            getTargetColor: this.#pathColour.bind(this),
            highlightColor: o => this.#pathColour(o.object),
            opacity: 1.0,
            getWidth: 3,
        }
    }

    #addArcLayer(pathType)
    //====================
    {
        const layer = this.#pathStyles.get(pathType).dashed
                        ? new ArcDashedLayer(this.#layerOptions(pathType))
                        : new ArcMapLayer(this.#layerOptions(pathType))
        this.#arcLayers.set(pathType, layer)
    }

    #setupDeckOverlay()
    //=================
    {
        [...this.#pathStyles.values()].filter(style => this.#pathManager.pathTypeEnabled(style.type))
                                      .forEach(style => this.#addArcLayer(style.type))
        this.#deckOverlay = new DeckOverlay({
            layers: [...this.#arcLayers.values()],
        })
    }
}

//==============================================================================

dbrnz avatar Feb 14 '24 01:02 dbrnz