1use crate::graph::GridGraph;
23use crate::Float;
24use crate::NeighborDirection;
25use nalgebra::Point2;
26
27pub fn enforce_symmetry<F: Float>(graph: &mut GridGraph<F>) -> usize {
33 let n = graph.neighbors.len();
34 let present: Vec<Vec<usize>> = graph
36 .neighbors
37 .iter()
38 .map(|ns| ns.iter().map(|n| n.index).collect())
39 .collect();
40
41 let has_reverse = |a: usize, b: usize| -> bool { present[b].contains(&a) };
42
43 let mut removed = 0usize;
44 for a in 0..n {
45 let before = graph.neighbors[a].len();
46 graph.neighbors[a].retain(|n| has_reverse(a, n.index));
47 removed += before - graph.neighbors[a].len();
48 }
49 removed
50}
51
52pub fn prune_by_edge_straightness<F: Float>(
66 graph: &mut GridGraph<F>,
67 positions: &[Point2<F>],
68 max_deviation_deg: F,
69) -> usize {
70 assert_eq!(
71 positions.len(),
72 graph.neighbors.len(),
73 "positions and graph node count must match"
74 );
75
76 let deg_to_rad: F = F::pi() / F::from_subset(&180.0);
79 let max_dev_rad: F = max_deviation_deg * deg_to_rad;
80 let min_cos_antiparallel: F = -(max_dev_rad.cos());
81
82 let mut removed = 0usize;
83 let mut to_drop: Vec<(usize, usize)> = Vec::new();
84
85 for node in 0..graph.neighbors.len() {
86 let pos_c = positions[node];
87 let ns = &graph.neighbors[node];
88 for pair in [
90 (NeighborDirection::Right, NeighborDirection::Left),
91 (NeighborDirection::Up, NeighborDirection::Down),
92 ] {
93 let a = ns.iter().find(|n| n.direction == pair.0);
94 let b = ns.iter().find(|n| n.direction == pair.1);
95 let (Some(a), Some(b)) = (a, b) else { continue };
96 let pa = positions[a.index];
97 let pb = positions[b.index];
98 let va = pa - pos_c;
99 let vb = pb - pos_c;
100 let na = va.norm();
101 let nb = vb.norm();
102 if na <= F::default_epsilon() || nb <= F::default_epsilon() {
103 continue;
104 }
105 let cos_angle = va.dot(&vb) / (na * nb);
106 if cos_angle > min_cos_antiparallel {
108 let drop_idx =
110 if a.score > b.score || (a.score == b.score && a.distance > b.distance) {
111 a.index
112 } else {
113 b.index
114 };
115 to_drop.push((node, drop_idx));
116 }
117 }
118 }
119
120 for (src, dst) in to_drop {
121 let before = graph.neighbors[src].len();
122 graph.neighbors[src].retain(|n| n.index != dst);
123 removed += before - graph.neighbors[src].len();
124 }
125 removed
126}
127
128pub fn prune_crossing_edges<F: Float>(graph: &mut GridGraph<F>, positions: &[Point2<F>]) -> usize {
142 assert_eq!(
143 positions.len(),
144 graph.neighbors.len(),
145 "positions and graph node count must match"
146 );
147
148 #[derive(Clone)]
151 struct UndirectedEdge<F: Float> {
152 a: usize,
153 b: usize,
154 score: F,
155 }
156
157 let mut seen: std::collections::HashSet<(usize, usize)> = std::collections::HashSet::new();
158 let mut edges: Vec<UndirectedEdge<F>> = Vec::new();
159 for (src, ns) in graph.neighbors.iter().enumerate() {
160 for n in ns {
161 let (a, b) = if src < n.index {
162 (src, n.index)
163 } else {
164 (n.index, src)
165 };
166 if seen.insert((a, b)) {
167 let rev_score = graph.neighbors[n.index]
169 .iter()
170 .find(|rn| rn.index == src)
171 .map(|rn| rn.score);
172 let combined_score = rev_score.map(|s| s + n.score).unwrap_or(n.score);
173 edges.push(UndirectedEdge {
174 a,
175 b,
176 score: combined_score,
177 });
178 }
179 }
180 }
181
182 let mut drop_set: std::collections::HashSet<(usize, usize)> = std::collections::HashSet::new();
184 for i in 0..edges.len() {
185 if drop_set.contains(&(edges[i].a, edges[i].b)) {
186 continue;
187 }
188 for j in (i + 1)..edges.len() {
189 if drop_set.contains(&(edges[j].a, edges[j].b)) {
190 continue;
191 }
192 let (p1, p2) = (positions[edges[i].a], positions[edges[i].b]);
193 let (p3, p4) = (positions[edges[j].a], positions[edges[j].b]);
194 if segments_properly_cross(p1, p2, p3, p4) {
195 let drop_idx = if edges[i].score >= edges[j].score {
197 i
198 } else {
199 j
200 };
201 drop_set.insert((edges[drop_idx].a, edges[drop_idx].b));
202 }
203 }
204 }
205
206 let mut removed = 0usize;
207 for (a, b) in drop_set {
208 let before_a = graph.neighbors[a].len();
209 graph.neighbors[a].retain(|n| n.index != b);
210 removed += before_a - graph.neighbors[a].len();
211 let before_b = graph.neighbors[b].len();
212 graph.neighbors[b].retain(|n| n.index != a);
213 removed += before_b - graph.neighbors[b].len();
214 }
215 removed
216}
217
218pub fn prune_isolated_pairs<F: Float>(graph: &mut GridGraph<F>, min_node_degree: usize) -> usize {
232 let degrees: Vec<usize> = graph.neighbors.iter().map(|ns| ns.len()).collect();
233 let mut removed = 0usize;
234 let mut to_drop: Vec<(usize, usize)> = Vec::new();
235 for (a, ns) in graph.neighbors.iter().enumerate() {
236 if degrees[a] > min_node_degree {
237 continue;
238 }
239 for n in ns {
240 if degrees[n.index] <= min_node_degree {
241 to_drop.push((a, n.index));
242 }
243 }
244 }
245 for (a, b) in to_drop {
246 let before = graph.neighbors[a].len();
247 graph.neighbors[a].retain(|n| n.index != b);
248 removed += before - graph.neighbors[a].len();
249 }
250 removed
251}
252
253fn cross2<F: Float>(a: nalgebra::Vector2<F>, b: nalgebra::Vector2<F>) -> F {
256 a.x * b.y - a.y * b.x
257}
258
259pub fn segments_properly_cross<F: Float>(
264 p1: Point2<F>,
265 p2: Point2<F>,
266 p3: Point2<F>,
267 p4: Point2<F>,
268) -> bool {
269 let eps = F::from_subset(&1e-6);
270 let same = |a: Point2<F>, b: Point2<F>| -> bool {
271 (a.x - b.x).abs() <= eps && (a.y - b.y).abs() <= eps
272 };
273 if same(p1, p3) || same(p1, p4) || same(p2, p3) || same(p2, p4) {
274 return false;
275 }
276 let d12 = p2 - p1;
277 let d34 = p4 - p3;
278
279 let d1 = cross2::<F>(d34, p1 - p3);
280 let d2 = cross2::<F>(d34, p2 - p3);
281 let d3 = cross2::<F>(d12, p3 - p1);
282 let d4 = cross2::<F>(d12, p4 - p1);
283
284 let zero: F = F::zero();
285 ((d1 > zero) != (d2 > zero)) && ((d3 > zero) != (d4 > zero)) && d1 != zero && d2 != zero
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::NodeNeighbor;
292
293 fn make_node<F: Float>(
294 direction: NeighborDirection,
295 index: usize,
296 distance: F,
297 score: F,
298 ) -> NodeNeighbor<F> {
299 NodeNeighbor {
300 direction,
301 index,
302 distance,
303 score,
304 }
305 }
306
307 #[test]
308 fn symmetry_drops_one_sided_edges() {
309 let _positions = [
312 Point2::new(10.0f32, 0.0),
313 Point2::new(20.0, 0.0),
314 Point2::new(0.0, 0.0),
315 ];
316 let mut graph = GridGraph::<f32> {
317 neighbors: vec![
318 vec![
319 make_node(NeighborDirection::Right, 1, 10.0, 0.1),
320 make_node(NeighborDirection::Left, 2, 10.0, 0.2),
321 ],
322 vec![make_node(NeighborDirection::Left, 0, 10.0, 0.1)],
323 vec![], ],
325 };
326 let removed = enforce_symmetry(&mut graph);
327 assert_eq!(removed, 1);
328 assert_eq!(graph.neighbors[0].len(), 1);
329 }
330
331 #[test]
332 fn planarity_drops_the_crossing_edge() {
333 let positions = vec![
335 Point2::new(0.0f32, 0.0), Point2::new(1.0, 0.0), Point2::new(1.0, 1.0), Point2::new(0.0, 1.0), ];
340 let mut graph = GridGraph::<f32> {
343 neighbors: vec![
344 vec![
345 make_node(NeighborDirection::Right, 1, 1.0, 0.1),
346 make_node(NeighborDirection::Down, 2, 1.41, 1.0),
347 ],
348 vec![
349 make_node(NeighborDirection::Left, 0, 1.0, 0.1),
350 make_node(NeighborDirection::Down, 3, 1.41, 0.5),
351 ],
352 vec![make_node(NeighborDirection::Up, 0, 1.41, 1.0)],
353 vec![make_node(NeighborDirection::Up, 1, 1.41, 0.5)],
354 ],
355 };
356 let removed = prune_crossing_edges(&mut graph, &positions);
357 assert!(removed >= 2, "expected at least 2 directed edges removed");
359 assert!(!graph.neighbors[0].iter().any(|n| n.index == 2));
360 assert!(!graph.neighbors[2].iter().any(|n| n.index == 0));
361 assert!(graph.neighbors[1].iter().any(|n| n.index == 3));
363 assert!(graph.neighbors[3].iter().any(|n| n.index == 1));
364 }
365
366 #[test]
367 fn straightness_drops_bent_pair() {
368 let positions = vec![
372 Point2::new(0.0f32, 0.0), Point2::new(10.0, 0.0), Point2::new(-3.0, 10.0), ];
376 let mut graph = GridGraph::<f32> {
377 neighbors: vec![
378 vec![
379 make_node(NeighborDirection::Right, 1, 10.0, 0.1),
380 make_node(NeighborDirection::Left, 2, 10.44, 0.9), ],
382 vec![make_node(NeighborDirection::Left, 0, 10.0, 0.1)],
383 vec![make_node(NeighborDirection::Right, 0, 10.44, 0.9)],
384 ],
385 };
386 let removed = prune_by_edge_straightness(&mut graph, &positions, 15.0);
387 assert_eq!(removed, 1);
388 assert!(!graph.neighbors[0].iter().any(|n| n.index == 2));
390 assert!(graph.neighbors[2].iter().any(|n| n.index == 0));
392 }
393
394 #[test]
395 fn straightness_keeps_colinear_pair() {
396 let positions = vec![
397 Point2::new(0.0f32, 0.0),
398 Point2::new(10.0, 0.0),
399 Point2::new(-10.0, 0.0),
400 ];
401 let mut graph = GridGraph::<f32> {
402 neighbors: vec![
403 vec![
404 make_node(NeighborDirection::Right, 1, 10.0, 0.1),
405 make_node(NeighborDirection::Left, 2, 10.0, 0.1),
406 ],
407 vec![make_node(NeighborDirection::Left, 0, 10.0, 0.1)],
408 vec![make_node(NeighborDirection::Right, 0, 10.0, 0.1)],
409 ],
410 };
411 let removed = prune_by_edge_straightness(&mut graph, &positions, 15.0);
412 assert_eq!(removed, 0);
413 assert_eq!(graph.neighbors[0].len(), 2);
414 }
415
416 #[test]
417 fn segments_proper_cross_test() {
418 assert!(segments_properly_cross(
420 Point2::new(0.0f32, 0.0),
421 Point2::new(1.0, 1.0),
422 Point2::new(0.0, 1.0),
423 Point2::new(1.0, 0.0),
424 ));
425 assert!(!segments_properly_cross(
427 Point2::new(0.0f32, 0.0),
428 Point2::new(1.0, 0.0),
429 Point2::new(0.0, 0.0),
430 Point2::new(0.0, 1.0),
431 ));
432 assert!(!segments_properly_cross(
434 Point2::new(0.0f32, 0.0),
435 Point2::new(1.0, 0.0),
436 Point2::new(0.0, 1.0),
437 Point2::new(1.0, 1.0),
438 ));
439 }
440
441 #[test]
442 fn isolated_pair_prune_drops_dangling_pair() {
443 let mut graph = GridGraph::<f32> {
445 neighbors: vec![
446 vec![make_node(NeighborDirection::Right, 1, 10.0, 0.1)],
447 vec![make_node(NeighborDirection::Left, 0, 10.0, 0.1)],
448 vec![make_node(NeighborDirection::Right, 3, 10.0, 0.1)],
449 vec![make_node(NeighborDirection::Left, 2, 10.0, 0.1)],
450 ],
451 };
452 let removed = prune_isolated_pairs(&mut graph, 1);
455 assert_eq!(removed, 4);
456 assert!(graph.neighbors.iter().all(|ns| ns.is_empty()));
457 }
458}