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