1use alloc::vec;
7use alloc::vec::Vec;
8use core::ops::{Mul, Range};
9
10use crate::MAX_EXTREMA;
11use crate::{Line, QuadSpline, Vec2};
12use arrayvec::ArrayVec;
13
14use crate::common::{
15 GAUSS_LEGENDRE_COEFFS_8, GAUSS_LEGENDRE_COEFFS_8_HALF, GAUSS_LEGENDRE_COEFFS_16_HALF,
16 GAUSS_LEGENDRE_COEFFS_24_HALF, solve_cubic, solve_quadratic, solve_quartic,
17};
18use crate::{
19 Affine, Nearest, ParamCurve, ParamCurveArclen, ParamCurveArea, ParamCurveCurvature,
20 ParamCurveDeriv, ParamCurveExtrema, ParamCurveNearest, PathEl, Point, QuadBez, Rect, Shape,
21};
22
23#[cfg(not(feature = "std"))]
24use crate::common::FloatFuncs;
25
26const MAX_SPLINE_SPLIT: usize = 100;
27
28#[derive(Clone, Copy, Debug, PartialEq)]
30#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[allow(missing_docs)]
33pub struct CubicBez {
34 pub p0: Point,
35 pub p1: Point,
36 pub p2: Point,
37 pub p3: Point,
38}
39
40struct ToQuads {
42 c: CubicBez,
43 i: usize,
44 n: usize,
45}
46
47#[derive(Debug)]
49pub enum CuspType {
50 Loop,
52 DoubleInflection,
54}
55
56impl CubicBez {
57 #[inline(always)]
59 pub fn new<P: Into<Point>>(p0: P, p1: P, p2: P, p3: P) -> CubicBez {
60 CubicBez {
61 p0: p0.into(),
62 p1: p1.into(),
63 p2: p2.into(),
64 p3: p3.into(),
65 }
66 }
67
68 #[inline]
78 pub fn to_quads(&self, accuracy: f64) -> impl Iterator<Item = (f64, f64, QuadBez)> {
79 let max_hypot2 = 432.0 * accuracy * accuracy;
91 let p1x2 = 3.0 * self.p1.to_vec2() - self.p0.to_vec2();
92 let p2x2 = 3.0 * self.p2.to_vec2() - self.p3.to_vec2();
93 let err = (p2x2 - p1x2).hypot2();
94 let n = ((err / max_hypot2).powf(1. / 6.0).ceil() as usize).max(1);
95
96 ToQuads { c: *self, n, i: 0 }
97 }
98
99 pub fn approx_spline(&self, accuracy: f64) -> Option<QuadSpline> {
104 (1..=MAX_SPLINE_SPLIT).find_map(|n| self.approx_spline_n(n, accuracy))
105 }
106
107 fn approx_spline_n(&self, n: usize, accuracy: f64) -> Option<QuadSpline> {
109 if n == 1 {
110 return self
111 .try_approx_quadratic(accuracy)
112 .map(|quad| QuadSpline::new(vec![quad.p0, quad.p1, quad.p2]));
113 }
114 let mut cubics = self.split_into_n(n);
115
116 let mut next_cubic = cubics.next().unwrap();
119 let mut next_q1: Point = next_cubic.approx_quad_control(0.0);
120 let mut q2 = self.p0;
121 let mut d1 = Vec2::ZERO;
122 let mut spline = vec![self.p0, next_q1];
123 for i in 1..=n {
124 let current_cubic: CubicBez = next_cubic;
125 let q0 = q2;
126 let q1 = next_q1;
127 q2 = if i < n {
128 next_cubic = cubics.next().unwrap();
129 next_q1 = next_cubic.approx_quad_control(i as f64 / (n - 1) as f64);
130
131 spline.push(next_q1);
132 q1.midpoint(next_q1)
133 } else {
134 current_cubic.p3
135 };
136 let d0 = d1;
137 d1 = q2.to_vec2() - current_cubic.p3.to_vec2();
138
139 if d1.hypot() > accuracy
140 || !CubicBez::new(
141 d0.to_point(),
142 q0.lerp(q1, 2.0 / 3.0) - current_cubic.p1.to_vec2(),
143 q2.lerp(q1, 2.0 / 3.0) - current_cubic.p2.to_vec2(),
144 d1.to_point(),
145 )
146 .fit_inside(accuracy)
147 {
148 return None;
149 }
150 }
151 spline.push(self.p3);
152 Some(QuadSpline::new(spline))
153 }
154
155 fn approx_quad_control(&self, t: f64) -> Point {
156 let p1 = self.p0 + (self.p1 - self.p0) * 1.5;
157 let p2 = self.p3 + (self.p2 - self.p3) * 1.5;
158 p1.lerp(p2, t)
159 }
160
161 fn try_approx_quadratic(&self, accuracy: f64) -> Option<QuadBez> {
166 if let Some(q1) = Line::new(self.p0, self.p1)
167 .crossing_point(Line::new(self.p2, self.p3))
168 .or_else(|| {
169 (self.p1 == self.p2 && (self.p0 == self.p1 || self.p2 == self.p3))
173 .then_some(self.p1)
174 })
175 {
176 let c1 = self.p0.lerp(q1, 2.0 / 3.0);
177 let c2 = self.p3.lerp(q1, 2.0 / 3.0);
178 if !CubicBez::new(
179 Point::ZERO,
180 c1 - self.p1.to_vec2(),
181 c2 - self.p2.to_vec2(),
182 Point::ZERO,
183 )
184 .fit_inside(accuracy)
185 {
186 return None;
187 }
188 return Some(QuadBez::new(self.p0, q1, self.p3));
189 }
190 None
191 }
192
193 fn split_into_n(&self, n: usize) -> impl Iterator<Item = CubicBez> {
194 let mut storage = ArrayVec::<_, 6>::new();
197
198 match n {
199 1 => storage.push(*self),
200 2 => {
201 let (l, r) = self.subdivide();
202 storage.try_extend_from_slice(&[r, l]).unwrap();
203 }
204 3 => {
205 let (left, mid, right) = self.subdivide_3();
206 storage.try_extend_from_slice(&[right, mid, left]).unwrap();
207 }
208 4 => {
209 let (l, r) = self.subdivide();
210 let (ll, lr) = l.subdivide();
211 let (rl, rr) = r.subdivide();
212 storage.try_extend_from_slice(&[rr, rl, lr, ll]).unwrap();
213 }
214 6 => {
215 let (l, r) = self.subdivide();
216 let (l1, l2, l3) = l.subdivide_3();
217 let (r1, r2, r3) = r.subdivide_3();
218 storage
219 .try_extend_from_slice(&[r3, r2, r1, l3, l2, l1])
220 .unwrap();
221 }
222 _ => (),
223 }
224
225 let mut storage = if storage.is_empty() {
234 None
235 } else {
236 Some(storage)
237 };
238
239 let mut i = 0;
241 let (a, b, c, d) = self.parameters();
242 let dt = 1.0 / n as f64;
243 let delta_2 = dt * dt;
244 let delta_3 = dt * delta_2;
245
246 core::iter::from_fn(move || {
247 if let Some(storage) = storage.as_mut() {
249 return storage.pop();
250 }
251
252 if i >= n {
254 return None;
255 }
256
257 let t1 = i as f64 * dt;
258 let t1_2 = t1 * t1;
259 let a1 = a * delta_3;
260 let b1 = (3.0 * a * t1 + b) * delta_2;
261 let c1 = (2.0 * b * t1 + c + 3.0 * a * t1_2) * dt;
262 let d1 = a * t1 * t1_2 + b * t1_2 + c * t1 + d;
263 let result = CubicBez::from_parameters(a1, b1, c1, d1);
264 i += 1;
265 Some(result)
266 })
267 }
268
269 fn parameters(&self) -> (Vec2, Vec2, Vec2, Vec2) {
270 let c = (self.p1 - self.p0) * 3.0;
271 let b = (self.p2 - self.p1) * 3.0 - c;
272 let d = self.p0.to_vec2();
273 let a = self.p3.to_vec2() - d - c - b;
274 (a, b, c, d)
275 }
276
277 fn from_parameters(a: Vec2, b: Vec2, c: Vec2, d: Vec2) -> Self {
279 let p0 = d.to_point();
280 let p1 = c.div_exact(3.0).to_point() + d;
281 let p2 = (b + c).div_exact(3.0).to_point() + p1.to_vec2();
282 let p3 = (a + d + c + b).to_point();
283 CubicBez::new(p0, p1, p2, p3)
284 }
285
286 fn subdivide_3(&self) -> (CubicBez, CubicBez, CubicBez) {
287 let (p0, p1, p2, p3) = (
288 self.p0.to_vec2(),
289 self.p1.to_vec2(),
290 self.p2.to_vec2(),
291 self.p3.to_vec2(),
292 );
293 let one_27th = 27.0_f64.recip();
301 let mid1 = ((8.0 * p0 + 12.0 * p1 + 6.0 * p2 + p3) * one_27th).to_point();
302 let deriv1 = (p3 + 3.0 * p2 - 4.0 * p0) * one_27th;
303 let mid2 = ((p0 + 6.0 * p1 + 12.0 * p2 + 8.0 * p3) * one_27th).to_point();
304 let deriv2 = (4.0 * p3 - 3.0 * p1 - p0) * one_27th;
305
306 let left = CubicBez::new(
307 self.p0,
308 (2.0 * p0 + p1).div_exact(3.0).to_point(),
309 mid1 - deriv1,
310 mid1,
311 );
312 let mid = CubicBez::new(mid1, mid1 + deriv1, mid2 - deriv2, mid2);
313 let right = CubicBez::new(
314 mid2,
315 mid2 + deriv2,
316 (p2 + 2.0 * p3).div_exact(3.0).to_point(),
317 self.p3,
318 );
319 (left, mid, right)
320 }
321
322 fn fit_inside(&self, distance: f64) -> bool {
326 if self.p2.to_vec2().hypot() <= distance && self.p1.to_vec2().hypot() <= distance {
327 return true;
328 }
329 let mid =
330 (self.p0.to_vec2() + 3.0 * (self.p1.to_vec2() + self.p2.to_vec2()) + self.p3.to_vec2())
331 * 0.125;
332 if mid.hypot() > distance {
333 return false;
334 }
335 let (left, right) = self.subdivide();
337 left.fit_inside(distance) && right.fit_inside(distance)
338 }
339
340 #[inline]
342 pub const fn is_finite(&self) -> bool {
343 self.p0.is_finite() && self.p1.is_finite() && self.p2.is_finite() && self.p3.is_finite()
344 }
345
346 #[inline]
348 pub const fn is_nan(&self) -> bool {
349 self.p0.is_nan() || self.p1.is_nan() || self.p2.is_nan() || self.p3.is_nan()
350 }
351
352 pub fn inflections(&self) -> ArrayVec<f64, 2> {
360 let a = self.p1 - self.p0;
361 let b = (self.p2 - self.p1) - a;
362 let c = (self.p3 - self.p0) - 3. * (self.p2 - self.p1);
363 solve_quadratic(a.cross(b), a.cross(c), b.cross(c))
364 .iter()
365 .copied()
366 .filter(|t| *t >= 0.0 && *t <= 1.0)
367 .collect()
368 }
369
370 pub fn tangents_to_point(&self, p: Point) -> ArrayVec<f64, 4> {
376 let (a, b, c, d_orig) = self.parameters();
377 let d = d_orig - p.to_vec2();
378 let c4 = b.cross(a);
380 let c3 = 2.0 * c.cross(a);
381 let c2 = c.cross(b) + 3.0 * d.cross(a);
382 let c1 = 2.0 * d.cross(b);
383 let c0 = d.cross(c);
384 solve_quartic(c0, c1, c2, c3, c4)
385 .iter()
386 .copied()
387 .filter(|t| *t >= 0.0 && *t <= 1.0)
388 .collect()
389 }
390
391 pub(crate) fn regularize_cusp(&self, dimension: f64) -> CubicBez {
397 let mut c = *self;
398 if let Some(cusp_type) = self.detect_cusp(dimension) {
401 let d01 = c.p1 - c.p0;
402 let d01h = d01.hypot();
403 let d23 = c.p3 - c.p2;
404 let d23h = d23.hypot();
405 match cusp_type {
406 CuspType::Loop => {
407 c.p1 += (dimension / d01h) * d01;
408 c.p2 -= (dimension / d23h) * d23;
409 }
410 CuspType::DoubleInflection => {
411 if d01h > 2.0 * dimension {
413 c.p1 -= (dimension / d01h) * d01;
414 }
415 if d23h > 2.0 * dimension {
416 c.p2 += (dimension / d23h) * d23;
417 }
418 }
419 }
420 }
421 c
422 }
423
424 fn detect_cusp(&self, dimension: f64) -> Option<CuspType> {
429 let d01 = self.p1 - self.p0;
430 let d02 = self.p2 - self.p0;
431 let d03 = self.p3 - self.p0;
432 let d12 = self.p2 - self.p1;
433 let d23 = self.p3 - self.p2;
434 let det_012 = d01.cross(d02);
435 let det_123 = d12.cross(d23);
436 let det_013 = d01.cross(d03);
437 let det_023 = d02.cross(d03);
438 if det_012 * det_123 > 0.0 && det_012 * det_013 < 0.0 && det_012 * det_023 < 0.0 {
439 let q = self.deriv();
440 let nearest = q.nearest(Point::ORIGIN, 1e-9);
442 let d = q.eval(nearest.t);
445 let d2 = q.deriv().eval(nearest.t);
446 let cross = d.to_vec2().cross(d2.to_vec2());
447 if nearest.distance_sq.powi(3) <= (cross * dimension).powi(2) {
448 let a = 3. * det_012 + det_023 - 2. * det_013;
449 let b = -3. * det_012 + det_013;
450 let c = det_012;
451 let d = b * b - 4. * a * c;
452 if d > 0.0 {
453 return Some(CuspType::DoubleInflection);
454 } else {
455 return Some(CuspType::Loop);
456 }
457 }
458 }
459 None
460 }
461
462 pub(crate) fn solve_monotonic_for_y(&self, y: f64) -> f64 {
467 let start = self.start();
468 let end = self.end();
469
470 debug_assert!(start.y.min(end.y) <= y && y <= start.y.max(end.y));
471
472 let p1 = self.p1;
473 let p2 = self.p2;
474 let a = end.y - 3.0 * p2.y + 3.0 * p1.y - start.y;
475 let b = 3.0 * (p2.y - 2.0 * p1.y + start.y);
476 let c = 3.0 * (p1.y - start.y);
477 let d = start.y - y;
478 for t in solve_cubic(d, c, b, a) {
479 if (0.0..=1.0).contains(&t) {
480 return t;
481 }
482 }
483
484 if (start.y - y).abs() <= (end.y - y).abs() {
490 0.0
491 } else {
492 1.0
493 }
494 }
495}
496
497pub struct CubicBezIter {
499 cubic: CubicBez,
500 ix: usize,
501}
502
503impl Shape for CubicBez {
504 type PathElementsIter<'iter> = CubicBezIter;
505
506 #[inline]
507 fn path_elements(&self, _tolerance: f64) -> CubicBezIter {
508 CubicBezIter {
509 cubic: *self,
510 ix: 0,
511 }
512 }
513
514 #[inline(always)]
515 fn area(&self) -> f64 {
516 0.0
517 }
518
519 #[inline]
520 fn perimeter(&self, accuracy: f64) -> f64 {
521 self.arclen(accuracy)
522 }
523
524 #[inline(always)]
525 fn winding(&self, _pt: Point) -> i32 {
526 0
527 }
528
529 #[inline]
530 fn bounding_box(&self) -> Rect {
531 ParamCurveExtrema::bounding_box(self)
532 }
533}
534
535impl Iterator for CubicBezIter {
536 type Item = PathEl;
537
538 fn next(&mut self) -> Option<PathEl> {
539 self.ix += 1;
540 match self.ix {
541 1 => Some(PathEl::MoveTo(self.cubic.p0)),
542 2 => Some(PathEl::CurveTo(self.cubic.p1, self.cubic.p2, self.cubic.p3)),
543 _ => None,
544 }
545 }
546}
547
548impl ParamCurve for CubicBez {
549 #[inline]
550 fn eval(&self, t: f64) -> Point {
551 let mt = 1.0 - t;
552 let v = self.p0.to_vec2() * (mt * mt * mt)
553 + (self.p1.to_vec2() * (mt * mt * 3.0)
554 + (self.p2.to_vec2() * (mt * 3.0) + self.p3.to_vec2() * t) * t)
555 * t;
556 v.to_point()
557 }
558
559 fn subsegment(&self, range: Range<f64>) -> CubicBez {
560 let (t0, t1) = (range.start, range.end);
561 let p0 = self.eval(t0);
562 let p3 = self.eval(t1);
563 let d = self.deriv();
564 let scale = (t1 - t0) * (1.0 / 3.0);
565 let p1 = p0 + scale * d.eval(t0).to_vec2();
566 let p2 = p3 - scale * d.eval(t1).to_vec2();
567 CubicBez { p0, p1, p2, p3 }
568 }
569
570 #[inline]
572 fn subdivide(&self) -> (CubicBez, CubicBez) {
573 let pm = self.eval(0.5);
574 (
575 CubicBez::new(
576 self.p0,
577 self.p0.midpoint(self.p1),
578 ((self.p0.to_vec2() + self.p1.to_vec2() * 2.0 + self.p2.to_vec2()) * 0.25)
579 .to_point(),
580 pm,
581 ),
582 CubicBez::new(
583 pm,
584 ((self.p1.to_vec2() + self.p2.to_vec2() * 2.0 + self.p3.to_vec2()) * 0.25)
585 .to_point(),
586 self.p2.midpoint(self.p3),
587 self.p3,
588 ),
589 )
590 }
591
592 #[inline(always)]
593 fn start(&self) -> Point {
594 self.p0
595 }
596
597 #[inline(always)]
598 fn end(&self) -> Point {
599 self.p3
600 }
601}
602
603impl ParamCurveDeriv for CubicBez {
604 type DerivResult = QuadBez;
605
606 #[inline]
607 fn deriv(&self) -> QuadBez {
608 QuadBez::new(
609 (3.0 * (self.p1 - self.p0)).to_point(),
610 (3.0 * (self.p2 - self.p1)).to_point(),
611 (3.0 * (self.p3 - self.p2)).to_point(),
612 )
613 }
614}
615
616fn arclen_quadrature_core(coeffs: &[(f64, f64)], dm: Vec2, dm1: Vec2, dm2: Vec2) -> f64 {
617 coeffs
618 .iter()
619 .map(|&(wi, xi)| {
620 let d = dm + dm2 * (xi * xi);
621 let dpx = (d + dm1 * xi).hypot();
622 let dmx = (d - dm1 * xi).hypot();
623 (2.25f64.sqrt() * wi) * (dpx + dmx)
624 })
625 .sum::<f64>()
626}
627
628fn arclen_rec(c: &CubicBez, accuracy: f64, depth: usize) -> f64 {
629 let d03 = c.p3 - c.p0;
630 let d01 = c.p1 - c.p0;
631 let d12 = c.p2 - c.p1;
632 let d23 = c.p3 - c.p2;
633 let lp_lc = d01.hypot() + d12.hypot() + d23.hypot() - d03.hypot();
634 let dd1 = d12 - d01;
635 let dd2 = d23 - d12;
636 let dm = 0.25 * (d01 + d23) + 0.5 * d12; let dm1 = 0.5 * (dd2 + dd1); let dm2 = 0.25 * (dd2 - dd1); let est = GAUSS_LEGENDRE_COEFFS_8
643 .iter()
644 .map(|&(wi, xi)| {
645 wi * {
646 let d_norm2 = (dm + dm1 * xi + dm2 * (xi * xi)).hypot2();
647 let dd_norm2 = (dm1 + dm2 * (2.0 * xi)).hypot2();
648 dd_norm2 / d_norm2
649 }
650 })
651 .sum::<f64>();
652 let est_gauss8_error = (est.powi(3) * 2.5e-6).min(3e-2) * lp_lc;
653 if est_gauss8_error < accuracy {
654 return arclen_quadrature_core(GAUSS_LEGENDRE_COEFFS_8_HALF, dm, dm1, dm2);
655 }
656 let est_gauss16_error = (est.powi(6) * 1.5e-11).min(9e-3) * lp_lc;
657 if est_gauss16_error < accuracy {
658 return arclen_quadrature_core(GAUSS_LEGENDRE_COEFFS_16_HALF, dm, dm1, dm2);
659 }
660 let est_gauss24_error = (est.powi(9) * 3.5e-16).min(3.5e-3) * lp_lc;
661 if est_gauss24_error < accuracy || depth >= 20 {
662 return arclen_quadrature_core(GAUSS_LEGENDRE_COEFFS_24_HALF, dm, dm1, dm2);
663 }
664 let (c0, c1) = c.subdivide();
665 arclen_rec(&c0, accuracy * 0.5, depth + 1) + arclen_rec(&c1, accuracy * 0.5, depth + 1)
666}
667
668impl ParamCurveArclen for CubicBez {
669 fn arclen(&self, accuracy: f64) -> f64 {
674 arclen_rec(self, accuracy, 0)
675 }
676}
677
678impl ParamCurveArea for CubicBez {
679 #[inline]
680 fn signed_area(&self) -> f64 {
681 (self.p0.x * (6.0 * self.p1.y + 3.0 * self.p2.y + self.p3.y)
682 + 3.0
683 * (self.p1.x * (-2.0 * self.p0.y + self.p2.y + self.p3.y)
684 - self.p2.x * (self.p0.y + self.p1.y - 2.0 * self.p3.y))
685 - self.p3.x * (self.p0.y + 3.0 * self.p1.y + 6.0 * self.p2.y))
686 * (1.0 / 20.0)
687 }
688}
689
690impl ParamCurveNearest for CubicBez {
691 fn nearest(&self, p: Point, accuracy: f64) -> Nearest {
696 fn eval_t(p: Point, t_best: &mut f64, r_best: &mut Option<f64>, t: f64, p0: Point) {
697 let r = (p0 - p).hypot2();
698 if r_best.map(|r_best| r < r_best).unwrap_or(true) {
699 *r_best = Some(r);
700 *t_best = t;
701 }
702 }
703
704 let mut r_best = None;
705 let mut t_best = 0.0;
706
707 let q0 = self.p0 - p;
709 let q1 = 3.0 * (self.p1 - self.p0);
710 let q2 = 3.0 * (self.p0.to_vec2() - 2.0 * self.p1.to_vec2() + self.p2.to_vec2());
711 let q3 = -self.p0.to_vec2() + 3.0 * self.p1.to_vec2() - 3.0 * self.p2.to_vec2()
712 + self.p3.to_vec2();
713
714 let c0 = q0.dot(q1);
716 let c1 = q1.hypot2() + 2.0 * q2.dot(q0);
717 let c2 = 3.0 * (q2.dot(q1) + q3.dot(q0));
718 let c3 = 4.0 * q3.dot(q1) + 2.0 * q2.hypot2();
719 let c4 = 5.0 * q3.dot(q2);
720 let c5 = 3.0 * q3.hypot2();
721
722 let roots = polycool::Poly::new([c0, c1, c2, c3, c4, c5]).roots_between(0.0, 1.0, accuracy);
723
724 for &t in &roots {
725 eval_t(p, &mut t_best, &mut r_best, t, self.eval(t));
726 }
727
728 if roots.len() != 5 {
730 eval_t(p, &mut t_best, &mut r_best, 0.0, self.p0);
731 eval_t(p, &mut t_best, &mut r_best, 1.0, self.p3);
732 }
733
734 Nearest {
735 t: t_best,
736 distance_sq: r_best.unwrap(),
737 }
738 }
739}
740
741impl ParamCurveCurvature for CubicBez {}
742
743impl ParamCurveExtrema for CubicBez {
744 fn extrema(&self) -> ArrayVec<f64, MAX_EXTREMA> {
745 fn one_coord(result: &mut ArrayVec<f64, MAX_EXTREMA>, d0: f64, d1: f64, d2: f64) {
746 let a = d0 - 2.0 * d1 + d2;
747 let b = 2.0 * (d1 - d0);
748 let c = d0;
749 let roots = solve_quadratic(c, b, a);
750 for &t in &roots {
751 if t > 0.0 && t < 1.0 {
752 result.push(t);
753 }
754 }
755 }
756 let mut result = ArrayVec::new();
757 let d0 = self.p1 - self.p0;
758 let d1 = self.p2 - self.p1;
759 let d2 = self.p3 - self.p2;
760 one_coord(&mut result, d0.x, d1.x, d2.x);
761 one_coord(&mut result, d0.y, d1.y, d2.y);
762 result.sort_by(|a, b| a.partial_cmp(b).unwrap());
763 result
764 }
765}
766
767impl Mul<CubicBez> for Affine {
768 type Output = CubicBez;
769
770 #[inline]
771 fn mul(self, c: CubicBez) -> CubicBez {
772 CubicBez {
773 p0: self * c.p0,
774 p1: self * c.p1,
775 p2: self * c.p2,
776 p3: self * c.p3,
777 }
778 }
779}
780
781impl Iterator for ToQuads {
782 type Item = (f64, f64, QuadBez);
783
784 #[inline]
785 fn next(&mut self) -> Option<(f64, f64, QuadBez)> {
786 if self.i == self.n {
787 return None;
788 }
789 let t0 = self.i as f64 / self.n as f64;
790 let t1 = (self.i + 1) as f64 / self.n as f64;
791 let seg = self.c.subsegment(t0..t1);
792 let p1x2 = 3.0 * seg.p1.to_vec2() - seg.p0.to_vec2();
793 let p2x2 = 3.0 * seg.p2.to_vec2() - seg.p3.to_vec2();
794 let result = QuadBez::new(seg.p0, ((p1x2 + p2x2) / 4.0).to_point(), seg.p3);
795 self.i += 1;
796 Some((t0, t1, result))
797 }
798
799 fn size_hint(&self) -> (usize, Option<usize>) {
800 let remaining = self.n - self.i;
801 (remaining, Some(remaining))
802 }
803}
804
805pub fn cubics_to_quadratic_splines(curves: &[CubicBez], accuracy: f64) -> Option<Vec<QuadSpline>> {
811 let mut result = Vec::new();
812 let mut split_order = 0;
813
814 while split_order <= MAX_SPLINE_SPLIT {
815 split_order += 1;
816 result.clear();
817
818 for curve in curves {
819 match curve.approx_spline_n(split_order, accuracy) {
820 Some(spline) => result.push(spline),
821 None => break,
822 }
823 }
824
825 if result.len() == curves.len() {
826 return Some(result);
827 }
828 }
829 None
830}
831#[cfg(test)]
832mod tests {
833 use crate::{
834 Affine, CubicBez, Nearest, ParamCurve, ParamCurveArclen, ParamCurveArea, ParamCurveDeriv,
835 ParamCurveExtrema, ParamCurveNearest, Point, QuadBez, QuadSpline,
836 cubics_to_quadratic_splines,
837 };
838
839 #[test]
840 fn cubicbez_deriv() {
841 let c = CubicBez::new(
843 (0.0, 0.0),
844 (1.0 / 3.0, 0.0),
845 (2.0 / 3.0, 1.0 / 3.0),
846 (1.0, 1.0),
847 );
848 let deriv = c.deriv();
849
850 let n = 10;
851 for i in 0..=n {
852 let t = (i as f64) * (n as f64).recip();
853 let delta = 1e-6;
854 let p = c.eval(t);
855 let p1 = c.eval(t + delta);
856 let d_approx = (p1 - p) * delta.recip();
857 let d = deriv.eval(t).to_vec2();
858 assert!((d - d_approx).hypot() < delta * 2.0);
859 }
860 }
861
862 #[test]
863 fn cubicbez_arclen() {
864 let c = CubicBez::new(
866 (0.0, 0.0),
867 (1.0 / 3.0, 0.0),
868 (2.0 / 3.0, 1.0 / 3.0),
869 (1.0, 1.0),
870 );
871 let true_arclen = 0.5 * 5.0f64.sqrt() + 0.25 * (2.0 + 5.0f64.sqrt()).ln();
872 for i in 0..12 {
873 let accuracy = 0.1f64.powi(i);
874 let error = c.arclen(accuracy) - true_arclen;
875 assert!(error.abs() < accuracy);
876 }
877 }
878
879 #[test]
880 fn cubicbez_inv_arclen() {
881 let c = CubicBez::new(
883 (0.0, 0.0),
884 (100.0 / 3.0, 0.0),
885 (200.0 / 3.0, 100.0 / 3.0),
886 (100.0, 100.0),
887 );
888 let true_arclen = 100.0 * (0.5 * 5.0f64.sqrt() + 0.25 * (2.0 + 5.0f64.sqrt()).ln());
889 for i in 0..12 {
890 let accuracy = 0.1f64.powi(i);
891 let n = 10;
892 for j in 0..=n {
893 let arc = (j as f64) * ((n as f64).recip() * true_arclen);
894 let t = c.inv_arclen(arc, accuracy * 0.5);
895 let actual_arc = c.subsegment(0.0..t).arclen(accuracy * 0.5);
896 assert!(
897 (arc - actual_arc).abs() < accuracy,
898 "at accuracy {accuracy:e}, wanted {actual_arc} got {arc}"
899 );
900 }
901 }
902 let accuracy = true_arclen * 1.1;
904 let arc = true_arclen * 0.5;
905 let t = c.inv_arclen(arc, accuracy);
906 let actual_arc = c.subsegment(0.0..t).arclen(accuracy);
907 assert!(
908 (arc - actual_arc).abs() < 2.0 * accuracy,
909 "at accuracy {accuracy:e}, want {actual_arc} got {arc}"
910 );
911 }
912
913 #[test]
914 fn cubicbez_inv_arclen_accuracy() {
915 let c = CubicBez::new((0.2, 0.73), (0.35, 1.08), (0.85, 1.08), (1.0, 0.73));
916 let true_t = c.inv_arclen(0.5, 1e-12);
917 for i in 1..12 {
918 let accuracy = (0.1f64).powi(i);
919 let approx_t = c.inv_arclen(0.5, accuracy);
920 assert!((approx_t - true_t).abs() <= accuracy);
921 }
922 }
923
924 #[test]
925 #[allow(clippy::float_cmp)]
926 fn cubicbez_signed_area_linear() {
927 let c = CubicBez::new(
929 (1.0, 0.0),
930 (2.0 / 3.0, 1.0 / 3.0),
931 (1.0 / 3.0, 2.0 / 3.0),
932 (0.0, 1.0),
933 );
934 let epsilon = 1e-12;
935 assert_eq!((Affine::rotate(0.5) * c).signed_area(), 0.5);
936 assert!(((Affine::rotate(0.5) * c).signed_area() - 0.5).abs() < epsilon);
937 assert!(((Affine::translate((0.0, 1.0)) * c).signed_area() - 1.0).abs() < epsilon);
938 assert!(((Affine::translate((1.0, 0.0)) * c).signed_area() - 1.0).abs() < epsilon);
939 }
940
941 #[test]
942 fn cubicbez_signed_area() {
943 let c = CubicBez::new((1.0, 0.0), (2.0 / 3.0, 1.0), (1.0 / 3.0, 1.0), (0.0, 1.0));
945 let epsilon = 1e-12;
946 assert!((c.signed_area() - 0.75).abs() < epsilon);
947 assert!(((Affine::rotate(0.5) * c).signed_area() - 0.75).abs() < epsilon);
948 assert!(((Affine::translate((0.0, 1.0)) * c).signed_area() - 1.25).abs() < epsilon);
949 assert!(((Affine::translate((1.0, 0.0)) * c).signed_area() - 1.25).abs() < epsilon);
950 }
951
952 #[test]
953 fn cubicbez_nearest() {
954 fn verify(result: Nearest, expected: f64) {
955 assert!(
956 (result.t - expected).abs() < 1e-6,
957 "got {result:?} expected {expected}"
958 );
959 }
960 let c = CubicBez::new((0.0, 0.0), (1.0 / 3.0, 0.0), (2.0 / 3.0, 0.0), (1.0, 1.0));
962 verify(c.nearest((0.1, 0.001).into(), 1e-6), 0.1);
963 verify(c.nearest((0.2, 0.008).into(), 1e-6), 0.2);
964 verify(c.nearest((0.3, 0.027).into(), 1e-6), 0.3);
965 verify(c.nearest((0.4, 0.064).into(), 1e-6), 0.4);
966 verify(c.nearest((0.5, 0.125).into(), 1e-6), 0.5);
967 verify(c.nearest((0.6, 0.216).into(), 1e-6), 0.6);
968 verify(c.nearest((0.7, 0.343).into(), 1e-6), 0.7);
969 verify(c.nearest((0.8, 0.512).into(), 1e-6), 0.8);
970 verify(c.nearest((0.9, 0.729).into(), 1e-6), 0.9);
971 verify(c.nearest((1.0, 1.0).into(), 1e-6), 1.0);
972 verify(c.nearest((1.1, 1.1).into(), 1e-6), 1.0);
973 verify(c.nearest((-0.1, 0.0).into(), 1e-6), 0.0);
974 let a = Affine::rotate(0.5);
975 verify((a * c).nearest(a * Point::new(0.1, 0.001), 1e-6), 0.1);
976
977 let curve = CubicBez::new(
980 (461.0, 123.0),
981 (460.99999999999994, 123.00000000000004),
982 (111.0, 319.0),
983 (111.0, 319.0),
984 );
985 let p = Point::new(282.0379003395483, 223.21877580985594);
986 let eps = 0.0005;
987 let nearest = curve.nearest(p, eps);
988 let q = curve.eval(nearest.t);
989 let r = curve.eval(0.5075474297354187);
990
991 assert!((q - p).hypot() <= (r - p).hypot() + eps);
992 }
993
994 #[test]
996 fn degenerate_to_quads() {
997 let c = CubicBez::new((0., 9.), (6., 6.), (12., 3.0), (18., 0.0));
998 let quads = c.to_quads(1e-6).collect::<Vec<_>>();
999 assert_eq!(quads.len(), 1, "{:?}", &quads);
1000 }
1001
1002 #[test]
1003 fn cubicbez_extrema() {
1004 let q = CubicBez::new((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0));
1006 let extrema = q.extrema();
1007 assert_eq!(extrema.len(), 1);
1008 assert!((extrema[0] - 0.5).abs() < 1e-6);
1009
1010 let q = CubicBez::new((0.4, 0.5), (0.0, 1.0), (1.0, 0.0), (0.5, 0.4));
1011 let extrema = q.extrema();
1012 assert_eq!(extrema.len(), 4);
1013 }
1014
1015 #[test]
1016 fn cubicbez_toquads() {
1017 let c = CubicBez::new((0.0, 0.0), (1.0 / 3.0, 0.0), (2.0 / 3.0, 0.0), (1.0, 1.0));
1019 for i in 0..10 {
1020 let accuracy = 0.1f64.powi(i);
1021 let mut worst: f64 = 0.0;
1022 for (t0, t1, q) in c.to_quads(accuracy) {
1023 let epsilon = 1e-12;
1024 assert!((q.start() - c.eval(t0)).hypot() < epsilon);
1025 assert!((q.end() - c.eval(t1)).hypot() < epsilon);
1026 let n = 4;
1027 for j in 0..=n {
1028 let t = (j as f64) * (n as f64).recip();
1029 let p = q.eval(t);
1030 let err = (p.y - p.x.powi(3)).abs();
1031 worst = worst.max(err);
1032 assert!(err < accuracy, "got {err} wanted {accuracy}");
1033 }
1034 }
1035 }
1036 }
1037
1038 #[test]
1039 fn cubicbez_approx_spline() {
1040 let c1 = CubicBez::new(
1041 (550.0, 258.0),
1042 (1044.0, 482.0),
1043 (2029.0, 1841.0),
1044 (1934.0, 1554.0),
1045 );
1046
1047 let quad = c1.try_approx_quadratic(344.0);
1048 let expected = QuadBez::new(
1049 Point::new(550.0, 258.0),
1050 Point::new(1673.665720592873, 767.5164401068898),
1051 Point::new(1934.0, 1554.0),
1052 );
1053 assert!(quad.is_some());
1054 assert_eq!(quad.unwrap(), expected);
1055
1056 let quad = c1.try_approx_quadratic(343.0);
1057 assert!(quad.is_none());
1058
1059 let spline = c1.approx_spline_n(2, 343.0);
1060 assert!(spline.is_some());
1061 let spline = spline.unwrap();
1062 let expected = [
1063 Point::new(550.0, 258.0),
1064 Point::new(920.5, 426.0),
1065 Point::new(2005.25, 1769.25),
1066 Point::new(1934.0, 1554.0),
1067 ];
1068 assert_eq!(spline.points().len(), expected.len());
1069 for (got, &wanted) in spline.points().iter().zip(expected.iter()) {
1070 assert!(got.distance(wanted) < 5.0);
1071 }
1072
1073 let spline = c1.approx_spline(5.0);
1074 let expected = [
1075 Point::new(550.0, 258.0),
1076 Point::new(673.5, 314.0),
1077 Point::new(984.8777777777776, 584.2666666666667),
1078 Point::new(1312.6305555555557, 927.825),
1079 Point::new(1613.1194444444443, 1267.425),
1080 Point::new(1842.7055555555555, 1525.8166666666666),
1081 Point::new(1957.75, 1625.75),
1082 Point::new(1934.0, 1554.0),
1083 ];
1084 assert!(spline.is_some());
1085 let spline = spline.unwrap();
1086 assert_eq!(spline.points().len(), expected.len());
1087 for (got, &wanted) in spline.points().iter().zip(expected.iter()) {
1088 assert!(got.distance(wanted) < 5.0);
1089 }
1090 }
1091
1092 #[test]
1093 fn cubicbez_cubics_to_quadratic_splines() {
1094 let curves = vec![
1095 CubicBez::new(
1096 (550.0, 258.0),
1097 (1044.0, 482.0),
1098 (2029.0, 1841.0),
1099 (1934.0, 1554.0),
1100 ),
1101 CubicBez::new(
1102 (859.0, 384.0),
1103 (1998.0, 116.0),
1104 (1596.0, 1772.0),
1105 (8.0, 1824.0),
1106 ),
1107 CubicBez::new(
1108 (1090.0, 937.0),
1109 (418.0, 1300.0),
1110 (125.0, 91.0),
1111 (104.0, 37.0),
1112 ),
1113 ];
1114 let converted = cubics_to_quadratic_splines(&curves, 5.0);
1115 assert!(converted.is_some());
1116 let converted = converted.unwrap();
1117 assert_eq!(converted[0].points().len(), 8);
1118 assert_eq!(converted[1].points().len(), 8);
1119 assert_eq!(converted[2].points().len(), 8);
1120 assert!(converted[0].points()[1].distance(Point::new(673.5, 314.0)) < 0.0001);
1121 assert!(
1122 converted[0].points()[2].distance(Point::new(88639.0 / 90.0, 52584.0 / 90.0)) < 0.0001
1123 );
1124 }
1125
1126 #[test]
1127 fn cubicbez_approx_spline_div_exact() {
1128 let cubic = CubicBez::new(
1132 Point::new(408.0, 321.0),
1133 Point::new(408.0, 452.0),
1134 Point::new(342.0, 560.0),
1135 Point::new(260.0, 560.0),
1136 );
1137 let spline = cubic.approx_spline(1.0).unwrap();
1138 assert_eq!(
1139 spline.points(),
1140 &[
1141 Point::new(408.0, 321.0),
1142 Point::new(408.0, 386.5),
1145 Point::new(368.16666666666663, 495.0833333333333),
1146 Point::new(301.0, 560.0),
1147 Point::new(260.0, 560.0)
1148 ]
1149 );
1150 }
1151
1152 #[test]
1153 fn cubicbez_inflections() {
1154 let c = CubicBez::new((0., 0.), (0.8, 1.), (0.2, 1.), (1., 0.));
1155 let inflections = c.inflections();
1156 assert_eq!(inflections.len(), 2);
1157 assert!((inflections[0] - 0.311018).abs() < 1e-6);
1158 assert!((inflections[1] - 0.688982).abs() < 1e-6);
1159 let c = CubicBez::new((0., 0.), (1., 1.), (2., -1.), (3., 0.));
1160 let inflections = c.inflections();
1161 assert_eq!(inflections.len(), 1);
1162 assert!((inflections[0] - 0.5).abs() < 1e-6);
1163 let c = CubicBez::new((0., 0.), (1., 1.), (2., 1.), (3., 0.));
1164 let inflections = c.inflections();
1165 assert_eq!(inflections.len(), 0);
1166 }
1167
1168 #[test]
1169 fn cubic_to_quadratic_matches_python() {
1170 let cubic = CubicBez {
1172 p0: (796.0, 319.0).into(),
1173 p1: (727.0, 314.0).into(),
1174 p2: (242.0, 303.0).into(),
1175 p3: (106.0, 303.0).into(),
1176 };
1177
1178 assert!(cubic.approx_spline_n(7, 1.0).is_some());
1180
1181 assert!(cubics_to_quadratic_splines(&[cubic], 0.001).is_some());
1183 }
1184
1185 #[test]
1186 fn cubic_to_quadratic_all_points_equal() {
1187 let pt = Point::new(5.0, 5.0);
1188 let cubic = CubicBez::new(pt, pt, pt, pt);
1189 let quads = cubics_to_quadratic_splines(&[cubic], 0.1).unwrap();
1190 assert_eq!(quads, [QuadSpline::new(vec![pt, pt, pt])]);
1191 }
1192
1193 #[test]
1194 fn cubic_to_quadratic_3_points_equal_single_quad_within_tolerance() {
1195 let p0 = Point::new(5.0, 5.0);
1196 let p1 = Point::new(5.0, 5.1);
1197 let cubic = CubicBez::new(p0, p0, p0, p1);
1198 let quads = cubics_to_quadratic_splines(&[cubic], 0.1).unwrap();
1199 assert_eq!(quads, [QuadSpline::new(vec![p0, p0, p1])]);
1201 }
1202
1203 #[test]
1204 fn cubic_to_quadratic_3_points_equal_exceeding_tolerance() {
1205 let p0 = Point::new(5.0, 5.0);
1206 let p1 = Point::new(5.0, 5.1);
1207 let cubic = CubicBez::new(p0, p0, p0, p1);
1208 let quads = cubics_to_quadratic_splines(&[cubic], 0.01).unwrap();
1209 assert_eq!(
1212 quads,
1213 [QuadSpline::new(vec![p0, p0, (5.0, 5.025).into(), p1])]
1214 );
1215 }
1216
1217 #[test]
1218 fn cubics_to_quadratic_splines_matches_python() {
1219 let light = CubicBez::new((378., 608.), (378., 524.), (355., 455.), (266., 455.));
1221 let regular = CubicBez::new((367., 607.), (367., 511.), (338., 472.), (243., 472.));
1222 let bold = CubicBez::new(
1223 (372.425, 593.05),
1224 (372.425, 524.95),
1225 (355.05, 485.95),
1226 (274., 485.95),
1227 );
1228 let qsplines = cubics_to_quadratic_splines(&[light, regular, bold], 1.0).unwrap();
1229 assert_eq!(
1230 qsplines,
1231 [
1232 QuadSpline::new(vec![
1233 (378.0, 608.0).into(),
1234 (378.0, 566.0).into(),
1235 (359.0833333333333, 496.5).into(),
1236 (310.5, 455.0).into(),
1237 (266.0, 455.0).into(),
1238 ]),
1239 QuadSpline::new(vec![
1240 (367.0, 607.0).into(),
1241 (367.0, 559.0).into(),
1242 (344.5833333333333, 499.49999999999994).into(),
1244 (290.5, 472.0).into(),
1245 (243.0, 472.0).into(),
1246 ]),
1247 QuadSpline::new(vec![
1248 (372.425, 593.05).into(),
1249 (372.425, 559.0).into(),
1250 (356.98333333333335, 511.125).into(),
1251 (314.525, 485.95).into(),
1252 (274.0, 485.95).into(),
1253 ]),
1254 ]
1255 );
1256 }
1257}