facett-core 0.1.7

facett — visual kernel: render a node/edge Scene into egui (wgpu fast path to come)
Documentation
// L0 thick-AA line shader — instanced oriented quads, a capsule (stadium) SDF in
// the fragment shader. The coverage math matches `render/cpu/sdf.rs::line_coverage`
// so a GPU line matches the CPU raster.

const CAP_BUTT:  u32 = 0u;
const CAP_ROUND: u32 = 1u;

struct Uniforms {
    viewport: vec2<f32>,
    _pad: vec2<f32>,
};
@group(0) @binding(0) var<uniform> U: Uniforms;

// One LineInstance (matches `prim::LineInstance`, 48 bytes).
struct Instance {
    @location(0) a: vec2<f32>,
    @location(1) b: vec2<f32>,
    @location(2) half_width: f32,
    @location(3) aa: f32,
    @location(4) cap: u32,
    @location(5) color: vec4<f32>,
};

struct VsOut {
    @builtin(position) clip: vec4<f32>,
    @location(0) p: vec2<f32>,                       // pixel position of this fragment
    @location(1) @interpolate(flat) a: vec2<f32>,
    @location(2) @interpolate(flat) b: vec2<f32>,
    @location(3) @interpolate(flat) half_width: f32,
    @location(4) @interpolate(flat) aa: f32,
    @location(5) @interpolate(flat) cap: u32,
    @location(6) color: vec4<f32>,
};

@vertex
fn vs_main(@builtin(vertex_index) vi: u32, inst: Instance) -> VsOut {
    // Oriented quad around the segment, inflated by (half_width + aa) on each side
    // and past each cap.
    let dir_raw = inst.b - inst.a;
    let len = max(length(dir_raw), 1e-6);
    let dir = dir_raw / len;
    let nrm = vec2<f32>(-dir.y, dir.x);
    let r = inst.half_width + inst.aa;

    // Corner selection: u along the axis (−r .. len+r), v across (−r .. r).
    var us = array<f32, 6>(-1.0, 1.0, -1.0, 1.0, 1.0, -1.0);
    var vs = array<f32, 6>(-1.0, -1.0, 1.0, -1.0, 1.0, 1.0);
    let along = select(0.0, len, us[vi] > 0.0) + us[vi] * r;
    let across = vs[vi] * r;
    let px = inst.a + dir * along + nrm * across;

    let ndc = vec2<f32>(px.x / U.viewport.x * 2.0 - 1.0, 1.0 - px.y / U.viewport.y * 2.0);

    var out: VsOut;
    out.clip = vec4<f32>(ndc, 0.0, 1.0);
    out.p = px;
    out.a = inst.a;
    out.b = inst.b;
    out.half_width = inst.half_width;
    out.aa = inst.aa;
    out.cap = inst.cap;
    out.color = inst.color;
    return out;
}

fn coverage_from_sd(d: f32, aa: f32) -> f32 {
    if (aa <= 0.0) {
        return select(0.0, 1.0, d <= 0.0);
    }
    return 1.0 - smoothstep(-aa, aa, d);
}

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    let ab = in.b - in.a;
    let len2 = max(dot(ab, ab), 1e-12);
    let t = clamp(dot(in.p - in.a, ab) / len2, 0.0, 1.0);
    let closest = in.a + ab * t;
    let dist = length(in.p - closest);
    var cov = coverage_from_sd(dist - in.half_width, in.aa);

    if (in.cap == CAP_BUTT) {
        let len = sqrt(len2);
        let u = ab / len;
        let s = dot(in.p - in.a, u);
        let axis_sd = max(-s, s - len);
        cov = cov * coverage_from_sd(axis_sd, in.aa);
    }
    if (cov <= 0.0) {
        discard;
    }
    let a = in.color.a * cov;
    return vec4<f32>(in.color.rgb * a, a);
}