#[cfg(feature = "parallel")]
use rayon::prelude::*;
use crate::geom::{Coord, Envelope, LinearRing, Polygon};
use crate::precision::PrecisionModel;
use crate::triangulation::delaunay_triangulation;
#[cfg(feature = "parallel")]
const PARALLEL_MIN_SITES: usize = 2048;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VoronoiOptions {
pub epsilon: f64,
pub precision: Option<PrecisionModel>,
pub clip: Option<Envelope>,
}
impl Default for VoronoiOptions {
fn default() -> Self {
Self {
epsilon: 1.0e-9,
precision: None,
clip: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VoronoiDiagram {
pub sites: Vec<Coord>,
pub cells: Vec<Polygon>,
pub clip: Envelope,
}
pub fn voronoi_diagram(points: &[Coord], epsilon: f64) -> VoronoiDiagram {
if points.is_empty() {
return VoronoiDiagram {
sites: vec![],
cells: vec![],
clip: Envelope::new(0.0, 0.0, 0.0, 0.0),
};
}
let clip = auto_clip_envelope(points);
voronoi_diagram_with_clip(points, epsilon, clip)
}
pub fn voronoi_diagram_with_precision(points: &[Coord], precision: PrecisionModel) -> VoronoiDiagram {
let mut snapped = points.to_vec();
precision.apply_coords_in_place(&mut snapped);
voronoi_diagram(&snapped, precision.epsilon())
}
pub fn voronoi_diagram_with_clip(points: &[Coord], epsilon: f64, clip: Envelope) -> VoronoiDiagram {
let tri = delaunay_triangulation(points, epsilon);
let sites = tri.points;
if sites.is_empty() {
return VoronoiDiagram {
sites,
cells: vec![],
clip,
};
}
if sites.len() == 1 {
return VoronoiDiagram {
sites,
cells: vec![rect_polygon(clip)],
clip,
};
}
let neighbors = build_site_neighbors(sites.len(), &tri.triangles);
#[cfg(feature = "parallel")]
let cells = if sites.len() >= PARALLEL_MIN_SITES {
sites
.par_iter()
.enumerate()
.map(|(i, &p)| build_cell_for_site(i, p, &sites, &neighbors, clip, epsilon))
.collect()
} else {
sites
.iter()
.enumerate()
.map(|(i, &p)| build_cell_for_site(i, p, &sites, &neighbors, clip, epsilon))
.collect()
};
#[cfg(not(feature = "parallel"))]
let cells = sites
.iter()
.enumerate()
.map(|(i, &p)| build_cell_for_site(i, p, &sites, &neighbors, clip, epsilon))
.collect();
VoronoiDiagram { sites, cells, clip }
}
pub fn voronoi_diagram_with_clip_with_precision(
points: &[Coord],
precision: PrecisionModel,
clip: Envelope,
) -> VoronoiDiagram {
let mut snapped = points.to_vec();
precision.apply_coords_in_place(&mut snapped);
voronoi_diagram_with_clip(&snapped, precision.epsilon(), clip)
}
pub fn voronoi_diagram_with_options(points: &[Coord], options: VoronoiOptions) -> VoronoiDiagram {
let eps = options.epsilon;
match (options.precision, options.clip) {
(Some(pm), Some(clip)) => {
let mut snapped = points.to_vec();
pm.apply_coords_in_place(&mut snapped);
voronoi_diagram_with_clip(&snapped, eps.max(pm.epsilon()), clip)
}
(Some(pm), None) => {
let mut snapped = points.to_vec();
pm.apply_coords_in_place(&mut snapped);
voronoi_diagram(&snapped, eps.max(pm.epsilon()))
}
(None, Some(clip)) => voronoi_diagram_with_clip(points, eps, clip),
(None, None) => voronoi_diagram(points, eps),
}
}
fn build_cell_for_site(
i: usize,
p: Coord,
sites: &[Coord],
neighbors: &[Vec<usize>],
clip: Envelope,
epsilon: f64,
) -> Polygon {
let mut cell = rect_coords(clip);
let mut scratch = Vec::<Coord>::with_capacity(cell.len() + 8);
for &j in &neighbors[i] {
let q = sites[j];
let nx = q.x - p.x;
let ny = q.y - p.y;
if nx == 0.0 && ny == 0.0 {
continue;
}
let c = 0.5 * ((q.x * q.x + q.y * q.y) - (p.x * p.x + p.y * p.y));
clip_polygon_halfplane_into(&cell, &mut scratch, nx, ny, c, epsilon);
std::mem::swap(&mut cell, &mut scratch);
scratch.clear();
if cell.len() < 3 {
break;
}
}
if cell.len() < 3 {
Polygon::new(LinearRing::new(vec![]), vec![])
} else {
Polygon::new(LinearRing::new(cell), vec![])
}
}
fn auto_clip_envelope(points: &[Coord]) -> Envelope {
let mut min_x = points[0].x;
let mut min_y = points[0].y;
let mut max_x = points[0].x;
let mut max_y = points[0].y;
for &p in &points[1..] {
if p.x < min_x {
min_x = p.x;
}
if p.x > max_x {
max_x = p.x;
}
if p.y < min_y {
min_y = p.y;
}
if p.y > max_y {
max_y = p.y;
}
}
let dx = max_x - min_x;
let dy = max_y - min_y;
let pad = dx.max(dy).max(1.0) * 2.0;
Envelope::new(min_x - pad, min_y - pad, max_x + pad, max_y + pad)
}
fn rect_coords(env: Envelope) -> Vec<Coord> {
vec![
Coord::xy(env.min_x, env.min_y),
Coord::xy(env.max_x, env.min_y),
Coord::xy(env.max_x, env.max_y),
Coord::xy(env.min_x, env.max_y),
]
}
fn rect_polygon(env: Envelope) -> Polygon {
Polygon::new(LinearRing::new(rect_coords(env)), vec![])
}
fn build_site_neighbors(site_count: usize, triangles: &[[usize; 3]]) -> Vec<Vec<usize>> {
let mut neighbors = vec![Vec::<usize>::new(); site_count];
for &t in triangles {
let a = t[0];
let b = t[1];
let c = t[2];
if a < site_count && b < site_count {
neighbors[a].push(b);
neighbors[b].push(a);
}
if b < site_count && c < site_count {
neighbors[b].push(c);
neighbors[c].push(b);
}
if c < site_count && a < site_count {
neighbors[c].push(a);
neighbors[a].push(c);
}
}
let mut seen = vec![0u32; site_count];
let mut stamp = 1u32;
for nbrs in &mut neighbors {
if nbrs.len() <= 1 {
if stamp == u32::MAX {
seen.fill(0);
stamp = 1;
}
stamp = stamp.wrapping_add(1);
continue;
}
if stamp == u32::MAX {
seen.fill(0);
stamp = 1;
}
let mut write = 0usize;
for read in 0..nbrs.len() {
let id = nbrs[read];
if id >= site_count {
continue;
}
if seen[id] == stamp {
continue;
}
seen[id] = stamp;
nbrs[write] = id;
write += 1;
}
nbrs.truncate(write);
stamp = stamp.wrapping_add(1);
}
neighbors
}
fn clip_polygon_halfplane_into(
poly: &[Coord],
out: &mut Vec<Coord>,
nx: f64,
ny: f64,
c: f64,
eps: f64,
) {
if poly.len() < 3 {
out.clear();
return;
}
out.clear();
out.reserve(poly.len() + 4);
let n = poly.len();
for i in 0..n {
let a = poly[i];
let b = poly[(i + 1) % n];
let da = nx * a.x + ny * a.y - c;
let db = nx * b.x + ny * b.y - c;
let a_in = da <= eps;
let b_in = db <= eps;
if a_in && b_in {
out.push(b);
} else if a_in && !b_in {
if let Some(x) = segment_line_intersection(a, b, nx, ny, c, eps) {
out.push(x);
}
} else if !a_in && b_in {
if let Some(x) = segment_line_intersection(a, b, nx, ny, c, eps) {
out.push(x);
}
out.push(b);
}
}
dedup_consecutive(out, eps);
}
fn segment_line_intersection(a: Coord, b: Coord, nx: f64, ny: f64, c: f64, eps: f64) -> Option<Coord> {
let dx = b.x - a.x;
let dy = b.y - a.y;
let denom = nx * dx + ny * dy;
if denom.abs() <= eps.abs().max(1.0e-12) {
return None;
}
let t = (c - (nx * a.x + ny * a.y)) / denom;
let tc = t.clamp(0.0, 1.0);
Some(Coord::interpolate_segment(a, b, tc))
}
fn dedup_consecutive(coords: &mut Vec<Coord>, eps: f64) {
if coords.is_empty() {
return;
}
let tol = eps.abs().max(1.0e-12);
let mut write = 1usize;
for read in 1..coords.len() {
let p = coords[read];
let q = coords[write - 1];
if (p.x - q.x).abs() <= tol && (p.y - q.y).abs() <= tol {
continue;
}
coords[write] = p;
write += 1;
}
coords.truncate(write);
if coords.len() >= 2 {
let first = coords[0];
let last = coords[coords.len() - 1];
if (first.x - last.x).abs() <= tol && (first.y - last.y).abs() <= tol {
coords.pop();
}
}
}