1use crate::types::*;
8
9const GRID_SIZE: f32 = 10.0;
11
12const MIN_CLEARANCE: f32 = 30.0;
14
15const COMPONENT_HALF_SIZE: f32 = 15.0;
17
18pub fn optimize_layout(layout: &mut Layout) {
20 snap_to_grid(layout);
21 resolve_overlaps(layout);
22 minimize_crossings(layout);
23 resolve_wire_component_overlaps(layout);
24 center_groups(layout);
25}
26
27fn snap_to_grid(layout: &mut Layout) {
29 for comp in &mut layout.components {
30 comp.x = (comp.x / GRID_SIZE).round() * GRID_SIZE;
31 comp.y = (comp.y / GRID_SIZE).round() * GRID_SIZE;
32 }
33}
34
35fn resolve_overlaps(layout: &mut Layout) {
37 for _ in 0..10 {
40 let mut any_overlap = false;
41
42 for i in 0..layout.components.len() {
43 for j in (i + 1)..layout.components.len() {
44 let dx = layout.components[j].x - layout.components[i].x;
45 let dy = layout.components[j].y - layout.components[i].y;
46
47 let overlap_x = MIN_CLEARANCE - dx.abs();
48 let overlap_y = MIN_CLEARANCE - dy.abs();
49
50 if overlap_x > 0.0 && overlap_y > 0.0 {
51 any_overlap = true;
52
53 if overlap_x < overlap_y {
54 let push = overlap_x / 2.0 + 1.0;
56 if dx >= 0.0 {
57 layout.components[i].x -= push;
58 layout.components[j].x += push;
59 } else {
60 layout.components[i].x += push;
61 layout.components[j].x -= push;
62 }
63 } else {
64 let push = overlap_y / 2.0 + 1.0;
66 if dy >= 0.0 {
67 layout.components[i].y -= push;
68 layout.components[j].y += push;
69 } else {
70 layout.components[i].y += push;
71 layout.components[j].y -= push;
72 }
73 }
74 }
75 }
76 }
77
78 if !any_overlap {
79 break;
80 }
81 }
82
83 snap_to_grid(layout);
85}
86
87fn minimize_crossings(layout: &mut Layout) {
93 let mut group_members: std::collections::BTreeMap<String, Vec<usize>> =
95 std::collections::BTreeMap::new();
96 for (i, comp) in layout.components.iter().enumerate() {
97 group_members.entry(comp.group.clone()).or_default().push(i);
98 }
99
100 for (_group_name, members) in &group_members {
103 if members.len() <= 1 {
104 continue;
105 }
106
107 let mut barycenters: Vec<(usize, f32)> = members
109 .iter()
110 .map(|&idx| {
111 let comp_name = &layout.components[idx].name;
112 let connected_y: Vec<f32> = layout
113 .wires
114 .iter()
115 .filter(|w| {
116 w.points
117 .first()
118 .map(|p| {
119 (p[0] - layout.components[idx].x).abs() < COMPONENT_HALF_SIZE
120 && (p[1] - layout.components[idx].y).abs() < COMPONENT_HALF_SIZE
121 })
122 .unwrap_or(false)
123 || w.points
124 .last()
125 .map(|p| {
126 (p[0] - layout.components[idx].x).abs() < COMPONENT_HALF_SIZE
127 && (p[1] - layout.components[idx].y).abs()
128 < COMPONENT_HALF_SIZE
129 })
130 .unwrap_or(false)
131 })
132 .flat_map(|w| w.points.iter().map(|p| p[1]))
133 .collect();
134
135 let bc = if connected_y.is_empty() {
136 layout.components[idx].y
137 } else {
138 connected_y.iter().sum::<f32>() / connected_y.len() as f32
139 };
140
141 let _ = comp_name; (idx, bc)
143 })
144 .collect();
145
146 barycenters.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
147
148 let mut y_positions: Vec<f32> = barycenters
150 .iter()
151 .map(|&(idx, _)| layout.components[idx].y)
152 .collect();
153 y_positions.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
154
155 for (rank, &(idx, _)) in barycenters.iter().enumerate() {
156 if rank < y_positions.len() {
157 layout.components[idx].y = y_positions[rank];
158 }
159 }
160 }
161}
162
163fn resolve_wire_component_overlaps(layout: &mut Layout) {
166 let boxes: Vec<Rect> = layout
168 .components
169 .iter()
170 .map(|c| Rect {
171 x: c.x - COMPONENT_HALF_SIZE,
172 y: c.y - COMPONENT_HALF_SIZE,
173 w: COMPONENT_HALF_SIZE * 2.0,
174 h: COMPONENT_HALF_SIZE * 2.0,
175 })
176 .collect();
177
178 let centers: Vec<(f32, f32)> = layout.components.iter().map(|c| (c.x, c.y)).collect();
180
181 for wire in &mut layout.wires {
182 let mut new_points: Vec<[f32; 2]> = Vec::new();
183
184 for seg in wire.points.windows(2) {
185 new_points.push(seg[0]);
186
187 let p0 = Point::new(seg[0][0], seg[0][1]);
188 let p1 = Point::new(seg[1][0], seg[1][1]);
189
190 for (ci, bbox) in boxes.iter().enumerate() {
192 let (cx, cy) = centers[ci];
194 let is_endpoint = ((p0.x - cx).abs() < 1.0 && (p0.y - cy).abs() < 1.0)
195 || ((p1.x - cx).abs() < 1.0 && (p1.y - cy).abs() < 1.0);
196 if is_endpoint {
197 continue;
198 }
199
200 if !segment_intersects_rect(p0, p1, bbox) {
202 continue;
203 }
204
205 let jog_offset = COMPONENT_HALF_SIZE + 5.0;
207 let is_horizontal = (p0.y - p1.y).abs() < 0.5;
208
209 if is_horizontal {
210 let jog_y = if p0.y < cy {
212 bbox.y - 5.0
213 } else {
214 bbox.y + bbox.h + 5.0
215 };
216 new_points.push([cx - jog_offset, p0.y]);
217 new_points.push([cx - jog_offset, jog_y]);
218 new_points.push([cx + jog_offset, jog_y]);
219 new_points.push([cx + jog_offset, p1.y]);
220 } else {
221 let jog_x = if p0.x < cx {
223 bbox.x - 5.0
224 } else {
225 bbox.x + bbox.w + 5.0
226 };
227 new_points.push([p0.x, cy - jog_offset]);
228 new_points.push([jog_x, cy - jog_offset]);
229 new_points.push([jog_x, cy + jog_offset]);
230 new_points.push([p1.x, cy + jog_offset]);
231 }
232 break; }
234 }
235
236 if let Some(&last) = wire.points.last() {
238 new_points.push(last);
239 }
240
241 new_points.dedup();
243 wire.points = new_points;
244 }
245}
246
247fn segment_intersects_rect(p0: Point, p1: Point, rect: &Rect) -> bool {
249 let is_horizontal = (p0.y - p1.y).abs() < 0.5;
250 let is_vertical = (p0.x - p1.x).abs() < 0.5;
251
252 if is_horizontal {
253 let min_x = p0.x.min(p1.x);
255 let max_x = p0.x.max(p1.x);
256 p0.y > rect.y && p0.y < rect.y + rect.h && max_x > rect.x && min_x < rect.x + rect.w
257 } else if is_vertical {
258 let min_y = p0.y.min(p1.y);
260 let max_y = p0.y.max(p1.y);
261 p0.x > rect.x && p0.x < rect.x + rect.w && max_y > rect.y && min_y < rect.y + rect.h
262 } else {
263 false }
265}
266
267fn center_groups(layout: &mut Layout) {
269 for group in &mut layout.groups {
271 let members: Vec<usize> = layout
272 .components
273 .iter()
274 .enumerate()
275 .filter(|(_, c)| c.group == group.name)
276 .map(|(i, _)| i)
277 .collect();
278
279 if members.is_empty() {
280 continue;
281 }
282
283 let min_x = members
284 .iter()
285 .map(|&i| layout.components[i].x)
286 .fold(f32::INFINITY, f32::min);
287 let max_x = members
288 .iter()
289 .map(|&i| layout.components[i].x)
290 .fold(f32::NEG_INFINITY, f32::max);
291 let min_y = members
292 .iter()
293 .map(|&i| layout.components[i].y)
294 .fold(f32::INFINITY, f32::min);
295 let max_y = members
296 .iter()
297 .map(|&i| layout.components[i].y)
298 .fold(f32::NEG_INFINITY, f32::max);
299
300 let padding = COMPONENT_HALF_SIZE;
301 group.bounds = GroupBounds {
302 x: min_x - padding,
303 y: min_y - padding,
304 w: (max_x - min_x) + 2.0 * padding,
305 h: (max_y - min_y) + 2.0 * padding,
306 };
307 }
308}
309
310pub fn layout_score(layout: &Layout) -> f32 {
314 let crossing_penalty = count_wire_crossings(layout) as f32 * -10.0;
315 let alignment_bonus = count_grid_aligned(layout) as f32 * 1.0;
316 let overlap_penalty = count_overlaps(layout) as f32 * -100.0;
317 let symmetry_bonus = measure_vertical_symmetry(layout) * 5.0;
318 let wire_length_penalty = total_wire_length(layout) * -0.01;
319 let signal_flow_bonus = if signal_flows_left_to_right(layout) {
320 50.0
321 } else {
322 0.0
323 };
324
325 crossing_penalty
326 + alignment_bonus
327 + overlap_penalty
328 + symmetry_bonus
329 + wire_length_penalty
330 + signal_flow_bonus
331}
332
333fn count_wire_crossings(layout: &Layout) -> usize {
334 let mut crossings = 0;
335 for i in 0..layout.wires.len() {
336 for j in (i + 1)..layout.wires.len() {
337 let wire_a = &layout.wires[i];
339 let wire_b = &layout.wires[j];
340 for seg_a in wire_a.points.windows(2) {
341 for seg_b in wire_b.points.windows(2) {
342 if segments_cross(seg_a[0], seg_a[1], seg_b[0], seg_b[1]) {
343 crossings += 1;
344 }
345 }
346 }
347 }
348 }
349 crossings
350}
351
352fn segments_cross(a1: [f32; 2], a2: [f32; 2], b1: [f32; 2], b2: [f32; 2]) -> bool {
353 let a_horiz = (a1[1] - a2[1]).abs() < 0.5;
356 let b_horiz = (b1[1] - b2[1]).abs() < 0.5;
357
358 if a_horiz == b_horiz {
359 return false; }
361
362 let (h, v) = if a_horiz {
363 ((a1, a2), (b1, b2))
364 } else {
365 ((b1, b2), (a1, a2))
366 };
367
368 let (h_min_x, h_max_x) = minmax(h.0[0], h.1[0]);
369 let h_y = h.0[1];
370 let (v_min_y, v_max_y) = minmax(v.0[1], v.1[1]);
371 let v_x = v.0[0];
372
373 v_x > h_min_x && v_x < h_max_x && h_y > v_min_y && h_y < v_max_y
374}
375
376fn minmax(a: f32, b: f32) -> (f32, f32) {
377 if a <= b {
378 (a, b)
379 } else {
380 (b, a)
381 }
382}
383
384fn count_grid_aligned(layout: &Layout) -> usize {
385 layout
386 .components
387 .iter()
388 .filter(|c| (c.x % GRID_SIZE).abs() < 0.5 && (c.y % GRID_SIZE).abs() < 0.5)
389 .count()
390}
391
392fn count_overlaps(layout: &Layout) -> usize {
393 let mut overlaps = 0;
394 for i in 0..layout.components.len() {
395 for j in (i + 1)..layout.components.len() {
396 let dx = (layout.components[i].x - layout.components[j].x).abs();
397 let dy = (layout.components[i].y - layout.components[j].y).abs();
398 if dx < MIN_CLEARANCE && dy < MIN_CLEARANCE {
399 overlaps += 1;
400 }
401 }
402 }
403 overlaps
404}
405
406fn measure_vertical_symmetry(layout: &Layout) -> f32 {
407 if layout.components.is_empty() {
408 return 0.0;
409 }
410
411 let center_y = layout.bounds.height / 2.0;
412 let deviations: f32 = layout
413 .components
414 .iter()
415 .map(|c| (c.y - center_y).abs())
416 .sum();
417 let max_deviation = layout.components.len() as f32 * center_y;
418
419 if max_deviation > 0.0 {
420 1.0 - (deviations / max_deviation)
421 } else {
422 1.0
423 }
424}
425
426fn total_wire_length(layout: &Layout) -> f32 {
427 layout
428 .wires
429 .iter()
430 .flat_map(|w| {
431 w.points.windows(2).map(|seg| {
432 let dx = seg[1][0] - seg[0][0];
433 let dy = seg[1][1] - seg[0][1];
434 (dx * dx + dy * dy).sqrt()
435 })
436 })
437 .sum()
438}
439
440fn signal_flows_left_to_right(layout: &Layout) -> bool {
441 let signal_wires: Vec<_> = layout.wires.iter().filter(|w| w.signal_path).collect();
443 if signal_wires.is_empty() {
444 return true;
445 }
446
447 let left_to_right_count = signal_wires
448 .iter()
449 .filter(|w| {
450 if let (Some(first), Some(last)) = (w.points.first(), w.points.last()) {
451 first[0] <= last[0]
452 } else {
453 true
454 }
455 })
456 .count();
457
458 left_to_right_count * 2 >= signal_wires.len()
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn segments_cross_perpendicular() {
467 assert!(segments_cross(
469 [0.0, 5.0],
470 [10.0, 5.0],
471 [5.0, 0.0],
472 [5.0, 10.0]
473 ));
474 }
475
476 #[test]
477 fn segments_no_cross_parallel() {
478 assert!(!segments_cross(
480 [0.0, 5.0],
481 [10.0, 5.0],
482 [0.0, 7.0],
483 [10.0, 7.0]
484 ));
485 }
486
487 #[test]
488 fn segments_no_cross_miss() {
489 assert!(!segments_cross(
491 [0.0, 5.0],
492 [3.0, 5.0],
493 [5.0, 0.0],
494 [5.0, 10.0]
495 ));
496 }
497}