flo_curves/bezier/
subdivide.rs

1use crate::geo::*;
2use super::basis::*;
3
4use smallvec::*;
5
6///
7/// Subdivides a bezier curve with any number of weights at a particular point. Returns the weights for the
8/// two curves on either side of the subdivision point.
9///
10pub fn subdivide_n<TPoint, const N: usize>(t: f64, points: [TPoint; N]) -> ([TPoint; N], [TPoint; N])
11where
12    TPoint: Coordinate,
13{
14    // Want to store 1+2+3+...+N weights in total
15    let num_weights = (N * (N+1))/2;
16
17    // Calculate the weights at each layer using the de Casteljau algorithm
18    // TODO: num_weights is a constant so we could just use a slice instead of a vec here (Rust doesn't currently allow it, though)
19    let mut weights = Vec::with_capacity(num_weights);
20
21    // Copy the points into the initial set of weights
22    weights.extend(points);
23
24    // Use de Casteljau algorithm to generate all of the weights for the curve
25    let mut last_pos = 0;
26
27    for depth in (1..N).into_iter().rev() {
28        // Apply de Casteljau to the last set of weights (producing the weights for the tangents)
29        for p in 0..depth {
30            let wn = weights[last_pos+p]*(1.0-t) + weights[last_pos+p+1]*t;
31            weights.push(wn);
32        }
33
34        // Process the next set of weights to generate the full set of derivatives for the curve
35        last_pos += depth+1;
36    }
37
38    // First set of points are the first weight from each level, second set are the last weight from each level
39    // TODO: would be nice to avoid creating the vecs here (think this is a little more possible than for the main weights list with the current version of Rust)
40    let mut first_weights   = SmallVec::<[_; N]>::new();
41    let mut second_weights  = SmallVec::<[_; N]>::new();
42
43    let mut last_pos = 0;
44    for depth in (0..N).into_iter().rev() {
45        first_weights.push(weights[last_pos]);
46        second_weights.push(weights[last_pos+depth]);
47
48        last_pos += depth + 1;
49    }
50
51    // Second set of weights will be reversed at this point, so we need to switch them around
52    if let (Ok(first_weights), Ok(second_weights)) = (first_weights.into_inner(), second_weights.into_inner()) {
53        let mut second_weights: [TPoint; N] = second_weights;
54        second_weights.reverse();
55
56        (first_weights, second_weights)
57    } else {
58        unreachable!()
59    }
60}
61
62///
63/// Subdivides a cubic bezier curve at a particular point, returning the weights of
64/// the two component curves
65/// 
66pub fn subdivide4<Point: Coordinate>(t: f64, w1: Point, w2: Point, w3: Point, w4: Point) -> 
67    ((Point, Point, Point, Point), (Point, Point, Point, Point)) {
68    // Weights (from de casteljau)
69    let wn1 = w1*(1.0-t) + w2*t;
70    let wn2 = w2*(1.0-t) + w3*t;
71    let wn3 = w3*(1.0-t) + w4*t;
72
73    // Further refine the weights
74    let wnn1 = wn1*(1.0-t) + wn2*t;
75    let wnn2 = wn2*(1.0-t) + wn3*t;
76
77    // Get the point at which the two curves join
78    let p = de_casteljau2(t, wnn1, wnn2);
79
80    // Curves are built from the weight calculations and the final points
81    ((w1, wn1, wnn1, p), (p, wnn2, wn3, w4))
82}
83
84#[cfg(test)]
85mod test {
86    use super::*;
87
88    pub fn approx_equal(a: f64, b: f64) -> bool {
89        f64::floor(f64::abs(a-b)*10000.0) == 0.0
90    }
91
92    #[test]
93    fn subdivide_n_1() {
94        // Initial curve
95        let (w1, w2, w3, w4) = (1.0, 2.0, 3.0, 4.0);
96
97        // Subdivide at 33%, creating two curves
98        let ([wa1, wa2, wa3, wa4], [wb1, wb2, wb3, wb4])            = subdivide_n(0.33, [w1, w2, w3, w4]);
99        let ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4))    = subdivide4(0.33, w1, w2, w3, w4);
100
101        debug_assert!(approx_equal(wa1, waa1), "{:?} != {:?}", ((wa1, wa2, wa3, wa4), (wb1, wb2, wb3, wb4)), ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4)));
102        debug_assert!(approx_equal(wa2, waa2), "{:?} != {:?}", ((wa1, wa2, wa3, wa4), (wb1, wb2, wb3, wb4)), ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4)));
103        debug_assert!(approx_equal(wa3, waa3), "{:?} != {:?}", ((wa1, wa2, wa3, wa4), (wb1, wb2, wb3, wb4)), ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4)));
104        debug_assert!(approx_equal(wa4, waa4), "{:?} != {:?}", ((wa1, wa2, wa3, wa4), (wb1, wb2, wb3, wb4)), ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4)));
105
106        debug_assert!(approx_equal(wb1, wbb1), "{:?} != {:?}", ((wa1, wa2, wa3, wa4), (wb1, wb2, wb3, wb4)), ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4)));
107        debug_assert!(approx_equal(wb2, wbb2), "{:?} != {:?}", ((wa1, wa2, wa3, wa4), (wb1, wb2, wb3, wb4)), ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4)));
108        debug_assert!(approx_equal(wb3, wbb3), "{:?} != {:?}", ((wa1, wa2, wa3, wa4), (wb1, wb2, wb3, wb4)), ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4)));
109        debug_assert!(approx_equal(wb4, wbb4), "{:?} != {:?}", ((wa1, wa2, wa3, wa4), (wb1, wb2, wb3, wb4)), ((waa1, waa2, waa3, waa4), (wbb1, wbb2, wbb3, wbb4)));
110
111        // Check that the original curve corresponds to the basis function for wa
112        for x in 0..100 {
113            let t = (x as f64)/100.0;
114
115            let original    = basis(t*0.33, w1, w2, w3, w4);
116            let subdivision = basis(t, wa1, wa2, wa3, wa4);
117
118            assert!(approx_equal(original, subdivision));
119        }
120    }
121
122}