Skip to main content

steel_utils/climate/
mod.rs

1//! Climate system for biome selection in world generation.
2//!
3//! This module implements vanilla Minecraft's Climate system for biome lookup.
4//! Climate parameters (temperature, humidity, etc.) are quantized to long integers
5//! and used to find the best matching biome from a parameter space.
6//!
7//! # Key Types
8//!
9//! - [`TargetPoint`] - A sampled climate point with 6 quantized parameters
10//! - [`Parameter`] - A parameter range (min/max) for biome matching
11//! - [`ParameterPoint`] - Full biome parameter specification
12//! - [`ParameterList`] - Collection of biomes with their parameter points
13
14mod parameter_list;
15pub(crate) mod types;
16
17pub use parameter_list::ParameterList;
18pub use types::{Parameter, ParameterPoint, TargetPoint};
19
20/// Quantization factor used to convert floats to longs.
21/// This is the exact value from vanilla Climate.java.
22pub const QUANTIZATION_FACTOR: f32 = 10000.0;
23
24/// Number of climate parameters (temperature, humidity, continentalness, erosion, depth, weirdness, + offset).
25pub const PARAMETER_COUNT: usize = 7;
26
27/// Quantize a coordinate value to a long integer.
28///
29/// This matches vanilla's `Climate.quantizeCoord()` exactly:
30/// `(long)(coord * 10000.0F)`
31///
32/// **CRITICAL**: The input is cast to f32 first, then multiplied, then cast to i64.
33/// This ensures bit-exact matching with vanilla Java's float behavior.
34#[inline]
35#[must_use]
36pub fn quantize_coord(coord: f64) -> i64 {
37    ((coord as f32) * QUANTIZATION_FACTOR) as i64
38}
39
40/// Unquantize a long integer back to a float.
41///
42/// This matches vanilla's `Climate.unquantizeCoord()`:
43/// `(float)coord / 10000.0F`
44#[inline]
45#[must_use]
46pub fn unquantize_coord(coord: i64) -> f32 {
47    coord as f32 / QUANTIZATION_FACTOR
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_quantize_coord() {
56        assert_eq!(quantize_coord(0.0), 0);
57        assert_eq!(quantize_coord(1.0), 10000);
58        assert_eq!(quantize_coord(-1.0), -10000);
59        assert_eq!(quantize_coord(0.5), 5000);
60        assert_eq!(quantize_coord(-0.5), -5000);
61    }
62
63    #[test]
64    fn test_unquantize_coord() {
65        assert!((unquantize_coord(0) - 0.0).abs() < 1e-6);
66        assert!((unquantize_coord(10000) - 1.0).abs() < 1e-6);
67        assert!((unquantize_coord(-10000) - -1.0).abs() < 1e-6);
68    }
69
70    #[test]
71    fn test_quantize_roundtrip() {
72        let values = [0.0, 0.5, -0.5, 1.0, -1.0, 0.123, -0.456];
73        for v in values {
74            let quantized = quantize_coord(v);
75            let unquantized = unquantize_coord(quantized);
76            // Allow small error due to float precision
77            assert!(
78                (v as f32 - unquantized).abs() < 0.0001,
79                "Roundtrip failed for {v}: got {unquantized}",
80            );
81        }
82    }
83}