Skip to main content

steel_worldgen/noise/
end_islands.rs

1//! End islands terrain generation algorithm.
2//!
3//! Matches vanilla's `EndIslandFunction`, the simplex-noise-driven outer-island
4//! part of The End density graph.
5//!
6//! The simplex permutation uses the world seed after `consumeCount(17292)`.
7//!
8//! Result range: `[-0.84375, 0.5625]`.
9
10use crate::random::Random;
11use crate::random::legacy_random::LegacyRandom;
12
13use super::SimplexNoise;
14
15/// Threshold for simplex noise below which an island is spawned.
16///
17const ISLAND_THRESHOLD: f32 = -0.9;
18
19/// End islands density function.
20///
21/// Unlike overworld/nether density functions which are transpiled into native Rust,
22/// this is used directly at runtime because it's a self-contained leaf algorithm
23/// (simplex noise + neighbor loop) with no density function tree to transpile.
24#[derive(Debug, Clone)]
25pub struct EndIslands {
26    island_noise: SimplexNoise,
27}
28
29impl EndIslands {
30    /// Create a new `EndIslands` with the given world seed.
31    ///
32    /// Matches `EndIslandFunction.compileSampler` and `RandomState`'s compile context.
33    #[must_use]
34    pub fn new(seed: u64) -> Self {
35        let mut rng = LegacyRandom::from_seed(seed);
36        rng.consume_count(17292);
37        let island_noise = SimplexNoise::new_without_noise_offset(&mut rng);
38        Self { island_noise }
39    }
40
41    /// Sample the density value at block coordinates.
42    ///
43    /// Converts block coordinates to section coordinates internally (divides by 8).
44    #[must_use]
45    pub fn sample(&self, block_x: f64, _block_y: f64, block_z: f64) -> f64 {
46        let block_x = block_x as i32;
47        let block_z = block_z as i32;
48        f64::from(
49            (Self::get_height_value(&self.island_noise, block_x / 8, block_z / 8) - 8.0_f32)
50                / 128.0_f32,
51        )
52    }
53
54    /// Compute the height value at section coordinates.
55    /// Takes section coordinates (block position / 8).
56    fn get_height_value(island_noise: &SimplexNoise, section_x: i32, section_z: i32) -> f32 {
57        let chunk_x = section_x / 2;
58        let chunk_z = section_z / 2;
59        let sub_section_x = section_x % 2;
60        let sub_section_z = section_z % 2;
61
62        let mut doffs = -100.0_f32;
63
64        // Check 25×25 neighborhood for island contributions
65        for xo in -12..=12 {
66            for zo in -12..=12 {
67                let total_chunk_x = i64::from(chunk_x) + i64::from(xo);
68                let total_chunk_z = i64::from(chunk_z) + i64::from(zo);
69
70                if total_chunk_x * total_chunk_x + total_chunk_z * total_chunk_z > 4096
71                    && island_noise.get_value_2d(total_chunk_x as f64, total_chunk_z as f64)
72                        < ISLAND_THRESHOLD
73                {
74                    let island_size = ((total_chunk_x as f32).abs() * 3439.0
75                        + (total_chunk_z as f32).abs() * 147.0)
76                        % 13.0
77                        + 9.0;
78                    let xd = sub_section_x as f32 - (xo * 2) as f32;
79                    let zd = sub_section_z as f32 - (zo * 2) as f32;
80                    let new_doffs =
81                        (100.0_f32 - (xd * xd + zd * zd).sqrt() * island_size).clamp(-100.0, 80.0);
82                    if new_doffs > doffs {
83                        doffs = new_doffs;
84                    }
85                }
86            }
87        }
88
89        doffs
90    }
91}