veloren_world/layer/
rock.rs

1use crate::{
2    CONFIG, Canvas, ColumnSample,
3    util::{
4        NEIGHBORS, NEIGHBORS3, RandomField, Sampler, StructureGen2d, UnitChooser,
5        gen_cache::StructureGenCache, seed_expan,
6    },
7};
8use common::terrain::{Block, BlockKind};
9use ordered_float::NotNan;
10use rand::prelude::*;
11use rand_chacha::ChaChaRng;
12use vek::*;
13
14struct Rock {
15    wpos: Vec3<i32>,
16    seed: u32,
17    units: Vec2<Vec2<i32>>,
18    kind: RockKind,
19}
20
21pub fn apply_rocks_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
22    let mut rock_gen = StructureGenCache::new(StructureGen2d::new(canvas.index().seed, 24, 10));
23
24    let info = canvas.info();
25    canvas.foreach_col(|canvas, wpos2d, col| {
26        let rocks = rock_gen.get(wpos2d, |wpos, seed| {
27            let col = info.col_or_gen(wpos)?;
28
29            let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
30
31            const BASE_ROCK_DENSITY: f64 = 0.15;
32            if rng.gen_bool((BASE_ROCK_DENSITY * col.rock_density as f64).clamped(0.0, 1.0))
33                && col.path.is_none_or(|(d, _, _, _)| d > 6.0)
34            {
35                match (
36                    (col.alt - CONFIG.sea_level) as i32,
37                    (col.alt - col.water_level) as i32,
38                    col.water_dist.map_or(i32::MAX, |d| d as i32),
39                ) {
40                    (-3..=2, _, _) => {
41                        if rng.gen_bool(0.3) {
42                            Some(RockKind::Rauk(Pillar::generate(&mut rng)))
43                        } else {
44                            Some(RockKind::Rock(VoronoiCell::generate(
45                                rng.gen_range(1.0..3.0),
46                                &mut rng,
47                            )))
48                        }
49                    },
50                    (_, -15..=3, _) => Some(RockKind::Rock(VoronoiCell::generate(
51                        rng.gen_range(1.0..4.0),
52                        &mut rng,
53                    ))),
54                    (5..=i32::MAX, _, 0..=i32::MAX) => {
55                        if col.temp > CONFIG.desert_temp - 0.1
56                            && col.humidity < CONFIG.desert_hum + 0.1
57                        {
58                            Some(RockKind::Sandstone(VoronoiCell::generate(
59                                rng.gen_range(2.0..20.0 - 10.0 * col.tree_density),
60                                &mut rng,
61                            )))
62                        } else {
63                            Some(RockKind::Rock(VoronoiCell::generate(
64                                rng.gen_range(2.0..20.0 - 10.0 * col.tree_density),
65                                &mut rng,
66                            )))
67                        }
68                    },
69                    _ => None,
70                }
71                .map(|kind| Rock {
72                    wpos: wpos.with_z(col.alt as i32),
73                    seed,
74                    units: UnitChooser::new(seed).get(seed).into(),
75                    kind,
76                })
77            } else {
78                None
79            }
80        });
81
82        for rock in rocks {
83            let bounds = rock.kind.get_bounds();
84
85            let rpos2d = (wpos2d - rock.wpos.xy())
86                .map2(rock.units, |p, unit| unit * p)
87                .sum();
88
89            if !Aabr::from(bounds).contains_point(rpos2d) {
90                // Skip this column
91                continue;
92            }
93
94            let mut is_top = true;
95            let mut last_block = Block::empty();
96            for z in (bounds.min.z..bounds.max.z).rev() {
97                let wpos = Vec3::new(wpos2d.x, wpos2d.y, rock.wpos.z + z);
98                let model_pos = (wpos - rock.wpos)
99                    .xy()
100                    .map2(rock.units, |rpos, unit| unit * rpos)
101                    .sum()
102                    .with_z(wpos.z - rock.wpos.z);
103
104                rock.kind
105                    .take_sample(model_pos, rock.seed, last_block, col)
106                    .map(|block| {
107                        if col.snow_cover && is_top && block.is_filled() {
108                            canvas.set(
109                                wpos + Vec3::unit_z(),
110                                Block::new(BlockKind::Snow, Rgb::new(210, 210, 255)),
111                            );
112                        }
113                        canvas.set(wpos, block);
114                        is_top = false;
115                        last_block = block;
116                    });
117            }
118        }
119    });
120}
121
122struct VoronoiCell {
123    size: f32,
124    points: [Vec3<f32>; 26],
125}
126
127impl VoronoiCell {
128    fn generate(size: f32, rng: &mut impl Rng) -> Self {
129        let mut points = [Vec3::zero(); 26];
130        for (i, p) in NEIGHBORS3.iter().enumerate() {
131            points[i] = p.as_() * size
132                + Vec3::new(
133                    rng.gen_range(-0.5..=0.5) * size,
134                    rng.gen_range(-0.5..=0.5) * size,
135                    rng.gen_range(-0.5..=0.5) * size,
136                );
137        }
138        Self { size, points }
139    }
140
141    fn sample_at(&self, rpos: Vec3<i32>) -> bool {
142        let rposf = rpos.as_();
143        // Would theoretically only need to compare with 7 other points rather than 26,
144        // by checking all the points in the cells touching the closest corner of this
145        // point.
146        rposf.magnitude_squared()
147            <= *(0..26)
148                .map(|i| self.points[i].distance_squared(rposf))
149                .map(|d| NotNan::new(d).unwrap())
150                .min()
151                .unwrap()
152    }
153}
154
155struct Pillar {
156    height: f32,
157    max_extent: Vec2<f32>,
158    extents: [Vec2<f32>; 3],
159}
160
161impl Pillar {
162    fn generate(rng: &mut impl Rng) -> Self {
163        let extents = [
164            Vec2::new(rng.gen_range(0.5..1.5), rng.gen_range(0.5..1.5)),
165            Vec2::new(rng.gen_range(0.8..2.8), rng.gen_range(0.8..2.8)),
166            Vec2::new(rng.gen_range(0.5..1.5), rng.gen_range(0.5..3.5)),
167        ];
168        Self {
169            height: rng.gen_range(6.0..16.0),
170            extents,
171            max_extent: extents
172                .iter()
173                .cloned()
174                .reduce(|accum, item| accum.map2(item, |a, b| a.max(b)))
175                .unwrap(),
176        }
177    }
178
179    fn sample_at(&self, rpos: Vec3<i32>) -> bool {
180        let h = rpos.z as f32 / self.height;
181        let extent = if h < 0.0 {
182            self.extents[0] * (-h).max(1.0)
183        } else if h < 0.5 {
184            self.extents[0].map2(self.extents[1], |l, m| f32::lerp(l, m, h * 2.0))
185        } else if h < 1.0 {
186            self.extents[1].map2(self.extents[2], |m, t| f32::lerp(m, t, (h - 0.5) * 2.0))
187        } else {
188            self.extents[2]
189        };
190        h < 1.0
191            && extent
192                .map2(rpos.xy(), |e, p| p.abs() < e.ceil() as i32)
193                .reduce_and()
194    }
195}
196
197enum RockKind {
198    // A normal rock with a size
199    Rock(VoronoiCell),
200    Sandstone(VoronoiCell),
201    Rauk(Pillar),
202    // Arch,
203    // Hoodoos,
204}
205
206impl RockKind {
207    fn take_sample(
208        &self,
209        rpos: Vec3<i32>,
210        seed: u32,
211        last_block: Block,
212        col: &ColumnSample,
213    ) -> Option<Block> {
214        // Used to debug get_bounds
215        /*
216        let bounds = self.get_bounds();
217        if rpos
218            .map3(
219                bounds.min,
220                bounds.max,
221                |e, a, b| if e == a || e == b { 1 } else { 0 },
222            )
223            .sum()
224            >= 2
225        {
226            return Some(Block::new(BlockKind::Rock, Rgb::red()));
227        }
228        */
229
230        match self {
231            RockKind::Rock(cell) => {
232                if cell.sample_at(rpos) {
233                    let mossiness = 0.1
234                        + RandomField::new(seed).get_f32(Vec3::zero()) * 0.3
235                        + col.humidity * 0.9;
236                    Some(
237                        if last_block.is_filled()
238                            || (rpos.z as f32 / cell.size
239                                + RandomField::new(seed).get_f32(rpos) * 0.3
240                                > mossiness)
241                        {
242                            let mut i = 0;
243                            Block::new(
244                                BlockKind::WeakRock,
245                                col.stone_col.map(|c| {
246                                    i += 1;
247                                    c + RandomField::new(seed).get(rpos) as u8 % 10
248                                }),
249                            )
250                        } else {
251                            Block::new(
252                                BlockKind::Grass,
253                                col.surface_color.map(|e| (e * 255.0) as u8),
254                            )
255                        },
256                    )
257                } else {
258                    None
259                }
260            },
261            RockKind::Sandstone(cell) => {
262                if cell.sample_at(rpos) {
263                    let sandiness = 0.3 + RandomField::new(seed).get_f32(Vec3::zero()) * 0.4;
264                    Some(
265                        if last_block.is_filled()
266                            || (rpos.z as f32 / cell.size
267                                + RandomField::new(seed).get_f32(rpos) * 0.3
268                                > sandiness)
269                        {
270                            let mut i = 0;
271                            Block::new(
272                                BlockKind::WeakRock,
273                                Rgb::new(220, 160, 100).map(|c| {
274                                    i += 1;
275                                    c + RandomField::new(seed + i).get(Vec2::zero().with_z(rpos.z))
276                                        as u8
277                                        % 30
278                                }),
279                            )
280                        } else {
281                            Block::new(
282                                BlockKind::Grass,
283                                col.surface_color.map(|e| (e * 255.0) as u8),
284                            )
285                        },
286                    )
287                } else {
288                    None
289                }
290            },
291            RockKind::Rauk(pillar) => {
292                let max_extent = *pillar
293                    .max_extent
294                    .map(|e| NotNan::new(e).unwrap())
295                    .reduce_max();
296                let is_filled = |rpos| {
297                    pillar.sample_at(rpos)
298                        && RandomField::new(seed).chance(
299                            rpos,
300                            1.5 - rpos.z as f32 / pillar.height
301                                - rpos.xy().as_::<f32>().magnitude() / max_extent,
302                        )
303                };
304                if is_filled(rpos) ||
305                // Prevent floating blocks
306                (last_block.is_filled()
307                    && NEIGHBORS
308                        .iter()
309                        .all(|n| !is_filled(rpos + n.with_z(0))))
310                {
311                    Some(Block::new(
312                        BlockKind::WeakRock,
313                        Rgb::new(
314                            190 + RandomField::new(seed + 1).get(rpos) as u8 % 10,
315                            190 + RandomField::new(seed + 2).get(rpos) as u8 % 10,
316                            190 + RandomField::new(seed + 3).get(rpos) as u8 % 10,
317                        ),
318                    ))
319                } else {
320                    None
321                }
322            },
323        }
324    }
325
326    fn get_bounds(&self) -> Aabb<i32> {
327        match self {
328            RockKind::Rock(VoronoiCell { size, .. })
329            | RockKind::Sandstone(VoronoiCell { size, .. }) => {
330                // Need to use full size because rock can bleed over into other cells
331                let extent = *size as i32;
332                Aabb {
333                    min: Vec3::broadcast(-extent),
334                    max: Vec3::broadcast(extent),
335                }
336            },
337            RockKind::Rauk(Pillar {
338                max_extent: extent,
339                height,
340                ..
341            }) => Aabb {
342                min: (-extent.as_()).with_z(-2),
343                max: extent.as_().with_z(*height as i32),
344            },
345        }
346    }
347}