satellitesfactory 0.0.3

Satellite factory — classify, build and catalogue natural satellites for any planetary system: Solar System moons (Moon, Galileans, Titan, Triton…) or custom configurations.
Documentation
use crate::config::parameters::*;
use crate::engine::orbits::OrbitalElements;
use crate::types::captured::{CapturedComposition, CapturedSatellite};
use crate::types::co_orbital_moon::{CoOrbitalMoon, CoOrbitalType};
use crate::types::exomoon::Exomoon;
use crate::types::icy_moon::IcyMoon;
use crate::types::regular::{RegularSatellite, SatelliteClass};
use crate::types::ring_moon::{RingAssociation, RingMoon};
use crate::types::subsurface_ocean_moon::SubsurfaceOceanMoon;
use crate::types::volcanic_moon::VolcanicMoon;

pub struct SystemGeneratorConfig {
    pub parent_name: String,
    pub parent_mass: f64,
    pub parent_radius: f64,
    pub n_regular: usize,
    pub n_captured: usize,
    pub n_ring_moons: usize,
    pub n_volcanic: usize,
    pub n_icy: usize,
    pub n_ocean: usize,
    pub n_exomoon: usize,
    pub n_co_orbital: usize,
    pub inner_edge: f64,
    pub outer_edge: f64,
    pub seed: u64,
}

impl SystemGeneratorConfig {
    pub fn new(
        parent_name: &str,
        parent_mass: f64,
        parent_radius: f64,
        inner_edge: f64,
        outer_edge: f64,
    ) -> Self {
        Self {
            parent_name: parent_name.to_string(),
            parent_mass,
            parent_radius,
            n_regular: 0,
            n_captured: 0,
            n_ring_moons: 0,
            n_volcanic: 0,
            n_icy: 0,
            n_ocean: 0,
            n_exomoon: 0,
            n_co_orbital: 0,
            inner_edge,
            outer_edge,
            seed: 42,
        }
    }

    pub fn with_counts(
        mut self,
        n_regular: usize,
        n_captured: usize,
        n_ring_moons: usize,
        n_volcanic: usize,
        n_icy: usize,
    ) -> Self {
        self.n_regular = n_regular;
        self.n_captured = n_captured;
        self.n_ring_moons = n_ring_moons;
        self.n_volcanic = n_volcanic;
        self.n_icy = n_icy;
        self
    }

    pub fn with_exotic(mut self, n_ocean: usize, n_exomoon: usize, n_co_orbital: usize) -> Self {
        self.n_ocean = n_ocean;
        self.n_exomoon = n_exomoon;
        self.n_co_orbital = n_co_orbital;
        self
    }

    pub fn with_seed(mut self, seed: u64) -> Self {
        self.seed = seed;
        self
    }
}

#[derive(Debug, Clone)]
pub enum GeneratedSatellite {
    Regular(RegularSatellite),
    Captured(CapturedSatellite),
    Ring(RingMoon),
    Volcanic(VolcanicMoon),
    Icy(IcyMoon),
    SubsurfaceOcean(SubsurfaceOceanMoon),
    Exo(Exomoon),
    CoOrbital(CoOrbitalMoon),
}

fn simple_hash(seed: u64, index: u64) -> f64 {
    let mut x = seed
        .wrapping_mul(6364136223846793005)
        .wrapping_add(index.wrapping_mul(1442695040888963407));
    x ^= x >> 33;
    x = x.wrapping_mul(0xff51afd7ed558ccd);
    x ^= x >> 33;
    (x as f64) / (u64::MAX as f64)
}

