1use crate::graph::LayoutGraph;
8use crate::types::*;
9use std::collections::{BTreeMap, BTreeSet};
10
11const OBSTACLE_RADIUS: f32 = 25.0;
14
15pub fn route_wires(layout: &mut Layout, graph: &LayoutGraph) {
17 let mut wires = Vec::new();
18
19 let comp_positions: BTreeMap<&str, (f32, f32)> = layout
21 .components
22 .iter()
23 .map(|c| (c.name.as_str(), (c.x, c.y)))
24 .collect();
25
26 let comp_indices: BTreeMap<&str, usize> = layout
28 .components
29 .iter()
30 .enumerate()
31 .map(|(i, c)| (c.name.as_str(), i))
32 .collect();
33
34 let obstacles: Vec<(f32, f32)> = layout.components.iter().map(|c| (c.x, c.y)).collect();
36
37 let feedback_comps: BTreeSet<(usize, usize)> = graph
39 .edges
40 .iter()
41 .filter(|e| e.is_feedback)
42 .map(|e| (e.from, e.to))
43 .collect();
44
45 let gnd_rail_y = layout
47 .supply_rails
48 .iter()
49 .find(|r| r.name == "gnd")
50 .map(|r| r.y)
51 .unwrap_or(layout.bounds.height);
52 let feedback_y = gnd_rail_y - 20.0;
53
54 for ng in &graph.net_groups {
56 let net_name = ng
57 .name
58 .clone()
59 .unwrap_or_else(|| format!("net_{}", ng.index));
60
61 let is_supply_net = ng.name.as_deref() == Some("vcc") || ng.name.as_deref() == Some("gnd");
62
63 let is_feedback_net = ng.component_ids.iter().any(|cid_a| {
65 let node_a = graph.id_to_node.get(cid_a).copied();
66 ng.component_ids.iter().any(|cid_b| {
67 let node_b = graph.id_to_node.get(cid_b).copied();
68 if let (Some(a), Some(b)) = (node_a, node_b) {
69 feedback_comps.contains(&(a, b)) || feedback_comps.contains(&(b, a))
70 } else {
71 false
72 }
73 })
74 });
75
76 let mut positions: Vec<(String, f32, f32)> = Vec::new();
78 for cid in &ng.component_ids {
79 let lookup_name = match cid.as_str() {
81 "__in" | "__out" | "__vcc" | "__gnd" => continue, name => name,
83 };
84 if let Some(&(x, y)) = comp_positions.get(lookup_name) {
85 positions.push((lookup_name.to_string(), x, y));
86 }
87 }
88
89 if positions.len() < 2 {
90 if is_supply_net && positions.len() == 1 {
92 let (name, x, y) = &positions[0];
93 let rail_y = if ng.name.as_deref() == Some("vcc") {
94 layout
95 .supply_rails
96 .iter()
97 .find(|r| r.name == "vcc")
98 .map(|r| r.y)
99 .unwrap_or(0.0)
100 } else {
101 layout
102 .supply_rails
103 .iter()
104 .find(|r| r.name == "gnd")
105 .map(|r| r.y)
106 .unwrap_or(layout.bounds.height)
107 };
108
109 wires.push(Wire {
110 net: net_name,
111 points: vec![[*x, *y], [*x, rail_y]],
112 signal_path: false,
113 monitor_index_start: comp_indices
114 .get(name.as_str())
115 .and_then(|&i| layout.components[i].monitor_index),
116 monitor_index_end: None,
117 });
118 }
119 continue;
120 }
121
122 positions.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
124
125 for window in positions.windows(2) {
127 let (name_a, x_a, y_a) = &window[0];
128 let (name_b, x_b, y_b) = &window[1];
129
130 let start = Point::new(*x_a, *y_a);
131 let end = Point::new(*x_b, *y_b);
132
133 let points = if is_feedback_net {
134 route_wire_feedback(start, end, feedback_y)
136 } else {
137 route_wire_with_obstacles(start, end, &obstacles)
138 };
139
140 let is_signal = !is_supply_net
141 && ng.name.as_deref() != Some("vcc")
142 && ng.name.as_deref() != Some("gnd");
143
144 let monitor_start = comp_indices
145 .get(name_a.as_str())
146 .and_then(|&i| layout.components[i].monitor_index);
147 let monitor_end = comp_indices
148 .get(name_b.as_str())
149 .and_then(|&i| layout.components[i].monitor_index);
150
151 wires.push(Wire {
152 net: net_name.clone(),
153 points: points.into_iter().map(|p| [p.x, p.y]).collect(),
154 signal_path: is_signal,
155 monitor_index_start: monitor_start,
156 monitor_index_end: monitor_end,
157 });
158 }
159 }
160
161 layout.wires = wires;
162}
163
164fn route_wire(start: Point, end: Point) -> Vec<Point> {
170 const EPSILON: f32 = 0.5;
171
172 if (start.y - end.y).abs() < EPSILON {
173 vec![start, end]
175 } else if (start.x - end.x).abs() < EPSILON {
176 vec![start, end]
178 } else {
179 let mid = Point::new(end.x, start.y);
181 vec![start, mid, end]
182 }
183}
184
185fn route_wire_with_obstacles(start: Point, end: Point, obstacles: &[(f32, f32)]) -> Vec<Point> {
188 let basic = route_wire(start, end);
189
190 if basic.len() != 3 {
192 return basic;
193 }
194
195 let corner = &basic[1];
196 let corner_blocked = obstacles.iter().any(|&(ox, oy)| {
197 let is_endpoint = ((ox - start.x).abs() < 1.0 && (oy - start.y).abs() < 1.0)
199 || ((ox - end.x).abs() < 1.0 && (oy - end.y).abs() < 1.0);
200 if is_endpoint {
201 return false;
202 }
203 let dx = corner.x - ox;
204 let dy = corner.y - oy;
205 (dx * dx + dy * dy).sqrt() < OBSTACLE_RADIUS
206 });
207
208 if corner_blocked {
209 let mid_x = (start.x + end.x) / 2.0;
211 route_wire_z(start, end, mid_x)
212 } else {
213 basic
214 }
215}
216
217fn route_wire_z(start: Point, end: Point, jog_x: f32) -> Vec<Point> {
220 vec![
221 start,
222 Point::new(jog_x, start.y),
223 Point::new(jog_x, end.y),
224 end,
225 ]
226}
227
228fn route_wire_feedback(start: Point, end: Point, bottom_y: f32) -> Vec<Point> {
230 vec![
231 start,
232 Point::new(start.x, bottom_y),
233 Point::new(end.x, bottom_y),
234 end,
235 ]
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn straight_horizontal() {
244 let pts = route_wire(Point::new(0.0, 100.0), Point::new(200.0, 100.0));
245 assert_eq!(pts.len(), 2);
246 }
247
248 #[test]
249 fn straight_vertical() {
250 let pts = route_wire(Point::new(100.0, 0.0), Point::new(100.0, 200.0));
251 assert_eq!(pts.len(), 2);
252 }
253
254 #[test]
255 fn l_route() {
256 let pts = route_wire(Point::new(0.0, 0.0), Point::new(200.0, 100.0));
257 assert_eq!(pts.len(), 3);
258 assert_eq!(pts[1].x, 200.0);
259 assert_eq!(pts[1].y, 0.0);
260 }
261}