1use crate::{Path, PathBuilder};
6use glam::Vec2;
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum Shape {
11 Rect {
13 position: Vec2,
15 size: Vec2,
17 },
18 RoundedRect {
20 position: Vec2,
22 size: Vec2,
24 radii: [f32; 4],
26 },
27 Circle {
29 center: Vec2,
31 radius: f32,
33 },
34 Ellipse {
36 center: Vec2,
38 radii: Vec2,
40 },
41 Line {
43 start: Vec2,
45 end: Vec2,
47 },
48 Polyline {
50 points: Vec<Vec2>,
52 closed: bool,
54 },
55 Polygon {
57 points: Vec<Vec2>,
59 },
60 RegularPolygon {
62 center: Vec2,
64 radius: f32,
66 sides: u32,
68 rotation: f32,
70 },
71 Star {
73 center: Vec2,
75 outer_radius: f32,
77 inner_radius: f32,
79 points: u32,
81 rotation: f32,
83 },
84 Arc {
86 center: Vec2,
88 radius: f32,
90 start_angle: f32,
92 end_angle: f32,
94 },
95 Pie {
97 center: Vec2,
99 radius: f32,
101 start_angle: f32,
103 end_angle: f32,
105 },
106 Path(Path),
108}
109
110impl Shape {
111 pub fn rect(position: Vec2, size: Vec2) -> Self {
117 Self::Rect { position, size }
118 }
119
120 pub fn rect_centered(center: Vec2, size: Vec2) -> Self {
122 Self::Rect {
123 position: center - size * 0.5,
124 size,
125 }
126 }
127
128 pub fn rounded_rect(position: Vec2, size: Vec2, radius: f32) -> Self {
130 Self::RoundedRect {
131 position,
132 size,
133 radii: [radius; 4],
134 }
135 }
136
137 pub fn rounded_rect_varying(position: Vec2, size: Vec2, radii: [f32; 4]) -> Self {
139 Self::RoundedRect {
140 position,
141 size,
142 radii,
143 }
144 }
145
146 pub fn circle(center: Vec2, radius: f32) -> Self {
148 Self::Circle { center, radius }
149 }
150
151 pub fn ellipse(center: Vec2, radii: Vec2) -> Self {
153 Self::Ellipse { center, radii }
154 }
155
156 pub fn line(start: Vec2, end: Vec2) -> Self {
158 Self::Line { start, end }
159 }
160
161 pub fn polyline(points: Vec<Vec2>, closed: bool) -> Self {
163 Self::Polyline { points, closed }
164 }
165
166 pub fn polygon(points: Vec<Vec2>) -> Self {
168 Self::Polygon { points }
169 }
170
171 pub fn regular_polygon(center: Vec2, radius: f32, sides: u32) -> Self {
173 Self::RegularPolygon {
174 center,
175 radius,
176 sides,
177 rotation: 0.0,
178 }
179 }
180
181 pub fn regular_polygon_rotated(center: Vec2, radius: f32, sides: u32, rotation: f32) -> Self {
183 Self::RegularPolygon {
184 center,
185 radius,
186 sides,
187 rotation,
188 }
189 }
190
191 pub fn star(center: Vec2, outer_radius: f32, inner_radius: f32, points: u32) -> Self {
193 Self::Star {
194 center,
195 outer_radius,
196 inner_radius,
197 points,
198 rotation: 0.0,
199 }
200 }
201
202 pub fn star_rotated(
204 center: Vec2,
205 outer_radius: f32,
206 inner_radius: f32,
207 points: u32,
208 rotation: f32,
209 ) -> Self {
210 Self::Star {
211 center,
212 outer_radius,
213 inner_radius,
214 points,
215 rotation,
216 }
217 }
218
219 pub fn arc(center: Vec2, radius: f32, start_angle: f32, end_angle: f32) -> Self {
221 Self::Arc {
222 center,
223 radius,
224 start_angle,
225 end_angle,
226 }
227 }
228
229 pub fn pie(center: Vec2, radius: f32, start_angle: f32, end_angle: f32) -> Self {
231 Self::Pie {
232 center,
233 radius,
234 start_angle,
235 end_angle,
236 }
237 }
238
239 pub fn path(path: Path) -> Self {
241 Self::Path(path)
242 }
243
244 pub fn to_path(&self) -> Path {
250 let mut builder = PathBuilder::new();
251
252 match self {
253 Shape::Rect { position, size } => {
254 builder.rect(*position, *size);
255 }
256
257 Shape::RoundedRect {
258 position,
259 size,
260 radii,
261 } => {
262 let r = radii[0].min(size.x / 2.0).min(size.y / 2.0);
265 builder.rounded_rect(*position, *size, r);
266 }
267
268 Shape::Circle { center, radius } => {
269 builder.circle(*center, *radius);
270 }
271
272 Shape::Ellipse { center, radii } => {
273 builder.ellipse(*center, *radii);
274 }
275
276 Shape::Line { start, end } => {
277 builder.move_to(*start);
278 builder.line_to(*end);
279 }
280
281 Shape::Polyline { points, closed } => {
282 if !points.is_empty() {
283 builder.move_to(points[0]);
284 for point in &points[1..] {
285 builder.line_to(*point);
286 }
287 if *closed {
288 builder.close();
289 }
290 }
291 }
292
293 Shape::Polygon { points } => {
294 builder.polygon(points);
295 }
296
297 Shape::RegularPolygon {
298 center,
299 radius,
300 sides,
301 rotation,
302 } => {
303 let points = generate_regular_polygon(*center, *radius, *sides, *rotation);
304 builder.polygon(&points);
305 }
306
307 Shape::Star {
308 center,
309 outer_radius,
310 inner_radius,
311 points,
312 rotation,
313 } => {
314 let star_points =
315 generate_star(*center, *outer_radius, *inner_radius, *points, *rotation);
316 builder.polygon(&star_points);
317 }
318
319 Shape::Arc {
320 center,
321 radius,
322 start_angle,
323 end_angle,
324 } => {
325 let arc_points = approximate_arc(*center, *radius, *start_angle, *end_angle, 32);
326 if !arc_points.is_empty() {
327 builder.move_to(arc_points[0]);
328 for point in &arc_points[1..] {
329 builder.line_to(*point);
330 }
331 }
332 }
333
334 Shape::Pie {
335 center,
336 radius,
337 start_angle,
338 end_angle,
339 } => {
340 let arc_points = approximate_arc(*center, *radius, *start_angle, *end_angle, 32);
341 builder.move_to(*center);
342 if !arc_points.is_empty() {
343 builder.line_to(arc_points[0]);
344 for point in &arc_points[1..] {
345 builder.line_to(*point);
346 }
347 }
348 builder.close();
349 }
350
351 Shape::Path(path) => {
352 return path.clone();
353 }
354 }
355
356 builder.build()
357 }
358
359 pub fn bounds(&self) -> Option<(Vec2, Vec2)> {
361 match self {
362 Shape::Rect { position, size } => Some((*position, *position + *size)),
363
364 Shape::RoundedRect { position, size, .. } => Some((*position, *position + *size)),
365
366 Shape::Circle { center, radius } => {
367 let r = Vec2::splat(*radius);
368 Some((*center - r, *center + r))
369 }
370
371 Shape::Ellipse { center, radii } => Some((*center - *radii, *center + *radii)),
372
373 Shape::Line { start, end } => Some((start.min(*end), start.max(*end))),
374
375 Shape::Polyline { points, .. } | Shape::Polygon { points } => {
376 if points.is_empty() {
377 return None;
378 }
379 let mut min = points[0];
380 let mut max = points[0];
381 for p in &points[1..] {
382 min = min.min(*p);
383 max = max.max(*p);
384 }
385 Some((min, max))
386 }
387
388 Shape::RegularPolygon { center, radius, .. } => {
389 let r = Vec2::splat(*radius);
390 Some((*center - r, *center + r))
391 }
392
393 Shape::Star {
394 center,
395 outer_radius,
396 ..
397 } => {
398 let r = Vec2::splat(*outer_radius);
399 Some((*center - r, *center + r))
400 }
401
402 Shape::Arc { center, radius, .. } | Shape::Pie { center, radius, .. } => {
403 let r = Vec2::splat(*radius);
405 Some((*center - r, *center + r))
406 }
407
408 Shape::Path(path) => path.bounds(),
409 }
410 }
411}
412
413fn generate_regular_polygon(center: Vec2, radius: f32, sides: u32, rotation: f32) -> Vec<Vec2> {
415 let mut points = Vec::with_capacity(sides as usize);
416 let angle_step = std::f32::consts::TAU / sides as f32;
417
418 for i in 0..sides {
419 let angle = rotation + angle_step * i as f32;
420 points.push(center + Vec2::new(angle.cos(), angle.sin()) * radius);
421 }
422
423 points
424}
425
426fn generate_star(
428 center: Vec2,
429 outer_radius: f32,
430 inner_radius: f32,
431 points: u32,
432 rotation: f32,
433) -> Vec<Vec2> {
434 let mut vertices = Vec::with_capacity(points as usize * 2);
435 let angle_step = std::f32::consts::TAU / (points * 2) as f32;
436
437 for i in 0..(points * 2) {
438 let angle = rotation - std::f32::consts::FRAC_PI_2 + angle_step * i as f32;
439 let radius = if i % 2 == 0 {
440 outer_radius
441 } else {
442 inner_radius
443 };
444 vertices.push(center + Vec2::new(angle.cos(), angle.sin()) * radius);
445 }
446
447 vertices
448}
449
450fn approximate_arc(
452 center: Vec2,
453 radius: f32,
454 start_angle: f32,
455 end_angle: f32,
456 segments: u32,
457) -> Vec<Vec2> {
458 let mut points = Vec::with_capacity(segments as usize + 1);
459 let angle_span = end_angle - start_angle;
460 let angle_step = angle_span / segments as f32;
461
462 for i in 0..=segments {
463 let angle = start_angle + angle_step * i as f32;
464 points.push(center + Vec2::new(angle.cos(), angle.sin()) * radius);
465 }
466
467 points
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn test_rect_bounds() {
476 let shape = Shape::rect(Vec2::new(10.0, 20.0), Vec2::new(100.0, 50.0));
477 let (min, max) = shape.bounds().unwrap();
478 assert_eq!(min, Vec2::new(10.0, 20.0));
479 assert_eq!(max, Vec2::new(110.0, 70.0));
480 }
481
482 #[test]
483 fn test_circle_bounds() {
484 let shape = Shape::circle(Vec2::new(50.0, 50.0), 25.0);
485 let (min, max) = shape.bounds().unwrap();
486 assert_eq!(min, Vec2::new(25.0, 25.0));
487 assert_eq!(max, Vec2::new(75.0, 75.0));
488 }
489
490 #[test]
491 fn test_rect_to_path() {
492 let shape = Shape::rect(Vec2::new(0.0, 0.0), Vec2::new(100.0, 100.0));
493 let path = shape.to_path();
494 assert!(!path.is_empty());
495 }
496
497 #[test]
498 fn test_regular_polygon() {
499 let points = generate_regular_polygon(Vec2::ZERO, 10.0, 4, 0.0);
500 assert_eq!(points.len(), 4);
501 }
502
503 #[test]
504 fn test_star() {
505 let points = generate_star(Vec2::ZERO, 10.0, 5.0, 5, 0.0);
506 assert_eq!(points.len(), 10);
507 }
508}