/ examples / winding-numbers.rs
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  }