oxihuman_core/
voronoi_2d.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, Copy, PartialEq)]
9#[allow(dead_code)]
10pub struct Point2 {
11 pub x: f32,
12 pub y: f32,
13}
14
15impl Point2 {
16 #[allow(dead_code)]
17 pub fn new(x: f32, y: f32) -> Self {
18 Self { x, y }
19 }
20
21 #[allow(dead_code)]
22 pub fn dist_sq(self, other: Self) -> f32 {
23 let dx = self.x - other.x;
24 let dy = self.y - other.y;
25 dx * dx + dy * dy
26 }
27}
28
29#[derive(Debug, Clone)]
31#[allow(dead_code)]
32pub struct VoronoiCell2D {
33 pub seed: Point2,
34 pub id: usize,
35}
36
37#[derive(Debug, Clone, Default)]
39#[allow(dead_code)]
40pub struct Voronoi2D {
41 pub seeds: Vec<Point2>,
42}
43
44impl Voronoi2D {
45 #[allow(dead_code)]
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 #[allow(dead_code)]
53 pub fn add_seed(&mut self, p: Point2) {
54 self.seeds.push(p);
55 }
56
57 #[allow(dead_code)]
60 pub fn nearest_seed(&self, q: Point2) -> Option<usize> {
61 if self.seeds.is_empty() {
62 return None;
63 }
64 let mut best = 0;
65 let mut best_d = q.dist_sq(self.seeds[0]);
66 for (i, &s) in self.seeds.iter().enumerate().skip(1) {
67 let d = q.dist_sq(s);
68 if d < best_d {
69 best_d = d;
70 best = i;
71 }
72 }
73 Some(best)
74 }
75
76 #[allow(dead_code)]
79 pub fn rasterize(&self, width: usize, height: usize) -> Vec<usize> {
80 let mut grid = vec![0usize; width * height];
81 if self.seeds.is_empty() {
82 return grid;
83 }
84 for row in 0..height {
85 for col in 0..width {
86 let q = Point2::new(col as f32, row as f32);
87 let idx = self.nearest_seed(q).unwrap_or(0);
88 grid[row * width + col] = idx;
89 }
90 }
91 grid
92 }
93
94 #[allow(dead_code)]
96 pub fn seed_count(&self) -> usize {
97 self.seeds.len()
98 }
99
100 #[allow(dead_code)]
102 pub fn clear(&mut self) {
103 self.seeds.clear();
104 }
105
106 #[allow(dead_code)]
108 pub fn cells(&self) -> Vec<VoronoiCell2D> {
109 self.seeds
110 .iter()
111 .enumerate()
112 .map(|(id, &seed)| VoronoiCell2D { seed, id })
113 .collect()
114 }
115}
116
117#[allow(dead_code)]
119pub fn voronoi_assign(diagram: &Voronoi2D, queries: &[Point2]) -> Vec<usize> {
120 queries
121 .iter()
122 .map(|&q| diagram.nearest_seed(q).unwrap_or(0))
123 .collect()
124}
125
126#[allow(dead_code)]
128pub fn build_voronoi(seeds: &[Point2]) -> Voronoi2D {
129 let mut v = Voronoi2D::new();
130 for &s in seeds {
131 v.add_seed(s);
132 }
133 v
134}
135
136#[allow(dead_code)]
138pub fn cell_centroid(diagram: &Voronoi2D, cell_id: usize, samples: &[Point2]) -> Option<Point2> {
139 let pts: Vec<Point2> = samples
140 .iter()
141 .copied()
142 .filter(|&q| diagram.nearest_seed(q) == Some(cell_id))
143 .collect();
144 if pts.is_empty() {
145 return None;
146 }
147 let n = pts.len() as f32;
148 let sx: f32 = pts.iter().map(|p| p.x).sum();
149 let sy: f32 = pts.iter().map(|p| p.y).sum();
150 Some(Point2::new(sx / n, sy / n))
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 fn make_seeds() -> Voronoi2D {
158 let mut v = Voronoi2D::new();
159 v.add_seed(Point2::new(0.0, 0.0));
160 v.add_seed(Point2::new(10.0, 0.0));
161 v.add_seed(Point2::new(5.0, 10.0));
162 v
163 }
164
165 #[test]
166 fn nearest_seed_returns_none_for_empty() {
167 let v = Voronoi2D::new();
168 assert!(v.nearest_seed(Point2::new(1.0, 1.0)).is_none());
169 }
170
171 #[test]
172 fn nearest_seed_basic() {
173 let v = make_seeds();
174 assert_eq!(v.nearest_seed(Point2::new(0.5, 0.5)), Some(0));
175 assert_eq!(v.nearest_seed(Point2::new(9.5, 0.5)), Some(1));
176 assert_eq!(v.nearest_seed(Point2::new(5.0, 9.5)), Some(2));
177 }
178
179 #[test]
180 fn seed_count_matches() {
181 let v = make_seeds();
182 assert_eq!(v.seed_count(), 3);
183 }
184
185 #[test]
186 fn clear_removes_all_seeds() {
187 let mut v = make_seeds();
188 v.clear();
189 assert_eq!(v.seed_count(), 0);
190 }
191
192 #[test]
193 fn cells_returns_correct_ids() {
194 let v = make_seeds();
195 let cells = v.cells();
196 assert_eq!(cells.len(), 3);
197 assert_eq!(cells[0].id, 0);
198 assert_eq!(cells[2].id, 2);
199 }
200
201 #[test]
202 fn rasterize_size_matches() {
203 let v = make_seeds();
204 let grid = v.rasterize(4, 3);
205 assert_eq!(grid.len(), 12);
206 }
207
208 #[test]
209 fn rasterize_empty_returns_zeros() {
210 let v = Voronoi2D::new();
211 let grid = v.rasterize(3, 3);
212 assert!(grid.iter().all(|&x| x == 0));
213 }
214
215 #[test]
216 fn voronoi_assign_empty_seeds() {
217 let v = Voronoi2D::new();
218 let pts = vec![Point2::new(0.0, 0.0)];
219 let out = voronoi_assign(&v, &pts);
220 assert_eq!(out, vec![0]);
221 }
222
223 #[test]
224 fn build_voronoi_seed_count() {
225 let seeds = vec![Point2::new(0.0, 0.0), Point2::new(1.0, 1.0)];
226 let v = build_voronoi(&seeds);
227 assert_eq!(v.seed_count(), 2);
228 }
229
230 #[test]
231 fn cell_centroid_none_when_no_samples() {
232 let v = make_seeds();
233 let c = cell_centroid(&v, 0, &[]);
234 assert!(c.is_none());
235 }
236}