struct Frame {
    clip_from_world:  mat4x4<f32>,
    stroke_color:     vec4<f32>,
    construction:     vec4<f32>,
    pixels_per_mm:    f32,
    dash_period_px:   f32,
    dash_on_ratio:    f32,
    _pad:             f32,
};

struct Instance {
    @location(0) a:             vec2<f32>,
    @location(1) b:             vec2<f32>,
    @location(2) half_width_px: f32,
    @location(3) pick_id:       u32,
    @location(4) style_bits:    u32,
};

struct VsOut {
    @builtin(position) clip:    vec4<f32>,
    @location(0) local_px:      vec2<f32>,
    @location(1) length_px:     f32,
    @location(2) half_width_px: f32,
    @location(3) @interpolate(flat) style_bits: u32,
    @location(4) @interpolate(flat) pick_id:    u32,
};

struct FsOut {
    @location(0) color:   vec4<f32>,
    @location(1) pick_id: u32,
};

@group(0) @binding(0) var<uniform> u: Frame;

const CORNERS: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
    vec2<f32>(0.0, 0.0),
    vec2<f32>(1.0, 0.0),
    vec2<f32>(0.0, 1.0),
    vec2<f32>(0.0, 1.0),
    vec2<f32>(1.0, 0.0),
    vec2<f32>(1.0, 1.0),
);

const CONSTRUCTION_BIT: u32 = 1u;

@vertex
fn vs(@builtin(vertex_index) vid: u32, inst: Instance) -> VsOut {
    var corners = CORNERS;
    let corner = corners[vid];
    let d_mm = inst.b - inst.a;
    let len_mm = length(d_mm);
    let safe = max(len_mm, 1.0e-9);
    let t_hat = select(vec2<f32>(1.0, 0.0), d_mm / safe, len_mm > 1.0e-9);
    let n_hat = vec2<f32>(-t_hat.y, t_hat.x);

    let expand_mm = (inst.half_width_px + 1.0) / u.pixels_per_mm;
    let along_mm = mix(-expand_mm, len_mm + expand_mm, corner.x);
    let perp_mm = mix(-expand_mm, expand_mm, corner.y);

    let world_mm = inst.a + along_mm * t_hat + perp_mm * n_hat;
    let clip = u.clip_from_world * vec4<f32>(world_mm, 0.0, 1.0);

    var out: VsOut;
    out.clip = clip;
    out.local_px = vec2<f32>(along_mm, perp_mm) * u.pixels_per_mm;
    out.length_px = len_mm * u.pixels_per_mm;
    out.half_width_px = inst.half_width_px;
    out.style_bits = inst.style_bits;
    out.pick_id = inst.pick_id;
    return out;
}

@fragment
fn fs(in: VsOut) -> FsOut {
    let along = clamp(in.local_px.x, 0.0, in.length_px);
    let dx = in.local_px.x - along;
    let dy = in.local_px.y;
    let d_px = sqrt(dx * dx + dy * dy);
    let aa = 0.5;
    let coverage = 1.0 - smoothstep(in.half_width_px - aa, in.half_width_px + aa, d_px);
    if (coverage <= 0.0) {
        discard;
    }

    let is_construction = (in.style_bits & CONSTRUCTION_BIT) != 0u;
    let dash_visible = !is_construction
        || u.dash_period_px <= 0.0
        || fract(along / u.dash_period_px) <= u.dash_on_ratio;
    let color_coverage = select(0.0, coverage, dash_visible);

    let base = select(u.stroke_color, u.construction, is_construction);
    let a = base.a * color_coverage;

    var out: FsOut;
    out.color = vec4<f32>(base.rgb * a, a);
    out.pick_id = in.pick_id;
    return out;
}
