veloren_world/layer/
mod.rs

1pub mod cave;
2pub mod rock;
3pub mod scatter;
4pub mod shrub;
5pub mod spot;
6pub mod tree;
7pub mod wildlife;
8
9pub use self::{
10    cave::apply_caves_to, rock::apply_rocks_to, scatter::apply_scatter_to, shrub::apply_shrubs_to,
11    spot::apply_spots_to, tree::apply_trees_to,
12};
13
14use crate::{
15    Canvas, CanvasInfo,
16    column::ColumnSample,
17    config::CONFIG,
18    sim,
19    util::{FastNoise, RandomPerm, Sampler},
20};
21use common::terrain::{Block, BlockKind, SpriteKind};
22use hashbrown::HashMap;
23use noise::NoiseFn;
24use rand::prelude::*;
25use serde::Deserialize;
26use std::{
27    f32,
28    ops::{Add, Mul, Range, Sub},
29};
30use vek::*;
31
32#[derive(Deserialize)]
33pub struct Colors {
34    pub bridge: (u8, u8, u8),
35}
36
37const EMPTY_AIR: Block = Block::empty();
38
39pub struct PathLocals {
40    pub riverless_alt: f32,
41    pub alt: f32,
42    pub water_dist: f32,
43    pub bridge_offset: f32,
44    pub depth: i32,
45}
46
47impl PathLocals {
48    pub fn new(info: &CanvasInfo, col: &ColumnSample, path_nearest: Vec2<f32>) -> PathLocals {
49        // Try to use the column at the centre of the path for sampling to make them
50        // flatter
51        let col_pos = -info.wpos().map(|e| e as f32) + path_nearest;
52        let col00 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 0));
53        let col10 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 0));
54        let col01 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 1));
55        let col11 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 1));
56        let col_attr = |col: &ColumnSample| {
57            Vec3::new(col.riverless_alt, col.alt, col.water_dist.unwrap_or(1000.0))
58        };
59        let [riverless_alt, alt, water_dist] = match (col00, col10, col01, col11) {
60            (Some(col00), Some(col10), Some(col01), Some(col11)) => Lerp::lerp(
61                Lerp::lerp(col_attr(col00), col_attr(col10), path_nearest.x.fract()),
62                Lerp::lerp(col_attr(col01), col_attr(col11), path_nearest.x.fract()),
63                path_nearest.y.fract(),
64            ),
65            _ => col_attr(col),
66        }
67        .into_array();
68        let (bridge_offset, depth) = (
69            ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0,
70            ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs())
71                * (riverless_alt + 5.0 - alt).max(0.0)
72                * 1.75
73                + 3.0) as i32,
74        );
75        PathLocals {
76            riverless_alt,
77            alt,
78            water_dist,
79            bridge_offset,
80            depth,
81        }
82    }
83}
84
85pub fn apply_paths_to(canvas: &mut Canvas) {
86    canvas.foreach_col(|canvas, wpos2d, col| {
87        if let Some((path_dist, path_nearest, path, _)) =
88            col.path.filter(|(dist, _, path, _)| *dist < path.width)
89        {
90            let inset = 0;
91
92            let PathLocals {
93                riverless_alt,
94                alt: _,
95                water_dist: _,
96                bridge_offset: _,
97                depth: _,
98            } = PathLocals::new(&canvas.info(), col, path_nearest);
99
100            let depth = 4;
101            let surface_z = riverless_alt.floor() as i32;
102
103            for z in inset - depth..inset {
104                let wpos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z);
105                let path_color =
106                    path.surface_color(col.sub_surface_color.map(|e| (e * 255.0) as u8), wpos);
107                canvas.set(wpos, Block::new(BlockKind::Earth, path_color));
108            }
109            let head_space = path.head_space(path_dist);
110            for z in inset..inset + head_space {
111                let pos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z);
112                if canvas.get(pos).kind() != BlockKind::Water {
113                    canvas.set(pos, EMPTY_AIR);
114                }
115            }
116        }
117    });
118}
119
120pub fn apply_trains_to(
121    canvas: &mut Canvas,
122    sim: &sim::WorldSim,
123    sim_chunk: &sim::SimChunk,
124    chunk_center_wpos2d: Vec2<i32>,
125) {
126    let mut splines = Vec::new();
127    let g = |v: Vec2<f32>| -> Vec3<f32> {
128        let path_nearest = sim
129            .get_nearest_path(v.as_::<i32>())
130            .map(|x| x.1)
131            .unwrap_or(v.as_::<f32>());
132        let alt = if let Some(c) = canvas.col_or_gen(v.as_::<i32>()) {
133            let pl = PathLocals::new(canvas, &c, path_nearest);
134            pl.riverless_alt + pl.bridge_offset + 0.75
135        } else {
136            sim_chunk.alt
137        };
138        v.with_z(alt)
139    };
140    fn hermite_to_bezier(
141        p0: Vec3<f32>,
142        m0: Vec3<f32>,
143        p3: Vec3<f32>,
144        m3: Vec3<f32>,
145    ) -> CubicBezier3<f32> {
146        let hermite = Vec4::new(p0, p3, m0, m3);
147        let hermite = hermite.map(|v| v.with_w(0.0));
148        let hermite: [[f32; 4]; 4] = hermite.map(|v: Vec4<f32>| v.into_array()).into_array();
149        // https://courses.engr.illinois.edu/cs418/sp2009/notes/12-MoreSplines.pdf
150        let mut m = Mat4::from_row_arrays([
151            [1.0, 0.0, 0.0, 0.0],
152            [0.0, 0.0, 0.0, 1.0],
153            [-3.0, 3.0, 0.0, 0.0],
154            [0.0, 0.0, -3.0, 3.0],
155        ]);
156        m.invert();
157        let bezier = m * Mat4::from_row_arrays(hermite);
158        let bezier: Vec4<Vec4<f32>> =
159            Vec4::<[f32; 4]>::from(bezier.into_row_arrays()).map(Vec4::from);
160        let bezier = bezier.map(Vec3::from);
161        CubicBezier3::from(bezier)
162    }
163    for sim::NearestWaysData { bezier: bez, .. } in
164        sim.get_nearest_ways(chunk_center_wpos2d, &|chunk| Some(chunk.path))
165    {
166        if bez.length_by_discretization(16) < 0.125 {
167            continue;
168        }
169        let a = 0.0;
170        let b = 1.0;
171        for bez in bez.split((a + b) / 2.0) {
172            let p0 = g(bez.evaluate(a));
173            let p1 = g(bez.evaluate(a + (b - a) / 3.0));
174            let p2 = g(bez.evaluate(a + 2.0 * (b - a) / 3.0));
175            let p3 = g(bez.evaluate(b));
176            splines.push(hermite_to_bezier(p0, 3.0 * (p1 - p0), p3, 3.0 * (p3 - p2)));
177        }
178    }
179    for spline in splines.into_iter() {
180        canvas.chunk.meta_mut().add_track(spline);
181    }
182}
183
184pub fn apply_coral_to(canvas: &mut Canvas) {
185    let info = canvas.info();
186
187    if !info.chunk.river.near_water() {
188        return; // Don't bother with coral for a chunk nowhere near water
189    }
190
191    canvas.foreach_col(|canvas, wpos2d, col| {
192        const CORAL_DEPTH: Range<f32> = 14.0..32.0;
193        const CORAL_HEIGHT: f32 = 14.0;
194        const CORAL_DEPTH_FADEOUT: f32 = 5.0;
195        const CORAL_SCALE: f32 = 10.0;
196
197        let water_depth = col.water_level - col.alt;
198
199        if !CORAL_DEPTH.contains(&water_depth) {
200            return; // Avoid coral entirely for this column if we're outside coral depths
201        }
202
203        for z in col.alt.floor() as i32..(col.alt + CORAL_HEIGHT) as i32 {
204            let wpos = Vec3::new(wpos2d.x, wpos2d.y, z);
205
206            let coral_factor = Lerp::lerp(
207                1.0,
208                0.0,
209                // Fade coral out due to incorrect depth
210                ((water_depth.clamped(CORAL_DEPTH.start, CORAL_DEPTH.end) - water_depth).abs()
211                    / CORAL_DEPTH_FADEOUT)
212                    .min(1.0),
213            ) * Lerp::lerp(
214                1.0,
215                0.0,
216                // Fade coral out due to incorrect altitude above the seabed
217                ((z as f32 - col.alt) / CORAL_HEIGHT).powi(2),
218            ) * FastNoise::new(info.index.seed + 7)
219                .get(wpos.map(|e| e as f64) / 32.0)
220                .sub(0.2)
221                .mul(100.0)
222                .clamped(0.0, 1.0);
223
224            let nz = Vec3::iota().map(|e: u32| FastNoise::new(info.index.seed + e * 177));
225
226            let wpos_warped = wpos.map(|e| e as f32)
227                + nz.map(|nz| {
228                    nz.get(wpos.map(|e| e as f64) / CORAL_SCALE as f64) * CORAL_SCALE * 0.3
229                });
230
231            // let is_coral = FastNoise2d::new(info.index.seed + 17)
232            //     .get(wpos_warped.xy().map(|e| e as f64) / CORAL_SCALE)
233            //     .sub(1.0 - coral_factor)
234            //     .max(0.0)
235            //     .div(coral_factor) > 0.5;
236
237            let is_coral = [
238                FastNoise::new(info.index.seed),
239                FastNoise::new(info.index.seed + 177),
240            ]
241            .iter()
242            .all(|nz| {
243                nz.get(wpos_warped.map(|e| e as f64) / CORAL_SCALE as f64)
244                    .abs()
245                    < coral_factor * 0.3
246            });
247
248            if is_coral {
249                canvas.set(wpos, Block::new(BlockKind::Rock, Rgb::new(170, 220, 210)));
250            }
251        }
252    });
253}
254
255pub fn apply_caverns_to<R: Rng>(canvas: &mut Canvas, dynamic_rng: &mut R) {
256    let info = canvas.info();
257
258    let canvern_nz_at = |wpos2d: Vec2<i32>| {
259        // Horizontal average scale of caverns
260        let scale = 2048.0;
261        // How common should they be? (0.0 - 1.0)
262        let common = 0.15;
263
264        let cavern_nz = info
265            .index()
266            .noise
267            .cave_nz
268            .get((wpos2d.map(|e| e as f64) / scale).into_array()) as f32;
269        ((cavern_nz * 0.5 + 0.5 - (1.0 - common)).max(0.0) / common).powf(common * 2.0)
270    };
271
272    // Get cavern attributes at a position
273    let cavern_at = |wpos2d| {
274        let alt = info.land().get_alt_approx(wpos2d);
275
276        // Range of heights for the caverns
277        let height_range = 16.0..250.0;
278        // Minimum distance below the surface
279        let surface_clearance = 64.0;
280
281        let cavern_avg_height = Lerp::lerp(
282            height_range.start,
283            height_range.end,
284            info.index()
285                .noise
286                .cave_nz
287                .get((wpos2d.map(|e| e as f64) / 300.0).into_array()) as f32
288                * 0.5
289                + 0.5,
290        );
291
292        let cavern_avg_alt =
293            CONFIG.sea_level.min(alt * 0.25) - height_range.end - surface_clearance;
294
295        let cavern = canvern_nz_at(wpos2d);
296        let cavern_height = cavern * cavern_avg_height;
297
298        // Stalagtites
299        let stalactite = info
300            .index()
301            .noise
302            .cave_nz
303            .get(wpos2d.map(|e| e as f64 * 0.015).into_array())
304            .sub(0.5)
305            .max(0.0)
306            .mul((cavern_height as f64 - 5.0).mul(0.15).clamped(0.0, 1.0))
307            .mul(32.0 + cavern_avg_height as f64);
308
309        let hill = info
310            .index()
311            .noise
312            .cave_nz
313            .get((wpos2d.map(|e| e as f64) / 96.0).into_array()) as f32
314            * cavern
315            * 24.0;
316        let rugged = 0.4; // How bumpy should the floor be relative to the ceiling?
317        let cavern_bottom = (cavern_avg_alt - cavern_height * rugged + hill) as i32;
318        let cavern_avg_bottom =
319            (cavern_avg_alt - ((height_range.start + height_range.end) * 0.5) * rugged) as i32;
320        let cavern_top = (cavern_avg_alt + cavern_height) as i32;
321        let cavern_avg_top = (cavern_avg_alt + cavern_avg_height) as i32;
322
323        // Stalagmites rise up to meet stalactites
324        let stalagmite = stalactite;
325
326        let floor = stalagmite as i32;
327
328        (
329            cavern_bottom,
330            cavern_top,
331            cavern_avg_bottom,
332            cavern_avg_top,
333            floor,
334            stalactite,
335            cavern_avg_bottom + 16, // Water level
336        )
337    };
338
339    let mut mushroom_cache = HashMap::new();
340
341    struct Mushroom {
342        pos: Vec3<i32>,
343        stalk: f32,
344        head_color: Rgb<u8>,
345    }
346
347    // Get mushroom block, if any, at a position
348    let mut get_mushroom = |wpos: Vec3<i32>, dynamic_rng: &mut R| {
349        for (wpos2d, seed) in info.chunks().gen_ctx.structure_gen.get(wpos.xy()) {
350            let mushroom = if let Some(mushroom) =
351                mushroom_cache.entry(wpos2d).or_insert_with(|| {
352                    let mut rng = RandomPerm::new(seed);
353                    let (cavern_bottom, cavern_top, _, _, floor, _, water_level) =
354                        cavern_at(wpos2d);
355                    let pos = wpos2d.with_z(cavern_bottom + floor);
356                    if rng.gen_bool(0.15)
357                        && cavern_top - cavern_bottom > 32
358                        && pos.z > water_level - 2
359                    {
360                        Some(Mushroom {
361                            pos,
362                            stalk: 12.0 + rng.gen::<f32>().powf(2.0) * 35.0,
363                            head_color: Rgb::new(
364                                50,
365                                rng.gen_range(70..110),
366                                rng.gen_range(100..200),
367                            ),
368                        })
369                    } else {
370                        None
371                    }
372                }) {
373                mushroom
374            } else {
375                continue;
376            };
377
378            let wposf = wpos.map(|e| e as f64);
379            let warp_freq = 1.0 / 32.0;
380            let warp_amp = Vec3::new(12.0, 12.0, 12.0);
381            let wposf_warped = wposf.map(|e| e as f32)
382                + Vec3::new(
383                    FastNoise::new(seed).get(wposf * warp_freq),
384                    FastNoise::new(seed + 1).get(wposf * warp_freq),
385                    FastNoise::new(seed + 2).get(wposf * warp_freq),
386                ) * warp_amp
387                    * (wposf.z as f32 - mushroom.pos.z as f32)
388                        .mul(0.1)
389                        .clamped(0.0, 1.0);
390
391            let rpos = wposf_warped - mushroom.pos.map(|e| e as f32);
392
393            let stalk_radius = 2.5f32;
394            let head_radius = 18.0f32;
395            let head_height = 16.0;
396
397            let dist_sq = rpos.xy().magnitude_squared();
398            if dist_sq < head_radius.powi(2) {
399                let dist = dist_sq.sqrt();
400                let head_dist = ((rpos - Vec3::unit_z() * mushroom.stalk)
401                    / Vec2::broadcast(head_radius).with_z(head_height))
402                .magnitude();
403
404                let stalk = mushroom.stalk + Lerp::lerp(head_height * 0.5, 0.0, dist / head_radius);
405
406                // Head
407                if rpos.z > stalk
408                    && rpos.z <= mushroom.stalk + head_height
409                    && dist
410                        < head_radius * (1.0 - (rpos.z - mushroom.stalk) / head_height).powf(0.125)
411                {
412                    if head_dist < 0.85 {
413                        let radial = (rpos.x.atan2(rpos.y) * 10.0).sin() * 0.5 + 0.5;
414                        return Some(Block::new(
415                            BlockKind::GlowingMushroom,
416                            Rgb::new(30, 50 + (radial * 100.0) as u8, 100 - (radial * 50.0) as u8),
417                        ));
418                    } else if head_dist < 1.0 {
419                        return Some(Block::new(BlockKind::Wood, mushroom.head_color));
420                    }
421                }
422
423                if rpos.z <= mushroom.stalk + head_height - 1.0
424                    && dist_sq
425                        < (stalk_radius * Lerp::lerp(1.5, 0.75, rpos.z / mushroom.stalk)).powi(2)
426                {
427                    // Stalk
428                    return Some(Block::new(BlockKind::Wood, Rgb::new(25, 60, 90)));
429                } else if ((mushroom.stalk - 0.1)..(mushroom.stalk + 0.9)).contains(&rpos.z) // Hanging orbs
430                    && dist > head_radius * 0.85
431                    && dynamic_rng.gen_bool(0.1)
432                {
433                    use SpriteKind::*;
434                    let sprites = if dynamic_rng.gen_bool(0.1) {
435                        &[Beehive, Lantern] as &[_]
436                    } else {
437                        &[Orb, MycelBlue, MycelBlue] as &[_]
438                    };
439                    return Some(Block::air(*sprites.choose(dynamic_rng).unwrap()));
440                }
441            }
442        }
443
444        None
445    };
446
447    canvas.foreach_col(|canvas, wpos2d, _col| {
448        if canvern_nz_at(wpos2d) <= 0.0 {
449            return;
450        }
451
452        let (
453            cavern_bottom,
454            cavern_top,
455            cavern_avg_bottom,
456            cavern_avg_top,
457            floor,
458            stalactite,
459            water_level,
460        ) = cavern_at(wpos2d);
461
462        let mini_stalactite = info
463            .index()
464            .noise
465            .cave_nz
466            .get(wpos2d.map(|e| e as f64 * 0.08).into_array())
467            .sub(0.5)
468            .max(0.0)
469            .mul(
470                ((cavern_top - cavern_bottom) as f64 - 5.0)
471                    .mul(0.15)
472                    .clamped(0.0, 1.0),
473            )
474            .mul(24.0 + (cavern_avg_top - cavern_avg_bottom) as f64 * 0.2);
475        let stalactite_height = (stalactite + mini_stalactite) as i32;
476
477        let moss_common = 1.5;
478        let moss = info
479            .index()
480            .noise
481            .cave_nz
482            .get(wpos2d.map(|e| e as f64 * 0.035).into_array())
483            .sub(1.0 - moss_common)
484            .max(0.0)
485            .mul(1.0 / moss_common)
486            .powf(8.0 * moss_common)
487            .mul(
488                ((cavern_top - cavern_bottom) as f64)
489                    .mul(0.15)
490                    .clamped(0.0, 1.0),
491            )
492            .mul(16.0 + (cavern_avg_top - cavern_avg_bottom) as f64 * 0.35);
493
494        let plant_factor = info
495            .index()
496            .noise
497            .cave_nz
498            .get(wpos2d.map(|e| e as f64 * 0.015).into_array())
499            .add(1.0)
500            .mul(0.5)
501            .powf(2.0);
502
503        let is_vine = |wpos: Vec3<f32>, dynamic_rng: &mut R| {
504            let wpos = wpos + wpos.xy().yx().with_z(0.0) * 0.2; // A little twist
505            let dims = Vec2::new(7.0, 256.0); // Long and thin
506            let vine_posf = (wpos + Vec2::new(0.0, (wpos.x / dims.x).floor() * 733.0)) / dims; // ~Random offset
507            let vine_pos = vine_posf.map(|e| e.floor() as i32);
508            let mut rng = RandomPerm::new(((vine_pos.x << 16) | vine_pos.y) as u32); // Rng for vine attributes
509            if rng.gen_bool(0.2) {
510                let vine_height = (cavern_avg_top - cavern_avg_bottom).max(64) as f32;
511                let vine_base = cavern_avg_bottom as f32 + rng.gen_range(48.0..vine_height);
512                let vine_y = (vine_posf.y.fract() - 0.5).abs() * 2.0 * dims.y;
513                let vine_reach = (vine_y * 0.05).powf(2.0).min(1024.0);
514                let vine_z = vine_base + vine_reach;
515                if Vec2::new(vine_posf.x.fract() * 2.0 - 1.0, (wpos.z - vine_z) / 5.0)
516                    .magnitude_squared()
517                    < 1.0f32
518                {
519                    let kind = if dynamic_rng.gen_bool(0.025) {
520                        BlockKind::GlowingRock
521                    } else {
522                        BlockKind::Leaves
523                    };
524                    Some(Block::new(
525                        kind,
526                        Rgb::new(
527                            85,
528                            (vine_y + vine_reach).mul(0.05).sin().mul(35.0).add(85.0) as u8,
529                            20,
530                        ),
531                    ))
532                } else {
533                    None
534                }
535            } else {
536                None
537            }
538        };
539
540        let mut last_kind = BlockKind::Rock;
541        for z in cavern_bottom - 1..cavern_top {
542            use SpriteKind::*;
543
544            let wpos = wpos2d.with_z(z);
545            let wposf = wpos.map(|e| e as f32);
546
547            let block = if z < cavern_bottom {
548                if z > water_level + dynamic_rng.gen_range(4..16) {
549                    Block::new(BlockKind::Grass, Rgb::new(10, 75, 90))
550                } else {
551                    Block::new(BlockKind::Rock, Rgb::new(50, 40, 10))
552                }
553            } else if z < cavern_bottom + floor {
554                Block::new(BlockKind::WeakRock, Rgb::new(110, 120, 150))
555            } else if z > cavern_top - stalactite_height {
556                if dynamic_rng.gen_bool(0.0035) {
557                    // Glowing rock in stalactites
558                    Block::new(BlockKind::GlowingRock, Rgb::new(30, 150, 120))
559                } else {
560                    Block::new(BlockKind::WeakRock, Rgb::new(110, 120, 150))
561                }
562            } else if let Some(mushroom_block) = get_mushroom(wpos, dynamic_rng) {
563                mushroom_block
564            } else if z > cavern_top - moss as i32 {
565                let kind = if dynamic_rng
566                    .gen_bool(0.05 / (1.0 + ((cavern_top - z).max(0) as f64).mul(0.1)))
567                {
568                    BlockKind::GlowingMushroom
569                } else {
570                    BlockKind::Leaves
571                };
572                Block::new(kind, Rgb::new(50, 120, 160))
573            } else if z < water_level {
574                Block::water(Empty).with_sprite(
575                    if z == cavern_bottom + floor && dynamic_rng.gen_bool(0.01) {
576                        *[Seagrass, SeaGrapes, SeaweedTemperate, StonyCoral]
577                            .choose(dynamic_rng)
578                            .unwrap()
579                    } else {
580                        Empty
581                    },
582                )
583            } else if z == water_level
584                && dynamic_rng.gen_bool(Lerp::lerp(0.0, 0.05, plant_factor))
585                && last_kind == BlockKind::Water
586            {
587                Block::air(CavernLillypadBlue)
588            } else if z == cavern_bottom + floor
589                && dynamic_rng.gen_bool(Lerp::lerp(0.0, 0.5, plant_factor))
590                && last_kind == BlockKind::Grass
591            {
592                Block::air(
593                    *if dynamic_rng.gen_bool(0.9) {
594                        // High density
595                        &[GrassBlueShort, GrassBlueMedium, GrassBlueLong] as &[_]
596                    } else if dynamic_rng.gen_bool(0.5) {
597                        // Medium density
598                        &[CaveMushroom] as &[_]
599                    } else {
600                        // Low density
601                        &[LeafyPlant, Fern, Pyrebloom, Moonbell, Welwitch, GrassBlue] as &[_]
602                    }
603                    .choose(dynamic_rng)
604                    .unwrap(),
605                )
606            } else if z == cavern_top - 1 && dynamic_rng.gen_bool(0.001) {
607                Block::air(
608                    *[CrystalHigh, CeilingMushroom, Orb, MycelBlue]
609                        .choose(dynamic_rng)
610                        .unwrap(),
611                )
612            } else if let Some(vine) = is_vine(wposf, dynamic_rng)
613                .or_else(|| is_vine(wposf.xy().yx().with_z(wposf.z), dynamic_rng))
614            {
615                vine
616            } else {
617                Block::empty()
618            };
619
620            last_kind = block.kind();
621
622            let block = if block.is_filled() {
623                Block::new(
624                    block.kind(),
625                    block.get_color().unwrap_or_default().map(|e| {
626                        (e as f32 * dynamic_rng.gen_range(0.95..1.05)).clamped(0.0, 255.0) as u8
627                    }),
628                )
629            } else {
630                block
631            };
632
633            canvas.set(wpos, block);
634        }
635    });
636}