use std::sync::LazyLock; use serde::Deserialize; use crate::{noise::simplex::OctaveSimplexNoiseSampler, random::legacy_rand::LegacyRand}; /// Global noise sampler for biome temperature variation. pub static TEMPERATURE_NOISE: LazyLock = LazyLock::new(|| { let mut rand = LegacyRand::from_seed(1234); OctaveSimplexNoiseSampler::new(&mut rand, &[0]) }); /// Global noise sampler for frozen ocean biome adjustments. pub static FROZEN_OCEAN_NOISE: LazyLock = LazyLock::new(|| { let mut rand = LegacyRand::from_seed(3456); OctaveSimplexNoiseSampler::new(&mut rand, &[-2, -1, 0]) }); /// Global noise sampler for foliage-based temperature adjustments. pub static FOLIAGE_NOISE: LazyLock = LazyLock::new(|| { let mut rand = LegacyRand::from_seed(2345); OctaveSimplexNoiseSampler::new(&mut rand, &[0]) }); /// Modifiers that adjust the base biome temperature. #[derive(Clone, Deserialize, Copy, Hash, PartialEq, Eq, Debug)] #[serde(rename_all = "UPPERCASE")] pub enum TemperatureModifier { /// No temperature modification. None, /// Frozen biome adjustment, typically used for frozen oceans and snowy areas. Frozen, } impl TemperatureModifier { /// Applies the temperature modifier to a given position and base temperature. /// /// # Parameters /// - `x`: X coordinate in the world. /// - `z`: Z coordinate in the world. /// - `temperature`: The base biome temperature. /// /// # Returns /// - The modified temperature as an `f32`. pub fn convert_temperature(&self, x: f64, z: f64, temperature: f32) -> f32 { match self { Self::None => temperature, Self::Frozen => { let frozen_ocean_sample = FROZEN_OCEAN_NOISE.sample(x * 0.05, z * 0.05, false) * 7.0; let foliage_sample = FOLIAGE_NOISE.sample(x * 0.2, z * 0.2, false); let threshold = frozen_ocean_sample + foliage_sample; if threshold < 0.3 { let foliage_sample = FOLIAGE_NOISE.sample(x * 0.09, z * 0.09, false); if foliage_sample < 0.8 { return 0.2f32; } } temperature } } } } /// Represents weather information for a biome, including temperature and precipitation. #[derive(Clone, Debug)] pub struct Weather { has_precipitation: bool, /// Base temperature of the biome. temperature: f32, /// Modifier affecting the base temperature. temperature_modifier: TemperatureModifier, /// Rate of rainfall or snowfall in the biome. #[expect(dead_code)] downfall: f32, } impl Weather { /// Creates a new `Weather` instance. /// /// # Parameters /// - `has_precipitation`: Whether the biome has precipitation. /// - `temperature`: Base temperature of the biome. /// - `temperature_modifier`: Modifier affecting the temperature. /// - `downfall`: Amount of rainfall or snowfall. #[must_use] pub const fn new( has_precipitation: bool, temperature: f32, temperature_modifier: TemperatureModifier, downfall: f32, ) -> Self { Self { has_precipitation, temperature, temperature_modifier, downfall, } } /// Returns the raw base temperature of the biome, without any height or modifier adjustment. #[must_use] pub const fn base_temperature(&self) -> f32 { self.temperature } /// Computes the effective temperature at a given position. /// /// # Parameters /// - `x`, `z`: World coordinates. /// - `y`: Y-level of the position. /// - `sea_level`: Sea level in the world. /// /// # Returns /// - The temperature at the position as `f32`. /// /// # Notes /// - This function is computationally expensive and should be cached. /// - Temperature is influenced by `TemperatureModifier` and noise samplers. pub fn compute_temperature(&self, x: f64, y: i32, z: f64, sea_level: i32) -> f32 { let modified_temperature = self.temperature_modifier .convert_temperature(x, z, self.temperature); let offset_sea_level = sea_level + 17; if y > offset_sea_level { let temperature_noise = (TEMPERATURE_NOISE.sample(x / 8.0, z / 8.0, false) * 8.0) as f32; modified_temperature - (temperature_noise + y as f32 - offset_sea_level as f32) * 0.05f32 / 40.0f32 } else { modified_temperature } } #[must_use] pub fn warm_enough_to_rain(&self, x: i32, y: i32, z: i32, sea_level: i32) -> bool { self.compute_temperature(f64::from(x), y, f64::from(z), sea_level) >= 0.15 } #[must_use] pub fn is_rain_at(&self, x: i32, y: i32, z: i32, sea_level: i32) -> bool { self.has_precipitation && self.warm_enough_to_rain(x, y, z, sea_level) } } #[cfg(test)] mod tests { use super::{TemperatureModifier, Weather}; #[test] fn precipitation_controls_rain() { let weather = Weather::new(false, 1.0, TemperatureModifier::None, 0.0); assert!(!weather.is_rain_at(0, 64, 0, 63)); } #[test] fn warm_precipitation_is_rain() { let weather = Weather::new(true, 1.0, TemperatureModifier::None, 0.0); assert!(weather.is_rain_at(0, 64, 0, 63)); } #[test] fn cold_precipitation_is_snow() { let weather = Weather::new(true, 0.0, TemperatureModifier::None, 0.0); assert!(!weather.is_rain_at(0, 64, 0, 63)); } }