gon/
star.rs

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    /// The lower this value, the more pointy the star is.
68    ///
69    /// Values for `inner_radius_over_radius` must be in the range (0, 1].
70    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}