struct Grid {
    world_from_clip: mat4x4<f32>,
    minor:            vec4<f32>,
    major:            vec4<f32>,
    axis_x:           vec4<f32>,
    axis_y:           vec4<f32>,
    origin:           vec4<f32>,
    viewport:         vec2<f32>,
    minor_spacing:    f32,
    major_every:      f32,
    line_width_px:    f32,
    axis_width_px:    f32,
    origin_radius_px: f32,
    pixels_per_mm:    f32,
};

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

@vertex
fn vs(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> {
    var positions = array<vec2<f32>, 3>(
        vec2<f32>(-1.0, -1.0),
        vec2<f32>( 3.0, -1.0),
        vec2<f32>(-1.0,  3.0),
    );
    return vec4<f32>(positions[vid], 0.0, 1.0);
}

fn line_distance_world(coord: f32, spacing: f32) -> f32 {
    let nearest = round(coord / spacing) * spacing;
    return abs(coord - nearest);
}

fn line_alpha(dist_px: f32, half_width_px: f32) -> f32 {
    let aa = min(0.5, half_width_px);
    return 1.0 - smoothstep(half_width_px - aa, half_width_px + aa, dist_px);
}

fn over(dst_pma: vec4<f32>, src: vec4<f32>, coverage: f32) -> vec4<f32> {
    let a = src.a * coverage;
    let src_pma = vec4<f32>(src.rgb * a, a);
    return src_pma + dst_pma * (1.0 - a);
}

@fragment
fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
    let ndc = (pos.xy / u.viewport) * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0);
    let world4 = u.world_from_clip * vec4<f32>(ndc, 0.0, 1.0);
    let world = world4.xy / world4.w;

    var acc = vec4<f32>(0.0, 0.0, 0.0, 0.0);

    let half_line = u.line_width_px * 0.5;
    let dx_minor = line_distance_world(world.x, u.minor_spacing) * u.pixels_per_mm;
    let dy_minor = line_distance_world(world.y, u.minor_spacing) * u.pixels_per_mm;
    let minor_cov = max(line_alpha(dx_minor, half_line), line_alpha(dy_minor, half_line));
    acc = over(acc, u.minor, minor_cov);

    let major_spacing = u.minor_spacing * u.major_every;
    let dx_major = line_distance_world(world.x, major_spacing) * u.pixels_per_mm;
    let dy_major = line_distance_world(world.y, major_spacing) * u.pixels_per_mm;
    let major_cov = max(line_alpha(dx_major, half_line), line_alpha(dy_major, half_line));
    acc = over(acc, u.major, major_cov);

    let axis_half = u.axis_width_px * 0.5;
    let ax_y_cov = line_alpha(abs(world.x) * u.pixels_per_mm, axis_half);
    acc = over(acc, u.axis_y, ax_y_cov);
    let ax_x_cov = line_alpha(abs(world.y) * u.pixels_per_mm, axis_half);
    acc = over(acc, u.axis_x, ax_x_cov);

    let d_origin_px = length(world) * u.pixels_per_mm;
    let origin_cov = 1.0 - smoothstep(u.origin_radius_px - 1.0, u.origin_radius_px + 1.0, d_origin_px);
    acc = over(acc, u.origin, origin_cov);

    return acc;
}
