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 elliptical 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 /// Returns a copy of this `Arc` in the opposite direction.
53 ///
54 /// The new `Arc` will sweep towards the original `Arc`s
55 /// start angle.
56 #[must_use]
57 #[inline]
58 pub fn reversed(&self) -> Arc {
59 Self {
60 center: self.center,
61 radii: self.radii,
62 start_angle: self.start_angle + self.sweep_angle,
63 sweep_angle: -self.sweep_angle,
64 x_rotation: self.x_rotation,
65 }
66 }
67
68 /// Create an iterator generating Bezier path elements.
69 ///
70 /// The generated elements can be appended to an existing bezier path.
71 pub fn append_iter(&self, tolerance: f64) -> ArcAppendIter {
72 let sign = self.sweep_angle.signum();
73 let scaled_err = self.radii.x.max(self.radii.y) / tolerance;
74 // Number of subdivisions per ellipse based on error tolerance.
75 // Note: this may slightly underestimate the error for quadrants.
76 let n_err = (1.1163 * scaled_err).powf(1.0 / 6.0).max(3.999_999);
77 let n = (n_err * self.sweep_angle.abs() * (1.0 / (2.0 * PI))).ceil();
78 let angle_step = self.sweep_angle / n;
79 let n = n as usize;
80 let arm_len = (4.0 / 3.0) * (0.25 * angle_step).abs().tan() * sign;
81 let angle0 = self.start_angle;
82 let p0 = sample_ellipse(self.radii, self.x_rotation, angle0);
83
84 ArcAppendIter {
85 idx: 0,
86
87 center: self.center,
88 radii: self.radii,
89 x_rotation: self.x_rotation,
90 n,
91 arm_len,
92 angle_step,
93
94 p0,
95 angle0,
96 }
97 }
98
99 /// Converts an `Arc` into a series of cubic bezier segments.
100 ///
101 /// The closure `p` will be invoked with the control points for each segment.
102 pub fn to_cubic_beziers<P>(self, tolerance: f64, mut p: P)
103 where
104 P: FnMut(Point, Point, Point),
105 {
106 let mut path = self.append_iter(tolerance);
107 while let Some(PathEl::CurveTo(p1, p2, p3)) = path.next() {
108 p(p1, p2, p3);
109 }
110 }
111}
112
113#[doc(hidden)]
114pub struct ArcAppendIter {
115 idx: usize,
116
117 center: Point,
118 radii: Vec2,
119 x_rotation: f64,
120 n: usize,
121 arm_len: f64,
122 angle_step: f64,
123
124 p0: Vec2,
125 angle0: f64,
126}
127
128impl Iterator for ArcAppendIter {
129 type Item = PathEl;
130
131 fn next(&mut self) -> Option<Self::Item> {
132 if self.idx >= self.n {
133 return None;
134 }
135
136 let angle1 = self.angle0 + self.angle_step;
137 let p0 = self.p0;
138 let p1 = p0
139 + self.arm_len * sample_ellipse(self.radii, self.x_rotation, self.angle0 + FRAC_PI_2);
140 let p3 = sample_ellipse(self.radii, self.x_rotation, angle1);
141 let p2 =
142 p3 - self.arm_len * sample_ellipse(self.radii, self.x_rotation, angle1 + FRAC_PI_2);
143
144 self.angle0 = angle1;
145 self.p0 = p3;
146 self.idx += 1;
147
148 Some(PathEl::CurveTo(
149 self.center + p1,
150 self.center + p2,
151 self.center + p3,
152 ))
153 }
154}
155
156/// Take the ellipse radii, how the radii are rotated, and the sweep angle, and return a point on
157/// the ellipse.
158fn sample_ellipse(radii: Vec2, x_rotation: f64, angle: f64) -> Vec2 {
159 let (angle_sin: f64, angle_cos: f64) = angle.sin_cos();
160 let u: f64 = radii.x * angle_cos;
161 let v: f64 = radii.y * angle_sin;
162 rotate_pt(pt:Vec2::new(u, v), angle:x_rotation)
163}
164
165/// Rotate `pt` about the origin by `angle` radians.
166fn rotate_pt(pt: Vec2, angle: f64) -> Vec2 {
167 let (angle_sin: f64, angle_cos: f64) = angle.sin_cos();
168 Vec2::new(
169 x:pt.x * angle_cos - pt.y * angle_sin,
170 y:pt.x * angle_sin + pt.y * angle_cos,
171 )
172}
173
174impl Shape for Arc {
175 type PathElementsIter<'iter> = iter::Chain<iter::Once<PathEl>, ArcAppendIter>;
176
177 fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> {
178 let p0 = sample_ellipse(self.radii, self.x_rotation, self.start_angle);
179 iter::once(PathEl::MoveTo(self.center + p0)).chain(self.append_iter(tolerance))
180 }
181
182 /// Note: shape isn't closed so area is not well defined.
183 #[inline]
184 fn area(&self) -> f64 {
185 let Vec2 { x, y } = self.radii;
186 PI * x * y
187 }
188
189 /// The perimeter of the arc.
190 ///
191 /// For now we just approximate by using the bezier curve representation.
192 #[inline]
193 fn perimeter(&self, accuracy: f64) -> f64 {
194 self.path_segments(0.1).perimeter(accuracy)
195 }
196
197 /// Note: shape isn't closed, so a point's winding number is not well defined.
198 #[inline]
199 fn winding(&self, pt: Point) -> i32 {
200 self.path_segments(0.1).winding(pt)
201 }
202
203 #[inline]
204 fn bounding_box(&self) -> Rect {
205 self.path_segments(0.1).bounding_box()
206 }
207}
208
209impl Mul<Arc> for Affine {
210 type Output = Arc;
211
212 fn mul(self, arc: Arc) -> Self::Output {
213 let ellipse: Ellipse = self * Ellipse::new(arc.center, arc.radii, arc.x_rotation);
214 let center: Point = ellipse.center();
215 let (radii: Vec2, rotation: f64) = ellipse.radii_and_rotation();
216 Arc {
217 center,
218 radii,
219 x_rotation: rotation,
220 start_angle: arc.start_angle,
221 sweep_angle: arc.sweep_angle,
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 #[test]
230 fn reversed_arc() {
231 let a = Arc::new((0., 0.), (1., 0.), 0., PI, 0.);
232 let f = a.reversed();
233
234 // Most fields should be unchanged:
235 assert_eq!(a.center, f.center);
236 assert_eq!(a.radii, f.radii);
237 assert_eq!(a.x_rotation, f.x_rotation);
238
239 // Sweep angle should be in reverse
240 assert_eq!(a.sweep_angle, -f.sweep_angle);
241
242 // Reversing it again should result in the original arc
243 assert_eq!(a, f.reversed());
244 }
245}
246