blockworx/derived/
route.rs1use egui::{Pos2, Vec2};
8
9use crate::document::{GridPos, LinearDistance, RouteDirection, RouteEdge};
10use crate::grid::LINE_RADIUS;
11use crate::store::{EdgeId, IdMap, IdMapExt};
12use crate::widget::edge::RouteEdgeExt;
13
14#[derive(Clone, Copy, PartialEq, Debug)]
23pub struct Crossing {
24 pub pos: GridPos,
25 pub orientation: RouteDirection,
26}
27
28#[derive(Clone, Copy, Debug)]
30pub struct LocAndDirection {
31 pub location: Pos2,
32 pub direction: RouteDirection,
33}
34
35#[derive(Default, Debug, Clone, PartialEq)]
36pub struct RouteGeometry {
37 pub edges: IdMap<EdgeId, RouteEdge>,
38 pub start_pos: GridPos,
39 pub end_pos: GridPos,
40 pub crossings: Vec<Crossing>,
41}
42
43impl RouteGeometry {
44 pub fn edge(&self, edge_index: EdgeId) -> Option<&RouteEdge> {
45 self.edges.get(&edge_index)
46 }
47
48 pub fn iter_edges(&self) -> impl Iterator<Item = (EdgeId, &RouteEdge)> {
49 self.edges.iter().map(|(&k, v)| (k, v))
50 }
51
52 pub fn start_pos(&self) -> Pos2 {
53 self.start_pos.into()
54 }
55
56 pub fn end_pos(&self) -> Pos2 {
57 self.end_pos.into()
58 }
59
60 pub fn points(&self) -> Vec<Pos2> {
63 let mut points: Vec<Pos2> = Vec::new();
64 points.push(self.start_pos.into());
65 for (_, edge) in self.iter_edges() {
66 points.push(edge.end.into());
67 }
68 points
69 }
70
71 pub fn hovered_corner(&self, hover_pos: Pos2) -> Option<(EdgeId, EdgeId)> {
72 self.edges.windows(2).find_map(|edges| {
73 let (edge_id1, edge1) = edges[0];
74 let (edge_id2, edge2) = edges[1];
75 if Pos2::from(edge1.end).distance(hover_pos) <= LINE_RADIUS
76 && edge1.direction() != edge2.direction()
77 {
78 Some((edge_id1, edge_id2))
79 } else {
80 None
81 }
82 })
83 }
84
85 pub fn hovered_edge(&self, hover_pos: Pos2) -> Option<EdgeId> {
86 self.iter_edges().find_map(|(eid, edge)| {
87 if edge.distance(hover_pos).1 <= LINE_RADIUS
88 && Pos2::from(edge.start).distance(hover_pos) > LINE_RADIUS
89 && Pos2::from(edge.end).distance(hover_pos) > LINE_RADIUS
90 {
91 Some(eid)
92 } else {
93 None
94 }
95 })
96 }
97
98 pub fn hovered_edge_distance(&self, hover_pos: Pos2) -> Option<f32> {
105 if self.start_pos().distance(hover_pos) <= LINE_RADIUS
110 || self.end_pos().distance(hover_pos) <= LINE_RADIUS
111 {
112 return None;
113 }
114 self.iter_edges()
115 .filter_map(|(_, edge)| {
116 let perp = edge.distance(hover_pos).1;
117 (perp <= LINE_RADIUS).then_some(perp)
118 })
119 .min_by(f32::total_cmp)
120 }
121
122 pub fn map_linear_distance_to_position(
126 &self,
127 linear_distance: LinearDistance,
128 ) -> LocAndDirection {
129 let mut distance: f32 = linear_distance.into();
130 for (_, edge) in self.iter_edges() {
131 if edge.length() < distance {
132 distance -= edge.length();
133 } else {
134 let frac = distance / edge.length();
135 let start: Pos2 = edge.start.into();
136 let end: Pos2 = edge.end.into();
137 return LocAndDirection {
138 location: start + frac * (end - start),
139 direction: edge.direction(),
140 };
141 }
142 }
143 LocAndDirection {
144 location: self.end_pos.into(),
145 direction: RouteDirection::Horizontal,
146 }
147 }
148
149 pub fn slide_along(&self, from: LinearDistance, delta: Vec2) -> LinearDistance {
152 let anchor = self.map_linear_distance_to_position(from).location;
153 self.distance_along(anchor + delta)
154 }
155
156 pub fn distance_along(&self, pos: Pos2) -> LinearDistance {
158 let mut min_distance = f32::INFINITY;
159 let mut accum_linear_distance = LinearDistance::from(0.0_f64);
160 let mut min_linear_distance = LinearDistance::from(0.0_f64);
161 for (_, edge) in self.iter_edges() {
162 let (lin_distance, distance_to_point) = edge.distance(pos);
163 if distance_to_point < min_distance {
164 min_distance = distance_to_point;
165 min_linear_distance = accum_linear_distance + lin_distance;
166 }
167 accum_linear_distance += edge.length();
168 }
169 min_linear_distance
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::grid::GRID_SIZE;
177 use egui::vec2;
178
179 fn horizontal_wire() -> RouteGeometry {
181 let start = GridPos { x: 0, y: 0 };
182 let end = GridPos { x: 10, y: 0 };
183 let mut edges: IdMap<EdgeId, RouteEdge> = IdMap::default();
184 edges.insert_value(RouteEdge { start, end });
185 RouteGeometry {
186 edges,
187 start_pos: start,
188 end_pos: end,
189 crossings: Vec::new(),
190 }
191 }
192
193 #[test]
194 fn sliding_along_the_wire_advances_by_the_delta() {
195 let wire = horizontal_wire();
196 assert_eq!(wire.iter_edges().count(), 1, "one segment to slide along");
197 assert_eq!(
198 wire.start_pos().y,
199 wire.end_pos().y,
200 "the segment is horizontal"
201 );
202 let span = wire.end_pos().x - wire.start_pos().x;
203 assert!(
204 span > 5.0 * GRID_SIZE,
205 "long enough to slide two cells from three cells in without clamping: {span}"
206 );
207
208 let from = LinearDistance::from(3.0 * GRID_SIZE);
209 let slid = wire.slide_along(from, vec2(2.0 * GRID_SIZE, 0.0));
210
211 assert!(
212 (f32::from(slid) - 5.0 * GRID_SIZE).abs() < 0.01,
213 "sliding {} advanced to {}, expected {}",
214 2.0 * GRID_SIZE,
215 f32::from(slid),
216 5.0 * GRID_SIZE
217 );
218 }
219
220 #[test]
221 fn a_perpendicular_slide_reprojects_onto_the_wire() {
222 let wire = horizontal_wire();
223 let from = LinearDistance::from(3.0 * GRID_SIZE);
224 let anchor = wire.map_linear_distance_to_position(from).location;
225 assert!(
226 (anchor.y - wire.start_pos().y).abs() < 0.01,
227 "the starting anchor sits on the wire"
228 );
229
230 let slid = wire.slide_along(from, vec2(0.0, 4.0 * GRID_SIZE));
231 let landed = wire.map_linear_distance_to_position(slid).location;
232
233 assert!(
234 (landed.y - wire.start_pos().y).abs() < 0.01,
235 "an off-wire delta re-projects back onto the wire: {landed:?}"
236 );
237 assert!(
238 (landed.x - anchor.x).abs() < 0.01,
239 "and does not move along it: {} -> {}",
240 anchor.x,
241 landed.x
242 );
243 }
244}