jupiters 0.0.3

Jupiter celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
#[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()
    }
}