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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
// bevy_elements::noise — hashing and procedural noise shared by every domain.
//
// A supported import for consumers:
//     import bevy_elements::noise::{perlin3, perlin3_d, fbm3, worley3, curl3};
// The lattice is periodic with NOISE_PERIOD cells on every axis, so callers can wrap their
// coordinates (and drifting offsets) modulo NOISE_PERIOD without a seam and keep f32 precision.
// Deterministic on one GPU; not bit-identical across vendors. `src/gpu/noise_mirror.rs` is a
// CPU transliteration used by the tests.

/// Lattice period of every noise in this module, cells.
const NOISE_PERIOD: i32 = 4096;

/// How far a cell network's feature point is drawn *towards* its cell's centre, 0 to 1.
///
/// A cell network's cells are the same size when the feature points sit on a lattice, and a
/// face of equal cells is a mosaic. The point's offset inside its cell is the only place the
/// cells can differ in size: a point near its cell's corner leaves a big cell behind it, and
/// a point at the centre a small one. An inset of 0 leaves the point anywhere in the cell and
/// the cells' sizes vary the most; an inset of 1 puts every point at the centre and every
/// cell is the same size.
///
/// The rock's blocks are the cells of a 3D network (`docs/research/rock-fragments.md`), and a
/// cliff's blocks are not all the same size: the fracture spacing follows a Weibull whose
/// geometric standard deviation is about 0.31 (Verdian et al. 2021).
const CELL_INSET: f32 = 0.0;

/// PCG3D hash (Jarzynski & Olano 2020, "Hash Functions for GPU Rendering").
fn pcg3d(v_in: vec3<u32>) -> vec3<u32> {
    var v = v_in * 1664525u + 1013904223u;
    v.x += v.y * v.z;
    v.y += v.z * v.x;
    v.z += v.x * v.y;
    v ^= v >> vec3<u32>(16u);
    v.x += v.y * v.z;
    v.y += v.z * v.x;
    v.z += v.x * v.y;
    return v;
}

/// Hash of an integer lattice point (wrapped to the period) to three floats in [0, 1).
fn hash33(p: vec3<i32>) -> vec3<f32> {
    let w = p & vec3<i32>(NOISE_PERIOD - 1);
    return vec3<f32>(pcg3d(bitcast<vec3<u32>>(w)) >> vec3<u32>(8u)) * (1.0 / 16777216.0);
}

/// Hash of an integer lattice point to one float in [0, 1).
fn hash31(p: vec3<i32>) -> f32 {
    return hash33(p).x;
}

fn lattice_gradient(cell: vec3<i32>) -> vec3<f32> {
    return normalize(hash33(cell) * 2.0 - 1.0 + vec3<f32>(1e-6));
}

/// Gradient (Perlin) noise with its analytic derivative: `.x` the value, roughly in [-1, 1],
/// `.yzw` = ∂/∂(x, y, z) per lattice unit. Quintic interpolation (Quílez's closed form), so the
/// derivative is exact and needs no finite differences.
fn perlin3_d(p: vec3<f32>) -> vec4<f32> {
    let i = vec3<i32>(floor(p));
    let f = p - floor(p);
    let u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
    let du = 30.0 * f * f * (f * (f - 2.0) + 1.0);

    let ga = lattice_gradient(i);
    let gb = lattice_gradient(i + vec3<i32>(1, 0, 0));
    let gc = lattice_gradient(i + vec3<i32>(0, 1, 0));
    let gd = lattice_gradient(i + vec3<i32>(1, 1, 0));
    let ge = lattice_gradient(i + vec3<i32>(0, 0, 1));
    let gf = lattice_gradient(i + vec3<i32>(1, 0, 1));
    let gg = lattice_gradient(i + vec3<i32>(0, 1, 1));
    let gh = lattice_gradient(i + vec3<i32>(1, 1, 1));

    let va = dot(ga, f);
    let vb = dot(gb, f - vec3<f32>(1.0, 0.0, 0.0));
    let vc = dot(gc, f - vec3<f32>(0.0, 1.0, 0.0));
    let vd = dot(gd, f - vec3<f32>(1.0, 1.0, 0.0));
    let ve = dot(ge, f - vec3<f32>(0.0, 0.0, 1.0));
    let vf = dot(gf, f - vec3<f32>(1.0, 0.0, 1.0));
    let vg = dot(gg, f - vec3<f32>(0.0, 1.0, 1.0));
    let vh = dot(gh, f - vec3<f32>(1.0, 1.0, 1.0));

    let k1 = vb - va;
    let k2 = vc - va;
    let k3 = ve - va;
    let k4 = va - vb - vc + vd;
    let k5 = va - vc - ve + vg;
    let k6 = va - vb - ve + vf;
    let k7 = -va + vb + vc - vd + ve - vf - vg + vh;

    let value = va + u.x * k1 + u.y * k2 + u.z * k3 + u.x * u.y * k4 + u.y * u.z * k5
        + u.z * u.x * k6 + u.x * u.y * u.z * k7;
    let grad = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.z * (ge - ga)
        + u.x * u.y * (ga - gb - gc + gd) + u.y * u.z * (ga - gc - ge + gg)
        + u.z * u.x * (ga - gb - ge + gf) + u.x * u.y * u.z * (-ga + gb + gc - gd + ge - gf - gg + gh)
        + du * (vec3<f32>(k1, k2, k3)
            + u.yzx * vec3<f32>(k4, k5, k6)
            + u.zxy * vec3<f32>(k6, k4, k5)
            + u.yzx * u.zxy * k7);
    return vec4<f32>(value, grad) * 1.1547;
}

