1use glam::{Vec2, Vec3};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
8pub struct VoronoiCell {
9 pub site: Vec2,
10 pub vertices: Vec<Vec2>,
11 pub neighbor_indices: Vec<usize>,
12}
13
14#[derive(Debug, Clone)]
16pub struct VoronoiDiagram {
17 pub sites: Vec<Vec2>,
18 pub cells: Vec<VoronoiCell>,
19 pub bounds: (Vec2, Vec2),
20}
21
22impl VoronoiDiagram {
23 pub fn compute(sites: &[Vec2], bounds_min: Vec2, bounds_max: Vec2) -> Self {
25 let mut cells = Vec::with_capacity(sites.len());
26
27 for (i, &site) in sites.iter().enumerate() {
29 let mut polygon = vec![
30 bounds_min,
31 Vec2::new(bounds_max.x, bounds_min.y),
32 bounds_max,
33 Vec2::new(bounds_min.x, bounds_max.y),
34 ];
35
36 let mut neighbors = Vec::new();
37 for (j, &other) in sites.iter().enumerate() {
38 if i == j { continue; }
39 let clipped = clip_polygon_by_bisector(&polygon, site, other);
40 if clipped.len() != polygon.len() || clipped.iter().zip(polygon.iter()).any(|(a, b)| (*a - *b).length() > 1e-6) {
41 neighbors.push(j);
42 }
43 polygon = clipped;
44 if polygon.is_empty() { break; }
45 }
46
47 cells.push(VoronoiCell {
48 site,
49 vertices: polygon,
50 neighbor_indices: neighbors,
51 });
52 }
53
54 Self {
55 sites: sites.to_vec(),
56 cells,
57 bounds: (bounds_min, bounds_max),
58 }
59 }
60
61 pub fn nearest_site(&self, point: Vec2) -> usize {
63 self.sites.iter().enumerate()
64 .min_by(|(_, a), (_, b)| {
65 let da = (**a - point).length_squared();
66 let db = (**b - point).length_squared();
67 da.partial_cmp(&db).unwrap()
68 })
69 .map(|(i, _)| i)
70 .unwrap_or(0)
71 }
72
73 pub fn cell_area(&self, index: usize) -> f32 {
75 polygon_area(&self.cells[index].vertices)
76 }
77}
78
79fn clip_polygon_by_bisector(polygon: &[Vec2], keep: Vec2, other: Vec2) -> Vec<Vec2> {
82 if polygon.is_empty() { return Vec::new(); }
83 let mid = (keep + other) * 0.5;
84 let normal = (other - keep).normalize();
85
86 let mut output = Vec::new();
87 let n = polygon.len();
88
89 for i in 0..n {
90 let a = polygon[i];
91 let b = polygon[(i + 1) % n];
92 let da = (a - mid).dot(normal);
93 let db = (b - mid).dot(normal);
94
95 if da <= 0.0 { output.push(a); }
96 if (da > 0.0) != (db > 0.0) {
97 let t = da / (da - db);
98 output.push(a + (b - a) * t);
99 }
100 }
101 output
102}
103
104fn polygon_area(verts: &[Vec2]) -> f32 {
105 let n = verts.len();
106 if n < 3 { return 0.0; }
107 let mut area = 0.0;
108 for i in 0..n {
109 let j = (i + 1) % n;
110 area += verts[i].x * verts[j].y;
111 area -= verts[j].x * verts[i].y;
112 }
113 area.abs() * 0.5
114}
115
116#[derive(Debug, Clone, Copy)]
120pub struct DelaunayTriangle {
121 pub a: usize,
122 pub b: usize,
123 pub c: usize,
124}
125
126#[derive(Debug, Clone)]
128pub struct DelaunayTriangulation {
129 pub points: Vec<Vec2>,
130 pub triangles: Vec<DelaunayTriangle>,
131}
132
133impl DelaunayTriangulation {
134 pub fn compute(points: &[Vec2]) -> Self {
136 if points.len() < 3 {
137 return Self { points: points.to_vec(), triangles: Vec::new() };
138 }
139
140 let (mut min, mut max) = (points[0], points[0]);
142 for p in points {
143 min = min.min(*p);
144 max = max.max(*p);
145 }
146 let d = (max - min).max_element() * 10.0;
147 let mid = (min + max) * 0.5;
148
149 let mut all_points = vec![
150 mid + Vec2::new(-d, -d),
151 mid + Vec2::new(d, -d),
152 mid + Vec2::new(0.0, d),
153 ];
154 let super_start = 0;
155 all_points.extend_from_slice(points);
156
157 let mut triangles = vec![DelaunayTriangle { a: 0, b: 1, c: 2 }];
158
159 for pi in 3..all_points.len() {
161 let p = all_points[pi];
162 let mut bad_triangles = Vec::new();
163
164 for (ti, tri) in triangles.iter().enumerate() {
165 if circumcircle_contains(&all_points, tri, p) {
166 bad_triangles.push(ti);
167 }
168 }
169
170 let mut boundary: Vec<(usize, usize)> = Vec::new();
172 for &ti in &bad_triangles {
173 let tri = &triangles[ti];
174 let edges = [(tri.a, tri.b), (tri.b, tri.c), (tri.c, tri.a)];
175 for &(a, b) in &edges {
176 let shared = bad_triangles.iter().any(|&oti| {
177 if oti == ti { return false; }
178 let ot = &triangles[oti];
179 let oedges = [(ot.a, ot.b), (ot.b, ot.c), (ot.c, ot.a)];
180 oedges.iter().any(|&(oa, ob)| (oa == b && ob == a) || (oa == a && ob == b))
181 });
182 if !shared {
183 boundary.push((a, b));
184 }
185 }
186 }
187
188 bad_triangles.sort_unstable_by(|a, b| b.cmp(a));
190 for ti in bad_triangles {
191 triangles.swap_remove(ti);
192 }
193
194 for (a, b) in boundary {
196 triangles.push(DelaunayTriangle { a, b, c: pi });
197 }
198 }
199
200 triangles.retain(|t| t.a >= 3 && t.b >= 3 && t.c >= 3);
202
203 for t in &mut triangles {
205 t.a -= 3;
206 t.b -= 3;
207 t.c -= 3;
208 }
209
210 Self {
211 points: points.to_vec(),
212 triangles,
213 }
214 }
215
216 pub fn triangle_count(&self) -> usize { self.triangles.len() }
217}
218
219fn circumcircle_contains(points: &[Vec2], tri: &DelaunayTriangle, p: Vec2) -> bool {
220 let a = points[tri.a];
221 let b = points[tri.b];
222 let c = points[tri.c];
223
224 let ax = a.x - p.x;
225 let ay = a.y - p.y;
226 let bx = b.x - p.x;
227 let by = b.y - p.y;
228 let cx = c.x - p.x;
229 let cy = c.y - p.y;
230
231 let det = ax * (by * (cx * cx + cy * cy) - cy * (bx * bx + by * by))
232 - bx * (ay * (cx * cx + cy * cy) - cy * (ax * ax + ay * ay))
233 + cx * (ay * (bx * bx + by * by) - by * (ax * ax + ay * ay));
234
235 det > 0.0
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn voronoi_creates_cells() {
244 let sites = vec![Vec2::new(1.0, 1.0), Vec2::new(3.0, 1.0), Vec2::new(2.0, 3.0)];
245 let vor = VoronoiDiagram::compute(&sites, Vec2::ZERO, Vec2::splat(4.0));
246 assert_eq!(vor.cells.len(), 3);
247 for cell in &vor.cells {
248 assert!(!cell.vertices.is_empty());
249 }
250 }
251
252 #[test]
253 fn nearest_site_correct() {
254 let sites = vec![Vec2::ZERO, Vec2::new(10.0, 0.0)];
255 let vor = VoronoiDiagram::compute(&sites, Vec2::splat(-20.0), Vec2::splat(20.0));
256 assert_eq!(vor.nearest_site(Vec2::new(1.0, 0.0)), 0);
257 assert_eq!(vor.nearest_site(Vec2::new(9.0, 0.0)), 1);
258 }
259
260 #[test]
261 fn delaunay_basic() {
262 let points = vec![
263 Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0),
264 Vec2::new(0.0, 1.0), Vec2::new(1.0, 1.0),
265 ];
266 let dt = DelaunayTriangulation::compute(&points);
267 assert!(dt.triangle_count() >= 2);
268 }
269
270 #[test]
271 fn delaunay_too_few_points() {
272 let dt = DelaunayTriangulation::compute(&[Vec2::ZERO, Vec2::X]);
273 assert_eq!(dt.triangle_count(), 0);
274 }
275}