1use std::f64::consts::{PI, TAU};
2
3use crate::geometry::{Point, Transform, Transformation};
4use crate::internal_iter::InternalIterator;
5
6#[derive(Clone, Copy, Debug, Default, PartialEq)]
7#[repr(C)]
8pub struct Arc {
9 pub from: Point,
10 pub to: Point,
11 pub radius: Point,
12 pub x_axis_rotation: f64,
13 pub large_arc: bool,
14 pub sweep: bool,
15
16 center: Point,
17 transformed_point: Point,
18 transformed_center: Point,
19 start_angle: f64,
20 sweep_angle: f64,
21 end_angle: f64,
22 rx: f64,
23 ry: f64,
24}
25
26impl Arc {
27 pub fn new(
30 from: Point,
31 to: Point,
32 radius: Point,
33 x_axis_rotation: f64,
34 large_arc: bool,
35 sweep: bool,
36 ) -> Arc {
37 if from.x == to.x && from.y == to.y {
40 return Arc {
41 from,
42 to,
43 radius,
44 x_axis_rotation,
45 large_arc,
46 sweep,
47 ..Default::default()
48 };
49 }
50
51 let mut rx = radius.x.abs();
52 let mut ry = radius.y.abs();
53 if rx == 0.0 || ry == 0.0 {
54 return Arc {
56 from,
57 to,
58 radius,
59 x_axis_rotation,
60 large_arc,
61 sweep,
62 ..Default::default()
63 };
64 }
65
66 let x_axis_rotation = x_axis_rotation % 360.0;
67 let x_axis_rotation_radians = x_axis_rotation * (PI / 180.0);
68
69 let dx = (from.x - to.x) / 2.0;
71 let dy = (from.y - to.y) / 2.0;
72 let transformed_point = Point {
73 x: x_axis_rotation_radians.cos() * dx + x_axis_rotation_radians.sin() * dy,
74 y: -x_axis_rotation_radians.sin() * dx + x_axis_rotation_radians.cos() * dy,
75 };
76
77 let radii_check =
79 transformed_point.x.powi(2) / rx.powi(2) + transformed_point.y.powi(2) / ry.powi(2);
80 if radii_check > 1.0 {
81 rx *= radii_check.sqrt();
82 ry *= radii_check.sqrt();
83 }
84
85 let c_square_numerator = rx.powi(2) * ry.powi(2)
87 - rx.powi(2) * transformed_point.y.powi(2)
88 - ry.powi(2) * transformed_point.x.powi(2);
89 let c_square_denominator =
90 rx.powi(2) * transformed_point.y.powi(2)
91 + ry.powi(2) * transformed_point.x.powi(2);
92 let c_radicand = (c_square_numerator / c_square_denominator).max(0.0);
93 let c_coefficient = if sweep != large_arc {
94 1.0
95 } else {
96 -1.0
97 } * c_radicand.sqrt();
98 let transformed_center = Point {
99 x: c_coefficient * ((rx * transformed_point.y) / ry),
100 y: c_coefficient * (-(ry * transformed_point.x) / rx),
101 };
102
103 let center = Point {
104 x: x_axis_rotation_radians.cos() * transformed_center.x
105 - x_axis_rotation_radians.sin() * transformed_center.y
106 + ((from.x + to.x) / 2.0),
107 y: x_axis_rotation_radians.sin() * transformed_center.x
108 + x_axis_rotation_radians.cos() * transformed_center.y
109 + ((from.y + to.y) / 2.0),
110 };
111
112 let start_vector = Point {
116 x: (transformed_point.x - transformed_center.x) / rx,
117 y: (transformed_point.y - transformed_center.y) / ry,
118 };
119 let start_angle = angle_between(Point { x: 1.0, y: 0.0 }, start_vector);
120 debug_assert!(!start_angle.is_nan());
121
122 let end_vector = Point {
123 x: (-transformed_point.x - transformed_center.x) / rx,
124 y: (-transformed_point.y - transformed_center.y) / ry,
125 };
126 let mut sweep_angle = angle_between(start_vector, end_vector);
127
128 if !sweep && sweep_angle > 0.0 {
129 sweep_angle -= TAU;
130 } else if sweep && sweep_angle < 0.0 {
131 sweep_angle += TAU;
132 }
133 sweep_angle %= TAU;
134 let end_angle = start_angle + sweep_angle;
135
136 Arc {
137 from,
138 to,
139 radius,
140 x_axis_rotation,
141 large_arc,
142 sweep,
143
144 center,
145 transformed_point,
146 transformed_center,
147 start_angle,
148 sweep_angle,
149 end_angle,
150 rx,
151 ry,
152 }
153 }
154
155 pub fn is_approximately_linear(&self, epsilon: f64) -> bool {
157 let center = self.from.lerp(self.to, 0.5);
158 let midpoint = self.midpoint();
159
160 let dx = midpoint.x - center.x;
161 let dy = midpoint.y - center.y;
162 let sagitta = (dx.powi(2) + dy.powi(2)).sqrt();
163
164 sagitta < epsilon
165 }
166
167 pub fn point_on_curve(&self, t: f64) -> Point {
168 debug_assert!(t >= 0.0 && t <= 1.0);
169
170 let angle = self.start_angle + self.sweep_angle * t;
171 let ellipse_component_x = self.rx * angle.cos();
172 let ellipse_component_y = self.ry * angle.sin();
173 let x_axis_rotation_radians = self.x_axis_rotation * (PI / 180.0);
174 Point {
175 x: x_axis_rotation_radians.cos() * ellipse_component_x
176 - x_axis_rotation_radians.sin() * ellipse_component_y
177 + self.center.x,
178 y: x_axis_rotation_radians.sin() * ellipse_component_x
179 + x_axis_rotation_radians.cos() * ellipse_component_y
180 + self.center.y,
181 }
182 }
183
184 pub fn midpoint(&self) -> Point {
185 self.point_on_curve(0.5)
186 }
187
188 pub fn split(&self, t: f64) -> (Arc, Arc) {
190 let split_at = self.point_on_curve(t);
191 let angle = self.start_angle + self.sweep_angle * t;
192
193 (Arc::new(
194 self.from,
195 split_at,
196 self.radius,
197 self.x_axis_rotation,
198 (angle - self.start_angle) > PI,
199 self.sweep,
200 ),
201 Arc::new(
202 split_at,
203 self.to,
204 self.radius,
205 self.x_axis_rotation,
206 (self.end_angle - angle) > PI,
207 self.sweep,
208 ))
209 }
210
211 pub fn linearize(self, epsilon: f64) -> Linearize {
212 Linearize {
213 segment: self,
214 epsilon,
215 }
216 }
217}
218
219fn angle_between(v0: Point, v1: Point) -> f64 {
220 let adjacent = v0.x * v1.x + v0.y * v1.y;
221 let hypotenuse = (
222 (v0.x.powi(2) + v0.y.powi(2)) * (v1.x.powi(2) + v1.y.powi(2))
223 ).sqrt();
224 let sign = if v0.x * v1.y - v0.y * v1.x < 0.0 {
225 -1.0
226 } else {
227 1.0
228 };
229 sign * (adjacent / hypotenuse).clamp(-1.0, 1.0).acos()
230}
231
232impl Transform for Arc {
233 fn transform<T>(self, t: &T) -> Arc
234 where
235 T: Transformation,
236 {
237 Arc::new(
238 self.from.transform(t),
239 self.to.transform(t),
240 self.radius,
241 self.x_axis_rotation,
242 self.large_arc,
243 self.sweep,
244 )
245 }
246
247 fn transform_mut<T>(&mut self, t: &T)
248 where
249 T: Transformation,
250 {
251 *self = self.transform(t);
252 }
253}
254
255#[derive(Clone, Copy)]
256pub struct Linearize {
257 segment: Arc,
258 epsilon: f64,
259}
260
261impl InternalIterator for Linearize {
262 type Item = Point;
263
264 fn for_each<F>(self, f: &mut F) -> bool
265 where
266 F: FnMut(Point) -> bool,
267 {
268 if self.segment.is_approximately_linear(self.epsilon) {
269 return f(self.segment.to);
270 }
271 let (segment_0, segment_1) = self.segment.split(0.5);
272 if !segment_0.linearize(self.epsilon).for_each(f) {
273 return false;
274 }
275 segment_1.linearize(self.epsilon).for_each(f)
276 }
277}