1// Copyright 2019 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! An ellipse arc.
5
6use crate::{Affine, Ellipse, PathEl, Point, Rect, Shape, Vec2};
7use core::{
8 f64::consts::{FRAC_PI_2, PI},
9 iter,
10 ops::Mul,
11};
12
13#[cfg(not(feature = "std"))]
14use crate::common::FloatFuncs;
15
16/// A single arc segment.
17#[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 /// The arc's centre point.
22 pub center: Point,
23 /// The arc's radii, where the vector's x-component is the radius in the
24 /// positive x direction after applying `x_rotation`.
25 pub radii: Vec2,
26 /// The start angle in radians.
27 pub start_angle: f64,
28 /// The angle between the start and end of the arc, in radians.
29 pub sweep_angle: f64,
30 /// How much the arc is rotated, in radians.
31 pub x_rotation: f64,
32}
33
34impl Arc {
35 /// Create a new `Arc`.
36 pub fn new(
37 center: impl Into<Point>,
38 radii: impl Into<Vec2>,
39 start_angle: f64,
40 sweep_angle: f64,
41 x_rotation: f64,
42 ) -> Self {
43 Self {
44 center: center.into(),
45 radii: radii.into(),
46 start_angle,
47 sweep_angle,
48 x_rotation,
49 }
50 }
51
52 /// Create an iterator generating Bezier path elements.
53 ///
54 /// The generated elements can be appended to an existing bezier path.
55 pub fn append_iter(&self, tolerance: f64) -> ArcAppendIter {
56 let sign = self.sweep_angle.signum();
57 let scaled_err = self.radii.x.max(self.radii.y) / tolerance;
58 // Number of subdivisions per ellipse based on error tolerance.
59 // Note: this may slightly underestimate the error for quadrants.
60 let n_err = (1.1163 * scaled_err).powf(1.0 / 6.0).max(3.999_999);
61 let n = (n_err * self.sweep_angle.abs() * (1.0 / (2.0 * PI))).ceil();
62 let angle_step = self.sweep_angle / n;
63 let n = n as usize;
64 let arm_len = (4.0 / 3.0) * (0.25 * angle_step).abs().tan() * sign;
65 let angle0 = self.start_angle;
66 let p0 = sample_ellipse(self.radii, self.x_rotation, angle0);
67
68 ArcAppendIter {
69 idx: 0,
70
71 center: self.center,
72 radii: self.radii,
73 x_rotation: self.x_rotation,
74 n,
75 arm_len,
76 angle_step,
77
78 p0,
79 angle0,
80 }
81 }
82
83 /// Converts an Arc into a series of cubic bezier segments.
84 ///
85 /// Closure will be invoked for each segment.
86 pub fn to_cubic_beziers<P>(self, tolerance: f64, mut p: P)
87 where
88 P: FnMut(Point, Point, Point),
89 {
90 let mut path = self.append_iter(tolerance);
91 while let Some(PathEl::CurveTo(p1, p2, p3)) = path.next() {
92 p(p1, p2, p3);
93 }
94 }
95}
96
97#[doc(hidden)]
98pub struct ArcAppendIter {
99 idx: usize,
100
101 center: Point,
102 radii: Vec2,
103 x_rotation: f64,
104 n: usize,
105 arm_len: f64,
106 angle_step: f64,
107
108 p0: Vec2,
109 angle0: f64,
110}
111
112impl Iterator for ArcAppendIter {
113 type Item = PathEl;
114
115 fn next(&mut self) -> Option<Self::Item> {
116 if self.idx >= self.n {
117 return None;
118 }
119
120 let angle1 = self.angle0 + self.angle_step;
121 let p0 = self.p0;
122 let p1 = p0
123 + self.arm_len * sample_ellipse(self.radii, self.x_rotation, self.angle0 + FRAC_PI_2);
124 let p3 = sample_ellipse(self.radii, self.x_rotation, angle1);
125 let p2 =
126 p3 - self.arm_len * sample_ellipse(self.radii, self.x_rotation, angle1 + FRAC_PI_2);
127
128 self.angle0 = angle1;
129 self.p0 = p3;
130 self.idx += 1;
131
132 Some(PathEl::CurveTo(
133 self.center + p1,
134 self.center + p2,
135 self.center + p3,
136 ))
137 }
138}
139
140/// Take the ellipse radii, how the radii are rotated, and the sweep angle, and return a point on
141/// the ellipse.
142fn sample_ellipse(radii: Vec2, x_rotation: f64, angle: f64) -> Vec2 {
143 let (angle_sin: f64, angle_cos: f64) = angle.sin_cos();
144 let u: f64 = radii.x * angle_cos;
145 let v: f64 = radii.y * angle_sin;
146 rotate_pt(pt:Vec2::new(u, v), angle:x_rotation)
147}
148
149/// Rotate `pt` about the origin by `angle` radians.
150fn rotate_pt(pt: Vec2, angle: f64) -> Vec2 {
151 let (angle_sin: f64, angle_cos: f64) = angle.sin_cos();
152 Vec2::new(
153 x:pt.x * angle_cos - pt.y * angle_sin,
154 y:pt.x * angle_sin + pt.y * angle_cos,
155 )
156}
157
158impl Shape for Arc {
159 type PathElementsIter<'iter> = iter::Chain<iter::Once<PathEl>, ArcAppendIter>;
160
161 fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> {
162 let p0 = sample_ellipse(self.radii, self.x_rotation, self.start_angle);
163 iter::once(PathEl::MoveTo(self.center + p0)).chain(self.append_iter(tolerance))
164 }
165
166 /// Note: shape isn't closed so area is not well defined.
167 #[inline]
168 fn area(&self) -> f64 {
169 let Vec2 { x, y } = self.radii;
170 PI * x * y
171 }
172
173 /// The perimeter of the ellipse.
174 ///
175 /// Note: Finding the perimeter of an ellipse is [fairly involved][wikipedia],
176 /// so for now we just approximate by using the bezier curve representation.
177 ///
178 /// [wikipedia]: https://en.wikipedia.org/wiki/Ellipse#Circumference
179 #[inline]
180 fn perimeter(&self, accuracy: f64) -> f64 {
181 self.path_segments(0.1).perimeter(accuracy)
182 }
183
184 /// Note: shape isn't closed, so a point's winding number is not well defined.
185 #[inline]
186 fn winding(&self, pt: Point) -> i32 {
187 self.path_segments(0.1).winding(pt)
188 }
189
190 #[inline]
191 fn bounding_box(&self) -> Rect {
192 self.path_segments(0.1).bounding_box()
193 }
194}
195
196impl Mul<Arc> for Affine {
197 type Output = Arc;
198
199 fn mul(self, arc: Arc) -> Self::Output {
200 let ellipse: Ellipse = self * Ellipse::new(arc.center, arc.radii, arc.x_rotation);
201 let center: Point = ellipse.center();
202 let (radii: Vec2, rotation: f64) = ellipse.radii_and_rotation();
203 Arc {
204 center,
205 radii,
206 x_rotation: rotation,
207 start_angle: arc.start_angle,
208 sweep_angle: arc.sweep_angle,
209 }
210 }
211}
212