pendragon/
bezier.rs

1use crate::{
2    options::{Options, StrokeOptions},
3    tess, PolyBuilder,
4};
5use gee::{Point, Rect};
6
7#[derive(Clone, Debug)]
8pub enum ControlPoint {
9    Quadratic(Point),
10    Cubic(Point, Point),
11}
12
13#[derive(Clone, Debug)]
14pub struct BezierSegment {
15    end: Point,
16    ctrl: ControlPoint,
17}
18
19impl BezierSegment {
20    pub fn new(end: Point, ctrl: ControlPoint) -> Self {
21        Self { end, ctrl }
22    }
23
24    pub fn quadratic(end: Point, ctrl: Point) -> Self {
25        Self::new(end, ControlPoint::Quadratic(ctrl))
26    }
27
28    pub fn cubic(end: Point, ctrl1: Point, ctrl2: Point) -> Self {
29        Self::new(end, ControlPoint::Cubic(ctrl1, ctrl2))
30    }
31}
32
33#[derive(Clone, Debug, Default)]
34pub struct BezierBuilder {
35    start: Point,
36    segments: Vec<BezierSegment>,
37    open: bool,
38    options: Options,
39}
40
41impl BezierBuilder {
42    pub fn new(start: Point) -> Self {
43        Self::default().with_start(start)
44    }
45
46    pub fn from_bezier_segment(start: Point, segment: BezierSegment) -> Self {
47        Self::new(start).with_bezier_segment(segment)
48    }
49
50    pub fn from_bezier_segments(
51        start: Point,
52        segments: impl IntoIterator<Item = BezierSegment>,
53    ) -> Self {
54        Self::new(start).with_bezier_segments(segments)
55    }
56
57    pub fn from_quadratic_segment(start: Point, end: Point, ctrl: Point) -> Self {
58        Self::from_bezier_segment(start, BezierSegment::quadratic(end, ctrl))
59    }
60
61    pub fn from_cubic_segment(start: Point, end: Point, ctrl1: Point, ctrl2: Point) -> Self {
62        Self::from_bezier_segment(start, BezierSegment::cubic(end, ctrl1, ctrl2))
63    }
64
65    pub fn with_start(mut self, start: Point) -> Self {
66        self.start = start;
67        self
68    }
69
70    pub fn with_bezier_segment(mut self, segment: BezierSegment) -> Self {
71        self.segments.push(segment);
72        self
73    }
74
75    pub fn with_bezier_segments(
76        mut self,
77        segments: impl IntoIterator<Item = BezierSegment>,
78    ) -> Self {
79        self.segments.extend(segments);
80        self
81    }
82
83    pub fn with_quadratic_segment(self, end: Point, ctrl: Point) -> Self {
84        self.with_bezier_segment(BezierSegment::quadratic(end, ctrl))
85    }
86
87    pub fn with_cubic_segment(self, end: Point, ctrl1: Point, ctrl2: Point) -> Self {
88        self.with_bezier_segment(BezierSegment::cubic(end, ctrl1, ctrl2))
89    }
90
91    pub fn with_stroke(mut self, stroke_width: f32, open: bool) -> Self {
92        self.open = open;
93        self._with_stroke(stroke_width)
94    }
95
96    pub fn with_stroke_open(self, stroke_width: f32) -> Self {
97        self.with_stroke(stroke_width, true)
98    }
99
100    pub fn with_stroke_closed(self, stroke_width: f32) -> Self {
101        self.with_stroke(stroke_width, false)
102    }
103
104    pub fn with_stroke_opts(mut self, stroke_options: StrokeOptions, open: bool) -> Self {
105        self.open = open;
106        self._with_stroke_opts(stroke_options)
107    }
108
109    pub fn with_stroke_opts_open(self, stroke_options: StrokeOptions) -> Self {
110        self.with_stroke_opts(stroke_options, true)
111    }
112
113    pub fn with_stroke_opts_closed(self, stroke_options: StrokeOptions) -> Self {
114        self.with_stroke_opts(stroke_options, false)
115    }
116
117    stroke!(private);
118
119    fill!();
120
121    build!();
122}
123
124impl PolyBuilder for BezierBuilder {
125    fn options(&self) -> &Options {
126        &self.options
127    }
128
129    fn bounding_rect(&self) -> Rect {
130        todo!("bézier curves can't be filled")
131    }
132
133    fn build<B: tess::path::traits::PathBuilder>(self, builder: &mut B) {
134        builder.begin(self.start.into());
135        for segment in self.segments {
136            match segment.ctrl {
137                ControlPoint::Quadratic(ctrl) => {
138                    builder.quadratic_bezier_to(ctrl.into(), segment.end.into());
139                }
140                ControlPoint::Cubic(ctrl1, ctrl2) => {
141                    builder.cubic_bezier_to(ctrl1.into(), ctrl2.into(), segment.end.into());
142                }
143            }
144        }
145        builder.end(!self.open);
146    }
147}