1// Copyright 2021 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Quadratic Bézier splines.
5use crate::Point;
6
7use crate::QuadBez;
8use 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)]
12pub struct QuadSpline(Vec<Point>);
13
14impl 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
39struct ToQuadBez<'a> {
40 idx: usize,
41 points: &'a Vec<Point>,
42}
43
44impl<'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)]
65mod 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