viewport-lib 0.14.0

3D viewport rendering library
Documentation
// polyline_outline_mask.wgsl : renders polyline segments as screen-space quads
// into the R8 mask texture.  Replicates the geometry expansion from polyline.wgsl
// but outputs white rather than interpolated colour.  No clip plane testing.
//
// Group 0: camera_bind_group_layout (binding 0 only).
// Group 1: polyline_bgl (binding 0 only; texture and sampler bindings unused).
//
// Instance input: same 112-byte per-segment format as polyline.wgsl.

struct Camera {
    view_proj: mat4x4<f32>,
    eye_pos:   vec3<f32>,
    _pad:      f32,
};

struct PolylineUniform {
    default_colour:   vec4<f32>,
    line_width:      f32,
    scalar_min:      f32,
    scalar_max:      f32,
    has_scalar:      u32,
    viewport_width:  f32,
    viewport_height: f32,
    _pad:            vec2<f32>,
};

@group(0) @binding(0) var<uniform> camera:     Camera;
@group(1) @binding(0) var<uniform> pl_uniform: PolylineUniform;

struct SegmentIn {
    @location(0)  pos_a:            vec3<f32>,
    @location(1)  pos_b:            vec3<f32>,
    @location(2)  prev_pos:         vec3<f32>,
    @location(3)  next_pos:         vec3<f32>,
    @location(4)  scalar_a:         f32,
    @location(5)  scalar_b:         f32,
    @location(6)  has_prev:         u32,
    @location(7)  has_next:         u32,
    @location(8)  colour_a:          vec4<f32>,
    @location(9)  colour_b:          vec4<f32>,
    @location(10) radius_a:         f32,
    @location(11) radius_b:         f32,
    @location(12) use_direct_colour: u32,
};

fn to_screen(p: vec3<f32>) -> vec2<f32> {
    let clip = camera.view_proj * vec4<f32>(p, 1.0);
    let w = max(clip.w, 0.0001f);
    let ndc = clip.xy / w;
    return ndc * vec2<f32>(pl_uniform.viewport_width * 0.5f,
                           pl_uniform.viewport_height * 0.5f);
}

fn miter_extrusion(dir_in: vec2<f32>, dir_out: vec2<f32>) -> vec2<f32> {
    let perp_in  = vec2<f32>(-dir_in.y,  dir_in.x);
    let perp_out = vec2<f32>(-dir_out.y, dir_out.x);
    let bisect   = normalize(perp_in + perp_out);
    let cos_half = max(dot(bisect, perp_out), 0.25f);
    return bisect / cos_half;
}

@vertex
fn vs_main(
    @builtin(vertex_index) vid: u32,
    seg: SegmentIn,
) -> @builtin(position) vec4<f32> {
    let use_b     = (vid == 1u || vid == 3u || vid == 4u);
    let use_right = (vid == 2u || vid == 4u || vid == 5u);
    let pos  = select(seg.pos_a, seg.pos_b, use_b);
    let side = select(-1.0f, 1.0f, use_right);

    let screen_prev = to_screen(seg.prev_pos);
    let screen_a    = to_screen(seg.pos_a);
    let screen_b    = to_screen(seg.pos_b);
    let screen_next = to_screen(seg.next_pos);

    let ab_vec = screen_b - screen_a;
    let ab_len = length(ab_vec);
    let dir_ab = select(vec2<f32>(1.0f, 0.0f), ab_vec / ab_len, ab_len > 0.001f);

    var extrusion_a: vec2<f32>;
    if seg.has_prev != 0u {
        let pa_vec = screen_a - screen_prev;
        let pa_len = length(pa_vec);
        let dir_pa = select(dir_ab, pa_vec / pa_len, pa_len > 0.001f);
        extrusion_a = miter_extrusion(dir_pa, dir_ab);
    } else {
        extrusion_a = vec2<f32>(-dir_ab.y, dir_ab.x);
    }

    var extrusion_b: vec2<f32>;
    if seg.has_next != 0u {
        let bn_vec = screen_next - screen_b;
        let bn_len = length(bn_vec);
        let dir_bn = select(dir_ab, bn_vec / bn_len, bn_len > 0.001f);
        extrusion_b = miter_extrusion(dir_ab, dir_bn);
    } else {
        extrusion_b = vec2<f32>(-dir_ab.y, dir_ab.x);
    }

    let extrusion = select(extrusion_a, extrusion_b, use_b);
    var clip_pos = camera.view_proj * vec4<f32>(pos, 1.0f);
    let radius = select(seg.radius_a, seg.radius_b, use_b);

    let half_w = radius * 0.5f;
    let ndc_offset = side * half_w * extrusion
        * vec2<f32>(2.0f / pl_uniform.viewport_width, 2.0f / pl_uniform.viewport_height);
    clip_pos.x += ndc_offset.x * clip_pos.w;
    clip_pos.y += ndc_offset.y * clip_pos.w;

    return clip_pos;
}

@fragment
fn fs_main() -> @location(0) vec4<f32> {
    return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}