1 | // Copyright 2021 the Kurbo Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | //! Quadratic Bézier splines. |
5 | use crate::Point; |
6 | |
7 | use crate::QuadBez; |
8 | use alloc::vec::Vec; |
9 | |
10 | /// A quadratic Bézier spline in [B-spline](https://en.wikipedia.org/wiki/B-spline) format. |
11 | #[derive (Clone, Debug, PartialEq)] |
12 | pub struct QuadSpline(Vec<Point>); |
13 | |
14 | impl QuadSpline { |
15 | /// Construct a new `QuadSpline` from an array of [`Point`]s. |
16 | #[inline ] |
17 | pub fn new(points: Vec<Point>) -> Self { |
18 | Self(points) |
19 | } |
20 | |
21 | /// Return the spline's control [`Point`]s. |
22 | #[inline ] |
23 | pub fn points(&self) -> &[Point] { |
24 | &self.0 |
25 | } |
26 | |
27 | /// Return an iterator over the implied [`QuadBez`] sequence. |
28 | /// |
29 | /// The returned quads are guaranteed to be G1 continuous. |
30 | #[inline ] |
31 | pub fn to_quads(&self) -> impl Iterator<Item = QuadBez> + '_ { |
32 | ToQuadBez { |
33 | idx: 0, |
34 | points: &self.0, |
35 | } |
36 | } |
37 | } |
38 | |
39 | struct ToQuadBez<'a> { |
40 | idx: usize, |
41 | points: &'a Vec<Point>, |
42 | } |
43 | |
44 | impl<'a> Iterator for ToQuadBez<'a> { |
45 | type Item = QuadBez; |
46 | |
47 | fn next(&mut self) -> Option<Self::Item> { |
48 | let [mut p0: Point, p1: Point, mut p2: Point]: [Point; 3] = |
49 | self.points.get(self.idx..=self.idx + 2)?.try_into().ok()?; |
50 | |
51 | if self.idx != 0 { |
52 | p0 = p0.midpoint(p1); |
53 | } |
54 | if self.idx + 2 < self.points.len() - 1 { |
55 | p2 = p1.midpoint(p2); |
56 | } |
57 | |
58 | self.idx += 1; |
59 | |
60 | Some(QuadBez { p0, p1, p2 }) |
61 | } |
62 | } |
63 | |
64 | #[cfg (test)] |
65 | mod tests { |
66 | use crate::{Point, QuadBez, QuadSpline}; |
67 | |
68 | #[test ] |
69 | pub fn no_points_no_quads() { |
70 | assert!(QuadSpline::new(Vec::new()).to_quads().next().is_none()); |
71 | } |
72 | |
73 | #[test ] |
74 | pub fn one_point_no_quads() { |
75 | assert!(QuadSpline::new(vec![Point::new(1.0, 1.0)]) |
76 | .to_quads() |
77 | .next() |
78 | .is_none()); |
79 | } |
80 | |
81 | #[test ] |
82 | pub fn two_points_no_quads() { |
83 | assert!( |
84 | QuadSpline::new(vec![Point::new(1.0, 1.0), Point::new(1.0, 1.0)]) |
85 | .to_quads() |
86 | .next() |
87 | .is_none() |
88 | ); |
89 | } |
90 | |
91 | #[test ] |
92 | pub fn three_points_same_quad() { |
93 | let p0 = Point::new(1.0, 1.0); |
94 | let p1 = Point::new(2.0, 2.0); |
95 | let p2 = Point::new(3.0, 3.0); |
96 | assert_eq!( |
97 | vec![QuadBez { p0, p1, p2 }], |
98 | QuadSpline::new(vec![p0, p1, p2]) |
99 | .to_quads() |
100 | .collect::<Vec<_>>() |
101 | ); |
102 | } |
103 | |
104 | #[test ] |
105 | pub fn four_points_implicit_on_curve() { |
106 | let p0 = Point::new(1.0, 1.0); |
107 | let p1 = Point::new(3.0, 3.0); |
108 | let p2 = Point::new(5.0, 5.0); |
109 | let p3 = Point::new(8.0, 8.0); |
110 | assert_eq!( |
111 | vec![ |
112 | QuadBez { |
113 | p0, |
114 | p1, |
115 | p2: p1.midpoint(p2) |
116 | }, |
117 | QuadBez { |
118 | p0: p1.midpoint(p2), |
119 | p1: p2, |
120 | p2: p3 |
121 | } |
122 | ], |
123 | QuadSpline::new(vec![p0, p1, p2, p3]) |
124 | .to_quads() |
125 | .collect::<Vec<_>>() |
126 | ); |
127 | } |
128 | } |
129 | |