builtin-programs/draw/curve.folk

# Bezier implementation adapted from https://www.shadertoy.com/view/XdVBWd

Wish the GPU compiles function "curveSegmentDistance" {{vec2 p vec2 a vec2 b} float {
    vec2 pa = p - a;
    vec2 ba = b - a;
    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
    vec2 d = pa - ba * h;
    return dot(d, d);
}}

Wish the GPU compiles function "curveBezierDistance" {{vec2 p0 vec2 p1 vec2 p2 vec2 p3 vec2 pos fn curveSegmentDistance} float {
    const int kNumSamples = 50;
    float distance = 1e10;
    vec2 a = p0;
    for (int i = 1; i < kNumSamples; i++) {
        float t = float(i) / float(kNumSamples - 1);
        float s = 1.0 - t;
        vec2 b = p0 * s * s * s +
                 p1 * 3.0 * s * s * t +
                 p2 * 3.0 * s * t * t +
                 p3 * t * t * t;
        distance = min(distance, curveSegmentDistance(pos, a, b));
        a = b;
    }
    return sqrt(distance);
}}

Wish the GPU compiles pipeline "curve" {
    {vec2 viewport mat3 surfaceToClip
     vec2 p0 vec2 p1 vec2 p2 vec2 p3 float thickness vec4 color} {
        vec2 from = min(min(p0, p1), min(p2, p3)) - thickness;
        vec2 to = max(max(p0, p1), max(p2, p3)) + thickness;

        vec2 vertices[6] = vec2[6](
            from,
            vec2(to.x, from.y),
            vec2(from.x, to.y),
            vec2(to.x, from.y),
            to,
            vec2(from.x, to.y)
        );

        vec3 v = surfaceToClip * vec3(vertices[gl_VertexIndex], 1.0);
        return vec4(v.xy / v.z, 0.0, 1.0);
    } {fn curveBezierDistance} {
        vec2 clipXy = (gl_FragCoord.xy / viewport) * 2.0 - 1.0;
        vec3 surfaceXy = inverse(surfaceToClip) * vec3(clipXy, 1.0);
        surfaceXy /= surfaceXy.z;

        float distance = curveBezierDistance(p0, p1, p2, p3, surfaceXy.xy);
        float edge = max(fwidth(distance), thickness * 0.05);
        float alpha = 1.0 - smoothstep(thickness, thickness + edge, distance);

        return (alpha < 0.01) ? vec4(0.0) : vec4(color.rgb, color.a * alpha);
    }
}

When the color map is /colorMap/ &\
     /p/ has canvas /id/ with /...wiOptions/ &\
     /p/ has canvas projection /surfaceToClip/ &\
     /someone/ wishes to draw a curve onto /p/ with /...options/ {

    set p0 [dict get $options p0]
    set p1 [dict get $options p1]
    set p2 [dict get $options p2]
    set p3 [dict get $options p3]
    set thickness [dict get $options thickness]

    set color [dict get $options color]
    set color [dict getdef $colorMap $color $color]
    set layer [dict getdef $options layer 0]

    set wiResolution [list [dict get $wiOptions width] [dict get $wiOptions height]]

    Wish the GPU draws pipeline "curve" onto canvas $id with arguments \
        [list $wiResolution $surfaceToClip \
              $p0 $p1 $p2 $p3 $thickness $color] \
        layer $layer
}