use delaunator::{next_halfedge, Point, Triangulation, EMPTY}; use std::{env, fs::File, io::Write}; const CANVAS_SIZE: f64 = 800.; const POINT_SIZE: usize = 4; const LINE_WIDTH: usize = 1; const HULL_COLOR: &str = "green"; const LINE_COLOR: &str = "blue"; const POINT_COLOR: &str = "black"; const HULL_POINT_COLOR: &str = "red"; /// Takes the first argument and use as path to load points data. If no argument provided, loads one of the test fixtures data file /// Example: cargo run --example svg -- tests/fixtures/issue24.json fn main() -> std::io::Result<()> { // load points from file let default_path = "tests/fixtures/robust4.json".to_string(); let args = env::args().collect::>(); let path = args.get(1).unwrap_or(&default_path); let points: Vec = serde_json::from_reader::<_, Vec<(f64, f64)>>(File::open(path)?)? .iter() .map(|p| Point { x: p.0, y: p.1 }) .collect(); // triangulate and scale points for display let triangulation = delaunator::triangulate(&points); println!("{:#?}", triangulation); let points = center_and_scale(&points, &triangulation); // generate SVG let contents = format!( r#" {circles} {lines} "#, width = CANVAS_SIZE, height = CANVAS_SIZE, circles = render_point(&points, &triangulation), lines = (0..triangulation.triangles.len()).fold(String::new(), |acc, e| { if e > triangulation.halfedges[e] || triangulation.halfedges[e] == EMPTY { let start = &points[triangulation.triangles[e]]; let end = &points[triangulation.triangles[next_halfedge(e)]]; let color = if triangulation.halfedges[e] == EMPTY { HULL_COLOR } else { LINE_COLOR }; acc + &format!(r#""#, x0 = start.x, y0 = start.y, x1=end.x, y1=end.y, width = LINE_WIDTH, color = color) } else { acc } }) ); File::create("triangulation.svg")?.write_all(contents.as_bytes()) } /// Finds the center point and farthest point from it, then generates a new vector of /// scaled and offset points such that they fit between [0..SIZE] fn center_and_scale(points: &Vec, t: &Triangulation) -> Vec { let center = &points[*t.triangles.get(0).unwrap_or(&0)]; let farthest_distance = points .iter() .map(|p| { let (x, y) = (center.x - p.x, center.y - p.y); x * x + y * y }) .reduce(f64::max) .unwrap() .sqrt(); let scale = CANVAS_SIZE / (farthest_distance * 2.0); let offset = ( (CANVAS_SIZE / 2.0) - (scale * center.x), (CANVAS_SIZE / 2.0) - (scale * center.y), ); points .iter() .map(|p| Point { x: scale * p.x + offset.0, y: scale * p.y + offset.1, }) .collect() } fn render_point(points: &[Point], triangulation: &Triangulation) -> String { let mut circles = points .iter() .enumerate() .fold(String::new(), |acc, (i, p)| { let color = if triangulation.hull.contains(&i) { HULL_POINT_COLOR } else { POINT_COLOR }; acc + &format!( r#""#, x = p.x, y = p.y, size = POINT_SIZE, color = color ) }); // show ids for points if input is relatively small if points.len() < 100 { circles = points.iter().enumerate().fold(circles, |acc, (i, p)| { acc + &format!( r#"{i}"#, i = i, x = p.x + 10., y = p.y - 5. ) }) } circles }