1use std::simd::i32x4;
11
12use rustc_hash::FxHashMap;
13
14use crate::density::{ColumnCache, DimensionNoises, NoiseSettings};
15use steel_math::{clamp, map, map_clamped};
16use steel_registry::{REGISTRY, vanilla_blocks};
17use steel_utils::BlockStateId;
18use steel_utils::random::name_hash::NameHash;
19use steel_utils::random::{PositionalRandom, Random, RandomSplitter};
20
21pub struct LazyAquifer<'a, N: DimensionNoises> {
24 chunk_min_x: i32,
25 chunk_min_z: i32,
26 splitter: &'a RandomSplitter,
27 noises: &'a N,
28 inner: Option<Aquifer<N>>,
29}
30
31impl<'a, N: DimensionNoises> LazyAquifer<'a, N> {
32 #[must_use]
34 pub const fn new(
35 chunk_min_x: i32,
36 chunk_min_z: i32,
37 splitter: &'a RandomSplitter,
38 noises: &'a N,
39 ) -> Self {
40 Self {
41 chunk_min_x,
42 chunk_min_z,
43 splitter,
44 noises,
45 inner: None,
46 }
47 }
48
49 pub fn ensure(&mut self, height_cache: &N::ColumnCache) -> &mut Aquifer<N> {
54 if self.inner.is_none() {
55 self.inner = Some(Aquifer::<N>::new(
56 self.chunk_min_x,
57 self.chunk_min_z,
58 <N::Settings as NoiseSettings>::MIN_Y,
59 <N::Settings as NoiseSettings>::HEIGHT,
60 self.splitter,
61 self.noises,
62 height_cache.clone(),
63 ));
64 }
65 #[expect(clippy::unwrap_used, reason = "just initialized above")]
66 self.inner.as_mut().unwrap()
67 }
68}
69
70const Y_SPACING: i32 = 12;
72
73const X_RANGE: i32 = 10;
75const Y_RANGE: i32 = 9;
76const Z_RANGE: i32 = 10;
77
78const SAMPLE_OFFSET_X: i32 = -5;
80const SAMPLE_OFFSET_Y: i32 = 1;
81const SAMPLE_OFFSET_Z: i32 = -5;
82
83const LAVA_LEVEL: i32 = -54;
84const WAY_BELOW_MIN_Y: i32 = -32512;
86const FLOWING_UPDATE_SIMILARITY: f64 = 1.0 - ((12 * 12 - 10 * 10) as f64) / 25.0;
87
88const SURFACE_SAMPLING_OFFSETS: [[i32; 2]; 13] = [
91 [0, 0],
92 [-2, -1],
93 [-1, -1],
94 [0, -1],
95 [1, -1],
96 [-3, 0],
97 [-2, 0],
98 [-1, 0],
99 [1, 0],
100 [-2, 1],
101 [-1, 1],
102 [0, 1],
103 [1, 1],
104];
105
106#[derive(Clone, Copy, PartialEq, Eq)]
111struct FluidStatus {
112 fluid_level: i32,
114 fluid_type: BlockStateId,
116}
117
118impl FluidStatus {
119 const fn at(self, block_y: i32) -> Option<BlockStateId> {
122 if block_y < self.fluid_level {
123 Some(self.fluid_type)
124 } else {
125 None
126 }
127 }
128}
129
130pub enum AquiferResult {
132 Solid,
134 Air,
136 Fluid(BlockStateId),
138}
139
140struct AquiferColumnCache {
151 world_x: i32,
152 world_z: i32,
153 y_anchor: i32,
154 cell_loc_y: [i32; 16],
158 cell_xz_dist_sq: [i32; 16],
160 cell_idx: [u32; 12],
162}
163
164impl Default for AquiferColumnCache {
165 fn default() -> Self {
166 Self {
167 world_x: 0,
168 world_z: 0,
169 y_anchor: i32::MIN,
170 cell_loc_y: [0; 16],
171 cell_xz_dist_sq: [0; 16],
172 cell_idx: [0; 12],
173 }
174 }
175}
176
177pub struct Aquifer<N: DimensionNoises> {
181 location_cache: Vec<i64>,
184 status_cache: Vec<Option<FluidStatus>>,
186 splitter: RandomSplitter,
188 cache: N::ColumnCache,
190 min_grid_x: i32,
192 min_grid_y: i32,
193 min_grid_z: i32,
194 grid_size_x: i32,
195 grid_size_z: i32,
196 skip_sampling_above_y: i32,
198 sea_level: i32,
200 lava_floor: i32,
204 water_id: BlockStateId,
206 lava_id: BlockStateId,
207 default_fluid_id: BlockStateId,
209 should_schedule_fluid_update: bool,
211 col_cache: AquiferColumnCache,
215 prelim_cache: FxHashMap<(i32, i32), i32>,
222}
223
224#[inline]
226const fn grid_x(block: i32) -> i32 {
227 block >> 4
228}
229#[inline]
230const fn grid_z(block: i32) -> i32 {
231 block >> 4
232}
233#[inline]
234const fn grid_y(block: i32) -> i32 {
235 block.div_euclid(Y_SPACING)
236}
237#[inline]
238const fn from_grid_x(grid: i32, offset: i32) -> i32 {
239 (grid << 4) + offset
240}
241#[inline]
242const fn from_grid_y(grid: i32, offset: i32) -> i32 {
243 grid * Y_SPACING + offset
244}
245#[inline]
246const fn from_grid_z(grid: i32, offset: i32) -> i32 {
247 (grid << 4) + offset
248}
249
250const PACKED_X_MASK: i64 = 0x3FF_FFFF; const PACKED_Y_MASK: i64 = 0xFFF; const PACKED_Z_MASK: i64 = 0x3FF_FFFF; const X_OFFSET: i32 = 38;
255const Z_OFFSET: i32 = 12;
256
257#[inline]
258fn pack_pos(x: i32, y: i32, z: i32) -> i64 {
259 ((i64::from(x) & PACKED_X_MASK) << X_OFFSET)
260 | (i64::from(y) & PACKED_Y_MASK)
261 | ((i64::from(z) & PACKED_Z_MASK) << Z_OFFSET)
262}
263
264#[inline]
265const fn unpack_x(packed: i64) -> i32 {
266 (packed >> X_OFFSET) as i32
267}
268
269#[inline]
270const fn unpack_y(packed: i64) -> i32 {
271 ((packed << 52) >> 52) as i32
272}
273
274#[inline]
275const fn unpack_z(packed: i64) -> i32 {
276 ((packed << 26) >> X_OFFSET) as i32
277}
278
279#[inline]
282fn similarity(dist_sq1: i32, dist_sq2: i32) -> f64 {
283 1.0 - f64::from(dist_sq2 - dist_sq1) / 25.0
284}
285
286fn is_deep_dark_region<N: DimensionNoises>(
288 noises: &N,
289 cache: &mut N::ColumnCache,
290 x: i32,
291 y: i32,
292 z: i32,
293) -> bool {
294 cache.ensure(x, z, noises);
295 let erosion = noises.router_erosion(cache, x, y, z);
296 let depth = noises.router_depth(cache, x, y, z);
297 erosion < -0.225 && depth > 0.9
298}
299
300const fn global_fluid(
305 y: i32,
306 lava_floor: i32,
307 sea_level: i32,
308 lava_id: BlockStateId,
309 default_fluid_id: BlockStateId,
310) -> FluidStatus {
311 if y < lava_floor {
312 FluidStatus {
313 fluid_level: LAVA_LEVEL,
314 fluid_type: lava_id,
315 }
316 } else {
317 FluidStatus {
318 fluid_level: sea_level,
319 fluid_type: default_fluid_id,
320 }
321 }
322}
323
324impl<N: DimensionNoises> Aquifer<N> {
325 #[must_use]
333 pub fn new(
334 chunk_min_x: i32,
335 chunk_min_z: i32,
336 min_block_y: i32,
337 y_block_size: i32,
338 splitter: &RandomSplitter,
339 noises: &N,
340 cache: N::ColumnCache,
341 ) -> Self {
342 Self::new_sized(
343 chunk_min_x,
344 chunk_min_z,
345 16,
346 16,
347 min_block_y,
348 y_block_size,
349 splitter,
350 noises,
351 cache,
352 )
353 }
354
355 #[expect(
358 clippy::too_many_arguments,
359 reason = "mirrors vanilla's Aquifer constructor shape"
360 )]
361 pub fn new_sized(
362 chunk_min_x: i32,
363 chunk_min_z: i32,
364 width_x: i32,
365 width_z: i32,
366 min_block_y: i32,
367 y_block_size: i32,
368 splitter: &RandomSplitter,
369 noises: &N,
370 mut cache: N::ColumnCache,
371 ) -> Self {
372 const AQUIFER_HASH: NameHash = NameHash::new("minecraft:aquifer");
373
374 let sea_level = N::Settings::SEA_LEVEL;
375 let water_id = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::WATER);
376 let lava_id = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::LAVA);
377 let default_fluid_id = N::Settings::default_fluid_id();
378
379 let mut aquifer_rng = splitter.with_hash_of(&AQUIFER_HASH);
380 let splitter = aquifer_rng.next_positional();
381
382 if !N::Settings::AQUIFERS_ENABLED {
385 return Self {
386 location_cache: Vec::new(),
387 status_cache: Vec::new(),
388 splitter,
389 cache,
390 col_cache: AquiferColumnCache::default(),
391 min_grid_x: 0,
392 min_grid_y: 0,
393 min_grid_z: 0,
394 grid_size_x: 0,
395 grid_size_z: 0,
396 skip_sampling_above_y: 0,
397 sea_level,
398 lava_floor: LAVA_LEVEL.min(sea_level),
399 water_id,
400 lava_id,
401 default_fluid_id,
402 should_schedule_fluid_update: false,
403 prelim_cache: FxHashMap::default(),
404 };
405 }
406
407 let chunk_max_x = chunk_min_x + width_x - 1;
408 let chunk_max_z = chunk_min_z + width_z - 1;
409
410 let min_grid_x = grid_x(chunk_min_x + SAMPLE_OFFSET_X);
411 let max_grid_x = grid_x(chunk_max_x + SAMPLE_OFFSET_X) + 1;
412 let grid_size_x = max_grid_x - min_grid_x + 1;
413
414 let min_grid_y = grid_y(min_block_y + SAMPLE_OFFSET_Y) - 1;
415 let max_grid_y = grid_y(min_block_y + y_block_size + SAMPLE_OFFSET_Y) + 1;
416 let grid_size_y = max_grid_y - min_grid_y + 1;
417
418 let min_grid_z = grid_z(chunk_min_z + SAMPLE_OFFSET_Z);
419 let max_grid_z = grid_z(chunk_max_z + SAMPLE_OFFSET_Z) + 1;
420 let grid_size_z = max_grid_z - min_grid_z + 1;
421
422 let total = (grid_size_x * grid_size_y * grid_size_z) as usize;
423 let location_cache = vec![i64::MAX; total];
424 let status_cache = vec![None; total];
425
426 let mut prelim_cache = FxHashMap::default();
429 let max_surface = Self::max_preliminary_surface_level(
430 noises,
431 &mut cache,
432 &mut prelim_cache,
433 from_grid_x(min_grid_x, 0),
434 from_grid_z(min_grid_z, 0),
435 from_grid_x(max_grid_x, X_RANGE - 1),
436 from_grid_z(max_grid_z, Z_RANGE - 1),
437 );
438 let adjusted = max_surface + 8;
439 let skip_grid_y = grid_y(adjusted + 12) + 1;
440 let skip_sampling_above_y = from_grid_y(skip_grid_y, Y_RANGE + 2) - 1;
441
442 Self {
443 location_cache,
444 status_cache,
445 splitter,
446 cache,
447 col_cache: AquiferColumnCache::default(),
448 min_grid_x,
449 min_grid_y,
450 min_grid_z,
451 grid_size_x,
452 grid_size_z,
453 skip_sampling_above_y,
454 sea_level,
455 lava_floor: LAVA_LEVEL.min(sea_level),
456 water_id,
457 lava_id,
458 default_fluid_id,
459 should_schedule_fluid_update: false,
460 prelim_cache,
461 }
462 }
463
464 fn max_preliminary_surface_level(
465 noises: &N,
466 cache: &mut N::ColumnCache,
467 prelim_cache: &mut FxHashMap<(i32, i32), i32>,
468 min_x: i32,
469 min_z: i32,
470 max_x: i32,
471 max_z: i32,
472 ) -> i32 {
473 let mut max_level = i32::MIN;
474 let mut z = min_z;
476 while z <= max_z {
477 let mut x = min_x;
478 while x <= max_x {
479 let level = cached_preliminary_surface_level(noises, cache, prelim_cache, x, z);
480 if level > max_level {
481 max_level = level;
482 }
483 x += 4;
484 }
485 z += 4;
486 }
487 max_level
488 }
489
490 #[inline]
491 const fn get_index(&self, gx: i32, gy: i32, gz: i32) -> usize {
492 let x = gx - self.min_grid_x;
493 let y = gy - self.min_grid_y;
494 let z = gz - self.min_grid_z;
495 ((y * self.grid_size_z + z) * self.grid_size_x + x) as usize
496 }
497
498 fn refill_col_cache(&mut self, world_x: i32, world_y: i32, world_z: i32) -> i32 {
504 let x_anchor = grid_x(world_x + SAMPLE_OFFSET_X);
505 let y_anchor = grid_y(world_y + SAMPLE_OFFSET_Y);
506 let z_anchor = grid_z(world_z + SAMPLE_OFFSET_Z);
507
508 let mut i = 0;
509 for x1 in 0..=1i32 {
510 for y1 in -1..=1i32 {
511 for z1 in 0..=1i32 {
512 let gx = x_anchor + x1;
513 let gy = y_anchor + y1;
514 let gz = z_anchor + z1;
515 let idx = self.get_index(gx, gy, gz);
516
517 let loc = self.location_cache[idx];
518 let loc = if loc == i64::MAX {
519 let mut rng = self.splitter.at(gx, gy, gz);
520 let packed = pack_pos(
521 from_grid_x(gx, rng.next_i32_bounded(X_RANGE)),
522 from_grid_y(gy, rng.next_i32_bounded(Y_RANGE)),
523 from_grid_z(gz, rng.next_i32_bounded(Z_RANGE)),
524 );
525 self.location_cache[idx] = packed;
526 packed
527 } else {
528 loc
529 };
530
531 let dx = unpack_x(loc) - world_x;
532 let dz = unpack_z(loc) - world_z;
533 self.col_cache.cell_loc_y[i] = unpack_y(loc);
534 self.col_cache.cell_xz_dist_sq[i] = dx * dx + dz * dz;
535 self.col_cache.cell_idx[i] = idx as u32;
536 i += 1;
537 }
538 }
539 }
540
541 self.col_cache.world_x = world_x;
542 self.col_cache.world_z = world_z;
543 self.col_cache.y_anchor = y_anchor;
544 y_anchor
545 }
546
547 #[expect(
549 clippy::too_many_lines,
550 reason = "splitting would hurt readability of the aquifer sampling logic"
551 )]
552 pub fn compute_substance(
553 &mut self,
554 noises: &N,
555 world_x: i32,
556 world_y: i32,
557 world_z: i32,
558 density: f64,
559 ) -> AquiferResult {
560 if density > 0.0 {
562 self.should_schedule_fluid_update = false;
563 return AquiferResult::Solid;
564 }
565
566 if !N::Settings::AQUIFERS_ENABLED {
569 self.should_schedule_fluid_update = false;
570 let gf = global_fluid(
571 world_y,
572 self.lava_floor,
573 self.sea_level,
574 self.lava_id,
575 self.default_fluid_id,
576 );
577 return match gf.at(world_y) {
578 Some(id) => AquiferResult::Fluid(id),
579 None => AquiferResult::Air,
580 };
581 }
582
583 let gf = global_fluid(
584 world_y,
585 self.lava_floor,
586 self.sea_level,
587 self.lava_id,
588 self.default_fluid_id,
589 );
590
591 if world_y > self.skip_sampling_above_y {
593 self.should_schedule_fluid_update = false;
594 return match gf.at(world_y) {
595 Some(id) => AquiferResult::Fluid(id),
596 None => AquiferResult::Air,
597 };
598 }
599
600 if gf.fluid_type == self.lava_id && world_y < gf.fluid_level {
602 self.should_schedule_fluid_update = false;
603 return AquiferResult::Fluid(self.lava_id);
604 }
605
606 let y_anchor = grid_y(world_y + SAMPLE_OFFSET_Y);
611 if self.col_cache.world_x != world_x
612 || self.col_cache.world_z != world_z
613 || self.col_cache.y_anchor != y_anchor
614 {
615 self.refill_col_cache(world_x, world_y, world_z);
616 }
617
618 let world_y_v = i32x4::splat(world_y);
623 let mut dists = [0i32; 12];
624 for batch in 0..3 {
625 let base = batch * 4;
626 let loc_y_v = i32x4::from_slice(&self.col_cache.cell_loc_y[base..base + 4]);
627 let xz_v = i32x4::from_slice(&self.col_cache.cell_xz_dist_sq[base..base + 4]);
628 let dy = loc_y_v - world_y_v;
629 let dist_v = xz_v + dy * dy;
630 dists[base..base + 4].copy_from_slice(&dist_v.to_array());
631 }
632
633 let mut dist_sq = [i32::MAX; 4];
634 let mut closest_idx = [0usize; 4];
635
636 for (i, &new_dist) in dists.iter().enumerate() {
637 let index = self.col_cache.cell_idx[i] as usize;
638
639 if dist_sq[0] >= new_dist {
641 dist_sq[3] = dist_sq[2];
642 closest_idx[3] = closest_idx[2];
643 dist_sq[2] = dist_sq[1];
644 closest_idx[2] = closest_idx[1];
645 dist_sq[1] = dist_sq[0];
646 closest_idx[1] = closest_idx[0];
647 dist_sq[0] = new_dist;
648 closest_idx[0] = index;
649 } else if dist_sq[1] >= new_dist {
650 dist_sq[3] = dist_sq[2];
651 closest_idx[3] = closest_idx[2];
652 dist_sq[2] = dist_sq[1];
653 closest_idx[2] = closest_idx[1];
654 dist_sq[1] = new_dist;
655 closest_idx[1] = index;
656 } else if dist_sq[2] >= new_dist {
657 dist_sq[3] = dist_sq[2];
658 closest_idx[3] = closest_idx[2];
659 dist_sq[2] = new_dist;
660 closest_idx[2] = index;
661 } else if dist_sq[3] >= new_dist {
662 dist_sq[3] = new_dist;
663 closest_idx[3] = index;
664 }
665 }
666
667 let status1 = self.get_aquifer_status(closest_idx[0], noises);
668 let fluid_at = status1.at(world_y);
669
670 let dist12_delta = dist_sq[1] - dist_sq[0];
675 if dist12_delta >= 25 {
676 if dist12_delta <= 12 * 12 - 10 * 10 {
677 let status2 = self.get_aquifer_status(closest_idx[1], noises);
678 self.should_schedule_fluid_update = status1 != status2;
679 } else {
680 self.should_schedule_fluid_update = false;
681 }
682 return match fluid_at {
683 Some(id) => AquiferResult::Fluid(id),
684 None => AquiferResult::Air,
685 };
686 }
687 let sim12 = similarity(dist_sq[0], dist_sq[1]);
688
689 if let Some(id) = fluid_at
691 && id == self.water_id
692 {
693 let below = global_fluid(
694 world_y - 1,
695 self.lava_floor,
696 self.sea_level,
697 self.lava_id,
698 self.default_fluid_id,
699 );
700 if below.fluid_type == self.lava_id && (world_y - 1) < below.fluid_level {
701 self.should_schedule_fluid_update = true;
702 return AquiferResult::Fluid(id);
703 }
704 }
705
706 let mut barrier_noise = f64::NAN;
708 let status2 = self.get_aquifer_status(closest_idx[1], noises);
709 let barrier12 = sim12
710 * self.calculate_pressure(
711 noises,
712 world_x,
713 world_y,
714 world_z,
715 &mut barrier_noise,
716 status1,
717 status2,
718 );
719 if density + barrier12 > 0.0 {
720 self.should_schedule_fluid_update = false;
721 return AquiferResult::Solid;
722 }
723
724 let status3 = self.get_aquifer_status(closest_idx[2], noises);
725 let sim13 = similarity(dist_sq[0], dist_sq[2]);
726 if sim13 > 0.0 {
727 let barrier13 = sim12
728 * sim13
729 * self.calculate_pressure(
730 noises,
731 world_x,
732 world_y,
733 world_z,
734 &mut barrier_noise,
735 status1,
736 status3,
737 );
738 if density + barrier13 > 0.0 {
739 self.should_schedule_fluid_update = false;
740 return AquiferResult::Solid;
741 }
742 }
743
744 let sim23 = similarity(dist_sq[1], dist_sq[2]);
745 if sim23 > 0.0 {
746 let barrier23 = sim12
747 * sim23
748 * self.calculate_pressure(
749 noises,
750 world_x,
751 world_y,
752 world_z,
753 &mut barrier_noise,
754 status2,
755 status3,
756 );
757 if density + barrier23 > 0.0 {
758 self.should_schedule_fluid_update = false;
759 return AquiferResult::Solid;
760 }
761 }
762
763 let may_flow12 = status1 != status2;
764 let may_flow23 = sim23 >= FLOWING_UPDATE_SIMILARITY && status2 != status3;
765 let may_flow13 = sim13 >= FLOWING_UPDATE_SIMILARITY && status1 != status3;
766 if may_flow12 || may_flow23 || may_flow13 {
767 self.should_schedule_fluid_update = true;
768 } else {
769 self.should_schedule_fluid_update = sim13 >= FLOWING_UPDATE_SIMILARITY
770 && similarity(dist_sq[0], dist_sq[3]) >= FLOWING_UPDATE_SIMILARITY
771 && status1 != self.get_aquifer_status(closest_idx[3], noises);
772 }
773
774 match fluid_at {
776 Some(id) => AquiferResult::Fluid(id),
777 None => AquiferResult::Air,
778 }
779 }
780
781 #[must_use]
783 pub const fn should_schedule_fluid_update(&self) -> bool {
784 self.should_schedule_fluid_update
785 }
786
787 pub fn preliminary_surface_level(&mut self, noises: &N, x: i32, z: i32) -> i32 {
790 cached_preliminary_surface_level(noises, &mut self.cache, &mut self.prelim_cache, x, z)
791 }
792
793 fn get_aquifer_status(&mut self, index: usize, noises: &N) -> FluidStatus {
795 if let Some(status) = self.status_cache[index] {
796 return status;
797 }
798
799 let loc = self.location_cache[index];
800 let x = unpack_x(loc);
801 let y = unpack_y(loc);
802 let z = unpack_z(loc);
803 let status = self.compute_fluid(x, y, z, noises);
804 self.status_cache[index] = Some(status);
805 status
806 }
807
808 fn compute_fluid(&mut self, x: i32, y: i32, z: i32, noises: &N) -> FluidStatus {
810 let gf = global_fluid(
811 y,
812 self.lava_floor,
813 self.sea_level,
814 self.lava_id,
815 self.default_fluid_id,
816 );
817 let mut lowest_surface = i32::MAX;
818 let top_of_cell = y + Y_SPACING;
819 let bottom_of_cell = y - Y_SPACING;
820 let mut surface_under_global = false;
821
822 for offset in &SURFACE_SAMPLING_OFFSETS {
823 let sx = x + offset[0] * 16; let sz = z + offset[1] * 16;
825
826 let preliminary = cached_preliminary_surface_level(
827 noises,
828 &mut self.cache,
829 &mut self.prelim_cache,
830 sx,
831 sz,
832 );
833 let adjusted = preliminary + 8;
834
835 let is_center = offset[0] == 0 && offset[1] == 0;
836
837 if is_center && bottom_of_cell > adjusted {
838 return gf;
839 }
840
841 let top_pokes_above = top_of_cell > adjusted;
842 if top_pokes_above || is_center {
843 let gf_at_surface = global_fluid(
844 adjusted,
845 self.lava_floor,
846 self.sea_level,
847 self.lava_id,
848 self.default_fluid_id,
849 );
850 let has_fluid = adjusted < gf_at_surface.fluid_level;
851 if has_fluid {
852 if is_center {
853 surface_under_global = true;
854 }
855 if top_pokes_above {
856 return gf_at_surface;
857 }
858 }
859 }
860
861 if preliminary < lowest_surface {
862 lowest_surface = preliminary;
863 }
864 }
865
866 let fluid_level =
867 self.compute_surface_level(x, y, z, noises, gf, lowest_surface, surface_under_global);
868 let fluid_type = self.compute_fluid_type(x, y, z, noises, gf, fluid_level);
869 FluidStatus {
870 fluid_level,
871 fluid_type,
872 }
873 }
874
875 #[expect(
876 clippy::too_many_arguments,
877 reason = "matches vanilla NoiseBasedAquifer.computeSurface signature"
878 )]
879 fn compute_surface_level(
880 &mut self,
881 x: i32,
882 y: i32,
883 z: i32,
884 noises: &N,
885 gf: FluidStatus,
886 lowest_surface: i32,
887 surface_under_global: bool,
888 ) -> i32 {
889 let (partially_flooded, fully_flooded) =
890 if is_deep_dark_region(noises, &mut self.cache, x, y, z) {
891 (-1.0, -1.0)
892 } else {
893 let dist_below = lowest_surface + 8 - y;
894 let floodedness_factor = if surface_under_global {
895 map_clamped(f64::from(dist_below), 0.0, 64.0, 1.0, 0.0)
896 } else {
897 0.0
898 };
899
900 self.cache.ensure(x, z, noises);
901 let floodedness_noise = clamp(
902 noises.router_fluid_level_floodedness(&mut self.cache, x, y, z),
903 -1.0,
904 1.0,
905 );
906
907 let fully_threshold = map(floodedness_factor, 1.0, 0.0, -0.3, 0.8);
908 let partially_threshold = map(floodedness_factor, 1.0, 0.0, -0.8, 0.4);
909
910 (
911 floodedness_noise - partially_threshold,
912 floodedness_noise - fully_threshold,
913 )
914 };
915
916 if fully_flooded > 0.0 {
917 gf.fluid_level
918 } else if partially_flooded > 0.0 {
919 self.compute_randomized_fluid_surface_level(x, y, z, noises, lowest_surface)
920 } else {
921 WAY_BELOW_MIN_Y
922 }
923 }
924
925 fn compute_randomized_fluid_surface_level(
926 &mut self,
927 x: i32,
928 y: i32,
929 z: i32,
930 noises: &N,
931 lowest_surface: i32,
932 ) -> i32 {
933 let cell_x = x.div_euclid(16);
934 let cell_y = y.div_euclid(40);
935 let cell_z = z.div_euclid(16);
936 let cell_middle_y = cell_y * 40 + 20;
937
938 self.cache.ensure(cell_x, cell_z, noises);
940 let spread =
941 noises.router_fluid_level_spread(&mut self.cache, cell_x, cell_y, cell_z) * 10.0;
942 let spread_quantized = quantize(spread, 3);
943 let target = cell_middle_y + spread_quantized;
944
945 lowest_surface.min(target)
946 }
947
948 fn compute_fluid_type(
949 &mut self,
950 x: i32,
951 y: i32,
952 z: i32,
953 noises: &N,
954 gf: FluidStatus,
955 fluid_level: i32,
956 ) -> BlockStateId {
957 if fluid_level <= -10 && fluid_level != WAY_BELOW_MIN_Y && gf.fluid_type != self.lava_id {
958 let cell_x = x.div_euclid(64);
959 let cell_y = y.div_euclid(40);
960 let cell_z = z.div_euclid(64);
961 self.cache.ensure(cell_x, cell_z, noises);
962 let lava_noise = noises.router_lava(&mut self.cache, cell_x, cell_y, cell_z);
963 if lava_noise.abs() > 0.3 {
964 return self.lava_id;
965 }
966 }
967 gf.fluid_type
968 }
969
970 #[expect(
974 clippy::too_many_arguments,
975 reason = "matches vanilla NoiseBasedAquifer.calculatePressure signature"
976 )]
977 fn calculate_pressure(
978 &mut self,
979 noises: &N,
980 x: i32,
981 y: i32,
982 z: i32,
983 barrier_noise: &mut f64,
984 s1: FluidStatus,
985 s2: FluidStatus,
986 ) -> f64 {
987 let f1 = s1.at(y);
988 let f2 = s2.at(y);
989 let f1_is_lava = f1 == Some(self.lava_id);
990 let f2_is_lava = f2 == Some(self.lava_id);
991 let f1_is_water = f1 == Some(self.water_id);
992 let f2_is_water = f2 == Some(self.water_id);
993
994 if (f1_is_lava && f2_is_water) || (f1_is_water && f2_is_lava) {
996 return 2.0;
997 }
998
999 let fluid_y_diff = (s1.fluid_level - s2.fluid_level).abs();
1000 if fluid_y_diff == 0 {
1001 return 0.0;
1002 }
1003
1004 let avg_fluid_y = 0.5 * f64::from(s1.fluid_level + s2.fluid_level);
1005 let above_avg = f64::from(y) + 0.5 - avg_fluid_y;
1006 let base = f64::from(fluid_y_diff) / 2.0;
1007 let edge_dist = base - above_avg.abs();
1008
1009 let gradient = if above_avg > 0.0 {
1010 if edge_dist > 0.0 {
1011 edge_dist / 1.5
1012 } else {
1013 edge_dist / 2.5
1014 }
1015 } else {
1016 let center = 3.0 + edge_dist;
1017 if center > 0.0 {
1018 center / 3.0
1019 } else {
1020 center / 10.0
1021 }
1022 };
1023
1024 let noise_val = if !(-2.0..=2.0).contains(&gradient) {
1025 0.0
1026 } else if barrier_noise.is_nan() {
1027 self.cache.ensure(x, z, noises);
1028 let n = noises.router_barrier(&mut self.cache, x, y, z);
1029 *barrier_noise = n;
1030 n
1031 } else {
1032 *barrier_noise
1033 };
1034
1035 2.0 * (noise_val + gradient)
1036 }
1037}
1038
1039#[inline]
1041fn quantize(value: f64, quantum: i32) -> i32 {
1042 let q = f64::from(quantum);
1043 (value / q).floor() as i32 * quantum
1044}
1045
1046pub fn preliminary_surface_level<N: DimensionNoises>(
1051 noises: &N,
1052 cache: &mut N::ColumnCache,
1053 x: i32,
1054 z: i32,
1055) -> i32 {
1056 let qx = (x >> 2) << 2;
1058 let qz = (z >> 2) << 2;
1059 cache.ensure(qx, qz, noises);
1060 noises
1062 .router_preliminary_surface_level(cache, qx, 0, qz)
1063 .floor() as i32
1064}
1065
1066fn cached_preliminary_surface_level<N: DimensionNoises>(
1071 noises: &N,
1072 cache: &mut N::ColumnCache,
1073 prelim_cache: &mut FxHashMap<(i32, i32), i32>,
1074 x: i32,
1075 z: i32,
1076) -> i32 {
1077 let key = ((x >> 2) << 2, (z >> 2) << 2);
1078 if let Some(&level) = prelim_cache.get(&key) {
1079 return level;
1080 }
1081 let level = preliminary_surface_level(noises, cache, x, z);
1082 prelim_cache.insert(key, level);
1083 level
1084}