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}