marss 0.0.3

Mars celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use marss::rendering::atmosphere_scattering::*;
use marss::rendering::dust_rendering::*;
use marss::rendering::materials::*;
use marss::rendering::shaders::*;
use marss::rendering::sky::*;

// === AtmosphereEndpoint ===

#[test]
fn atmosphere_endpoint_mars() {
    let a = AtmosphereEndpoint::mars();
    assert!(a.planet_radius_m > 0.0);
    assert!(a.atmosphere_height_m > 0.0);
    assert!(a.sea_level_pressure_pa > 0.0);
    assert!(a.sea_level_number_density_m3 > 0.0);
    assert!(!a.species.is_empty());
}

#[test]
fn atmosphere_species_co2_dominant() {
    let a = AtmosphereEndpoint::mars();
    let co2 = a.species.iter().find(|s| s.symbol == "CO2").unwrap();
    assert!(
        co2.volume_fraction > 0.9,
        "CO2 dominant: {}",
        co2.volume_fraction
    );
}

#[test]
fn atmosphere_species_molar_mass_positive() {
    let a = AtmosphereEndpoint::mars();
    for s in &a.species {
        assert!(
            s.molar_mass_kg_mol > 0.0,
            "{} molar mass: {}",
            s.name,
            s.molar_mass_kg_mol
        );
    }
}

#[test]
fn rayleigh_density_decreases_with_alt() {
    let a = AtmosphereEndpoint::mars();
    let d0 = a.rayleigh_density(0.0);
    let d10 = a.rayleigh_density(10_000.0);
    assert!(d0 > d10, "Rayleigh decreases: {d0} > {d10}");
}

#[test]
fn dust_density_method() {
    let a = AtmosphereEndpoint::mars();
    let d = a.dust_density(0.0);
    assert!(d >= 0.0, "Dust density >= 0: {d}");
}

#[test]
fn sky_color_rgb_range() {
    let a = AtmosphereEndpoint::mars();
    let c = a.sky_color(45.0);
    assert!(c[0] >= 0.0 && c[0] <= 1.0);
    assert!(c[1] >= 0.0 && c[1] <= 1.0);
    assert!(c[2] >= 0.0 && c[2] <= 1.0);
}

#[test]
fn sky_color_butterscotch_tint() {
    let a = AtmosphereEndpoint::mars();
    let c = a.sky_color(45.0);
    assert!(c[0] > c[2], "Mars sky red > blue: r={} b={}", c[0], c[2]);
}

#[test]
fn direct_transmission_decreases_with_airmass() {
    let a = AtmosphereEndpoint::mars();
    let t90 = a.direct_transmission(90.0);
    let t30 = a.direct_transmission(30.0);
    assert!(
        t90 >= t30,
        "Higher elev = more transmission: {t90} vs {t30}"
    );
}

#[test]
fn direct_transmission_bounded() {
    let a = AtmosphereEndpoint::mars();
    let t = a.direct_transmission(45.0);
    assert!((0.0..=1.0).contains(&t), "Transmission in [0,1]: {t}");
}

#[test]
fn sky_luminance_positive() {
    let a = AtmosphereEndpoint::mars();
    let l = a.sky_luminance(0.0, 0.7);
    assert!(l >= 0.0, "Luminance >= 0: {l}");
}

#[test]
fn rayleigh_coefficients_positive() {
    let a = AtmosphereEndpoint::mars();
    assert!(a.rayleigh_coefficients_rgb[0] > 0.0);
    assert!(a.rayleigh_coefficients_rgb[1] > 0.0);
    assert!(a.rayleigh_coefficients_rgb[2] > 0.0);
}

// === DustSystemEndpoint ===

#[test]
fn dust_background_od() {
    let d = DustSystemEndpoint::mars_background();
    assert!(d.optical_depth > 0.0 && d.optical_depth < 2.0);
}

#[test]
fn dust_global_storm_higher() {
    let d = DustSystemEndpoint::mars_global_storm();
    let bg = DustSystemEndpoint::mars_background();
    assert!(d.optical_depth > bg.optical_depth);
}

#[test]
fn dust_global_storm_larger_particles() {
    let bg = DustSystemEndpoint::mars_background();
    let gs = DustSystemEndpoint::mars_global_storm();
    assert!(gs.particle_effective_radius_um > bg.particle_effective_radius_um);
}

#[test]
fn density_at_altitude_decreases() {
    let d = DustSystemEndpoint::mars_background();
    let d0 = d.density_at_altitude(0.0);
    let d10 = d.density_at_altitude(10_000.0);
    assert!(d0 > d10, "Density decreases: {d0} vs {d10}");
}

