1use crate::basics::{is_stop, VertexSource, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP, PI};
8
9pub struct Arc {
16 x: f64,
17 y: f64,
18 rx: f64,
19 ry: f64,
20 angle: f64,
21 start: f64,
22 end: f64,
23 scale: f64,
24 da: f64,
25 ccw: bool,
26 initialized: bool,
27 path_cmd: u32,
28}
29
30impl Arc {
31 #[allow(clippy::too_many_arguments)]
33 pub fn new(x: f64, y: f64, rx: f64, ry: f64, a1: f64, a2: f64, ccw: bool) -> Self {
34 let mut arc = Self {
35 x,
36 y,
37 rx,
38 ry,
39 angle: 0.0,
40 start: 0.0,
41 end: 0.0,
42 scale: 1.0,
43 da: 0.0,
44 ccw: false,
45 initialized: false,
46 path_cmd: PATH_CMD_STOP,
47 };
48 arc.normalize(a1, a2, ccw);
49 arc
50 }
51
52 pub fn default_new() -> Self {
54 Self {
55 x: 0.0,
56 y: 0.0,
57 rx: 0.0,
58 ry: 0.0,
59 angle: 0.0,
60 start: 0.0,
61 end: 0.0,
62 scale: 1.0,
63 da: 0.0,
64 ccw: false,
65 initialized: false,
66 path_cmd: PATH_CMD_STOP,
67 }
68 }
69
70 #[allow(clippy::too_many_arguments)]
72 pub fn init(&mut self, x: f64, y: f64, rx: f64, ry: f64, a1: f64, a2: f64, ccw: bool) {
73 self.x = x;
74 self.y = y;
75 self.rx = rx;
76 self.ry = ry;
77 self.normalize(a1, a2, ccw);
78 }
79
80 pub fn set_approximation_scale(&mut self, s: f64) {
82 self.scale = s;
83 if self.initialized {
84 self.normalize(self.start, self.end, self.ccw);
85 }
86 }
87
88 pub fn approximation_scale(&self) -> f64 {
90 self.scale
91 }
92
93 fn normalize(&mut self, a1: f64, a2: f64, ccw: bool) {
95 let ra = (self.rx.abs() + self.ry.abs()) / 2.0;
96 self.da = (ra / (ra + 0.125 / self.scale)).acos() * 2.0;
97
98 let mut a1 = a1;
99 let mut a2 = a2;
100
101 if ccw {
102 while a2 < a1 {
103 a2 += PI * 2.0;
104 }
105 } else {
106 while a1 < a2 {
107 a1 += PI * 2.0;
108 }
109 self.da = -self.da;
110 }
111
112 self.ccw = ccw;
113 self.start = a1;
114 self.end = a2;
115 self.initialized = true;
116 }
117}
118
119impl VertexSource for Arc {
120 fn rewind(&mut self, _path_id: u32) {
121 self.path_cmd = PATH_CMD_MOVE_TO;
122 self.angle = self.start;
123 }
124
125 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
126 if is_stop(self.path_cmd) {
127 return PATH_CMD_STOP;
128 }
129
130 if (self.angle < self.end - self.da / 4.0) != self.ccw {
131 *x = self.x + self.end.cos() * self.rx;
132 *y = self.y + self.end.sin() * self.ry;
133 self.path_cmd = PATH_CMD_STOP;
134 return PATH_CMD_LINE_TO;
135 }
136
137 *x = self.x + self.angle.cos() * self.rx;
138 *y = self.y + self.angle.sin() * self.ry;
139
140 self.angle += self.da;
141
142 let pf = self.path_cmd;
143 self.path_cmd = PATH_CMD_LINE_TO;
144 pf
145 }
146}
147
148#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_arc_full_circle_ccw() {
158 let mut arc = Arc::new(0.0, 0.0, 10.0, 10.0, 0.0, PI * 2.0, true);
159 arc.rewind(0);
160 let mut x = 0.0;
161 let mut y = 0.0;
162
163 let cmd = arc.vertex(&mut x, &mut y);
165 assert_eq!(cmd, PATH_CMD_MOVE_TO);
166 assert!((x - 10.0).abs() < 1e-6);
167 assert!(y.abs() < 1e-6);
168
169 let mut count = 1;
171 loop {
172 let cmd = arc.vertex(&mut x, &mut y);
173 if is_stop(cmd) {
174 break;
175 }
176 assert_eq!(cmd, PATH_CMD_LINE_TO);
177 count += 1;
178 }
179 assert!(count > 4);
181 }
182
183 #[test]
184 fn test_arc_quarter_circle() {
185 let mut arc = Arc::new(0.0, 0.0, 10.0, 10.0, 0.0, PI / 2.0, true);
186 arc.rewind(0);
187 let mut x = 0.0;
188 let mut y = 0.0;
189
190 let cmd = arc.vertex(&mut x, &mut y);
192 assert_eq!(cmd, PATH_CMD_MOVE_TO);
193 assert!((x - 10.0).abs() < 1e-6);
194 assert!(y.abs() < 1e-6);
195
196 let mut last_x = x;
198 let mut last_y = y;
199 loop {
200 let cmd = arc.vertex(&mut x, &mut y);
201 if is_stop(cmd) {
202 break;
203 }
204 last_x = x;
205 last_y = y;
206 }
207 assert!(last_x.abs() < 1e-6);
209 assert!((last_y - 10.0).abs() < 1e-6);
210 }
211
212 #[test]
213 fn test_arc_cw_direction() {
214 let mut arc = Arc::new(0.0, 0.0, 10.0, 10.0, PI / 2.0, 0.0, false);
215 arc.rewind(0);
216 let mut x = 0.0;
217 let mut y = 0.0;
218
219 let cmd = arc.vertex(&mut x, &mut y);
221 assert_eq!(cmd, PATH_CMD_MOVE_TO);
222 assert!(x.abs() < 1e-6);
223 assert!((y - 10.0).abs() < 1e-6);
224
225 let mut last_x = x;
227 let mut last_y = y;
228 loop {
229 let cmd = arc.vertex(&mut x, &mut y);
230 if is_stop(cmd) {
231 break;
232 }
233 last_x = x;
234 last_y = y;
235 }
236 assert!((last_x - 10.0).abs() < 1e-6);
237 assert!(last_y.abs() < 1e-6);
238 }
239
240 #[test]
241 fn test_arc_elliptical() {
242 let mut arc = Arc::new(5.0, 5.0, 20.0, 10.0, 0.0, PI / 2.0, true);
243 arc.rewind(0);
244 let mut x = 0.0;
245 let mut y = 0.0;
246
247 let cmd = arc.vertex(&mut x, &mut y);
249 assert_eq!(cmd, PATH_CMD_MOVE_TO);
250 assert!((x - 25.0).abs() < 1e-6); assert!((y - 5.0).abs() < 1e-6);
252
253 let mut last_x = x;
255 let mut last_y = y;
256 loop {
257 let cmd = arc.vertex(&mut x, &mut y);
258 if is_stop(cmd) {
259 break;
260 }
261 last_x = x;
262 last_y = y;
263 }
264 assert!((last_x - 5.0).abs() < 1e-6); assert!((last_y - 15.0).abs() < 1e-6); }
267
268 #[test]
269 fn test_arc_rewind_restarts() {
270 let mut arc = Arc::new(0.0, 0.0, 10.0, 10.0, 0.0, PI, true);
271 let mut x = 0.0;
272 let mut y = 0.0;
273
274 arc.rewind(0);
276 while !is_stop(arc.vertex(&mut x, &mut y)) {}
277
278 arc.rewind(0);
280 let cmd = arc.vertex(&mut x, &mut y);
281 assert_eq!(cmd, PATH_CMD_MOVE_TO);
282 }
283
284 #[test]
285 fn test_arc_approximation_scale() {
286 let mut arc = Arc::new(0.0, 0.0, 100.0, 100.0, 0.0, PI * 2.0, true);
287 arc.rewind(0);
288 let mut x = 0.0;
289 let mut y = 0.0;
290 let mut count1 = 0;
291 while !is_stop(arc.vertex(&mut x, &mut y)) {
292 count1 += 1;
293 }
294
295 arc.set_approximation_scale(4.0);
297 arc.rewind(0);
298 let mut count2 = 0;
299 while !is_stop(arc.vertex(&mut x, &mut y)) {
300 count2 += 1;
301 }
302 assert!(count2 > count1);
303 }
304
305 #[test]
306 fn test_arc_default_new() {
307 let arc = Arc::default_new();
308 assert_eq!(arc.approximation_scale(), 1.0);
309 assert!(!arc.initialized);
310 }
311}