LCOV - code coverage report
Current view: top level - dashql-dataflow/src/ortho_router - mod.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 96.1 % 152 146
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : //! Orthogonal connector routing around rectangular obstacles: right-angled routes between ports on the sides of
       2              : //! boxes, with few bends, and routes that share a channel spread apart.
       3              : //!
       4              : //! Vendored from ortho-router 0.1.0 (https://crates.io/crates/ortho-router, part of
       5              : //! https://github.com/quazardous/egui-pop), MIT license (`LICENSE-MIT`, NOTICE). Changed: the crate root is this
       6              : //! module, its tests are `tests.rs`.
       7              : #![forbid(unsafe_code)]
       8              : #![warn(missing_docs)]
       9              : 
      10              : mod geom;
      11              : mod grid;
      12              : mod nudge;
      13              : 
      14              : pub use geom::{Point, Rect, Side};
      15              : 
      16              : use geom::EPS;
      17              : use grid::{Grid, Search};
      18              : 
      19              : /// A connector's end: a point on a node's edge and the side it leaves
      20              : /// from.
      21              : #[derive(Clone, Copy, Debug, PartialEq)]
      22              : pub struct Port {
      23              :     /// Where the connector starts or ends, normally on its node's edge.
      24              :     pub at: Point,
      25              :     /// The side of the node the port is on: the connector leaves (or
      26              :     /// comes in) at a right angle to it.
      27              :     pub side: Side,
      28              : }
      29              : 
      30              : impl Port {
      31              :     /// A port at `at` on the `side` of its node.
      32          124 :     pub fn new(at: impl Into<Point>, side: Side) -> Port {
      33          124 :         Port {
      34          124 :             at: at.into(),
      35          124 :             side,
      36          124 :         }
      37          124 :     }
      38              : }
      39              : 
      40              : /// A connector to route, from one port to another.
      41              : #[derive(Clone, Copy, Debug, PartialEq)]
      42              : pub struct Connector {
      43              :     /// Where it starts.
      44              :     pub from: Port,
      45              :     /// Where it ends.
      46              :     pub to: Port,
      47              : }
      48              : 
      49              : impl Connector {
      50              :     /// A connector from `from` to `to`.
      51           62 :     pub fn new(from: Port, to: Port) -> Connector {
      52           62 :         Connector { from, to }
      53           62 :     }
      54              : }
      55              : 
      56              : /// How routes are drawn. The distances are in the caller's units.
      57              : #[derive(Clone, Copy, Debug, PartialEq)]
      58              : pub struct Options {
      59              :     /// How far routes keep from obstacles (default 12). It is also the
      60              :     /// length of the straight stub a route leaves a port with.
      61              :     pub margin: f32,
      62              :     /// What a bend costs, as a length (default 40): the higher, the
      63              :     /// fewer bends at the price of longer routes.
      64              :     pub bend_penalty: f32,
      65              :     /// How far apart [`Router::route_all`] spreads wires that share a
      66              :     /// channel (default 6; 0 leaves them on top of each other). A group
      67              :     /// spreads over the margin at most, so with many wires in one
      68              :     /// channel they come closer than this.
      69              :     pub spacing: f32,
      70              : }
      71              : 
      72              : impl Default for Options {
      73           38 :     fn default() -> Options {
      74           38 :         Options {
      75           38 :             margin: 12.0,
      76           38 :             bend_penalty: 40.0,
      77           38 :             spacing: 6.0,
      78           38 :         }
      79           38 :     }
      80              : }
      81              : 
      82              : /// A routed connector: a polyline of horizontal and vertical segments.
      83              : #[derive(Clone, Debug, Default, PartialEq)]
      84              : pub struct Route {
      85              :     /// Its vertices, from the first port to the second, without
      86              :     /// repeated or collinear points.
      87              :     pub points: Vec<Point>,
      88              :     /// `false` when no way around the obstacles was found: `points` is
      89              :     /// then the fallback, a stub out of each port joined by a single
      90              :     /// elbow (orthogonal, but it may cross obstacles). A connector with
      91              :     /// a coordinate that is not finite falls back to the straight
      92              :     /// segment between its ports.
      93              :     pub routed: bool,
      94              : }
      95              : 
      96              : impl Route {
      97              :     /// How many times it turns.
      98            8 :     pub fn bends(&self) -> usize {
      99            8 :         self.points.len().saturating_sub(2)
     100            8 :     }
     101              : 
     102              :     /// Its length.
     103            6 :     pub fn length(&self) -> f32 {
     104            6 :         self.points
     105            6 :             .windows(2)
     106           18 :             .map(|w| (w[1].x - w[0].x).abs() + (w[1].y - w[0].y).abs())
     107            6 :             .sum()
     108            6 :     }
     109              : }
     110              : 
     111              : /// Routes connectors around obstacles.
     112              : ///
     113              : /// Keep one between frames: it keeps the obstacles and the search's
     114              : /// buffers. Its output depends only on its obstacles, its options and
     115              : /// the connectors, so a caller can cache routes under
     116              : /// [`fingerprint`](Router::fingerprint).
     117              : #[derive(Clone, Debug, Default)]
     118              : pub struct Router {
     119              :     obstacles: Vec<Rect>,
     120              :     options: Options,
     121              :     search: Search,
     122              : }
     123              : 
     124              : impl Router {
     125              :     /// A router with these options and no obstacles.
     126           19 :     pub fn new(options: Options) -> Router {
     127           19 :         Router {
     128           19 :             options,
     129           19 :             ..Router::default()
     130           19 :         }
     131           19 :     }
     132              : 
     133              :     /// The router with these obstacles.
     134           19 :     pub fn with_obstacles(mut self, obstacles: impl IntoIterator<Item = Rect>) -> Router {
     135           19 :         self.set_obstacles(obstacles);
     136           19 :         self
     137           19 :     }
     138              : 
     139              :     /// Replaces the obstacles, typically the nodes' rectangles. A
     140              :     /// rectangle that is not [valid](Rect::is_valid) is ignored.
     141           19 :     pub fn set_obstacles(&mut self, obstacles: impl IntoIterator<Item = Rect>) {
     142           19 :         self.obstacles.clear();
     143           19 :         self.obstacles
     144           19 :             .extend(obstacles.into_iter().filter(Rect::is_valid));
     145           19 :     }
     146              : 
     147              :     /// The obstacles.
     148            1 :     pub fn obstacles(&self) -> &[Rect] {
     149            1 :         &self.obstacles
     150            1 :     }
     151              : 
     152              :     /// The options.
     153            0 :     pub fn options(&self) -> &Options {
     154            0 :         &self.options
     155            0 :     }
     156              : 
     157              :     /// Replaces the options.
     158            1 :     pub fn set_options(&mut self, options: Options) {
     159            1 :         self.options = options;
     160            1 :     }
     161              : 
     162              :     /// Routes one connector.
     163           10 :     pub fn route(&mut self, connector: &Connector) -> Route {
     164           10 :         self.route_all(std::slice::from_ref(connector))
     165           10 :             .pop()
     166           10 :             .unwrap_or_default()
     167           10 :     }
     168              : 
     169              :     /// Routes connectors, in order: the routes match `connectors`.
     170              :     ///
     171              :     /// The connectors share one grid, built from the obstacles and every
     172              :     /// connector's ports, so routing many at once costs little more than
     173              :     /// routing one. Then wires sharing a channel are spread
     174              :     /// [`spacing`](Options::spacing) apart: their segments on one line
     175              :     /// move across it, the segments at the ports excepted.
     176           17 :     pub fn route_all(&mut self, connectors: &[Connector]) -> Vec<Route> {
     177           17 :         let margin = self.options.margin.max(0.0);
     178           17 :         let bend = self.options.bend_penalty.max(0.0);
     179          155 :         let grown: Vec<Rect> = self.obstacles.iter().map(|o| o.expand(margin)).collect();
     180           17 :         let stubs: Vec<Point> = connectors
     181           17 :             .iter()
     182          110 :             .flat_map(|c| [stub(&c.from, margin), stub(&c.to, margin)])
     183          220 :             .filter(|p| p.is_finite())
     184           17 :             .collect();
     185           17 :         let grid = Grid::build(&grown, &stubs);
     186           17 :         let mut routes: Vec<Route> = connectors
     187           17 :             .iter()
     188          110 :             .map(|c| route_in(&grid, &mut self.search, c, margin, bend))
     189           17 :             .collect();
     190           17 :         nudge::nudge(&mut routes, self.options.spacing, margin);
     191           17 :         routes
     192           17 :     }
     193              : 
     194              :     /// A hash of the obstacles, the options and `connectors`: the same
     195              :     /// value, the same routes. Stable across runs and platforms (FNV-1a
     196              :     /// over the coordinates' bits), so it can key a cache.
     197            5 :     pub fn fingerprint(&self, connectors: &[Connector]) -> u64 {
     198            5 :         let mut h = Fnv::default();
     199            5 :         h.f32(self.options.margin);
     200            5 :         h.f32(self.options.bend_penalty);
     201            5 :         h.f32(self.options.spacing);
     202            5 :         h.u64(self.obstacles.len() as u64);
     203            5 :         for o in &self.obstacles {
     204           20 :             for v in [o.min.x, o.min.y, o.max.x, o.max.y] {
     205           20 :                 h.f32(v);
     206           20 :             }
     207              :         }
     208            5 :         h.u64(connectors.len() as u64);
     209            5 :         for c in connectors {
     210           10 :             for p in [c.from, c.to] {
     211           10 :                 h.f32(p.at.x);
     212           10 :                 h.f32(p.at.y);
     213           10 :                 h.u64(p.side as u64);
     214           10 :             }
     215              :         }
     216            5 :         h.0
     217            5 :     }
     218              : }
     219              : 
     220              : /// Where a route leaving `port` turns for the first time at the
     221              : /// earliest: `margin` out of the port.
     222          438 : fn stub(port: &Port, margin: f32) -> Point {
     223          438 :     port.at.toward(port.side, margin)
     224          438 : }
     225              : 
     226              : /// One connector over the grid, or its fallback.
     227          110 : fn route_in(grid: &Grid, search: &mut Search, c: &Connector, margin: f32, bend: f32) -> Route {
     228          110 :     let (a, b) = (c.from.at, c.to.at);
     229          110 :     if !a.is_finite() || !b.is_finite() {
     230            1 :         return Route {
     231            1 :             points: vec![a, b],
     232            1 :             routed: false,
     233            1 :         };
     234          109 :     }
     235          109 :     let (sa, sb) = (stub(&c.from, margin), stub(&c.to, margin));
     236          109 :     let path = grid.vertex(sa).zip(grid.vertex(sb)).and_then(|(va, vb)| {
     237              :         // The route arrives going into the second port's node.
     238          108 :         search.find(grid, va, c.from.side, vb, c.to.side.opposite(), bend)
     239          108 :     });
     240          109 :     match path {
     241          108 :         Some(path) => Route {
     242          108 :             points: simplify(
     243          108 :                 std::iter::once(a)
     244          784 :                     .chain(path.into_iter().map(|v| grid.point(v)))
     245          108 :                     .chain(std::iter::once(b)),
     246              :             ),
     247              :             routed: true,
     248              :         },
     249            1 :         None => Route {
     250            1 :             points: simplify(fallback(a, sa, c.from.side, sb, b)),
     251            1 :             routed: false,
     252            1 :         },
     253              :     }
     254          110 : }
     255              : 
     256              : /// A stub out of each port joined by one elbow, halfway across.
     257            1 : fn fallback(a: Point, sa: Point, side: Side, sb: Point, b: Point) -> [Point; 6] {
     258            1 :     let (p, q) = if side.is_horizontal() {
     259            1 :         let x = (sa.x + sb.x) / 2.0;
     260            1 :         (Point::new(x, sa.y), Point::new(x, sb.y))
     261              :     } else {
     262            0 :         let y = (sa.y + sb.y) / 2.0;
     263            0 :         (Point::new(sa.x, y), Point::new(sb.x, y))
     264              :     };
     265            1 :     [a, sa, p, q, sb, b]
     266            1 : }
     267              : 
     268              : /// FNV-1a, 64 bits.
     269              : struct Fnv(u64);
     270              : 
     271              : impl Default for Fnv {
     272            5 :     fn default() -> Fnv {
     273            5 :         Fnv(0xcbf2_9ce4_8422_2325)
     274            5 :     }
     275              : }
     276              : 
     277              : impl Fnv {
     278           75 :     fn u64(&mut self, v: u64) {
     279          600 :         for b in v.to_le_bytes() {
     280          600 :             self.0 ^= u64::from(b);
     281          600 :             self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
     282          600 :         }
     283           75 :     }
     284              : 
     285           55 :     fn f32(&mut self, v: f32) {
     286              :         // -0.0 and 0.0 route the same.
     287           55 :         self.u64(u64::from((v + 0.0).to_bits()));
     288           55 :     }
     289              : }
     290              : 
     291              : /// The points without repeats and without the middle of three on one
     292              : /// line.
     293          109 : fn simplify(points: impl IntoIterator<Item = Point>) -> Vec<Point> {
     294          109 :     let mut out: Vec<Point> = Vec::new();
     295         1006 :     for p in points {
     296         1006 :         if out.last().is_some_and(|l| l.near(p)) {
     297            0 :             continue;
     298         1006 :         }
     299         1006 :         if let [.., u, v] = out[..] {
     300          788 :             let vertical = (u.x - v.x).abs() <= EPS && (v.x - p.x).abs() <= EPS;
     301          788 :             let horizontal = (u.y - v.y).abs() <= EPS && (v.y - p.y).abs() <= EPS;
     302          788 :             if vertical || horizontal {
     303          612 :                 out.pop();
     304          612 :             }
     305          218 :         }
     306         1006 :         out.push(p);
     307              :     }
     308          109 :     out
     309          109 : }
     310              : 
     311              : #[cfg(test)]
     312              : mod tests;
        

Generated by: LCOV version 2.0-1