diff --git a/src/main.rs b/src/main.rs index f853755..d4d4eca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -130,9 +130,9 @@ fn main() { let container = { let points = vec![ (0.0, -250.0), // - (-500.0, 0.0), // - (0.0, 250.0), // (500.0, 0.0), // + (0.0, 250.0), // + (-500.0, 0.0), // ]; // let points = vec![ @@ -142,17 +142,36 @@ fn main() { // (0.0, 250.0), // // ]; - let mut points = vec![]; - for i in 0_u16..=180 { - let i = f32::from(i); - points.push(( - 30.0 * i, - 800.0 * (8.0 * i / 180.0 * std::f32::consts::PI).sin(), - )); - } - points.push((30.0 * 180.0 / 2.0, 900.0)); - - circle_packing::shapes::Polyline::new(points).unwrap() + // let mut points = vec![]; + // for i in 0_u16..=180 { + // let i = f32::from(i); + // points.push(( + // 30.0 * i, + // 800.0 * (8.0 * i / 180.0 * std::f32::consts::PI).sin(), + // )); + // } + // points.push((30.0 * 180.0 / 2.0, 900.0)); + + let mut poly = circle_packing::shapes::Polyline::new(points).unwrap(); + + let mut hole = circle_packing::shapes::Polyline::new(vec![ + (0.0, -150.0), // + (400.0, 0.0), // + (0.0, 150.0), // + (-400.0, 0.0), // + ]) + .unwrap(); + hole.push_hole( + circle_packing::shapes::Polyline::new(vec![ + (0.0, -50.0), // + (300.0, 0.0), // + (0.0, 50.0), // + (-300.0, 0.0), // + ]) + .unwrap(), + ); + poly.push_hole(hole); + poly }; let mut root = PackShape::new(container); root.color = 1 % settings.palette.len(); diff --git a/src/shapes.rs b/src/shapes.rs index c669a75..cb6d5c8 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -25,6 +25,7 @@ pub struct Circle { #[derive(Clone, Debug)] pub struct Polyline { points: Vec<(f32, f32)>, + holes: Vec, cx: f32, cy: f32, bbox: Bbox, @@ -157,7 +158,7 @@ impl Polyline { } else { let (mut cx, mut cy) = points[0]; let mut bbox = Bbox::new(cx, cy); - for &(x, y) in &points[1..] { + for &(x, y) in points.iter().skip(1) { bbox.expand(x, y); cx += x; cy += y; @@ -169,15 +170,45 @@ impl Polyline { cx, cy, bbox, + holes: vec![], }) } } + + /// as of now, hole must be completely contained in the polyline otherwise + /// it won't be added to the list of holes. This is to ensure the naive area + /// calculation is correct. + // + // TODO: clipping + pub fn push_hole(&mut self, hole: Polyline) -> bool { + let completely_contained = hole.points.iter().all(|&(x, y)| self.sdf(x, y) < 0.0); + if !completely_contained { + return false; + } + + self.holes.push(hole); + true + } + + fn get_d(&self) -> String { + let mut d = format!("M {},{}", self.points[0].0, self.points[0].1); + for &(x, y) in self.points.iter().skip(1) { + d += &format!("L {},{}", x, y); + } + d += "Z"; + + for hole in &self.holes { + d += &hole.get_d(); + } + + d + } } impl Shape for Polyline { fn bbox(&self) -> Bbox { let mut bbox = Bbox::new(self.points[0].0, self.points[0].1); - for &(x, y) in &self.points[1..] { + for &(x, y) in self.points.iter().skip(1) { bbox.expand(x, y); } bbox @@ -216,7 +247,13 @@ impl Shape for Polyline { } } - s * d.sqrt() + let mut d = s * d.sqrt(); + for h in &self.holes { + let dd = h.sdf(x, y); + d = d.max(-dd); + } + + d } fn area(&self) -> f32 { @@ -230,7 +267,9 @@ impl Shape for Polyline { area += x0 * y1 - x1 * y0; } - area.abs() / 2.0 + area = area.abs() / 2.0; + + area - self.holes.iter().map(|h| h.area()).sum::() } fn random_point(&self, rng: &mut R) -> (f32, f32) { @@ -243,16 +282,12 @@ impl Shape for Polyline { } fn write_svg(&self, w: &mut W, fill: &str, stroke: &str) -> io::Result<()> { - let points = self - .points - .iter() - .map(|(x, y)| format!("{},{} ", x, y)) - .collect::(); - writeln!( w, - r#""#, - points, fill, stroke + r#""#, + self.get_d(), + fill, + stroke ) } }