/// Gradient (Perlin) noise, roughly in [-1, 1]: the **value only**.
///
/// The same function as `perlin3_d` with the **derivative omitted**: the derivative is a second
/// large polynomial — eight vector differences, a `du` term and three swizzles — and it is
/// computed for every evaluation whether or not the caller wants it.
///
/// The crate makes **five** evaluations per rock pixel and **four** of them use the value
/// alone: the bed's region, the rock's rind, the second joint set's segment field, the turf's
/// patch field and the scree's grain. So the derivative is computed and thrown away most of
/// the time, on the crate's hottest path (`docs/research/rock-cost.md`).
///
/// The value is **bit-identical** to `perlin3_d(p).x`: the same eight lattice gradients and the
/// same interpolation, with only the derivative's terms left out. So a call site can switch
/// without changing a single pixel.
fn perlin3(p: vec3<f32>) -> f32 {
    let i = vec3<i32>(floor(p));
    let f = p - floor(p);
    let u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);

    let ga = lattice_gradient(i);
    let gb = lattice_gradient(i + vec3<i32>(1, 0, 0));
    let gc = lattice_gradient(i + vec3<i32>(0, 1, 0));
    let gd = lattice_gradient(i + vec3<i32>(1, 1, 0));
    let ge = lattice_gradient(i + vec3<i32>(0, 0, 1));
    let gf = lattice_gradient(i + vec3<i32>(1, 0, 1));
    let gg = lattice_gradient(i + vec3<i32>(0, 1, 1));
    let gh = lattice_gradient(i + vec3<i32>(1, 1, 1));

    let va = dot(ga, f);
    let vb = dot(gb, f - vec3<f32>(1.0, 0.0, 0.0));
    let vc = dot(gc, f - vec3<f32>(0.0, 1.0, 0.0));
    let vd = dot(gd, f - vec3<f32>(1.0, 1.0, 0.0));
    let ve = dot(ge, f - vec3<f32>(0.0, 0.0, 1.0));
    let vf = dot(gf, f - vec3<f32>(1.0, 0.0, 1.0));
    let vg = dot(gg, f - vec3<f32>(0.0, 1.0, 1.0));
    let vh = dot(gh, f - vec3<f32>(1.0, 1.0, 1.0));

    let k1 = vb - va;
    let k2 = vc - va;
    let k3 = ve - va;
    let k4 = va - vb - vc + vd;
    let k5 = va - vc - ve + vg;
    let k6 = va - vb - ve + vf;
    let k7 = -va + vb + vc - vd + ve - vf - vg + vh;

    let value = va + u.x * k1 + u.y * k2 + u.z * k3 + u.x * u.y * k4 + u.y * u.z * k5
        + u.z * u.x * k6 + u.x * u.y * u.z * k7;
    return value * 1.1547;
}