pub fn generate_system(config: &SystemGeneratorConfig) -> Vec<GeneratedSatellite> {
    let mut satellites = Vec::new();
    let range = config.outer_edge - config.inner_edge;

    for idx in 0..config.n_regular {
        let t = simple_hash(config.seed, idx as u64);
        let a = config.inner_edge + range * (0.2 + 0.6 * t);
        let mass =
            config.parent_mass * 1e-4 * (0.01 + 0.99 * simple_hash(config.seed + 1, idx as u64));
        let density = 1500.0 + 2000.0 * simple_hash(config.seed + 2, idx as u64);
        let radius = (3.0 * mass / (4.0 * std::f64::consts::PI * density)).cbrt();
        let e = 0.001 + 0.01 * simple_hash(config.seed + 3, idx as u64);
        let inc = 0.5 * simple_hash(config.seed + 4, idx as u64);
        let ice_frac = 0.3 * simple_hash(config.seed + 5, idx as u64);
        let rock_frac = 0.5 + 0.3 * simple_hash(config.seed + 6, idx as u64);

        satellites.push(GeneratedSatellite::Regular(RegularSatellite {
            name: format!("{}-R{}", config.parent_name, idx + 1),
            parent: config.parent_name.clone(),
            mass,
            radius,
            j2: 0.0,
            obliquity: 0.0,
            elements: OrbitalElements::new(a, e, inc, 0.0, 0.0, 0.0),
            class: SatelliteClass::Regular,
            albedo: 0.2 + 0.6 * simple_hash(config.seed + 7, idx as u64),
            ice_fraction: ice_frac,
            rock_fraction: rock_frac.min(1.0 - ice_frac),
        }));
    }

    for idx in 0..config.n_captured {
        let t = simple_hash(config.seed + 100, idx as u64);
        let a = config.outer_edge * (1.0 + 2.0 * t);
        let mass = 1e15 + 1e18 * simple_hash(config.seed + 101, idx as u64);
        let density = 1200.0 + 1500.0 * simple_hash(config.seed + 102, idx as u64);
        let radius = (3.0 * mass / (4.0 * std::f64::consts::PI * density)).cbrt();
        let e = 0.1 + 0.4 * simple_hash(config.seed + 103, idx as u64);
        let inc = std::f64::consts::PI * simple_hash(config.seed + 104, idx as u64);
        let retrograde = simple_hash(config.seed + 105, idx as u64) > 0.5;

        satellites.push(GeneratedSatellite::Captured(CapturedSatellite {
            name: format!("{}-C{}", config.parent_name, idx + 1),
            parent: config.parent_name.clone(),
            mass,
            radius,
            j2: 0.0,
            obliquity: 0.0,
            elements: OrbitalElements::new(a, e, inc, 0.0, 0.0, 0.0),
            albedo: 0.04 + 0.08 * simple_hash(config.seed + 106, idx as u64),
            retrograde,
            capture_velocity_excess: 100.0 + 400.0 * simple_hash(config.seed + 107, idx as u64),
            composition: if simple_hash(config.seed + 108, idx as u64) > 0.5 {
                CapturedComposition::Carbonaceous
            } else {
                CapturedComposition::Silicate
            },
        }));
    }

    for idx in 0..config.n_ring_moons {
        let t = simple_hash(config.seed + 200, idx as u64);
        let a = config.inner_edge * (0.8 + 0.4 * t);
        let mass = 1e14 + 1e17 * simple_hash(config.seed + 201, idx as u64);
        let density = 400.0 + 600.0 * simple_hash(config.seed + 202, idx as u64);
        let radius = (3.0 * mass / (4.0 * std::f64::consts::PI * density)).cbrt();

        satellites.push(GeneratedSatellite::Ring(RingMoon {
            name: format!("{}-S{}", config.parent_name, idx + 1),
            parent: config.parent_name.clone(),
            mass,
            radius,
            elements: OrbitalElements::new(a, 0.001, 0.001, 0.0, 0.0, 0.0),
            albedo: 0.5 + 0.4 * simple_hash(config.seed + 203, idx as u64),
            ring_association: RingAssociation::Shepherd,
            cryovolcanic: false,
            ice_fraction: 0.7 + 0.2 * simple_hash(config.seed + 204, idx as u64),
        }));
    }

    for idx in 0..config.n_volcanic {
        let t = simple_hash(config.seed + 300, idx as u64);
        let a = config.inner_edge + range * (0.1 + 0.3 * t);
        let mass =
            config.parent_mass * 1e-4 * (0.1 + 0.9 * simple_hash(config.seed + 301, idx as u64));
        let density = 2500.0 + 1500.0 * simple_hash(config.seed + 302, idx as u64);
        let radius = (3.0 * mass / (4.0 * std::f64::consts::PI * density)).cbrt();
        let e = 0.003 + 0.01 * simple_hash(config.seed + 303, idx as u64);
        let inc = 0.1 * simple_hash(config.seed + 304, idx as u64);

        satellites.push(GeneratedSatellite::Volcanic(VolcanicMoon {
            name: format!("{}-V{}", config.parent_name, idx + 1),
            parent: config.parent_name.clone(),
            mass,
            radius,
            j2: 0.0,
            obliquity: 0.0,
            elements: OrbitalElements::new(a, e, inc, 0.0, 0.0, 0.0),
            albedo: 0.3 + 0.5 * simple_hash(config.seed + 305, idx as u64),
            silicate_fraction: 0.85 + 0.1 * simple_hash(config.seed + 306, idx as u64),
            tidal_q: 20.0 + 180.0 * simple_hash(config.seed + 307, idx as u64),
            eruption_rate: 1e3 + 1e5 * simple_hash(config.seed + 308, idx as u64),
        }));
    }

    for idx in 0..config.n_icy {
        let t = simple_hash(config.seed + 400, idx as u64);
        let a = config.inner_edge + range * (0.4 + 0.5 * t);
        let mass =
            config.parent_mass * 1e-5 * (0.1 + 0.9 * simple_hash(config.seed + 401, idx as u64));
        let density = 1000.0 + 1500.0 * simple_hash(config.seed + 402, idx as u64);
        let radius = (3.0 * mass / (4.0 * std::f64::consts::PI * density)).cbrt();
        let e = 0.001 + 0.01 * simple_hash(config.seed + 403, idx as u64);
        let inc = 0.2 * simple_hash(config.seed + 404, idx as u64);
        let ice = 0.3 + 0.5 * simple_hash(config.seed + 405, idx as u64);
        let rock = (0.6 * (1.0 - ice)).max(0.1);

        satellites.push(GeneratedSatellite::Icy(IcyMoon {
            name: format!("{}-I{}", config.parent_name, idx + 1),
            parent: config.parent_name.clone(),
            mass,
            radius,
            j2: 0.0,
            obliquity: 0.0,
            elements: OrbitalElements::new(a, e, inc, 0.0, 0.0, 0.0),
            albedo: 0.4 + 0.5 * simple_hash(config.seed + 406, idx as u64),
            ice_fraction: ice,
            rock_fraction: rock,
            surface_temperature: 50.0 + 100.0 * simple_hash(config.seed + 407, idx as u64),
        }));
    }

    for idx in 0..config.n_ocean {
        let t = simple_hash(config.seed + 500, idx as u64);
        let a = config.inner_edge + range * (0.3 + 0.4 * t);
        let mass =
            config.parent_mass * 1e-5 * (0.5 + 0.5 * simple_hash(config.seed + 501, idx as u64));
        let density = 1800.0 + 1200.0 * simple_hash(config.seed + 502, idx as u64);
        let radius = (3.0 * mass / (4.0 * std::f64::consts::PI * density)).cbrt();
        let e = 0.002 + 0.01 * simple_hash(config.seed + 503, idx as u64);
        let inc = 0.1 * simple_hash(config.seed + 504, idx as u64);

        satellites.push(GeneratedSatellite::SubsurfaceOcean(SubsurfaceOceanMoon {
            name: format!("{}-O{}", config.parent_name, idx + 1),
            parent: config.parent_name.clone(),
            mass,
            radius,
            j2: 0.0,
            obliquity: 0.0,
            elements: OrbitalElements::new(a, e, inc, 0.0, 0.0, 0.0),
            albedo: 0.5 + 0.4 * simple_hash(config.seed + 505, idx as u64),
            ice_shell_thickness: 5.0e3 + 50.0e3 * simple_hash(config.seed + 506, idx as u64),
            ocean_salinity: 10.0 + 80.0 * simple_hash(config.seed + 507, idx as u64),
            tidal_q: 20.0 + 100.0 * simple_hash(config.seed + 508, idx as u64),
        }));
    }

    for idx in 0..config.n_exomoon {
        let t = simple_hash(config.seed + 600, idx as u64);
        let a = config.inner_edge + range * (0.2 + 0.6 * t);
        let mass =
            config.parent_mass * 1e-4 * (0.01 + 0.99 * simple_hash(config.seed + 601, idx as u64));
        let density = 1500.0 + 3000.0 * simple_hash(config.seed + 602, idx as u64);
        let radius = (3.0 * mass / (4.0 * std::f64::consts::PI * density)).cbrt();
        let e = 0.001 + 0.05 * simple_hash(config.seed + 603, idx as u64);
        let inc = 0.5 * simple_hash(config.seed + 604, idx as u64);

        satellites.push(GeneratedSatellite::Exo(Exomoon {
            name: format!("{}-X{}", config.parent_name, idx + 1),
            parent: config.parent_name.clone(),
            mass,
            radius,
            j2: 0.0,
            obliquity: 0.0,
            elements: OrbitalElements::new(a, e, inc, 0.0, 0.0, 0.0),
            albedo: 0.1 + 0.7 * simple_hash(config.seed + 605, idx as u64),
            parent_mass: config.parent_mass,
            star_mass: M_SUN,
            star_planet_distance: AU,
        }));
    }

    for idx in 0..config.n_co_orbital {
        let t = simple_hash(config.seed + 700, idx as u64);
        let a = config.inner_edge + range * (0.3 + 0.2 * t);
        let mass = 1e15 + 1e18 * simple_hash(config.seed + 701, idx as u64);
        let density = 500.0 + 800.0 * simple_hash(config.seed + 702, idx as u64);
        let radius = (3.0 * mass / (4.0 * std::f64::consts::PI * density)).cbrt();
        let co_type = if simple_hash(config.seed + 703, idx as u64) > 0.5 {
            CoOrbitalType::Trojan
        } else {
            CoOrbitalType::Horseshoe
        };

        satellites.push(GeneratedSatellite::CoOrbital(CoOrbitalMoon {
            name: format!("{}-T{}", config.parent_name, idx + 1),
            parent: config.parent_name.clone(),
            mass,
            radius,
            elements: OrbitalElements::new(a, 0.007, 0.001, 0.0, 0.0, 0.0),
            albedo: 0.3 + 0.5 * simple_hash(config.seed + 704, idx as u64),
            co_type,
            partner_mass: 1e15 + 1e18 * simple_hash(config.seed + 705, idx as u64),
            libration_amplitude: 0.1 + 0.5 * simple_hash(config.seed + 706, idx as u64),
        }));
    }

    satellites
}