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