steel_core/worldgen/feature/features/
lake.rs1use 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}