1use super::morph::Morph;
16use super::polygon::{CornerRounding, Pt, RoundedPolygon, angle_degrees, cos, pt, sin};
17use std::sync::OnceLock;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21#[allow(missing_docs)] pub enum ShapeKind {
23 Circle,
24 Square,
25 Slanted,
26 Arch,
27 Fan,
28 Arrow,
29 SemiCircle,
30 Oval,
31 Pill,
32 Triangle,
33 Diamond,
34 ClamShell,
35 Pentagon,
36 Gem,
37 Sunny,
38 VerySunny,
39 Cookie4Sided,
40 Cookie6Sided,
41 Cookie7Sided,
42 Cookie9Sided,
43 Cookie12Sided,
44 Ghostish,
45 Clover4Leaf,
46 Clover8Leaf,
47 Burst,
48 SoftBurst,
49 Boom,
50 SoftBoom,
51 Flower,
52 Puffy,
53 PuffyDiamond,
54 PixelCircle,
55 PixelTriangle,
56 Bun,
57 Heart,
58}
59
60const COUNT: usize = 35;
61
62static POLYGONS: [OnceLock<RoundedPolygon>; COUNT] = [const { OnceLock::new() }; COUNT];
63static MORPHS: [OnceLock<Box<Morph>>; COUNT * COUNT] = [const { OnceLock::new() }; COUNT * COUNT];
65
66impl ShapeKind {
67 pub const ALL: [Self; COUNT] = [
69 Self::Circle,
70 Self::Square,
71 Self::Slanted,
72 Self::Arch,
73 Self::Fan,
74 Self::Arrow,
75 Self::SemiCircle,
76 Self::Oval,
77 Self::Pill,
78 Self::Triangle,
79 Self::Diamond,
80 Self::ClamShell,
81 Self::Pentagon,
82 Self::Gem,
83 Self::Sunny,
84 Self::VerySunny,
85 Self::Cookie4Sided,
86 Self::Cookie6Sided,
87 Self::Cookie7Sided,
88 Self::Cookie9Sided,
89 Self::Cookie12Sided,
90 Self::Ghostish,
91 Self::Clover4Leaf,
92 Self::Clover8Leaf,
93 Self::Burst,
94 Self::SoftBurst,
95 Self::Boom,
96 Self::SoftBoom,
97 Self::Flower,
98 Self::Puffy,
99 Self::PuffyDiamond,
100 Self::PixelCircle,
101 Self::PixelTriangle,
102 Self::Bun,
103 Self::Heart,
104 ];
105
106 pub fn polygon(self) -> &'static RoundedPolygon {
109 POLYGONS[self as usize].get_or_init(|| outline(self).normalized())
110 }
111
112 pub fn morph_to(self, to: Self) -> &'static Morph {
115 MORPHS[self as usize * COUNT + to as usize]
116 .get_or_init(|| Box::new(Morph::new(self.polygon(), to.polygon())))
117 }
118}
119
120const R15: CornerRounding = CornerRounding::new(0.15);
121const R20: CornerRounding = CornerRounding::new(0.2);
122const R30: CornerRounding = CornerRounding::new(0.3);
123const R50: CornerRounding = CornerRounding::new(0.5);
124const R100: CornerRounding = CornerRounding::new(1.);
125const SHARP: CornerRounding = CornerRounding::UNROUNDED;
126const ORIGIN: (f32, f32) = (0., 0.);
127const PI: f32 = std::f32::consts::PI;
128
129const fn p(x: f32, y: f32) -> (Pt, CornerRounding) {
130 (pt(x, y), SHARP)
131}
132const fn r(x: f32, y: f32, radius: f32) -> (Pt, CornerRounding) {
133 (pt(x, y), CornerRounding::new(radius))
134}
135const fn s(x: f32, y: f32, radius: f32, smoothing: f32) -> (Pt, CornerRounding) {
136 (pt(x, y), CornerRounding::smoothed(radius, smoothing))
137}
138
139fn to_radians(degrees: f32) -> f32 {
140 degrees / 360. * 2. * PI
141}
142
143fn repeated(
146 points: &[(Pt, CornerRounding)],
147 reps: usize,
148 mirroring: bool,
149) -> Vec<(Pt, CornerRounding)> {
150 let center = pt(0.5, 0.5);
151 let mut out = Vec::new();
152 if mirroring {
153 let angles: Vec<f32> = points.iter().map(|q| angle_degrees(q.0 - center)).collect();
154 let distances: Vec<f32> = points.iter().map(|q| (q.0 - center).distance()).collect();
155 let actual_reps = reps * 2;
156 let section = 360. / actual_reps as f32;
157 for it in 0..actual_reps {
158 for index in 0..points.len() {
159 let even = it % 2 == 0;
160 let i = if even {
161 index
162 } else {
163 points.len() - 1 - index
164 };
165 if i > 0 || even {
166 let turned = if even {
167 angles[i]
168 } else {
169 section - angles[i] + 2. * angles[0]
170 };
171 let a = to_radians(section * it as f32 + turned);
172 out.push((pt(cos(a), sin(a)) * distances[i] + center, points[i].1));
173 }
174 }
175 }
176 } else {
177 let np = points.len();
178 for it in 0..np * reps {
179 let a = to_radians((it / np) as f32 * 360. / reps as f32);
180 let off = points[it % np].0 - center;
181 out.push((
182 pt(
183 off.x * cos(a) - off.y * sin(a),
184 off.x * sin(a) + off.y * cos(a),
185 ) + center,
186 points[it % np].1,
187 ));
188 }
189 }
190 out
191}
192
193fn custom(points: &[(Pt, CornerRounding)], reps: usize, mirroring: bool) -> RoundedPolygon {
194 let actual = repeated(points, reps, mirroring);
195 let vertices: Vec<(f32, f32)> = actual.iter().map(|q| (q.0.x, q.0.y)).collect();
196 let rounding: Vec<CornerRounding> = actual.iter().map(|q| q.1).collect();
197 RoundedPolygon::from_vertices(&vertices, SHARP, Some(&rounding), Some((0.5, 0.5)))
198 .unwrap_or_else(unbuildable)
199}
200
201fn unbuildable() -> RoundedPolygon {
205 RoundedPolygon::from_vertices(&[(0.5, 0.5); 3], SHARP, None, None).unwrap_or_else(|| {
206 unreachable!("three finite vertices with no per-vertex rounding are always accepted")
207 })
208}
209
210fn star(points: usize, inner: f32, rounding: CornerRounding) -> RoundedPolygon {
211 RoundedPolygon::star(points, 1., inner, rounding, None, ORIGIN).unwrap_or_else(unbuildable)
212}
213
214fn outline(kind: ShapeKind) -> RoundedPolygon {
215 use ShapeKind as K;
216 match kind {
217 K::Circle => RoundedPolygon::circle(10, 1., ORIGIN).unwrap_or_else(unbuildable),
218 K::Square => {
219 RoundedPolygon::rectangle(1., 1., R30, None, ORIGIN).unwrap_or_else(unbuildable)
220 }
221 K::Slanted => custom(
222 &[
223 s(0.926, 0.970, 0.189, 0.811),
224 s(-0.021, 0.967, 0.187, 0.057),
225 ],
226 2,
227 false,
228 ),
229 K::Arch => RoundedPolygon::regular(4, 1., ORIGIN, SHARP, Some(&[R100, R100, R20, R20]))
230 .unwrap_or_else(unbuildable)
231 .rotated(-135.),
232 K::Fan => custom(
233 &[
234 s(1.004, 1.000, 0.148, 0.417),
235 r(0.000, 1.000, 0.151),
236 r(0.000, -0.003, 0.148),
237 r(0.978, 0.020, 0.803),
238 ],
239 1,
240 false,
241 ),
242 K::Arrow => custom(
243 &[
244 r(0.500, 0.892, 0.313),
245 r(-0.216, 1.050, 0.207),
246 s(0.499, -0.160, 0.215, 1.000),
247 r(1.225, 1.060, 0.211),
248 ],
249 1,
250 false,
251 ),
252 K::SemiCircle => {
253 RoundedPolygon::rectangle(1.6, 1., SHARP, Some(&[R20, R20, R100, R100]), ORIGIN)
254 .unwrap_or_else(unbuildable)
255 }
256 K::Oval => RoundedPolygon::circle(8, 1., ORIGIN)
257 .unwrap_or_else(unbuildable)
258 .transformed(|x, y| (x, y * 0.64))
259 .rotated(-45.),
260 K::Pill => custom(
261 &[
262 r(0.961, 0.039, 0.426),
263 p(1.001, 0.428),
264 r(1.000, 0.609, 1.000),
265 ],
266 2,
267 true,
268 ),
269 K::Triangle => RoundedPolygon::regular(3, 1., ORIGIN, R20, None)
270 .unwrap_or_else(unbuildable)
271 .rotated(-90.),
272 K::Diamond => custom(
273 &[s(0.500, 1.096, 0.151, 0.524), r(0.040, 0.500, 0.159)],
274 2,
275 false,
276 ),
277 K::ClamShell => custom(
278 &[
279 r(0.171, 0.841, 0.159),
280 r(-0.020, 0.500, 0.140),
281 r(0.170, 0.159, 0.159),
282 ],
283 2,
284 false,
285 ),
286 K::Pentagon => custom(
287 &[
288 r(0.500, -0.009, 0.172),
289 r(1.030, 0.365, 0.164),
290 r(0.828, 0.970, 0.169),
291 ],
292 1,
293 true,
294 ),
295 K::Gem => custom(
296 &[
297 s(0.499, 1.023, 0.241, 0.778),
298 r(-0.005, 0.792, 0.208),
299 r(0.073, 0.258, 0.228),
300 r(0.433, -0.000, 0.491),
301 ],
302 1,
303 true,
304 ),
305 K::Sunny => star(8, 0.8, R15),
306 K::VerySunny => custom(&[r(0.500, 1.080, 0.085), r(0.358, 0.843, 0.085)], 8, false),
307 K::Cookie4Sided => custom(&[r(1.237, 1.236, 0.258), r(0.500, 0.918, 0.233)], 4, false),
308 K::Cookie6Sided => custom(&[r(0.723, 0.884, 0.394), r(0.500, 1.099, 0.398)], 6, false),
309 K::Cookie7Sided => star(7, 0.75, R50).rotated(-90.),
310 K::Cookie9Sided => star(9, 0.8, R50).rotated(-90.),
311 K::Cookie12Sided => star(12, 0.8, R50).rotated(-90.),
312 K::Ghostish => custom(
313 &[
314 r(0.500, 0., 1.000),
315 r(1., 0., 1.000),
316 s(1., 1.140, 0.254, 0.106),
317 r(0.575, 0.906, 0.253),
318 ],
319 1,
320 true,
321 ),
322 K::Clover4Leaf => custom(&[p(0.500, 0.074), r(0.725, -0.099, 0.476)], 4, true),
323 K::Clover8Leaf => custom(&[p(0.500, 0.036), r(0.758, -0.101, 0.209)], 8, false),
324 K::Burst => custom(
325 &[r(0.500, -0.006, 0.006), r(0.592, 0.158, 0.006)],
326 12,
327 false,
328 ),
329 K::SoftBurst => custom(&[r(0.193, 0.277, 0.053), r(0.176, 0.055, 0.053)], 10, false),
330 K::Boom => custom(
331 &[r(0.457, 0.296, 0.007), r(0.500, -0.051, 0.007)],
332 15,
333 false,
334 ),
335 K::SoftBoom => custom(
336 &[
337 p(0.733, 0.454),
338 r(0.839, 0.437, 0.532),
339 s(0.949, 0.449, 0.439, 1.000),
340 r(0.998, 0.478, 0.174),
341 ],
342 16,
343 true,
344 ),
345 K::Flower => custom(
346 &[
347 p(0.370, 0.187),
348 r(0.416, 0.049, 0.381),
349 r(0.479, 0.001, 0.095),
350 ],
351 8,
352 true,
353 ),
354 K::Puffy => custom(
355 &[
356 p(0.500, 0.053),
357 r(0.545, -0.040, 0.405),
358 r(0.670, -0.035, 0.426),
359 r(0.717, 0.066, 0.574),
360 p(0.722, 0.128),
361 r(0.777, 0.002, 0.360),
362 r(0.914, 0.149, 0.660),
363 r(0.926, 0.289, 0.660),
364 p(0.881, 0.346),
365 r(0.940, 0.344, 0.126),
366 r(1.003, 0.437, 0.255),
367 ],
368 2,
369 true,
370 )
371 .transformed(|x, y| (x, y * 0.742)),
372 K::PuffyDiamond => custom(
373 &[
374 r(0.870, 0.130, 0.146),
375 p(0.818, 0.357),
376 r(1.000, 0.332, 0.853),
377 ],
378 4,
379 true,
380 ),
381 K::PixelCircle => custom(
382 &[
383 p(0.500, 0.000),
384 p(0.704, 0.000),
385 p(0.704, 0.065),
386 p(0.843, 0.065),
387 p(0.843, 0.148),
388 p(0.926, 0.148),
389 p(0.926, 0.296),
390 p(1.000, 0.296),
391 ],
392 2,
393 true,
394 ),
395 K::PixelTriangle => custom(
396 &[
397 p(0.110, 0.500),
398 p(0.113, 0.000),
399 p(0.287, 0.000),
400 p(0.287, 0.087),
401 p(0.421, 0.087),
402 p(0.421, 0.170),
403 p(0.560, 0.170),
404 p(0.560, 0.265),
405 p(0.674, 0.265),
406 p(0.675, 0.344),
407 p(0.789, 0.344),
408 p(0.789, 0.439),
409 p(0.888, 0.439),
410 ],
411 1,
412 true,
413 ),
414 K::Bun => custom(
415 &[
416 p(0.796, 0.500),
417 r(0.853, 0.518, 1.),
418 r(0.992, 0.631, 1.),
419 r(0.968, 1.000, 1.),
420 ],
421 2,
422 true,
423 ),
424 K::Heart => custom(
425 &[
426 r(0.500, 0.268, 0.016),
427 r(0.792, -0.066, 0.958),
428 r(1.064, 0.276, 1.000),
429 r(0.501, 0.946, 0.129),
430 ],
431 1,
432 true,
433 ),
434 }
435}