1use crate::affine::rotation_about;
6use kurbo::{BezPath, Point, Vec2};
7
8pub const KAPPA: f64 = 0.552_284_749_830_793_6;
11
12#[must_use]
17pub fn rectangle(center: Point, size: Vec2, radius: f64, degrees: f64) -> BezPath {
18 let (hw, hh) = (size.x.abs() * 0.5, size.y.abs() * 0.5);
19 let r = radius.max(0.0).min(hw).min(hh);
20 let (left, right, top, bottom) = (center.x - hw, center.x + hw, center.y - hh, center.y + hh);
21 let mut path = BezPath::new();
22 if r <= 0.0 {
23 path.move_to((right, top));
24 path.line_to((right, bottom));
25 path.line_to((left, bottom));
26 path.line_to((left, top));
27 path.close_path();
28 } else {
29 let k = r * (1.0 - KAPPA);
30 path.move_to((right, top + r));
31 path.line_to((right, bottom - r));
32 path.curve_to(
33 (right, bottom - k),
34 (right - k, bottom),
35 (right - r, bottom),
36 );
37 path.line_to((left + r, bottom));
38 path.curve_to((left + k, bottom), (left, bottom - k), (left, bottom - r));
39 path.line_to((left, top + r));
40 path.curve_to((left, top + k), (left + k, top), (left + r, top));
41 path.line_to((right - r, top));
42 path.curve_to((right - k, top), (right, top + k), (right, top + r));
43 path.close_path();
44 }
45 turned(path, center, degrees)
46}
47
48#[must_use]
51pub fn ellipse(center: Point, size: Vec2, degrees: f64) -> BezPath {
52 let (rx, ry) = (size.x.abs() * 0.5, size.y.abs() * 0.5);
53 let (kx, ky) = (rx * KAPPA, ry * KAPPA);
54 let (x, y) = (center.x, center.y);
55 let mut path = BezPath::new();
56 path.move_to((x, y - ry));
57 path.curve_to((x + kx, y - ry), (x + rx, y - ky), (x + rx, y));
58 path.curve_to((x + rx, y + ky), (x + kx, y + ry), (x, y + ry));
59 path.curve_to((x - kx, y + ry), (x - rx, y + ky), (x - rx, y));
60 path.curve_to((x - rx, y - ky), (x - kx, y - ry), (x, y - ry));
61 path.close_path();
62 turned(path, center, degrees)
63}
64
65#[derive(Copy, Clone, Debug, PartialEq)]
67pub struct Polystar {
68 pub star: bool,
70 pub center: Point,
72 pub points: f64,
74 pub outer_radius: f64,
76 pub inner_radius: f64,
78 pub outer_roundness: f64,
80 pub inner_roundness: f64,
82 pub degrees: f64,
84}
85
86#[must_use]
91pub fn polystar(star: &Polystar) -> BezPath {
92 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
94 let points = star.points.max(0.0).floor().min(1e4) as u32;
95 let mut path = BezPath::new();
96 if points == 0 {
97 return path;
98 }
99 let corners = if star.star { points * 2 } else { points };
100 let step = std::f64::consts::TAU / f64::from(corners);
101 let arc = |radius: f64| std::f64::consts::TAU * radius / f64::from(corners * 2);
102 let mut vertices = Vec::with_capacity(corners as usize);
103 for corner in 0..corners {
104 let outer = !star.star || corner % 2 == 0;
105 let (radius, roundness) = if outer {
106 (star.outer_radius, star.outer_roundness)
107 } else {
108 (star.inner_radius, star.inner_roundness)
109 };
110 let angle = -std::f64::consts::FRAC_PI_2 + step * f64::from(corner);
111 let (sin, cos) = (libm::sin(angle), libm::cos(angle));
112 let offset = Vec2::new(radius * cos, radius * sin);
113 let tangent = Vec2::new(-sin, cos);
115 let segment = if star.star {
116 arc(radius)
117 } else {
118 arc(radius) * 0.5
119 };
120 let handle = tangent * (segment * roundness);
121 vertices.push((star.center + offset, handle));
122 }
123 path.move_to(vertices[0].0);
124 for index in 0..vertices.len() {
125 let (from, from_handle) = vertices[index];
126 let (to, to_handle) = vertices[(index + 1) % vertices.len()];
127 if from_handle == Vec2::ZERO && to_handle == Vec2::ZERO {
128 path.line_to(to);
129 } else {
130 path.curve_to(from + from_handle, to - to_handle, to);
131 }
132 }
133 path.close_path();
134 turned(path, star.center, star.degrees)
135}
136
137#[derive(Copy, Clone, Debug, Default, PartialEq)]
140pub struct Vertex {
141 pub point: Point,
143 pub in_tangent: Vec2,
145 pub out_tangent: Vec2,
147}
148
149pub fn append_subpath(path: &mut BezPath, vertices: &[Vertex], closed: bool) {
153 let Some(first) = vertices.first() else {
154 return;
155 };
156 path.move_to(first.point);
157 let segment = |path: &mut BezPath, from: &Vertex, to: &Vertex| {
158 if from.out_tangent == Vec2::ZERO && to.in_tangent == Vec2::ZERO {
159 path.line_to(to.point);
160 } else {
161 path.curve_to(
162 from.point + from.out_tangent,
163 to.point + to.in_tangent,
164 to.point,
165 );
166 }
167 };
168 for pair in vertices.windows(2) {
169 segment(path, &pair[0], &pair[1]);
170 }
171 if closed {
172 if vertices.len() > 1 {
173 segment(path, &vertices[vertices.len() - 1], first);
174 }
175 path.close_path();
176 }
177}
178
179fn turned(path: BezPath, center: Point, degrees: f64) -> BezPath {
180 if degrees == 0.0 {
181 path
182 } else {
183 rotation_about(degrees, center) * path
184 }
185}