1use crate::circular_stats as cs;
40use kiddo::{KdTree, SquaredEuclidean};
41use nalgebra::{Point2, Vector2};
42use std::collections::{HashMap, HashSet, VecDeque};
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum Admit {
47 Accept,
49 Reject,
52}
53
54#[derive(Clone, Copy, Debug)]
57pub struct LabelledNeighbour {
58 pub idx: usize,
59 pub at: (i32, i32),
60 pub position: Point2<f32>,
61}
62
63pub trait GrowValidator {
70 fn is_eligible(&self, idx: usize) -> bool;
73
74 fn required_label_at(&self, i: i32, j: i32) -> Option<u8>;
77
78 fn label_of(&self, idx: usize) -> Option<u8>;
82
83 fn accept_candidate(
88 &self,
89 idx: usize,
90 at: (i32, i32),
91 prediction: Point2<f32>,
92 neighbours: &[LabelledNeighbour],
93 ) -> Admit;
94
95 fn edge_ok(
103 &self,
104 _candidate_idx: usize,
105 _neighbour_idx: usize,
106 _at_candidate: (i32, i32),
107 _at_neighbour: (i32, i32),
108 ) -> bool {
109 true
110 }
111}
112
113#[non_exhaustive]
115#[derive(Clone, Copy, Debug)]
116pub struct GrowParams {
117 pub attach_search_rel: f32,
120 pub attach_ambiguity_factor: f32,
123}
124
125impl Default for GrowParams {
126 fn default() -> Self {
127 Self {
128 attach_search_rel: 0.35,
129 attach_ambiguity_factor: 1.5,
130 }
131 }
132}
133
134impl GrowParams {
135 pub fn new(attach_search_rel: f32, attach_ambiguity_factor: f32) -> Self {
136 Self {
137 attach_search_rel,
138 attach_ambiguity_factor,
139 }
140 }
141}
142
143#[derive(Clone, Copy, Debug)]
146pub struct Seed {
147 pub a: usize,
148 pub b: usize,
149 pub c: usize,
150 pub d: usize,
151}
152
153#[derive(Debug, Default)]
155pub struct GrowResult {
156 pub labelled: HashMap<(i32, i32), usize>,
159 pub by_corner: HashMap<usize, (i32, i32)>,
161 pub ambiguous: HashSet<(i32, i32)>,
163 pub holes: HashSet<(i32, i32)>,
165 pub grid_u: Vector2<f32>,
167 pub grid_v: Vector2<f32>,
168}
169
170pub fn bfs_grow<V: GrowValidator>(
181 positions: &[Point2<f32>],
182 seed: Seed,
183 cell_size: f32,
184 params: &GrowParams,
185 validator: &V,
186) -> GrowResult {
187 let _ = cs::wrap_pi; let grid_u = {
191 let raw = positions[seed.b] - positions[seed.a];
192 let n = raw.norm().max(1e-6);
193 raw / n
194 };
195 let grid_v = {
196 let raw = positions[seed.c] - positions[seed.a];
197 let n = raw.norm().max(1e-6);
198 raw / n
199 };
200
201 let mut tree: KdTree<f32, 2> = KdTree::new();
203 let mut tree_slot_to_corner: Vec<usize> = Vec::new();
204 for (idx, pos) in positions.iter().enumerate() {
205 if validator.is_eligible(idx) {
206 tree.add(&[pos.x, pos.y], tree_slot_to_corner.len() as u64);
207 tree_slot_to_corner.push(idx);
208 }
209 }
210
211 let mut labelled: HashMap<(i32, i32), usize> = HashMap::new();
212 let mut by_corner: HashMap<usize, (i32, i32)> = HashMap::new();
213 let mut ambiguous: HashSet<(i32, i32)> = HashSet::new();
214 let mut holes: HashSet<(i32, i32)> = HashSet::new();
215
216 for (ij, idx) in [
217 ((0, 0), seed.a),
218 ((1, 0), seed.b),
219 ((0, 1), seed.c),
220 ((1, 1), seed.d),
221 ] {
222 labelled.insert(ij, idx);
223 by_corner.insert(idx, ij);
224 }
225
226 let mut boundary: VecDeque<(i32, i32)> = VecDeque::new();
227 let mut seen_boundary: HashSet<(i32, i32)> = HashSet::new();
228 for ij in labelled.keys().copied().collect::<Vec<_>>() {
229 enqueue_cardinal_neighbours(ij, &labelled, &mut boundary, &mut seen_boundary);
230 }
231
232 let search_r = params.attach_search_rel * cell_size;
233
234 while let Some(pos) = boundary.pop_front() {
235 if labelled.contains_key(&pos) {
236 continue;
237 }
238
239 let neighbours = collect_labelled_neighbours(pos, 1, &labelled, positions);
240 if neighbours.is_empty() {
241 holes.insert(pos);
242 continue;
243 }
244
245 let prediction = predict_from_neighbours(pos, &neighbours, grid_u, grid_v, cell_size);
246
247 let required_label = validator.required_label_at(pos.0, pos.1);
248 let candidates = collect_candidates(
249 &tree,
250 &tree_slot_to_corner,
251 prediction,
252 search_r,
253 validator,
254 required_label,
255 &by_corner,
256 );
257
258 let choice = choose_unambiguous(
259 &candidates,
260 params.attach_ambiguity_factor,
261 prediction,
262 positions,
263 validator,
264 pos,
265 &neighbours,
266 );
267 match choice {
268 CandidateChoice::None => {
269 holes.insert(pos);
270 }
271 CandidateChoice::Ambiguous => {
272 ambiguous.insert(pos);
273 }
274 CandidateChoice::Unique(c_idx) => {
275 if !any_cardinal_edge_ok(c_idx, pos, &labelled, validator) {
276 holes.insert(pos);
277 continue;
278 }
279 labelled.insert(pos, c_idx);
280 by_corner.insert(c_idx, pos);
281 enqueue_cardinal_neighbours(pos, &labelled, &mut boundary, &mut seen_boundary);
282 }
283 }
284 }
285
286 let (min_i, min_j) = labelled
288 .keys()
289 .fold((i32::MAX, i32::MAX), |(a, b), &(i, j)| (a.min(i), b.min(j)));
290 if min_i != 0 || min_j != 0 {
291 let rebased: HashMap<(i32, i32), usize> = labelled
292 .into_iter()
293 .map(|((i, j), idx)| ((i - min_i, j - min_j), idx))
294 .collect();
295 let rebased_by_corner: HashMap<usize, (i32, i32)> =
296 rebased.iter().map(|(&ij, &idx)| (idx, ij)).collect();
297 labelled = rebased;
298 by_corner = rebased_by_corner;
299 }
300 let rebase_pos = |(i, j)| (i - min_i, j - min_j);
301 let ambiguous: HashSet<(i32, i32)> = ambiguous.into_iter().map(rebase_pos).collect();
302 let holes: HashSet<(i32, i32)> = holes.into_iter().map(rebase_pos).collect();
303
304 GrowResult {
305 labelled,
306 by_corner,
307 ambiguous,
308 holes,
309 grid_u,
310 grid_v,
311 }
312}
313
314fn enqueue_cardinal_neighbours(
315 pos: (i32, i32),
316 labelled: &HashMap<(i32, i32), usize>,
317 boundary: &mut VecDeque<(i32, i32)>,
318 seen: &mut HashSet<(i32, i32)>,
319) {
320 for (di, dj) in [(1, 0), (-1, 0), (0, 1), (0, -1)] {
321 let neigh = (pos.0 + di, pos.1 + dj);
322 if !labelled.contains_key(&neigh) && seen.insert(neigh) {
323 boundary.push_back(neigh);
324 }
325 }
326}
327
328fn collect_labelled_neighbours(
329 pos: (i32, i32),
330 window_half: i32,
331 labelled: &HashMap<(i32, i32), usize>,
332 positions: &[Point2<f32>],
333) -> Vec<LabelledNeighbour> {
334 let mut out = Vec::new();
335 for dj in -window_half..=window_half {
336 for di in -window_half..=window_half {
337 if di == 0 && dj == 0 {
338 continue;
339 }
340 let at = (pos.0 + di, pos.1 + dj);
341 if let Some(&idx) = labelled.get(&at) {
342 out.push(LabelledNeighbour {
343 idx,
344 at,
345 position: positions[idx],
346 });
347 }
348 }
349 }
350 out
351}
352
353pub fn predict_from_neighbours(
356 target: (i32, i32),
357 neighbours: &[LabelledNeighbour],
358 u: Vector2<f32>,
359 v: Vector2<f32>,
360 cell_size: f32,
361) -> Point2<f32> {
362 debug_assert!(!neighbours.is_empty());
363 let mut sum_x = 0.0_f32;
364 let mut sum_y = 0.0_f32;
365 for n in neighbours {
366 let di = (target.0 - n.at.0) as f32;
367 let dj = (target.1 - n.at.1) as f32;
368 let off = u * (di * cell_size) + v * (dj * cell_size);
369 sum_x += n.position.x + off.x;
370 sum_y += n.position.y + off.y;
371 }
372 let denom = neighbours.len() as f32;
373 Point2::new(sum_x / denom, sum_y / denom)
374}
375
376fn collect_candidates<V: GrowValidator>(
377 tree: &KdTree<f32, 2>,
378 slot_to_corner: &[usize],
379 prediction: Point2<f32>,
380 search_r: f32,
381 validator: &V,
382 required_label: Option<u8>,
383 by_corner: &HashMap<usize, (i32, i32)>,
384) -> Vec<(usize, f32)> {
385 let r2 = search_r * search_r;
386 let mut out: Vec<(usize, f32)> = Vec::new();
387 for nn in tree
388 .within_unsorted::<SquaredEuclidean>(&[prediction.x, prediction.y], r2)
389 .into_iter()
390 {
391 let idx = slot_to_corner[nn.item as usize];
392 if by_corner.contains_key(&idx) {
393 continue;
394 }
395 if let Some(req) = required_label {
396 let Some(got) = validator.label_of(idx) else {
397 continue;
398 };
399 if got != req {
400 continue;
401 }
402 }
403 let d = nn.distance.sqrt();
404 out.push((idx, d));
405 }
406 out.sort_by(|a, b| a.1.total_cmp(&b.1));
407 out
408}
409
410enum CandidateChoice {
411 None,
412 Ambiguous,
413 Unique(usize),
414}
415
416fn choose_unambiguous<V: GrowValidator>(
417 candidates: &[(usize, f32)],
418 ambiguity_factor: f32,
419 prediction: Point2<f32>,
420 positions: &[Point2<f32>],
421 validator: &V,
422 at: (i32, i32),
423 neighbours: &[LabelledNeighbour],
424) -> CandidateChoice {
425 if candidates.is_empty() {
429 return CandidateChoice::None;
430 }
431 if candidates.len() >= 2 {
432 let (_, d0) = candidates[0];
433 let (_, d1) = candidates[1];
434 if d0 <= f32::EPSILON {
435 return CandidateChoice::Ambiguous;
436 }
437 if d1 / d0 < ambiguity_factor {
438 return CandidateChoice::Ambiguous;
439 }
440 }
441 for &(idx, _dist) in candidates {
442 let pos = positions[idx];
443 let _ = pos; match validator.accept_candidate(idx, at, prediction, neighbours) {
445 Admit::Accept => return CandidateChoice::Unique(idx),
446 Admit::Reject => continue,
447 }
448 }
449 CandidateChoice::None
450}
451
452fn any_cardinal_edge_ok<V: GrowValidator>(
453 c_idx: usize,
454 pos: (i32, i32),
455 labelled: &HashMap<(i32, i32), usize>,
456 validator: &V,
457) -> bool {
458 let mut found_any = false;
459 for (di, dj) in [(1, 0), (-1, 0), (0, 1), (0, -1)] {
460 let neigh = (pos.0 + di, pos.1 + dj);
461 if let Some(&n_idx) = labelled.get(&neigh) {
462 found_any = true;
463 if validator.edge_ok(c_idx, n_idx, pos, neigh) {
464 return true;
465 }
466 }
467 }
468 !found_any
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 struct OpenValidator;
480
481 impl GrowValidator for OpenValidator {
482 fn is_eligible(&self, _idx: usize) -> bool {
483 true
484 }
485 fn required_label_at(&self, _i: i32, _j: i32) -> Option<u8> {
486 None
487 }
488 fn label_of(&self, _idx: usize) -> Option<u8> {
489 None
490 }
491 fn accept_candidate(
492 &self,
493 _idx: usize,
494 _at: (i32, i32),
495 _prediction: Point2<f32>,
496 _neighbours: &[LabelledNeighbour],
497 ) -> Admit {
498 Admit::Accept
499 }
500 }
501
502 #[test]
503 fn open_validator_grows_clean_grid() {
504 let s = 20.0_f32;
505 let rows = 6_i32;
506 let cols = 6_i32;
507 let mut positions = Vec::new();
508 let mut seed_idx = [0usize; 4];
509 for j in 0..rows {
510 for i in 0..cols {
511 let x = i as f32 * s + 50.0;
512 let y = j as f32 * s + 50.0;
513 let k = positions.len();
514 positions.push(Point2::new(x, y));
515 if (i, j) == (0, 0) {
516 seed_idx[0] = k;
517 }
518 if (i, j) == (1, 0) {
519 seed_idx[1] = k;
520 }
521 if (i, j) == (0, 1) {
522 seed_idx[2] = k;
523 }
524 if (i, j) == (1, 1) {
525 seed_idx[3] = k;
526 }
527 }
528 }
529
530 let seed = Seed {
531 a: seed_idx[0],
532 b: seed_idx[1],
533 c: seed_idx[2],
534 d: seed_idx[3],
535 };
536 let res = bfs_grow(&positions, seed, s, &GrowParams::default(), &OpenValidator);
537 assert_eq!(res.labelled.len(), (rows * cols) as usize);
538 let (mi, mj) = res
540 .labelled
541 .keys()
542 .fold((i32::MAX, i32::MAX), |(a, b), &(i, j)| (a.min(i), b.min(j)));
543 assert_eq!((mi, mj), (0, 0));
544 }
545}