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 | |