1use i_overlay::core::fill_rule::FillRule;
4use i_overlay::core::overlay_rule::OverlayRule;
5use i_overlay::float::simplify::SimplifyShape;
6use i_overlay::float::single::SingleFloatOverlay;
7use i_overlay::i_shape::float::area::Area;
8use i_overlay::mesh::float::outline::offset::OutlineOffset;
9use i_overlay::mesh::float::stroke::offset::StrokeOffset;
10use i_overlay::mesh::float::style::{LineCap, LineJoin, OutlineStyle, StrokeStyle};
11
12use super::{capsule_polygon, circle_polygon, polygon_to_svg_d};
13
14type Contour = Vec<[f64; 2]>;
15type OverlayShape = Vec<Contour>;
16type Shapes = Vec<OverlayShape>;
17
18#[derive(Clone, Debug, Default)]
20pub struct Shape {
21 shapes: Shapes,
22}
23
24fn pt(p: (f64, f64)) -> [f64; 2] {
25 [p.0, p.1]
26}
27
28fn xy(p: [f64; 2]) -> (f64, f64) {
29 (p[0], p[1])
30}
31
32impl Shape {
33 fn wrap(shapes: Shapes) -> Self {
34 Self { shapes }
35 }
36
37 pub fn empty() -> Self {
38 Self::wrap(Vec::new())
39 }
40
41 pub fn is_empty(&self) -> bool {
42 self.shapes.is_empty() || self.shapes.iter().all(|s| s.is_empty() || s[0].len() < 3)
43 }
44
45 pub fn from_ring(ring: &[(f64, f64)]) -> Self {
47 if ring.len() < 3 {
48 return Self::empty();
49 }
50 let c: Contour = ring.iter().copied().map(pt).collect();
51 Self::wrap(c.simplify_shape(FillRule::NonZero))
52 }
53
54 pub fn from_contours_evenodd(contours: &[Vec<(f64, f64)>]) -> Self {
56 let parts: Vec<Contour> = contours
57 .iter()
58 .filter(|r| r.len() >= 3)
59 .map(|r| r.iter().copied().map(pt).collect())
60 .collect();
61 if parts.is_empty() {
62 return Self::empty();
63 }
64 Self::wrap(parts.simplify_shape(FillRule::EvenOdd))
65 }
66
67 pub fn disk(cx: f64, cy: f64, radius: f64, quad_segs: u32) -> Self {
68 Self::from_ring(&circle_polygon(cx, cy, radius, quad_segs))
69 }
70
71 pub fn capsule(x1: f64, y1: f64, x2: f64, y2: f64, radius: f64) -> Self {
72 Self::from_ring(&capsule_polygon(x1, y1, x2, y2, radius, 8))
73 }
74
75 pub fn polyline_buffer(pts: &[(f64, f64)], radius: f64) -> Self {
76 if pts.len() < 2 || radius <= 0.0 {
77 return Self::empty();
78 }
79 if pts.len() == 2 {
80 return Self::capsule(pts[0].0, pts[0].1, pts[1].0, pts[1].1, radius);
81 }
82 let path: Contour = pts.iter().copied().map(pt).collect();
83 let r = radius.max(0.05) * 0.2;
84 let style = StrokeStyle::new(radius * 2.0)
85 .line_join(LineJoin::Round(r))
86 .start_cap(LineCap::Round(r))
87 .end_cap(LineCap::Round(r));
88 Self::wrap(path.stroke(style, false))
89 }
90
91 pub fn multipoint_buffer(pts: &[(f64, f64)], radius: f64) -> Self {
92 pts.iter()
93 .fold(Self::empty(), |acc, &(x, y)| acc.union(&Self::disk(x, y, radius, 8)))
94 }
95
96 pub fn annular(cx: f64, cy: f64, r: f64, stroke_width: f64) -> Self {
97 if r <= 0.0 || stroke_width <= 0.0 {
98 return Self::empty();
99 }
100 let outer = Self::disk(cx, cy, r + 0.5 * stroke_width, 16);
101 let inner_r = r - 0.5 * stroke_width;
102 if inner_r > 1e-6 {
103 outer.difference(&Self::disk(cx, cy, inner_r, 16))
104 } else {
105 outer
106 }
107 }
108
109 fn overlay_with(&self, other: &Self, rule: OverlayRule) -> Self {
110 Self::wrap(
111 self.shapes
112 .overlay(&other.shapes, rule, FillRule::NonZero),
113 )
114 }
115
116 pub fn union(&self, other: &Self) -> Self {
117 if self.is_empty() {
118 return other.clone();
119 }
120 if other.is_empty() {
121 return self.clone();
122 }
123 self.overlay_with(other, OverlayRule::Union)
124 }
125
126 pub fn difference(&self, other: &Self) -> Self {
127 if self.is_empty() {
128 return Self::empty();
129 }
130 if other.is_empty() {
131 return self.clone();
132 }
133 self.overlay_with(other, OverlayRule::Difference)
134 }
135
136 pub fn xor(&self, other: &Self) -> Self {
137 self.overlay_with(other, OverlayRule::Xor)
138 }
139
140 pub fn buffer(&self, dist: f64) -> Self {
142 if self.is_empty() || dist.abs() < 1e-15 {
143 return self.clone();
144 }
145 let join = LineJoin::Round((dist.abs() * 0.35).max(0.25));
146 Self::wrap(
147 self.shapes
148 .outline(&OutlineStyle::new(dist).line_join(join)),
149 )
150 }
151
152 pub fn simplify(&self) -> Self {
154 if self.is_empty() {
155 return Self::empty();
156 }
157 Self::wrap(self.shapes.simplify_shape(FillRule::NonZero))
158 }
159
160 pub fn point_count(&self) -> usize {
161 self.shapes.iter().flat_map(|s| s.iter()).map(|c| c.len()).sum()
162 }
163
164 pub fn halo(&self, dist: f64) -> Self {
166 if self.is_empty() || dist <= 0.0 {
167 return Self::empty();
168 }
169 self.simplify().buffer(dist)
170 }
171
172 pub fn has_holes(&self) -> bool {
173 self.shapes.iter().any(|s| s.len() > 1)
174 }
175
176 pub fn translate(&self, dx: f64, dy: f64) -> Self {
177 let mut shapes = self.shapes.clone();
178 for p in shapes.iter_mut().flatten().flatten() {
179 p[0] += dx;
180 p[1] += dy;
181 }
182 Self::wrap(shapes)
183 }
184
185 pub fn scale(&self, sx: f64, sy: f64, ox: f64, oy: f64) -> Self {
186 let mut shapes = self.shapes.clone();
187 for p in shapes.iter_mut().flatten().flatten() {
188 p[0] = ox + (p[0] - ox) * sx;
189 p[1] = oy + (p[1] - oy) * sy;
190 }
191 Self::wrap(shapes).simplify()
193 }
194
195 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
196 let mut iter = self.shapes.iter().flatten().flatten();
197 let first = iter.next()?;
198 let (mut minx, mut miny, mut maxx, mut maxy) = (first[0], first[1], first[0], first[1]);
199 for p in iter {
200 minx = minx.min(p[0]);
201 miny = miny.min(p[1]);
202 maxx = maxx.max(p[0]);
203 maxy = maxy.max(p[1]);
204 }
205 Some((minx, miny, maxx, maxy))
206 }
207
208 pub fn area(&self) -> f64 {
209 self.shapes.area().abs()
210 }
211
212 pub fn centroid(&self) -> Option<(f64, f64)> {
213 let (minx, miny, maxx, maxy) = self.bounds()?;
214 Some(((minx + maxx) * 0.5, (miny + maxy) * 0.5))
215 }
216
217 pub fn contains(&self, x: f64, y: f64) -> bool {
218 self.shapes.iter().any(|shape| {
220 shape
221 .iter()
222 .filter(|c| c.len() >= 3)
223 .fold(false, |inside, c| inside ^ point_in_ring(x, y, c))
224 })
225 }
226
227 pub fn to_svg_d(&self) -> String {
228 self.shapes
229 .iter()
230 .flatten()
231 .filter(|c| c.len() >= 3)
232 .map(|c| {
233 let ring: Vec<(f64, f64)> = c.iter().copied().map(xy).collect();
234 polygon_to_svg_d(&ring)
235 })
236 .filter(|d| !d.is_empty())
237 .collect::<Vec<_>>()
238 .join(" ")
239 }
240
241 pub fn horizontal_span_at(&self, y: f64) -> f64 {
243 let mut xs = Vec::new();
244 for contour in self.shapes.iter().flatten() {
245 let n = contour.len();
246 for i in 0..n {
247 let a = contour[i];
248 let b = contour[(i + 1) % n];
249 if (a[1] - y) * (b[1] - y) > 0.0 || (a[1] - b[1]).abs() < 1e-15 {
250 continue;
251 }
252 let t = (y - a[1]) / (b[1] - a[1]);
253 if (0.0..=1.0).contains(&t) {
254 xs.push(a[0] + t * (b[0] - a[0]));
255 }
256 }
257 }
258 if xs.len() < 2 {
259 return 0.0;
260 }
261 xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
262 xs.dedup_by(|a, b| (*a - *b).abs() <= 1e-9);
263 if xs.len() < 2 {
264 0.0
265 } else {
266 xs[1] - xs[0]
267 }
268 }
269
270 #[allow(clippy::type_complexity)]
271 pub fn polygons_rings(&self) -> Vec<(Vec<(f64, f64)>, Vec<Vec<(f64, f64)>>)> {
272 self.shapes
273 .iter()
274 .filter(|s| !s.is_empty())
275 .map(|shape| {
276 let exterior: Vec<(f64, f64)> = shape[0].iter().copied().map(xy).collect();
277 let holes: Vec<Vec<(f64, f64)>> = shape[1..]
278 .iter()
279 .map(|c| c.iter().copied().map(xy).collect())
280 .collect();
281 (exterior, holes)
282 })
283 .collect()
284 }
285}
286
287fn point_in_ring(x: f64, y: f64, ring: &Contour) -> bool {
288 let n = ring.len();
289 let mut inside = false;
290 let mut j = n - 1;
291 for i in 0..n {
292 let (xi, yi) = (ring[i][0], ring[i][1]);
293 let (xj, yj) = (ring[j][0], ring[j][1]);
294 if ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi + f64::EPSILON) + xi) {
295 inside = !inside;
296 }
297 j = i;
298 }
299 inside
300}