#[test]
fn density_outside_bounds_zero() {
    let d = DustSystemEndpoint::mars_background();
    assert_eq!(d.density_at_altitude(-100.0), 0.0);
    assert_eq!(d.density_at_altitude(100_000.0), 0.0);
}

#[test]
fn surface_solar_fraction_bounded() {
    let d = DustSystemEndpoint::mars_background();
    let f = d.surface_solar_fraction();
    assert!(f > 0.0 && f <= 1.0, "Solar fraction: {f}");
}

#[test]
fn phase_function_forward_scatter() {
    let d = DustSystemEndpoint::mars_background();
    let pf = d.phase_function(1.0);
    let pb = d.phase_function(-1.0);
    assert!(pf > pb, "Forward > backward");
}

#[test]
fn effective_radius_positive() {
    let d = DustSystemEndpoint::mars_background();
    assert!(d.effective_radius_um() > 0.0);
}

// === Materials ===

#[test]
fn regolith_material_check() {
    let m = regolith();
    assert!(m.albedo[0] >= 0.0 && m.albedo[0] <= 1.0);
    assert!(m.roughness > 0.0);
}

#[test]
fn bright_dust_brighter_than_dark_dune() {
    let bright = bright_dust();
    let dark = dark_dune();
    let ba = (bright.albedo[0] + bright.albedo[1] + bright.albedo[2]) / 3.0;
    let da = (dark.albedo[0] + dark.albedo[1] + dark.albedo[2]) / 3.0;
    assert!(ba > da);
}

#[test]
fn basalt_material_check() {
    let m = basalt();
    assert!(m.roughness > 0.0);
    assert!(m.metallic >= 0.0 && m.metallic <= 1.0);
}

#[test]
fn water_ice_high_albedo() {
    let m = water_ice();
    let avg = (m.albedo[0] + m.albedo[1] + m.albedo[2]) / 3.0;
    assert!(avg > 0.4, "Ice albedo high: {avg}");
}

#[test]
fn co2_ice_smooth() {
    let m = co2_ice();
    assert!(m.roughness < 0.5, "Ice smooth: {}", m.roughness);
}

#[test]
fn sulfate_salt_check() {
    let m = sulfate_salt();
    assert!(m.albedo[0] >= 0.0);
}

#[test]
fn volcanic_summit_check() {
    let m = volcanic_summit();
    assert!(m.roughness > 0.0);
}

// === ShaderEndpoint ===

#[test]
fn terrain_shader_endpoint() {
    let s = ShaderEndpoint::terrain();
    assert!(!s.name.is_empty());
    assert!(!s.uniforms.is_empty());
    assert!(s.uniforms.iter().any(|u| u.name == "u_sun_direction"));
    assert!(s.uniforms.iter().any(|u| u.name == "u_dust_optical_depth"));
}

#[test]
fn atmosphere_shader_endpoint() {
    let s = ShaderEndpoint::atmosphere();
    assert!(!s.name.is_empty());
    assert!(s.uniforms.iter().any(|u| u.name == "u_rayleigh_coeff"));
    assert!(s.uniforms.iter().any(|u| u.name == "u_mie_g"));
}

#[test]
fn dust_storm_shader_endpoint() {
    let s = ShaderEndpoint::dust_storm();
    assert!(!s.uniforms.is_empty());
    assert!(s.uniforms.iter().any(|u| u.name == "u_dust_optical_depth"));
    assert!(s.uniforms.iter().any(|u| u.name == "u_storm_intensity"));
}

#[test]
fn shader_uniform_value_mutation() {
    let mut s = ShaderEndpoint::terrain();
    s.uniforms[0].value = UniformValue::Vec3([1.0, 0.0, 0.0]);
    match &s.uniforms[0].value {
        UniformValue::Vec3(v) => assert_eq!(v[0], 1.0),
        _ => panic!("Expected Vec3"),
    }
}

// === Sky ===

#[test]
fn sky_state_zenith_color() {
    let s = SkyState::new(45.0, 0.3);
    let c = s.zenith_color();
    assert!(c[0] >= 0.0 && c[0] <= 1.0);
}

#[test]
fn sky_state_sunset_color() {
    let s = SkyState::new(5.0, 0.3);
    let c = s.sunset_color();
    assert!(c[2] > c[0], "Mars sunset blue > red");
}

#[test]
fn sky_state_horizon_glow() {
    let s = SkyState::new(2.0, 0.5);
    let g = s.horizon_glow();
    assert!(g > 0.0, "Horizon glow > 0 near sunset");
}

#[test]
fn sky_state_night() {
    let s = SkyState::new(-20.0, 0.3);
    let c = s.zenith_color();
    let brightness = c[0] + c[1] + c[2];
    assert!(brightness < 0.5, "Night sky dim: {brightness}");
}