traffic_sim/math/curve/
bezier.rs

1use super::{ParametricCurve2d, Point2d, Vector2d};
2use crate::util::Interval;
3use cgmath::prelude::*;
4use serde::{Deserialize, Serialize};
5
6/// A line segment
7#[derive(Copy, Clone)]
8pub struct LineSegment2d {
9    start: Point2d,
10    tangent: Vector2d,
11    length: f64,
12}
13
14impl LineSegment2d {
15    /// Creates a 2D line segment given two endpoints.
16    pub fn from_ends(start: Point2d, end: Point2d) -> Self {
17        let diff = end.to_vec() - start.to_vec();
18        let length = diff.magnitude();
19        let mut tangent = diff / length;
20        if tangent.x.is_nan() {
21            tangent = Vector2d::zero();
22        }
23        Self {
24            start,
25            tangent,
26            length,
27        }
28    }
29
30    /// The start point of the line segment.
31    pub const fn start(&self) -> Point2d {
32        self.start
33    }
34
35    /// The end point of the line segment.
36    pub fn end(&self) -> Point2d {
37        self.start + self.length * self.tangent
38    }
39}
40
41impl ParametricCurve2d for LineSegment2d {
42    fn sample(&self, t: f64) -> Point2d {
43        self.start + t * self.tangent
44    }
45
46    fn bounds(&self) -> Interval<f64> {
47        Interval::new(0.0, self.length)
48    }
49
50    fn sample_dt(&self, _t: f64) -> Vector2d {
51        self.tangent
52    }
53}
54
55/// A quadratic bezier curve
56#[derive(Copy, Clone, Serialize, Deserialize)]
57pub struct QuadraticBezier2d {
58    points: [Point2d; 3],
59}
60
61impl QuadraticBezier2d {
62    /// Creates a new cubic bezier curve that passes through the provided control points.
63    pub const fn new(points: &[Point2d; 3]) -> Self {
64        Self { points: *points }
65    }
66}
67
68impl ParametricCurve2d for QuadraticBezier2d {
69    fn sample(&self, t: f64) -> Point2d {
70        let t1 = 1.0 - t;
71        Point2d::from_vec(
72            t1 * t1 * self.points[0].to_vec()
73                + 2.0 * t1 * t * self.points[1].to_vec()
74                + t * t * self.points[2].to_vec(),
75        )
76    }
77
78    fn bounds(&self) -> Interval<f64> {
79        Interval { min: 0.0, max: 1.0 }
80    }
81
82    fn sample_dt(&self, t: f64) -> Vector2d {
83        let t1 = 1.0 - t;
84        -2.0 * t1 * self.points[0].to_vec()
85            + (2.0 - 4.0 * t) * self.points[1].to_vec()
86            + 2.0 * t * self.points[2].to_vec()
87    }
88
89    fn sample_dt2(&self, _t: f64) -> Vector2d {
90        2.0 * self.points[0].to_vec() - 4.0 * self.points[1].to_vec()
91            + 2.0 * self.points[2].to_vec()
92    }
93}
94
95/// A cubic bezier curve
96#[derive(Copy, Clone, Debug)]
97pub struct CubicBezier2d {
98    points: [Point2d; 4],
99}
100
101impl CubicBezier2d {
102    /// Creates a new cubic bezier curve that passes through the provided control points.
103    pub const fn new(points: &[Point2d; 4]) -> Self {
104        Self { points: *points }
105    }
106
107    /// Creates a cubic bezier curve which is a straight line passing through the given endpoints.
108    pub fn line(start: Point2d, end: Point2d) -> Self {
109        let s = start.to_vec();
110        let e = end.to_vec();
111        let ps = [s, s.lerp(e, 1. / 3.), s.lerp(e, 2. / 3.), e];
112        Self {
113            points: ps.map(Point2d::from_vec),
114        }
115    }
116
117    /// Creates a cubic bezier curve which is identical to the quadratic bezier curve
118    /// that passes through the provided control points.
119    pub fn quadratic(points: &[Point2d; 3]) -> Self {
120        let points = [
121            points[0],
122            points[0] + (2. / 3.) * (points[1] - points[0]),
123            points[2] + (2. / 3.) * (points[1] - points[2]),
124            points[2],
125        ];
126        Self { points }
127    }
128
129    /// Subdivide the curve into two sub curves at the value `t`.
130    pub fn subdivide(&self, t: f64) -> [CubicBezier2d; 2] {
131        let [p00, p01, p02, p03] = self.points.map(|x| x.to_vec());
132        let p10 = p00.lerp(p01, t);
133        let p11 = p01.lerp(p02, t);
134        let p12 = p02.lerp(p03, t);
135        let p20 = p10.lerp(p11, t);
136        let p21 = p11.lerp(p12, t);
137        let p30 = p20.lerp(p21, t);
138        let curves = [[p00, p10, p20, p30], [p30, p21, p12, p03]];
139        curves.map(|p| CubicBezier2d {
140            points: p.map(Point2d::from_vec),
141        })
142    }
143
144    /// Reverses the order of the control points in the bezier curve,
145    /// such that the point which at `t=0` is now at `t=1` and vise versa.
146    pub fn reverse(&mut self) {
147        self.points.reverse()
148    }
149}
150
151impl ParametricCurve2d for CubicBezier2d {
152    fn sample(&self, t: f64) -> Point2d {
153        let t1 = 1.0 - t;
154        Point2d::from_vec(
155            t1 * t1 * t1 * self.points[0].to_vec()
156                + 3.0 * t1 * t1 * t * self.points[1].to_vec()
157                + 3.0 * t1 * t * t * self.points[2].to_vec()
158                + t * t * t * self.points[3].to_vec(),
159        )
160    }
161
162    fn bounds(&self) -> Interval<f64> {
163        Interval { min: 0.0, max: 1.0 }
164    }
165
166    fn sample_dt(&self, t: f64) -> Vector2d {
167        let t1 = 1.0 - t;
168        (-3.0 * t1 * t1) * self.points[0].to_vec()
169            + (9.0 * t * t - 12.0 * t + 3.0) * self.points[1].to_vec()
170            + (-9.0 * t * t + 6.0 * t) * self.points[2].to_vec()
171            + (3.0 * t * t) * self.points[3].to_vec()
172    }
173}
174
175#[cfg(test)]
176mod test {
177    use super::QuadraticBezier2d;
178    use crate::{
179        math::{ParametricCurve2d, Point2d},
180        util::Interval,
181    };
182    use assert_approx_eq::assert_approx_eq;
183
184    #[test]
185    fn quadratic_beziers() {
186        let b = QuadraticBezier2d::new(&[
187            Point2d::new(10.0, 15.0),
188            Point2d::new(50.0, 30.0),
189            Point2d::new(20.0, 75.0),
190        ]);
191
192        assert_eq!(b.bounds(), Interval::new(0.0, 1.0));
193        assert_approx_eq!(b.sample(0.0).x, 10.0);
194        assert_approx_eq!(b.sample(0.0).y, 15.0);
195        assert_approx_eq!(b.sample(0.5).x, 32.5);
196        assert_approx_eq!(b.sample(0.5).y, 37.5);
197        assert_approx_eq!(b.sample(1.0).x, 20.0);
198        assert_approx_eq!(b.sample(1.0).y, 75.0);
199    }
200}