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}