marss 0.0.3

Mars celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use marss::terrain::heightmap::*;
use marss::terrain::lod::*;
use marss::terrain::mesh::*;
use marss::terrain::texturing::*;

// === Heightmap ===

#[test]
fn heightmap_generate_dims() {
    let h = Heightmap::generate(64, 64);
    assert_eq!(h.width, 64);
    assert_eq!(h.height, 64);
}

#[test]
fn heightmap_sample_finite() {
    let h = Heightmap::generate(32, 32);
    let v = h.sample(0.0, 0.0);
    assert!(v.is_finite());
}

#[test]
fn heightmap_min_max() {
    let h = Heightmap::generate(64, 64);
    let (mn, mx) = h.min_max();
    assert!(mn <= mx, "min <= max: {mn} vs {mx}");
}

#[test]
fn heightmap_radius_at() {
    let h = Heightmap::generate(32, 32);
    let r = h.radius_at(0.0, 0.0);
    assert!(r > 3.3e6, "Mars radius ~3.39e6: {r}");
}

#[test]
fn heightmap_data_len() {
    let h = Heightmap::generate(16, 16);
    assert_eq!(h.data.len(), 16 * 16);
}

// === Landmark Elevations ===

#[test]
fn olympus_mons_summit() {
    let e = olympus_mons_summit_m();
    assert!(e > 21000.0, "Olympus ~21229 m: {e}");
}

#[test]
fn hellas_floor() {
    let e = hellas_floor_m();
    assert!(e < -7000.0, "Hellas floor < -7000: {e}");
}

#[test]
fn mars_elevation_olympus() {
    let e = mars_elevation(18.65, -133.8);
    assert!(e > 15000.0, "Olympus region high: {e}");
}

#[test]
fn mars_elevation_hellas() {
    let e = mars_elevation(-42.7, 70.0);
    assert!(e < 0.0, "Hellas basin negative: {e}");
}

#[test]
fn mars_elevation_finite() {
    let e = mars_elevation(0.0, 0.0);
    assert!(e.is_finite());
}

#[test]
fn other_landmarks() {
    assert!(ascraeus_mons_summit_m() > 14000.0);
    assert!(valles_marineris_floor_m() < 0.0);
    assert!(tharsis_plateau_m() > 5000.0);
    assert!(northern_lowlands_mean_m() < southern_highlands_mean_m());
}

#[test]
fn dichotomy_boundary() {
    let lat = dichotomy_boundary_lat_deg();
    assert!(lat.abs() < 60.0);
}

// === LOD ===

#[test]
fn quad_tree_node_new() {
    let n = QuadTreeNode::new(Face::PosX, 0, 0, 0);
    assert!(n.is_leaf());
}

#[test]
fn quad_tree_center_on_sphere() {
    let n = QuadTreeNode::new(Face::PosX, 0, 0, 0);
    let c = n.center_on_sphere(1.0);
    let mag = (c.0 * c.0 + c.1 * c.1 + c.2 * c.2).sqrt();
    assert!((mag - 1.0).abs() < 0.1, "Unit sphere: {mag}");
}

#[test]
fn lod_config_default() {
    let cfg = LodConfig::default();
    assert!(cfg.max_level > 0 && cfg.max_level <= 20);
}

#[test]
fn lod_terrain_new() {
    let cfg = LodConfig::default();
    let t = LodTerrain::new(cfg);
    assert!(t.leaf_count() > 0);
}

#[test]
fn lod_terrain_update() {
    let cfg = LodConfig::default();
    let mut t = LodTerrain::new(cfg);
    t.update((1.0e7, 0.0, 0.0));
    assert!(t.leaf_count() > 0);
}

// === Mesh ===

#[test]
fn terrain_mesh_from_region() {
    let m = TerrainMesh::from_region(-10.0, 10.0, -10.0, 10.0, 4);
    assert!(m.vertex_count() > 0);
    assert!(m.triangle_count() > 0);
}

#[test]
fn terrain_mesh_vertices_finite() {
    let m = TerrainMesh::from_region(0.0, 5.0, 0.0, 5.0, 2);
    for v in &m.vertices {
        assert!(
            v.position[0].is_finite() && v.position[1].is_finite() && v.position[2].is_finite()
        );
    }
}

#[test]
fn terrain_mesh_normals() {
    let m = TerrainMesh::from_region(0.0, 5.0, 0.0, 5.0, 2);
    for v in &m.vertices {
        let mag =
            (v.normal[0] * v.normal[0] + v.normal[1] * v.normal[1] + v.normal[2] * v.normal[2])
                .sqrt();
        assert!((mag - 1.0).abs() < 0.1, "Normal unit: {mag}");
    }
}

#[test]
fn terrain_mesh_indices() {
    let m = TerrainMesh::from_region(0.0, 5.0, 0.0, 5.0, 2);
    let vc = m.vertex_count();
    for &i in &m.indices {
        assert!((i as usize) < vc);
    }
}

// === Texturing / Biome Classifier ===

#[test]
fn biome_classifier_default() {
    let bc = BiomeClassifier::default();
    let b = bc.classify(0.0, 0.0, 200.0);
    assert!(matches!(
        b,
        MarsBiome::DarkDune
            | MarsBiome::BrightDust
            | MarsBiome::Regolith
            | MarsBiome::Basalt
            | MarsBiome::SulfateSalt
            | MarsBiome::PolarIce
            | MarsBiome::Co2Frost
            | MarsBiome::VolcanicSummit
            | MarsBiome::ImpactEjecta
    ));
}

#[test]
fn biome_polar_ice() {
    let bc = BiomeClassifier::default();
    let b = bc.classify(85.0, -3000.0, 50.0);
    assert!(matches!(b, MarsBiome::PolarIce | MarsBiome::Co2Frost));
}

#[test]
fn biome_volcanic_high() {
    let bc = BiomeClassifier::default();
    let b = bc.classify(18.0, 21000.0, 100.0);
    assert!(matches!(b, MarsBiome::VolcanicSummit | MarsBiome::Basalt));
}

#[test]
fn splat_weights_sum_to_one() {
    let bc = BiomeClassifier::default();
    let w = bc.splat_weights(0.0, 0.0, 200.0);
    let sum: f32 = w.weights.iter().sum();
    assert!((sum - 1.0).abs() < 0.02, "Splat weights sum: {sum}");
}

#[test]
fn mars_biome_all_variants() {
    let variants = [
        MarsBiome::DarkDune,
        MarsBiome::BrightDust,
        MarsBiome::Regolith,
        MarsBiome::Basalt,
        MarsBiome::SulfateSalt,
        MarsBiome::PolarIce,
        MarsBiome::Co2Frost,
        MarsBiome::VolcanicSummit,
        MarsBiome::ImpactEjecta,
    ];
    for (i, a) in variants.iter().enumerate() {
        for (j, b) in variants.iter().enumerate() {
            if i == j {
                assert!(std::mem::discriminant(a) == std::mem::discriminant(b));
            }
        }
    }
}