mod bounding_box;
mod cell_builder;
mod voronoi_cell;
mod iterator;
mod utils;
mod voronoi_builder;
use std::{fmt::Display, str::FromStr};
use delaunator::{EMPTY, Triangulation, triangulate};
use self::{
utils::cicumcenter,
cell_builder::*
};
pub use voronoi_builder::VoronoiBuilder;
pub use bounding_box::BoundingBox;
pub use voronoi_cell::VoronoiCell;
pub use iterator::TopologicalNeighborSiteIterator;
pub use iterator::NeighborSiteIterator;
pub use iterator::CellPathIterator;
pub use delaunator::Point;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ClipBehavior {
None,
RemoveSitesOutsideBoundingBoxOnly,
Clip,
}
impl Default for ClipBehavior {
fn default() -> Self {
ClipBehavior::Clip
}
}
impl Display for ClipBehavior {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl FromStr for ClipBehavior {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"None" => Ok(Self::None),
"RemoveSitesOutsideBoundingBoxOnly" => Ok(Self::RemoveSitesOutsideBoundingBoxOnly),
"Clip" => Ok(Self::Clip),
_ => Err("Invalid option".to_string())
}
}
}
#[derive(Clone)]
pub struct Voronoi {
sites: Vec<Point>,
bounding_box: BoundingBox,
triangulation: Triangulation,
clip_behavior: ClipBehavior,
circumcenters: Vec<Point>,
site_to_incoming_leftmost_halfedge: Vec<usize>,
cells: Vec<Vec<usize>>
}
impl std::fmt::Debug for Voronoi {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Voronoi")
.field("sites", &self.sites)
.field("circumcenters", &self.circumcenters)
.field("cell_triangles", &self.cells)
.finish()
}
}
impl Voronoi {
fn new(sites: Vec<Point>, bounding_box: BoundingBox, clip_behavior: ClipBehavior) -> Option<Self> {
let sites = match clip_behavior {
ClipBehavior::RemoveSitesOutsideBoundingBoxOnly | ClipBehavior::Clip => sites.into_iter().filter(|p| bounding_box.is_inside(p)).collect::<Vec<Point>>(),
ClipBehavior::None => sites
};
let triangulation = triangulate(&sites);
let num_of_triangles = triangulation.triangles.len() / 3;
if num_of_triangles == 0 {
return None
}
let circumcenters = (0..num_of_triangles).map(|t| cicumcenter(
&sites[triangulation.triangles[3* t]],
&sites[triangulation.triangles[3* t + 1]],
&sites[triangulation.triangles[3* t + 2]])
).collect();
let cell_builder = CellBuilder::new(&triangulation, &sites, circumcenters, bounding_box.clone(), clip_behavior);
let result = cell_builder.build();
Some(Voronoi {
bounding_box,
site_to_incoming_leftmost_halfedge: result.site_to_incoming_leftmost_halfedge,
triangulation,
sites,
clip_behavior,
circumcenters: result.vertices,
cells: result.cells
})
}
#[inline]
pub fn sites(&self) -> &Vec<Point> {
&self.sites
}
#[inline]
pub fn cell(&self, site: usize) -> VoronoiCell {
VoronoiCell::new(site, self)
}
pub fn iter_cells<'v>(&'v self) -> impl Iterator<Item = VoronoiCell<'v>> + Clone {
(0..self.sites.len())
.map(move |s| self.cell(s))
}
#[inline]
pub fn cells(&self) -> &Vec<Vec<usize>> {
&self.cells
}
#[inline]
pub fn vertices(&self) -> &Vec<Point> {
&self.circumcenters
}
#[inline]
pub fn triangulation(&self) -> &Triangulation {
&self.triangulation
}
pub fn bounding_box(&self) -> &BoundingBox {
&self.bounding_box
}
fn number_of_triangles(&self) -> usize {
self.triangulation.triangles.len() / 3
}
}
#[cfg(test)]
mod tests {
use rand::Rng;
use super::*;
fn create_random_builder(size: usize) -> VoronoiBuilder {
let mut rng = rand::thread_rng();
let builder = VoronoiBuilder::default();
let bbox = BoundingBox::default();
let x_range = rand::distributions::Uniform::new(-bbox.width() / 2.0, bbox.width() / 2.0);
let y_range = rand::distributions::Uniform::new(-bbox.height() / 2.0, bbox.height() / 2.0);
let sites = (0..size)
.map(|_| Point { x: rng.sample(x_range), y: rng.sample(y_range) })
.collect();
builder
.set_bounding_box(bbox)
.set_sites(sites)
}
#[test]
fn random_100_000_site_generation_test() {
let voronoi = create_random_builder(100_000)
.build()
.expect("Some voronoi expected.");
utils::test::validate_voronoi(&voronoi);
}
#[test]
fn random_15_site_generation_test() {
for _ in 0..1000 {
let voronoi = create_random_builder(15)
.build()
.expect("Some voronoi expected.");
utils::test::validate_voronoi(&voronoi);
}
}
#[test]
fn validate_examples() -> std::io::Result<()> {
for path in [
"degenerated1.json",
"degenerated2.json",
"degenerated3.json",
"degenerated4.json",
"degenerated5.json",
"degenerated6.json",
"degenerated7.json",
"degenerated8.json",
"degenerated9.json",
"degenerated10.json",
"clockwise1.json",
] {
let voronoi = utils::test::new_voronoi_builder_from_asset(path)?
.build()
.expect("Some voronoi expected");
utils::test::validate_voronoi(&voronoi);
}
Ok(())
}
#[test]
fn collinear_sites() {
let voronoi = VoronoiBuilder::default()
.set_sites([ Point { x: 0.0, y: 0.0 }, Point { x: 0.0, y: 1.0 }, Point { x: 0.0, y: 2.0 } ].to_vec())
.build();
assert!(voronoi.is_none(), "Collinear points do not generate valid voronoi");
}
}