1use std::collections::BTreeMap;
2
3use blockworx_router::{
4 ClosedRouter, Direction, WIRE_COST, cost::Cost, direction_between, point::Point,
5};
6
7use crate::{
8 edit::create::PathOrdinal,
9 widget::{edge::RouteEdge, segmentkind::SegmentKind},
10};
11
12fn route_leg_preferring_straight(
21 router: &mut ClosedRouter,
22 a: Point,
23 b: Point,
24 incoming: Option<Direction>,
25) -> (Vec<Point>, Option<Direction>) {
26 if a != b
27 && (a.x == b.x || a.y == b.y)
28 && !router.is_wire_blocked(a, b)
29 && !router.wire_hugs_block(a, b)
30 {
31 (vec![a, b], direction_between(a, b))
32 } else {
33 router.route_leg(a, b, incoming)
34 }
35}
36
37#[derive(Copy, Clone)]
38pub struct TaggedPoint {
39 pub segment: SegmentKind,
40 pub pos: Point,
41}
42
43impl std::fmt::Debug for TaggedPoint {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self.segment {
46 SegmentKind::StartToEnd => write!(f, "s->e {}", self.pos),
47 SegmentKind::StartToWaypoint(wp) => {
48 write!(f, "s->wp[{}] {}", usize::from(wp), self.pos)
49 }
50 SegmentKind::WaypointToWaypoint(wp0, wp1) => {
51 write!(
52 f,
53 "wp[{}]->wp[{}] {}",
54 usize::from(wp0),
55 usize::from(wp1),
56 self.pos
57 )
58 }
59 SegmentKind::WaypointToEnd(wp) => {
60 write!(f, "wp[{}] -> e {}", usize::from(wp), self.pos)
61 }
62 }
63 }
64}
65
66#[cfg_attr(
89 not(test),
90 allow(dead_code, reason = "Skip is exercised by tests only")
91)]
92#[derive(Clone, Copy, PartialEq, Eq)]
93pub enum SelfCost {
94 Apply,
95 Skip,
96}
97
98pub struct RouteRequest<'a> {
101 pub start: Point,
102 pub end: Point,
103 pub wp_ids: &'a [PathOrdinal],
104 pub wp_positions: &'a BTreeMap<PathOrdinal, Point>,
105 pub self_cost: SelfCost,
106}
107
108impl RouteRequest<'_> {
109 pub fn route(&self, router: &mut ClosedRouter) -> Vec<TaggedPoint> {
110 let Self {
111 start,
112 end,
113 wp_ids,
114 wp_positions,
115 self_cost,
116 } = *self;
117 let bump = |router: &mut ClosedRouter, subpath: &[Point]| {
118 if self_cost == SelfCost::Apply {
119 router.bump_leg(subpath, WIRE_COST);
120 }
121 };
122 let mut path = Vec::new();
123 let mut incoming: Option<Direction> = None;
124 let Some(&first_id) = wp_ids.first() else {
125 let (subpath, _) = route_leg_preferring_straight(router, start, end, incoming);
127 bump(router, &subpath);
128 return subpath
129 .into_iter()
130 .map(|pos| TaggedPoint {
131 pos,
132 segment: SegmentKind::StartToEnd,
133 })
134 .collect();
135 };
136
137 let (subpath, outgoing) =
138 route_leg_preferring_straight(router, start, wp_positions[&first_id], incoming);
139 incoming = outgoing;
140 bump(router, &subpath);
141 path.extend(subpath.into_iter().map(|pos| TaggedPoint {
142 pos,
143 segment: SegmentKind::StartToWaypoint(first_id),
144 }));
145
146 for w in wp_ids.windows(2) {
147 let (a_id, b_id) = (w[0], w[1]);
148 let (subpath, outgoing) = route_leg_preferring_straight(
149 router,
150 wp_positions[&a_id],
151 wp_positions[&b_id],
152 incoming,
153 );
154 incoming = outgoing;
155 bump(router, &subpath);
156 path.extend(subpath.into_iter().map(|pos| TaggedPoint {
157 pos,
158 segment: SegmentKind::WaypointToWaypoint(a_id, b_id),
159 }));
160 }
161
162 let last_id = wp_ids.last().copied().unwrap_or(first_id);
163 let (subpath, _) =
164 route_leg_preferring_straight(router, wp_positions[&last_id], end, incoming);
165 bump(router, &subpath);
166 path.extend(subpath.into_iter().map(|pos| TaggedPoint {
167 pos,
168 segment: SegmentKind::WaypointToEnd(last_id),
169 }));
170 path
171 }
172}
173
174pub struct FixedLegs {
179 path: Vec<TaggedPoint>,
182 tail: Point,
184 tail_wp: Option<PathOrdinal>,
186 incoming: Option<Direction>,
189}
190
191pub fn route_fixed_legs(
195 router: &mut ClosedRouter,
196 wp_positions: &BTreeMap<PathOrdinal, Point>,
197 start: Point,
198 wp_ids: &[PathOrdinal],
199) -> FixedLegs {
200 let mut path = Vec::new();
201 let mut incoming: Option<Direction> = None;
202 let Some(&first_id) = wp_ids.first() else {
203 return FixedLegs {
204 path,
205 tail: start,
206 tail_wp: None,
207 incoming,
208 };
209 };
210 let (subpath, outgoing) =
211 route_leg_preferring_straight(router, start, wp_positions[&first_id], incoming);
212 incoming = outgoing;
213 path.extend(subpath.into_iter().map(|pos| TaggedPoint {
214 pos,
215 segment: SegmentKind::StartToWaypoint(first_id),
216 }));
217 for w in wp_ids.windows(2) {
218 let (a_id, b_id) = (w[0], w[1]);
219 let (subpath, outgoing) = route_leg_preferring_straight(
220 router,
221 wp_positions[&a_id],
222 wp_positions[&b_id],
223 incoming,
224 );
225 incoming = outgoing;
226 path.extend(subpath.into_iter().map(|pos| TaggedPoint {
227 pos,
228 segment: SegmentKind::WaypointToWaypoint(a_id, b_id),
229 }));
230 }
231 let last_id = wp_ids.last().copied().unwrap_or(first_id);
232 FixedLegs {
233 path,
234 tail: wp_positions[&last_id],
235 tail_wp: Some(last_id),
236 incoming,
237 }
238}
239
240pub fn route_to_head(
245 router: &mut ClosedRouter,
246 fixed: &FixedLegs,
247 head: Point,
248) -> Vec<TaggedPoint> {
249 let (subpath, _) = route_leg_preferring_straight(router, fixed.tail, head, fixed.incoming);
250 let segment = match fixed.tail_wp {
251 Some(id) => SegmentKind::WaypointToEnd(id),
252 None => SegmentKind::StartToEnd,
253 };
254 let mut path = fixed.path.clone();
255 path.extend(subpath.into_iter().map(|pos| TaggedPoint { segment, pos }));
256 path
257}
258
259pub fn add_route_cost<'a>(
262 router: &mut ClosedRouter,
263 edges: impl Iterator<Item = &'a RouteEdge>,
264 cost: Cost,
265) {
266 for edge in edges {
267 router.add_wire_cost(edge.start.into(), edge.end.into(), cost);
268 }
269}
270
271pub fn route_edges_blocked<'a>(
274 router: &ClosedRouter,
275 mut edges: impl Iterator<Item = &'a RouteEdge>,
276) -> bool {
277 edges.any(|edge| router.is_wire_blocked(edge.start.into(), edge.end.into()))
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use blockworx_router::RouterNGBuilder;
284 use blockworx_router::point::point;
285
286 #[test]
287 fn a_legal_straight_leg_is_taken_verbatim() {
288 let a = point(0, 0);
292 let b = point(10, 0);
293 let mut builder = RouterNGBuilder::default();
294 builder.add_seed_point(a);
295 builder.add_seed_point(b);
296 let mut router = builder.build_closed();
297
298 let (path, dir) = route_leg_preferring_straight(&mut router, a, b, None);
299 assert_eq!(path, vec![a, b], "a legal straight leg is taken verbatim");
300 assert_eq!(dir, Some(Direction::East));
301 }
302
303 #[test]
304 fn a_hugging_straight_leg_routes_around_the_block() {
305 let mut builder = RouterNGBuilder::default();
309 builder.add_block(point(0, 0), point(10, 10));
310 let a = point(11, 2);
311 let b = point(11, 8);
312 builder.add_seed_point(a);
313 builder.add_seed_point(b);
314 let mut router = builder.build_closed();
315
316 let (path, _) = route_leg_preferring_straight(&mut router, a, b, None);
317 assert_ne!(
318 path,
319 vec![a, b],
320 "a hugging straight leg must not stay verbatim"
321 );
322 assert!(path.len() > 2, "the leg bowed out around the block");
323 }
324}