use bevy::prelude::*;
pub const LAPSE_RATE_C_PER_M: f32 = -0.0065;
#[derive(Resource, Clone, Debug, Reflect)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Climate {
pub mean_temperature_c: f32,
pub annual_amplitude_c: f32,
pub diurnal_amplitude_c: f32,
pub warmest_day: u16,
pub mean_humidity: f32,
pub wet_hour_probability: f32,
pub storm_share: f32,
pub fog_share: f32,
pub dust_share: f32,
pub prevailing_wind: Vec2,
pub mean_wind_ms: f32,
pub reference_elevation_m: f32,
pub seed: u64,
}
impl Default for Climate {
fn default() -> Self {
Self::temperate()
}
}
impl Climate {
#[allow(clippy::too_many_arguments)]
const fn preset(
mean: f32,
annual: f32,
diurnal: f32,
humidity: f32,
wet: f32,
storm: f32,
fog: f32,
dust: f32,
wind: f32,
) -> Self {
Self {
mean_temperature_c: mean,
annual_amplitude_c: annual,
diurnal_amplitude_c: diurnal,
warmest_day: 200,
mean_humidity: humidity,
wet_hour_probability: wet,
storm_share: storm,
fog_share: fog,
dust_share: dust,
prevailing_wind: Vec2::X,
mean_wind_ms: wind,
reference_elevation_m: 0.0,
seed: 0,
}
}
pub const fn temperate() -> Self {
Self::preset(11.0, 9.0, 5.0, 0.75, 0.35, 0.05, 0.08, 0.0, 4.0)
}
pub const fn alpine() -> Self {
Self::preset(2.0, 10.0, 7.0, 0.70, 0.40, 0.04, 0.06, 0.0, 6.0)
}
pub const fn desert() -> Self {
Self::preset(24.0, 9.0, 14.0, 0.25, 0.03, 0.01, 0.0, 0.08, 5.0)
}
pub const fn tropical() -> Self {
Self::preset(27.0, 2.0, 6.0, 0.85, 0.55, 0.20, 0.03, 0.0, 3.0)
}
pub const fn tundra() -> Self {
Self::preset(-8.0, 18.0, 4.0, 0.80, 0.25, 0.0, 0.05, 0.0, 7.0)
}
pub const fn with_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
pub fn warmest_day_for(&self, southern: bool) -> f32 {
f32::from(self.warmest_day) + if southern { 182.0 } else { 0.0 }
}
pub fn base_temperature_c(&self, day_of_year: f32, hour: f32, southern: bool) -> f32 {
use std::f32::consts::TAU;
let warmest = self.warmest_day_for(southern);
self.mean_temperature_c
+ self.annual_amplitude_c * (TAU * (day_of_year - warmest) / 365.25).cos()
+ self.diurnal_amplitude_c * (TAU * (hour - 15.0) / 24.0).cos()
}
pub fn lapse_c(&self, elevation_m: f32) -> f32 {
LAPSE_RATE_C_PER_M * (elevation_m - self.reference_elevation_m)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn temperature_harmonics_hit_the_research_targets() {
let c = Climate::temperate();
assert!((c.base_temperature_c(200.0, 15.0, false) - 25.0).abs() < 1e-4);
assert!((c.base_temperature_c(200.0, 3.0, false) - 15.0).abs() < 1e-4);
assert!((c.base_temperature_c(17.0, 3.0, false) - -3.0).abs() < 1e-3);
assert!((c.lapse_c(1000.0) - -6.5).abs() < 1e-5);
}
#[test]
fn the_southern_hemisphere_has_its_summer_in_january() {
let c = Climate::temperate();
assert!(c.base_temperature_c(17.0, 15.0, true) > c.base_temperature_c(200.0, 15.0, true));
}
}