1use std::collections::HashSet;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum BugVariant {
22 Bug0,
23 Bug1,
24 Bug2,
25}
26
27#[derive(Debug, Clone)]
29pub struct BugConfig {
30 pub variant: BugVariant,
32 pub max_iterations: usize,
34}
35
36impl Default for BugConfig {
37 fn default() -> Self {
38 Self {
39 variant: BugVariant::Bug0,
40 max_iterations: 1_000_000,
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct BugResult {
48 pub path_x: Vec<f64>,
50 pub path_y: Vec<f64>,
52}
53
54pub struct BugPlanner {
60 goal_x: f64,
61 goal_y: f64,
62 obs: HashSet<(i64, i64)>,
63 boundary: HashSet<(i64, i64)>,
64 path_x: Vec<f64>,
65 path_y: Vec<f64>,
66}
67
68impl BugPlanner {
69 pub fn new(
73 start_x: f64,
74 start_y: f64,
75 goal_x: f64,
76 goal_y: f64,
77 obs_x: &[f64],
78 obs_y: &[f64],
79 ) -> Self {
80 let obs: HashSet<(i64, i64)> = obs_x
81 .iter()
82 .zip(obs_y.iter())
83 .map(|(&x, &y)| (x as i64, y as i64))
84 .collect();
85
86 let dirs: [(i64, i64); 8] = [
88 (1, 1),
89 (0, 1),
90 (-1, 1),
91 (-1, 0),
92 (-1, -1),
93 (0, -1),
94 (1, -1),
95 (1, 0),
96 ];
97 let mut boundary = HashSet::new();
98 for &(ox, oy) in &obs {
99 for &(dx, dy) in &dirs {
100 let cx = ox + dx;
101 let cy = oy + dy;
102 if !obs.contains(&(cx, cy)) {
103 boundary.insert((cx, cy));
104 }
105 }
106 }
107
108 Self {
109 goal_x,
110 goal_y,
111 obs,
112 boundary,
113 path_x: vec![start_x],
114 path_y: vec![start_y],
115 }
116 }
117
118 pub fn plan(mut self, config: &BugConfig) -> Option<BugResult> {
120 let ok = match config.variant {
121 BugVariant::Bug0 => self.bug0(config.max_iterations),
122 BugVariant::Bug1 => self.bug1(config.max_iterations),
123 BugVariant::Bug2 => self.bug2(config.max_iterations),
124 };
125 if ok {
126 Some(BugResult {
127 path_x: self.path_x,
128 path_y: self.path_y,
129 })
130 } else {
131 None
132 }
133 }
134
135 fn cur(&self) -> (f64, f64) {
139 (*self.path_x.last().unwrap(), *self.path_y.last().unwrap())
140 }
141
142 fn mov_normal(&self) -> (f64, f64) {
144 let (cx, cy) = self.cur();
145 (cx + sign(self.goal_x - cx), cy + sign(self.goal_y - cy))
146 }
147
148 fn mov_to_next_obs(&self, visited: &HashSet<(i64, i64)>) -> (f64, f64, bool) {
152 let (cx, cy) = self.cur();
153 let cx = cx as i64;
154 let cy = cy as i64;
155 for &(dx, dy) in &[(1i64, 0i64), (0, 1), (-1, 0), (0, -1)] {
156 let nx = cx + dx;
157 let ny = cy + dy;
158 if self.boundary.contains(&(nx, ny)) && !visited.contains(&(nx, ny)) {
159 return (nx as f64, ny as f64, false);
160 }
161 }
162 (cx as f64, cy as f64, true)
163 }
164
165 fn at_goal(&self) -> bool {
166 let (cx, cy) = self.cur();
167 cx == self.goal_x && cy == self.goal_y
168 }
169
170 fn push(&mut self, x: f64, y: f64) {
171 self.path_x.push(x);
172 self.path_y.push(y);
173 }
174
175 fn is_boundary(&self, x: f64, y: f64) -> bool {
177 self.boundary.contains(&(x as i64, y as i64))
178 }
179
180 fn is_obs(&self, x: f64, y: f64) -> bool {
182 self.obs.contains(&(x as i64, y as i64))
183 }
184
185 fn bug0(&mut self, max_iter: usize) -> bool {
188 let mut mode = if self.is_boundary(self.cur().0, self.cur().1) {
189 Mode::Obs
190 } else {
191 Mode::Normal
192 };
193 let mut visited = HashSet::new();
194
195 for _ in 0..max_iter {
196 if self.at_goal() {
197 return true;
198 }
199
200 let (cand_x, cand_y) = if mode == Mode::Normal {
201 self.mov_normal()
202 } else {
203 let (x, y, _) = self.mov_to_next_obs(&visited);
204 (x, y)
205 };
206
207 match mode {
208 Mode::Normal => {
209 if self.is_boundary(cand_x, cand_y) {
210 self.push(cand_x, cand_y);
211 visited.clear();
212 visited.insert((cand_x as i64, cand_y as i64));
213 mode = Mode::Obs;
214 } else {
215 self.push(cand_x, cand_y);
216 }
217 }
218 Mode::Obs => {
219 let (nx, ny) = self.mov_normal();
220 if !self.is_obs(nx, ny) {
221 mode = Mode::Normal;
222 } else {
223 self.push(cand_x, cand_y);
224 visited.insert((cand_x as i64, cand_y as i64));
225 }
226 }
227 }
228 }
229 false
230 }
231
232 fn bug1(&mut self, max_iter: usize) -> bool {
235 let mut mode = if self.is_boundary(self.cur().0, self.cur().1) {
236 Mode::Obs
237 } else {
238 Mode::Normal
239 };
240 let mut visited = HashSet::new();
241 let mut dist = f64::INFINITY;
242 let mut exit_x = f64::NEG_INFINITY;
243 let mut exit_y = f64::NEG_INFINITY;
244 let mut back_to_start;
245 let mut second_round = false;
246
247 for _ in 0..max_iter {
248 if self.at_goal() {
249 return true;
250 }
251
252 let (cand_x, cand_y) = if mode == Mode::Normal {
253 let c = self.mov_normal();
254 back_to_start = false;
255 c
256 } else {
257 let (x, y, b) = self.mov_to_next_obs(&visited);
258 back_to_start = b;
259 (x, y)
260 };
261
262 match mode {
263 Mode::Normal => {
264 if self.is_boundary(cand_x, cand_y) {
265 self.push(cand_x, cand_y);
266 visited.clear();
267 visited.insert((cand_x as i64, cand_y as i64));
268 mode = Mode::Obs;
269 dist = f64::INFINITY;
270 second_round = false;
271 } else {
272 self.push(cand_x, cand_y);
273 }
274 }
275 Mode::Obs => {
276 let d =
277 ((cand_x - self.goal_x).powi(2) + (cand_y - self.goal_y).powi(2)).sqrt();
278 if d < dist && !second_round {
279 exit_x = cand_x;
280 exit_y = cand_y;
281 dist = d;
282 }
283 if back_to_start && !second_round {
284 second_round = true;
285 let n = visited.len();
288 let len = self.path_x.len();
289 self.path_x.truncate(len.saturating_sub(n));
290 self.path_y.truncate(len.saturating_sub(n));
291 visited.clear();
292 }
293 self.push(cand_x, cand_y);
294 visited.insert((cand_x as i64, cand_y as i64));
295 if cand_x == exit_x && cand_y == exit_y && second_round {
296 mode = Mode::Normal;
297 }
298 }
299 }
300 }
301 false
302 }
303
304 fn bug2(&mut self, max_iter: usize) -> bool {
307 let (sx, sy) = self.cur();
309 let mut hit_points: Vec<(f64, f64)> = Vec::new();
310 {
311 let mut lx = sx;
312 let mut ly = sy;
313 loop {
314 if lx == self.goal_x && ly == self.goal_y {
315 break;
316 }
317 let cx = lx + sign(self.goal_x - lx);
318 let cy = ly + sign(self.goal_y - ly);
319 if self.is_boundary(cx, cy) {
320 hit_points.push((cx, cy));
321 }
322 lx = cx;
323 ly = cy;
324 }
325 }
326
327 let mut mode = if self.is_boundary(sx, sy) {
328 Mode::Obs
329 } else {
330 Mode::Normal
331 };
332 let mut visited = HashSet::new();
333
334 for _ in 0..max_iter {
335 if self.at_goal() {
336 return true;
337 }
338
339 let (cand_x, cand_y) = if mode == Mode::Normal {
340 self.mov_normal()
341 } else {
342 let (x, y, _) = self.mov_to_next_obs(&visited);
343 (x, y)
344 };
345
346 match mode {
347 Mode::Normal => {
348 if self.is_boundary(cand_x, cand_y) {
349 self.push(cand_x, cand_y);
350 visited.clear();
351 visited.insert((cand_x as i64, cand_y as i64));
352 if let Some(idx) = hit_points
354 .iter()
355 .position(|&(hx, hy)| hx == cand_x && hy == cand_y)
356 {
357 hit_points.remove(idx);
358 }
359 mode = Mode::Obs;
360 } else {
361 self.push(cand_x, cand_y);
362 }
363 }
364 Mode::Obs => {
365 self.push(cand_x, cand_y);
366 visited.insert((cand_x as i64, cand_y as i64));
367 if let Some(idx) = hit_points
368 .iter()
369 .position(|&(hx, hy)| hx == cand_x && hy == cand_y)
370 {
371 hit_points.remove(idx);
372 mode = Mode::Normal;
373 }
374 }
375 }
376 }
377 false
378 }
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382enum Mode {
383 Normal,
384 Obs,
385}
386
387fn sign(v: f64) -> f64 {
389 if v > 0.0 {
390 1.0
391 } else if v < 0.0 {
392 -1.0
393 } else {
394 0.0
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 fn build_obstacles() -> (Vec<f64>, Vec<f64>) {
404 let mut ox = Vec::new();
405 let mut oy = Vec::new();
406 let ranges: &[(i64, i64, i64, i64)] = &[
407 (20, 40, 20, 40),
408 (60, 100, 40, 80),
409 (120, 140, 80, 100),
410 (80, 140, 0, 20),
411 (0, 20, 60, 100),
412 (20, 40, 80, 100),
413 (120, 160, 40, 60),
414 ];
415 for &(x0, x1, y0, y1) in ranges {
416 for i in x0..x1 {
417 for j in y0..y1 {
418 ox.push(i as f64);
419 oy.push(j as f64);
420 }
421 }
422 }
423 (ox, oy)
424 }
425
426 #[test]
427 fn test_bug0_reaches_goal() {
428 let (ox, oy) = build_obstacles();
429 let planner = BugPlanner::new(0.0, 0.0, 167.0, 50.0, &ox, &oy);
430 let config = BugConfig {
431 variant: BugVariant::Bug0,
432 ..Default::default()
433 };
434 let result = planner.plan(&config).expect("Bug0 should find a path");
435 assert_eq!(*result.path_x.last().unwrap(), 167.0);
436 assert_eq!(*result.path_y.last().unwrap(), 50.0);
437 assert!(result.path_x.len() > 2);
438 }
439
440 #[test]
441 fn test_bug1_reaches_goal() {
442 let (ox, oy) = build_obstacles();
443 let planner = BugPlanner::new(0.0, 0.0, 167.0, 50.0, &ox, &oy);
444 let config = BugConfig {
445 variant: BugVariant::Bug1,
446 ..Default::default()
447 };
448 let result = planner.plan(&config).expect("Bug1 should find a path");
449 assert_eq!(*result.path_x.last().unwrap(), 167.0);
450 assert_eq!(*result.path_y.last().unwrap(), 50.0);
451 }
452
453 #[test]
454 fn test_bug2_reaches_goal() {
455 let (ox, oy) = build_obstacles();
456 let planner = BugPlanner::new(0.0, 0.0, 167.0, 50.0, &ox, &oy);
457 let config = BugConfig {
458 variant: BugVariant::Bug2,
459 ..Default::default()
460 };
461 let result = planner.plan(&config).expect("Bug2 should find a path");
462 assert_eq!(*result.path_x.last().unwrap(), 167.0);
463 assert_eq!(*result.path_y.last().unwrap(), 50.0);
464 }
465
466 #[test]
467 fn test_straight_path_no_obstacles() {
468 let planner = BugPlanner::new(0.0, 0.0, 5.0, 5.0, &[], &[]);
469 let config = BugConfig {
470 variant: BugVariant::Bug0,
471 ..Default::default()
472 };
473 let result = planner.plan(&config).expect("should reach goal");
474 assert_eq!(*result.path_x.last().unwrap(), 5.0);
475 assert_eq!(*result.path_y.last().unwrap(), 5.0);
476 assert_eq!(result.path_x.len(), 6);
478 }
479
480 #[test]
481 fn test_config_default() {
482 let config = BugConfig::default();
483 assert_eq!(config.variant, BugVariant::Bug0);
484 assert_eq!(config.max_iterations, 1_000_000);
485 }
486}