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