conspire 0.7.1

The Rust interface to conspire.
Documentation
use super::{
    Surface, collapse_short_edges, edge, edge_lengths, flip_edges, split_long_edges,
    tangential_smooth,
};
use crate::{
    geometry::Coordinates,
    math::{
        Tensor,
        test::{TestError, assert_eq_within_tols},
    },
};
use std::collections::HashMap;

fn right_triangle(leg: f64) -> Coordinates<3> {
    Coordinates::from(vec![[0.0, 0.0, 0.0], [leg, 0.0, 0.0], [0.0, leg, 0.0]])
}

#[test]
fn splits_only_long_edges() -> Result<(), TestError> {
    let mut connectivity = vec![[0, 1, 2]];
    let mut coordinates = right_triangle(3.0);
    let lengths = edge_lengths(&connectivity, &coordinates);
    let mut sizing = vec![3.0; coordinates.len()];
    split_long_edges(&mut connectivity, &mut coordinates, &lengths, &mut sizing);
    assert_eq!(coordinates.len(), 4);
    assert_eq_within_tols(&coordinates[3], &[1.5, 1.5, 0.0].into())?;
    assert_eq!(connectivity, vec![[1, 3, 0], [3, 2, 0]]);
    Ok(())
}

#[test]
fn leaves_short_edges_alone() {
    let mut connectivity = vec![[0, 1, 2]];
    let mut coordinates = right_triangle(3.0);
    let lengths = edge_lengths(&connectivity, &coordinates);
    let mut sizing = vec![3.75; coordinates.len()];
    split_long_edges(&mut connectivity, &mut coordinates, &lengths, &mut sizing);
    assert_eq!(coordinates.len(), 3);
    assert_eq!(connectivity, vec![[0, 1, 2]]);
}

#[test]
fn three_split_makes_four_faces() {
    let mut connectivity = vec![[0, 1, 2]];
    let mut coordinates = right_triangle(4.0);
    let lengths = edge_lengths(&connectivity, &coordinates);
    let mut sizing = vec![0.75; coordinates.len()];
    split_long_edges(&mut connectivity, &mut coordinates, &lengths, &mut sizing);
    assert_eq!(connectivity.len(), 4);
    assert_eq!(coordinates.len(), 6);
}

#[test]
fn flip_reduces_overvalent_hub() {
    let mut connectivity: Vec<[usize; 3]> = (1..8)
        .map(|i| [0, i, i + 1])
        .chain(std::iter::once([0, 8, 1]))
        .collect();
    let mut points = vec![[0.0, 0.0, 0.0]];
    points.extend((0..8).map(|i| {
        let angle = i as f64 * std::f64::consts::FRAC_PI_4;
        [angle.cos(), angle.sin(), 0.0]
    }));
    let coordinates = Coordinates::from(points);
    flip_edges(&mut connectivity, &coordinates);
    assert_eq!(connectivity.len(), 8, "flips must preserve the face count");

    let mut hub_neighbors = std::collections::HashSet::new();
    let mut edge_uses: HashMap<(usize, usize), usize> = HashMap::new();
    for &[a, b, c] in &connectivity {
        for (u, v) in [(a, b), (b, c), (c, a)] {
            *edge_uses.entry(edge(u, v)).or_default() += 1;
            if u == 0 {
                hub_neighbors.insert(v);
            } else if v == 0 {
                hub_neighbors.insert(u);
            }
        }
    }
    assert_eq!(
        hub_neighbors.len(),
        7,
        "hub valence should drop from 8 to 7"
    );
    assert!(
        edge_uses.values().all(|&count| count <= 2),
        "mesh must stay manifold (each edge in at most two faces)"
    );
}

#[test]
fn collapse_merges_short_edge() -> Result<(), TestError> {
    let mut connectivity = vec![
        [4, 0, 1],
        [4, 1, 2],
        [4, 2, 3],
        [4, 3, 0],
        [5, 1, 0],
        [5, 2, 1],
        [5, 3, 2],
        [5, 0, 3],
    ];
    let mut coordinates = Coordinates::from(vec![
        [1.0, 0.0, 0.0],
        [1.1, 0.2, 0.0],
        [0.0, 1.0, 0.0],
        [-1.0, 0.0, 0.0],
        [0.0, 0.0, 1.0],
        [0.0, 0.0, -1.0],
    ]);
    let lengths = edge_lengths(&connectivity, &coordinates);
    let mut sizing = vec![1.6; coordinates.len()];
    collapse_short_edges(&mut connectivity, &mut coordinates, &lengths, &mut sizing);
    assert_eq!(
        connectivity.len(),
        6,
        "two incident faces should be dropped"
    );
    assert_eq!(coordinates.len(), 5, "the merged-out vertex should be gone");
    assert_eq_within_tols(&coordinates[0], &[1.05, 0.1, 0.0].into())?;
    let mut edge_uses: HashMap<(usize, usize), usize> = HashMap::new();
    for &[a, b, c] in &connectivity {
        for (u, v) in [(a, b), (b, c), (c, a)] {
            *edge_uses.entry(edge(u, v)).or_default() += 1;
        }
    }
    assert!(
        edge_uses.values().all(|&count| count <= 2),
        "mesh must stay manifold after collapse"
    );
    Ok(())
}

#[test]
fn smooth_relaxes_hub_to_ring_centroid() -> Result<(), TestError> {
    let s = 3.0_f64.sqrt() / 2.0;
    let connectivity = vec![
        [0, 1, 2],
        [0, 2, 3],
        [0, 3, 4],
        [0, 4, 5],
        [0, 5, 6],
        [0, 6, 1],
    ];
    let mut coordinates = Coordinates::from(vec![
        [0.5, 0.3, 0.0],
        [1.0, 0.0, 0.0],
        [0.5, s, 0.0],
        [-0.5, s, 0.0],
        [-1.0, 0.0, 0.0],
        [-0.5, -s, 0.0],
        [0.5, -s, 0.0],
    ]);
    tangential_smooth(&connectivity, &mut coordinates);
    assert_eq_within_tols(&coordinates[0], &[0.0, 0.0, 0.0].into())?;
    assert_eq_within_tols(&coordinates[1], &[1.0, 0.0, 0.0].into())
}

#[test]
fn reproject_snaps_vertex_onto_surface() -> Result<(), TestError> {
    let surface = Surface::new(
        &[[0, 1, 2], [0, 2, 3]],
        &Coordinates::from(vec![
            [0.0, 0.0, 0.0],
            [1.0, 0.0, 0.0],
            [1.0, 1.0, 0.0],
            [0.0, 1.0, 0.0],
        ]),
    );
    let mut coordinates = Coordinates::from(vec![[0.3, 0.3, 0.5]]);
    surface.reproject(&mut coordinates);
    assert_eq_within_tols(&coordinates[0], &[0.3, 0.3, 0.0].into())
}