Search code examples
glslcesiumjs

How to create a dashed arrow in Cesium


Cesium has solid arrows (PolylineArrow) and dashed lines (PolylineDash). I want to combine the two to make a PolylineDashArrow (An Arrow with a Dash Fill, or a dash line with an arrow head).

It sounds like this should be possible using Cesium's Fabric. Though I think I need to add a GLSL like the ones for arrow and dash. (The Fabric page doesn't say anything about how to add a custom GLSL to use for the source)

This seems like something that should be really easy to do but I can't find any information on anyone else trying to do this.


Solution

  • So, it should have been straightforward. But there's a small catch in that you don't want the dashes interrupting the arrow head itself. The arrow head should always be solid, or it looks wrong.

    The biggest problem I ran into is that the dash material doesn't just mark the gaps between dashes as transparent, it actually marks them for discard. The good news is this is done with a Boolean value (not a raw discard keyword) that can be tricked into becoming false again, to keep those gaps from interrupting the arrow head.

    So I had to cheat a bit to disable the dashMaterial's discard, but I got it to work.

    Here's what I ended up with: Sandcastle demo of dashed arrow.

    The code for that demo looks like this:

    var viewer = new Cesium.Viewer('cesiumContainer');
    var scene = viewer.scene;
    
    // Create sample polyline primitive.
    var polylines = scene.primitives.add(new Cesium.PolylineCollection());
    var polyline = polylines.add({
        positions : Cesium.PolylinePipeline.generateCartesianArc({
            positions : Cesium.Cartesian3.fromDegreesArray([-110.0, 42.0,
                                                            -85.0, 36.0,
                                                            -100.0, 25.0,
                                                            -77.0, 12.0])
        }),
        width : 15.0
    });
    
    // Assign a new fabric material blend of arrow and dashes.
    polyline.material = new Cesium.Material({
        fabric : {
            materials : {
                // The arrowMaterial provides the color and overall shape.
                arrowMaterial : {
                    type : 'PolylineArrow',
                    uniforms : {
                        color : Cesium.Color.YELLOW
                    }
                },
                // The dashMaterial will punch holes in the arrowMaterial.
                // Uniforms could be added to control the dash parameters.
                dashMaterial : {
                    type : 'PolylineDash',
                },
                // "headMaterial" is copy-paste of the arrow head size code, written to alpha.
                // It is used to mask out the dashes, to keep them from destroying the arrow head.
                // A small tail is included behind the arrow head, to keep it from becoming a triangle.
                headMaterial : {
                    source :
                    'czm_material czm_getMaterial(czm_materialInput materialInput) { \n' +
                    '    czm_material material = czm_getDefaultMaterial(materialInput); \n' +
                    '    vec2 st = materialInput.st; \n' +
                    '#ifdef GL_OES_standard_derivatives \n' +
                    // Original multiplier "10.0" changed to "15.0" to add short tail to head.
                    '    float base = 1.0 - abs(fwidth(st.s)) * 15.0 * czm_pixelRatio; \n' +
                    '#else \n' +
                    '    float base = 0.975; // 2.5% of the line will be the arrow head \n' +
                    '#endif \n' +
                    '    material.alpha = 1.0 - smoothstep(base - 0.0001, base, st.s); \n' +
                    '    return material; \n' +
                    '} \n'
                }
            },
            // Finally, the "alpha" contains a cheat, where we undo czm_discard from the dashMaterial.
            components : {
                diffuse : 'arrowMaterial.diffuse',
                alpha : 'arrowMaterial.alpha * (1.0 - (headMaterial.alpha * (1.0 - dashMaterial.alpha))); czm_discard = false'
            }
        }
    });