1use crate::basics::{PointD, VertexSource, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP, PI};
17use crate::math::calc_sq_distance;
18
19const CURVE_COLLINEARITY_EPSILON: f64 = 1e-30;
24const CURVE_ANGLE_TOLERANCE_EPSILON: f64 = 0.01;
25const CURVE_RECURSION_LIMIT: u32 = 32;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum CurveApproximationMethod {
34 Inc,
35 Div,
36}
37
38#[derive(Debug, Clone, Copy)]
44pub struct Curve4Points {
45 pub cp: [f64; 8],
46}
47
48impl Curve4Points {
49 #[allow(clippy::too_many_arguments)]
50 pub fn new(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Self {
51 Self {
52 cp: [x1, y1, x2, y2, x3, y3, x4, y4],
53 }
54 }
55
56 #[allow(clippy::too_many_arguments)]
57 pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) {
58 self.cp = [x1, y1, x2, y2, x3, y3, x4, y4];
59 }
60}
61
62impl std::ops::Index<usize> for Curve4Points {
63 type Output = f64;
64 fn index(&self, i: usize) -> &f64 {
65 &self.cp[i]
66 }
67}
68
69impl std::ops::IndexMut<usize> for Curve4Points {
70 fn index_mut(&mut self, i: usize) -> &mut f64 {
71 &mut self.cp[i]
72 }
73}
74
75#[allow(clippy::too_many_arguments)]
81pub fn catrom_to_bezier(
82 x1: f64,
83 y1: f64,
84 x2: f64,
85 y2: f64,
86 x3: f64,
87 y3: f64,
88 x4: f64,
89 y4: f64,
90) -> Curve4Points {
91 Curve4Points::new(
92 x2,
93 y2,
94 (-x1 + 6.0 * x2 + x3) / 6.0,
95 (-y1 + 6.0 * y2 + y3) / 6.0,
96 (x2 + 6.0 * x3 - x4) / 6.0,
97 (y2 + 6.0 * y3 - y4) / 6.0,
98 x3,
99 y3,
100 )
101}
102
103#[allow(clippy::too_many_arguments)]
105pub fn ubspline_to_bezier(
106 x1: f64,
107 y1: f64,
108 x2: f64,
109 y2: f64,
110 x3: f64,
111 y3: f64,
112 x4: f64,
113 y4: f64,
114) -> Curve4Points {
115 Curve4Points::new(
116 (x1 + 4.0 * x2 + x3) / 6.0,
117 (y1 + 4.0 * y2 + y3) / 6.0,
118 (4.0 * x2 + 2.0 * x3) / 6.0,
119 (4.0 * y2 + 2.0 * y3) / 6.0,
120 (2.0 * x2 + 4.0 * x3) / 6.0,
121 (2.0 * y2 + 4.0 * y3) / 6.0,
122 (x2 + 4.0 * x3 + x4) / 6.0,
123 (y2 + 4.0 * y3 + y4) / 6.0,
124 )
125}
126
127#[allow(clippy::too_many_arguments)]
129pub fn hermite_to_bezier(
130 x1: f64,
131 y1: f64,
132 x2: f64,
133 y2: f64,
134 x3: f64,
135 y3: f64,
136 x4: f64,
137 y4: f64,
138) -> Curve4Points {
139 Curve4Points::new(
140 x1,
141 y1,
142 (3.0 * x1 + x3) / 3.0,
143 (3.0 * y1 + y3) / 3.0,
144 (3.0 * x2 - x4) / 3.0,
145 (3.0 * y2 - y4) / 3.0,
146 x2,
147 y2,
148 )
149}
150
151pub struct Curve3Inc {
159 num_steps: i32,
160 step: i32,
161 scale: f64,
162 start_x: f64,
163 start_y: f64,
164 end_x: f64,
165 end_y: f64,
166 fx: f64,
167 fy: f64,
168 dfx: f64,
169 dfy: f64,
170 ddfx: f64,
171 ddfy: f64,
172 saved_fx: f64,
173 saved_fy: f64,
174 saved_dfx: f64,
175 saved_dfy: f64,
176}
177
178impl Curve3Inc {
179 pub fn new() -> Self {
180 Self {
181 num_steps: 0,
182 step: 0,
183 scale: 1.0,
184 start_x: 0.0,
185 start_y: 0.0,
186 end_x: 0.0,
187 end_y: 0.0,
188 fx: 0.0,
189 fy: 0.0,
190 dfx: 0.0,
191 dfy: 0.0,
192 ddfx: 0.0,
193 ddfy: 0.0,
194 saved_fx: 0.0,
195 saved_fy: 0.0,
196 saved_dfx: 0.0,
197 saved_dfy: 0.0,
198 }
199 }
200
201 pub fn new_with_points(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {
202 let mut c = Self::new();
203 c.init(x1, y1, x2, y2, x3, y3);
204 c
205 }
206
207 pub fn reset(&mut self) {
208 self.num_steps = 0;
209 self.step = -1;
210 }
211
212 pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) {
213 self.start_x = x1;
214 self.start_y = y1;
215 self.end_x = x3;
216 self.end_y = y3;
217
218 let dx1 = x2 - x1;
219 let dy1 = y2 - y1;
220 let dx2 = x3 - x2;
221 let dy2 = y3 - y2;
222
223 let len = (dx1 * dx1 + dy1 * dy1).sqrt() + (dx2 * dx2 + dy2 * dy2).sqrt();
224
225 self.num_steps = crate::basics::uround(len * 0.25 * self.scale) as i32;
226
227 if self.num_steps < 4 {
228 self.num_steps = 4;
229 }
230
231 let subdivide_step = 1.0 / self.num_steps as f64;
232 let subdivide_step2 = subdivide_step * subdivide_step;
233
234 let tmpx = (x1 - x2 * 2.0 + x3) * subdivide_step2;
235 let tmpy = (y1 - y2 * 2.0 + y3) * subdivide_step2;
236
237 self.fx = x1;
238 self.saved_fx = x1;
239 self.fy = y1;
240 self.saved_fy = y1;
241
242 self.dfx = tmpx + (x2 - x1) * (2.0 * subdivide_step);
243 self.saved_dfx = self.dfx;
244 self.dfy = tmpy + (y2 - y1) * (2.0 * subdivide_step);
245 self.saved_dfy = self.dfy;
246
247 self.ddfx = tmpx * 2.0;
248 self.ddfy = tmpy * 2.0;
249
250 self.step = self.num_steps;
251 }
252
253 pub fn set_approximation_scale(&mut self, s: f64) {
254 self.scale = s;
255 }
256
257 pub fn approximation_scale(&self) -> f64 {
258 self.scale
259 }
260}
261
262impl Default for Curve3Inc {
263 fn default() -> Self {
264 Self::new()
265 }
266}
267
268impl VertexSource for Curve3Inc {
269 fn rewind(&mut self, _path_id: u32) {
270 if self.num_steps == 0 {
271 self.step = -1;
272 return;
273 }
274 self.step = self.num_steps;
275 self.fx = self.saved_fx;
276 self.fy = self.saved_fy;
277 self.dfx = self.saved_dfx;
278 self.dfy = self.saved_dfy;
279 }
280
281 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
282 if self.step < 0 {
283 return PATH_CMD_STOP;
284 }
285 if self.step == self.num_steps {
286 *x = self.start_x;
287 *y = self.start_y;
288 self.step -= 1;
289 return PATH_CMD_MOVE_TO;
290 }
291 if self.step == 0 {
292 *x = self.end_x;
293 *y = self.end_y;
294 self.step -= 1;
295 return PATH_CMD_LINE_TO;
296 }
297 self.fx += self.dfx;
298 self.fy += self.dfy;
299 self.dfx += self.ddfx;
300 self.dfy += self.ddfy;
301 *x = self.fx;
302 *y = self.fy;
303 self.step -= 1;
304 PATH_CMD_LINE_TO
305 }
306}
307
308pub struct Curve3Div {
316 approximation_scale: f64,
317 distance_tolerance_square: f64,
318 angle_tolerance: f64,
319 count: usize,
320 points: Vec<PointD>,
321}
322
323impl Curve3Div {
324 pub fn new() -> Self {
325 Self {
326 approximation_scale: 1.0,
327 distance_tolerance_square: 0.0,
328 angle_tolerance: 0.0,
329 count: 0,
330 points: Vec::new(),
331 }
332 }
333
334 pub fn new_with_points(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {
335 let mut c = Self::new();
336 c.init(x1, y1, x2, y2, x3, y3);
337 c
338 }
339
340 pub fn reset(&mut self) {
341 self.points.clear();
342 self.count = 0;
343 }
344
345 pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) {
346 self.points.clear();
347 self.distance_tolerance_square = 0.5 / self.approximation_scale;
348 self.distance_tolerance_square *= self.distance_tolerance_square;
349 self.bezier(x1, y1, x2, y2, x3, y3);
350 self.count = 0;
351 }
352
353 pub fn set_approximation_scale(&mut self, s: f64) {
354 self.approximation_scale = s;
355 }
356
357 pub fn approximation_scale(&self) -> f64 {
358 self.approximation_scale
359 }
360
361 pub fn set_angle_tolerance(&mut self, a: f64) {
362 self.angle_tolerance = a;
363 }
364
365 pub fn angle_tolerance(&self) -> f64 {
366 self.angle_tolerance
367 }
368
369 fn bezier(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) {
370 self.points.push(PointD { x: x1, y: y1 });
371 self.recursive_bezier(x1, y1, x2, y2, x3, y3, 0);
372 self.points.push(PointD { x: x3, y: y3 });
373 }
374
375 #[allow(clippy::too_many_arguments)]
376 fn recursive_bezier(
377 &mut self,
378 x1: f64,
379 y1: f64,
380 x2: f64,
381 y2: f64,
382 x3: f64,
383 y3: f64,
384 level: u32,
385 ) {
386 if level > CURVE_RECURSION_LIMIT {
387 return;
388 }
389
390 let x12 = (x1 + x2) / 2.0;
392 let y12 = (y1 + y2) / 2.0;
393 let x23 = (x2 + x3) / 2.0;
394 let y23 = (y2 + y3) / 2.0;
395 let x123 = (x12 + x23) / 2.0;
396 let y123 = (y12 + y23) / 2.0;
397
398 let dx = x3 - x1;
399 let dy = y3 - y1;
400 let d = ((x2 - x3) * dy - (y2 - y3) * dx).abs();
401
402 if d > CURVE_COLLINEARITY_EPSILON {
403 if d * d <= self.distance_tolerance_square * (dx * dx + dy * dy) {
405 if self.angle_tolerance < CURVE_ANGLE_TOLERANCE_EPSILON {
406 self.points.push(PointD { x: x123, y: y123 });
407 return;
408 }
409
410 let mut da = ((y3 - y2).atan2(x3 - x2) - (y2 - y1).atan2(x2 - x1)).abs();
412 if da >= PI {
413 da = 2.0 * PI - da;
414 }
415
416 if da < self.angle_tolerance {
417 self.points.push(PointD { x: x123, y: y123 });
418 return;
419 }
420 }
421 } else {
422 let da = dx * dx + dy * dy;
424 let d_val = if da == 0.0 {
425 calc_sq_distance(x1, y1, x2, y2)
426 } else {
427 let d_param = ((x2 - x1) * dx + (y2 - y1) * dy) / da;
428 if d_param > 0.0 && d_param < 1.0 {
429 return;
431 }
432 if d_param <= 0.0 {
433 calc_sq_distance(x2, y2, x1, y1)
434 } else if d_param >= 1.0 {
435 calc_sq_distance(x2, y2, x3, y3)
436 } else {
437 calc_sq_distance(x2, y2, x1 + d_param * dx, y1 + d_param * dy)
438 }
439 };
440 if d_val < self.distance_tolerance_square {
441 self.points.push(PointD { x: x2, y: y2 });
442 return;
443 }
444 }
445
446 self.recursive_bezier(x1, y1, x12, y12, x123, y123, level + 1);
448 self.recursive_bezier(x123, y123, x23, y23, x3, y3, level + 1);
449 }
450}
451
452impl Default for Curve3Div {
453 fn default() -> Self {
454 Self::new()
455 }
456}
457
458impl VertexSource for Curve3Div {
459 fn rewind(&mut self, _path_id: u32) {
460 self.count = 0;
461 }
462
463 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
464 if self.count >= self.points.len() {
465 return PATH_CMD_STOP;
466 }
467 let p = &self.points[self.count];
468 *x = p.x;
469 *y = p.y;
470 self.count += 1;
471 if self.count == 1 {
472 PATH_CMD_MOVE_TO
473 } else {
474 PATH_CMD_LINE_TO
475 }
476 }
477}
478
479pub struct Curve4Inc {
487 num_steps: i32,
488 step: i32,
489 scale: f64,
490 start_x: f64,
491 start_y: f64,
492 end_x: f64,
493 end_y: f64,
494 fx: f64,
495 fy: f64,
496 dfx: f64,
497 dfy: f64,
498 ddfx: f64,
499 ddfy: f64,
500 dddfx: f64,
501 dddfy: f64,
502 saved_fx: f64,
503 saved_fy: f64,
504 saved_dfx: f64,
505 saved_dfy: f64,
506 saved_ddfx: f64,
507 saved_ddfy: f64,
508}
509
510impl Curve4Inc {
511 pub fn new() -> Self {
512 Self {
513 num_steps: 0,
514 step: 0,
515 scale: 1.0,
516 start_x: 0.0,
517 start_y: 0.0,
518 end_x: 0.0,
519 end_y: 0.0,
520 fx: 0.0,
521 fy: 0.0,
522 dfx: 0.0,
523 dfy: 0.0,
524 ddfx: 0.0,
525 ddfy: 0.0,
526 dddfx: 0.0,
527 dddfy: 0.0,
528 saved_fx: 0.0,
529 saved_fy: 0.0,
530 saved_dfx: 0.0,
531 saved_dfy: 0.0,
532 saved_ddfx: 0.0,
533 saved_ddfy: 0.0,
534 }
535 }
536
537 #[allow(clippy::too_many_arguments)]
538 pub fn new_with_points(
539 x1: f64,
540 y1: f64,
541 x2: f64,
542 y2: f64,
543 x3: f64,
544 y3: f64,
545 x4: f64,
546 y4: f64,
547 ) -> Self {
548 let mut c = Self::new();
549 c.init(x1, y1, x2, y2, x3, y3, x4, y4);
550 c
551 }
552
553 pub fn new_with_curve4_points(cp: &Curve4Points) -> Self {
554 let mut c = Self::new();
555 c.init(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
556 c
557 }
558
559 pub fn reset(&mut self) {
560 self.num_steps = 0;
561 self.step = -1;
562 }
563
564 #[allow(clippy::too_many_arguments)]
565 pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) {
566 self.start_x = x1;
567 self.start_y = y1;
568 self.end_x = x4;
569 self.end_y = y4;
570
571 let dx1 = x2 - x1;
572 let dy1 = y2 - y1;
573 let dx2 = x3 - x2;
574 let dy2 = y3 - y2;
575 let dx3 = x4 - x3;
576 let dy3 = y4 - y3;
577
578 let len = ((dx1 * dx1 + dy1 * dy1).sqrt()
579 + (dx2 * dx2 + dy2 * dy2).sqrt()
580 + (dx3 * dx3 + dy3 * dy3).sqrt())
581 * 0.25
582 * self.scale;
583
584 self.num_steps = crate::basics::uround(len) as i32;
585
586 if self.num_steps < 4 {
587 self.num_steps = 4;
588 }
589
590 let subdivide_step = 1.0 / self.num_steps as f64;
591 let subdivide_step2 = subdivide_step * subdivide_step;
592 let subdivide_step3 = subdivide_step * subdivide_step * subdivide_step;
593
594 let pre1 = 3.0 * subdivide_step;
595 let pre2 = 3.0 * subdivide_step2;
596 let pre4 = 6.0 * subdivide_step2;
597 let pre5 = 6.0 * subdivide_step3;
598
599 let tmp1x = x1 - x2 * 2.0 + x3;
600 let tmp1y = y1 - y2 * 2.0 + y3;
601
602 let tmp2x = (x2 - x3) * 3.0 - x1 + x4;
603 let tmp2y = (y2 - y3) * 3.0 - y1 + y4;
604
605 self.saved_fx = x1;
606 self.fx = x1;
607 self.saved_fy = y1;
608 self.fy = y1;
609
610 self.saved_dfx = (x2 - x1) * pre1 + tmp1x * pre2 + tmp2x * subdivide_step3;
611 self.dfx = self.saved_dfx;
612 self.saved_dfy = (y2 - y1) * pre1 + tmp1y * pre2 + tmp2y * subdivide_step3;
613 self.dfy = self.saved_dfy;
614
615 self.saved_ddfx = tmp1x * pre4 + tmp2x * pre5;
616 self.ddfx = self.saved_ddfx;
617 self.saved_ddfy = tmp1y * pre4 + tmp2y * pre5;
618 self.ddfy = self.saved_ddfy;
619
620 self.dddfx = tmp2x * pre5;
621 self.dddfy = tmp2y * pre5;
622
623 self.step = self.num_steps;
624 }
625
626 pub fn init_with_curve4_points(&mut self, cp: &Curve4Points) {
627 self.init(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
628 }
629
630 pub fn set_approximation_scale(&mut self, s: f64) {
631 self.scale = s;
632 }
633
634 pub fn approximation_scale(&self) -> f64 {
635 self.scale
636 }
637}
638
639impl Default for Curve4Inc {
640 fn default() -> Self {
641 Self::new()
642 }
643}
644
645impl VertexSource for Curve4Inc {
646 fn rewind(&mut self, _path_id: u32) {
647 if self.num_steps == 0 {
648 self.step = -1;
649 return;
650 }
651 self.step = self.num_steps;
652 self.fx = self.saved_fx;
653 self.fy = self.saved_fy;
654 self.dfx = self.saved_dfx;
655 self.dfy = self.saved_dfy;
656 self.ddfx = self.saved_ddfx;
657 self.ddfy = self.saved_ddfy;
658 }
659
660 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
661 if self.step < 0 {
662 return PATH_CMD_STOP;
663 }
664 if self.step == self.num_steps {
665 *x = self.start_x;
666 *y = self.start_y;
667 self.step -= 1;
668 return PATH_CMD_MOVE_TO;
669 }
670 if self.step == 0 {
671 *x = self.end_x;
672 *y = self.end_y;
673 self.step -= 1;
674 return PATH_CMD_LINE_TO;
675 }
676
677 self.fx += self.dfx;
678 self.fy += self.dfy;
679 self.dfx += self.ddfx;
680 self.dfy += self.ddfy;
681 self.ddfx += self.dddfx;
682 self.ddfy += self.dddfy;
683
684 *x = self.fx;
685 *y = self.fy;
686 self.step -= 1;
687 PATH_CMD_LINE_TO
688 }
689}
690
691pub struct Curve4Div {
699 approximation_scale: f64,
700 distance_tolerance_square: f64,
701 angle_tolerance: f64,
702 cusp_limit: f64,
703 count: usize,
704 points: Vec<PointD>,
705}
706
707impl Curve4Div {
708 pub fn new() -> Self {
709 Self {
710 approximation_scale: 1.0,
711 distance_tolerance_square: 0.0,
712 angle_tolerance: 0.0,
713 cusp_limit: 0.0,
714 count: 0,
715 points: Vec::new(),
716 }
717 }
718
719 #[allow(clippy::too_many_arguments)]
720 pub fn new_with_points(
721 x1: f64,
722 y1: f64,
723 x2: f64,
724 y2: f64,
725 x3: f64,
726 y3: f64,
727 x4: f64,
728 y4: f64,
729 ) -> Self {
730 let mut c = Self::new();
731 c.init(x1, y1, x2, y2, x3, y3, x4, y4);
732 c
733 }
734
735 pub fn new_with_curve4_points(cp: &Curve4Points) -> Self {
736 let mut c = Self::new();
737 c.init(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
738 c
739 }
740
741 pub fn reset(&mut self) {
742 self.points.clear();
743 self.count = 0;
744 }
745
746 #[allow(clippy::too_many_arguments)]
747 pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) {
748 self.points.clear();
749 self.distance_tolerance_square = 0.5 / self.approximation_scale;
750 self.distance_tolerance_square *= self.distance_tolerance_square;
751 self.bezier(x1, y1, x2, y2, x3, y3, x4, y4);
752 self.count = 0;
753 }
754
755 pub fn init_with_curve4_points(&mut self, cp: &Curve4Points) {
756 self.init(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
757 }
758
759 pub fn set_approximation_scale(&mut self, s: f64) {
760 self.approximation_scale = s;
761 }
762
763 pub fn approximation_scale(&self) -> f64 {
764 self.approximation_scale
765 }
766
767 pub fn set_angle_tolerance(&mut self, a: f64) {
768 self.angle_tolerance = a;
769 }
770
771 pub fn angle_tolerance(&self) -> f64 {
772 self.angle_tolerance
773 }
774
775 pub fn set_cusp_limit(&mut self, v: f64) {
776 self.cusp_limit = if v == 0.0 { 0.0 } else { PI - v };
777 }
778
779 pub fn cusp_limit(&self) -> f64 {
780 if self.cusp_limit == 0.0 {
781 0.0
782 } else {
783 PI - self.cusp_limit
784 }
785 }
786
787 #[allow(clippy::too_many_arguments)]
788 fn bezier(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) {
789 self.points.push(PointD { x: x1, y: y1 });
790 self.recursive_bezier(x1, y1, x2, y2, x3, y3, x4, y4, 0);
791 self.points.push(PointD { x: x4, y: y4 });
792 }
793
794 #[allow(clippy::too_many_arguments)]
795 fn recursive_bezier(
796 &mut self,
797 x1: f64,
798 y1: f64,
799 x2: f64,
800 y2: f64,
801 x3: f64,
802 y3: f64,
803 x4: f64,
804 y4: f64,
805 level: u32,
806 ) {
807 if level > CURVE_RECURSION_LIMIT {
808 return;
809 }
810
811 let x12 = (x1 + x2) / 2.0;
813 let y12 = (y1 + y2) / 2.0;
814 let x23 = (x2 + x3) / 2.0;
815 let y23 = (y2 + y3) / 2.0;
816 let x34 = (x3 + x4) / 2.0;
817 let y34 = (y3 + y4) / 2.0;
818 let x123 = (x12 + x23) / 2.0;
819 let y123 = (y12 + y23) / 2.0;
820 let x234 = (x23 + x34) / 2.0;
821 let y234 = (y23 + y34) / 2.0;
822 let x1234 = (x123 + x234) / 2.0;
823 let y1234 = (y123 + y234) / 2.0;
824
825 let dx = x4 - x1;
827 let dy = y4 - y1;
828
829 let mut d2 = ((x2 - x4) * dy - (y2 - y4) * dx).abs();
830 let mut d3 = ((x3 - x4) * dy - (y3 - y4) * dx).abs();
831
832 let case = ((d2 > CURVE_COLLINEARITY_EPSILON) as u32) << 1
833 | (d3 > CURVE_COLLINEARITY_EPSILON) as u32;
834
835 match case {
836 0 => {
837 let k = dx * dx + dy * dy;
839 if k == 0.0 {
840 d2 = calc_sq_distance(x1, y1, x2, y2);
841 d3 = calc_sq_distance(x4, y4, x3, y3);
842 } else {
843 let k = 1.0 / k;
844 let da1 = x2 - x1;
845 let da2 = y2 - y1;
846 d2 = k * (da1 * dx + da2 * dy);
847 let da1 = x3 - x1;
848 let da2 = y3 - y1;
849 d3 = k * (da1 * dx + da2 * dy);
850 if d2 > 0.0 && d2 < 1.0 && d3 > 0.0 && d3 < 1.0 {
851 return;
853 }
854 if d2 <= 0.0 {
855 d2 = calc_sq_distance(x2, y2, x1, y1);
856 } else if d2 >= 1.0 {
857 d2 = calc_sq_distance(x2, y2, x4, y4);
858 } else {
859 d2 = calc_sq_distance(x2, y2, x1 + d2 * dx, y1 + d2 * dy);
860 }
861
862 if d3 <= 0.0 {
863 d3 = calc_sq_distance(x3, y3, x1, y1);
864 } else if d3 >= 1.0 {
865 d3 = calc_sq_distance(x3, y3, x4, y4);
866 } else {
867 d3 = calc_sq_distance(x3, y3, x1 + d3 * dx, y1 + d3 * dy);
868 }
869 }
870 if d2 > d3 {
871 if d2 < self.distance_tolerance_square {
872 self.points.push(PointD { x: x2, y: y2 });
873 return;
874 }
875 } else if d3 < self.distance_tolerance_square {
876 self.points.push(PointD { x: x3, y: y3 });
877 return;
878 }
879 }
880
881 1 => {
882 if d3 * d3 <= self.distance_tolerance_square * (dx * dx + dy * dy) {
884 if self.angle_tolerance < CURVE_ANGLE_TOLERANCE_EPSILON {
885 self.points.push(PointD { x: x23, y: y23 });
886 return;
887 }
888
889 let mut da1 = ((y4 - y3).atan2(x4 - x3) - (y3 - y2).atan2(x3 - x2)).abs();
891 if da1 >= PI {
892 da1 = 2.0 * PI - da1;
893 }
894
895 if da1 < self.angle_tolerance {
896 self.points.push(PointD { x: x2, y: y2 });
897 self.points.push(PointD { x: x3, y: y3 });
898 return;
899 }
900
901 if self.cusp_limit != 0.0 && da1 > self.cusp_limit {
902 self.points.push(PointD { x: x3, y: y3 });
903 return;
904 }
905 }
906 }
907
908 2 => {
909 if d2 * d2 <= self.distance_tolerance_square * (dx * dx + dy * dy) {
911 if self.angle_tolerance < CURVE_ANGLE_TOLERANCE_EPSILON {
912 self.points.push(PointD { x: x23, y: y23 });
913 return;
914 }
915
916 let mut da1 = ((y3 - y2).atan2(x3 - x2) - (y2 - y1).atan2(x2 - x1)).abs();
918 if da1 >= PI {
919 da1 = 2.0 * PI - da1;
920 }
921
922 if da1 < self.angle_tolerance {
923 self.points.push(PointD { x: x2, y: y2 });
924 self.points.push(PointD { x: x3, y: y3 });
925 return;
926 }
927
928 if self.cusp_limit != 0.0 && da1 > self.cusp_limit {
929 self.points.push(PointD { x: x2, y: y2 });
930 return;
931 }
932 }
933 }
934
935 3 => {
936 if (d2 + d3) * (d2 + d3) <= self.distance_tolerance_square * (dx * dx + dy * dy) {
938 if self.angle_tolerance < CURVE_ANGLE_TOLERANCE_EPSILON {
939 self.points.push(PointD { x: x23, y: y23 });
940 return;
941 }
942
943 let k = (y3 - y2).atan2(x3 - x2);
945 let mut da1 = (k - (y2 - y1).atan2(x2 - x1)).abs();
946 let mut da2 = ((y4 - y3).atan2(x4 - x3) - k).abs();
947 if da1 >= PI {
948 da1 = 2.0 * PI - da1;
949 }
950 if da2 >= PI {
951 da2 = 2.0 * PI - da2;
952 }
953
954 if da1 + da2 < self.angle_tolerance {
955 self.points.push(PointD { x: x23, y: y23 });
956 return;
957 }
958
959 if self.cusp_limit != 0.0 {
960 if da1 > self.cusp_limit {
961 self.points.push(PointD { x: x2, y: y2 });
962 return;
963 }
964
965 if da2 > self.cusp_limit {
966 self.points.push(PointD { x: x3, y: y3 });
967 return;
968 }
969 }
970 }
971 }
972
973 _ => unreachable!(),
974 }
975
976 self.recursive_bezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1);
978 self.recursive_bezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1);
979 }
980}
981
982impl Default for Curve4Div {
983 fn default() -> Self {
984 Self::new()
985 }
986}
987
988impl VertexSource for Curve4Div {
989 fn rewind(&mut self, _path_id: u32) {
990 self.count = 0;
991 }
992
993 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
994 if self.count >= self.points.len() {
995 return PATH_CMD_STOP;
996 }
997 let p = &self.points[self.count];
998 *x = p.x;
999 *y = p.y;
1000 self.count += 1;
1001 if self.count == 1 {
1002 PATH_CMD_MOVE_TO
1003 } else {
1004 PATH_CMD_LINE_TO
1005 }
1006 }
1007}
1008
1009pub struct Curve3 {
1019 curve_inc: Curve3Inc,
1020 curve_div: Curve3Div,
1021 approximation_method: CurveApproximationMethod,
1022}
1023
1024impl Curve3 {
1025 pub fn new() -> Self {
1026 Self {
1027 curve_inc: Curve3Inc::new(),
1028 curve_div: Curve3Div::new(),
1029 approximation_method: CurveApproximationMethod::Div,
1030 }
1031 }
1032
1033 pub fn new_with_points(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {
1034 let mut c = Self::new();
1035 c.init(x1, y1, x2, y2, x3, y3);
1036 c
1037 }
1038
1039 pub fn reset(&mut self) {
1040 self.curve_inc.reset();
1041 self.curve_div.reset();
1042 }
1043
1044 pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) {
1045 if self.approximation_method == CurveApproximationMethod::Inc {
1046 self.curve_inc.init(x1, y1, x2, y2, x3, y3);
1047 } else {
1048 self.curve_div.init(x1, y1, x2, y2, x3, y3);
1049 }
1050 }
1051
1052 pub fn set_approximation_method(&mut self, v: CurveApproximationMethod) {
1053 self.approximation_method = v;
1054 }
1055
1056 pub fn approximation_method(&self) -> CurveApproximationMethod {
1057 self.approximation_method
1058 }
1059
1060 pub fn set_approximation_scale(&mut self, s: f64) {
1061 self.curve_inc.set_approximation_scale(s);
1062 self.curve_div.set_approximation_scale(s);
1063 }
1064
1065 pub fn approximation_scale(&self) -> f64 {
1066 self.curve_inc.approximation_scale()
1067 }
1068
1069 pub fn set_angle_tolerance(&mut self, a: f64) {
1070 self.curve_div.set_angle_tolerance(a);
1071 }
1072
1073 pub fn angle_tolerance(&self) -> f64 {
1074 self.curve_div.angle_tolerance()
1075 }
1076}
1077
1078impl Default for Curve3 {
1079 fn default() -> Self {
1080 Self::new()
1081 }
1082}
1083
1084impl VertexSource for Curve3 {
1085 fn rewind(&mut self, path_id: u32) {
1086 if self.approximation_method == CurveApproximationMethod::Inc {
1087 self.curve_inc.rewind(path_id);
1088 } else {
1089 self.curve_div.rewind(path_id);
1090 }
1091 }
1092
1093 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
1094 if self.approximation_method == CurveApproximationMethod::Inc {
1095 self.curve_inc.vertex(x, y)
1096 } else {
1097 self.curve_div.vertex(x, y)
1098 }
1099 }
1100}
1101
1102pub struct Curve4 {
1112 curve_inc: Curve4Inc,
1113 curve_div: Curve4Div,
1114 approximation_method: CurveApproximationMethod,
1115}
1116
1117impl Curve4 {
1118 pub fn new() -> Self {
1119 Self {
1120 curve_inc: Curve4Inc::new(),
1121 curve_div: Curve4Div::new(),
1122 approximation_method: CurveApproximationMethod::Div,
1123 }
1124 }
1125
1126 #[allow(clippy::too_many_arguments)]
1127 pub fn new_with_points(
1128 x1: f64,
1129 y1: f64,
1130 x2: f64,
1131 y2: f64,
1132 x3: f64,
1133 y3: f64,
1134 x4: f64,
1135 y4: f64,
1136 ) -> Self {
1137 let mut c = Self::new();
1138 c.init(x1, y1, x2, y2, x3, y3, x4, y4);
1139 c
1140 }
1141
1142 pub fn new_with_curve4_points(cp: &Curve4Points) -> Self {
1143 let mut c = Self::new();
1144 c.init(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
1145 c
1146 }
1147
1148 pub fn reset(&mut self) {
1149 self.curve_inc.reset();
1150 self.curve_div.reset();
1151 }
1152
1153 #[allow(clippy::too_many_arguments)]
1154 pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) {
1155 if self.approximation_method == CurveApproximationMethod::Inc {
1156 self.curve_inc.init(x1, y1, x2, y2, x3, y3, x4, y4);
1157 } else {
1158 self.curve_div.init(x1, y1, x2, y2, x3, y3, x4, y4);
1159 }
1160 }
1161
1162 pub fn init_with_curve4_points(&mut self, cp: &Curve4Points) {
1163 self.init(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
1164 }
1165
1166 pub fn set_approximation_method(&mut self, v: CurveApproximationMethod) {
1167 self.approximation_method = v;
1168 }
1169
1170 pub fn approximation_method(&self) -> CurveApproximationMethod {
1171 self.approximation_method
1172 }
1173
1174 pub fn set_approximation_scale(&mut self, s: f64) {
1175 self.curve_inc.set_approximation_scale(s);
1176 self.curve_div.set_approximation_scale(s);
1177 }
1178
1179 pub fn approximation_scale(&self) -> f64 {
1180 self.curve_inc.approximation_scale()
1181 }
1182
1183 pub fn set_angle_tolerance(&mut self, v: f64) {
1184 self.curve_div.set_angle_tolerance(v);
1185 }
1186
1187 pub fn angle_tolerance(&self) -> f64 {
1188 self.curve_div.angle_tolerance()
1189 }
1190
1191 pub fn set_cusp_limit(&mut self, v: f64) {
1192 self.curve_div.set_cusp_limit(v);
1193 }
1194
1195 pub fn cusp_limit(&self) -> f64 {
1196 self.curve_div.cusp_limit()
1197 }
1198}
1199
1200impl Default for Curve4 {
1201 fn default() -> Self {
1202 Self::new()
1203 }
1204}
1205
1206impl VertexSource for Curve4 {
1207 fn rewind(&mut self, path_id: u32) {
1208 if self.approximation_method == CurveApproximationMethod::Inc {
1209 self.curve_inc.rewind(path_id);
1210 } else {
1211 self.curve_div.rewind(path_id);
1212 }
1213 }
1214
1215 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
1216 if self.approximation_method == CurveApproximationMethod::Inc {
1217 self.curve_inc.vertex(x, y)
1218 } else {
1219 self.curve_div.vertex(x, y)
1220 }
1221 }
1222}
1223
1224#[cfg(test)]
1229mod tests {
1230 use super::*;
1231 use crate::basics::is_stop;
1232
1233 fn collect_vertices(vs: &mut dyn VertexSource) -> Vec<(f64, f64, u32)> {
1235 vs.rewind(0);
1236 let mut result = Vec::new();
1237 loop {
1238 let mut x = 0.0;
1239 let mut y = 0.0;
1240 let cmd = vs.vertex(&mut x, &mut y);
1241 if is_stop(cmd) {
1242 break;
1243 }
1244 result.push((x, y, cmd));
1245 }
1246 result
1247 }
1248
1249 #[test]
1252 fn test_curve3_inc_basic() {
1253 let mut c = Curve3Inc::new_with_points(0.0, 0.0, 50.0, 100.0, 100.0, 0.0);
1254 let verts = collect_vertices(&mut c);
1255 assert!(verts.len() >= 4);
1256 assert_eq!(verts[0].2, PATH_CMD_MOVE_TO);
1257 assert!((verts[0].0).abs() < 1e-6);
1258 assert!((verts[0].1).abs() < 1e-6);
1259 let last = &verts[verts.len() - 1];
1260 assert!((last.0 - 100.0).abs() < 1e-6);
1261 assert!((last.1).abs() < 1e-6);
1262 }
1263
1264 #[test]
1265 fn test_curve3_inc_reset() {
1266 let mut c = Curve3Inc::new();
1267 c.reset();
1268 let mut x = 0.0;
1269 let mut y = 0.0;
1270 c.rewind(0);
1271 let cmd = c.vertex(&mut x, &mut y);
1272 assert!(is_stop(cmd));
1273 }
1274
1275 #[test]
1276 fn test_curve3_inc_rewind_replays() {
1277 let mut c = Curve3Inc::new_with_points(0.0, 0.0, 50.0, 100.0, 100.0, 0.0);
1278 let verts1 = collect_vertices(&mut c);
1279 let verts2 = collect_vertices(&mut c);
1280 assert_eq!(verts1.len(), verts2.len());
1281 for (a, b) in verts1.iter().zip(verts2.iter()) {
1282 assert!((a.0 - b.0).abs() < 1e-10);
1283 assert!((a.1 - b.1).abs() < 1e-10);
1284 }
1285 }
1286
1287 #[test]
1288 fn test_curve3_inc_scale() {
1289 let mut c1 = Curve3Inc::new();
1290 c1.set_approximation_scale(1.0);
1291 c1.init(0.0, 0.0, 50.0, 100.0, 100.0, 0.0);
1292 let v1 = collect_vertices(&mut c1);
1293
1294 let mut c2 = Curve3Inc::new();
1295 c2.set_approximation_scale(4.0);
1296 c2.init(0.0, 0.0, 50.0, 100.0, 100.0, 0.0);
1297 let v2 = collect_vertices(&mut c2);
1298
1299 assert!(v2.len() > v1.len());
1300 }
1301
1302 #[test]
1305 fn test_curve3_div_basic() {
1306 let mut c = Curve3Div::new_with_points(0.0, 0.0, 50.0, 100.0, 100.0, 0.0);
1307 let verts = collect_vertices(&mut c);
1308 assert!(verts.len() >= 3);
1309 assert_eq!(verts[0].2, PATH_CMD_MOVE_TO);
1310 assert!((verts[0].0).abs() < 1e-6);
1311 let last = &verts[verts.len() - 1];
1312 assert!((last.0 - 100.0).abs() < 1e-6);
1313 assert!((last.1).abs() < 1e-6);
1314 }
1315
1316 #[test]
1317 fn test_curve3_div_straight_line() {
1318 let mut c = Curve3Div::new_with_points(0.0, 0.0, 50.0, 0.0, 100.0, 0.0);
1320 let verts = collect_vertices(&mut c);
1321 for v in &verts {
1323 assert!(v.1.abs() < 1e-6);
1324 }
1325 }
1326
1327 #[test]
1328 fn test_curve3_div_angle_tolerance() {
1329 let mut c = Curve3Div::new();
1330 c.set_angle_tolerance(0.1);
1331 assert!((c.angle_tolerance() - 0.1).abs() < 1e-10);
1332 c.init(0.0, 0.0, 50.0, 100.0, 100.0, 0.0);
1333 let verts = collect_vertices(&mut c);
1334 assert!(verts.len() >= 3);
1335 }
1336
1337 #[test]
1340 fn test_curve4_inc_basic() {
1341 let mut c = Curve4Inc::new_with_points(0.0, 0.0, 33.0, 100.0, 66.0, 100.0, 100.0, 0.0);
1342 let verts = collect_vertices(&mut c);
1343 assert!(verts.len() >= 4);
1344 assert_eq!(verts[0].2, PATH_CMD_MOVE_TO);
1345 assert!((verts[0].0).abs() < 1e-6);
1346 let last = &verts[verts.len() - 1];
1347 assert!((last.0 - 100.0).abs() < 1e-6);
1348 assert!((last.1).abs() < 1e-6);
1349 }
1350
1351 #[test]
1352 fn test_curve4_inc_reset() {
1353 let mut c = Curve4Inc::new();
1354 c.reset();
1355 c.rewind(0);
1356 let mut x = 0.0;
1357 let mut y = 0.0;
1358 let cmd = c.vertex(&mut x, &mut y);
1359 assert!(is_stop(cmd));
1360 }
1361
1362 #[test]
1363 fn test_curve4_inc_curve4_points() {
1364 let cp = Curve4Points::new(0.0, 0.0, 33.0, 100.0, 66.0, 100.0, 100.0, 0.0);
1365 let mut c = Curve4Inc::new_with_curve4_points(&cp);
1366 let verts = collect_vertices(&mut c);
1367 assert!(verts.len() >= 4);
1368 }
1369
1370 #[test]
1371 fn test_curve4_inc_scale() {
1372 let mut c1 = Curve4Inc::new();
1373 c1.set_approximation_scale(1.0);
1374 c1.init(0.0, 0.0, 33.0, 100.0, 66.0, 100.0, 100.0, 0.0);
1375 let v1 = collect_vertices(&mut c1);
1376
1377 let mut c2 = Curve4Inc::new();
1378 c2.set_approximation_scale(4.0);
1379 c2.init(0.0, 0.0, 33.0, 100.0, 66.0, 100.0, 100.0, 0.0);
1380 let v2 = collect_vertices(&mut c2);
1381
1382 assert!(v2.len() > v1.len());
1383 }
1384
1385 #[test]
1388 fn test_curve4_div_basic() {
1389 let mut c = Curve4Div::new_with_points(0.0, 0.0, 33.0, 100.0, 66.0, 100.0, 100.0, 0.0);
1390 let verts = collect_vertices(&mut c);
1391 assert!(verts.len() >= 3);
1392 assert_eq!(verts[0].2, PATH_CMD_MOVE_TO);
1393 let last = &verts[verts.len() - 1];
1394 assert!((last.0 - 100.0).abs() < 1e-6);
1395 assert!((last.1).abs() < 1e-6);
1396 }
1397
1398 #[test]
1399 fn test_curve4_div_straight_line() {
1400 let mut c = Curve4Div::new_with_points(0.0, 0.0, 33.0, 0.0, 66.0, 0.0, 100.0, 0.0);
1401 let verts = collect_vertices(&mut c);
1402 for v in &verts {
1403 assert!(v.1.abs() < 1e-6);
1404 }
1405 }
1406
1407 #[test]
1408 fn test_curve4_div_cusp_limit() {
1409 let mut c = Curve4Div::new();
1410 c.set_cusp_limit(0.0);
1411 assert!((c.cusp_limit() - 0.0).abs() < 1e-10);
1412 c.set_cusp_limit(0.5);
1413 assert!((c.cusp_limit() - 0.5).abs() < 1e-10);
1414 }
1415
1416 #[test]
1417 fn test_curve4_div_curve4_points() {
1418 let cp = Curve4Points::new(0.0, 0.0, 33.0, 100.0, 66.0, 100.0, 100.0, 0.0);
1419 let mut c = Curve4Div::new_with_curve4_points(&cp);
1420 let verts = collect_vertices(&mut c);
1421 assert!(verts.len() >= 3);
1422 }
1423
1424 #[test]
1427 fn test_curve3_facade_default_div() {
1428 let c = Curve3::new();
1429 assert_eq!(c.approximation_method(), CurveApproximationMethod::Div);
1430 }
1431
1432 #[test]
1433 fn test_curve3_facade_switch_method() {
1434 let mut c = Curve3::new();
1435 c.set_approximation_method(CurveApproximationMethod::Inc);
1436 c.init(0.0, 0.0, 50.0, 100.0, 100.0, 0.0);
1437 let v_inc = collect_vertices(&mut c);
1438
1439 c.set_approximation_method(CurveApproximationMethod::Div);
1440 c.init(0.0, 0.0, 50.0, 100.0, 100.0, 0.0);
1441 let v_div = collect_vertices(&mut c);
1442
1443 assert!(v_inc.len() >= 3);
1445 assert!(v_div.len() >= 3);
1446 assert!((v_inc[0].0).abs() < 1e-6);
1448 assert!((v_div[0].0).abs() < 1e-6);
1449 }
1450
1451 #[test]
1454 fn test_curve4_facade_default_div() {
1455 let c = Curve4::new();
1456 assert_eq!(c.approximation_method(), CurveApproximationMethod::Div);
1457 }
1458
1459 #[test]
1460 fn test_curve4_facade_switch_method() {
1461 let mut c = Curve4::new();
1462 c.set_approximation_method(CurveApproximationMethod::Inc);
1463 c.init(0.0, 0.0, 33.0, 100.0, 66.0, 100.0, 100.0, 0.0);
1464 let v_inc = collect_vertices(&mut c);
1465
1466 c.set_approximation_method(CurveApproximationMethod::Div);
1467 c.init(0.0, 0.0, 33.0, 100.0, 66.0, 100.0, 100.0, 0.0);
1468 let v_div = collect_vertices(&mut c);
1469
1470 assert!(v_inc.len() >= 4);
1471 assert!(v_div.len() >= 3);
1472 }
1473
1474 #[test]
1477 fn test_curve4_points_index() {
1478 let cp = Curve4Points::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);
1479 assert_eq!(cp[0], 1.0);
1480 assert_eq!(cp[1], 2.0);
1481 assert_eq!(cp[6], 7.0);
1482 assert_eq!(cp[7], 8.0);
1483 }
1484
1485 #[test]
1486 fn test_curve4_points_init() {
1487 let mut cp = Curve4Points::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1488 cp.init(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);
1489 assert_eq!(cp[4], 5.0);
1490 }
1491
1492 #[test]
1495 fn test_catrom_to_bezier() {
1496 let cp = catrom_to_bezier(0.0, 0.0, 10.0, 0.0, 20.0, 0.0, 30.0, 0.0);
1497 assert!((cp[0] - 10.0).abs() < 1e-6);
1499 assert!(cp[1].abs() < 1e-6);
1500 assert!((cp[6] - 20.0).abs() < 1e-6);
1502 assert!(cp[7].abs() < 1e-6);
1503 }
1504
1505 #[test]
1506 fn test_ubspline_to_bezier() {
1507 let cp = ubspline_to_bezier(0.0, 0.0, 10.0, 0.0, 20.0, 0.0, 30.0, 0.0);
1508 assert!(cp[0] > 0.0 && cp[0] < 30.0);
1510 assert!(cp[6] > 0.0 && cp[6] < 30.0);
1511 }
1512
1513 #[test]
1514 fn test_hermite_to_bezier() {
1515 let cp = hermite_to_bezier(0.0, 0.0, 100.0, 0.0, 30.0, 0.0, 30.0, 0.0);
1516 assert!(cp[0].abs() < 1e-6);
1518 assert!(cp[1].abs() < 1e-6);
1519 assert!((cp[6] - 100.0).abs() < 1e-6);
1521 assert!(cp[7].abs() < 1e-6);
1522 }
1523
1524 #[test]
1525 fn test_curve3_inc_and_div_same_endpoints() {
1526 let mut inc = Curve3Inc::new_with_points(10.0, 20.0, 50.0, 80.0, 90.0, 20.0);
1528 let mut div = Curve3Div::new_with_points(10.0, 20.0, 50.0, 80.0, 90.0, 20.0);
1529 let vi = collect_vertices(&mut inc);
1530 let vd = collect_vertices(&mut div);
1531
1532 assert!((vi[0].0 - 10.0).abs() < 1e-6);
1534 assert!((vi[0].1 - 20.0).abs() < 1e-6);
1535 assert!((vd[0].0 - 10.0).abs() < 1e-6);
1536 assert!((vd[0].1 - 20.0).abs() < 1e-6);
1537
1538 let li = &vi[vi.len() - 1];
1540 let ld = &vd[vd.len() - 1];
1541 assert!((li.0 - 90.0).abs() < 1e-6);
1542 assert!((li.1 - 20.0).abs() < 1e-6);
1543 assert!((ld.0 - 90.0).abs() < 1e-6);
1544 assert!((ld.1 - 20.0).abs() < 1e-6);
1545 }
1546
1547 #[test]
1548 fn test_curve4_inc_and_div_same_endpoints() {
1549 let mut inc = Curve4Inc::new_with_points(10.0, 20.0, 30.0, 80.0, 70.0, 80.0, 90.0, 20.0);
1550 let mut div = Curve4Div::new_with_points(10.0, 20.0, 30.0, 80.0, 70.0, 80.0, 90.0, 20.0);
1551 let vi = collect_vertices(&mut inc);
1552 let vd = collect_vertices(&mut div);
1553
1554 assert!((vi[0].0 - 10.0).abs() < 1e-6);
1555 assert!((vi[0].1 - 20.0).abs() < 1e-6);
1556 assert!((vd[0].0 - 10.0).abs() < 1e-6);
1557 assert!((vd[0].1 - 20.0).abs() < 1e-6);
1558
1559 let li = &vi[vi.len() - 1];
1560 let ld = &vd[vd.len() - 1];
1561 assert!((li.0 - 90.0).abs() < 1e-6);
1562 assert!((li.1 - 20.0).abs() < 1e-6);
1563 assert!((ld.0 - 90.0).abs() < 1e-6);
1564 assert!((ld.1 - 20.0).abs() < 1e-6);
1565 }
1566}