Skip to main content

steel_worldgen/biomes/
nether_climate_sampler.rs

1//! Climate sampler for nether world generation.
2//!
3//! Uses the compiled nether density functions from steel-registry. The nether's
4//! noise router is much simpler than the overworld — only temperature and vegetation
5//! are real density functions; all other climate parameters are constant 0.
6//!
7//! The nether uses `legacy_random_source=true` which means noise generators are
8//! created with Java's `java.util.Random` (LCG) instead of Xoroshiro, and
9//! temperature/vegetation use hardcoded parameters `(-7, [1.0, 1.0])` via
10//! `NormalNoise.createLegacyNetherBiome()`. The shift (offset) noise is effectively
11//! zeroed with params `(0, [0.0])`. See `RandomState.java:55-76`.
12
13use steel_utils::climate::{TargetPoint, quantize_coord};
14use steel_utils::random::RandomSource;
15use steel_utils::random::legacy_random::LegacyRandom;
16use steel_worldgen::density_functions::nether::{self, NetherColumnCache, NetherNoises};
17use steel_worldgen::noise::{BlendedNoise, NormalNoise};
18
19/// Climate sampler for the nether using compiled density functions.
20///
21/// Only evaluates temperature and vegetation from the nether noise router.
22/// Continentalness, erosion, depth, and weirdness are always 0 in the nether.
23pub struct NetherClimateSampler {
24    /// Noise generators needed by the nether density functions.
25    noises: Box<NetherNoises>,
26}
27
28impl NetherClimateSampler {
29    /// Create a new nether climate sampler with the given seed.
30    ///
31    /// Uses the legacy random source path matching vanilla's `RandomState` with
32    /// `useLegacyRandomSource=true`:
33    /// - Temperature: `LegacyRandomSource(seed + 0)`, `createLegacyNetherBiome`, params `(-7, [1.0, 1.0])`
34    /// - Vegetation: `LegacyRandomSource(seed + 1)`, `createLegacyNetherBiome`, params `(-7, [1.0, 1.0])`
35    /// - Offset (shift): `random.fromHashOf("minecraft:offset")`, regular create, params `(0, [0.0])`
36    #[must_use]
37    pub fn new(seed: u64) -> Self {
38        // Temperature: LegacyRandomSource(seed + 0), legacy nether biome path
39        let mut temp_rng = RandomSource::Legacy(LegacyRandom::from_seed(seed));
40        let n_temperature = NormalNoise::create_legacy_nether_biome(&mut temp_rng, -7, &[1.0, 1.0]);
41
42        // Vegetation: LegacyRandomSource(seed + 1), legacy nether biome path
43        let mut veg_rng = RandomSource::Legacy(LegacyRandom::from_seed(seed.wrapping_add(1)));
44        let n_vegetation = NormalNoise::create_legacy_nether_biome(&mut veg_rng, -7, &[1.0, 1.0]);
45
46        // BlendedNoise: nether uses legacy random with seed + 0 (useLegacyRandomSource=true)
47        let mut blended_rng = RandomSource::Legacy(LegacyRandom::from_seed(seed));
48        let blended_noise = BlendedNoise::new(&mut blended_rng, 0.25, 0.375, 80.0, 60.0, 8.0);
49
50        let noises = NetherNoises {
51            n_nether__temperature: n_temperature,
52            n_nether__vegetation: n_vegetation,
53            blended_noise,
54        };
55
56        Self {
57            noises: Box::new(noises),
58        }
59    }
60
61    /// Pre-populate a column cache's flat-noise grid for a chunk's quart columns.
62    ///
63    /// Mirrors [`OverworldClimateSampler::init_column_grid`]: lets the biome
64    /// stage reuse vanilla's `NoiseChunk.FlatCache` grid instead of recomputing
65    /// flat (xz-only) climate noise for every quart cell.
66    pub fn init_column_grid(
67        &self,
68        cache: &mut NetherColumnCache,
69        chunk_block_x: i32,
70        chunk_block_z: i32,
71    ) {
72        cache.init_grid(chunk_block_x, chunk_block_z, &self.noises);
73    }
74
75    /// Sample climate at a quart position.
76    ///
77    /// The `cache` holds column-level (xz-only) precomputed values.
78    #[must_use]
79    pub fn sample(
80        &self,
81        quart_x: i32,
82        quart_y: i32,
83        quart_z: i32,
84        cache: &mut NetherColumnCache,
85    ) -> TargetPoint {
86        let block_x = quart_x << 2;
87        let block_y = quart_y << 2;
88        let block_z = quart_z << 2;
89
90        cache.ensure(block_x, block_z, &self.noises);
91
92        let block_x = f64::from(block_x);
93        let block_y = f64::from(block_y);
94        let block_z = f64::from(block_z);
95
96        let temp =
97            nether::router_temperature(&self.noises, cache, block_x, block_y, block_z) as f32;
98        let humidity =
99            nether::router_vegetation(&self.noises, cache, block_x, block_y, block_z) as f32;
100
101        // Nether noise router has continentalness, erosion, depth, ridges all as constant 0.
102        TargetPoint::new(
103            quantize_coord(f64::from(temp)),
104            quantize_coord(f64::from(humidity)),
105            0,
106            0,
107            0,
108            0,
109        )
110    }
111}