squid_n_core/region_gen/
mod.rs1use crate::ids::{ElemId, NodeId};
27use std::collections::HashMap;
28
29pub mod floor;
30pub mod wall;
31
32pub use floor::{
33 crossing_beams, generate_region_boundaries, scan_region_boundaries, RegionBoundary,
34 RegionBoundaryScan,
35};
36pub use wall::{
37 generate_wall_region_boundaries, scan_wall_region_boundaries, WallRegionBoundary,
38 WallRegionBoundaryScan,
39};
40
41pub(crate) struct Edge {
43 pub a: NodeId,
44 pub b: NodeId,
45 pub elem: ElemId,
46}
47
48pub(crate) struct Face {
50 pub boundary: Vec<NodeId>,
51 pub edges: Vec<ElemId>,
52 pub signed_area: f64,
55}
56
57pub(crate) fn scan_faces<P>(edges: &[Edge], proj: P) -> (Vec<Face>, usize)
63where
64 P: Fn(NodeId) -> Option<[f64; 2]>,
65{
66 let mut half: HashMap<(NodeId, NodeId), ElemId> = HashMap::new();
69 for e in edges {
70 if e.a == e.b {
71 continue;
72 }
73 half.entry((e.a, e.b)).or_insert(e.elem);
74 half.entry((e.b, e.a)).or_insert(e.elem);
75 }
76
77 let mut around: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
79 for &(from, to) in half.keys() {
80 around.entry(from).or_default().push(to);
81 }
82 for (from, list) in around.iter_mut() {
83 let Some(origin) = proj(*from) else {
84 continue;
85 };
86 list.sort_by(|x, y| angle_at(&proj, origin, *x).total_cmp(&angle_at(&proj, origin, *y)));
87 list.dedup();
88 }
89
90 let mut visited: HashMap<(NodeId, NodeId), bool> = HashMap::new();
91 let mut faces = Vec::new();
92 let mut unclosed = 0;
93 let mut starts: Vec<(NodeId, NodeId)> = half.keys().copied().collect();
94 starts.sort_by_key(|(a, b)| (a.0, b.0));
96
97 for start in starts {
98 if visited.contains_key(&start) {
99 continue;
100 }
101 let mut boundary = Vec::new();
102 let mut face_edges = Vec::new();
103 let mut cur = start;
104 let mut closed = false;
105 for _ in 0..=half.len() {
107 if visited.insert(cur, true).is_some() {
108 break;
109 }
110 boundary.push(cur.0);
111 face_edges.push(half[&cur]);
112 let Some(next) = next_half_edge(&around, cur) else {
113 break;
114 };
115 cur = next;
116 if cur == start {
117 closed = true;
118 break;
119 }
120 }
121 if !closed {
122 unclosed += 1;
124 continue;
125 }
126 if boundary.len() < 3 {
127 continue;
128 }
129 let pts: Vec<[f64; 2]> = boundary.iter().filter_map(|&n| proj(n)).collect();
130 if pts.len() != boundary.len() {
131 continue;
132 }
133 faces.push(Face {
134 signed_area: crate::geom::polygon::signed_area(&pts),
135 boundary,
136 edges: face_edges,
137 });
138 }
139
140 (faces, unclosed)
141}
142
143fn angle_at<P>(proj: &P, origin: [f64; 2], to: NodeId) -> f64
145where
146 P: Fn(NodeId) -> Option<[f64; 2]>,
147{
148 match proj(to) {
149 Some(c) => (c[1] - origin[1]).atan2(c[0] - origin[0]),
150 None => f64::MAX,
151 }
152}
153
154fn next_half_edge(
157 around: &HashMap<NodeId, Vec<NodeId>>,
158 (u, v): (NodeId, NodeId),
159) -> Option<(NodeId, NodeId)> {
160 let list = around.get(&v)?;
161 let pos = list.iter().position(|&w| w == u)?;
162 let prev = if pos == 0 { list.len() - 1 } else { pos - 1 };
163 Some((v, list[prev]))
164}