LCOV - code coverage report
Current view: top level - dashql-dataflow/src/ortho_router - nudge.rs Coverage Total Hit
Test: DashQL 915828b Lines: 98.5 % 133 131
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : //! Nudging: routes found one by one share the grid's lines, so wires
       2              : //! through one channel are drawn on top of each other. Afterwards,
       3              : //! segments of different routes that lie on one line and overlap are
       4              : //! spread apart across it, `spacing` apart and centred on the line.
       5              : //!
       6              : //! A segment moves as a whole, across its own direction: its two
       7              : //! neighbours are perpendicular to it, so they only grow or shrink and
       8              : //! the route stays orthogonal. A route's first and last segments are
       9              : //! left in place, since they hold its ports. The spread is at most the
      10              : //! margin (half of it on each side of the line), so a nudged segment
      11              : //! keeps at least half the margin away from the obstacles.
      12              : //!
      13              : //! Within a group, segments are ordered so the spreading adds as few
      14              : //! crossings as it can. Take vertical segments, ordered left to right:
      15              : //! each has a top end and a bottom end, where it turns left or right.
      16              : //! A segment turning left at both ends goes left of one turning right
      17              : //! at both; between two turning the same way at the top, the one whose
      18              : //! top is lower goes on that side (so its turn does not cut the other
      19              : //! one), and between two turning the same way at the bottom, the one
      20              : //! whose bottom is higher goes on that side. Horizontal segments are
      21              : //! ordered the same way with the axes swapped. Ties keep the
      22              : //! connectors' order.
      23              : 
      24              : use super::Route;
      25              : use super::geom::{EPS, Point};
      26              : 
      27              : /// A segment eligible for nudging.
      28              : #[derive(Clone, Copy, Debug)]
      29              : struct Seg {
      30              :     /// The route and the index of the segment's first point.
      31              :     route: usize,
      32              :     k: usize,
      33              :     horizontal: bool,
      34              :     /// Its line: `y` when horizontal, else `x`.
      35              :     at: f32,
      36              :     lo: f32,
      37              :     hi: f32,
      38              :     /// Its place across the line, smallest first.
      39              :     key: (f32, f32),
      40              : }
      41              : 
      42              : /// Spreads overlapping collinear segments of `routes` apart.
      43           21 : pub(crate) fn nudge(routes: &mut [Route], spacing: f32, margin: f32) {
      44           21 :     if spacing <= 0.0 || !spacing.is_finite() {
      45            0 :         return;
      46           21 :     }
      47           21 :     let mut segs = segments(routes);
      48          318 :     segs.sort_by(|a, b| {
      49          318 :         a.horizontal
      50          318 :             .cmp(&b.horizontal)
      51          318 :             .then(a.at.total_cmp(&b.at))
      52          318 :             .then(a.lo.total_cmp(&b.lo))
      53          318 :             .then(a.route.cmp(&b.route))
      54          318 :             .then(a.k.cmp(&b.k))
      55          318 :     });
      56              :     // Shifts, decided on the routes as found, then applied.
      57           21 :     let mut shifts: Vec<(usize, usize, bool, f32)> = Vec::new();
      58           21 :     let mut start = 0;
      59           57 :     while start < segs.len() {
      60              :         // A group: one line, and each segment overlapping the span of
      61              :         // those before it.
      62           36 :         let first = segs[start];
      63           36 :         let mut end = start + 1;
      64           36 :         let mut hi = first.hi;
      65          101 :         while end < segs.len() {
      66           85 :             let s = segs[end];
      67           85 :             if s.horizontal != first.horizontal || (s.at - first.at).abs() > EPS || s.lo >= hi - EPS
      68              :             {
      69           20 :                 break;
      70           65 :             }
      71           65 :             hi = hi.max(s.hi);
      72           65 :             end += 1;
      73              :         }
      74           36 :         let group = &mut segs[start..end];
      75           36 :         start = end;
      76           36 :         let routes_in = {
      77           36 :             let mut r: Vec<usize> = group.iter().map(|s| s.route).collect();
      78           36 :             r.dedup();
      79           36 :             r.len()
      80              :         };
      81           36 :         if group.len() < 2 || routes_in < 2 {
      82           22 :             continue;
      83           14 :         }
      84          159 :         group.sort_by(|a, b| {
      85          159 :             a.key
      86          159 :                 .0
      87          159 :                 .total_cmp(&b.key.0)
      88          159 :                 .then(a.key.1.total_cmp(&b.key.1))
      89          159 :                 .then(a.route.cmp(&b.route))
      90          159 :         });
      91           14 :         let n = group.len() as f32;
      92           14 :         let step = spacing.min(margin.max(0.0) / (n - 1.0));
      93           79 :         for (m, s) in group.iter().enumerate() {
      94           79 :             let off = (m as f32 - (n - 1.0) / 2.0) * step;
      95           79 :             if off != 0.0 {
      96           78 :                 shifts.push((s.route, s.k, s.horizontal, off));
      97           78 :             }
      98              :         }
      99              :     }
     100           78 :     for (r, k, horizontal, off) in shifts {
     101           78 :         let points = &mut routes[r].points;
     102          156 :         for p in &mut points[k..k + 2] {
     103          156 :             if horizontal {
     104            0 :                 p.y += off;
     105          156 :             } else {
     106          156 :                 p.x += off;
     107          156 :             }
     108              :         }
     109              :     }
     110           21 : }
     111              : 
     112              : /// The routes' segments that may move: not a route's first or last,
     113              : /// and only of routes found around the obstacles.
     114           21 : fn segments(routes: &[Route]) -> Vec<Seg> {
     115           21 :     let mut out = Vec::new();
     116          121 :     for (r, route) in routes.iter().enumerate() {
     117          121 :         if !route.routed {
     118            2 :             continue;
     119          119 :         }
     120          119 :         let p = &route.points;
     121          119 :         for k in 1..p.len().saturating_sub(2) {
     122          101 :             let (a, b) = (p[k], p[k + 1]);
     123          101 :             let (before, after) = (p[k - 1], p[k + 2]);
     124          101 :             let horizontal = (a.y - b.y).abs() <= EPS;
     125              :             // Along the segment `u`, across it `v`.
     126          606 :             let uv = |p: Point| if horizontal { (p.x, p.y) } else { (p.y, p.x) };
     127          101 :             let ((ua, va), (ub, _)) = (uv(a), uv(b));
     128              :             // Which way it turns at an end: -1 toward smaller `v`.
     129          202 :             let turn = |end: Point, next: Point| (uv(next).1 - uv(end).1).signum();
     130          101 :             let (ta, tb) = (turn(a, before), turn(b, after));
     131              :             // The first end (smaller `u`) and the second.
     132          101 :             let ((u1, t1), (u2, t2)) = if ua <= ub {
     133           69 :                 ((ua, ta), (ub, tb))
     134              :             } else {
     135           32 :                 ((ub, tb), (ua, ta))
     136              :             };
     137          101 :             out.push(Seg {
     138          101 :                 route: r,
     139          101 :                 k,
     140          101 :                 horizontal,
     141          101 :                 at: va,
     142          101 :                 lo: u1,
     143          101 :                 hi: u2,
     144          101 :                 key: (t1 + t2, t1 * u1 - t2 * u2),
     145          101 :             });
     146              :         }
     147              :     }
     148           21 :     out
     149           21 : }
     150              : 
     151              : #[cfg(test)]
     152              : mod tests {
     153              :     use super::*;
     154              : 
     155           11 :     fn route(points: &[[f32; 2]]) -> Route {
     156              :         Route {
     157           40 :             points: points.iter().map(|&p| Point::from(p)).collect(),
     158              :             routed: true,
     159              :         }
     160           11 :     }
     161              : 
     162              :     #[test]
     163            1 :     fn two_wires_down_one_channel_are_spread_apart() {
     164              :         // Both go down x = 50 between y = 0 and y = 100.
     165            1 :         let mut routes = vec![
     166            1 :             route(&[[0.0, 0.0], [50.0, 0.0], [50.0, 100.0], [100.0, 100.0]]),
     167            1 :             route(&[[0.0, 20.0], [50.0, 20.0], [50.0, 120.0], [100.0, 120.0]]),
     168              :         ];
     169            1 :         nudge(&mut routes, 6.0, 12.0);
     170            1 :         let x0 = routes[0].points[1].x;
     171            1 :         let x1 = routes[1].points[1].x;
     172              :         // The second turns in lower at the top and out lower at the
     173              :         // bottom: on the left, it crosses neither turn of the first.
     174            1 :         assert_eq!(x0 - x1, 6.0);
     175            1 :         assert_eq!(x0 + x1, 100.0, "centred on the line");
     176            2 :         for r in &routes {
     177            2 :             assert_eq!(r.points[1].x, r.points[2].x, "still vertical");
     178            2 :             assert_eq!(r.points[0], Point::new(0.0, r.points[0].y));
     179              :         }
     180            1 :     }
     181              : 
     182              :     #[test]
     183            1 :     fn the_spread_is_at_most_the_margin() {
     184            1 :         let mut routes: Vec<Route> = (0..5)
     185            5 :             .map(|i| {
     186            5 :                 let y = i as f32 * 10.0;
     187            5 :                 route(&[[0.0, y], [50.0, y], [50.0, y + 100.0], [100.0, y + 100.0]])
     188            5 :             })
     189            1 :             .collect();
     190            1 :         nudge(&mut routes, 6.0, 8.0);
     191            5 :         let xs: Vec<f32> = routes.iter().map(|r| r.points[1].x).collect();
     192            1 :         let (lo, hi) = xs
     193            1 :             .iter()
     194            5 :             .fold((f32::MAX, f32::MIN), |(l, h), &x| (l.min(x), h.max(x)));
     195            1 :         assert!(hi - lo <= 8.0 + 1e-4, "{xs:?}");
     196            1 :     }
     197              : 
     198              :     #[test]
     199            1 :     fn segments_that_do_not_overlap_stay() {
     200            1 :         let mut routes = vec![
     201            1 :             route(&[[0.0, 0.0], [50.0, 0.0], [50.0, 40.0], [100.0, 40.0]]),
     202            1 :             route(&[[0.0, 60.0], [50.0, 60.0], [50.0, 100.0], [100.0, 100.0]]),
     203              :         ];
     204            1 :         let before = routes.clone();
     205            1 :         nudge(&mut routes, 6.0, 12.0);
     206            1 :         assert_eq!(routes, before);
     207            1 :     }
     208              : 
     209              :     #[test]
     210            1 :     fn ports_never_move() {
     211            1 :         let mut routes = vec![
     212            1 :             route(&[[0.0, 0.0], [100.0, 0.0]]),
     213            1 :             route(&[[0.0, 0.0], [100.0, 0.0]]),
     214              :         ];
     215            1 :         let before = routes.clone();
     216            1 :         nudge(&mut routes, 6.0, 12.0);
     217            1 :         assert_eq!(routes, before);
     218            1 :     }
     219              : }
        

Generated by: LCOV version 2.0-1