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