1use crate::{Affine, Ellipse, ParamCurve, PathEl, Point, Rect, Shape, Vec2};
7use core::{
8 f64::consts::{FRAC_PI_2, PI},
9 iter,
10 ops::{Mul, Range},
11};
12
13#[cfg(not(feature = "std"))]
14use crate::common::FloatFuncs;
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Arc {
21 pub center: Point,
23 pub radii: Vec2,
26 pub start_angle: f64,
28 pub sweep_angle: f64,
30 pub x_rotation: f64,
32}
33
34impl Arc {
35 #[inline(always)]
37 pub fn new(
38 center: impl Into<Point>,
39 radii: impl Into<Vec2>,
40 start_angle: f64,
41 sweep_angle: f64,
42 x_rotation: f64,
43 ) -> Self {
44 Self {
45 center: center.into(),
46 radii: radii.into(),
47 start_angle,
48 sweep_angle,
49 x_rotation,
50 }
51 }
52
53 #[must_use]
58 #[inline]
59 pub fn reversed(&self) -> Arc {
60 Self {
61 center: self.center,
62 radii: self.radii,
63 start_angle: self.start_angle + self.sweep_angle,
64 sweep_angle: -self.sweep_angle,
65 x_rotation: self.x_rotation,
66 }
67 }
68
69 #[inline]
70 fn angle_at(&self, t: f64) -> f64 {
71 self.start_angle + self.sweep_angle * t
72 }
73
74 pub fn append_iter(&self, tolerance: f64) -> ArcAppendIter {
78 let sign = self.sweep_angle.signum();
79 let scaled_err = self.radii.x.max(self.radii.y) / tolerance;
80 let n_err = (1.1163 * scaled_err).powf(1.0 / 6.0).max(3.999_999);
83 let n = (n_err * self.sweep_angle.abs() * (1.0 / (2.0 * PI))).ceil();
84 let angle_step = self.sweep_angle / n;
85 let n = n as usize;
86 let arm_len = (4.0 / 3.0) * (0.25 * angle_step).abs().tan() * sign;
87 let angle0 = self.start_angle;
88 let p0 = sample_ellipse(self.radii, self.x_rotation, angle0);
89
90 ArcAppendIter {
91 idx: 0,
92
93 center: self.center,
94 radii: self.radii,
95 x_rotation: self.x_rotation,
96 n,
97 arm_len,
98 angle_step,
99
100 p0,
101 angle0,
102 }
103 }
104
105 pub fn to_cubic_beziers<P>(self, tolerance: f64, mut p: P)
109 where
110 P: FnMut(Point, Point, Point),
111 {
112 let mut path = self.append_iter(tolerance);
113 while let Some(PathEl::CurveTo(p1, p2, p3)) = path.next() {
114 p(p1, p2, p3);
115 }
116 }
117}
118
119#[doc(hidden)]
120pub struct ArcAppendIter {
121 idx: usize,
122
123 center: Point,
124 radii: Vec2,
125 x_rotation: f64,
126 n: usize,
127 arm_len: f64,
128 angle_step: f64,
129
130 p0: Vec2,
131 angle0: f64,
132}
133
134impl Iterator for ArcAppendIter {
135 type Item = PathEl;
136
137 fn next(&mut self) -> Option<Self::Item> {
138 if self.idx >= self.n {
139 return None;
140 }
141
142 let angle1 = self.angle0 + self.angle_step;
143 let p0 = self.p0;
144 let p1 = p0
145 + self.arm_len * sample_ellipse(self.radii, self.x_rotation, self.angle0 + FRAC_PI_2);
146 let p3 = sample_ellipse(self.radii, self.x_rotation, angle1);
147 let p2 =
148 p3 - self.arm_len * sample_ellipse(self.radii, self.x_rotation, angle1 + FRAC_PI_2);
149
150 self.angle0 = angle1;
151 self.p0 = p3;
152 self.idx += 1;
153
154 Some(PathEl::CurveTo(
155 self.center + p1,
156 self.center + p2,
157 self.center + p3,
158 ))
159 }
160}
161
162fn sample_ellipse(radii: Vec2, x_rotation: f64, angle: f64) -> Vec2 {
165 let (angle_sin, angle_cos) = angle.sin_cos();
166 let u = radii.x * angle_cos;
167 let v = radii.y * angle_sin;
168 rotate_pt(Vec2::new(u, v), x_rotation)
169}
170
171fn rotate_pt(pt: Vec2, angle: f64) -> Vec2 {
173 let (angle_sin, angle_cos) = angle.sin_cos();
174 Vec2::new(
175 pt.x * angle_cos - pt.y * angle_sin,
176 pt.x * angle_sin + pt.y * angle_cos,
177 )
178}
179
180impl ParamCurve for Arc {
181 fn eval(&self, t: f64) -> Point {
182 self.center + sample_ellipse(self.radii, self.x_rotation, self.angle_at(t))
183 }
184
185 fn subsegment(&self, range: Range<f64>) -> Self {
186 Self {
187 center: self.center,
188 radii: self.radii,
189 start_angle: self.angle_at(range.start),
190 sweep_angle: self.sweep_angle * (range.end - range.start),
191 x_rotation: self.x_rotation,
192 }
193 }
194}
195
196impl Shape for Arc {
197 type PathElementsIter<'iter> = iter::Chain<iter::Once<PathEl>, ArcAppendIter>;
198
199 fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> {
200 iter::once(PathEl::MoveTo(self.start())).chain(self.append_iter(tolerance))
201 }
202
203 #[inline]
205 fn area(&self) -> f64 {
206 let Vec2 { x, y } = self.radii;
207 PI * x * y
208 }
209
210 #[inline]
214 fn perimeter(&self, accuracy: f64) -> f64 {
215 self.path_segments(0.1).perimeter(accuracy)
216 }
217
218 #[inline]
220 fn winding(&self, pt: Point) -> i32 {
221 self.path_segments(0.1).winding(pt)
222 }
223
224 #[inline]
225 fn bounding_box(&self) -> Rect {
226 self.path_segments(0.1).bounding_box()
227 }
228}
229
230impl Mul<Arc> for Affine {
231 type Output = Arc;
232
233 fn mul(self, arc: Arc) -> Self::Output {
234 let ellipse = self * Ellipse::new(arc.center, arc.radii, arc.x_rotation);
235 let center = ellipse.center();
236 let (radii, rotation) = ellipse.radii_and_rotation();
237 Arc {
238 center,
239 radii,
240 x_rotation: rotation,
241 start_angle: arc.start_angle,
242 sweep_angle: arc.sweep_angle,
243 }
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use core::f64::consts::{FRAC_PI_4, FRAC_PI_6};
250
251 use crate::ParamCurve;
252
253 use super::*;
254
255 fn assert_point_near(actual: Point, expected: Point) {
256 let epsilon = 1e-12;
257 assert!(
258 actual.distance(expected) <= epsilon,
259 "expected {expected:?}, got {actual:?}"
260 );
261 }
262
263 fn assert_subsegment_matches(arc: Arc, range: Range<f64>) {
264 let subsegment = arc.subsegment(range.clone());
265 for t in [0.0, 0.25, 0.5, 1.0] {
266 let expected_t = range.start + (range.end - range.start) * t;
267 assert_point_near(subsegment.eval(t), arc.eval(expected_t));
268 }
269 assert_point_near(subsegment.start(), arc.eval(range.start));
270 assert_point_near(subsegment.end(), arc.eval(range.end));
271 }
272
273 #[test]
274 fn reversed_arc() {
275 let a = Arc::new((0., 0.), (1., 0.), 0., PI, 0.);
276 let f = a.reversed();
277
278 assert_eq!(a.center, f.center);
280 assert_eq!(a.radii, f.radii);
281 assert_eq!(a.x_rotation, f.x_rotation);
282
283 assert_eq!(a.sweep_angle, -f.sweep_angle);
285
286 assert_eq!(a, f.reversed());
288 }
289
290 #[test]
291 fn eval_endpoints_and_midpoint() {
292 let arc = Arc::new((3.0, -2.0), (4.0, 1.5), FRAC_PI_6, PI, 0.0);
293 assert_point_near(arc.eval(0.0), arc.start());
294 assert_point_near(arc.eval(1.0), arc.end());
295 assert_point_near(
296 arc.eval(0.5),
297 arc.center + sample_ellipse(arc.radii, arc.x_rotation, arc.angle_at(0.5)),
298 );
299 }
300
301 #[test]
302 fn eval_rotated_ellipse() {
303 let arc = Arc::new((5.0, 7.0), (3.0, 2.0), FRAC_PI_6, PI / 3.0, FRAC_PI_4);
304 assert_point_near(
305 arc.start(),
306 arc.center + sample_ellipse(arc.radii, arc.x_rotation, arc.start_angle),
307 );
308 assert_point_near(
309 arc.end(),
310 arc.center
311 + sample_ellipse(arc.radii, arc.x_rotation, arc.start_angle + arc.sweep_angle),
312 );
313 }
314
315 #[test]
316 fn eval_reversed_arc() {
317 let arc = Arc::new((2.0, 3.0), (4.0, 1.0), FRAC_PI_6, PI / 2.0, FRAC_PI_4);
318 let reversed = arc.reversed();
319 assert_point_near(reversed.start(), arc.end());
320 assert_point_near(reversed.end(), arc.start());
321 assert_point_near(reversed.eval(0.5), arc.eval(0.5));
322 }
323
324 #[test]
325 fn subsegment_matches_original() {
326 let arc = Arc::new((1.0, -4.0), (3.0, 2.0), -FRAC_PI_4, PI, FRAC_PI_6);
327 assert_subsegment_matches(arc, 0.2..0.7);
328 }
329
330 #[test]
331 fn subsegment_negative_sweep_matches_original() {
332 let arc = Arc::new((1.0, 2.0), (5.0, 3.0), PI, -0.75 * PI, FRAC_PI_6);
333 assert_subsegment_matches(arc, 0.1..0.8);
334 }
335
336 #[test]
337 fn subsegment_tiny_sweep_matches_original() {
338 let arc = Arc::new((0.0, 0.0), (2.0, 1.0), 1.0, 1e-9, FRAC_PI_4);
339 assert_subsegment_matches(arc, 0.25..0.75);
340 }
341}