bevy_elements 0.5.0

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
// Procedural terrain generator: one thread per cell. See docs/research/terrain.md ("Procedural
// generator"). The CPU transliteration is `model/mirror.rs`; keep them in step.
//
// With p = local_xz / F and octave k sampling perlin3(R_k p 2^k + v_k, 0.5 + k) (R_k the golden
// angle rotation, v_k a seeded whole-cell offset), two sums are formed:
//   f = Σ g^k n_k / Σ g^k                          (fBm)
//   r = Σ g^k s_k / Σ g^k, s_k = w_k (1 − |n_k|)²  (ridged multifractal, w_0 = 1, w_k = clamp(2 s_{k−1}, 0, 1))
// and the surface is  base + R · ((1 − q) u_f + q u_r)  with u_f, u_r the unit-range maps of f, r.

import bevy_elements::noise::perlin3;

struct GenParams {
    // x, y: cells per axis. z: octave count. w: the seed's contribution to the octave offsets.
    size: vec4<u32>,
    // x: cell edge (m). y: feature size F (m). z: relief R (m). w: ridged mix q.
    shape: vec4<f32>,
    // x: base height (m). y: initial sand depth (m).
    base: vec4<f32>,
    // x: fBm half range. y: ridged low end. z: ridged span. w: gain.
    consts: vec4<f32>,
    // x: lacunarity. y: octave rotation (rad).
    octave: vec4<f32>,
}

@group(0) @binding(0) var<uniform> params: GenParams;
@group(0) @binding(1) var<storage, read_write> bedrock: array<f32>;
@group(0) @binding(2) var<storage, read_write> sediment: array<f32>;

@compute @workgroup_size(8, 8, 1)
fn terrain_generate(@builtin(global_invocation_id) id: vec3<u32>) {
    if id.x >= params.size.x || id.y >= params.size.y {
        return;
    }
    let local = (vec2<f32>(id.xy) + 0.5) * params.shape.x;
    let p = local / params.shape.y;
    var f_sum = 0.0;
    var r_sum = 0.0;
    var norm = 0.0;
    var gain = 1.0;
    var weight = 1.0;
    for (var k = 0u; k < params.size.z; k += 1u) {
        let a = params.octave.y * f32(k);
        let s = sin(a);
        let c = cos(a);
        let q = vec2<f32>(c * p.x - s * p.y, s * p.x + c * p.y) * pow(params.octave.x, f32(k));
        let v = f32((params.size.w + k * 1031u) % 4096u);
        let n = perlin3(vec3<f32>(q + vec2<f32>(v), 0.5 + f32(k)));
        let ridge = max(1.0 - abs(n), 0.0);
        let s_k = weight * ridge * ridge;
        f_sum += gain * n;
        r_sum += gain * s_k;
        norm += gain;
        weight = clamp(2.0 * s_k, 0.0, 1.0);
        gain *= params.consts.w;
    }
    let f = f_sum / norm;
    let r = r_sum / norm;
    let u_f = clamp(0.5 + f / (2.0 * params.consts.x), 0.0, 1.0);
    let u_r = clamp((r - params.consts.y) / params.consts.z, 0.0, 1.0);
    let q_mix = params.shape.w;
    let unit = (1.0 - q_mix) * u_f + q_mix * u_r;
    let sand = params.base.y;
    let i = id.y * params.size.x + id.x;
    // The surface is the generated field; the sand rests on bedrock lowered by its depth.
    bedrock[i] = params.base.x + params.shape.z * unit - sand;
    sediment[i] = sand;
}