1use crate::error::{SpatialError, SpatialResult};
31use std::collections::BinaryHeap;
32
33const EPSILON: f64 = 1e-10;
35
36#[derive(Debug, Clone, Copy)]
38pub struct VoronoiVertex {
39 pub x: f64,
41 pub y: f64,
43}
44
45impl VoronoiVertex {
46 pub fn new(x: f64, y: f64) -> Self {
48 Self { x, y }
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct VoronoiEdge {
55 pub start_vertex: Option<usize>,
57 pub end_vertex: Option<usize>,
59 pub left_site: usize,
61 pub right_site: usize,
63 pub direction: Option<[f64; 2]>,
65}
66
67#[derive(Debug, Clone)]
69pub struct VoronoiCell {
70 pub site_index: usize,
72 pub edge_indices: Vec<usize>,
74 pub vertex_indices: Vec<usize>,
76 pub is_bounded: bool,
78}
79
80#[derive(Debug, Clone)]
82pub struct VoronoiDiagram {
83 pub sites: Vec<[f64; 2]>,
85 pub vertices: Vec<VoronoiVertex>,
87 pub edges: Vec<VoronoiEdge>,
89 pub cells: Vec<VoronoiCell>,
91}
92
93impl VoronoiDiagram {
94 pub fn num_sites(&self) -> usize {
96 self.sites.len()
97 }
98
99 pub fn num_vertices(&self) -> usize {
101 self.vertices.len()
102 }
103
104 pub fn num_edges(&self) -> usize {
106 self.edges.len()
107 }
108
109 pub fn num_bounded_edges(&self) -> usize {
111 self.edges
112 .iter()
113 .filter(|e| e.start_vertex.is_some() && e.end_vertex.is_some())
114 .count()
115 }
116
117 pub fn nearest_site(&self, point: &[f64; 2]) -> usize {
119 let mut best_idx = 0;
120 let mut best_dist = f64::INFINITY;
121
122 for (i, site) in self.sites.iter().enumerate() {
123 let dx = point[0] - site[0];
124 let dy = point[1] - site[1];
125 let dist = dx * dx + dy * dy;
126 if dist < best_dist {
127 best_dist = dist;
128 best_idx = i;
129 }
130 }
131
132 best_idx
133 }
134}
135
136#[derive(Debug, Clone)]
138struct Arc {
139 site_idx: usize,
141 circle_event: Option<usize>,
143}
144
145#[derive(Debug, Clone)]
147enum EventKind {
148 Site(usize),
150 Circle {
152 arc_idx: usize,
153 center_x: f64,
154 center_y: f64,
155 radius: f64,
156 },
157}
158
159#[derive(Debug, Clone)]
161struct Event {
162 x: f64,
164 kind: EventKind,
166 valid: bool,
168 id: usize,
170}
171
172impl PartialEq for Event {
173 fn eq(&self, other: &Self) -> bool {
174 self.id == other.id
175 }
176}
177
178impl Eq for Event {}
179
180impl PartialOrd for Event {
181 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
182 Some(self.cmp(other))
183 }
184}
185
186impl Ord for Event {
187 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
188 other
190 .x
191 .partial_cmp(&self.x)
192 .unwrap_or(std::cmp::Ordering::Equal)
193 }
194}
195
196struct FortuneBuilder {
198 sites: Vec<[f64; 2]>,
199 beach_line: Vec<Arc>,
200 events: BinaryHeap<Event>,
201 event_counter: usize,
202 circle_event_valid: Vec<bool>,
204 vertices: Vec<VoronoiVertex>,
206 edges: Vec<VoronoiEdge>,
207 half_edges: Vec<HalfEdgeRecord>,
209}
210
211#[derive(Debug, Clone)]
213struct HalfEdgeRecord {
214 left_site: usize,
215 right_site: usize,
216 start_vertex: Option<usize>,
217 end_vertex: Option<usize>,
218 direction: [f64; 2],
219}
220
221impl FortuneBuilder {
222 fn new(sites: Vec<[f64; 2]>) -> Self {
223 Self {
224 sites,
225 beach_line: Vec::new(),
226 events: BinaryHeap::new(),
227 event_counter: 0,
228 circle_event_valid: Vec::new(),
229 vertices: Vec::new(),
230 edges: Vec::new(),
231 half_edges: Vec::new(),
232 }
233 }
234
235 fn next_event_id(&mut self) -> usize {
236 let id = self.event_counter;
237 self.event_counter += 1;
238 id
239 }
240
241 fn build(mut self) -> SpatialResult<VoronoiDiagram> {
242 let n = self.sites.len();
243 if n < 2 {
244 return Err(SpatialError::ValueError(
245 "Need at least 2 sites for Voronoi diagram".to_string(),
246 ));
247 }
248
249 let mut site_order: Vec<usize> = (0..n).collect();
251 site_order.sort_by(|&a, &b| {
252 self.sites[a][0]
253 .partial_cmp(&self.sites[b][0])
254 .unwrap_or(std::cmp::Ordering::Equal)
255 .then_with(|| {
256 self.sites[a][1]
257 .partial_cmp(&self.sites[b][1])
258 .unwrap_or(std::cmp::Ordering::Equal)
259 })
260 });
261
262 for &idx in &site_order {
264 let id = self.next_event_id();
265 self.events.push(Event {
266 x: self.sites[idx][0],
267 kind: EventKind::Site(idx),
268 valid: true,
269 id,
270 });
271 }
272
273 let max_iterations = n * n * 4 + 100;
275 let mut iterations = 0;
276
277 while let Some(event) = self.events.pop() {
278 iterations += 1;
279 if iterations > max_iterations {
280 break;
281 }
282
283 if !event.valid {
284 continue;
285 }
286
287 match event.kind {
288 EventKind::Site(site_idx) => {
289 self.handle_site_event(site_idx, event.x);
290 }
291 EventKind::Circle {
292 arc_idx,
293 center_x,
294 center_y,
295 ..
296 } => {
297 if arc_idx < self.circle_event_valid.len() && self.circle_event_valid[arc_idx] {
299 self.handle_circle_event(arc_idx, center_x, center_y);
300 }
301 }
302 }
303 }
304
305 self.finalize_edges();
307
308 let cells = self.build_cells();
310
311 Ok(VoronoiDiagram {
312 sites: self.sites,
313 vertices: self.vertices,
314 edges: self.edges,
315 cells,
316 })
317 }
318
319 fn handle_site_event(&mut self, site_idx: usize, _sweep_x: f64) {
320 if self.beach_line.is_empty() {
321 self.beach_line.push(Arc {
322 site_idx,
323 circle_event: None,
324 });
325 return;
326 }
327
328 let site = self.sites[site_idx];
329
330 let arc_idx = self.find_arc_above(site[1], site[0]);
332
333 if let Some(ce_idx) = self.beach_line[arc_idx].circle_event {
335 if ce_idx < self.circle_event_valid.len() {
336 self.circle_event_valid[ce_idx] = false;
337 }
338 }
339
340 let existing_site = self.beach_line[arc_idx].site_idx;
341
342 let dir = self.compute_edge_direction(existing_site, site_idx);
344 self.half_edges.push(HalfEdgeRecord {
345 left_site: existing_site,
346 right_site: site_idx,
347 start_vertex: None,
348 end_vertex: None,
349 direction: dir,
350 });
351
352 let old_arc = Arc {
354 site_idx: existing_site,
355 circle_event: None,
356 };
357 let new_arc = Arc {
358 site_idx,
359 circle_event: None,
360 };
361 let old_arc_copy = Arc {
362 site_idx: existing_site,
363 circle_event: None,
364 };
365
366 self.beach_line[arc_idx] = old_arc;
368 self.beach_line.insert(arc_idx + 1, new_arc);
369 self.beach_line.insert(arc_idx + 2, old_arc_copy);
370
371 while self.circle_event_valid.len() < self.beach_line.len() {
373 self.circle_event_valid.push(false);
374 }
375
376 if arc_idx > 0 {
378 self.check_circle_event(arc_idx);
379 }
380 if arc_idx + 2 < self.beach_line.len() {
381 self.check_circle_event(arc_idx + 2);
382 }
383 }
384
385 fn handle_circle_event(&mut self, arc_idx: usize, center_x: f64, center_y: f64) {
386 if arc_idx >= self.beach_line.len() || arc_idx == 0 || arc_idx >= self.beach_line.len() - 1
387 {
388 return;
389 }
390
391 if arc_idx < self.circle_event_valid.len() {
393 self.circle_event_valid[arc_idx] = false;
394 }
395
396 let vertex_idx = self.vertices.len();
398 self.vertices.push(VoronoiVertex::new(center_x, center_y));
399
400 let left_site = self.beach_line[arc_idx - 1].site_idx;
402 let mid_site = self.beach_line[arc_idx].site_idx;
403 let right_site = self.beach_line[arc_idx + 1].site_idx;
404
405 for he in &mut self.half_edges {
407 if ((he.left_site == left_site && he.right_site == mid_site)
408 || (he.left_site == mid_site && he.right_site == left_site))
409 && he.end_vertex.is_none()
410 {
411 he.end_vertex = Some(vertex_idx);
412 }
413 if ((he.left_site == mid_site && he.right_site == right_site)
414 || (he.left_site == right_site && he.right_site == mid_site))
415 && he.end_vertex.is_none()
416 {
417 he.end_vertex = Some(vertex_idx);
418 }
419 }
420
421 let dir = self.compute_edge_direction(left_site, right_site);
423 self.half_edges.push(HalfEdgeRecord {
424 left_site,
425 right_site,
426 start_vertex: Some(vertex_idx),
427 end_vertex: None,
428 direction: dir,
429 });
430
431 if arc_idx > 0 {
433 if let Some(ce_idx) = self.beach_line[arc_idx - 1].circle_event {
434 if ce_idx < self.circle_event_valid.len() {
435 self.circle_event_valid[ce_idx] = false;
436 }
437 }
438 self.beach_line[arc_idx - 1].circle_event = None;
439 }
440 if arc_idx + 1 < self.beach_line.len() {
441 if let Some(ce_idx) = self.beach_line[arc_idx + 1].circle_event {
442 if ce_idx < self.circle_event_valid.len() {
443 self.circle_event_valid[ce_idx] = false;
444 }
445 }
446 self.beach_line[arc_idx + 1].circle_event = None;
447 }
448
449 self.beach_line.remove(arc_idx);
451 if arc_idx < self.circle_event_valid.len() {
452 self.circle_event_valid.remove(arc_idx);
453 }
454
455 if arc_idx > 0 && arc_idx < self.beach_line.len() {
457 self.check_circle_event(arc_idx - 1);
458 if arc_idx < self.beach_line.len() {
459 self.check_circle_event(arc_idx);
460 }
461 }
462 }
463
464 fn find_arc_above(&self, y: f64, sweep_x: f64) -> usize {
465 if self.beach_line.len() <= 1 {
466 return 0;
467 }
468
469 for i in 0..self.beach_line.len() {
472 if i + 1 < self.beach_line.len() {
473 let site_a = self.sites[self.beach_line[i].site_idx];
474 let site_b = self.sites[self.beach_line[i + 1].site_idx];
475
476 if let Some(bp_y) = self.compute_breakpoint(site_a, site_b, sweep_x) {
478 if y <= bp_y {
479 return i;
480 }
481 }
482 } else {
483 return i;
484 }
485 }
486
487 self.beach_line.len() - 1
488 }
489
490 fn compute_breakpoint(&self, site_a: [f64; 2], site_b: [f64; 2], sweep_x: f64) -> Option<f64> {
491 let ax = site_a[0];
492 let ay = site_a[1];
493 let bx = site_b[0];
494 let by = site_b[1];
495
496 if (ax - bx).abs() < EPSILON {
498 return Some((ay + by) / 2.0);
499 }
500
501 if (ax - sweep_x).abs() < EPSILON {
503 return Some(ay);
504 }
505 if (bx - sweep_x).abs() < EPSILON {
506 return Some(by);
507 }
508
509 let da = ax - sweep_x;
511 let db = bx - sweep_x;
512
513 let a = 1.0 / da - 1.0 / db;
514 let b = -2.0 * (ay / da - by / db);
515 let c = (ay * ay + ax * ax - sweep_x * sweep_x) / da
516 - (by * by + bx * bx - sweep_x * sweep_x) / db;
517
518 if a.abs() < EPSILON {
519 if b.abs() < EPSILON {
520 return None;
521 }
522 return Some(-c / b);
523 }
524
525 let discriminant = b * b - 4.0 * a * c;
526 if discriminant < 0.0 {
527 return Some((ay + by) / 2.0);
528 }
529
530 let sqrt_disc = discriminant.sqrt();
531 let y1 = (-b - sqrt_disc) / (2.0 * a);
532 let y2 = (-b + sqrt_disc) / (2.0 * a);
533
534 if (ax < bx) == (y1 < y2) {
536 Some(y1)
537 } else {
538 Some(y2)
539 }
540 }
541
542 fn check_circle_event(&mut self, arc_idx: usize) {
543 if arc_idx == 0 || arc_idx >= self.beach_line.len() - 1 {
544 return;
545 }
546
547 let left = self.beach_line[arc_idx - 1].site_idx;
548 let mid = self.beach_line[arc_idx].site_idx;
549 let right = self.beach_line[arc_idx + 1].site_idx;
550
551 if left == right {
553 return;
554 }
555
556 let a = self.sites[left];
557 let b = self.sites[mid];
558 let c = self.sites[right];
559
560 if let Some((cx, cy, r)) = circumcircle(a, b, c) {
562 let event_x = cx + r;
563
564 let max_x = a[0].max(b[0]).max(c[0]);
566 if event_x >= max_x - EPSILON {
567 while self.circle_event_valid.len() <= arc_idx {
569 self.circle_event_valid.push(false);
570 }
571
572 self.circle_event_valid[arc_idx] = true;
573
574 let id = self.next_event_id();
575 self.beach_line[arc_idx].circle_event = Some(arc_idx);
576
577 self.events.push(Event {
578 x: event_x,
579 kind: EventKind::Circle {
580 arc_idx,
581 center_x: cx,
582 center_y: cy,
583 radius: r,
584 },
585 valid: true,
586 id,
587 });
588 }
589 }
590 }
591
592 fn compute_edge_direction(&self, site_a: usize, site_b: usize) -> [f64; 2] {
593 let a = self.sites[site_a];
594 let b = self.sites[site_b];
595
596 let dx = b[0] - a[0];
598 let dy = b[1] - a[1];
599
600 [-dy, dx]
602 }
603
604 fn finalize_edges(&mut self) {
605 for he in &self.half_edges {
607 self.edges.push(VoronoiEdge {
608 start_vertex: he.start_vertex,
609 end_vertex: he.end_vertex,
610 left_site: he.left_site,
611 right_site: he.right_site,
612 direction: Some(he.direction),
613 });
614 }
615 }
616
617 fn build_cells(&self) -> Vec<VoronoiCell> {
618 let n = self.sites.len();
619 let mut cells = Vec::with_capacity(n);
620
621 for site_idx in 0..n {
622 let mut edge_indices = Vec::new();
623 let mut vertex_indices = Vec::new();
624 let mut is_bounded = true;
625
626 for (edge_idx, edge) in self.edges.iter().enumerate() {
627 if edge.left_site == site_idx || edge.right_site == site_idx {
628 edge_indices.push(edge_idx);
629
630 if let Some(v) = edge.start_vertex {
631 if !vertex_indices.contains(&v) {
632 vertex_indices.push(v);
633 }
634 } else {
635 is_bounded = false;
636 }
637
638 if let Some(v) = edge.end_vertex {
639 if !vertex_indices.contains(&v) {
640 vertex_indices.push(v);
641 }
642 } else {
643 is_bounded = false;
644 }
645 }
646 }
647
648 cells.push(VoronoiCell {
649 site_index: site_idx,
650 edge_indices,
651 vertex_indices,
652 is_bounded,
653 });
654 }
655
656 cells
657 }
658}
659
660fn circumcircle(a: [f64; 2], b: [f64; 2], c: [f64; 2]) -> Option<(f64, f64, f64)> {
664 let ax = a[0];
665 let ay = a[1];
666 let bx = b[0];
667 let by = b[1];
668 let cx = c[0];
669 let cy = c[1];
670
671 let d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
672
673 if d.abs() < EPSILON {
674 return None; }
676
677 let ux = ((ax * ax + ay * ay) * (by - cy)
678 + (bx * bx + by * by) * (cy - ay)
679 + (cx * cx + cy * cy) * (ay - by))
680 / d;
681 let uy = ((ax * ax + ay * ay) * (cx - bx)
682 + (bx * bx + by * by) * (ax - cx)
683 + (cx * cx + cy * cy) * (bx - ax))
684 / d;
685
686 let r = ((ax - ux).powi(2) + (ay - uy).powi(2)).sqrt();
687
688 Some((ux, uy, r))
689}
690
691pub fn fortune_voronoi_2d(sites: &[[f64; 2]]) -> SpatialResult<VoronoiDiagram> {
713 if sites.len() < 2 {
714 return Err(SpatialError::ValueError(
715 "Need at least 2 sites for Voronoi diagram".to_string(),
716 ));
717 }
718
719 for i in 0..sites.len() {
721 for j in (i + 1)..sites.len() {
722 if (sites[i][0] - sites[j][0]).abs() < EPSILON
723 && (sites[i][1] - sites[j][1]).abs() < EPSILON
724 {
725 return Err(SpatialError::ValueError(format!(
726 "Duplicate sites at index {} and {}: [{}, {}]",
727 i, j, sites[i][0], sites[i][1]
728 )));
729 }
730 }
731 }
732
733 let builder = FortuneBuilder::new(sites.to_vec());
734 builder.build()
735}
736
737pub fn fortune_voronoi_from_array(
747 points: &scirs2_core::ndarray::ArrayView2<'_, f64>,
748) -> SpatialResult<VoronoiDiagram> {
749 if points.ncols() != 2 {
750 return Err(SpatialError::DimensionError(
751 "Points must be 2D for Voronoi diagram".to_string(),
752 ));
753 }
754
755 let sites: Vec<[f64; 2]> = (0..points.nrows())
756 .map(|i| [points[[i, 0]], points[[i, 1]]])
757 .collect();
758
759 fortune_voronoi_2d(&sites)
760}
761
762#[cfg(test)]
763mod tests {
764 use super::*;
765
766 #[test]
767 fn test_two_sites() {
768 let sites = vec![[0.0, 0.0], [2.0, 0.0]];
769 let diagram = fortune_voronoi_2d(&sites).expect("Operation failed");
770
771 assert_eq!(diagram.num_sites(), 2);
772 assert!(diagram.num_edges() >= 1);
774 }
775
776 #[test]
777 fn test_three_sites_triangle() {
778 let sites = vec![[0.0, 0.0], [2.0, 0.0], [1.0, 2.0]];
779 let diagram = fortune_voronoi_2d(&sites).expect("Operation failed");
780
781 assert_eq!(diagram.num_sites(), 3);
782 assert!(diagram.num_vertices() >= 1);
784 assert!(diagram.num_edges() >= 3);
785 }
786
787 #[test]
788 fn test_four_sites_square() {
789 let sites = vec![[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]];
790 let diagram = fortune_voronoi_2d(&sites).expect("Operation failed");
791
792 assert_eq!(diagram.num_sites(), 4);
793 assert!(!diagram.vertices.is_empty());
794 assert!(!diagram.edges.is_empty());
795 assert_eq!(diagram.cells.len(), 4);
797 }
798
799 #[test]
800 fn test_nearest_site() {
801 let sites = vec![[0.0, 0.0], [10.0, 0.0], [5.0, 10.0]];
802 let diagram = fortune_voronoi_2d(&sites).expect("Operation failed");
803
804 assert_eq!(diagram.nearest_site(&[0.1, 0.1]), 0);
806 assert_eq!(diagram.nearest_site(&[9.9, 0.1]), 1);
808 assert_eq!(diagram.nearest_site(&[5.0, 9.0]), 2);
810 }
811
812 #[test]
813 fn test_cells_created() {
814 let sites = vec![[0.0, 0.0], [1.0, 0.0], [0.5, 1.0]];
815 let diagram = fortune_voronoi_2d(&sites).expect("Operation failed");
816
817 for cell in &diagram.cells {
819 assert!(!cell.edge_indices.is_empty());
820 }
821 }
822
823 #[test]
824 fn test_collinear_sites() {
825 let sites = vec![[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]];
827 let diagram = fortune_voronoi_2d(&sites).expect("Operation failed");
828
829 assert_eq!(diagram.num_sites(), 3);
830 assert!(diagram.num_edges() >= 2);
832 }
833
834 #[test]
835 fn test_duplicate_sites_error() {
836 let sites = vec![[0.0, 0.0], [0.0, 0.0], [1.0, 1.0]];
837 let result = fortune_voronoi_2d(&sites);
838 assert!(result.is_err());
839 }
840
841 #[test]
842 fn test_too_few_sites() {
843 let sites = vec![[0.0, 0.0]];
844 let result = fortune_voronoi_2d(&sites);
845 assert!(result.is_err());
846 }
847
848 #[test]
849 fn test_circumcircle() {
850 let a = [0.0, 0.0];
851 let b = [1.0, 0.0];
852 let c = [0.0, 1.0];
853
854 let result = circumcircle(a, b, c);
855 assert!(result.is_some());
856
857 let (cx, cy, r) = result.expect("Operation failed");
858 assert!((cx - 0.5).abs() < 1e-10);
860 assert!((cy - 0.5).abs() < 1e-10);
861 assert!((r - (0.5_f64).sqrt()).abs() < 1e-10);
863 }
864
865 #[test]
866 fn test_circumcircle_collinear() {
867 let a = [0.0, 0.0];
868 let b = [1.0, 0.0];
869 let c = [2.0, 0.0];
870
871 let result = circumcircle(a, b, c);
872 assert!(result.is_none());
873 }
874
875 #[test]
876 fn test_from_array() {
877 use scirs2_core::ndarray::array;
878
879 let points = array![[0.0, 0.0], [1.0, 0.0], [0.5, 1.0]];
880 let diagram = fortune_voronoi_from_array(&points.view()).expect("Operation failed");
881 assert_eq!(diagram.num_sites(), 3);
882 }
883
884 #[test]
885 fn test_many_sites() {
886 let mut sites = Vec::new();
888 for i in 0..8 {
889 let angle = 2.0 * std::f64::consts::PI * (i as f64) / 8.0;
890 sites.push([angle.cos(), angle.sin()]);
891 }
892
893 let diagram = fortune_voronoi_2d(&sites).expect("Operation failed");
894 assert_eq!(diagram.num_sites(), 8);
895 assert!(!diagram.vertices.is_empty());
896 assert!(!diagram.edges.is_empty());
897 }
898}