/// Gradient (Perlin) noise on a lattice that wraps every `period` cells on every axis, so the
/// result tiles with period `period` (in lattice units). `period` must be a power of two no
/// larger than NOISE_PERIOD. Roughly in [-1, 1], quintic interpolation, same gradients as
/// `perlin3` (a cell's gradient is `lattice_gradient` of the wrapped cell).
fn perlin3_periodic(p: vec3<f32>, period: i32) -> f32 {
    let i = vec3<i32>(floor(p));
    let f = p - floor(p);
    let u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
    let m = vec3<i32>(period - 1);
    let va = dot(lattice_gradient(i & m), f);
    let vb = dot(lattice_gradient((i + vec3<i32>(1, 0, 0)) & m), f - vec3<f32>(1.0, 0.0, 0.0));
    let vc = dot(lattice_gradient((i + vec3<i32>(0, 1, 0)) & m), f - vec3<f32>(0.0, 1.0, 0.0));
    let vd = dot(lattice_gradient((i + vec3<i32>(1, 1, 0)) & m), f - vec3<f32>(1.0, 1.0, 0.0));
    let ve = dot(lattice_gradient((i + vec3<i32>(0, 0, 1)) & m), f - vec3<f32>(0.0, 0.0, 1.0));
    let vf = dot(lattice_gradient((i + vec3<i32>(1, 0, 1)) & m), f - vec3<f32>(1.0, 0.0, 1.0));
    let vg = dot(lattice_gradient((i + vec3<i32>(0, 1, 1)) & m), f - vec3<f32>(0.0, 1.0, 1.0));
    let vh = dot(lattice_gradient((i + vec3<i32>(1, 1, 1)) & m), f - vec3<f32>(1.0, 1.0, 1.0));
    let x0 = mix(mix(va, vb, u.x), mix(vc, vd, u.x), u.y);
    let x1 = mix(mix(ve, vf, u.x), mix(vg, vh, u.x), u.y);
    return mix(x0, x1, u.z) * 1.1547;
}

/// 2D gradient (Perlin) noise in the XZ plane, with its analytic derivative: `.x` the value in
/// roughly [-1, 1], `.yz` = ∂/∂(x, z) per lattice unit. The gradients are unit vectors on the
/// circle, so the noise is isotropic in the plane.
///
/// The ground's *turf* uses this, and the ground's turf is on the near-horizontal surfaces,
/// where the plane's projection is the surface. It is not used on a slope: a 2D noise in the
/// world XZ plane is the *projection* of the surface onto that plane, so on a slope the plane's
/// lattice is stretched along the slope and the lattice shows through as a weave at a fixed
/// world direction — 43.5 degrees at 2 m and 44.0 degrees at 6 m on a 45 degree face
/// (`docs/research/rock-material.md`, Model 1). The rock's material uses the 3D noise at the 3D
/// point instead, which has no projection at all.
fn perlin2_gradient(c: vec2<i32>) -> vec2<f32> {
    let h = hash33(vec3<i32>(c.x, c.y, 0)).xy * 2.0 - vec2<f32>(1.0);
    return normalize(h + vec2<f32>(1e-6));
}

fn perlin2_d(p: vec2<f32>) -> vec3<f32> {
    let i = vec2<i32>(floor(p));
    let f = p - floor(p);
    let u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
    let du = 30.0 * f * f * (f * (f - 2.0) + 1.0);
    let ga = perlin2_gradient(i);
    let gb = perlin2_gradient(i + vec2<i32>(1, 0));
    let gc = perlin2_gradient(i + vec2<i32>(0, 1));
    let gd = perlin2_gradient(i + vec2<i32>(1, 1));
    let va = dot(ga, f);
    let vb = dot(gb, f - vec2<f32>(1.0, 0.0));
    let vc = dot(gc, f - vec2<f32>(0.0, 1.0));
    let vd = dot(gd, f - vec2<f32>(1.0, 1.0));
    let k1 = vb - va;
    let k2 = vc - va;
    let k3 = va - vb - vc + vd;
    let value = va + u.x * k1 + u.y * k2 + u.x * u.y * k3;
    let grad = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.x * u.y * (ga - gb - gc + gd)
        + du * (vec2<f32>(k1, k2) + vec2<f32>(u.y, u.x) * k3);
    return vec3<f32>(value, grad) * 1.4142;
}

/// Fractal sum of `octaves` periodic Perlin octaves (lacunarity 2, gain 0.5) that tiles with
/// `period` lattice units at the base octave, divided by the sum of the amplitudes. Octave `o`
/// uses lattice period `period · 2^o` and its own whole-cell hash offset.
fn fbm3_periodic(p: vec3<f32>, period: i32, octaves: u32) -> f32 {
    var sum = 0.0;
    var amplitude = 0.5;
    var norm = 0.0;
    var q = p;
    var n = period;
    for (var o = 0u; o < octaves; o += 1u) {
        sum += amplitude * perlin3_periodic(q + seed_offset(o + 1u), n);
        norm += amplitude;
        amplitude *= 0.5;
        q = q * 2.0;
        n = n * 2;
    }
    return sum / max(norm, 1e-6);
}

