1use std::collections::{HashMap, VecDeque};
31
32#[derive(Debug, Clone)]
41pub struct RoutePlanner<W> {
42 routes: HashMap<u64, VecDeque<W>>,
43}
44
45impl<W> Default for RoutePlanner<W> {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl<W> RoutePlanner<W> {
52 pub fn new() -> Self {
54 Self {
55 routes: HashMap::new(),
56 }
57 }
58
59 pub fn set_route(&mut self, agent_id: u64, waypoints: Vec<W>) {
61 self.routes.insert(agent_id, VecDeque::from(waypoints));
62 }
63
64 pub fn has_route(&self, agent_id: u64) -> bool {
66 self.routes
67 .get(&agent_id)
68 .map(|r| !r.is_empty())
69 .unwrap_or(false)
70 }
71
72 pub fn is_stationary(&self, agent_id: u64) -> bool {
74 !self.has_route(agent_id)
75 }
76
77 pub fn route(&self, agent_id: u64) -> Option<&VecDeque<W>> {
79 self.routes.get(&agent_id)
80 }
81
82 pub fn clear_route(&mut self, agent_id: u64) {
84 self.routes.remove(&agent_id);
85 }
86
87 pub fn clear_all(&mut self) {
89 self.routes.clear();
90 }
91
92 pub fn len(&self) -> usize {
94 self.routes.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
99 self.routes.is_empty()
100 }
101}
102
103#[derive(Debug, Clone, Copy)]
105pub struct MoveResult2D {
106 pub position: (f64, f64),
108 pub finished: bool,
110}
111
112impl RoutePlanner<(f64, f64)> {
113 #[allow(clippy::too_many_arguments)]
124 pub fn move_along_route_2d(
125 &mut self,
126 agent_id: u64,
127 current_pos: (f64, f64),
128 speed: f64,
129 mut dt: f64,
130 periodic: bool,
131 extent_x: f64,
132 extent_y: f64,
133 ) -> MoveResult2D {
134 let route = match self.routes.get_mut(&agent_id) {
135 Some(r) if !r.is_empty() => r,
136 _ => {
137 return MoveResult2D {
138 position: current_pos,
139 finished: true,
140 };
141 }
142 };
143
144 let mut from = current_pos;
145 let mut next_pos = current_pos;
146
147 while let Some(&waypoint) = route.front() {
148 let dir = direction_2d(from, waypoint, periodic, extent_x, extent_y);
149 let dist_to_wp = (dir.0 * dir.0 + dir.1 * dir.1).sqrt();
150
151 if dist_to_wp < 1e-12 {
152 from = waypoint;
154 route.pop_front();
155 if route.is_empty() {
156 next_pos = waypoint;
157 break;
158 }
159 continue;
160 }
161
162 let move_dist = speed * dt;
163 let unit_dir = (dir.0 / dist_to_wp, dir.1 / dist_to_wp);
164
165 if move_dist >= dist_to_wp {
166 dt -= dist_to_wp / speed;
168 from = waypoint;
169 route.pop_front();
170 if route.is_empty() {
171 next_pos = waypoint;
172 break;
173 }
174 } else {
175 next_pos = (
177 from.0 + unit_dir.0 * move_dist,
178 from.1 + unit_dir.1 * move_dist,
179 );
180 if periodic {
181 next_pos = normalize_pos_2d(next_pos, extent_x, extent_y);
182 }
183 break;
184 }
185 }
186
187 let finished = route.is_empty();
188 if finished {
189 self.routes.remove(&agent_id);
190 }
191
192 MoveResult2D {
193 position: next_pos,
194 finished,
195 }
196 }
197
198 pub fn plan_from_path(&mut self, agent_id: u64, waypoints: Vec<(f64, f64)>) {
203 self.set_route(agent_id, waypoints);
204 }
205}
206
207#[derive(Debug, Clone, Copy)]
209pub struct MoveResult3D {
210 pub position: (f64, f64, f64),
212 pub finished: bool,
214}
215
216impl RoutePlanner<(f64, f64, f64)> {
217 #[allow(clippy::too_many_arguments)]
222 pub fn move_along_route_3d(
223 &mut self,
224 agent_id: u64,
225 current_pos: (f64, f64, f64),
226 speed: f64,
227 mut dt: f64,
228 periodic: bool,
229 extent_x: f64,
230 extent_y: f64,
231 extent_z: f64,
232 ) -> MoveResult3D {
233 let route = match self.routes.get_mut(&agent_id) {
234 Some(r) if !r.is_empty() => r,
235 _ => {
236 return MoveResult3D {
237 position: current_pos,
238 finished: true,
239 };
240 }
241 };
242
243 let mut from = current_pos;
244 let mut next_pos = current_pos;
245
246 while let Some(&waypoint) = route.front() {
247 let dir = direction_3d(from, waypoint, periodic, extent_x, extent_y, extent_z);
248 let dist_to_wp = (dir.0 * dir.0 + dir.1 * dir.1 + dir.2 * dir.2).sqrt();
249
250 if dist_to_wp < 1e-12 {
251 from = waypoint;
252 route.pop_front();
253 if route.is_empty() {
254 next_pos = waypoint;
255 break;
256 }
257 continue;
258 }
259
260 let move_dist = speed * dt;
261 let unit_dir = (dir.0 / dist_to_wp, dir.1 / dist_to_wp, dir.2 / dist_to_wp);
262
263 if move_dist >= dist_to_wp {
264 dt -= dist_to_wp / speed;
265 from = waypoint;
266 route.pop_front();
267 if route.is_empty() {
268 next_pos = waypoint;
269 break;
270 }
271 } else {
272 next_pos = (
273 from.0 + unit_dir.0 * move_dist,
274 from.1 + unit_dir.1 * move_dist,
275 from.2 + unit_dir.2 * move_dist,
276 );
277 if periodic {
278 next_pos = normalize_pos_3d(next_pos, extent_x, extent_y, extent_z);
279 }
280 break;
281 }
282 }
283
284 let finished = route.is_empty();
285 if finished {
286 self.routes.remove(&agent_id);
287 }
288
289 MoveResult3D {
290 position: next_pos,
291 finished,
292 }
293 }
294
295 pub fn plan_from_path(&mut self, agent_id: u64, waypoints: Vec<(f64, f64, f64)>) {
297 self.set_route(agent_id, waypoints);
298 }
299}
300
301impl RoutePlanner<(usize, usize)> {
303 pub fn next_waypoint(&mut self, agent_id: u64) -> Option<(usize, usize)> {
309 let route = self.routes.get_mut(&agent_id)?;
310 let wp = route.pop_front();
311 if route.is_empty() {
312 self.routes.remove(&agent_id);
313 }
314 wp
315 }
316}
317
318fn direction_2d(
323 from: (f64, f64),
324 to: (f64, f64),
325 periodic: bool,
326 extent_x: f64,
327 extent_y: f64,
328) -> (f64, f64) {
329 let mut dx = to.0 - from.0;
330 let mut dy = to.1 - from.1;
331 if periodic {
332 if dx > extent_x * 0.5 {
333 dx -= extent_x;
334 } else if dx < -extent_x * 0.5 {
335 dx += extent_x;
336 }
337 if dy > extent_y * 0.5 {
338 dy -= extent_y;
339 } else if dy < -extent_y * 0.5 {
340 dy += extent_y;
341 }
342 }
343 (dx, dy)
344}
345
346fn direction_3d(
347 from: (f64, f64, f64),
348 to: (f64, f64, f64),
349 periodic: bool,
350 extent_x: f64,
351 extent_y: f64,
352 extent_z: f64,
353) -> (f64, f64, f64) {
354 let mut dx = to.0 - from.0;
355 let mut dy = to.1 - from.1;
356 let mut dz = to.2 - from.2;
357 if periodic {
358 if dx > extent_x * 0.5 {
359 dx -= extent_x;
360 } else if dx < -extent_x * 0.5 {
361 dx += extent_x;
362 }
363 if dy > extent_y * 0.5 {
364 dy -= extent_y;
365 } else if dy < -extent_y * 0.5 {
366 dy += extent_y;
367 }
368 if dz > extent_z * 0.5 {
369 dz -= extent_z;
370 } else if dz < -extent_z * 0.5 {
371 dz += extent_z;
372 }
373 }
374 (dx, dy, dz)
375}
376
377fn normalize_pos_2d(pos: (f64, f64), extent_x: f64, extent_y: f64) -> (f64, f64) {
378 (
379 ((pos.0 % extent_x) + extent_x) % extent_x,
380 ((pos.1 % extent_y) + extent_y) % extent_y,
381 )
382}
383
384fn normalize_pos_3d(
385 pos: (f64, f64, f64),
386 extent_x: f64,
387 extent_y: f64,
388 extent_z: f64,
389) -> (f64, f64, f64) {
390 (
391 ((pos.0 % extent_x) + extent_x) % extent_x,
392 ((pos.1 % extent_y) + extent_y) % extent_y,
393 ((pos.2 % extent_z) + extent_z) % extent_z,
394 )
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn route_planner_basic() {
403 let mut planner = RoutePlanner::<(f64, f64)>::new();
404 assert!(planner.is_stationary(1));
405
406 planner.set_route(1, vec![(5.0, 5.0), (10.0, 10.0)]);
407 assert!(planner.has_route(1));
408 assert!(!planner.is_stationary(1));
409
410 planner.clear_route(1);
411 assert!(planner.is_stationary(1));
412 }
413
414 #[test]
415 fn move_along_route_exact_waypoint() {
416 let mut planner = RoutePlanner::new();
417 planner.set_route(1, vec![(10.0, 0.0)]);
418
419 let result = planner.move_along_route_2d(1, (0.0, 0.0), 10.0, 1.0, false, 100.0, 100.0);
421 assert!((result.position.0 - 10.0).abs() < 1e-10);
422 assert!((result.position.1 - 0.0).abs() < 1e-10);
423 assert!(result.finished);
424 }
425
426 #[test]
427 fn move_along_route_partial() {
428 let mut planner = RoutePlanner::new();
429 planner.set_route(1, vec![(10.0, 0.0)]);
430
431 let result = planner.move_along_route_2d(1, (0.0, 0.0), 5.0, 1.0, false, 100.0, 100.0);
433 assert!((result.position.0 - 5.0).abs() < 1e-10);
434 assert!(!result.finished);
435 }
436
437 #[test]
438 fn move_along_route_overshoot_chain() {
439 let mut planner = RoutePlanner::new();
440 planner.set_route(1, vec![(3.0, 0.0), (6.0, 0.0), (10.0, 0.0)]);
441
442 let result = planner.move_along_route_2d(1, (0.0, 0.0), 5.0, 1.0, false, 100.0, 100.0);
444 assert!((result.position.0 - 5.0).abs() < 1e-10);
445 assert!(!result.finished);
446 }
447
448 #[test]
449 fn move_along_route_no_route() {
450 let mut planner = RoutePlanner::<(f64, f64)>::new();
451 let result = planner.move_along_route_2d(1, (5.0, 5.0), 10.0, 1.0, false, 100.0, 100.0);
452 assert!((result.position.0 - 5.0).abs() < 1e-10);
453 assert!(result.finished);
454 }
455
456 #[test]
457 fn move_along_route_3d_basic() {
458 let mut planner = RoutePlanner::new();
459 planner.set_route(1, vec![(10.0, 0.0, 0.0)]);
460
461 let result =
462 planner.move_along_route_3d(1, (0.0, 0.0, 0.0), 10.0, 1.0, false, 100.0, 100.0, 100.0);
463 assert!((result.position.0 - 10.0).abs() < 1e-10);
464 assert!(result.finished);
465 }
466
467 #[test]
468 fn grid_route_next_waypoint() {
469 let mut planner = RoutePlanner::new();
470 planner.set_route(1, vec![(1, 0), (2, 0), (3, 0)]);
471
472 assert_eq!(planner.next_waypoint(1), Some((1, 0)));
473 assert_eq!(planner.next_waypoint(1), Some((2, 0)));
474 assert_eq!(planner.next_waypoint(1), Some((3, 0)));
475 assert_eq!(planner.next_waypoint(1), None);
476 assert!(planner.is_stationary(1));
477 }
478
479 #[test]
480 fn move_along_route_2d_periodic() {
481 let mut planner = RoutePlanner::new();
482 planner.set_route(1, vec![(1.0, 5.0)]);
484
485 let result = planner.move_along_route_2d(1, (9.0, 5.0), 1.0, 1.0, true, 10.0, 10.0);
486 assert!((result.position.0).abs() < 1e-10 || (result.position.0 - 10.0).abs() < 1e-10);
488 assert!(!result.finished);
489 }
490}