kurbo/
quadspline.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright 2021 the Kurbo Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Quadratic Bézier splines.
use crate::Point;

use crate::QuadBez;
use alloc::vec::Vec;

/// A quadratic Bézier spline in [B-spline](https://en.wikipedia.org/wiki/B-spline) format.
#[derive(Clone, Debug, PartialEq)]
pub struct QuadSpline(Vec<Point>);

impl QuadSpline {
    /// Construct a new `QuadSpline` from an array of [`Point`]s.
    #[inline]
    pub fn new(points: Vec<Point>) -> Self {
        Self(points)
    }

    /// Return the spline's control [`Point`]s.
    #[inline]
    pub fn points(&self) -> &[Point] {
        &self.0
    }

    /// Return an iterator over the implied [`QuadBez`] sequence.
    ///
    /// The returned quads are guaranteed to be G1 continuous.
    #[inline]
    pub fn to_quads(&self) -> impl Iterator<Item = QuadBez> + '_ {
        ToQuadBez {
            idx: 0,
            points: &self.0,
        }
    }
}

struct ToQuadBez<'a> {
    idx: usize,
    points: &'a Vec<Point>,
}

impl<'a> Iterator for ToQuadBez<'a> {
    type Item = QuadBez;

    fn next(&mut self) -> Option<Self::Item> {
        let [mut p0, p1, mut p2]: [Point; 3] =
            self.points.get(self.idx..=self.idx + 2)?.try_into().ok()?;

        if self.idx != 0 {
            p0 = p0.midpoint(p1);
        }
        if self.idx + 2 < self.points.len() - 1 {
            p2 = p1.midpoint(p2);
        }

        self.idx += 1;

        Some(QuadBez { p0, p1, p2 })
    }
}

#[cfg(test)]
mod tests {
    use crate::{Point, QuadBez, QuadSpline};

    #[test]
    pub fn no_points_no_quads() {
        assert!(QuadSpline::new(Vec::new()).to_quads().next().is_none());
    }

    #[test]
    pub fn one_point_no_quads() {
        assert!(QuadSpline::new(vec![Point::new(1.0, 1.0)])
            .to_quads()
            .next()
            .is_none());
    }

    #[test]
    pub fn two_points_no_quads() {
        assert!(
            QuadSpline::new(vec![Point::new(1.0, 1.0), Point::new(1.0, 1.0)])
                .to_quads()
                .next()
                .is_none()
        );
    }

    #[test]
    pub fn three_points_same_quad() {
        let p0 = Point::new(1.0, 1.0);
        let p1 = Point::new(2.0, 2.0);
        let p2 = Point::new(3.0, 3.0);
        assert_eq!(
            vec![QuadBez { p0, p1, p2 }],
            QuadSpline::new(vec![p0, p1, p2])
                .to_quads()
                .collect::<Vec<_>>()
        );
    }

    #[test]
    pub fn four_points_implicit_on_curve() {
        let p0 = Point::new(1.0, 1.0);
        let p1 = Point::new(3.0, 3.0);
        let p2 = Point::new(5.0, 5.0);
        let p3 = Point::new(8.0, 8.0);
        assert_eq!(
            vec![
                QuadBez {
                    p0,
                    p1,
                    p2: p1.midpoint(p2)
                },
                QuadBez {
                    p0: p1.midpoint(p2),
                    p1: p2,
                    p2: p3
                }
            ],
            QuadSpline::new(vec![p0, p1, p2, p3])
                .to_quads()
                .collect::<Vec<_>>()
        );
    }
}