1 | // Copyright 2019 the Kurbo Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | //! An ellipse arc. |
5 | |
6 | use crate::{Affine, Ellipse, PathEl, Point, Rect, Shape, Vec2}; |
7 | use core::{ |
8 | f64::consts::{FRAC_PI_2, PI}, |
9 | iter, |
10 | ops::Mul, |
11 | }; |
12 | |
13 | #[cfg (not(feature = "std" ))] |
14 | use 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))] |
20 | pub 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 | |
34 | impl 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)] |
114 | pub 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 | |
128 | impl 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. |
158 | fn 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. |
166 | fn 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 | |
174 | impl 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 | |
209 | impl 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)] |
227 | mod 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 | |