1use crate::biomes::ChunkBiomeSampler;
2use crate::density::traits::{ColumnCache, NoiseSettings};
3use crate::noise::AquiferResult;
4use crate::noise::LazyAquifer;
5use crate::structure::StructurePiece;
6use crate::utils::column_base_height;
7use crate::utils::column_interpolated_density;
8use crate::utils::find_solid_block_below_air;
9use crate::utils::iterate_noise_column_with_aquifer;
10use crate::{density::DimensionNoises, noise::Aquifer};
11use rustc_hash::FxHashMap;
12use std::cell::RefCell;
13use steel_registry::biome::BiomeRef;
14use steel_registry::template_pool::{TemplateData, TemplatePoolData};
15use steel_utils::Identifier;
16use steel_utils::random::RandomSplitter;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ColumnBlock {
21 Air,
23 Fluid,
25 Solid,
27}
28
29pub struct GenerationContext<'ctx, 'src, N: DimensionNoises>
34where
35 'src: 'ctx,
36{
37 seed: i64,
39 chunk_x: i32,
41 chunk_z: i32,
43 chunk_min_x: i32,
45 chunk_min_z: i32,
47 center_block_x: i32,
49 center_block_z: i32,
51 sea_level: i32,
53 surface_y_cache: &'ctx mut Option<i32>,
55 height_cache_grid_ready: &'ctx mut bool,
58
59 noises: &'src N,
61 splitter: &'src RandomSplitter,
63 template_pools: &'src FxHashMap<Identifier, TemplatePoolData>,
65 templates: &'src FxHashMap<Identifier, TemplateData>,
67
68 biome_sampler: &'ctx mut ChunkBiomeSampler<'src>,
70 height_cache: &'ctx mut N::ColumnCache,
72 aquifer: &'ctx mut LazyAquifer<'src, N>,
74 terrain_height_cache: RefCell<FxHashMap<(i32, i32, bool), i32>>,
76 terrain_opaque_cache: RefCell<FxHashMap<(i32, i32, i32, bool), bool>>,
78 terrain_probes: RefCell<FxHashMap<(i32, i32), TerrainProbe<N>>>,
80}
81
82pub struct TerrainProbe<N: DimensionNoises> {
84 cache: N::ColumnCache,
85 aquifer: Aquifer<N>,
86}
87
88impl<N: DimensionNoises> TerrainProbe<N> {
89 fn new(chunk_min_x: i32, chunk_min_z: i32, splitter: &RandomSplitter, noises: &N) -> Self {
90 let mut cache = N::ColumnCache::default();
91 cache.init_grid(chunk_min_x, chunk_min_z, noises);
92 let aquifer = Aquifer::<N>::new(
93 chunk_min_x,
94 chunk_min_z,
95 N::Settings::MIN_Y,
96 N::Settings::HEIGHT,
97 splitter,
98 noises,
99 cache.clone(),
100 );
101 Self { cache, aquifer }
102 }
103}
104
105pub struct GenerationStub {
107 pub position: (i32, i32, i32),
109 pub pieces: Vec<StructurePiece>,
111}
112
113pub trait StructureGenerationContext {
119 fn seed(&self) -> i64;
121 fn chunk_x(&self) -> i32;
123 fn chunk_z(&self) -> i32;
125 fn chunk_min_x(&self) -> i32;
127 fn chunk_min_z(&self) -> i32;
129 fn center_block_x(&self) -> i32;
131 fn center_block_z(&self) -> i32;
133 fn sea_level(&self) -> i32;
135 fn min_y(&self) -> i32;
137 fn height(&self) -> i32;
139 fn max_y(&self) -> i32 {
141 self.min_y() + self.height()
142 }
143 fn template_pools(&self) -> &FxHashMap<Identifier, TemplatePoolData>;
145 fn templates(&self) -> &FxHashMap<Identifier, TemplateData>;
147 fn base_height(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32;
149 fn base_height_full(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32;
151 fn biome_at(&mut self, block_x: i32, block_y: i32, block_z: i32) -> BiomeRef;
153 fn column_state(&mut self, x: i32, y: i32, z: i32) -> ColumnBlock;
155 fn solid_block_below_air(
157 &mut self,
158 x: i32,
159 z: i32,
160 start_y: i32,
161 min_solid_y: i32,
162 ) -> Option<i32> {
163 if start_y <= min_solid_y {
164 return None;
165 }
166
167 let mut above = self.column_state(x, start_y, z);
168 for y in (min_solid_y..start_y).rev() {
169 let current = self.column_state(x, y, z);
170 if above == ColumnBlock::Air && current == ColumnBlock::Solid {
171 return Some(y);
172 }
173 above = current;
174 }
175 None
176 }
177 fn surface_y(&mut self) -> i32;
179 fn terrain_surface_height(&self, x: i32, z: i32, ocean_floor: bool) -> i32;
181 fn terrain_is_opaque(&self, x: i32, y: i32, z: i32, ocean_floor: bool) -> bool;
183}
184
185impl<'ctx, 'src, N: DimensionNoises> GenerationContext<'ctx, 'src, N>
186where
187 'src: 'ctx,
188{
189 #[must_use]
191 #[expect(
192 clippy::too_many_arguments,
193 reason = "borrows all mutable per-chunk generation state without owning it"
194 )]
195 pub fn new(
196 seed: i64,
197 chunk_x: i32,
198 chunk_z: i32,
199 sea_level: i32,
200 noises: &'src N,
201 splitter: &'src RandomSplitter,
202 template_pools: &'src FxHashMap<Identifier, TemplatePoolData>,
203 templates: &'src FxHashMap<Identifier, TemplateData>,
204 biome_sampler: &'ctx mut ChunkBiomeSampler<'src>,
205 height_cache: &'ctx mut N::ColumnCache,
206 aquifer: &'ctx mut LazyAquifer<'src, N>,
207 surface_y_cache: &'ctx mut Option<i32>,
208 height_cache_grid_ready: &'ctx mut bool,
209 ) -> Self {
210 let chunk_min_x = chunk_x * 16;
211 let chunk_min_z = chunk_z * 16;
212 Self {
213 seed,
214 chunk_x,
215 chunk_z,
216 chunk_min_x,
217 chunk_min_z,
218 center_block_x: chunk_min_x + 8,
219 center_block_z: chunk_min_z + 8,
220 sea_level,
221 surface_y_cache,
222 height_cache_grid_ready,
223 noises,
224 splitter,
225 template_pools,
226 templates,
227 biome_sampler,
228 height_cache,
229 aquifer,
230 terrain_height_cache: RefCell::default(),
231 terrain_opaque_cache: RefCell::default(),
232 terrain_probes: RefCell::default(),
233 }
234 }
235
236 pub fn base_height(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32 {
244 self.ensure_height_cache_grid();
245 let aq = self.aquifer.ensure(self.height_cache);
246 column_base_height::<N>(self.height_cache, self.noises, aq, x, z, ocean_floor)
247 }
248
249 pub fn base_height_full(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32 {
251 self.ensure_height_cache_grid();
252 let aq = self.aquifer.ensure(self.height_cache);
253 iterate_noise_column_with_aquifer::<N>(
254 self.height_cache,
255 self.noises,
256 aq,
257 x,
258 z,
259 ocean_floor,
260 )
261 }
262
263 pub fn biome_at(&mut self, block_x: i32, block_y: i32, block_z: i32) -> BiomeRef {
265 self.biome_sampler
266 .sample(block_x >> 2, block_y >> 2, block_z >> 2)
267 }
268
269 pub fn column_state(&mut self, x: i32, y: i32, z: i32) -> ColumnBlock {
271 self.ensure_height_cache_grid();
272 let cw = N::Settings::CELL_WIDTH;
273 let ch = N::Settings::CELL_HEIGHT;
274 let density =
275 column_interpolated_density::<N>(self.height_cache, self.noises, x, y, z, cw, ch);
276 let aq = self.aquifer.ensure(self.height_cache);
277 match aq.compute_substance(self.noises, x, y, z, density) {
278 AquiferResult::Solid => ColumnBlock::Solid,
279 AquiferResult::Fluid(_) => ColumnBlock::Fluid,
280 AquiferResult::Air => ColumnBlock::Air,
281 }
282 }
283
284 pub fn solid_block_below_air(
286 &mut self,
287 x: i32,
288 z: i32,
289 start_y: i32,
290 min_solid_y: i32,
291 ) -> Option<i32> {
292 self.ensure_height_cache_grid();
293 let aq = self.aquifer.ensure(self.height_cache);
294 find_solid_block_below_air::<N>(
295 self.height_cache,
296 self.noises,
297 aq,
298 x,
299 z,
300 start_y,
301 min_solid_y,
302 )
303 }
304
305 pub fn surface_y(&mut self) -> i32 {
307 if let Some(y) = *self.surface_y_cache {
308 return y;
309 }
310 let y = self.base_height(self.center_block_x, self.center_block_z, false) - 1;
311 *self.surface_y_cache = Some(y);
312 y
313 }
314
315 fn ensure_height_cache_grid(&mut self) {
316 if *self.height_cache_grid_ready {
317 return;
318 }
319 self.height_cache
320 .init_grid(self.chunk_min_x, self.chunk_min_z, self.noises);
321 *self.height_cache_grid_ready = true;
322 }
323}
324
325impl<N: DimensionNoises> StructureGenerationContext for GenerationContext<'_, '_, N> {
326 fn seed(&self) -> i64 {
327 self.seed
328 }
329
330 fn chunk_x(&self) -> i32 {
331 self.chunk_x
332 }
333
334 fn chunk_z(&self) -> i32 {
335 self.chunk_z
336 }
337
338 fn chunk_min_x(&self) -> i32 {
339 self.chunk_min_x
340 }
341
342 fn chunk_min_z(&self) -> i32 {
343 self.chunk_min_z
344 }
345
346 fn center_block_x(&self) -> i32 {
347 self.center_block_x
348 }
349
350 fn center_block_z(&self) -> i32 {
351 self.center_block_z
352 }
353
354 fn sea_level(&self) -> i32 {
355 self.sea_level
356 }
357
358 fn min_y(&self) -> i32 {
359 N::Settings::MIN_Y
360 }
361
362 fn height(&self) -> i32 {
363 N::Settings::HEIGHT
364 }
365
366 fn template_pools(&self) -> &FxHashMap<Identifier, TemplatePoolData> {
367 self.template_pools
368 }
369
370 fn templates(&self) -> &FxHashMap<Identifier, TemplateData> {
371 self.templates
372 }
373
374 fn base_height(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32 {
375 GenerationContext::base_height(self, x, z, ocean_floor)
376 }
377
378 fn base_height_full(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32 {
379 GenerationContext::base_height_full(self, x, z, ocean_floor)
380 }
381
382 fn biome_at(&mut self, block_x: i32, block_y: i32, block_z: i32) -> BiomeRef {
383 GenerationContext::biome_at(self, block_x, block_y, block_z)
384 }
385
386 fn column_state(&mut self, x: i32, y: i32, z: i32) -> ColumnBlock {
387 GenerationContext::column_state(self, x, y, z)
388 }
389
390 fn solid_block_below_air(
391 &mut self,
392 x: i32,
393 z: i32,
394 start_y: i32,
395 min_solid_y: i32,
396 ) -> Option<i32> {
397 GenerationContext::solid_block_below_air(self, x, z, start_y, min_solid_y)
398 }
399
400 fn surface_y(&mut self) -> i32 {
401 GenerationContext::surface_y(self)
402 }
403
404 fn terrain_surface_height(&self, x: i32, z: i32, ocean_floor: bool) -> i32 {
405 if let Some(height) = self
406 .terrain_height_cache
407 .borrow()
408 .get(&(x, z, ocean_floor))
409 .copied()
410 {
411 return height;
412 }
413
414 let cell_w = N::Settings::CELL_WIDTH;
415 let cell_x = x.div_euclid(cell_w) * cell_w;
416 let cell_z = z.div_euclid(cell_w) * cell_w;
417 let aq_chunk_x = (cell_x >> 4) * 16;
418 let aq_chunk_z = (cell_z >> 4) * 16;
419 let height = {
420 let mut probes = self.terrain_probes.borrow_mut();
421 let probe = probes.entry((aq_chunk_x, aq_chunk_z)).or_insert_with(|| {
422 TerrainProbe::<N>::new(aq_chunk_x, aq_chunk_z, self.splitter, self.noises)
423 });
424 iterate_noise_column_with_aquifer::<N>(
425 &mut probe.cache,
426 self.noises,
427 &mut probe.aquifer,
428 x,
429 z,
430 ocean_floor,
431 )
432 };
433 self.terrain_height_cache
434 .borrow_mut()
435 .insert((x, z, ocean_floor), height);
436 height
437 }
438
439 fn terrain_is_opaque(&self, x: i32, y: i32, z: i32, ocean_floor: bool) -> bool {
440 if let Some(opaque) = self
441 .terrain_opaque_cache
442 .borrow()
443 .get(&(x, y, z, ocean_floor))
444 .copied()
445 {
446 return opaque;
447 }
448
449 let cell_w = N::Settings::CELL_WIDTH;
450 let cell_h = N::Settings::CELL_HEIGHT;
451 let cell_x = x.div_euclid(cell_w) * cell_w;
452 let cell_z = z.div_euclid(cell_w) * cell_w;
453 let aq_chunk_x = (cell_x >> 4) * 16;
454 let aq_chunk_z = (cell_z >> 4) * 16;
455 let opaque = {
456 let mut probes = self.terrain_probes.borrow_mut();
457 let probe = probes.entry((aq_chunk_x, aq_chunk_z)).or_insert_with(|| {
458 TerrainProbe::<N>::new(aq_chunk_x, aq_chunk_z, self.splitter, self.noises)
459 });
460 let density = column_interpolated_density::<N>(
461 &mut probe.cache,
462 self.noises,
463 x,
464 y,
465 z,
466 cell_w,
467 cell_h,
468 );
469 match probe
470 .aquifer
471 .compute_substance(self.noises, x, y, z, density)
472 {
473 AquiferResult::Solid => true,
474 AquiferResult::Fluid(_) => !ocean_floor,
475 AquiferResult::Air => false,
476 }
477 };
478 self.terrain_opaque_cache
479 .borrow_mut()
480 .insert((x, y, z, ocean_floor), opaque);
481 opaque
482 }
483}