1use super::{CubicBezier, LineSegment, Point, PointDot, PointIndex, PointNorm, QuadraticBezier};
4use num_traits::{Float, NumCast};
5
6const DEFAULT_LENGTH_STEPS: usize = 64;
7
8#[derive(Copy, Clone, Debug, PartialEq)]
13pub enum BezierSegment<P: Point> {
14 Linear(LineSegment<P>),
15 Quadratic(QuadraticBezier<P>),
16 Cubic(CubicBezier<P>),
17}
18
19impl<P> BezierSegment<P>
20where
21 P: Point,
22{
23 pub fn eval(&self, t: P::Scalar) -> P {
25 match self {
26 BezierSegment::Linear(segment) => segment.eval(t),
27 BezierSegment::Quadratic(segment) => segment.eval(t),
28 BezierSegment::Cubic(segment) => segment.eval(t),
29 }
30 }
31
32 pub fn start(&self) -> P {
34 match self {
35 BezierSegment::Linear(segment) => segment.start,
36 BezierSegment::Quadratic(segment) => segment.start,
37 BezierSegment::Cubic(segment) => segment.start,
38 }
39 }
40
41 #[inline]
42 pub fn end(&self) -> P {
44 match self {
45 BezierSegment::Linear(segment) => segment.end,
46 BezierSegment::Quadratic(segment) => segment.end,
47 BezierSegment::Cubic(segment) => segment.end,
48 }
49 }
50
51 pub fn reverse(&self) -> Self {
53 match self {
54 BezierSegment::Linear(segment) => BezierSegment::Linear(segment.reverse()),
55 BezierSegment::Quadratic(segment) => BezierSegment::Quadratic(segment.reverse()),
56 BezierSegment::Cubic(segment) => BezierSegment::Cubic(segment.reverse()),
57 }
58 }
59
60 #[inline]
61 pub fn is_linear<F>(&self, tolerance: F) -> bool
63 where
64 P: PointNorm,
65 F: Float + NumCast,
66 {
67 let tolerance = <P::Scalar as NumCast>::from(tolerance).unwrap();
68 match self {
69 BezierSegment::Linear(..) => true,
70 BezierSegment::Quadratic(segment) => segment.is_linear(tolerance),
71 BezierSegment::Cubic(segment) => segment.is_linear(tolerance),
72 }
73 }
74
75 #[inline]
76 pub fn baseline(&self) -> LineSegment<P> {
78 match self {
79 BezierSegment::Linear(segment) => *segment,
80 BezierSegment::Quadratic(segment) => segment.baseline(),
81 BezierSegment::Cubic(segment) => segment.baseline(),
82 }
83 }
84
85 #[inline]
86 pub fn bounding_box(&self) -> [(P::Scalar, P::Scalar); P::DIM]
88 where
89 P: PointIndex,
90 [P::Scalar; 1]: tinyvec::Array<Item = P::Scalar>,
91 [P::Scalar; 2]: tinyvec::Array<Item = P::Scalar>,
92 [P::Scalar; 3]: tinyvec::Array<Item = P::Scalar>,
93 [P::Scalar; 4]: tinyvec::Array<Item = P::Scalar>,
94 {
95 match self {
96 BezierSegment::Linear(segment) => segment.bounding_box(),
97 BezierSegment::Quadratic(segment) => segment.bounding_box(),
98 BezierSegment::Cubic(segment) => segment.bounding_box(),
99 }
100 }
101
102 pub fn split<F>(&self, t: F) -> (BezierSegment<P>, BezierSegment<P>)
104 where
105 F: Float + NumCast,
106 {
107 let t = <P::Scalar as NumCast>::from(t).unwrap();
108 match self {
109 BezierSegment::Linear(segment) => {
110 let (a, b) = segment.split(t);
111 (BezierSegment::Linear(a), BezierSegment::Linear(b))
112 }
113 BezierSegment::Quadratic(segment) => {
114 let (a, b) = segment.split(t);
115 (BezierSegment::Quadratic(a), BezierSegment::Quadratic(b))
116 }
117 BezierSegment::Cubic(segment) => {
118 let (a, b) = segment.split(t);
119 (BezierSegment::Cubic(a), BezierSegment::Cubic(b))
120 }
121 }
122 }
123
124 pub fn tangent(&self, t: P::Scalar) -> P
126 where
127 P: PointNorm,
128 {
129 match self {
130 BezierSegment::Linear(segment) => segment.tangent(t),
131 BezierSegment::Quadratic(segment) => segment.tangent(t),
132 BezierSegment::Cubic(segment) => segment.tangent(t),
133 }
134 }
135
136 pub fn curvature(&self, t: P::Scalar) -> P::Scalar
138 where
139 P: PointNorm + PointDot,
140 {
141 match self {
142 BezierSegment::Linear(segment) => segment.curvature(t),
143 BezierSegment::Quadratic(segment) => segment.curvature(t),
144 BezierSegment::Cubic(segment) => segment.curvature(t),
145 }
146 }
147
148 pub fn normal(&self, t: P::Scalar) -> Option<P>
152 where
153 P: PointNorm + PointDot,
154 {
155 match self {
156 BezierSegment::Linear(segment) => segment.normal(t),
157 BezierSegment::Quadratic(segment) => segment.normal(t),
158 BezierSegment::Cubic(segment) => segment.normal(t),
159 }
160 }
161
162 pub fn t_at_length_approx(&self, s: P::Scalar, nsteps: usize) -> P::Scalar
164 where
165 P: PointNorm,
166 {
167 match self {
168 BezierSegment::Linear(segment) => segment.t_at_length_approx(s, nsteps),
169 BezierSegment::Quadratic(segment) => segment.t_at_length_approx(s, nsteps),
170 BezierSegment::Cubic(segment) => segment.t_at_length_approx(s, nsteps),
171 }
172 }
173
174 pub fn t_at_length(&self, s: P::Scalar) -> P::Scalar
176 where
177 P: PointNorm,
178 {
179 self.t_at_length_approx(s, DEFAULT_LENGTH_STEPS)
180 }
181
182 pub fn point_at_length_approx(&self, s: P::Scalar, nsteps: usize) -> P
184 where
185 P: PointNorm,
186 {
187 self.eval(self.t_at_length_approx(s, nsteps))
188 }
189
190 pub fn point_at_length(&self, s: P::Scalar) -> P
192 where
193 P: PointNorm,
194 {
195 self.point_at_length_approx(s, DEFAULT_LENGTH_STEPS)
196 }
197}
198
199impl<P> From<LineSegment<P>> for BezierSegment<P>
200where
201 P: Point,
202{
203 fn from(s: LineSegment<P>) -> Self {
204 BezierSegment::Linear(s)
205 }
206}
207
208impl<P> From<QuadraticBezier<P>> for BezierSegment<P>
209where
210 P: Point,
211{
212 fn from(s: QuadraticBezier<P>) -> Self {
213 BezierSegment::Quadratic(s)
214 }
215}
216
217impl<P> From<CubicBezier<P>> for BezierSegment<P>
218where
219 P: Point,
220{
221 fn from(s: CubicBezier<P>) -> Self {
222 BezierSegment::Cubic(s)
223 }
224}
225
226impl<P> Default for BezierSegment<P>
227where
228 P: Point + Default,
229{
230 fn default() -> Self {
231 BezierSegment::Linear(LineSegment::new(P::default(), P::default()))
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use crate::{EPSILON, PointN, PointNorm};
239
240 #[test]
241 fn bezier_segment_api_parity() {
242 let line = LineSegment::new(PointN::new([0f64, 0f64]), PointN::new([2f64, 0f64]));
243 let segment = BezierSegment::from(line);
244
245 assert_eq!(segment.start(), segment.eval(0.0));
246 assert_eq!(segment.end(), segment.eval(1.0));
247
248 let reversed = segment.reverse();
249 let p0 = segment.eval(0.25);
250 let p1 = reversed.eval(0.75);
251 assert!((p0 - p1).squared_norm() < EPSILON);
252
253 let tangent = segment.tangent(0.3);
254 assert!((tangent[0] - 1.0).abs() < EPSILON);
255 assert!(tangent[1].abs() < EPSILON);
256
257 let curvature = segment.curvature(0.3);
258 assert!(curvature.abs() < EPSILON);
259 assert!(segment.normal(0.3).is_none());
260
261 let t = segment.t_at_length(1.0);
262 assert!((t - 0.5).abs() < EPSILON);
263
264 let p = segment.point_at_length(1.0);
265 assert!((p - PointN::new([1.0, 0.0])).squared_norm() < EPSILON);
266 }
267}