winding-numbers.rs
1 use std::path::PathBuf; 2 3 use clap::Parser; 4 5 use linesweeper::topology::Topology; 6 use linesweeper_util::svg_to_bezpaths; 7 8 #[derive(Parser)] 9 struct Args { 10 input: PathBuf, 11 output: PathBuf, 12 13 #[arg(long)] 14 epsilon: Option<f64>, 15 } 16 17 pub fn main() -> anyhow::Result<()> { 18 let args = Args::parse(); 19 20 let input = std::fs::read_to_string(&args.input)?; 21 let tree = usvg::Tree::from_str(&input, &usvg::Options::default())?; 22 let contours = svg_to_bezpaths(&tree); 23 24 let eps = args.epsilon.unwrap_or(0.1); 25 let top = 26 Topology::from_paths_binary(&contours[0], &contours[1..].iter().flatten().collect(), eps)?; 27 28 let bbox = top.bounding_box(); 29 let min_x = bbox.min_x(); 30 let min_y = bbox.min_y(); 31 let max_x = bbox.max_x(); 32 let max_y = bbox.max_y(); 33 let pad = 8.0 + eps; 34 let stroke_width = (max_y - min_y).max(max_x - max_y) / 512.0; 35 let dot_radius = stroke_width * 1.5; 36 let mut document = svg::Document::new().set( 37 "viewBox", 38 ( 39 min_x - pad, 40 min_y - pad, 41 max_x - min_x + 2.0 * pad, 42 max_y - min_y + 2.0 * pad, 43 ), 44 ); 45 46 let text_size = "8px"; 47 48 for seg in top.segment_indices() { 49 let p0 = &top.point(seg.first_half()); 50 let p1 = &top.point(seg.second_half()); 51 let (x0, y0) = (p0.x, p0.y); 52 let (x1, y1) = (p1.x, p1.y); 53 let c = svg::node::element::Circle::new() 54 .set("r", dot_radius) 55 .set("cy", y0) 56 .set("cx", x0) 57 .set("opacity", 0.5) 58 .set("fill", "blue"); 59 document = document.add(c); 60 61 let c = svg::node::element::Circle::new() 62 .set("r", dot_radius) 63 .set("cy", y1) 64 .set("cx", x1) 65 .set("opacity", 0.5) 66 .set("fill", "blue"); 67 document = document.add(c); 68 69 let data = svg::node::element::path::Data::new() 70 .move_to((x0, y0)) 71 .line_to((x1, y1)); 72 let path = svg::node::element::Path::new() 73 .set("stroke", "black") 74 .set("stroke-width", stroke_width / 2.0) 75 .set("stroke-opacity", "0.5") 76 .set("d", data); 77 document = document.add(path); 78 79 let nx = y1 - y0; 80 let ny = x0 - x1; 81 let norm = ((nx * nx) + (ny * ny)).sqrt(); 82 83 let nx = nx / norm * 3.0 * dot_radius; 84 let ny = ny / norm * 3.0 * dot_radius; 85 86 let text = svg::node::element::Text::new(format!( 87 "{:?}", 88 top.winding(seg.first_half()).counter_clockwise 89 )) 90 .set("font-size", text_size) 91 .set("text-anchor", "start") 92 .set("x", (x0 + x1) / 2.0 + nx) 93 .set("y", (y0 + y1) / 2.0 + ny); 94 document = document.add(text); 95 96 let text = 97 svg::node::element::Text::new(format!("{:?}", top.winding(seg.first_half()).clockwise)) 98 .set("font-size", text_size) 99 .set("text-anchor", "end") 100 .set("x", (x0 + x1) / 2.0 - nx) 101 .set("y", (y0 + y1) / 2.0 - ny); 102 document = document.add(text); 103 } 104 105 svg::save(&args.output, &document)?; 106 107 Ok(()) 108 }