makepad_vector/geometry/
cubic_segment.rs

1use crate::geometry::{Point, Transform, Transformation};
2use crate::internal_iter::InternalIterator;
3
4/// A cubic bezier curve segment in 2-dimensional Euclidian space.
5#[derive(Clone, Copy, Debug, PartialEq)]
6#[repr(C)]
7pub struct CubicSegment {
8    pub p0: Point,
9    pub p1: Point,
10    pub p2: Point,
11    pub p3: Point
12}
13
14impl CubicSegment {
15    /// Creates a new cubic bezier curve segment with the given control points.
16    pub fn new(p0: Point, p1: Point, p2: Point, p3: Point) -> CubicSegment {
17        CubicSegment { p0, p1, p2, p3 }
18    }
19
20    /// Returns true if `self` is approximately linear with tolerance `epsilon`.
21    pub fn is_approximately_linear(self, epsilon: f64) -> bool {
22        let v1 = self.p1 - self.p0;
23        let v2 = self.p2 - self.p0;
24        if let Some(vx) = (self.p3 - self.p0).normalize() {
25            // If the baseline is a line segment, the segment is approximately linear if the
26            // rejection of both control points from the baseline is less than `epsilon`.
27            v1.cross(vx).abs() < epsilon && v2.cross(vx).abs() < epsilon
28        } else {
29            // If the baseline is a single point, the segment is approximately linear if the
30            // distance of both control points from the baseline is less than `epsilon`.
31            v1.length() < epsilon && v2.length() < epsilon
32        }
33    }
34
35    /// Splits `self` into two quadratic Bezier curve segments, at parameter `t`.
36    pub fn split(self, t: f64) -> (CubicSegment, CubicSegment) {
37        let p01 = self.p0.lerp(self.p1, t);
38        let p12 = self.p1.lerp(self.p2, t);
39        let p23 = self.p2.lerp(self.p3, t);
40        let p012 = p01.lerp(p12, t);
41        let p123 = p12.lerp(p23, t);
42        let p0123 = p012.lerp(p123, t);
43        (
44            CubicSegment::new(self.p0, p01, p012, p0123),
45            CubicSegment::new(p0123, p123, p23, self.p3),
46        )
47    }
48
49    /// Returns an iterator over the points of a polyline that approximates `self` with tolerance
50    /// `epsilon`, *excluding* the first point.
51    pub fn linearize(self, epsilon: f64) -> Linearize {
52        Linearize {
53            segment: self,
54            epsilon,
55        }
56    }
57}
58
59impl Transform for CubicSegment {
60    fn transform<T>(self, t: &T) -> CubicSegment
61    where
62        T: Transformation,
63    {
64        CubicSegment::new(
65            self.p0.transform(t),
66            self.p1.transform(t),
67            self.p2.transform(t),
68            self.p3.transform(t),
69        )
70    }
71
72    fn transform_mut<T>(&mut self, t: &T)
73    where
74        T: Transformation,
75    {
76        *self = self.transform(t);
77    }
78}
79
80/// An iterator over the points of a polyline that approximates `self` with tolerance `epsilon`,
81/// *excluding* the first point.
82#[derive(Clone, Copy)]
83pub struct Linearize {
84    segment: CubicSegment,
85    epsilon: f64,
86}
87
88impl InternalIterator for Linearize {
89    type Item = Point;
90
91    fn for_each<F>(self, f: &mut F) -> bool
92    where
93        F: FnMut(Point) -> bool,
94    {
95        if self.segment.is_approximately_linear(self.epsilon) {
96            return f(self.segment.p3);
97        }
98        let (segment_0, segment_1) = self.segment.split(0.5);
99        if !segment_0.linearize(self.epsilon).for_each(f) {
100            return false;
101        }
102        segment_1.linearize(self.epsilon).for_each(f)
103    }
104}