bevy_elements 0.7.5

Living environments for Bevy: sun, moon and stars, climate-driven weather, GPU wind, volumetric clouds, rain, snow and fog, lightning, terrain with hydraulic and thermal erosion, flooding, snow cover, wetness and dust — simulated in compute shaders.
Documentation
// shelter_mask: which ground is sheltered from rain and snow by a PrecipitationBlocker. A ground
// point is sheltered when the ray from it toward where the precipitation came from, (−wind,
// fall speed), meets a blocker box (docs/research/wetness.md, "Shelter"; the slab test of the
// particle kernel). Runs when the blocker set or the fall directions change, not every step.
//
//   shelter[cell] = (rain exposure, snow exposure), 0 sheltered, 1 exposed

import bevy_elements::cover::common::CoverParams;

struct Blocker {
    // Rows of the inverse of the box's affine matrix: local = rot · (world − centre).
    rot: array<vec4<f32>, 3>,
    centre: vec3<f32>,
    extent: vec3<f32>,
}

@group(0) @binding(0) var<uniform> params: CoverParams;
@group(0) @binding(1) var<storage, read> blockers: array<Blocker>;
@group(0) @binding(2) var<storage, read_write> cells: array<vec4<f32>>;
@group(0) @binding(3) var<storage, read_write> shelter: array<vec2<f32>>;

// How high above the ground a blocker still shelters it, m.
const SHELTER_HEIGHT_M: f32 = 500.0;

fn segment_hits(b: Blocker, p0: vec3<f32>, p1: vec3<f32>) -> bool {
    let d0 = p0 - b.centre;
    let d1 = p1 - b.centre;
    let q0 = vec3<f32>(dot(b.rot[0].xyz, d0), dot(b.rot[1].xyz, d0), dot(b.rot[2].xyz, d0));
    let q1 = vec3<f32>(dot(b.rot[0].xyz, d1), dot(b.rot[1].xyz, d1), dot(b.rot[2].xyz, d1));
    let d = q1 - q0;
    var t_in = 0.0;
    var t_out = 1.0;
    for (var a = 0; a < 3; a += 1) {
        if abs(d[a]) < 1e-7 {
            if abs(q0[a]) > b.extent[a] {
                return false;
            }
        } else {
            let inv = 1.0 / d[a];
            let t_a = (-b.extent[a] - q0[a]) * inv;
            let t_b = (b.extent[a] - q0[a]) * inv;
            t_in = max(t_in, min(t_a, t_b));
            t_out = min(t_out, max(t_a, t_b));
        }
    }
    return t_in <= t_out;
}

// The segment from `p0` up the ray (dx, v_f, dz) to SHELTER_HEIGHT_M.
fn sheltered(p0: vec3<f32>, ray: vec4<f32>) -> bool {
    let p1 = p0 + vec3<f32>(ray.x, ray.w, ray.y) * (SHELTER_HEIGHT_M / ray.w);
    for (var i = 0u; i < params.flags.x; i += 1u) {
        if segment_hits(blockers[i], p0, p1) {
            return true;
        }
    }
    return false;
}

@compute @workgroup_size(8, 8, 1)
fn shelter_mask(@builtin(global_invocation_id) id: vec3<u32>) {
    let size = params.grid.size;
    if id.x >= size.x || id.y >= size.y {
        return;
    }
    let k = id.y * size.x + id.x;
    let centre = params.grid.origin + (vec2<f32>(id.xy) + 0.5) * params.grid.cell;
    let p0 = vec3<f32>(centre.x, params.terrain.y + cells[k].x, centre.y);
    shelter[k] = vec2<f32>(
        select(1.0, 0.0, sheltered(p0, params.rain)),
        select(1.0, 0.0, sheltered(p0, params.snow_ray)),
    );
}