traffic_sim/math/curve/
bezier.rs1use super::{ParametricCurve2d, Point2d, Vector2d};
2use crate::util::Interval;
3use cgmath::prelude::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Copy, Clone)]
8pub struct LineSegment2d {
9 start: Point2d,
10 tangent: Vector2d,
11 length: f64,
12}
13
14impl LineSegment2d {
15 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 pub const fn start(&self) -> Point2d {
32 self.start
33 }
34
35 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#[derive(Copy, Clone, Serialize, Deserialize)]
57pub struct QuadraticBezier2d {
58 points: [Point2d; 3],
59}
60
61impl QuadraticBezier2d {
62 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#[derive(Copy, Clone, Debug)]
97pub struct CubicBezier2d {
98 points: [Point2d; 4],
99}
100
101impl CubicBezier2d {
102 pub const fn new(points: &[Point2d; 4]) -> Self {
104 Self { points: *points }
105 }
106
107 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 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 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 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}