1#![allow(dead_code)]
2
3use std::cmp::Ordering;
12use std::collections::{BinaryHeap, HashMap};
13
14#[derive(Clone, Copy, Debug)]
20struct Point {
21 x: f64,
22 y: f64,
23}
24
25fn orientation(p: Point, q: Point, r: Point) -> i32 {
28 let val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
29 if val > 0.0 {
30 1
31 } else if val < 0.0 {
32 2
33 } else {
34 0
35 }
36}
37
38fn on_segment(p: Point, q: Point, r: Point) -> bool {
40 q.x <= p.x.max(r.x) && q.x >= p.x.min(r.x) && q.y <= p.y.max(r.y) && q.y >= p.y.min(r.y)
41}
42
43fn segments_intersect(p1: Point, q1: Point, p2: Point, q2: Point) -> bool {
46 let o1 = orientation(p1, q1, p2);
47 let o2 = orientation(p1, q1, q2);
48 let o3 = orientation(p2, q2, p1);
49 let o4 = orientation(p2, q2, q1);
50
51 if o1 != o2 && o3 != o4 {
52 return true;
53 }
54 if o1 == 0 && on_segment(p1, p2, q1) {
55 return true;
56 }
57 if o2 == 0 && on_segment(p1, q2, q1) {
58 return true;
59 }
60 if o3 == 0 && on_segment(p2, p1, q2) {
61 return true;
62 }
63 if o4 == 0 && on_segment(p2, q1, q2) {
64 return true;
65 }
66 false
67}
68
69#[derive(Clone, Debug)]
78pub struct ObstaclePolygon {
79 pub x_list: Vec<f64>,
81 pub y_list: Vec<f64>,
83}
84
85impl ObstaclePolygon {
86 pub fn new(mut x_list: Vec<f64>, mut y_list: Vec<f64>) -> Self {
90 assert!(
91 x_list.len() >= 3 && x_list.len() == y_list.len(),
92 "polygon must have at least 3 vertices"
93 );
94
95 let is_closed = (x_list[0] - *x_list.last().unwrap()).abs() < 1e-12
97 && (y_list[0] - *y_list.last().unwrap()).abs() < 1e-12;
98 if !is_closed {
99 x_list.push(x_list[0]);
100 y_list.push(y_list[0]);
101 }
102
103 if !Self::is_clockwise(&x_list, &y_list) {
105 x_list.reverse();
106 y_list.reverse();
107 }
108
109 ObstaclePolygon { x_list, y_list }
110 }
111
112 fn is_clockwise(x: &[f64], y: &[f64]) -> bool {
113 let n = x.len();
114 let mut sum = 0.0;
115 for i in 0..n - 1 {
116 sum += (x[i + 1] - x[i]) * (y[i + 1] + y[i]);
117 }
118 sum += (x[0] - x[n - 1]) * (y[0] + y[n - 1]);
119 sum >= 0.0
120 }
121
122 fn vertex_count(&self) -> usize {
124 self.x_list.len() - 1
125 }
126}
127
128#[derive(Clone)]
133struct Node {
134 x: f64,
135 y: f64,
136 cost: f64,
137 parent: Option<usize>,
138}
139
140impl Node {
141 fn new(x: f64, y: f64) -> Self {
142 Node {
143 x,
144 y,
145 cost: f64::INFINITY,
146 parent: None,
147 }
148 }
149}
150
151#[derive(Clone)]
152struct QueueItem {
153 cost: f64,
154 index: usize,
155}
156
157impl PartialEq for QueueItem {
158 fn eq(&self, other: &Self) -> bool {
159 self.cost == other.cost
160 }
161}
162
163impl Eq for QueueItem {}
164
165impl Ord for QueueItem {
166 fn cmp(&self, other: &Self) -> Ordering {
167 other
168 .cost
169 .partial_cmp(&self.cost)
170 .unwrap_or(Ordering::Equal)
171 }
172}
173
174impl PartialOrd for QueueItem {
175 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
176 Some(self.cmp(other))
177 }
178}
179
180fn dijkstra_search(
183 nodes: &mut [Node],
184 road_map: &[Vec<usize>],
185 start_idx: usize,
186 goal_idx: usize,
187) -> Option<Vec<usize>> {
188 nodes[start_idx].cost = 0.0;
189
190 let mut open = BinaryHeap::new();
191 open.push(QueueItem {
192 cost: 0.0,
193 index: start_idx,
194 });
195
196 let mut closed: HashMap<usize, bool> = HashMap::new();
197
198 while let Some(cur) = open.pop() {
199 if cur.index == goal_idx {
200 let mut path = Vec::new();
202 let mut idx = goal_idx;
203 loop {
204 path.push(idx);
205 match nodes[idx].parent {
206 Some(p) => idx = p,
207 None => break,
208 }
209 }
210 path.reverse();
211 return Some(path);
212 }
213
214 if closed.contains_key(&cur.index) {
215 continue;
216 }
217 closed.insert(cur.index, true);
218
219 for &nb in &road_map[cur.index] {
220 if closed.contains_key(&nb) {
221 continue;
222 }
223 let dx = nodes[nb].x - nodes[cur.index].x;
224 let dy = nodes[nb].y - nodes[cur.index].y;
225 let new_cost = nodes[cur.index].cost + (dx * dx + dy * dy).sqrt();
226 if new_cost < nodes[nb].cost {
227 nodes[nb].cost = new_cost;
228 nodes[nb].parent = Some(cur.index);
229 open.push(QueueItem {
230 cost: new_cost,
231 index: nb,
232 });
233 }
234 }
235 }
236 None
237}
238
239pub struct VisibilityRoadMap {
249 expand_distance: f64,
251}
252
253impl VisibilityRoadMap {
254 pub fn new(expand_distance: f64) -> Self {
256 VisibilityRoadMap { expand_distance }
257 }
258
259 pub fn plan(
263 &self,
264 sx: f64,
265 sy: f64,
266 gx: f64,
267 gy: f64,
268 obstacles: &[ObstaclePolygon],
269 ) -> Option<(Vec<f64>, Vec<f64>)> {
270 let mut nodes = self.generate_visibility_nodes(sx, sy, gx, gy, obstacles);
271 let road_map = self.generate_road_map(&nodes, obstacles);
272
273 let path_indices = dijkstra_search(&mut nodes, &road_map, 0, 1)?;
274
275 let rx: Vec<f64> = path_indices.iter().map(|&i| nodes[i].x).collect();
276 let ry: Vec<f64> = path_indices.iter().map(|&i| nodes[i].y).collect();
277 Some((rx, ry))
278 }
279
280 fn generate_visibility_nodes(
283 &self,
284 sx: f64,
285 sy: f64,
286 gx: f64,
287 gy: f64,
288 obstacles: &[ObstaclePolygon],
289 ) -> Vec<Node> {
290 let mut nodes = vec![Node::new(sx, sy), Node::new(gx, gy)];
291
292 for obs in obstacles {
293 let (cvx, cvy) = self.vertices_in_configuration_space(obs);
294 for (vx, vy) in cvx.into_iter().zip(cvy) {
295 nodes.push(Node::new(vx, vy));
296 }
297 }
298
299 nodes
300 }
301
302 fn vertices_in_configuration_space(&self, obs: &ObstaclePolygon) -> (Vec<f64>, Vec<f64>) {
304 let n = obs.vertex_count();
305 let x = &obs.x_list[..n];
307 let y = &obs.y_list[..n];
308
309 let mut cvx = Vec::with_capacity(n);
310 let mut cvy = Vec::with_capacity(n);
311
312 for i in 0..n {
313 let prev = if i == 0 { n - 1 } else { i - 1 };
314 let next = (i + 1) % n;
315 let (ox, oy) = self.calc_offset(x[prev], y[prev], x[i], y[i], x[next], y[next]);
316 cvx.push(ox);
317 cvy.push(oy);
318 }
319
320 (cvx, cvy)
321 }
322
323 fn calc_offset(&self, px: f64, py: f64, x: f64, y: f64, nx: f64, ny: f64) -> (f64, f64) {
325 let p_vec = (y - py).atan2(x - px);
326 let n_vec = (ny - y).atan2(nx - x);
327 let offset_vec = (p_vec.sin() + n_vec.sin()).atan2(p_vec.cos() + n_vec.cos())
328 + std::f64::consts::FRAC_PI_2;
329 let offset_x = x + self.expand_distance * offset_vec.cos();
330 let offset_y = y + self.expand_distance * offset_vec.sin();
331 (offset_x, offset_y)
332 }
333
334 fn generate_road_map(&self, nodes: &[Node], obstacles: &[ObstaclePolygon]) -> Vec<Vec<usize>> {
337 let n = nodes.len();
338 let mut road_map: Vec<Vec<usize>> = Vec::with_capacity(n);
339
340 for i in 0..n {
341 let mut neighbors = Vec::new();
342 for j in 0..n {
343 if i == j {
344 continue;
345 }
346 let dx = nodes[i].x - nodes[j].x;
347 let dy = nodes[i].y - nodes[j].y;
348 if (dx * dx + dy * dy).sqrt() <= 0.1 {
349 continue; }
351
352 if obstacles
353 .iter()
354 .all(|obs| Self::is_edge_valid(&nodes[i], &nodes[j], obs))
355 {
356 neighbors.push(j);
357 }
358 }
359 road_map.push(neighbors);
360 }
361
362 road_map
363 }
364
365 fn is_edge_valid(a: &Node, b: &Node, obs: &ObstaclePolygon) -> bool {
368 let p1 = Point { x: a.x, y: a.y };
369 let p2 = Point { x: b.x, y: b.y };
370
371 for i in 0..obs.x_list.len() - 1 {
372 let p3 = Point {
373 x: obs.x_list[i],
374 y: obs.y_list[i],
375 };
376 let p4 = Point {
377 x: obs.x_list[i + 1],
378 y: obs.y_list[i + 1],
379 };
380 if segments_intersect(p1, p2, p3, p4) {
381 return false;
382 }
383 }
384 true
385 }
386
387 pub fn get_edges(
391 &self,
392 sx: f64,
393 sy: f64,
394 gx: f64,
395 gy: f64,
396 obstacles: &[ObstaclePolygon],
397 ) -> Vec<((f64, f64), (f64, f64))> {
398 let nodes = self.generate_visibility_nodes(sx, sy, gx, gy, obstacles);
399 let road_map = self.generate_road_map(&nodes, obstacles);
400
401 let mut edges = Vec::new();
402 for (i, neighbors) in road_map.iter().enumerate() {
403 for &j in neighbors {
404 if i < j {
405 edges.push(((nodes[i].x, nodes[i].y), (nodes[j].x, nodes[j].y)));
406 }
407 }
408 }
409 edges
410 }
411}
412
413#[cfg(test)]
418mod tests {
419 use super::*;
420
421 fn make_obstacles() -> Vec<ObstaclePolygon> {
423 vec![
424 ObstaclePolygon::new(vec![20.0, 30.0, 15.0], vec![20.0, 20.0, 30.0]),
425 ObstaclePolygon::new(vec![40.0, 45.0, 50.0, 40.0], vec![50.0, 40.0, 20.0, 40.0]),
426 ObstaclePolygon::new(vec![20.0, 30.0, 30.0, 20.0], vec![40.0, 45.0, 60.0, 50.0]),
427 ]
428 }
429
430 #[test]
433 fn test_segments_intersect_crossing() {
434 let p1 = Point { x: 0.0, y: 0.0 };
435 let q1 = Point { x: 10.0, y: 10.0 };
436 let p2 = Point { x: 10.0, y: 0.0 };
437 let q2 = Point { x: 0.0, y: 10.0 };
438 assert!(segments_intersect(p1, q1, p2, q2));
439 }
440
441 #[test]
442 fn test_segments_no_intersect() {
443 let p1 = Point { x: 0.0, y: 0.0 };
444 let q1 = Point { x: 5.0, y: 0.0 };
445 let p2 = Point { x: 0.0, y: 1.0 };
446 let q2 = Point { x: 5.0, y: 1.0 };
447 assert!(!segments_intersect(p1, q1, p2, q2));
448 }
449
450 #[test]
451 fn test_segments_collinear_overlap() {
452 let p1 = Point { x: 0.0, y: 0.0 };
453 let q1 = Point { x: 5.0, y: 0.0 };
454 let p2 = Point { x: 3.0, y: 0.0 };
455 let q2 = Point { x: 7.0, y: 0.0 };
456 assert!(segments_intersect(p1, q1, p2, q2));
457 }
458
459 #[test]
462 fn test_polygon_closes_and_clockwise() {
463 let obs = ObstaclePolygon::new(vec![0.0, 1.0, 0.5], vec![0.0, 0.0, 1.0]);
464 assert_eq!(obs.x_list.first(), obs.x_list.last());
466 assert_eq!(obs.y_list.first(), obs.y_list.last());
467 assert!(ObstaclePolygon::is_clockwise(&obs.x_list, &obs.y_list));
469 }
470
471 #[test]
472 fn test_polygon_vertex_count() {
473 let obs = ObstaclePolygon::new(vec![0.0, 4.0, 4.0, 0.0], vec![0.0, 0.0, 4.0, 4.0]);
474 assert_eq!(obs.vertex_count(), 4);
475 }
476
477 #[test]
480 fn test_configuration_space_vertices_count() {
481 let planner = VisibilityRoadMap::new(5.0);
482 let obs = ObstaclePolygon::new(vec![20.0, 30.0, 15.0], vec![20.0, 20.0, 30.0]);
483 let (cvx, cvy) = planner.vertices_in_configuration_space(&obs);
484 assert_eq!(cvx.len(), obs.vertex_count());
486 assert_eq!(cvy.len(), obs.vertex_count());
487 }
488
489 #[test]
490 fn test_expanded_vertices_are_further_from_centroid() {
491 let planner = VisibilityRoadMap::new(5.0);
492 let obs = ObstaclePolygon::new(vec![0.0, 10.0, 5.0], vec![0.0, 0.0, 8.66]);
493 let (cvx, cvy) = planner.vertices_in_configuration_space(&obs);
494
495 let cx: f64 =
496 obs.x_list[..obs.vertex_count()].iter().sum::<f64>() / obs.vertex_count() as f64;
497 let cy: f64 =
498 obs.y_list[..obs.vertex_count()].iter().sum::<f64>() / obs.vertex_count() as f64;
499
500 for i in 0..obs.vertex_count() {
501 let orig_dist = ((obs.x_list[i] - cx).powi(2) + (obs.y_list[i] - cy).powi(2)).sqrt();
502 let exp_dist = ((cvx[i] - cx).powi(2) + (cvy[i] - cy).powi(2)).sqrt();
503 assert!(
504 exp_dist > orig_dist,
505 "expanded vertex {i} should be further from centroid"
506 );
507 }
508 }
509
510 #[test]
513 fn test_edge_valid_no_obstacle_crossing() {
514 let a = Node::new(0.0, 0.0);
515 let b = Node::new(10.0, 0.0);
516 let obs = ObstaclePolygon::new(vec![0.0, 5.0, 5.0, 0.0], vec![5.0, 5.0, 10.0, 10.0]);
517 assert!(VisibilityRoadMap::is_edge_valid(&a, &b, &obs));
518 }
519
520 #[test]
521 fn test_edge_invalid_crosses_obstacle() {
522 let a = Node::new(0.0, 0.0);
523 let b = Node::new(10.0, 10.0);
524 let obs = ObstaclePolygon::new(vec![3.0, 7.0, 5.0], vec![0.0, 0.0, 10.0]);
525 assert!(!VisibilityRoadMap::is_edge_valid(&a, &b, &obs));
526 }
527
528 #[test]
531 fn test_plan_finds_path() {
532 let planner = VisibilityRoadMap::new(5.0);
533 let obstacles = make_obstacles();
534 let result = planner.plan(10.0, 10.0, 50.0, 50.0, &obstacles);
535 assert!(result.is_some(), "planner should find a path");
536
537 let (rx, ry) = result.unwrap();
538 assert!(rx.len() >= 2);
539 assert_eq!(rx.len(), ry.len());
540
541 assert!((rx[0] - 10.0).abs() < 1e-9);
543 assert!((ry[0] - 10.0).abs() < 1e-9);
544
545 assert!((*rx.last().unwrap() - 50.0).abs() < 1e-9);
547 assert!((*ry.last().unwrap() - 50.0).abs() < 1e-9);
548 }
549
550 #[test]
551 fn test_plan_with_no_obstacles() {
552 let planner = VisibilityRoadMap::new(5.0);
553 let result = planner.plan(0.0, 0.0, 10.0, 10.0, &[]);
554 assert!(result.is_some());
555
556 let (rx, ry) = result.unwrap();
557 assert_eq!(rx.len(), 2);
559 assert!((rx[0] - 0.0).abs() < 1e-9);
560 assert!((*rx.last().unwrap() - 10.0).abs() < 1e-9);
561 assert!((ry[0] - 0.0).abs() < 1e-9);
562 assert!((*ry.last().unwrap() - 10.0).abs() < 1e-9);
563 }
564
565 #[test]
566 fn test_get_edges_nonempty() {
567 let planner = VisibilityRoadMap::new(5.0);
568 let obstacles = make_obstacles();
569 let edges = planner.get_edges(10.0, 10.0, 50.0, 50.0, &obstacles);
570 assert!(!edges.is_empty(), "road-map should have visibility edges");
571 }
572
573 #[test]
574 fn test_plan_path_cost_reasonable() {
575 let planner = VisibilityRoadMap::new(5.0);
576 let obstacles = make_obstacles();
577 let (rx, ry) = planner.plan(10.0, 10.0, 50.0, 50.0, &obstacles).unwrap();
578
579 let mut length = 0.0;
581 for i in 1..rx.len() {
582 length += ((rx[i] - rx[i - 1]).powi(2) + (ry[i] - ry[i - 1]).powi(2)).sqrt();
583 }
584
585 let straight = ((50.0 - 10.0_f64).powi(2) + (50.0 - 10.0_f64).powi(2)).sqrt();
586 assert!(
589 length >= straight,
590 "path cannot be shorter than straight line"
591 );
592 assert!(
593 length < straight * 3.0,
594 "path length {length:.1} seems excessively long (straight={straight:.1})"
595 );
596 }
597}