svg_nd/
bezier_path.rs

1/*a Copyright
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7  http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14
15@file    bezier_path.rs
16@brief   Part of geometry library
17 */
18
19//a Imports
20use geo_nd::Vector;
21
22use crate::{Bezier, Point};
23
24//a BezierPath
25//tp BezierPath
26/// A path is a set of Beziers that form a chain (i.e. the end of one is the start of the next)
27#[derive(Debug, Clone, Default)]
28pub struct BezierPath {
29    elements: Vec<Bezier>,
30}
31
32//ip BezierPath
33impl BezierPath {
34    //fp of_ellipse
35    /// Create a set of paths that make an ellipse
36    pub fn of_ellipse(origin: Point, radius: f64, eccentricity: f64, degrees: f64) -> Self {
37        let ra = (90.0f64).to_radians();
38        let rd = degrees.to_radians();
39        let c = rd.cos();
40        let s = rd.sin();
41        let x = Point::from_array([c * eccentricity, s * eccentricity]);
42        let y = Point::from_array([-s, c]);
43        let v = vec![
44            Bezier::arc(ra, radius, &origin, &x, &y, 0.),
45            Bezier::arc(ra, radius, &origin, &x, &y, ra),
46            Bezier::arc(ra, radius, &origin, &x, &y, ra * 2.),
47            Bezier::arc(ra, radius, &origin, &x, &y, ra * 3.),
48        ];
49        Self { elements: v }
50    }
51
52    //fp of_points
53    /// Generate a set of Beziers that join the corners
54    pub fn of_points(corners: &[Point], rounding: f64) -> Self {
55        let mut bp = Self::default();
56        let n = corners.len();
57        for i in 0..n {
58            let i_0 = (i) % n;
59            let i_1 = (i + 1) % n;
60            bp.add_bezier(Bezier::line(&corners[i_0], &corners[i_1]));
61        }
62        bp.round(rounding, true);
63        bp
64    }
65
66    //mp round
67    /// Run through the path; for every adjacent pair of Beziers that
68    /// are *line*s add an intermediate Bezier that is a rounded
69    /// corner of the correct radius.
70    ///
71    /// If the path is closed, thenn treat the first Bezier as
72    /// adjacent to the last Bezier
73    pub fn round(&mut self, rounding: f64, closed: bool) {
74        let mut n = self.elements.len();
75        if n < 2 || rounding == 0. {
76            return;
77        }
78        let mut i = n - 1;
79        if !closed {
80            i -= 1;
81        }
82        loop {
83            let i_1 = (i + 1) % n;
84            if self.elements[i].degree() == 1 && self.elements[i_1].degree() == 1 {
85                let corner = self.elements[i].borrow_pt(1); // same as i_1.borrow_pt(0);
86                let v0 = self.elements[i].tangent_at(1.);
87                let v1 = -self.elements[i_1].tangent_at(0.);
88                let bezier = Bezier::of_round_corner(corner, &v0, &v1, rounding);
89                let np00 = *self.elements[i].borrow_pt(0);
90                let np01 = *bezier.borrow_pt(0);
91                let np10 = *bezier.borrow_pt(1);
92                let np11 = *self.elements[i_1].borrow_pt(1);
93                self.elements[i] = Bezier::line(&np00, &np01);
94                self.elements[i_1] = Bezier::line(&np10, &np11);
95                self.elements.insert(i + 1, bezier);
96                n += 1; // Not really required but it keeps n == self.elements.len()
97            }
98            if i == 0 {
99                break;
100            }
101            i -= 1;
102        }
103    }
104
105    //mp get_pt
106    /// Get the start or the end point
107    pub fn get_pt(&self, index: usize) -> Point {
108        let n = self.elements.len();
109        if n == 0 {
110            Point::zero()
111        } else if index == 0 {
112            *self.elements[0].borrow_pt(0)
113        } else {
114            *self.elements[n - 1].borrow_pt(1)
115        }
116    }
117
118    //mp add_bezier
119    /// Add a Bezier at the end of the path
120    pub fn add_bezier(&mut self, b: Bezier) {
121        self.elements.push(b);
122    }
123
124    //mp apply_relief
125    /// Attempt to remove `distance` from the start or end of the path
126    /// but leave rest of path the same
127    pub fn apply_relief(&mut self, index: usize, straightness: f64, distance: f64) {
128        if self.elements.is_empty() {
129            return;
130        }
131        if index == 0 {
132            let b = self.elements[0];
133            let l = b.length(straightness);
134            // println!("Applying relief to start of bezier path {} straightness {} distance {} length {}",b, straightness, distance, l);
135            if distance > l {
136                self.elements.remove(0);
137                self.apply_relief(index, straightness, distance - l)
138            } else {
139                let (t, _in_bezier) = b.t_of_distance(straightness, distance);
140                if t == 0. {
141                } else if t == 1. {
142                    self.elements.remove(0);
143                } else {
144                    self.elements[0] = b.bezier_between(t, 1.);
145                }
146            }
147        } else {
148            let n = self.elements.len();
149            let b = self.elements[n - 1];
150            let l = b.length(straightness);
151            // println!("Applying relief to end of bezier path {} straightness {} distance {} length {}",b, straightness, distance, l);
152            if distance > l {
153                self.elements.pop();
154                self.apply_relief(index, straightness, distance - l)
155            } else {
156                let (t, _in_bezier) = b.t_of_distance(straightness, l - distance);
157                if t == 0. {
158                    self.elements.pop();
159                } else if t == 1. {
160                } else {
161                    self.elements[n - 1] = b.bezier_between(0., t);
162                }
163            }
164        }
165    }
166
167    //mp iter_beziers
168    /// Iterate through all the Beziers
169    pub fn iter_beziers(&self) -> impl Iterator<Item = &Bezier> {
170        self.elements.iter()
171    }
172}
173
174//ip std::ops::Index<Idx>
175impl<Idx> std::ops::Index<Idx> for BezierPath
176where
177    Idx: std::slice::SliceIndex<[Bezier]>,
178{
179    type Output = <Idx as std::slice::SliceIndex<[Bezier]>>::Output;
180
181    fn index(&self, index: Idx) -> &Self::Output {
182        &self.elements[index]
183    }
184}
185
186//a Test
187#[cfg(test)]
188mod test_path {
189    use super::*;
190    pub fn pt_eq(pt: &Point, x: f64, y: f64) {
191        assert!(
192            (pt[0] - x).abs() < 1E-8,
193            "mismatch in x {:?} {} {}",
194            pt,
195            x,
196            y
197        );
198        assert!(
199            (pt[1] - y).abs() < 1E-8,
200            "mismatch in x {:?} {} {}",
201            pt,
202            x,
203            y
204        );
205    }
206    pub fn bezier_eq(bez: &Bezier, v: Vec<(f64, f64)>) {
207        if bez.degree() == 3 {
208            pt_eq(bez.borrow_pt(0), v[0].0, v[0].1);
209            pt_eq(bez.borrow_pt(2), v[1].0, v[1].1);
210            pt_eq(bez.borrow_pt(3), v[2].0, v[2].1);
211            pt_eq(bez.borrow_pt(1), v[3].0, v[3].1);
212        } else if bez.degree() == 2 {
213            pt_eq(bez.borrow_pt(0), v[0].0, v[0].1);
214            pt_eq(bez.borrow_pt(2), v[1].0, v[1].1);
215            pt_eq(bez.borrow_pt(1), v[2].0, v[2].1);
216        } else {
217            pt_eq(bez.borrow_pt(0), v[0].0, v[0].1);
218            pt_eq(bez.borrow_pt(1), v[1].0, v[1].1);
219        }
220    }
221    #[test]
222    fn test_round_open() {
223        let p0 = Point::zero();
224        let p1 = Point::from_array([1., 0.]);
225        let p2 = Point::from_array([1., 1.]);
226        let p3 = Point::from_array([0., 1.]);
227        let mut bp = BezierPath::default();
228        bp.add_bezier(Bezier::line(&p0, &p1));
229        bp.add_bezier(Bezier::line(&p1, &p2));
230        bp.add_bezier(Bezier::line(&p2, &p3));
231        bp.add_bezier(Bezier::line(&p3, &p0));
232
233        // After open rounding of 0.1 the straight lines are 0.1-0.9 on each side except first and last
234        // and 7 elements with round corners except at origin
235        bp.round(0.1, false);
236        for b in bp.iter_beziers() {
237            println!("Bezier {}", b);
238        }
239        bezier_eq(&bp.elements[0], vec![(0., 0.), (0.9, 0.0)]);
240        bezier_eq(&bp.elements[2], vec![(1., 0.1), (1., 0.9)]);
241        bezier_eq(&bp.elements[4], vec![(0.9, 1.0), (0.1, 1.)]);
242        bezier_eq(&bp.elements[6], vec![(0., 0.9), (0., 0.)]);
243        assert_eq!(bp.elements.len(), 7, "Path should be 7 elements");
244    }
245    #[test]
246    fn test_round_closed() {
247        let p0 = Point::zero();
248        let p1 = Point::from_array([1., 0.]);
249        let p2 = Point::from_array([1., 1.]);
250        let p3 = Point::from_array([0., 1.]);
251        let mut bp = BezierPath::default();
252        bp.add_bezier(Bezier::line(&p0, &p1));
253        bp.add_bezier(Bezier::line(&p1, &p2));
254        bp.add_bezier(Bezier::line(&p2, &p3));
255        bp.add_bezier(Bezier::line(&p3, &p0));
256
257        // After closed rounding of 0.1 the straight lines are 0.1-0.9 on each side
258        // and 8 elements
259        bp.round(0.1, true);
260        for b in bp.iter_beziers() {
261            println!("Bezier {}", b);
262        }
263        bezier_eq(&bp.elements[0], vec![(0.1, 0.), (0.9, 0.0)]);
264        bezier_eq(&bp.elements[2], vec![(1., 0.1), (1., 0.9)]);
265        bezier_eq(&bp.elements[4], vec![(0.9, 1.0), (0.1, 1.)]);
266        bezier_eq(&bp.elements[6], vec![(0., 0.9), (0., 0.1)]);
267        assert_eq!(bp.elements.len(), 8, "Path should be 8 elements");
268    }
269}