struct Uniforms
{
    view_proj:    mat4x4<f32>,
    size:         vec2<f32>,
    body_frac:    f32,
    corner_r:     f32,
    border_w:     f32,
    fill_color:   vec4<f32>,
    border_color: vec4<f32>,
}

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

struct VertexIn
{
    @location(0) position: vec3<f32>,
    @location(1) uv:       vec2<f32>,
}

struct VertexOut
{
    @builtin(position) clip_pos: vec4<f32>,
    @location(0)       uv:       vec2<f32>,
}

@vertex
fn vs_main(in: VertexIn) -> VertexOut
{
    var out: VertexOut;
    out.clip_pos = u.view_proj * vec4<f32>(in.position, 1.0);
    out.uv       = in.uv;
    return out;
}

fn sdf_rounded_box(p: vec2<f32>, b: vec2<f32>, r: f32) -> f32
{
    let q = abs(p) - b + r;
    return length(max(q, vec2<f32>(0.0))) + min(max(q.x, q.y), 0.0) - r;
}

fn sdf_triangle(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>, c: vec2<f32>) -> f32
{
    let e0  = b - a;
    let e1  = c - b;
    let e2  = a - c;
    let v0  = p - a;
    let v1  = p - b;
    let v2  = p - c;
    let pq0 = v0 - e0 * clamp(dot(v0, e0) / dot(e0, e0), 0.0, 1.0);
    let pq1 = v1 - e1 * clamp(dot(v1, e1) / dot(e1, e1), 0.0, 1.0);
    let pq2 = v2 - e2 * clamp(dot(v2, e2) / dot(e2, e2), 0.0, 1.0);
    let s   = sign(e0.x * e2.y - e0.y * e2.x);
    let d   = min(
        min(
            vec2<f32>(dot(pq0, pq0), s * (v0.x * e0.y - v0.y * e0.x)),
            vec2<f32>(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x)),
        ),
        vec2<f32>(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x)),
    );
    return -sqrt(d.x) * sign(d.y);
}

var<private> bayer: array<f32, 16> = array<f32, 16>(
     0.0,  8.0,  2.0, 10.0,
    12.0,  4.0, 14.0,  6.0,
     3.0, 11.0,  1.0,  9.0,
    15.0,  7.0, 13.0,  5.0,
);

@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32>
{
    let body_half_w = u.size.x * 0.5;
    let body_half_h = u.size.y * u.body_frac * 0.5;

    // World-unit coords centred at body centre (y+ = up in billboard local space).
    let p = vec2<f32>(
        (in.uv.x - 0.5) * u.size.x,
        (u.body_frac * 0.5 - in.uv.y) * u.size.y,
    );

    // Body rounded rect.
    let body_half = vec2<f32>(body_half_w, body_half_h);
    let corner_r  = u.corner_r * min(body_half_w, body_half_h);
    let d_body    = sdf_rounded_box(p, body_half, corner_r);

    // Tail triangle pointing downward (toward character).
    let tail_height    = u.size.y * (1.0 - u.body_frac);
    let tail_base_half = body_half_w * 0.15;
    let ta             = vec2<f32>(-tail_base_half, -body_half_h);
    let tb             = vec2<f32>( tail_base_half, -body_half_h);
    let tc             = vec2<f32>(0.0, -(body_half_h + tail_height));
    let d_tail         = sdf_triangle(p, ta, tb, tc);

    let d_shape = min(d_body, d_tail);

    // Alpha with a tiny anti-alias band, then Bayer threshold.
    let aa     = 0.01 * min(body_half_w, body_half_h);
    let alpha  = 1.0 - smoothstep(-aa, aa, d_shape);
    let fx     = u32(in.clip_pos.x) % 4u;
    let fy     = u32(in.clip_pos.y) % 4u;
    let thresh = bayer[fy * 4u + fx] / 16.0;
    if alpha <= thresh
    {
        discard;
    }

    // Border ring on body only.
    let border_w = u.border_w * min(body_half_w, body_half_h);
    let inner_r  = max(corner_r - border_w, 0.0);
    let d_inner  = sdf_rounded_box(p, body_half - border_w, inner_r);
    if d_body <= 0.0 && d_inner > 0.0
    {
        return u.border_color;
    }
    return u.fill_color;
}
