use std::path::PathBuf;
use clap::Parser;
use linesweeper::{boolean_op, num::CheapOrderedFloat, Point};
type Float = CheapOrderedFloat;
#[derive(Copy, Clone, Debug, clap::ValueEnum)]
enum Example {
Checkerboard,
SlantedCheckerboard,
Slanties,
}
#[derive(Parser)]
struct Cli {
input: PathBuf,
output: PathBuf,
#[arg(long)]
non_zero: bool,
#[arg(long)]
epsilon: Option<f64>,
}
pub fn main() -> anyhow::Result<()> {
let args = Cli::parse();
let base = std::fs::read_to_string(args.input.join("base.json"))?;
let cutout = std::fs::read_to_string(args.input.join("cutout.json"))?;
let modifier = std::fs::read_to_string(args.input.join("modifier.json"))?;
let base_contours: Vec<Vec<Point<Float>>> = serde_json::from_str(&base)?;
let cutout_contours: Vec<Vec<Point<Float>>> = serde_json::from_str(&cutout)?;
let modifier_contours: Vec<Vec<Point<Float>>> = serde_json::from_str(&modifier)?;
let convert_contours = |cs: &[Vec<Point<Float>>]| {
cs.iter()
.map(|ps| {
ps.iter()
.map(|Point { x, y }| (x.into_inner(), y.into_inner()))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
};
let base_contours = convert_contours(&base_contours);
let cutout_contours = convert_contours(&cutout_contours);
let modifier_contours = convert_contours(&modifier_contours);
let sub = boolean_op(
&base_contours,
&cutout_contours,
linesweeper::FillRule::EvenOdd,
linesweeper::BooleanOp::Difference,
)
.unwrap();
let sub_contours = sub.contours().map(|c| c.points.clone()).collect::<Vec<_>>();
let sub = convert_contours(&sub_contours);
let result = boolean_op(
&sub,
&modifier_contours,
linesweeper::FillRule::EvenOdd,
linesweeper::BooleanOp::Union,
)
.unwrap();
let (xs, ys): (Vec<_>, Vec<_>) = result
.contours()
.flat_map(|c| &c.points)
.map(|p| (p.x, p.y))
.unzip();
let min_x = xs.iter().min().unwrap().into_inner();
let max_x = xs.iter().max().unwrap().into_inner();
let min_y = ys.iter().min().unwrap().into_inner();
let max_y = ys.iter().max().unwrap().into_inner();
let pad = 1.0;
let one_width = max_x - min_x + 2.0 * pad;
let one_height = max_y - min_y + 2.0 * pad;
let stroke_width = (max_y - min_y).max(max_x - max_y) / 512.0;
let mut doc = svg::Document::new().set(
"viewBox",
(min_x - pad, min_y - pad, one_width * 3.0, one_height * 2.0),
);
let colors = [
"#005F73", "#0A9396", "#94D2BD", "#E9D8A6", "#EE9B00", "#CA6702", "#BB3E03", "#AE2012",
"#9B2226",
];
let mut color_idx = 0;
for group in result.grouped() {
let mut data = svg::node::element::path::Data::new();
for contour_idx in group {
let mut contour = result[contour_idx].points.iter().cloned();
let Some(p) = contour.next() else {
continue;
};
let (x, y) = (p.x.into_inner(), p.y.into_inner());
data = data.move_to((x, y));
for p in contour {
let (x, y) = (p.x.into_inner(), p.y.into_inner());
data = data.line_to((x, y));
}
data = data.close();
}
let path = svg::node::element::Path::new()
.set("d", data)
.set("stroke", "black")
.set("stroke-width", stroke_width)
.set("stroke-linecap", "round")
.set("stroke-linejoin", "round")
.set("fill", colors[color_idx]);
doc = doc.add(path);
color_idx = (color_idx + 1) % colors.len();
}
svg::save(&args.output, &doc)?;
Ok(())
}