/// Integer lattice offset that decorrelates octave or component `k` (a different hash seed
/// without moving the lattice).
fn seed_offset(k: u32) -> vec3<f32> {
    return vec3<f32>(f32(k * 1031u % 4096u), f32(k * 2053u % 4096u), f32(k * 3079u % 4096u));
}

/// Fractal sum of `octaves` Perlin octaves (lacunarity 2, gain 0.5, a separate seed per octave),
/// divided by the sum of the amplitudes.
fn fbm3(p: vec3<f32>, octaves: u32) -> f32 {
    var sum = 0.0;
    var amplitude = 0.5;
    var norm = 0.0;
    var q = p;
    for (var o = 0u; o < octaves; o += 1u) {
        sum += amplitude * perlin3(q + seed_offset(o + 1u));
        norm += amplitude;
        amplitude *= 0.5;
        q = q * 2.0;
    }
    return sum / max(norm, 1e-6);
}

/// Worley (cellular) noise: distance to the nearest feature point, roughly in [0, 1].
/// `period` > 0 wraps the lattice so the result tiles with that period.
fn worley3(p: vec3<f32>, period: i32) -> f32 {
    let i = vec3<i32>(floor(p));
    let f = p - floor(p);
    var best = 1e9;
    for (var z = -1; z <= 1; z += 1) {
        for (var y = -1; y <= 1; y += 1) {
            for (var x = -1; x <= 1; x += 1) {
                let o = vec3<i32>(x, y, z);
                var c = i + o;
                if period > 0 {
                    c = ((c % period) + period) % period;
                }
                let d = vec3<f32>(o) + hash33(c) - f;
                best = min(best, dot(d, d));
            }
        }
    }
    return sqrt(best);
}

/// A cell of the 2D cellular network: where its feature point is, how far the point is, and the
/// cell's own random factor in [0, 1).
struct Cell2 {
    /// The distance to the feature point, in lattice units.
    distance: f32,
    /// The feature point's position, in lattice units.
    point: vec2<f32>,
    /// The cell's own random factor, in [0, 1): a per-cell draw, used for the cell's size.
    factor: f32,
}

/// The 2D cellular (Worley) network: the *nearest* feature point, its distance and its cell's
/// random factor.
///
/// The nearest point's *distance* is a field whose gradient points away from the point, so the
/// field is a dome over each point: the crate's scree uses that to shade each stone
/// (`docs/research/scree.md`). The cell's random factor is a per-cell draw, which is how one
/// network carries stones of *many* sizes: the cell's size is its own size times the factor.
///
/// The lattice is periodic with [`NOISE_PERIOD`] cells on both axes, so the caller can wrap the
/// coordinates modulo the period without a seam.
fn cell2(p: vec2<f32>) -> Cell2 {
    let i = vec2<i32>(floor(p));
    let f = p - floor(p);
    var best = 1e9;
    var best_point = vec2<f32>(0.0);
    var best_factor = 0.0;
    for (var y = -1; y <= 1; y += 1) {
        for (var x = -1; x <= 1; x += 1) {
            let o = vec2<i32>(x, y);
            let c = (i + o) & vec2<i32>(NOISE_PERIOD - 1);
            let h = hash33(vec3<i32>(c.x, c.y, 0));
            // The point's position inside its cell, and its factor, from one hash: the point's
            // third component is the factor, so the point does not sit on the cell's centre.
            let point = vec2<f32>(o) + h.xy;
            let d = dot(point - f, point - f);
            if d < best {
                best = d;
                best_point = point;
                best_factor = h.z;
            }
        }
    }
    return Cell2(sqrt(best), best_point, best_factor);
}

/// A cell of the 3D cellular network: where its feature point is, how far the point is, and
/// the cell's own random factor in [0, 1).
struct Cell3 {
    /// The distance to the feature point, in lattice units.
    distance: f32,
    /// The feature point's position, in lattice units.
    point: vec3<f32>,
    /// The cell's own random factor, in [0, 1): a per-cell draw, used for the cell's tone.
    factor: f32,
    /// A **second** per-cell draw, in [0, 1): an independent hash of the cell, for the rock's
    /// palette. The palette wants two draws — the *rock* a fragment is and the *region* it
    /// belongs to — and one hash gives one draw, so the second is a second hash of the same
    /// cell. It is a *separate* hash and not a component of the first, so the two draws are
    /// independent.
    factor2: f32,
}

