1#![allow(dead_code)]
2
3use std::cmp::Ordering;
9use std::collections::{BinaryHeap, HashMap};
10
11const N_KNN: usize = 10;
13const MAX_EDGE_LEN: f64 = 30.0;
14
15#[derive(Clone)]
17struct Node {
18 x: f64,
19 y: f64,
20 cost: f64,
21 parent: Option<usize>,
22}
23
24impl Node {
25 fn new(x: f64, y: f64) -> Self {
26 Node {
27 x,
28 y,
29 cost: f64::INFINITY,
30 parent: None,
31 }
32 }
33}
34
35#[derive(Clone)]
37struct QueueItem {
38 cost: f64,
39 index: usize,
40}
41
42impl PartialEq for QueueItem {
43 fn eq(&self, other: &Self) -> bool {
44 self.cost == other.cost
45 }
46}
47
48impl Eq for QueueItem {}
49
50impl Ord for QueueItem {
51 fn cmp(&self, other: &Self) -> Ordering {
52 other
53 .cost
54 .partial_cmp(&self.cost)
55 .unwrap_or(Ordering::Equal)
56 }
57}
58
59impl PartialOrd for QueueItem {
60 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
61 Some(self.cmp(other))
62 }
63}
64
65struct KDTree {
67 points: Vec<(f64, f64)>,
68}
69
70impl KDTree {
71 fn new(points: Vec<(f64, f64)>) -> Self {
72 KDTree { points }
73 }
74
75 fn query_knn(&self, x: f64, y: f64, k: usize) -> Vec<(usize, f64)> {
76 let mut distances: Vec<(usize, f64)> = self
77 .points
78 .iter()
79 .enumerate()
80 .map(|(i, (px, py))| {
81 let d = ((x - px).powi(2) + (y - py).powi(2)).sqrt();
82 (i, d)
83 })
84 .collect();
85
86 distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
87 distances.truncate(k + 1);
88 distances
89 }
90
91 fn min_distance(&self, x: f64, y: f64) -> f64 {
92 self.points
93 .iter()
94 .map(|(px, py)| ((x - px).powi(2) + (y - py).powi(2)).sqrt())
95 .fold(f64::INFINITY, f64::min)
96 }
97}
98
99fn circumcenter(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Option<(f64, f64)> {
101 let d = 2.0 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));
102
103 if d.abs() < 1e-10 {
104 return None;
105 }
106
107 let ux = ((x1 * x1 + y1 * y1) * (y2 - y3)
108 + (x2 * x2 + y2 * y2) * (y3 - y1)
109 + (x3 * x3 + y3 * y3) * (y1 - y2))
110 / d;
111 let uy = ((x1 * x1 + y1 * y1) * (x3 - x2)
112 + (x2 * x2 + y2 * y2) * (x1 - x3)
113 + (x3 * x3 + y3 * y3) * (x2 - x1))
114 / d;
115
116 Some((ux, uy))
117}
118
119fn compute_voronoi_vertices(
121 ox: &[f64],
122 oy: &[f64],
123 min_x: f64,
124 max_x: f64,
125 min_y: f64,
126 max_y: f64,
127 robot_radius: f64,
128) -> Vec<(f64, f64)> {
129 let mut vertices = Vec::new();
130 let n = ox.len();
131
132 if n < 3 {
133 return vertices;
134 }
135
136 for i in 0..n {
137 for j in (i + 1)..n {
138 let mx = (ox[i] + ox[j]) / 2.0;
139 let my = (oy[i] + oy[j]) / 2.0;
140
141 let dist_i = ((mx - ox[i]).powi(2) + (my - oy[i]).powi(2)).sqrt();
142 let dist_j = ((mx - ox[j]).powi(2) + (my - oy[j]).powi(2)).sqrt();
143
144 if dist_i > robot_radius * 1.5
145 && dist_j > robot_radius * 1.5
146 && mx > min_x
147 && mx < max_x
148 && my > min_y
149 && my < max_y
150 {
151 vertices.push((mx, my));
152 }
153 }
154 }
155
156 for i in 0..n {
157 for j in (i + 1)..n {
158 for k in (j + 1)..n {
159 if let Some((cx, cy)) = circumcenter(ox[i], oy[i], ox[j], oy[j], ox[k], oy[k]) {
160 if cx > min_x && cx < max_x && cy > min_y && cy < max_y {
161 let min_dist = ox
162 .iter()
163 .zip(oy.iter())
164 .map(|(&px, &py)| ((cx - px).powi(2) + (cy - py).powi(2)).sqrt())
165 .fold(f64::INFINITY, f64::min);
166
167 if min_dist > robot_radius * 1.5 {
168 vertices.push((cx, cy));
169 }
170 }
171 }
172 }
173 }
174 }
175
176 let mut unique_vertices = Vec::new();
177 for (x, y) in vertices {
178 let is_dup = unique_vertices
179 .iter()
180 .any(|(vx, vy): &(f64, f64)| ((x - vx).powi(2) + (y - vy).powi(2)).sqrt() < 1.0);
181 if !is_dup {
182 unique_vertices.push((x, y));
183 }
184 }
185
186 unique_vertices
187}
188
189pub struct VoronoiPlanner {
191 sample_x: Vec<f64>,
192 sample_y: Vec<f64>,
193 road_map: Vec<Vec<usize>>,
194}
195
196impl VoronoiPlanner {
197 pub fn new(
198 ox: &[f64],
199 oy: &[f64],
200 start: (f64, f64),
201 goal: (f64, f64),
202 robot_radius: f64,
203 ) -> Self {
204 let obstacle_tree = KDTree::new(ox.iter().zip(oy.iter()).map(|(&x, &y)| (x, y)).collect());
205
206 let min_x = ox.iter().cloned().fold(f64::INFINITY, f64::min);
207 let max_x = ox.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
208 let min_y = oy.iter().cloned().fold(f64::INFINITY, f64::min);
209 let max_y = oy.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
210
211 let voronoi_vertices =
212 compute_voronoi_vertices(ox, oy, min_x, max_x, min_y, max_y, robot_radius);
213
214 let mut sample_x: Vec<f64> = voronoi_vertices.iter().map(|(x, _)| *x).collect();
215 let mut sample_y: Vec<f64> = voronoi_vertices.iter().map(|(_, y)| *y).collect();
216
217 sample_x.push(start.0);
218 sample_y.push(start.1);
219 sample_x.push(goal.0);
220 sample_y.push(goal.1);
221
222 let road_map = Self::generate_road_map(&sample_x, &sample_y, robot_radius, &obstacle_tree);
223
224 VoronoiPlanner {
225 sample_x,
226 sample_y,
227 road_map,
228 }
229 }
230
231 fn generate_road_map(
232 sample_x: &[f64],
233 sample_y: &[f64],
234 robot_radius: f64,
235 obstacle_tree: &KDTree,
236 ) -> Vec<Vec<usize>> {
237 let sample_tree = KDTree::new(
238 sample_x
239 .iter()
240 .zip(sample_y.iter())
241 .map(|(&x, &y)| (x, y))
242 .collect(),
243 );
244
245 let mut road_map: Vec<Vec<usize>> = vec![Vec::new(); sample_x.len()];
246
247 for (i, (&x, &y)) in sample_x.iter().zip(sample_y.iter()).enumerate() {
248 let neighbors = sample_tree.query_knn(x, y, N_KNN);
249
250 for (j, dist) in neighbors {
251 if i == j {
252 continue;
253 }
254
255 if dist > MAX_EDGE_LEN {
256 continue;
257 }
258
259 if !Self::is_collision(x, y, sample_x[j], sample_y[j], robot_radius, obstacle_tree)
260 {
261 road_map[i].push(j);
262 }
263 }
264 }
265
266 road_map
267 }
268
269 fn is_collision(
270 x1: f64,
271 y1: f64,
272 x2: f64,
273 y2: f64,
274 robot_radius: f64,
275 obstacle_tree: &KDTree,
276 ) -> bool {
277 let dx = x2 - x1;
278 let dy = y2 - y1;
279 let d = (dx * dx + dy * dy).sqrt();
280
281 if d == 0.0 {
282 return false;
283 }
284
285 let step = robot_radius;
286 let n_steps = (d / step).ceil() as usize;
287
288 for i in 0..=n_steps {
289 let t = i as f64 / n_steps as f64;
290 let x = x1 + t * dx;
291 let y = y1 + t * dy;
292
293 let min_dist = obstacle_tree.min_distance(x, y);
294 if min_dist <= robot_radius {
295 return true;
296 }
297 }
298
299 false
300 }
301
302 pub fn plan(&self) -> Option<(Vec<f64>, Vec<f64>)> {
303 let n = self.sample_x.len();
304 if n < 2 {
305 return None;
306 }
307
308 let start_idx = n - 2;
309 let goal_idx = n - 1;
310
311 let mut nodes: Vec<Node> = self
312 .sample_x
313 .iter()
314 .zip(self.sample_y.iter())
315 .map(|(&x, &y)| Node::new(x, y))
316 .collect();
317
318 nodes[start_idx].cost = 0.0;
319
320 let mut open_set = BinaryHeap::new();
321 open_set.push(QueueItem {
322 cost: 0.0,
323 index: start_idx,
324 });
325
326 let mut closed_set: HashMap<usize, bool> = HashMap::new();
327
328 while let Some(current) = open_set.pop() {
329 if current.index == goal_idx {
330 return Some(self.reconstruct_path(&nodes, goal_idx));
331 }
332
333 if closed_set.contains_key(¤t.index) {
334 continue;
335 }
336 closed_set.insert(current.index, true);
337
338 for &neighbor_idx in &self.road_map[current.index] {
339 if closed_set.contains_key(&neighbor_idx) {
340 continue;
341 }
342
343 let dx = nodes[neighbor_idx].x - nodes[current.index].x;
344 let dy = nodes[neighbor_idx].y - nodes[current.index].y;
345 let edge_cost = (dx * dx + dy * dy).sqrt();
346 let new_cost = nodes[current.index].cost + edge_cost;
347
348 if new_cost < nodes[neighbor_idx].cost {
349 nodes[neighbor_idx].cost = new_cost;
350 nodes[neighbor_idx].parent = Some(current.index);
351 open_set.push(QueueItem {
352 cost: new_cost,
353 index: neighbor_idx,
354 });
355 }
356 }
357 }
358
359 None
360 }
361
362 fn reconstruct_path(&self, nodes: &[Node], goal_idx: usize) -> (Vec<f64>, Vec<f64>) {
363 let mut path_x = Vec::new();
364 let mut path_y = Vec::new();
365
366 let mut current = goal_idx;
367 while let Some(parent) = nodes[current].parent {
368 path_x.push(nodes[current].x);
369 path_y.push(nodes[current].y);
370 current = parent;
371 }
372 path_x.push(nodes[current].x);
373 path_y.push(nodes[current].y);
374
375 path_x.reverse();
376 path_y.reverse();
377
378 (path_x, path_y)
379 }
380
381 pub fn get_samples(&self) -> (&[f64], &[f64]) {
382 (&self.sample_x, &self.sample_y)
383 }
384
385 pub fn get_edges(&self) -> Vec<((f64, f64), (f64, f64))> {
386 let mut edges = Vec::new();
387
388 for (i, neighbors) in self.road_map.iter().enumerate() {
389 for &j in neighbors {
390 if i < j {
391 edges.push((
392 (self.sample_x[i], self.sample_y[i]),
393 (self.sample_x[j], self.sample_y[j]),
394 ));
395 }
396 }
397 }
398
399 edges
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 const TEST_WORLD_SIZE: i32 = 24;
408 const TEST_BOUNDARY_STEP: usize = 2;
409 const TEST_START: (f64, f64) = (4.0, 4.0);
410 const TEST_GOAL: (f64, f64) = (20.0, 20.0);
411 const TEST_ROBOT_RADIUS: f64 = 2.0;
412
413 fn make_boundary() -> (Vec<f64>, Vec<f64>) {
415 let mut ox = Vec::new();
416 let mut oy = Vec::new();
417
418 for i in (0..=TEST_WORLD_SIZE).step_by(TEST_BOUNDARY_STEP) {
420 let v = i as f64;
421 ox.push(v);
422 oy.push(0.0);
423 ox.push(v);
424 oy.push(TEST_WORLD_SIZE as f64);
425 }
426 for i in (0..=TEST_WORLD_SIZE).step_by(TEST_BOUNDARY_STEP) {
428 let v = i as f64;
429 ox.push(0.0);
430 oy.push(v);
431 ox.push(TEST_WORLD_SIZE as f64);
432 oy.push(v);
433 }
434
435 (ox, oy)
436 }
437
438 fn make_large_boundary() -> (Vec<f64>, Vec<f64>) {
439 let mut ox = Vec::new();
440 let mut oy = Vec::new();
441
442 for i in 0..=60 {
443 let v = i as f64;
444 ox.push(v);
445 oy.push(0.0);
446 ox.push(v);
447 oy.push(60.0);
448 ox.push(0.0);
449 oy.push(v);
450 ox.push(60.0);
451 oy.push(v);
452 }
453
454 (ox, oy)
455 }
456
457 #[test]
458 fn test_voronoi_planner_creation() {
459 let (ox, oy) = make_boundary();
460 let _planner = VoronoiPlanner::new(&ox, &oy, TEST_START, TEST_GOAL, TEST_ROBOT_RADIUS);
461 }
462
463 #[test]
464 fn test_voronoi_get_samples() {
465 let (ox, oy) = make_boundary();
466 let planner = VoronoiPlanner::new(&ox, &oy, TEST_START, TEST_GOAL, TEST_ROBOT_RADIUS);
467 let (sx, sy) = planner.get_samples();
468
469 assert!(sx.len() >= 2);
471 assert_eq!(sx.len(), sy.len());
472
473 let n = sx.len();
475 assert!((sx[n - 2] - TEST_START.0).abs() < 1e-9);
476 assert!((sy[n - 2] - TEST_START.1).abs() < 1e-9);
477 assert!((sx[n - 1] - TEST_GOAL.0).abs() < 1e-9);
478 assert!((sy[n - 1] - TEST_GOAL.1).abs() < 1e-9);
479 }
480
481 #[test]
482 fn test_voronoi_plan_simple() {
483 let (ox, oy) = make_boundary();
484 let planner = VoronoiPlanner::new(&ox, &oy, TEST_START, TEST_GOAL, TEST_ROBOT_RADIUS);
485 let result = planner.plan();
486
487 assert!(result.is_some(), "planner should find a path");
488 let (px, py) = result.unwrap();
489 assert!(px.len() >= 2);
490 assert_eq!(px.len(), py.len());
491
492 assert!((px[0] - TEST_START.0).abs() < 1e-9);
494 assert!((py[0] - TEST_START.1).abs() < 1e-9);
495 assert!((*px.last().unwrap() - TEST_GOAL.0).abs() < 1e-9);
496 assert!((*py.last().unwrap() - TEST_GOAL.1).abs() < 1e-9);
497 }
498
499 #[test]
500 fn test_voronoi_get_edges() {
501 let (ox, oy) = make_boundary();
502 let planner = VoronoiPlanner::new(&ox, &oy, TEST_START, TEST_GOAL, TEST_ROBOT_RADIUS);
503 let edges = planner.get_edges();
504
505 assert!(!edges.is_empty(), "road map should contain edges");
506
507 for ((x1, y1), (x2, y2)) in &edges {
509 assert!(*x1 >= 0.0 && *x1 <= TEST_WORLD_SIZE as f64);
510 assert!(*y1 >= 0.0 && *y1 <= TEST_WORLD_SIZE as f64);
511 assert!(*x2 >= 0.0 && *x2 <= TEST_WORLD_SIZE as f64);
512 assert!(*y2 >= 0.0 && *y2 <= TEST_WORLD_SIZE as f64);
513 }
514 }
515
516 #[test]
517 #[ignore = "long-running regression scenario"]
518 fn test_voronoi_plan_large_boundary() {
519 let (ox, oy) = make_large_boundary();
520 let planner = VoronoiPlanner::new(&ox, &oy, (10.0, 10.0), (50.0, 50.0), 5.0);
521 let result = planner.plan();
522
523 assert!(
524 result.is_some(),
525 "planner should find a path on the larger map"
526 );
527 }
528}