use crate::layout::Grid;
use crate::layout::grid::Attach;
use crate::types::Graph;
pub(crate) fn route_all(
grid: &mut Grid,
graph: &Graph,
attach_points: &[Option<(Attach, Attach)>],
tip_for: impl Fn(usize) -> char,
is_back: impl Fn(usize) -> bool,
) -> Vec<Option<Vec<(usize, usize)>>> {
let n = graph.edges.len();
let mut paths: Vec<Option<Vec<(usize, usize)>>> = vec![None; n];
let order = order_edges(graph, attach_points);
let horizontal_first = graph.direction.is_horizontal();
for edge_idx in order {
let Some(Some((src, dst))) = attach_points.get(edge_idx) else {
continue;
};
let (src, dst) = (*src, *dst);
let tip = tip_for(edge_idx);
let back = is_back(edge_idx);
let path = if back {
grid.route_back_edge(src.col, src.row, dst.col, dst.row, horizontal_first, tip)
} else if let Some(p) = try_straight_route(grid, src, dst, horizontal_first, tip) {
Some(p)
} else if let Some(p) = try_l_route(grid, src, dst, horizontal_first, tip) {
Some(p)
} else {
grid.route_edge(src.col, src.row, dst.col, dst.row, horizontal_first, tip)
};
paths[edge_idx] = path;
}
paths
}
fn order_edges(graph: &Graph, attach_points: &[Option<(Attach, Attach)>]) -> Vec<usize> {
let n = graph.edges.len();
let mut with_dist: Vec<(usize, usize)> = (0..n)
.map(|i| {
let dist = attach_points
.get(i)
.and_then(|p| p.as_ref())
.map(|(src, dst)| src.col.abs_diff(dst.col) + src.row.abs_diff(dst.row))
.unwrap_or(usize::MAX); (i, dist)
})
.collect();
with_dist.sort_by_key(|&(_, d)| d);
with_dist.into_iter().map(|(i, _)| i).collect()
}
fn try_straight_route(
grid: &mut Grid,
src: Attach,
dst: Attach,
horizontal_first: bool,
tip: char,
) -> Option<Vec<(usize, usize)>> {
if src.col == dst.col {
let (r0, r1) = min_max(src.row, dst.row);
if (r0..=r1).all(|r| !grid.is_node_box(src.col, r)) {
return grid.route_edge(src.col, src.row, dst.col, dst.row, horizontal_first, tip);
}
} else if src.row == dst.row {
let (c0, c1) = min_max(src.col, dst.col);
if (c0..=c1).all(|c| !grid.is_node_box(c, src.row)) {
return grid.route_edge(src.col, src.row, dst.col, dst.row, horizontal_first, tip);
}
}
None
}
fn try_l_route(
grid: &mut Grid,
src: Attach,
dst: Attach,
_horizontal_first: bool,
tip: char,
) -> Option<Vec<(usize, usize)>> {
if src.col == dst.col || src.row == dst.row {
return None;
}
let cost_hv = l_cost(grid, src, dst, true);
let cost_vh = l_cost(grid, src, dst, false);
match (cost_hv, cost_vh) {
(None, None) => None, (Some(_), None) => {
grid.route_edge(src.col, src.row, dst.col, dst.row, true, tip)
}
(None, Some(_)) => {
grid.route_edge(src.col, src.row, dst.col, dst.row, false, tip)
}
(Some(ch), Some(cv)) if ch == cv => {
None
}
(Some(ch), Some(cv)) => {
let prefer_hv = ch < cv;
grid.route_edge(src.col, src.row, dst.col, dst.row, prefer_hv, tip)
}
}
}
fn l_cost(grid: &Grid, src: Attach, dst: Attach, hv_first: bool) -> Option<u32> {
let (corner_c, corner_r) = if hv_first {
(dst.col, src.row)
} else {
(src.col, dst.row)
};
let mut cost = 0u32;
let (c0, c1) = if hv_first {
min_max(src.col, corner_c)
} else {
(src.col, src.col)
};
let (r0, r1) = if hv_first {
(src.row, src.row)
} else {
min_max(src.row, corner_r)
};
for c in c0..=c1 {
for r in r0..=r1 {
if grid.is_node_box(c, r) && !(c == dst.col && r == dst.row) {
return None;
}
cost += grid.edge_occupied_cost(c, r);
}
}
let (c0, c1) = if hv_first {
(corner_c, corner_c)
} else {
min_max(corner_c, dst.col)
};
let (r0, r1) = if hv_first {
min_max(corner_r, dst.row)
} else {
(dst.row, dst.row)
};
for c in c0..=c1 {
for r in r0..=r1 {
if grid.is_node_box(c, r) && !(c == dst.col && r == dst.row) {
return None;
}
cost += grid.edge_occupied_cost(c, r);
}
}
Some(cost)
}
fn min_max(a: usize, b: usize) -> (usize, usize) {
if a <= b { (a, b) } else { (b, a) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Direction, Edge, Graph, Node, NodeShape};
fn two_node_lr() -> (Graph, Vec<Option<(Attach, Attach)>>) {
let mut g = Graph::new(Direction::LeftToRight);
g.nodes.push(Node::new("A", "A", NodeShape::Rectangle));
g.nodes.push(Node::new("B", "B", NodeShape::Rectangle));
g.edges.push(Edge::new("A", "B", None));
let attaches = vec![Some((Attach { col: 7, row: 1 }, Attach { col: 9, row: 1 }))];
(g, attaches)
}
#[test]
fn route_all_straight_line_uses_fast_path() {
let (g, attaches) = two_node_lr();
let mut grid = Grid::new(20, 5);
let paths = route_all(
&mut grid,
&g,
&attaches,
|_| crate::layout::grid::arrow::RIGHT,
|_| false,
);
assert_eq!(paths.len(), 1);
let path = paths[0].as_ref().expect("expected a routed path");
assert_eq!(path.first(), Some(&(7, 1)));
assert_eq!(path.last(), Some(&(9, 1)));
}
#[test]
fn route_all_back_edge_skips_fast_paths() {
let mut g = Graph::new(Direction::LeftToRight);
g.nodes.push(Node::new("A", "A", NodeShape::Rectangle));
g.nodes.push(Node::new("B", "B", NodeShape::Rectangle));
g.edges.push(Edge::new("B", "A", None));
let attaches = vec![Some((Attach { col: 7, row: 3 }, Attach { col: 2, row: 3 }))];
let mut grid = Grid::new(30, 10);
let paths = route_all(
&mut grid,
&g,
&attaches,
|_| crate::layout::grid::arrow::LEFT,
|_| true, );
assert!(paths[0].is_some(), "back-edge should produce a path");
}
#[test]
fn route_all_l_route_single_bend_with_obstacle() {
let mut g = Graph::new(Direction::LeftToRight);
g.nodes.push(Node::new("A", "A", NodeShape::Rectangle));
g.nodes.push(Node::new("B", "B", NodeShape::Rectangle));
g.edges.push(Edge::new("A", "B", None));
let attaches = vec![Some((Attach { col: 0, row: 0 }, Attach { col: 4, row: 2 }))];
let mut grid = Grid::new(10, 5);
grid.mark_node_box(0, 2, 1, 1);
let paths = route_all(
&mut grid,
&g,
&attaches,
|_| crate::layout::grid::arrow::RIGHT,
|_| false,
);
let path = paths[0].as_ref().expect("expected a routed path");
assert_eq!(path.first(), Some(&(0, 0)));
assert_eq!(path.last(), Some(&(4, 2)));
assert!(path.len() >= 3, "L-path should have at least 3 cells");
}
#[test]
fn route_all_astar_fallback_detours_around_obstacle() {
let mut g = Graph::new(Direction::LeftToRight);
g.nodes.push(Node::new("A", "A", NodeShape::Rectangle));
g.nodes.push(Node::new("B", "B", NodeShape::Rectangle));
g.edges.push(Edge::new("A", "B", None));
let src = Attach { col: 0, row: 0 };
let dst = Attach { col: 5, row: 2 };
let attaches = vec![Some((src, dst))];
let mut grid = Grid::new(10, 5);
grid.mark_node_box(5, 0, 1, 1);
grid.mark_node_box(0, 2, 1, 1);
let paths = route_all(
&mut grid,
&g,
&attaches,
|_| crate::layout::grid::arrow::RIGHT,
|_| false,
);
let path = paths[0].as_ref().expect("A* must find a path");
assert_eq!(path.first(), Some(&(0, 0)));
assert_eq!(path.last(), Some(&(5, 2)));
}
#[test]
fn route_all_stable_indexing_multi_edge() {
let mut g = Graph::new(Direction::LeftToRight);
g.nodes.push(Node::new("A", "A", NodeShape::Rectangle));
g.nodes.push(Node::new("B", "B", NodeShape::Rectangle));
g.nodes.push(Node::new("C", "C", NodeShape::Rectangle));
g.edges.push(Edge::new("A", "B", None));
g.edges.push(Edge::new("B", "C", None));
g.edges.push(Edge::new("A", "C", None));
let attaches = vec![
Some((Attach { col: 0, row: 0 }, Attach { col: 4, row: 0 })),
None, Some((Attach { col: 0, row: 0 }, Attach { col: 8, row: 0 })),
];
let mut grid = Grid::new(15, 5);
let paths = route_all(
&mut grid,
&g,
&attaches,
|_| crate::layout::grid::arrow::RIGHT,
|_| false,
);
assert_eq!(paths.len(), 3);
assert!(paths[0].is_some());
assert!(paths[1].is_none(), "None attach must yield None path");
assert!(paths[2].is_some());
}
#[test]
fn order_edges_ascending_distance() {
let mut g = Graph::new(Direction::LeftToRight);
g.nodes.push(Node::new("A", "A", NodeShape::Rectangle));
g.nodes.push(Node::new("B", "B", NodeShape::Rectangle));
g.nodes.push(Node::new("C", "C", NodeShape::Rectangle));
g.edges.push(Edge::new("A", "C", None)); g.edges.push(Edge::new("A", "B", None)); let attaches = vec![
Some((Attach { col: 0, row: 0 }, Attach { col: 8, row: 0 })), Some((Attach { col: 0, row: 0 }, Attach { col: 4, row: 0 })), ];
let order = order_edges(&g, &attaches);
let pos_short = order.iter().position(|&i| i == 1).unwrap();
let pos_long = order.iter().position(|&i| i == 0).unwrap();
assert!(
pos_short < pos_long,
"short edge should route before long edge"
);
}
#[test]
fn order_edges_no_attach_sorted_last() {
let mut g = Graph::new(Direction::LeftToRight);
g.nodes.push(Node::new("A", "A", NodeShape::Rectangle));
g.nodes.push(Node::new("B", "B", NodeShape::Rectangle));
g.edges.push(Edge::new("A", "B", None));
g.edges.push(Edge::new("A", "B", None));
let attaches = vec![
None, Some((Attach { col: 0, row: 0 }, Attach { col: 4, row: 0 })), ];
let order = order_edges(&g, &attaches);
let pos_none = order.iter().position(|&i| i == 0).unwrap();
let pos_some = order.iter().position(|&i| i == 1).unwrap();
assert!(pos_some < pos_none, "edge with no attach should sort last");
}
}