This repository has no description
1use self::Shape::*;
2use crate::{Containable, Object, Point, Region};
3use anyhow::anyhow;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum LineSegment {
7 Straight(Point),
8 InwardCurve(Point),
9 OutwardCurve(Point),
10}
11
12#[derive(Debug, Clone)]
13pub enum Shape {
14 Polygon(Point, Vec<LineSegment>),
15 Line(Point, Point, f32),
16 CurveOutward(Point, Point, f32),
17 CurveInward(Point, Point, f32),
18 SmallCircle(Point),
19 BigDot(Point),
20 Dot(Point),
21 BigCircle(Point),
22 Text(Point, String, f32),
23 CenteredText(Point, String, f32),
24 // FittedText(Region, String),
25 Rectangle(Point, Point),
26 Image(Region, String),
27 RawSVG {
28 content: String,
29 /// String to search & replace in the SVG content when applying a color fill to the shape (see `Object`). For example, `#c0ffee`.
30 color: String,
31 },
32 // Tiling(Region, Box<Object>),
33 Component {
34 at: Point,
35 size: (usize, usize),
36 objects: Box<Vec<Object>>,
37 },
38}
39
40impl Shape {
41 pub fn translate(&mut self, dx: i32, dy: i32) {
42 match self {
43 Polygon(start, lines) => {
44 start.translate(dx, dy);
45 for line in lines {
46 match line {
47 LineSegment::InwardCurve(anchor)
48 | LineSegment::OutwardCurve(anchor)
49 | LineSegment::Straight(anchor) => {
50 anchor.translate(dx, dy)
51 }
52 }
53 }
54 }
55 Line(start, end, _)
56 | CurveInward(start, end, _)
57 | CurveOutward(start, end, _)
58 | Rectangle(start, end) => {
59 start.translate(dx, dy);
60 end.translate(dx, dy);
61 }
62 Text(anchor, _, _)
63 | CenteredText(anchor, ..)
64 | Dot(anchor)
65 | BigDot(anchor) => anchor.translate(dx, dy),
66 BigCircle(center) | SmallCircle(center) => center.translate(dx, dy),
67 Image(region, ..) => region.translate(dx, dy),
68 Component { at, .. } => at.translate(dx, dy),
69 RawSVG { .. } => {
70 unimplemented!()
71 }
72 }
73 }
74
75 pub fn position(&self) -> Point {
76 match self {
77 Polygon(at, ..)
78 | Line(at, ..)
79 | CurveInward(at, ..)
80 | CurveOutward(at, ..)
81 | Rectangle(at, ..)
82 | Text(at, ..)
83 | CenteredText(at, ..)
84 | Dot(at)
85 | BigDot(at)
86 | BigCircle(at)
87 | SmallCircle(at)
88 | Component { at, .. }
89 | Image(Region { start: at, .. }, ..) => *at,
90 RawSVG { .. } => {
91 unimplemented!()
92 }
93 }
94 }
95
96 pub fn translate_with(&mut self, delta: (i32, i32)) {
97 self.translate(delta.0, delta.1)
98 }
99
100 pub fn teleport(&mut self, x: i32, y: i32) {
101 let (current_x, current_y) = self.region().start.xy::<i32>();
102 let delta_x = x - current_x;
103 let delta_y = y - current_y;
104 self.translate(delta_x, delta_y);
105 }
106
107 pub fn teleport_with(&mut self, position: (i32, i32)) {
108 self.teleport(position.0, position.1)
109 }
110
111 pub fn region(&self) -> Region {
112 match self {
113 Polygon(start, lines) => {
114 let mut region: Region = (start, start).into();
115 for line in lines {
116 match line {
117 LineSegment::InwardCurve(anchor)
118 | LineSegment::OutwardCurve(anchor)
119 | LineSegment::Straight(anchor) => {
120 // println!(
121 // "extending region {} with {}",
122 // region,
123 // Region::from((start, anchor))
124 // );
125 region = *region.max(&(start, anchor).into())
126 }
127 }
128 }
129 // println!("region for {:?} -> {}", self, region);
130 region
131 }
132 Line(s, e, _) | CurveInward(s, e, _) | CurveOutward(s, e, _) => {
133 let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x());
134
135 let region = Region::new(
136 (x1.min(x2), y1.min(y2)),
137 (x1.max(x2), y1.max(y2)),
138 )
139 .map_err(|e| {
140 anyhow!("Could not construct region of {self:?}: {e:?}")
141 })
142 .unwrap();
143
144 region.enlarged(
145 if region.width() > 1 { -1 } else { 0 },
146 if region.height() > 1 { -1 } else { 0 },
147 )
148 }
149 Rectangle(start, end) => {
150 Region::new(*start, *end).unwrap().enlarged(-1, -1)
151 }
152 Text(anchor, _, _)
153 | CenteredText(anchor, ..)
154 | Dot(anchor)
155 | BigDot(anchor)
156 | SmallCircle(anchor) => anchor.region(),
157 BigCircle(center) => center.region(),
158 Image(region, ..) => *region,
159 Component { at, size, .. } => Region::from_topleft(*at, *size)
160 .expect("Invalid region for component"),
161 RawSVG { .. } => {
162 unimplemented!()
163 }
164 }
165 }
166
167 pub fn fillable(&self) -> bool {
168 !matches!(self, Line(..) | CurveInward(..) | CurveOutward(..))
169 }
170
171 pub fn hatchable(&self) -> bool {
172 self.fillable() && !matches!(self, Dot(..))
173 }
174
175 pub fn point_is_on_line(self, point: Point) -> bool {
176 match (&self, point) {
177 (Line(s, e, _), Point::Corner(x, y)) => {
178 if !self.region().contains(&point) {
179 return false;
180 }
181
182 let (sx, sy) = s.xy::<f32>();
183 let (ex, ey) = e.xy::<f32>();
184
185 let m = (ey - sy) / (ex - sx);
186 let p = sy - m * sx;
187
188 (m * x as f32 + p) as usize == y
189 }
190 (Line(..), _) => panic!("Point type not supported"),
191 _ => panic!("{self:?} is not a line object"),
192 }
193 }
194
195 pub fn meets_endpoint_of_line(&self, point: Point) -> bool {
196 match self {
197 Line(s, e, _) => *s == point || *e == point,
198 _ => panic!("{self:?} is not a line object"),
199 }
200 }
201
202 /// Check if this line intersects with another line.
203 /// Panics if either shape is not a line.
204 ///
205 /// ```
206 /// use shapemaker::{Line, Point::Center};
207 /// let line = |x1: usize, y1: usize, x2: usize, y2: usize|
208 /// Line(Center(x1, y1), Center(x2, y2), 1.0);
209 /// assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1)));
210 /// assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4)));
211 /// assert!(line(4, 4, 6, 3).intersects_with(line(5, 2, 6, 5)));
212 /// ```
213 pub fn intersects_with(&self, line: Shape) -> bool {
214 match (self, &line) {
215 (&Line(s1, e1, _), &Line(s2, e2, _)) => {
216 let parameters = |s: Point, e: Point| {
217 let (sx, sy) = s.xy::<isize>();
218 let (ex, ey) = e.xy::<isize>();
219 let a = ey - sy;
220 let b = sx - ex;
221 let c = (ex * sy) - (sx * ey);
222 (a, b, c)
223 };
224
225 let distance_to_line = |p: Point, (s, e): (Point, Point)| {
226 let (x, y) = p.xy::<isize>();
227 let (a, b, c) = parameters(s, e);
228
229 (a * x) + (b * y) + c
230 };
231
232 let same_side_of_line =
233 |p1: Point, p2: Point, line: (Point, Point)| {
234 let d1 = distance_to_line(p1, line);
235 let d2 = distance_to_line(p2, line);
236
237 if (d1 == 0) || (d2 == 0) {
238 return false;
239 }
240
241 d1.signum() == d2.signum()
242 };
243
244 if same_side_of_line(s2, e2, (s1, e1)) {
245 return false;
246 }
247
248 if same_side_of_line(s1, e1, (s2, e2)) {
249 return false;
250 }
251
252 let (a1, b1, _) = parameters(s1, e1);
253 let (a2, b2, _) = parameters(s2, e2);
254
255 (a1 * b2) != (a2 * b1)
256 }
257 _ => {
258 unimplemented!(
259 "Intersection not implemented for {self:?} and {:?}",
260 line.clone()
261 )
262 }
263 }
264 }
265}