conspire 0.7.0

The Rust interface to conspire.
Documentation
#[cfg(test)]
mod test;

use crate::{
    geometry::{
        Coordinates,
        bvh::BoundingVolumeHierarchy,
        mesh::{
            Connectivity, Mesh,
            smooth::Smoothing,
            tessellation::{D, Normals, Tessellation},
        },
    },
    math::{Scalar, Tensor, TensorVec},
};
use std::{array::from_fn, cell::OnceCell, collections::HashMap};

const WELD_TOLERANCE: Scalar = 1e-6;

impl Tessellation {
    pub fn mesh(&self) -> &Mesh<D> {
        &self.mesh
    }
    pub fn normals(&self) -> &Normals {
        &self.normals
    }
    pub fn bvh(&self) -> &BoundingVolumeHierarchy<D> {
        self.bvh
            .get_or_init(|| BoundingVolumeHierarchy::from(&self.mesh))
    }
    pub fn smooth(&mut self, smoothing: Smoothing) {
        self.mesh.smooth(smoothing);
        self.refresh();
    }
    pub fn smooth_welded(&mut self, smoothing: Smoothing) {
        let mut min = [f64::INFINITY; D];
        let mut max = [f64::NEG_INFINITY; D];
        for point in self.mesh.coordinates() {
            (0..D).for_each(|axis| {
                min[axis] = min[axis].min(point[axis]);
                max[axis] = max[axis].max(point[axis]);
            });
        }
        let diagonal = (0..D)
            .map(|axis| (max[axis] - min[axis]).powi(2))
            .sum::<f64>()
            .sqrt();
        self.smooth_welded_with_tolerance(smoothing, WELD_TOLERANCE * diagonal);
    }
    pub(crate) fn smooth_welded_with_tolerance(&mut self, smoothing: Smoothing, tolerance: f64) {
        let mut representatives = Vec::with_capacity(self.mesh.number_of_nodes());
        let mut anchors: HashMap<[i64; D], Vec<usize>> = HashMap::new();
        let mut welded = Coordinates::new();
        for point in self.mesh.coordinates() {
            let cell = from_fn(|axis| (point[axis] / tolerance).floor() as i64);
            let mut representative = None;
            'search: for dz in -1i64..=1 {
                for dy in -1i64..=1 {
                    for dx in -1i64..=1 {
                        if let Some(indices) =
                            anchors.get(&[cell[0] + dx, cell[1] + dy, cell[2] + dz])
                        {
                            for &index in indices {
                                if (point - &welded[index]).norm() <= tolerance {
                                    representative = Some(index);
                                    break 'search;
                                }
                            }
                        }
                    }
                }
            }
            representatives.push(representative.unwrap_or_else(|| {
                let index = welded.len();
                welded.push(point.clone());
                anchors.entry(cell).or_default().push(index);
                index
            }));
        }
        let triangles = match &self.mesh.connectivities()[0] {
            Connectivity::Triangular(triangles) => triangles
                .iter()
                .map(|&[a, b, c]| [representatives[a], representatives[b], representatives[c]])
                .collect::<Vec<_>>(),
            _ => panic!(),
        };
        let mut mesh = Mesh::from((vec![Connectivity::Triangular(triangles.into())], welded));
        mesh.smooth(smoothing);
        let smoothed = mesh.coordinates();
        self.mesh
            .coordinates
            .iter_mut()
            .zip(&representatives)
            .for_each(|(point, &representative)| *point = smoothed[representative].clone());
        self.refresh();
    }
    fn refresh(&mut self) {
        self.normals = self.mesh.normals();
        self.bvh = OnceCell::new();
    }
}