Skip to main content

blockworx_router/
block.rs

1use crate::{
2    coord::{CoordX, CoordY},
3    interval_overlap,
4    point::{Point, point},
5};
6
7/// The 1-cell clearance that pins occupy just outside a block edge; wires route
8/// beyond it (the moat/channels start a further cell out). A straight wire may
9/// run alongside an edge no closer than this without being judged to hug.
10pub const ROUTE_GUTTER: i32 = 1;
11
12/// How many moat lanes a block is ringed with.
13pub const MOAT_LANES: i32 = 5;
14
15/// How far outside its edges a block reaches into the lattice. The moat is
16/// seeded a lane at a time at `distance + 2` cells out, so the outermost lane —
17/// and with it the last channel this block trims or creates — sits here.
18///
19/// Anything routing within this of a block is routing in a channel that block
20/// shaped, and so is geometry that block's movement can change.
21pub const MOAT_REACH: i32 = (MOAT_LANES - 1) + 2;
22
23// A blocked rectangle - inclusive of the edges.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub struct Block {
26    pub top_left: Point,
27    pub bottom_right: Point,
28}
29
30impl Block {
31    #[must_use]
32    pub fn expand_x(&self, delta_x: impl Into<CoordX>) -> Self {
33        let delta_x: CoordX = delta_x.into();
34        Block {
35            top_left: point(self.top_left.x - delta_x, self.top_left.y),
36            bottom_right: point(self.bottom_right.x + delta_x, self.bottom_right.y),
37        }
38    }
39    #[must_use]
40    pub fn expand_y(&self, delta_y: impl Into<CoordY>) -> Self {
41        let delta_y: CoordY = delta_y.into();
42        Block {
43            top_left: point(self.top_left.x, self.top_left.y - delta_y),
44            bottom_right: point(self.bottom_right.x, self.bottom_right.y + delta_y),
45        }
46    }
47    pub fn spans_y(&self, y: CoordY) -> bool {
48        self.top_left.y <= y && self.bottom_right.y >= y
49    }
50    pub fn spans_x(&self, x: CoordX) -> bool {
51        self.top_left.x <= x && self.bottom_right.x >= x
52    }
53    pub fn is_left_of(&self, x: CoordX) -> bool {
54        self.bottom_right.x < x
55    }
56    pub fn is_right_of(&self, x: CoordX) -> bool {
57        self.top_left.x > x
58    }
59    pub fn is_above(&self, y: CoordY) -> bool {
60        self.bottom_right.y < y
61    }
62    pub fn is_below(&self, y: CoordY) -> bool {
63        self.top_left.y > y
64    }
65    pub fn contains(&self, point: Point) -> bool {
66        self.spans_x(point.x) && self.spans_y(point.y)
67    }
68    pub fn intersects_edge(
69        &self,
70        start_point: impl Into<Point>,
71        end_point: impl Into<Point>,
72    ) -> bool {
73        self.edge_crossing(start_point, end_point).is_some()
74    }
75
76    /// The part of the straight wire `a → b` that runs through this block, or
77    /// `None` when it passes clear — what [`Self::intersects_edge`] refuses.
78    /// Along the wire the overlap must have length, so a wire ending on an edge
79    /// is clear; across it the edges count, so a wire running along one is not.
80    pub fn edge_crossing(
81        &self,
82        a: impl Into<Point>,
83        b: impl Into<Point>,
84    ) -> Option<(Point, Point)> {
85        let (a, b): (Point, Point) = (a.into(), b.into());
86        if a.y == b.y {
87            let (lo, hi) = (a.x.min(b.x), a.x.max(b.x));
88            (self.spans_y(a.y) && interval_overlap(lo, hi, self.top_left.x, self.bottom_right.x))
89                .then(|| {
90                    (
91                        Point {
92                            x: lo.max(self.top_left.x),
93                            y: a.y,
94                        },
95                        Point {
96                            x: hi.min(self.bottom_right.x),
97                            y: a.y,
98                        },
99                    )
100                })
101        } else {
102            let (lo, hi) = (a.y.min(b.y), a.y.max(b.y));
103            (self.spans_x(a.x) && interval_overlap(lo, hi, self.top_left.y, self.bottom_right.y))
104                .then(|| {
105                    (
106                        Point {
107                            x: a.x,
108                            y: lo.max(self.top_left.y),
109                        },
110                        Point {
111                            x: a.x,
112                            y: hi.min(self.bottom_right.y),
113                        },
114                    )
115                })
116        }
117    }
118
119    /// The straight wire `a → b` runs alongside one of this block's edges within
120    /// `gutter` cells — parallel to the edge and overlapping its span — rather than
121    /// merely leaving a pin perpendicular to it (a stub touches the gutter only at
122    /// its anchored endpoint). Such a wire hugs the block and should route around.
123    pub fn hugs_wire(&self, a: impl Into<Point>, b: impl Into<Point>, gutter: i32) -> bool {
124        let (a, b) = (a.into(), b.into());
125        if a.x == b.x {
126            let near_edge = (a.x - self.top_left.x).abs() <= gutter
127                || (a.x - self.bottom_right.x).abs() <= gutter;
128            let (lo, hi) = (a.y.min(b.y), a.y.max(b.y));
129            near_edge && lo < self.bottom_right.y && self.top_left.y < hi
130        } else if a.y == b.y {
131            let near_edge = (a.y - self.top_left.y).abs() <= gutter
132                || (a.y - self.bottom_right.y).abs() <= gutter;
133            let (lo, hi) = (a.x.min(b.x), a.x.max(b.x));
134            near_edge && lo < self.bottom_right.x && self.top_left.x < hi
135        } else {
136            false
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    fn block_0_0_to_10_10() -> Block {
146        Block {
147            top_left: point(0, 0),
148            bottom_right: point(10, 10),
149        }
150    }
151
152    #[test]
153    fn vertical_same_side_hug() {
154        let block = block_0_0_to_10_10();
155        assert!(block.hugs_wire(point(11, 2), point(11, 8), ROUTE_GUTTER));
156    }
157
158    #[test]
159    fn wide_offset_vertical_does_not_hug() {
160        let block = block_0_0_to_10_10();
161        assert!(!block.hugs_wire(point(12, 2), point(12, 8), ROUTE_GUTTER));
162    }
163
164    #[test]
165    fn horizontal_east_pin_leave_from_middle_slot_does_not_hug() {
166        let block = block_0_0_to_10_10();
167        assert!(!block.hugs_wire(point(11, 5), point(20, 5), ROUTE_GUTTER));
168    }
169
170    #[test]
171    fn horizontal_east_pin_leave_from_top_slot_does_not_hug() {
172        let block = block_0_0_to_10_10();
173        assert!(!block.hugs_wire(point(11, 1), point(20, 1), ROUTE_GUTTER));
174    }
175
176    #[test]
177    fn horizontal_hug_above_top_edge() {
178        let block = block_0_0_to_10_10();
179        assert!(block.hugs_wire(point(2, -1), point(8, -1), ROUTE_GUTTER));
180    }
181
182    #[test]
183    fn diagonal_never_hugs() {
184        let block = block_0_0_to_10_10();
185        assert!(!block.hugs_wire(point(11, 2), point(20, 8), ROUTE_GUTTER));
186    }
187}