roxlap-gpu 0.30.0

GPU compute-shader renderer for the roxlap voxel engine (WGPU + WGSL DDA marcher). Sibling to roxlap-core's CPU DDA renderer.
Documentation
// XS.4.3 — sprite-cast shadow march for the scene pass (sprites CAST onto
// terrain). SNIPPET, not a standalone module: the renderer splices it into
// `scene_dda.wgsl` (replacing the `sprites_occlude` stub) only on
// sprite-shadow-capable devices, so it relies on that shader's `u` uniform and
// `shield_parallel`. It binds the sprite registry's instances / model meta /
// occupancy (bindings 19..21 — the scene pass reaches 19 storage buffers) and
// duplicates the sprite occupancy reader from sprite_model_dda.wgsl. The march
// loops the visible sprite instances (`u.sprite_cast_count`) and reports the
// first solid sprite voxel blocking a world-space shadow ray within `max_t`.
struct SpriteModelMetaS {
    occupancy_offset: u32,
    colors_offset: u32,
    color_offsets_offset: u32,
    occ_words_per_col: u32,
    dims: vec3<u32>,
    has_vox_materials: u32,
    pivot: vec3<f32>,
    voxel_world_size: f32,
};
struct SpriteInstanceS {
    inv_rot0: vec4<f32>,
    inv_rot1: vec4<f32>,
    inv_rot2: vec4<f32>,
    pos: vec3<f32>,
    model_id: u32,
    material: u32,
    alpha_mul: f32,
    flags: u32,
    _pad1: u32,
};
@group(0) @binding(19) var<storage, read> sprite_instances: array<SpriteInstanceS>;
@group(0) @binding(20) var<storage, read> sprite_models: array<SpriteModelMetaS>;
@group(0) @binding(21) var<storage, read> sprite_occupancy: array<u32>;

fn sprite_model_solid(m: SpriteModelMetaS, p: vec3<i32>) -> bool {
    let col = u32(p.x) + u32(p.y) * m.dims.x;
    let base = m.occupancy_offset + col * m.occ_words_per_col;
    let zw = u32(p.z) >> 5u;
    let zb = u32(p.z) & 31u;
    return (sprite_occupancy[base + zw] & (1u << zb)) != 0u;
}

// True if any casting sprite blocks the world-space ray within `max_t`.
fn sprites_occlude(origin_w: vec3<f32>, dir_w: vec3<f32>, max_t: f32) -> bool {
    for (var i: u32 = 0u; i < u.sprite_cast_count; i = i + 1u) {
        let inst = sprite_instances[i];
        // bit4 = NO_SHADOW_CAST → this sprite doesn't cast.
        if ((inst.flags & 16u) != 0u) { continue; }
        let m = sprite_models[inst.model_id];
        let inv = mat3x3<f32>(inst.inv_rot0.xyz, inst.inv_rot1.xyz, inst.inv_rot2.xyz);
        let s = m.voxel_world_size;
        // World → model-local. `d` includes the 1/s so the ray parameter `t`
        // stays in world units (== the caller's `max_t`).
        let o = inv * (origin_w - inst.pos) / s + m.pivot;
        let d = inv * dir_w / s;
        let box_max = vec3<f32>(f32(m.dims.x), f32(m.dims.y), f32(m.dims.z));
        let inv_d = 1.0 / d;
        let t0 = (vec3<f32>(0.0) - o) * inv_d;
        let t1 = (box_max - o) * inv_d;
        let tlo = min(t0, t1);
        let thi = max(t0, t1);
        let t_enter = max(max(tlo.x, tlo.y), max(tlo.z, 0.0));
        let t_exit = min(thi.x, min(thi.y, thi.z));
        if (t_exit < t_enter || t_enter >= max_t) { continue; }
        let entry = o + t_enter * d;
        let dim_i = vec3<i32>(i32(m.dims.x), i32(m.dims.y), i32(m.dims.z));
        var p = clamp(vec3<i32>(floor(entry)), vec3<i32>(0), dim_i - vec3<i32>(1));
        let step = vec3<i32>(sign(d));
        let t_delta = abs(inv_d);
        let next_b = vec3<f32>(
            select(f32(p.x), f32(p.x + 1), step.x > 0),
            select(f32(p.y), f32(p.y + 1), step.y > 0),
            select(f32(p.z), f32(p.z + 1), step.z > 0),
        );
        var t_max = shield_parallel((next_b - o) * inv_d, d);
        var t_hit = t_enter;
        let max_steps = m.dims.x + m.dims.y + m.dims.z + 3u;
        for (var k: u32 = 0u; k < max_steps; k = k + 1u) {
            if (t_hit >= max_t) { break; }
            if (sprite_model_solid(m, p)) { return true; }
            if (t_max.x < t_max.y && t_max.x < t_max.z) {
                t_hit = t_max.x; p.x = p.x + step.x; t_max.x = t_max.x + t_delta.x;
                if (p.x < 0 || p.x >= dim_i.x) { break; }
            } else if (t_max.y < t_max.z) {
                t_hit = t_max.y; p.y = p.y + step.y; t_max.y = t_max.y + t_delta.y;
                if (p.y < 0 || p.y >= dim_i.y) { break; }
            } else {
                t_hit = t_max.z; p.z = p.z + step.z; t_max.z = t_max.z + t_delta.z;
                if (p.z < 0 || p.z >= dim_i.z) { break; }
            }
        }
    }
    return false;
}