bus.rs
1 use kurbo::{Affine, BezPath}; 2 3 use linesweeper::topology::{Topology, WindingNumber}; 4 5 #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] 6 struct Windings { 7 main: i32, 8 cutout: i32, 9 modifier: i32, 10 } 11 12 impl std::ops::Add for Windings { 13 type Output = Windings; 14 15 fn add(self, rhs: Self) -> Self::Output { 16 Self { 17 main: self.main + rhs.main, 18 cutout: self.cutout + rhs.cutout, 19 modifier: self.modifier + rhs.modifier, 20 } 21 } 22 } 23 24 impl std::ops::AddAssign for Windings { 25 fn add_assign(&mut self, rhs: Self) { 26 *self = *self + rhs 27 } 28 } 29 30 impl WindingNumber for Windings { 31 type Tag = Tag; 32 33 fn single(tag: Self::Tag, positive: bool) -> Self { 34 let sign = if positive { 1 } else { -1 }; 35 match tag { 36 Tag::Main => Self { 37 main: sign, 38 ..Default::default() 39 }, 40 Tag::Cutout => Self { 41 cutout: sign, 42 ..Default::default() 43 }, 44 Tag::Modifier => Self { 45 modifier: sign, 46 ..Default::default() 47 }, 48 } 49 } 50 51 fn of_tag(&self, tag: Self::Tag) -> Self { 52 match tag { 53 Tag::Main => Self { 54 main: self.main, 55 ..Default::default() 56 }, 57 Tag::Cutout => Self { 58 cutout: self.cutout, 59 ..Default::default() 60 }, 61 Tag::Modifier => Self { 62 modifier: self.modifier, 63 ..Default::default() 64 }, 65 } 66 } 67 } 68 69 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 70 enum Tag { 71 Main, 72 Cutout, 73 Modifier, 74 } 75 76 pub fn main() -> anyhow::Result<()> { 77 let bus = "M0,80 C0,35.8 35.8,0 80,0 L432,0 C476.2,0 512,35.8 512,80 L512,368 C512,394.2 499.4,417.4 480,432 L480,480 C480,497.7 465.7,512 448,512 L416,512 C398.3,512 384,497.7 384,480 L384,448 L128,448 L128,480 C128,497.7 113.7,512 96,512 L64,512 C46.3,512 32,497.7 32,480 L32,432 C12.6,417.4 0,394.2 0,368 L0,80 Z M129.9,152.2 L112,224 L400,224 L382.1,152.2 C378.5,138 365.7,128 351,128 L161,128 C146.3,128 133.5,138 130,152.2 Z M128,320 A32 32 0 1 0 64,320 A32 32 0 1 0 128,320 Z M416,352 A32 32 0 1 0 416,288 A32 32 0 1 0 416,352 Z"; 78 let cutout = "M320 512V266.8C288.1 221.6 235.5 192 176 192C78.8 192 0 270.8 0 368c0 59.5 29.6 112.1 74.8 144H320z"; 79 let modifier = "M144 512c79.5 0 144-64.5 144-144s-64.5-144-144-144S0 288.5 0 368s64.5 144 144 144zm67.3-164.7l-72 72c-6.2 6.2-16.4 6.2-22.6 0l-40-40c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0L128 385.4l60.7-60.7c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6z"; 80 81 let bus = BezPath::from_svg(bus).unwrap(); 82 let cutout = Affine::translate((193.0, 0.0)) * BezPath::from_svg(cutout).unwrap(); 83 let modifier = Affine::translate((224.0, 0.0)) * BezPath::from_svg(modifier).unwrap(); 84 85 let eps = 1e-5; 86 let top = Topology::<Windings>::from_paths( 87 [ 88 (&bus, Tag::Main), 89 (&cutout, Tag::Cutout), 90 (&modifier, Tag::Modifier), 91 ], 92 eps, 93 ) 94 .unwrap(); 95 let bbox = top.bounding_box(); 96 let min_x = bbox.min_x(); 97 let min_y = bbox.min_y(); 98 let max_x = bbox.max_x(); 99 let max_y = bbox.max_y(); 100 let pad = 1.0 + eps; 101 let one_width = max_x - min_x + 2.0 * pad; 102 let one_height = max_y - min_y + 2.0 * pad; 103 let mut doc = svg::Document::new().set( 104 "viewBox", 105 (min_x - pad, min_y - pad, one_width * 3.0, one_height * 2.0), 106 ); 107 108 let contours = top.contours(|w| { 109 let inside = |winding| winding != 0; 110 111 (inside(w.main) && !inside(w.cutout)) || inside(w.modifier) 112 }); 113 114 for group in contours.grouped() { 115 let mut data = svg::node::element::path::Data::new(); 116 117 for contour_idx in group { 118 let path = &contours[contour_idx].path; 119 for el in path.iter() { 120 data = match el { 121 kurbo::PathEl::MoveTo(p) => data.move_to((p.x, p.y)), 122 kurbo::PathEl::LineTo(p) => data.line_to((p.x, p.y)), 123 kurbo::PathEl::QuadTo(p0, p1) => { 124 data.quadratic_curve_to(((p0.x, p0.y), (p1.x, p1.y))) 125 } 126 kurbo::PathEl::CurveTo(p0, p1, p2) => { 127 data.cubic_curve_to(((p0.x, p0.y), (p1.x, p1.y), (p2.x, p2.y))) 128 } 129 kurbo::PathEl::ClosePath => data.close(), 130 }; 131 } 132 } 133 let path = svg::node::element::Path::new() 134 .set("d", data) 135 .set("stroke", "black") 136 .set("stroke-linecap", "round") 137 .set("stroke-linejoin", "round"); 138 doc = doc.add(path); 139 } 140 141 svg::save("out.svg", &doc)?; 142 143 Ok(()) 144 }