Skip to main content

steel_core/worldgen/feature/features/
lake.rs

1use super::super::prelude::*;
2use super::super::runner::FeatureDecorationRunner;
3
4impl FeatureDecorationRunner {
5    pub(in crate::worldgen::feature) fn place_lake_feature(
6        region: &mut WorldGenRegion<'_>,
7        registry: &Registry,
8        random: &mut WorldgenRandom,
9        config: &LakeConfiguration,
10        origin: BlockPos,
11        biome_zoom_seed: i64,
12    ) -> bool {
13        if origin.y() <= region.min_y() + 4 {
14            return false;
15        }
16
17        let origin = origin.offset(-8, -4, -8);
18        let mut grid = vec![false; 16 * 16 * 8];
19        let spots = random.next_i32_bounded(4) + 4;
20        for _ in 0..spots {
21            Self::carve_lake_ellipsoid(random, &mut grid);
22        }
23
24        let fluid =
25            Self::sample_block_state_provider(region, registry, random, &config.fluid, origin);
26        if !Self::lake_boundary_valid(region, registry, config, &grid, origin, fluid) {
27            return false;
28        }
29
30        Self::place_lake_contents(region, registry, config, &grid, origin, fluid);
31        Self::place_lake_barrier(region, registry, random, config, &grid, origin);
32        if get_fluid_state_from_block(fluid).is_water() {
33            Self::freeze_lake_surface(region, registry, config, biome_zoom_seed, origin);
34        }
35
36        true
37    }
38
39    fn carve_lake_ellipsoid(random: &mut WorldgenRandom, grid: &mut [bool]) {
40        let x_radius = random.next_f64() * 6.0 + 3.0;
41        let y_radius = random.next_f64() * 4.0 + 2.0;
42        let z_radius = random.next_f64() * 6.0 + 3.0;
43        let x_center = random.next_f64() * (16.0 - x_radius - 2.0) + 1.0 + x_radius / 2.0;
44        let y_center = random.next_f64() * (8.0 - y_radius - 4.0) + 2.0 + y_radius / 2.0;
45        let z_center = random.next_f64() * (16.0 - z_radius - 2.0) + 1.0 + z_radius / 2.0;
46
47        for x in 1..15 {
48            for z in 1..15 {
49                for y in 1..7 {
50                    let x_delta = (f64::from(x) - x_center) / (x_radius / 2.0);
51                    let y_delta = (f64::from(y) - y_center) / (y_radius / 2.0);
52                    let z_delta = (f64::from(z) - z_center) / (z_radius / 2.0);
53                    if x_delta * x_delta + y_delta * y_delta + z_delta * z_delta < 1.0 {
54                        grid[Self::lake_index(x, y, z)] = true;
55                    }
56                }
57            }
58        }
59    }
60
61    fn lake_boundary_valid(
62        region: &WorldGenRegion<'_>,
63        registry: &Registry,
64        config: &LakeConfiguration,
65        grid: &[bool],
66        origin: BlockPos,
67        fluid: BlockStateId,
68    ) -> bool {
69        for x in 0..16 {
70            for z in 0..16 {
71                for y in 0..8 {
72                    if !Self::lake_is_boundary(grid, x, y, z) {
73                        continue;
74                    }
75
76                    let state = region.block_state(origin.offset(x, y, z));
77                    if y >= 4 && !get_fluid_state_from_block(state).is_empty() {
78                        return false;
79                    }
80
81                    if y < 4 && !state.is_solid() && state != fluid {
82                        return false;
83                    }
84
85                    if !Self::test_block_predicate(
86                        region,
87                        registry,
88                        &config.can_place_feature,
89                        origin.offset(x, y, z),
90                    ) {
91                        return false;
92                    }
93                }
94            }
95        }
96
97        true
98    }
99
100    fn place_lake_contents(
101        region: &mut WorldGenRegion<'_>,
102        registry: &Registry,
103        config: &LakeConfiguration,
104        grid: &[bool],
105        origin: BlockPos,
106        fluid: BlockStateId,
107    ) {
108        let cave_air = vanilla_blocks::CAVE_AIR.default_state();
109        for x in 0..16 {
110            for z in 0..16 {
111                for y in 0..8 {
112                    if !grid[Self::lake_index(x, y, z)] {
113                        continue;
114                    }
115
116                    let pos = origin.offset(x, y, z);
117                    if !Self::test_block_predicate(
118                        region,
119                        registry,
120                        &config.can_replace_with_air_or_fluid,
121                        pos,
122                    ) {
123                        continue;
124                    }
125
126                    if y >= 4 {
127                        let _ = region.set_block_state(pos, cave_air, UpdateFlags::UPDATE_CLIENTS);
128                        let _ = region.schedule_block_tick_default(pos, cave_air.get_block(), 0);
129                        Self::mark_above_for_postprocessing(region, pos);
130                    } else {
131                        let _ = region.set_block_state(pos, fluid, UpdateFlags::UPDATE_CLIENTS);
132                    }
133                }
134            }
135        }
136    }
137
138    fn place_lake_barrier(
139        region: &mut WorldGenRegion<'_>,
140        registry: &Registry,
141        random: &mut WorldgenRandom,
142        config: &LakeConfiguration,
143        grid: &[bool],
144        origin: BlockPos,
145    ) {
146        let barrier =
147            Self::sample_block_state_provider(region, registry, random, &config.barrier, origin);
148        if barrier.is_air() {
149            return;
150        }
151
152        for x in 0..16 {
153            for z in 0..16 {
154                for y in 0..8 {
155                    if !Self::lake_is_boundary(grid, x, y, z)
156                        || (y >= 4 && random.next_i32_bounded(2) == 0)
157                    {
158                        continue;
159                    }
160
161                    let pos = origin.offset(x, y, z);
162                    let state = region.block_state(pos);
163                    if state.is_solid()
164                        && Self::test_block_predicate(
165                            region,
166                            registry,
167                            &config.can_replace_with_barrier,
168                            pos,
169                        )
170                    {
171                        let _ = region.set_block_state(pos, barrier, UpdateFlags::UPDATE_CLIENTS);
172                        Self::mark_above_for_postprocessing(region, pos);
173                    }
174                }
175            }
176        }
177    }
178
179    fn freeze_lake_surface(
180        region: &mut WorldGenRegion<'_>,
181        registry: &Registry,
182        config: &LakeConfiguration,
183        biome_zoom_seed: i64,
184        origin: BlockPos,
185    ) {
186        let ice = vanilla_blocks::ICE.default_state();
187        for x in 0..16 {
188            for z in 0..16 {
189                let pos = origin.offset(x, 4, z);
190                if Self::should_freeze(region, registry, biome_zoom_seed, pos, false)
191                    && Self::test_block_predicate(
192                        region,
193                        registry,
194                        &config.can_replace_with_air_or_fluid,
195                        pos,
196                    )
197                {
198                    let _ = region.set_block_state(pos, ice, UpdateFlags::UPDATE_CLIENTS);
199                }
200            }
201        }
202    }
203
204    const fn lake_is_boundary(grid: &[bool], x: i32, y: i32, z: i32) -> bool {
205        !grid[Self::lake_index(x, y, z)]
206            && ((x < 15 && grid[Self::lake_index(x + 1, y, z)])
207                || (x > 0 && grid[Self::lake_index(x - 1, y, z)])
208                || (z < 15 && grid[Self::lake_index(x, y, z + 1)])
209                || (z > 0 && grid[Self::lake_index(x, y, z - 1)])
210                || (y < 7 && grid[Self::lake_index(x, y + 1, z)])
211                || (y > 0 && grid[Self::lake_index(x, y - 1, z)]))
212    }
213
214    const fn lake_index(x: i32, y: i32, z: i32) -> usize {
215        ((x * 16 + z) * 8 + y) as usize
216    }
217}