1use geo_nd::Vector;
21
22use crate::{Bezier, Point};
23
24#[derive(Debug, Clone, Default)]
28pub struct BezierPath {
29 elements: Vec<Bezier>,
30}
31
32impl BezierPath {
34 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 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 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); 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; }
98 if i == 0 {
99 break;
100 }
101 i -= 1;
102 }
103 }
104
105 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 pub fn add_bezier(&mut self, b: Bezier) {
121 self.elements.push(b);
122 }
123
124 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 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 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 pub fn iter_beziers(&self) -> impl Iterator<Item = &Bezier> {
170 self.elements.iter()
171 }
172}
173
174impl<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#[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 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 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}