traffic_sim/math/curve/
algorithms.rs1use super::ParametricCurve2d;
2use crate::{math::Point2d, util::Interval};
3use cgmath::prelude::*;
4
5pub fn project_point_onto_curve(
7 curve: &impl ParametricCurve2d,
8 point: Point2d,
9 max_error: f64,
10 t0: Option<f64>,
11) -> Option<f64> {
12 let bounds = curve.bounds();
13
14 let mut t = t0.unwrap_or_else(|| {
16 (0..=8)
17 .map(|i| bounds.lerp(i as f64 / 8.0))
18 .map(|t| (t, (point - curve.sample(t)).magnitude()))
19 .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
20 .unwrap()
21 .0
22 });
23 let (mut p, mut p_dt) = (curve.sample(t), curve.sample_dt(t));
24
25 for _ in 0..64 {
27 let error = p_dt.dot(point - p) / p_dt.magnitude();
28 t += error;
29 if error.abs() < max_error || !bounds.contains(t) {
30 return Some(t);
31 }
32 (p, p_dt) = (curve.sample(t), curve.sample_dt(t));
33 }
34
35 None
36}
37
38pub fn intersect_curves(
40 curve1: &impl ParametricCurve2d,
41 curve2: &impl ParametricCurve2d,
42) -> Option<(f64, f64)> {
43 let mut pos1 = curve1.bounds().min;
44 let mut pos2 = None;
45 while pos1 < curve1.bounds().max {
46 let point1 = curve1.sample(pos1);
47 pos2 = project_point_onto_curve(curve2, point1, 0.1, pos2);
48 if let Some(pos2) = pos2 {
49 let point2 = curve2.sample(pos2);
50 let distance = point1.distance(point2);
51 if distance < 0.01 {
52 return Some((pos1, pos2));
53 } else {
54 pos1 += distance;
55 }
56 } else {
57 pos1 += 1.0;
58 }
59 }
60 None
61}
62
63pub fn equidistant_points_along_curve(
65 curve: &impl ParametricCurve2d,
66 dist: f64,
67) -> (Vec<Point2d>, f64) {
68 let end_ts = curve.bounds();
69 let end_ps = [curve.sample(end_ts.min), curve.sample(end_ts.max)];
70
71 let mut ts = end_ts;
72 let mut ps = end_ps;
73 let mut dists = Interval::new(0.0, (ps[1] - ps[0]).magnitude());
74
75 let mut points = vec![end_ps[0]];
76 let mut last_p = end_ps[0];
77
78 while dists.max > dist {
79 for _ in 0..100 {
80 let new_t = ts.lerp(dists.inv_lerp(dist));
81 let new_p = curve.sample(new_t);
82 let new_dist = (new_p - last_p).magnitude();
83 let f = new_dist / dist;
84
85 if f < 0.99 {
86 ts.min = new_t;
87 ps[0] = new_p;
88 dists.min = new_dist;
89 } else if f > 1.01 {
90 ts.max = new_t;
91 ps[1] = new_p;
92 dists.max = new_dist;
93 } else {
94 points.push(new_p);
96 last_p = new_p;
97
98 ts = Interval::new(new_t, end_ts.max);
100 ps = [new_p, end_ps[1]];
101 dists = Interval::new(0.0, (ps[1] - ps[0]).magnitude());
102
103 break;
104 }
105 }
106 }
107
108 let last_point = *points.last().unwrap();
109 let end_vec = ps[1] - last_point;
110 let end_magnitude = end_vec.magnitude();
111 let mut length = (points.len() - 1) as f64 * dist;
112 if end_magnitude > 0.001 * dist {
113 length += end_magnitude;
114 points.push(last_point + end_vec.normalize_to(dist));
115 }
116
117 (points, length)
118}
119
120pub fn subdivided_points_along_curve(
122 curve: &impl ParametricCurve2d,
123 max_length: f64,
124) -> Vec<Point2d> {
125 SubdividedSamples::new(curve, max_length)
126 .map(|(_, p)| p)
127 .collect()
128}
129
130pub fn subdivided_samples_along_curve(
132 curve: &impl ParametricCurve2d,
133 max_length: f64,
134) -> impl Iterator<Item = (f64, Point2d)> + '_ {
135 SubdividedSamples::new(curve, max_length)
136}
137
138struct SubdividedSamples<'a, C> {
140 curve: &'a C,
141 stack: Vec<(f64, Point2d)>,
142 length2: f64,
143}
144
145impl<'a, C: ParametricCurve2d> SubdividedSamples<'a, C> {
146 fn new(curve: &'a C, max_length: f64) -> Self {
147 let Interval { min, max } = curve.bounds();
148 let mid = 0.5 * (min + max);
149 Self {
150 curve,
151 stack: vec![
152 (max, curve.sample(max)),
153 (mid, curve.sample(mid)),
154 (min, curve.sample(min)),
155 ],
156 length2: max_length.powi(2),
157 }
158 }
159}
160
161impl<'a, C: ParametricCurve2d> Iterator for SubdividedSamples<'a, C> {
162 type Item = (f64, Point2d);
163
164 fn next(&mut self) -> Option<Self::Item> {
165 let (t1, p1) = self.stack.pop()?;
166 if let Some((mut t2, mut p2)) = self.stack.last().copied() {
167 while (p2 - p1).magnitude2() > self.length2 {
168 let mid_t = 0.5 * (t1 + t2);
169 (t2, p2) = (mid_t, self.curve.sample(mid_t));
170 self.stack.push((t2, p2));
171 }
172 }
173 Some((t1, p1))
174 }
175}
176
177#[cfg(test)]
178mod test {
179 use super::*;
180 use crate::math::LineSegment2d;
181 use assert_approx_eq::assert_approx_eq;
182
183 #[test]
184 pub fn equidistant_points_along_curve_is_stable() {
185 for i in 0..100 {
186 let len = 0.1 * i as f64;
187 let points = equidistant_points_along_curve(
188 &LineSegment2d::from_ends(Point2d::new(10.0, 10.0), Point2d::new(10.0 + len, 10.0)),
189 0.5,
190 );
191 assert_approx_eq!(points.1, len);
192 for point in points.0.into_iter() {
193 assert!(!point.x.is_nan() && !point.y.is_nan());
194 }
195 }
196 }
197}