/// The 3D cellular (Worley) network: the *nearest* feature point, its distance and the cell's
/// random factor.
///
/// The rock's own 3D frame, so a block stays put on a slope: a 2D network in the ground plane
/// would stretch along the slope and weave, exactly as the crate's turf does
/// (`docs/research/rock-material.md` Model 1).
///
/// The lookup is **27 cells**, each a hash, so it is expensive — the crate's rock uses it only
/// where the rock shows, and the rock's material is the crate's hot path. The cells are pruned
/// by the distance to their nearest corner, which is an exact bound, so the result is the same
/// as the full search.
fn cell3(p: vec3<f32>) -> Cell3 {
    let i = vec3<i32>(floor(p));
    let f = p - floor(p);
    var best = 1e9;
    var best_point = vec3<f32>(0.0);
    var best_factor = 0.0;
    var best_factor2 = 0.0;
    // The cells are visited in order of increasing distance — the point's own cell first, then
    // the ring around it, then the ring around that — so the prune's bound tightens quickly.
    // In the natural order the bound is loose and about fourteen of the twenty-seven cells
    // are hashed; in this order it is about seven. The bound is exact, so the result is the
    // same either way, and the crate's rock is its hottest path.
    for (var ring = 0; ring < 3; ring += 1) {
      for (var z = -1; z <= 1; z += 1) {
        for (var y = -1; y <= 1; y += 1) {
            for (var x = -1; x <= 1; x += 1) {
                // The pass is the ring the cell is in, from the point's own cell outwards.
                if max(max(abs(x), abs(y)), abs(z)) != ring {
                    continue;
                }
                let o = vec3<i32>(x, y, z);
                // The cell is pruned by the distance to its *nearest corner*: a feature point
                // lies inside its cell, so if the corner is already further than the best
                // distance so far, the cell cannot hold a closer one and its hash — the
                // expensive part — is not computed at all. The bound is exact, so the result is
                // the same as the full search.
                // The distance from the point to the cell's *far* corner, per axis: the point
                // is at `f` inside its own cell, and the cell spans `[o, o + 1]`. A feature
                // point lies inside its cell, so a cell whose far corner is nearer than the
                // best distance so far cannot hold a closer one, and its hash — the expensive
                // part — is not computed at all. The bound is exact, so the result is the same
                // as the full search.
                //
                // The two distances are the point's offsets from the cell's two ends, and the
                // larger of them is the far one.
                let from_start = vec3<f32>(o) - f;
                let from_end = f - (vec3<f32>(o) + 1.0);
                let lo = max(from_start, from_end);
                if dot(lo, lo) >= best {
                    continue;
                }
                let c = (i + o) & vec3<i32>(NOISE_PERIOD - 1);
                let h = hash33(c);
                // The point's position inside its cell, and its factor, from one hash: the
                // point's third component is the factor, so the point does not sit on the
                // cell's centre.
                //
                // The point's offset is what makes the cells **unequal**, and it is the only
                // place a cell network's cells differ in size: the *cells* of a Voronoi
                // network are the same size when the feature points sit on a lattice, and a
                // face of equal cells is a mosaic. A point that sits near its cell's corner
                // leaves a *big* cell behind it and a point at the centre a small one, so the
                // offset's spread is the blocks' size spread.
                //
                // The offset is the hash's own value, drawn towards the cell's centre by
                // `CELL_INSET`: an inset of 0 leaves the point anywhere in the cell and the
                // cells' sizes vary the most, and an inset of 1 puts every point at the
                // centre and every cell is the same size.
                let inset = mix(h, vec3<f32>(0.5), CELL_INSET);
                let point = vec3<f32>(o) + inset;
                let d = dot(point - f, point - f);
                if d < best {
                    best = d;
                    best_point = point;
                    best_factor = h.z;
                    best_factor2 = hash31(c + vec3<i32>(7919, 104729, 1299709));
                }
            }
        }
      }
    }
    return Cell3(sqrt(best), best_point, best_factor, best_factor2);
}

/// Divergence-free noise (Bridson, Hourihan & Nordenstrm 2007): the curl of three Perlin
/// potentials with separate seeds, from analytic derivatives, per lattice unit. Unnormalised.
fn curl3(p: vec3<f32>) -> vec3<f32> {
    let a = perlin3_d(p + seed_offset(11u));
    let b = perlin3_d(p + seed_offset(12u));
    let c = perlin3_d(p + seed_offset(13u));
    // ψ = (a, b, c): curl = (∂c/∂y − ∂b/∂z, ∂a/∂z − ∂c/∂x, ∂b/∂x − ∂a/∂y)
    return vec3<f32>(c.z - b.w, a.w - c.y, b.y - a.z);
}