#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CloudType {
AmmoniaIce,
AmmoniumHydrosulfide,
WaterIce,
DeepWater,
HighAltitudeHaze,
}
pub struct CloudLayer {
pub cloud_type: CloudType,
pub base_altitude_m: f64,
pub thickness_m: f64,
pub coverage: f64,
pub density: f64,
pub wind_speed_m_s: f64,
pub wind_direction: [f64; 2],
pub droplet_radius_um: f64,
pub ice_fraction: f64,
}
impl CloudLayer {
pub fn ammonia_ice() -> Self {
Self {
cloud_type: CloudType::AmmoniaIce,
base_altitude_m: 30_000.0,
thickness_m: 15_000.0,
coverage: 0.85,
density: 0.6,
wind_speed_m_s: 150.0,
wind_direction: [1.0, 0.0],
droplet_radius_um: 5.0,
ice_fraction: 1.0,
}
}
pub fn ammonium_hydrosulfide() -> Self {
Self {
cloud_type: CloudType::AmmoniumHydrosulfide,
base_altitude_m: 10_000.0,
thickness_m: 20_000.0,
coverage: 0.75,
density: 0.7,
wind_speed_m_s: 120.0,
wind_direction: [0.9, 0.3],
droplet_radius_um: 8.0,
ice_fraction: 0.6,
}
}
pub fn water_ice() -> Self {
Self {
cloud_type: CloudType::WaterIce,
base_altitude_m: -20_000.0,
thickness_m: 30_000.0,
coverage: 0.65,
density: 0.5,
wind_speed_m_s: 80.0,
wind_direction: [0.8, 0.4],
droplet_radius_um: 12.0,
ice_fraction: 0.9,
}
}
pub fn deep_water() -> Self {
Self {
cloud_type: CloudType::DeepWater,
base_altitude_m: -80_000.0,
thickness_m: 40_000.0,
coverage: 0.9,
density: 0.95,
wind_speed_m_s: 40.0,
wind_direction: [0.7, 0.5],
droplet_radius_um: 20.0,
ice_fraction: 0.0,
}
}
pub fn high_altitude_haze() -> Self {
Self {
cloud_type: CloudType::HighAltitudeHaze,
base_altitude_m: 50_000.0,
thickness_m: 25_000.0,
coverage: 0.4,
density: 0.08,
wind_speed_m_s: 200.0,
wind_direction: [1.0, 0.1],
droplet_radius_um: 1.0,
ice_fraction: 0.0,
}
}
}
pub struct CloudSystemEndpoint {
pub layers: Vec<CloudLayer>,
}
impl CloudSystemEndpoint {
pub fn jupiter_default() -> Self {
Self {
layers: vec![
CloudLayer::high_altitude_haze(),
CloudLayer::ammonia_ice(),
CloudLayer::ammonium_hydrosulfide(),
CloudLayer::water_ice(),
CloudLayer::deep_water(),
],
}
}
pub fn jupiter_stormy() -> Self {
Self {
layers: vec![
CloudLayer::deep_water(),
CloudLayer::water_ice(),
CloudLayer::ammonia_ice(),
],
}
}
pub fn sample_density(&self, sample_alt: f64) -> f64 {
self.layers
.iter()
.map(|l| {
let alt_dist = (sample_alt - l.base_altitude_m).abs();
let sigma = l.thickness_m * 0.5;
let weight = (-0.5 * (alt_dist / sigma.max(1.0)).powi(2)).exp();
let mie_size_factor = l.droplet_radius_um / 10.0;
let phase_contrib = l.ice_fraction * 0.7 + (1.0 - l.ice_fraction) * 1.0;
l.density * l.coverage * weight * mie_size_factor * phase_contrib
})
.sum::<f64>()
/ self.layers.len().max(1) as f64
}
pub fn wind_transport(&self) -> f64 {
self.layers
.iter()
.map(|l| {
let wind_dir_len = l.wind_direction[0].hypot(l.wind_direction[1]);
l.wind_speed_m_s * wind_dir_len.max(0.01) * l.coverage
})
.sum::<f64>()
/ self.layers.len().max(1) as f64
}
pub fn optical_depth(&self) -> f64 {
self.layers
.iter()
.map(|l| {
let lwc = 0.20 * l.density;
let r_eff = l.droplet_radius_um * 1e-6;
let rho_w = 1000.0;
let tau = 1.5 * 2.0 * lwc * l.thickness_m * l.coverage / (rho_w * r_eff);
let ice_correction = 1.0 - 0.15 * l.ice_fraction;
tau * ice_correction
})
.sum()
}
pub fn total_thickness(&self) -> f64 {
self.layers.iter().map(|l| l.thickness_m * l.coverage).sum()
}
}