1use crate::geometry::Orientation;
7use crate::shapes::{Curve, Line, Rect};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum EdgeSource {
13 Line,
15 RectTop,
17 RectBottom,
19 RectLeft,
21 RectRight,
23 Curve,
25 Stream,
27 Explicit,
29}
30
31#[derive(Debug, Clone, PartialEq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct Edge {
38 pub x0: f64,
40 pub top: f64,
42 pub x1: f64,
44 pub bottom: f64,
46 pub orientation: Orientation,
48 pub source: EdgeSource,
50}
51
52pub fn edge_from_line(line: &Line) -> Edge {
54 Edge {
55 x0: line.x0,
56 top: line.top,
57 x1: line.x1,
58 bottom: line.bottom,
59 orientation: line.orientation,
60 source: EdgeSource::Line,
61 }
62}
63
64pub fn edges_from_rect(rect: &Rect) -> Vec<Edge> {
66 vec![
67 Edge {
68 x0: rect.x0,
69 top: rect.top,
70 x1: rect.x1,
71 bottom: rect.top,
72 orientation: Orientation::Horizontal,
73 source: EdgeSource::RectTop,
74 },
75 Edge {
76 x0: rect.x0,
77 top: rect.bottom,
78 x1: rect.x1,
79 bottom: rect.bottom,
80 orientation: Orientation::Horizontal,
81 source: EdgeSource::RectBottom,
82 },
83 Edge {
84 x0: rect.x0,
85 top: rect.top,
86 x1: rect.x0,
87 bottom: rect.bottom,
88 orientation: Orientation::Vertical,
89 source: EdgeSource::RectLeft,
90 },
91 Edge {
92 x0: rect.x1,
93 top: rect.top,
94 x1: rect.x1,
95 bottom: rect.bottom,
96 orientation: Orientation::Vertical,
97 source: EdgeSource::RectRight,
98 },
99 ]
100}
101
102const EDGE_AXIS_TOLERANCE: f64 = 1e-6;
104
105fn classify_edge_orientation(x0: f64, y0: f64, x1: f64, y1: f64) -> Orientation {
107 let dx = (x1 - x0).abs();
108 let dy = (y1 - y0).abs();
109 if dy < EDGE_AXIS_TOLERANCE {
110 Orientation::Horizontal
111 } else if dx < EDGE_AXIS_TOLERANCE {
112 Orientation::Vertical
113 } else {
114 Orientation::Diagonal
115 }
116}
117
118pub fn edge_from_curve(curve: &Curve) -> Edge {
120 let (start_x, start_y) = curve.pts[0];
121 let (end_x, end_y) = curve.pts[curve.pts.len() - 1];
122
123 let x0 = start_x.min(end_x);
124 let x1 = start_x.max(end_x);
125 let top = start_y.min(end_y);
126 let bottom = start_y.max(end_y);
127 let orientation = classify_edge_orientation(start_x, start_y, end_x, end_y);
128
129 Edge {
130 x0,
131 top,
132 x1,
133 bottom,
134 orientation,
135 source: EdgeSource::Curve,
136 }
137}
138
139pub fn derive_edges(lines: &[Line], rects: &[Rect], curves: &[Curve]) -> Vec<Edge> {
141 let mut edges = Vec::new();
142
143 for line in lines {
144 edges.push(edge_from_line(line));
145 }
146
147 for rect in rects {
148 edges.extend(edges_from_rect(rect));
149 }
150
151 for curve in curves {
152 edges.push(edge_from_curve(curve));
153 }
154
155 edges
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::painting::Color;
162
163 #[test]
164 fn test_edge_construction_and_field_access() {
165 let edge = Edge {
166 x0: 10.0,
167 top: 20.0,
168 x1: 200.0,
169 bottom: 20.0,
170 orientation: Orientation::Horizontal,
171 source: EdgeSource::Line,
172 };
173 assert_eq!(edge.x0, 10.0);
174 assert_eq!(edge.top, 20.0);
175 assert_eq!(edge.x1, 200.0);
176 assert_eq!(edge.bottom, 20.0);
177 assert_eq!(edge.orientation, Orientation::Horizontal);
178 assert_eq!(edge.source, EdgeSource::Line);
179 }
180
181 #[test]
182 fn test_edge_source_variants() {
183 let sources = [
184 EdgeSource::Line,
185 EdgeSource::RectTop,
186 EdgeSource::RectBottom,
187 EdgeSource::RectLeft,
188 EdgeSource::RectRight,
189 EdgeSource::Curve,
190 ];
191 for source in sources {
193 let copy = source;
194 assert_eq!(source, copy);
195 }
196 }
197
198 fn assert_approx(a: f64, b: f64) {
199 assert!(
200 (a - b).abs() < 1e-6,
201 "expected {b}, got {a}, diff={}",
202 (a - b).abs()
203 );
204 }
205
206 fn make_line(x0: f64, top: f64, x1: f64, bottom: f64, orient: Orientation) -> Line {
207 Line {
208 x0,
209 top,
210 x1,
211 bottom,
212 line_width: 1.0,
213 stroke_color: Color::black(),
214 orientation: orient,
215 }
216 }
217
218 fn make_rect(x0: f64, top: f64, x1: f64, bottom: f64) -> Rect {
219 Rect {
220 x0,
221 top,
222 x1,
223 bottom,
224 line_width: 1.0,
225 stroke: true,
226 fill: false,
227 stroke_color: Color::black(),
228 fill_color: Color::black(),
229 }
230 }
231
232 fn make_curve(pts: Vec<(f64, f64)>) -> Curve {
233 let xs: Vec<f64> = pts.iter().map(|p| p.0).collect();
234 let ys: Vec<f64> = pts.iter().map(|p| p.1).collect();
235 Curve {
236 x0: xs.iter().cloned().fold(f64::INFINITY, f64::min),
237 top: ys.iter().cloned().fold(f64::INFINITY, f64::min),
238 x1: xs.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
239 bottom: ys.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
240 pts,
241 line_width: 1.0,
242 stroke: true,
243 fill: false,
244 stroke_color: Color::black(),
245 fill_color: Color::black(),
246 }
247 }
248
249 #[test]
252 fn test_edge_from_horizontal_line() {
253 let line = make_line(10.0, 50.0, 100.0, 50.0, Orientation::Horizontal);
254 let edge = edge_from_line(&line);
255
256 assert_approx(edge.x0, 10.0);
257 assert_approx(edge.top, 50.0);
258 assert_approx(edge.x1, 100.0);
259 assert_approx(edge.bottom, 50.0);
260 assert_eq!(edge.orientation, Orientation::Horizontal);
261 assert_eq!(edge.source, EdgeSource::Line);
262 }
263
264 #[test]
265 fn test_edge_from_vertical_line() {
266 let line = make_line(50.0, 10.0, 50.0, 200.0, Orientation::Vertical);
267 let edge = edge_from_line(&line);
268
269 assert_approx(edge.x0, 50.0);
270 assert_approx(edge.top, 10.0);
271 assert_approx(edge.x1, 50.0);
272 assert_approx(edge.bottom, 200.0);
273 assert_eq!(edge.orientation, Orientation::Vertical);
274 assert_eq!(edge.source, EdgeSource::Line);
275 }
276
277 #[test]
278 fn test_edge_from_diagonal_line() {
279 let line = make_line(10.0, 20.0, 100.0, 200.0, Orientation::Diagonal);
280 let edge = edge_from_line(&line);
281
282 assert_eq!(edge.orientation, Orientation::Diagonal);
283 assert_eq!(edge.source, EdgeSource::Line);
284 }
285
286 #[test]
289 fn test_edges_from_rect_count() {
290 let rect = make_rect(10.0, 20.0, 110.0, 70.0);
291 let edges = edges_from_rect(&rect);
292 assert_eq!(edges.len(), 4);
293 }
294
295 #[test]
296 fn test_edges_from_rect_top() {
297 let rect = make_rect(10.0, 20.0, 110.0, 70.0);
298 let edges = edges_from_rect(&rect);
299 let top_edge = &edges[0];
300
301 assert_approx(top_edge.x0, 10.0);
302 assert_approx(top_edge.top, 20.0);
303 assert_approx(top_edge.x1, 110.0);
304 assert_approx(top_edge.bottom, 20.0);
305 assert_eq!(top_edge.orientation, Orientation::Horizontal);
306 assert_eq!(top_edge.source, EdgeSource::RectTop);
307 }
308
309 #[test]
310 fn test_edges_from_rect_bottom() {
311 let rect = make_rect(10.0, 20.0, 110.0, 70.0);
312 let edges = edges_from_rect(&rect);
313 let bottom_edge = &edges[1];
314
315 assert_approx(bottom_edge.x0, 10.0);
316 assert_approx(bottom_edge.top, 70.0);
317 assert_approx(bottom_edge.x1, 110.0);
318 assert_approx(bottom_edge.bottom, 70.0);
319 assert_eq!(bottom_edge.orientation, Orientation::Horizontal);
320 assert_eq!(bottom_edge.source, EdgeSource::RectBottom);
321 }
322
323 #[test]
324 fn test_edges_from_rect_left() {
325 let rect = make_rect(10.0, 20.0, 110.0, 70.0);
326 let edges = edges_from_rect(&rect);
327 let left_edge = &edges[2];
328
329 assert_approx(left_edge.x0, 10.0);
330 assert_approx(left_edge.top, 20.0);
331 assert_approx(left_edge.x1, 10.0);
332 assert_approx(left_edge.bottom, 70.0);
333 assert_eq!(left_edge.orientation, Orientation::Vertical);
334 assert_eq!(left_edge.source, EdgeSource::RectLeft);
335 }
336
337 #[test]
338 fn test_edges_from_rect_right() {
339 let rect = make_rect(10.0, 20.0, 110.0, 70.0);
340 let edges = edges_from_rect(&rect);
341 let right_edge = &edges[3];
342
343 assert_approx(right_edge.x0, 110.0);
344 assert_approx(right_edge.top, 20.0);
345 assert_approx(right_edge.x1, 110.0);
346 assert_approx(right_edge.bottom, 70.0);
347 assert_eq!(right_edge.orientation, Orientation::Vertical);
348 assert_eq!(right_edge.source, EdgeSource::RectRight);
349 }
350
351 #[test]
354 fn test_edge_from_curve_horizontal_chord() {
355 let curve = make_curve(vec![
357 (0.0, 100.0),
358 (10.0, 50.0),
359 (90.0, 50.0),
360 (100.0, 100.0),
361 ]);
362 let edge = edge_from_curve(&curve);
363
364 assert_approx(edge.x0, 0.0);
365 assert_approx(edge.x1, 100.0);
366 assert_approx(edge.top, 100.0);
367 assert_approx(edge.bottom, 100.0);
368 assert_eq!(edge.orientation, Orientation::Horizontal);
369 assert_eq!(edge.source, EdgeSource::Curve);
370 }
371
372 #[test]
373 fn test_edge_from_curve_vertical_chord() {
374 let curve = make_curve(vec![
376 (50.0, 0.0),
377 (100.0, 30.0),
378 (100.0, 70.0),
379 (50.0, 100.0),
380 ]);
381 let edge = edge_from_curve(&curve);
382
383 assert_approx(edge.x0, 50.0);
384 assert_approx(edge.x1, 50.0);
385 assert_approx(edge.top, 0.0);
386 assert_approx(edge.bottom, 100.0);
387 assert_eq!(edge.orientation, Orientation::Vertical);
388 assert_eq!(edge.source, EdgeSource::Curve);
389 }
390
391 #[test]
392 fn test_edge_from_curve_diagonal_chord() {
393 let curve = make_curve(vec![(0.0, 0.0), (30.0, 70.0), (70.0, 30.0), (100.0, 100.0)]);
395 let edge = edge_from_curve(&curve);
396
397 assert_approx(edge.x0, 0.0);
398 assert_approx(edge.x1, 100.0);
399 assert_approx(edge.top, 0.0);
400 assert_approx(edge.bottom, 100.0);
401 assert_eq!(edge.orientation, Orientation::Diagonal);
402 assert_eq!(edge.source, EdgeSource::Curve);
403 }
404
405 #[test]
408 fn test_derive_edges_empty_inputs() {
409 let edges = derive_edges(&[], &[], &[]);
410 assert!(edges.is_empty());
411 }
412
413 #[test]
414 fn test_derive_edges_lines_only() {
415 let lines = vec![
416 make_line(0.0, 50.0, 100.0, 50.0, Orientation::Horizontal),
417 make_line(50.0, 0.0, 50.0, 100.0, Orientation::Vertical),
418 ];
419 let edges = derive_edges(&lines, &[], &[]);
420 assert_eq!(edges.len(), 2);
421 assert_eq!(edges[0].source, EdgeSource::Line);
422 assert_eq!(edges[1].source, EdgeSource::Line);
423 }
424
425 #[test]
426 fn test_derive_edges_rects_only() {
427 let rects = vec![make_rect(10.0, 20.0, 110.0, 70.0)];
428 let edges = derive_edges(&[], &rects, &[]);
429 assert_eq!(edges.len(), 4); }
431
432 #[test]
433 fn test_derive_edges_curves_only() {
434 let curves = vec![make_curve(vec![
435 (0.0, 100.0),
436 (10.0, 50.0),
437 (90.0, 50.0),
438 (100.0, 100.0),
439 ])];
440 let edges = derive_edges(&[], &[], &curves);
441 assert_eq!(edges.len(), 1);
442 assert_eq!(edges[0].source, EdgeSource::Curve);
443 }
444
445 #[test]
446 fn test_derive_edges_mixed() {
447 let lines = vec![make_line(0.0, 50.0, 100.0, 50.0, Orientation::Horizontal)];
448 let rects = vec![make_rect(10.0, 20.0, 110.0, 70.0)];
449 let curves = vec![make_curve(vec![
450 (0.0, 100.0),
451 (10.0, 50.0),
452 (90.0, 50.0),
453 (100.0, 100.0),
454 ])];
455 let edges = derive_edges(&lines, &rects, &curves);
456 assert_eq!(edges.len(), 6);
458 assert_eq!(edges[0].source, EdgeSource::Line);
459 assert_eq!(edges[1].source, EdgeSource::RectTop);
460 assert_eq!(edges[4].source, EdgeSource::RectRight);
461 assert_eq!(edges[5].source, EdgeSource::Curve);
462 }
463
464 #[test]
465 fn test_derive_edges_multiple_rects() {
466 let rects = vec![
467 make_rect(10.0, 20.0, 110.0, 70.0),
468 make_rect(200.0, 300.0, 350.0, 400.0),
469 ];
470 let edges = derive_edges(&[], &rects, &[]);
471 assert_eq!(edges.len(), 8); }
473}