facett-core 0.1.7

facett — visual kernel: render a node/edge Scene into egui (wgpu fast path to come)
Documentation
// GPU frustum-cull + LOD filter + stream-compaction.
//
// One invocation per way. Visible ways' vertices are claimed atomically
// from compact[] and copied from geom[]. The caller resets draw_count to 0
// (by writing [0, 1, 0, 0] to the indirect_buf) before dispatching.
//
// Vertex layout: 6 × u32 = 24 bytes (pos.xy as f32 bits, col.rgba as f32 bits).
// Using raw u32 arrays avoids WGSL struct-alignment padding (vec4 needs align 16).

struct WayMeta {
    bbox_min:   vec2<f32>,  // offset  0
    bbox_max:   vec2<f32>,  // offset  8
    vert_start: u32,        // offset 16 — index into geom[]
    vert_count: u32,        // offset 20
    lod:        u32,        // offset 24  (0=country 1=region 2=city)
    _pad:       u32,        // offset 28
}                           // stride  32

struct ViewportCull {
    min:  vec2<f32>,   // offset  0
    max:  vec2<f32>,   // offset  8
    lod:  u32,         // offset 16  — show ways with lod <= this
    _p0:  u32,         // offset 20
    _p1:  u32,         // offset 24
    _p2:  u32,         // offset 28
}                      // size    32

@group(0) @binding(0) var<storage, read>       way_meta:   array<WayMeta>;
@group(0) @binding(1) var<storage, read>       geom:       array<u32>;
@group(0) @binding(2) var<storage, read_write> compact:    array<u32>;
@group(0) @binding(3) var<storage, read_write> draw_count: atomic<u32>;
@group(0) @binding(4) var<uniform>             vp:         ViewportCull;

const VPV: u32 = 6u; // u32 words per vertex

@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
    let i = id.x;
    if i >= arrayLength(&way_meta) { return; }

    let m = way_meta[i];

    // Frustum cull — reject if bbox completely outside viewport
    if m.bbox_max.x < vp.min.x || m.bbox_min.x > vp.max.x { return; }
    if m.bbox_max.y < vp.min.y || m.bbox_min.y > vp.max.y { return; }

    // Cumulative LOD: country ways visible at all zoom levels, city only at high zoom
    if m.lod > vp.lod { return; }

    // Atomically claim vert_count vertex slots in compact[]
    let dst   = atomicAdd(&draw_count, m.vert_count);
    let cap   = arrayLength(&compact) / VPV;
    if dst + m.vert_count > cap { return; }

    // Copy 24-byte vertices word-by-word (avoids struct-alignment issues)
    for (var v = 0u; v < m.vert_count; v++) {
        let s = (m.vert_start + v) * VPV;
        let d = (dst           + v) * VPV;
        compact[d    ] = geom[s    ];
        compact[d + 1] = geom[s + 1];
        compact[d + 2] = geom[s + 2];
        compact[d + 3] = geom[s + 3];
        compact[d + 4] = geom[s + 4];
        compact[d + 5] = geom[s + 5];
    }
}