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 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 | /// 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)]
|
98 | pub 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 |
|
112 | impl 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.
|
142 | fn 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.
|
150 | fn 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 |
|
158 | impl 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 |
|
196 | impl 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 | |