epaint/
bezier.rs

1#![allow(clippy::many_single_char_names)]
2#![allow(clippy::wrong_self_convention)] // False positives
3
4use std::ops::Range;
5
6use crate::{shape::Shape, Color32, PathShape, PathStroke};
7use emath::*;
8
9// ----------------------------------------------------------------------------
10
11/// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
12///
13/// See also [`QuadraticBezierShape`].
14#[derive(Clone, Debug, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16pub struct CubicBezierShape {
17    /// The first point is the starting point and the last one is the ending point of the curve.
18    /// The middle points are the control points.
19    pub points: [Pos2; 4],
20    pub closed: bool,
21
22    pub fill: Color32,
23    pub stroke: PathStroke,
24}
25
26impl CubicBezierShape {
27    /// Creates a cubic Bézier curve based on 4 points and stroke.
28    ///
29    /// The first point is the starting point and the last one is the ending point of the curve.
30    /// The middle points are the control points.
31    pub fn from_points_stroke(
32        points: [Pos2; 4],
33        closed: bool,
34        fill: Color32,
35        stroke: impl Into<PathStroke>,
36    ) -> Self {
37        Self {
38            points,
39            closed,
40            fill,
41            stroke: stroke.into(),
42        }
43    }
44
45    /// Transform the curve with the given transform.
46    pub fn transform(&self, transform: &RectTransform) -> Self {
47        let mut points = [Pos2::default(); 4];
48        for (i, origin_point) in self.points.iter().enumerate() {
49            points[i] = transform * *origin_point;
50        }
51        Self {
52            points,
53            closed: self.closed,
54            fill: self.fill,
55            stroke: self.stroke.clone(),
56        }
57    }
58
59    /// Convert the cubic Bézier curve to one or two [`PathShape`]'s.
60    /// When the curve is closed and it has to intersect with the base line, it will be converted into two shapes.
61    /// Otherwise, it will be converted into one shape.
62    /// The `tolerance` will be used to control the max distance between the curve and the base line.
63    /// The `epsilon` is used when comparing two floats.
64    pub fn to_path_shapes(&self, tolerance: Option<f32>, epsilon: Option<f32>) -> Vec<PathShape> {
65        let mut pathshapes = Vec::new();
66        let mut points_vec = self.flatten_closed(tolerance, epsilon);
67        for points in points_vec.drain(..) {
68            let pathshape = PathShape {
69                points,
70                closed: self.closed,
71                fill: self.fill,
72                stroke: self.stroke.clone(),
73            };
74            pathshapes.push(pathshape);
75        }
76        pathshapes
77    }
78
79    /// The visual bounding rectangle (includes stroke width)
80    pub fn visual_bounding_rect(&self) -> Rect {
81        if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
82            Rect::NOTHING
83        } else {
84            self.logical_bounding_rect().expand(self.stroke.width / 2.0)
85        }
86    }
87
88    /// Logical bounding rectangle (ignoring stroke width)
89    pub fn logical_bounding_rect(&self) -> Rect {
90        //temporary solution
91        let (mut min_x, mut max_x) = if self.points[0].x < self.points[3].x {
92            (self.points[0].x, self.points[3].x)
93        } else {
94            (self.points[3].x, self.points[0].x)
95        };
96        let (mut min_y, mut max_y) = if self.points[0].y < self.points[3].y {
97            (self.points[0].y, self.points[3].y)
98        } else {
99            (self.points[3].y, self.points[0].y)
100        };
101
102        // find the inflection points and get the x value
103        cubic_for_each_local_extremum(
104            self.points[0].x,
105            self.points[1].x,
106            self.points[2].x,
107            self.points[3].x,
108            &mut |t| {
109                let x = self.sample(t).x;
110                if x < min_x {
111                    min_x = x;
112                }
113                if x > max_x {
114                    max_x = x;
115                }
116            },
117        );
118
119        // find the inflection points and get the y value
120        cubic_for_each_local_extremum(
121            self.points[0].y,
122            self.points[1].y,
123            self.points[2].y,
124            self.points[3].y,
125            &mut |t| {
126                let y = self.sample(t).y;
127                if y < min_y {
128                    min_y = y;
129                }
130                if y > max_y {
131                    max_y = y;
132                }
133            },
134        );
135
136        Rect {
137            min: Pos2 { x: min_x, y: min_y },
138            max: Pos2 { x: max_x, y: max_y },
139        }
140    }
141
142    /// split the original cubic curve into a new one within a range.
143    pub fn split_range(&self, t_range: Range<f32>) -> Self {
144        debug_assert!(
145            0.0 <= t_range.start && t_range.end <= 1.0 && t_range.start <= t_range.end,
146            "range should be in [0.0,1.0]"
147        );
148
149        let from = self.sample(t_range.start);
150        let to = self.sample(t_range.end);
151
152        let d_from = self.points[1] - self.points[0].to_vec2();
153        let d_ctrl = self.points[2] - self.points[1].to_vec2();
154        let d_to = self.points[3] - self.points[2].to_vec2();
155        let q = QuadraticBezierShape {
156            points: [d_from, d_ctrl, d_to],
157            closed: self.closed,
158            fill: self.fill,
159            stroke: self.stroke.clone(),
160        };
161        let delta_t = t_range.end - t_range.start;
162        let q_start = q.sample(t_range.start);
163        let q_end = q.sample(t_range.end);
164        let ctrl1 = from + q_start.to_vec2() * delta_t;
165        let ctrl2 = to - q_end.to_vec2() * delta_t;
166
167        Self {
168            points: [from, ctrl1, ctrl2, to],
169            closed: self.closed,
170            fill: self.fill,
171            stroke: self.stroke.clone(),
172        }
173    }
174
175    // copied from lyon::geom::flattern_cubic.rs
176    // Computes the number of quadratic bézier segments to approximate a cubic one.
177    // Derived by Raph Levien from section 10.6 of Sedeberg's CAGD notes
178    // https://scholarsarchive.byu.edu/cgi/viewcontent.cgi?article=1000&context=facpub#section.10.6
179    // and the error metric from the caffein owl blog post http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
180    pub fn num_quadratics(&self, tolerance: f32) -> u32 {
181        debug_assert!(tolerance > 0.0, "the tolerance should be positive");
182
183        let x =
184            self.points[0].x - 3.0 * self.points[1].x + 3.0 * self.points[2].x - self.points[3].x;
185        let y =
186            self.points[0].y - 3.0 * self.points[1].y + 3.0 * self.points[2].y - self.points[3].y;
187        let err = x * x + y * y;
188
189        (err / (432.0 * tolerance * tolerance))
190            .powf(1.0 / 6.0)
191            .ceil()
192            .max(1.0) as u32
193    }
194
195    /// Find out the t value for the point where the curve is intersected with the base line.
196    /// The base line is the line from P0 to P3.
197    /// If the curve only has two intersection points with the base line, they should be 0.0 and 1.0.
198    /// In this case, the "fill" will be simple since the curve is a convex line.
199    /// If the curve has more than two intersection points with the base line, the "fill" will be a problem.
200    /// We need to find out where is the 3rd t value (0<t<1)
201    /// And the original cubic curve will be split into two curves (0.0..t and t..1.0).
202    /// B(t) = (1-t)^3*P0 + 3*t*(1-t)^2*P1 + 3*t^2*(1-t)*P2 + t^3*P3
203    /// or B(t) = (P3 - 3*P2 + 3*P1 - P0)*t^3 + (3*P2 - 6*P1 + 3*P0)*t^2 + (3*P1 - 3*P0)*t + P0
204    /// this B(t) should be on the line between P0 and P3. Therefore:
205    /// (B.x - P0.x)/(P3.x - P0.x) = (B.y - P0.y)/(P3.y - P0.y), or:
206    /// B.x * (P3.y - P0.y) - B.y * (P3.x - P0.x) + P0.x * (P0.y - P3.y) + P0.y * (P3.x - P0.x) = 0
207    /// B.x = (P3.x - 3 * P2.x + 3 * P1.x - P0.x) * t^3 + (3 * P2.x - 6 * P1.x + 3 * P0.x) * t^2 + (3 * P1.x - 3 * P0.x) * t + P0.x
208    /// B.y = (P3.y - 3 * P2.y + 3 * P1.y - P0.y) * t^3 + (3 * P2.y - 6 * P1.y + 3 * P0.y) * t^2 + (3 * P1.y - 3 * P0.y) * t + P0.y
209    /// Combine the above three equations and iliminate B.x and B.y, we get:
210    /// t^3 * ( (P3.x - 3*P2.x + 3*P1.x - P0.x) * (P3.y - P0.y) - (P3.y - 3*P2.y + 3*P1.y - P0.y) * (P3.x - P0.x))
211    /// + t^2 * ( (3 * P2.x - 6 * P1.x + 3 * P0.x) * (P3.y - P0.y) - (3 * P2.y - 6 * P1.y + 3 * P0.y) * (P3.x - P0.x))
212    /// + t^1 * ( (3 * P1.x - 3 * P0.x) * (P3.y - P0.y) - (3 * P1.y - 3 * P0.y) * (P3.x - P0.x))
213    /// + (P0.x * (P3.y - P0.y) - P0.y * (P3.x - P0.x)) + P0.x * (P0.y - P3.y) + P0.y * (P3.x - P0.x)
214    /// = 0
215    /// or a * t^3 + b * t^2 + c * t + d = 0
216    ///
217    /// let x = t - b / (3 * a), then we have:
218    /// x^3 + p * x + q = 0, where:
219    /// p = (3.0 * a * c - b^2) / (3.0 * a^2)
220    /// q = (2.0 * b^3 - 9.0 * a * b * c + 27.0 * a^2 * d) / (27.0 * a^3)
221    ///
222    /// when p > 0, there will be one real root, two complex roots
223    /// when p = 0, there will be two real roots, when p=q=0, there will be three real roots but all 0.
224    /// when p < 0, there will be three unique real roots. this is what we need. (x1, x2, x3)
225    ///  t = x + b / (3 * a), then we have: t1, t2, t3.
226    /// the one between 0.0 and 1.0 is what we need.
227    /// <`https://baike.baidu.com/item/%E4%B8%80%E5%85%83%E4%B8%89%E6%AC%A1%E6%96%B9%E7%A8%8B/8388473 /`>
228    ///
229    pub fn find_cross_t(&self, epsilon: f32) -> Option<f32> {
230        let p0 = self.points[0];
231        let p1 = self.points[1];
232        let p2 = self.points[2];
233        let p3 = self.points[3];
234
235        let a = (p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x) * (p3.y - p0.y)
236            - (p3.y - 3.0 * p2.y + 3.0 * p1.y - p0.y) * (p3.x - p0.x);
237        let b = (3.0 * p2.x - 6.0 * p1.x + 3.0 * p0.x) * (p3.y - p0.y)
238            - (3.0 * p2.y - 6.0 * p1.y + 3.0 * p0.y) * (p3.x - p0.x);
239        let c =
240            (3.0 * p1.x - 3.0 * p0.x) * (p3.y - p0.y) - (3.0 * p1.y - 3.0 * p0.y) * (p3.x - p0.x);
241        let d = p0.x * (p3.y - p0.y) - p0.y * (p3.x - p0.x)
242            + p0.x * (p0.y - p3.y)
243            + p0.y * (p3.x - p0.x);
244
245        let h = -b / (3.0 * a);
246        let p = (3.0 * a * c - b * b) / (3.0 * a * a);
247        let q = (2.0 * b * b * b - 9.0 * a * b * c + 27.0 * a * a * d) / (27.0 * a * a * a);
248
249        if p > 0.0 {
250            return None;
251        }
252        let r = (-1.0 * (p / 3.0).powi(3)).sqrt();
253        let theta = (-1.0 * q / (2.0 * r)).acos() / 3.0;
254
255        let t1 = 2.0 * r.cbrt() * theta.cos() + h;
256        let t2 = 2.0 * r.cbrt() * (theta + 120.0 * std::f32::consts::PI / 180.0).cos() + h;
257        let t3 = 2.0 * r.cbrt() * (theta + 240.0 * std::f32::consts::PI / 180.0).cos() + h;
258
259        if t1 > epsilon && t1 < 1.0 - epsilon {
260            return Some(t1);
261        }
262        if t2 > epsilon && t2 < 1.0 - epsilon {
263            return Some(t2);
264        }
265        if t3 > epsilon && t3 < 1.0 - epsilon {
266            return Some(t3);
267        }
268        None
269    }
270
271    /// Calculate the point (x,y) at t based on the cubic Bézier curve equation.
272    /// t is in [0.0,1.0]
273    /// [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves)
274    ///
275    pub fn sample(&self, t: f32) -> Pos2 {
276        debug_assert!(
277            t >= 0.0 && t <= 1.0,
278            "the sample value should be in [0.0,1.0]"
279        );
280
281        let h = 1.0 - t;
282        let a = t * t * t;
283        let b = 3.0 * t * t * h;
284        let c = 3.0 * t * h * h;
285        let d = h * h * h;
286        let result = self.points[3].to_vec2() * a
287            + self.points[2].to_vec2() * b
288            + self.points[1].to_vec2() * c
289            + self.points[0].to_vec2() * d;
290        result.to_pos2()
291    }
292
293    /// find a set of points that approximate the cubic Bézier curve.
294    /// the number of points is determined by the tolerance.
295    /// the points may not be evenly distributed in the range [0.0,1.0] (t value)
296    pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
297        let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[3].x).abs() * 0.001);
298        let mut result = vec![self.points[0]];
299        self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
300            result.push(p);
301        });
302        result
303    }
304
305    /// find a set of points that approximate the cubic Bézier curve.
306    /// the number of points is determined by the tolerance.
307    /// the points may not be evenly distributed in the range [0.0,1.0] (t value)
308    /// this api will check whether the curve will cross the base line or not when closed = true.
309    /// The result will be a vec of vec of Pos2. it will store two closed aren in different vec.
310    /// The epsilon is used to compare a float value.
311    pub fn flatten_closed(&self, tolerance: Option<f32>, epsilon: Option<f32>) -> Vec<Vec<Pos2>> {
312        let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[3].x).abs() * 0.001);
313        let epsilon = epsilon.unwrap_or(1.0e-5);
314        let mut result = Vec::new();
315        let mut first_half = Vec::new();
316        let mut second_half = Vec::new();
317        let mut flipped = false;
318        first_half.push(self.points[0]);
319
320        let cross = self.find_cross_t(epsilon);
321        match cross {
322            Some(cross) => {
323                if self.closed {
324                    self.for_each_flattened_with_t(tolerance, &mut |p, t| {
325                        if t < cross {
326                            first_half.push(p);
327                        } else {
328                            if !flipped {
329                                // when just crossed the base line, flip the order of the points
330                                // add the cross point to the first half as the last point
331                                // and add the cross point to the second half as the first point
332                                flipped = true;
333                                let cross_point = self.sample(cross);
334                                first_half.push(cross_point);
335                                second_half.push(cross_point);
336                            }
337                            second_half.push(p);
338                        }
339                    });
340                } else {
341                    self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
342                        first_half.push(p);
343                    });
344                }
345            }
346            None => {
347                self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
348                    first_half.push(p);
349                });
350            }
351        }
352
353        result.push(first_half);
354        if !second_half.is_empty() {
355            result.push(second_half);
356        }
357        result
358    }
359    // from lyon_geom::cubic_bezier.rs
360    /// Iterates through the curve invoking a callback at each point.
361    pub fn for_each_flattened_with_t<F: FnMut(Pos2, f32)>(&self, tolerance: f32, callback: &mut F) {
362        flatten_cubic_bezier_with_t(self, tolerance, callback);
363    }
364}
365
366impl From<CubicBezierShape> for Shape {
367    #[inline(always)]
368    fn from(shape: CubicBezierShape) -> Self {
369        Self::CubicBezier(shape)
370    }
371}
372
373// ----------------------------------------------------------------------------
374
375/// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
376///
377/// See also [`CubicBezierShape`].
378#[derive(Clone, Debug, PartialEq)]
379#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
380pub struct QuadraticBezierShape {
381    /// The first point is the starting point and the last one is the ending point of the curve.
382    /// The middle point is the control points.
383    pub points: [Pos2; 3],
384    pub closed: bool,
385
386    pub fill: Color32,
387    pub stroke: PathStroke,
388}
389
390impl QuadraticBezierShape {
391    /// Create a new quadratic Bézier shape based on the 3 points and stroke.
392    ///
393    /// The first point is the starting point and the last one is the ending point of the curve.
394    /// The middle point is the control points.
395    /// The points should be in the order [start, control, end]
396    pub fn from_points_stroke(
397        points: [Pos2; 3],
398        closed: bool,
399        fill: Color32,
400        stroke: impl Into<PathStroke>,
401    ) -> Self {
402        Self {
403            points,
404            closed,
405            fill,
406            stroke: stroke.into(),
407        }
408    }
409
410    /// Transform the curve with the given transform.
411    pub fn transform(&self, transform: &RectTransform) -> Self {
412        let mut points = [Pos2::default(); 3];
413        for (i, origin_point) in self.points.iter().enumerate() {
414            points[i] = transform * *origin_point;
415        }
416        Self {
417            points,
418            closed: self.closed,
419            fill: self.fill,
420            stroke: self.stroke.clone(),
421        }
422    }
423
424    /// Convert the quadratic Bézier curve to one [`PathShape`].
425    /// The `tolerance` will be used to control the max distance between the curve and the base line.
426    pub fn to_path_shape(&self, tolerance: Option<f32>) -> PathShape {
427        let points = self.flatten(tolerance);
428        PathShape {
429            points,
430            closed: self.closed,
431            fill: self.fill,
432            stroke: self.stroke.clone(),
433        }
434    }
435
436    /// The visual bounding rectangle (includes stroke width)
437    pub fn visual_bounding_rect(&self) -> Rect {
438        if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
439            Rect::NOTHING
440        } else {
441            self.logical_bounding_rect().expand(self.stroke.width / 2.0)
442        }
443    }
444
445    /// Logical bounding rectangle (ignoring stroke width)
446    pub fn logical_bounding_rect(&self) -> Rect {
447        let (mut min_x, mut max_x) = if self.points[0].x < self.points[2].x {
448            (self.points[0].x, self.points[2].x)
449        } else {
450            (self.points[2].x, self.points[0].x)
451        };
452        let (mut min_y, mut max_y) = if self.points[0].y < self.points[2].y {
453            (self.points[0].y, self.points[2].y)
454        } else {
455            (self.points[2].y, self.points[0].y)
456        };
457
458        quadratic_for_each_local_extremum(
459            self.points[0].x,
460            self.points[1].x,
461            self.points[2].x,
462            &mut |t| {
463                let x = self.sample(t).x;
464                if x < min_x {
465                    min_x = x;
466                }
467                if x > max_x {
468                    max_x = x;
469                }
470            },
471        );
472
473        quadratic_for_each_local_extremum(
474            self.points[0].y,
475            self.points[1].y,
476            self.points[2].y,
477            &mut |t| {
478                let y = self.sample(t).y;
479                if y < min_y {
480                    min_y = y;
481                }
482                if y > max_y {
483                    max_y = y;
484                }
485            },
486        );
487
488        Rect {
489            min: Pos2 { x: min_x, y: min_y },
490            max: Pos2 { x: max_x, y: max_y },
491        }
492    }
493
494    /// Calculate the point (x,y) at t based on the quadratic Bézier curve equation.
495    /// t is in [0.0,1.0]
496    /// [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves)
497    ///
498    pub fn sample(&self, t: f32) -> Pos2 {
499        debug_assert!(
500            t >= 0.0 && t <= 1.0,
501            "the sample value should be in [0.0,1.0]"
502        );
503
504        let h = 1.0 - t;
505        let a = t * t;
506        let b = 2.0 * t * h;
507        let c = h * h;
508        let result = self.points[2].to_vec2() * a
509            + self.points[1].to_vec2() * b
510            + self.points[0].to_vec2() * c;
511        result.to_pos2()
512    }
513
514    /// find a set of points that approximate the quadratic Bézier curve.
515    /// the number of points is determined by the tolerance.
516    /// the points may not be evenly distributed in the range [0.0,1.0] (t value)
517    pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
518        let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[2].x).abs() * 0.001);
519        let mut result = vec![self.points[0]];
520        self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
521            result.push(p);
522        });
523        result
524    }
525
526    // copied from https://docs.rs/lyon_geom/latest/lyon_geom/
527    /// Compute a flattened approximation of the curve, invoking a callback at
528    /// each step.
529    ///
530    /// The callback takes the point and corresponding curve parameter at each step.
531    ///
532    /// This implements the algorithm described by Raph Levien at
533    /// <https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html>
534    pub fn for_each_flattened_with_t<F>(&self, tolerance: f32, callback: &mut F)
535    where
536        F: FnMut(Pos2, f32),
537    {
538        let params = FlatteningParameters::from_curve(self, tolerance);
539        if params.is_point {
540            return;
541        }
542
543        let count = params.count as u32;
544        for index in 1..count {
545            let t = params.t_at_iteration(index as f32);
546
547            callback(self.sample(t), t);
548        }
549
550        callback(self.sample(1.0), 1.0);
551    }
552}
553
554impl From<QuadraticBezierShape> for Shape {
555    #[inline(always)]
556    fn from(shape: QuadraticBezierShape) -> Self {
557        Self::QuadraticBezier(shape)
558    }
559}
560
561// ----------------------------------------------------------------------------
562
563// lyon_geom::flatten_cubic.rs
564// copied from https://docs.rs/lyon_geom/latest/lyon_geom/
565fn flatten_cubic_bezier_with_t<F: FnMut(Pos2, f32)>(
566    curve: &CubicBezierShape,
567    tolerance: f32,
568    callback: &mut F,
569) {
570    // debug_assert!(tolerance >= S::EPSILON * S::EPSILON);
571    let quadratics_tolerance = tolerance * 0.2;
572    let flattening_tolerance = tolerance * 0.8;
573
574    let num_quadratics = curve.num_quadratics(quadratics_tolerance);
575    let step = 1.0 / num_quadratics as f32;
576    let n = num_quadratics;
577    let mut t0 = 0.0;
578    for _ in 0..(n - 1) {
579        let t1 = t0 + step;
580
581        let quadratic = single_curve_approximation(&curve.split_range(t0..t1));
582        quadratic.for_each_flattened_with_t(flattening_tolerance, &mut |point, t_sub| {
583            let t = t0 + step * t_sub;
584            callback(point, t);
585        });
586
587        t0 = t1;
588    }
589
590    // Do the last step manually to make sure we finish at t = 1.0 exactly.
591    let quadratic = single_curve_approximation(&curve.split_range(t0..1.0));
592    quadratic.for_each_flattened_with_t(flattening_tolerance, &mut |point, t_sub| {
593        let t = t0 + step * t_sub;
594        callback(point, t);
595    });
596}
597
598// from lyon_geom::quadratic_bezier.rs
599// copied from https://docs.rs/lyon_geom/latest/lyon_geom/
600struct FlatteningParameters {
601    count: f32,
602    integral_from: f32,
603    integral_step: f32,
604    inv_integral_from: f32,
605    div_inv_integral_diff: f32,
606    is_point: bool,
607}
608
609impl FlatteningParameters {
610    // https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html
611    pub fn from_curve(curve: &QuadraticBezierShape, tolerance: f32) -> Self {
612        // Map the quadratic bézier segment to y = x^2 parabola.
613        let from = curve.points[0];
614        let ctrl = curve.points[1];
615        let to = curve.points[2];
616
617        let ddx = 2.0 * ctrl.x - from.x - to.x;
618        let ddy = 2.0 * ctrl.y - from.y - to.y;
619        let cross = (to.x - from.x) * ddy - (to.y - from.y) * ddx;
620        let inv_cross = 1.0 / cross;
621        let parabola_from = ((ctrl.x - from.x) * ddx + (ctrl.y - from.y) * ddy) * inv_cross;
622        let parabola_to = ((to.x - ctrl.x) * ddx + (to.y - ctrl.y) * ddy) * inv_cross;
623        // Note, scale can be NaN, for example with straight lines. When it happens the NaN will
624        // propagate to other parameters. We catch it all by setting the iteration count to zero
625        // and leave the rest as garbage.
626        let scale = cross.abs() / (ddx.hypot(ddy) * (parabola_to - parabola_from).abs());
627
628        let integral_from = approx_parabola_integral(parabola_from);
629        let integral_to = approx_parabola_integral(parabola_to);
630        let integral_diff = integral_to - integral_from;
631
632        let inv_integral_from = approx_parabola_inv_integral(integral_from);
633        let inv_integral_to = approx_parabola_inv_integral(integral_to);
634        let div_inv_integral_diff = 1.0 / (inv_integral_to - inv_integral_from);
635
636        // the original author thinks it can be stored as integer if it's not generic.
637        // but if so, we have to handle the edge case of the integral being infinite.
638        let mut count = (0.5 * integral_diff.abs() * (scale / tolerance).sqrt()).ceil();
639        let mut is_point = false;
640        // If count is NaN the curve can be approximated by a single straight line or a point.
641        if !count.is_finite() {
642            count = 0.0;
643            is_point = (to.x - from.x).hypot(to.y - from.y) < tolerance * tolerance;
644        }
645
646        let integral_step = integral_diff / count;
647
648        Self {
649            count,
650            integral_from,
651            integral_step,
652            inv_integral_from,
653            div_inv_integral_diff,
654            is_point,
655        }
656    }
657
658    fn t_at_iteration(&self, iteration: f32) -> f32 {
659        let u = approx_parabola_inv_integral(self.integral_from + self.integral_step * iteration);
660        (u - self.inv_integral_from) * self.div_inv_integral_diff
661    }
662}
663
664/// Compute an approximation to integral (1 + 4x^2) ^ -0.25 dx used in the flattening code.
665fn approx_parabola_integral(x: f32) -> f32 {
666    let d: f32 = 0.67;
667    let quarter = 0.25;
668    x / (1.0 - d + (d.powi(4) + quarter * x * x).sqrt().sqrt())
669}
670
671/// Approximate the inverse of the function above.
672fn approx_parabola_inv_integral(x: f32) -> f32 {
673    let b = 0.39;
674    let quarter = 0.25;
675    x * (1.0 - b + (b * b + quarter * x * x).sqrt())
676}
677
678fn single_curve_approximation(curve: &CubicBezierShape) -> QuadraticBezierShape {
679    let c1_x = (curve.points[1].x * 3.0 - curve.points[0].x) * 0.5;
680    let c1_y = (curve.points[1].y * 3.0 - curve.points[0].y) * 0.5;
681    let c2_x = (curve.points[2].x * 3.0 - curve.points[3].x) * 0.5;
682    let c2_y = (curve.points[2].y * 3.0 - curve.points[3].y) * 0.5;
683    let c = Pos2 {
684        x: (c1_x + c2_x) * 0.5,
685        y: (c1_y + c2_y) * 0.5,
686    };
687    QuadraticBezierShape {
688        points: [curve.points[0], c, curve.points[3]],
689        closed: curve.closed,
690        fill: curve.fill,
691        stroke: curve.stroke.clone(),
692    }
693}
694
695fn quadratic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, cb: &mut F) {
696    // A quadratic Bézier curve can be derived by a linear function:
697    // p(t) = p0 + t(p1 - p0) + t^2(p2 - 2p1 + p0)
698    // The derivative is:
699    // p'(t) = (p1 - p0) + 2(p2 - 2p1 + p0)t or:
700    // f(x) = a* x + b
701    let a = p2 - 2.0 * p1 + p0;
702    // let b = p1 - p0;
703    // no need to check for zero, since we're only interested in local extrema
704    if a == 0.0 {
705        return;
706    }
707
708    let t = (p0 - p1) / a;
709    if t > 0.0 && t < 1.0 {
710        cb(t);
711    }
712}
713
714fn cubic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, p3: f32, cb: &mut F) {
715    // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation
716    // A cubic Bézier curve can be derived by the following equation:
717    // B'(t) = 3(1-t)^2(p1-p0) + 6(1-t)t(p2-p1) + 3t^2(p3-p2) or
718    // f(x) = a * x² + b * x + c
719    let a = 3.0 * (p3 + 3.0 * (p1 - p2) - p0);
720    let b = 6.0 * (p2 - 2.0 * p1 + p0);
721    let c = 3.0 * (p1 - p0);
722
723    let in_range = |t: f32| t <= 1.0 && t >= 0.0;
724
725    // linear situation
726    if a == 0.0 {
727        if b != 0.0 {
728            let t = -c / b;
729            if in_range(t) {
730                cb(t);
731            }
732        }
733        return;
734    }
735
736    let discr = b * b - 4.0 * a * c;
737    // no Real solution
738    if discr < 0.0 {
739        return;
740    }
741
742    // one Real solution
743    if discr == 0.0 {
744        let t = -b / (2.0 * a);
745        if in_range(t) {
746            cb(t);
747        }
748        return;
749    }
750
751    // two Real solutions
752    let discr = discr.sqrt();
753    let t1 = (-b - discr) / (2.0 * a);
754    let t2 = (-b + discr) / (2.0 * a);
755    if in_range(t1) {
756        cb(t1);
757    }
758    if in_range(t2) {
759        cb(t2);
760    }
761}
762
763#[cfg(test)]
764mod tests {
765    use super::*;
766
767    #[test]
768    fn test_quadratic_bounding_box() {
769        let curve = QuadraticBezierShape {
770            points: [
771                Pos2 { x: 110.0, y: 170.0 },
772                Pos2 { x: 10.0, y: 10.0 },
773                Pos2 { x: 180.0, y: 30.0 },
774            ],
775            closed: false,
776            fill: Default::default(),
777            stroke: Default::default(),
778        };
779        let bbox = curve.logical_bounding_rect();
780        assert!((bbox.min.x - 72.96).abs() < 0.01);
781        assert!((bbox.min.y - 27.78).abs() < 0.01);
782
783        assert!((bbox.max.x - 180.0).abs() < 0.01);
784        assert!((bbox.max.y - 170.0).abs() < 0.01);
785
786        let mut result = vec![curve.points[0]]; //add the start point
787        curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
788            result.push(pos);
789        });
790
791        assert_eq!(result.len(), 26);
792
793        let curve = QuadraticBezierShape {
794            points: [
795                Pos2 { x: 110.0, y: 170.0 },
796                Pos2 { x: 180.0, y: 30.0 },
797                Pos2 { x: 10.0, y: 10.0 },
798            ],
799            closed: false,
800            fill: Default::default(),
801            stroke: Default::default(),
802        };
803        let bbox = curve.logical_bounding_rect();
804        assert!((bbox.min.x - 10.0).abs() < 0.01);
805        assert!((bbox.min.y - 10.0).abs() < 0.01);
806
807        assert!((bbox.max.x - 130.42).abs() < 0.01);
808        assert!((bbox.max.y - 170.0).abs() < 0.01);
809
810        let mut result = vec![curve.points[0]]; //add the start point
811        curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
812            result.push(pos);
813        });
814
815        assert_eq!(result.len(), 25);
816    }
817
818    #[test]
819    fn test_quadratic_different_tolerance() {
820        let curve = QuadraticBezierShape {
821            points: [
822                Pos2 { x: 110.0, y: 170.0 },
823                Pos2 { x: 180.0, y: 30.0 },
824                Pos2 { x: 10.0, y: 10.0 },
825            ],
826            closed: false,
827            fill: Default::default(),
828            stroke: Default::default(),
829        };
830        let mut result = vec![curve.points[0]]; //add the start point
831        curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
832            result.push(pos);
833        });
834
835        assert_eq!(result.len(), 9);
836
837        let mut result = vec![curve.points[0]]; //add the start point
838        curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
839            result.push(pos);
840        });
841
842        assert_eq!(result.len(), 25);
843
844        let mut result = vec![curve.points[0]]; //add the start point
845        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
846            result.push(pos);
847        });
848
849        assert_eq!(result.len(), 77);
850
851        let mut result = vec![curve.points[0]]; //add the start point
852        curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
853            result.push(pos);
854        });
855
856        assert_eq!(result.len(), 240);
857    }
858
859    #[test]
860    fn test_cubic_bounding_box() {
861        let curve = CubicBezierShape {
862            points: [
863                pos2(10.0, 10.0),
864                pos2(110.0, 170.0),
865                pos2(180.0, 30.0),
866                pos2(270.0, 210.0),
867            ],
868            closed: false,
869            fill: Default::default(),
870            stroke: Default::default(),
871        };
872
873        let bbox = curve.logical_bounding_rect();
874        assert_eq!(bbox.min.x, 10.0);
875        assert_eq!(bbox.min.y, 10.0);
876        assert_eq!(bbox.max.x, 270.0);
877        assert_eq!(bbox.max.y, 210.0);
878
879        let curve = CubicBezierShape {
880            points: [
881                pos2(10.0, 10.0),
882                pos2(110.0, 170.0),
883                pos2(270.0, 210.0),
884                pos2(180.0, 30.0),
885            ],
886            closed: false,
887            fill: Default::default(),
888            stroke: Default::default(),
889        };
890
891        let bbox = curve.logical_bounding_rect();
892        assert_eq!(bbox.min.x, 10.0);
893        assert_eq!(bbox.min.y, 10.0);
894        assert!((bbox.max.x - 206.50).abs() < 0.01);
895        assert!((bbox.max.y - 148.48).abs() < 0.01);
896
897        let curve = CubicBezierShape {
898            points: [
899                pos2(110.0, 170.0),
900                pos2(10.0, 10.0),
901                pos2(270.0, 210.0),
902                pos2(180.0, 30.0),
903            ],
904            closed: false,
905            fill: Default::default(),
906            stroke: Default::default(),
907        };
908
909        let bbox = curve.logical_bounding_rect();
910        assert!((bbox.min.x - 86.71).abs() < 0.01);
911        assert!((bbox.min.y - 30.0).abs() < 0.01);
912
913        assert!((bbox.max.x - 199.27).abs() < 0.01);
914        assert!((bbox.max.y - 170.0).abs() < 0.01);
915    }
916
917    #[test]
918    fn test_cubic_different_tolerance_flattening() {
919        let curve = CubicBezierShape {
920            points: [
921                pos2(0.0, 0.0),
922                pos2(100.0, 0.0),
923                pos2(100.0, 100.0),
924                pos2(100.0, 200.0),
925            ],
926            closed: false,
927            fill: Default::default(),
928            stroke: Default::default(),
929        };
930
931        let mut result = vec![curve.points[0]]; //add the start point
932        curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
933            result.push(pos);
934        });
935
936        assert_eq!(result.len(), 10);
937
938        let mut result = vec![curve.points[0]]; //add the start point
939        curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
940            result.push(pos);
941        });
942
943        assert_eq!(result.len(), 13);
944
945        let mut result = vec![curve.points[0]]; //add the start point
946        curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
947            result.push(pos);
948        });
949
950        assert_eq!(result.len(), 28);
951
952        let mut result = vec![curve.points[0]]; //add the start point
953        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
954            result.push(pos);
955        });
956
957        assert_eq!(result.len(), 83);
958
959        let mut result = vec![curve.points[0]]; //add the start point
960        curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
961            result.push(pos);
962        });
963
964        assert_eq!(result.len(), 248);
965    }
966
967    #[test]
968    fn test_cubic_different_shape_flattening() {
969        let curve = CubicBezierShape {
970            points: [
971                pos2(90.0, 110.0),
972                pos2(30.0, 170.0),
973                pos2(210.0, 170.0),
974                pos2(170.0, 110.0),
975            ],
976            closed: false,
977            fill: Default::default(),
978            stroke: Default::default(),
979        };
980
981        let mut result = vec![curve.points[0]]; //add the start point
982        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
983            result.push(pos);
984        });
985
986        assert_eq!(result.len(), 117);
987
988        let curve = CubicBezierShape {
989            points: [
990                pos2(90.0, 110.0),
991                pos2(90.0, 170.0),
992                pos2(170.0, 170.0),
993                pos2(170.0, 110.0),
994            ],
995            closed: false,
996            fill: Default::default(),
997            stroke: Default::default(),
998        };
999
1000        let mut result = vec![curve.points[0]]; //add the start point
1001        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1002            result.push(pos);
1003        });
1004
1005        assert_eq!(result.len(), 91);
1006
1007        let curve = CubicBezierShape {
1008            points: [
1009                pos2(90.0, 110.0),
1010                pos2(110.0, 170.0),
1011                pos2(150.0, 170.0),
1012                pos2(170.0, 110.0),
1013            ],
1014            closed: false,
1015            fill: Default::default(),
1016            stroke: Default::default(),
1017        };
1018
1019        let mut result = vec![curve.points[0]]; //add the start point
1020        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1021            result.push(pos);
1022        });
1023
1024        assert_eq!(result.len(), 75);
1025
1026        let curve = CubicBezierShape {
1027            points: [
1028                pos2(90.0, 110.0),
1029                pos2(110.0, 170.0),
1030                pos2(230.0, 110.0),
1031                pos2(170.0, 110.0),
1032            ],
1033            closed: false,
1034            fill: Default::default(),
1035            stroke: Default::default(),
1036        };
1037
1038        let mut result = vec![curve.points[0]]; //add the start point
1039        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1040            result.push(pos);
1041        });
1042
1043        assert_eq!(result.len(), 100);
1044
1045        let curve = CubicBezierShape {
1046            points: [
1047                pos2(90.0, 110.0),
1048                pos2(110.0, 170.0),
1049                pos2(210.0, 70.0),
1050                pos2(170.0, 110.0),
1051            ],
1052            closed: false,
1053            fill: Default::default(),
1054            stroke: Default::default(),
1055        };
1056
1057        let mut result = vec![curve.points[0]]; //add the start point
1058        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1059            result.push(pos);
1060        });
1061
1062        assert_eq!(result.len(), 71);
1063
1064        let curve = CubicBezierShape {
1065            points: [
1066                pos2(90.0, 110.0),
1067                pos2(110.0, 170.0),
1068                pos2(150.0, 50.0),
1069                pos2(170.0, 110.0),
1070            ],
1071            closed: false,
1072            fill: Default::default(),
1073            stroke: Default::default(),
1074        };
1075
1076        let mut result = vec![curve.points[0]]; //add the start point
1077        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1078            result.push(pos);
1079        });
1080
1081        assert_eq!(result.len(), 88);
1082    }
1083
1084    #[test]
1085    fn test_quadratic_flattening() {
1086        let curve = QuadraticBezierShape {
1087            points: [pos2(0.0, 0.0), pos2(80.0, 200.0), pos2(100.0, 30.0)],
1088            closed: false,
1089            fill: Default::default(),
1090            stroke: Default::default(),
1091        };
1092
1093        let mut result = vec![curve.points[0]]; //add the start point
1094        curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
1095            result.push(pos);
1096        });
1097
1098        assert_eq!(result.len(), 9);
1099
1100        let mut result = vec![curve.points[0]]; //add the start point
1101        curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
1102            result.push(pos);
1103        });
1104
1105        assert_eq!(result.len(), 11);
1106
1107        let mut result = vec![curve.points[0]]; //add the start point
1108        curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
1109            result.push(pos);
1110        });
1111
1112        assert_eq!(result.len(), 24);
1113
1114        let mut result = vec![curve.points[0]]; //add the start point
1115        curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1116            result.push(pos);
1117        });
1118
1119        assert_eq!(result.len(), 72);
1120        let mut result = vec![curve.points[0]]; //add the start point
1121        curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
1122            result.push(pos);
1123        });
1124
1125        assert_eq!(result.len(), 223);
1126    }
1127}