1use crate::{
2 default_start_angle,
3 options::{Options, StrokeOptions},
4 tess, FreePolyBuilder, PolyBuilder, DEFAULT_RADIUS,
5};
6use gee::{Angle, Circle, Point, Rect};
7use itertools::Itertools as _;
8
9#[derive(Clone, Debug)]
10pub struct StarBuilder {
11 circle: Circle,
12 inner_radius_over_radius: f32,
13 tips: u32,
14 start_angle: Angle,
15 options: Options,
16}
17
18impl Default for StarBuilder {
19 fn default() -> Self {
20 Self {
21 circle: Circle::from_radius(DEFAULT_RADIUS),
22 inner_radius_over_radius: 0.5,
23 tips: 5,
24 start_angle: default_start_angle(),
25 options: Default::default(),
26 }
27 }
28}
29
30impl StarBuilder {
31 pub fn new(tips: u32) -> Self {
32 Self::default().with_tips(tips)
33 }
34
35 pub fn pentagram() -> Self {
36 Self::new(5)
37 }
38
39 pub fn hexagram() -> Self {
40 Self::new(6)
41 }
42
43 pub fn with_tips(mut self, tips: u32) -> Self {
44 assert!(
45 tips >= 3,
46 "stars must have at least 3 tips, but this one has {}",
47 tips
48 );
49 self.tips = tips;
50 self
51 }
52
53 pub fn with_circle(mut self, circle: Circle) -> Self {
54 self.circle = circle;
55 self
56 }
57
58 pub fn with_center_and_radius(self, center: Point, radius: f32) -> Self {
59 self.with_circle(Circle::new(center, radius))
60 }
61
62 pub fn with_rotation(mut self, start_angle: impl Into<Angle>) -> Self {
63 self.start_angle = start_angle.into();
64 self
65 }
66
67 pub fn with_inner_radius_ratio(mut self, inner_radius_over_radius: f32) -> Self {
71 assert!(
72 inner_radius_over_radius > 0.0 && inner_radius_over_radius <= 1.0,
73 "`inner_radius_ratio` must be in the range `(0, 1]`"
74 );
75 self.inner_radius_over_radius = inner_radius_over_radius;
76 self
77 }
78
79 stroke!(public);
80
81 fill!();
82
83 build!();
84}
85
86impl PolyBuilder for StarBuilder {
87 fn options(&self) -> &Options {
88 &self.options
89 }
90
91 fn bounding_rect(&self) -> Rect {
92 self.circle.bounding_rect()
93 }
94
95 fn build<B: tess::path::traits::PathBuilder>(self, builder: &mut B) {
96 PolyBuilder::build(
97 FreePolyBuilder::from_parts(
98 {
99 let top_angle = self.start_angle;
100 let inner_offset = gee::Angle::PI() / self.tips as f32;
101 let inner_circle = self.circle.scale_radius(self.inner_radius_over_radius);
102 self.circle
103 .circle_points(self.tips, top_angle)
104 .interleave(inner_circle.circle_points(self.tips, top_angle + inner_offset))
105 },
106 true,
107 Some(self.bounding_rect()),
108 self.options,
109 ),
110 builder,
111 );
112 }
113}