geomutil_triangulation 0.1.1

A Rust library providing triangulation functions.
Documentation
use crate::triangulate;
use geomutil_util::{Edge2D, Point2D, Shape2D, Triangle2D};
use std::collections::{HashMap, VecDeque};

struct AlphaShape2D {
    alpha: f32,
    triangles: Vec<Triangle2D>,
    connections: Vec<[Option<usize>; 3]>,
}

impl AlphaShape2D {
    fn new(triangles: Vec<Triangle2D>, alpha: f32) -> Self {
        Self {
            alpha,
            triangles,
            connections: Vec::new(),
        }
    }

    fn prune(&mut self) {
        let r_sq = (1.0 / self.alpha) * (1.0 / self.alpha);
        for i in (0..self.triangles.len()).rev() {
            if self.triangles[i].circumcircle_radius_squared() > r_sq {
                self.triangles.swap_remove(i);
            }
        }
        self.connections = vec![[None; 3]; self.triangles.len()];
    }

    fn build_connections_graph(&mut self) {
        let mut adjacent_edges: HashMap<Edge2D, [Option<usize>; 2]> =
            HashMap::with_capacity(self.triangles.len() * 2);
        for (i, t) in self.triangles.iter().enumerate() {
            for e in t.edges() {
                let e = e.canonical();
                adjacent_edges
                    .entry(e)
                    .and_modify(|adjacent: &mut _| adjacent[1] = Some(i))
                    .or_insert([Some(i), None]);
            }
        }
        for (_, neighbours) in adjacent_edges.drain() {
            for (i, j) in [
                (neighbours[0], neighbours[1]),
                (neighbours[1], neighbours[0]),
            ] {
                if let Some(i) = i {
                    if let Some(neighbour) = self.connections[i].iter_mut().find(|a| a.is_none()) {
                        *neighbour = j;
                    }
                }
            }
        }
    }

    fn shapes(&mut self) -> Vec<Shape2D> {
        self.prune();
        self.build_connections_graph();
        let mut queue = VecDeque::new();
        let mut visited = vec![false; self.triangles.len()];
        let mut shapes = Vec::new();
        for i in 0..self.triangles.len() {
            if visited[i] {
                continue;
            }
            queue.push_back(i);
            let mut shape = Vec::new();
            while let Some(i) = queue.pop_front() {
                visited[i] = true;
                shape.push(self.triangles[i].clone());
                for neigh_i in self.connections[i].into_iter().flatten() {
                    if !visited[neigh_i] {
                        queue.push_back(neigh_i);
                    }
                }
            }
            shapes.push(Shape2D::new(shape));
        }
        shapes
    }
}

pub fn alpha_shape_2d(points: &[Point2D], alpha: f32) -> Option<Vec<Shape2D>> {
    let triangulation = triangulate(points)?;
    let mut alpha_shape = AlphaShape2D::new(triangulation.triangles, alpha);
    let shapes = alpha_shape.shapes();
    Some(shapes)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_single_square() {
        let points = vec![
            Point2D::new(0.0, 0.0),
            Point2D::new(1.0, 0.0),
            Point2D::new(1.0, 1.0),
            Point2D::new(0.0, 1.0),
        ];
        let shapes = alpha_shape_2d(&points, 1.25);
        assert!(shapes.is_some());
        let shapes = shapes.unwrap();
        assert_eq!(shapes.len(), 1);
        assert_eq!(shapes[0].triangles.len(), 2);
    }

    #[test]
    fn test_two_disconnected_squares() {
        let points = vec![
            Point2D::new(0.0, 0.0),
            Point2D::new(1.0, 0.0),
            Point2D::new(1.0, 1.0),
            Point2D::new(0.0, 1.0),
            Point2D::new(3.0, 0.0),
            Point2D::new(4.0, 0.0),
            Point2D::new(4.0, 1.0),
            Point2D::new(3.0, 1.0),
        ];
        let shapes = alpha_shape_2d(&points, 1.25);
        assert!(shapes.is_some());
        let shapes = shapes.unwrap();
        assert_eq!(shapes.len(), 2);
        assert_eq!(shapes[0].triangles.len(), 2);
        assert_eq!(shapes[1].triangles.len(), 2);
    }
}