Skip to main content

kurbo/
quadspline.rs

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(always)]
17    pub fn new(points: Vec<Point>) -> Self {
18        Self(points)
19    }
20
21    /// Return the spline's control [`Point`]s.
22    #[inline(always)]
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(always)]
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 Iterator for ToQuadBez<'_> {
45    type Item = QuadBez;
46
47    fn next(&mut self) -> Option<Self::Item> {
48        let [mut p0, p1, mut p2]: [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    fn no_points_no_quads() {
70        assert!(QuadSpline::new(Vec::new()).to_quads().next().is_none());
71    }
72
73    #[test]
74    fn one_point_no_quads() {
75        assert!(
76            QuadSpline::new(vec![Point::new(1.0, 1.0)])
77                .to_quads()
78                .next()
79                .is_none()
80        );
81    }
82
83    #[test]
84    fn two_points_no_quads() {
85        assert!(
86            QuadSpline::new(vec![Point::new(1.0, 1.0), Point::new(1.0, 1.0)])
87                .to_quads()
88                .next()
89                .is_none()
90        );
91    }
92
93    #[test]
94    fn three_points_same_quad() {
95        let p0 = Point::new(1.0, 1.0);
96        let p1 = Point::new(2.0, 2.0);
97        let p2 = Point::new(3.0, 3.0);
98        assert_eq!(
99            vec![QuadBez { p0, p1, p2 }],
100            QuadSpline::new(vec![p0, p1, p2])
101                .to_quads()
102                .collect::<Vec<_>>()
103        );
104    }
105
106    #[test]
107    fn four_points_implicit_on_curve() {
108        let p0 = Point::new(1.0, 1.0);
109        let p1 = Point::new(3.0, 3.0);
110        let p2 = Point::new(5.0, 5.0);
111        let p3 = Point::new(8.0, 8.0);
112        assert_eq!(
113            vec![
114                QuadBez {
115                    p0,
116                    p1,
117                    p2: p1.midpoint(p2)
118                },
119                QuadBez {
120                    p0: p1.midpoint(p2),
121                    p1: p2,
122                    p2: p3
123                }
124            ],
125            QuadSpline::new(vec![p0, p1, p2, p3])
126                .to_quads()
127                .collect::<Vec<_>>()
128        );
129    }
130}