1#![doc = include_str!("../README.md")]
2mod common;
23mod linear_programming;
24mod obstacles;
25mod simulator;
26mod visibility_set;
27
28pub use glam::Vec2;
29
30use common::*;
31use linear_programming::{solve_linear_program, Line};
32use obstacles::get_lines_for_agent_to_obstacle;
33
34pub use obstacles::Obstacle;
35
36pub use simulator::{AgentParameters, Simulator, SimulatorMargin};
37pub use visibility_set::VisibilitySet;
38
39#[derive(Clone, PartialEq, Debug)]
41pub struct Agent {
42 pub position: Vec2,
44 pub velocity: Vec2,
46
47 pub radius: f32,
50 pub max_velocity: f32,
52
53 pub avoidance_responsibility: f32,
58}
59
60#[derive(Clone, PartialEq, Debug)]
62pub struct AvoidanceOptions {
63 pub obstacle_margin: f32,
68 pub time_horizon: f32,
70 pub obstacle_time_horizon: f32,
72}
73
74impl Agent {
75 pub fn compute_avoiding_velocity(
82 &self,
83 neighbours: &[&Agent],
84 obstacles: &[&Obstacle],
85 preferred_velocity: Vec2,
86 time_step: f32,
87 avoidance_options: &AvoidanceOptions,
88 ) -> Vec2 {
89 assert!(time_step > 0.0, "time_step must be positive, was {}", time_step);
90
91 let lines = obstacles
92 .iter()
93 .flat_map(|o| {
94 get_lines_for_agent_to_obstacle(
95 self,
96 o,
97 avoidance_options.obstacle_margin,
98 avoidance_options.obstacle_time_horizon,
99 )
100 })
101 .chain(neighbours.iter().map(|neighbour| {
102 self.get_line_for_neighbour(
103 neighbour,
104 avoidance_options.time_horizon,
105 time_step,
106 )
107 }))
108 .collect::<Vec<Line>>();
109
110 let obstacle_line_count = lines.len() - neighbours.len();
113
114 solve_linear_program(
115 &lines,
116 obstacle_line_count,
117 self.max_velocity,
118 preferred_velocity,
119 )
120 }
121
122 fn get_line_for_neighbour(
125 &self,
126 neighbour: &Agent,
127 time_horizon: f32,
128 time_step: f32,
129 ) -> Line {
130 let relative_neighbour_position = neighbour.position - self.position;
140 let relative_agent_velocity = self.velocity - neighbour.velocity;
141
142 let distance_squared = relative_neighbour_position.length_squared();
143
144 let sum_radius = self.radius + neighbour.radius;
145 let sum_radius_squared = sum_radius * sum_radius;
146
147 let vo_normal;
148 let relative_velocity_projected_to_vo;
149 let inside_vo;
150
151 if distance_squared > sum_radius_squared {
156 let cutoff_circle_center = relative_neighbour_position / time_horizon;
170 let cutoff_circle_center_to_relative_velocity =
171 relative_agent_velocity - cutoff_circle_center;
172 let cutoff_circle_center_to_relative_velocity_length_squared =
173 cutoff_circle_center_to_relative_velocity.length_squared();
174
175 let dot = cutoff_circle_center_to_relative_velocity
176 .dot(relative_neighbour_position);
177
178 if dot < 0.0
183 && dot * dot
184 > sum_radius_squared
185 * cutoff_circle_center_to_relative_velocity_length_squared
186 {
187 let cutoff_circle_radius = sum_radius / time_horizon;
191
192 vo_normal =
193 cutoff_circle_center_to_relative_velocity.normalize_or_zero();
194 relative_velocity_projected_to_vo =
195 vo_normal * cutoff_circle_radius + cutoff_circle_center;
196 inside_vo = cutoff_circle_center_to_relative_velocity_length_squared
197 < cutoff_circle_radius * cutoff_circle_radius;
198 } else {
199 let tangent_triangle_leg =
203 (distance_squared - sum_radius_squared).sqrt();
204
205 let tangent_side = determinant(
217 relative_neighbour_position,
218 cutoff_circle_center_to_relative_velocity,
219 )
220 .signum();
221
222 let shadow_direction =
226 relative_neighbour_position * tangent_triangle_leg * tangent_side
227 + relative_neighbour_position.perp() * sum_radius;
228
229 let shadow_direction = shadow_direction / distance_squared;
231
232 vo_normal = shadow_direction.perp();
233 relative_velocity_projected_to_vo =
235 relative_agent_velocity.project_onto_normalized(shadow_direction);
236 inside_vo = determinant(relative_agent_velocity, shadow_direction)
239 * tangent_side
240 >= 0.0;
241 }
242 } else {
243 let cutoff_circle_center = relative_neighbour_position / time_step;
248 let cutoff_circle_radius = sum_radius / time_step;
249
250 vo_normal =
253 (relative_agent_velocity - cutoff_circle_center).normalize_or_zero();
254 relative_velocity_projected_to_vo =
257 vo_normal * cutoff_circle_radius + cutoff_circle_center;
258 inside_vo = true;
259 }
260
261 let u = relative_velocity_projected_to_vo - relative_agent_velocity;
264
265 let responsibility = if inside_vo {
266 self.avoidance_responsibility
267 / (self.avoidance_responsibility + neighbour.avoidance_responsibility)
268 } else {
269 1.0
270 };
271
272 Line {
273 point: self.velocity + u * responsibility,
274 direction: -vo_normal.perp(),
275 }
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 mod get_line_for_neighbour_tests {
284 use glam::Vec2;
285
286 use super::{Agent, Line};
287
288 macro_rules! assert_line_eq {
289 ($a: expr, $b: expr) => {{
290 let a = $a;
291 let b = $b;
292
293 assert!(
294 a.point.distance_squared(b.point) < 1e-5,
295 "\n left: {:?}\n right: {:?}",
296 a,
297 b
298 );
299 assert!(
300 a.direction.distance_squared(b.direction) < 1e-5,
301 "\n left: {:?}\n right: {:?}",
302 a,
303 b
304 );
305 }};
306 }
307
308 #[test]
309 fn velocity_projects_on_cutoff_circle() {
310 let position = Vec2::new(1.0, 2.0);
311 let radius = 2.0;
312
313 let agent = Agent {
314 position: Vec2::ZERO,
315 velocity: Vec2::ZERO,
316 radius: radius - 1.0,
317 avoidance_responsibility: 1.0,
318 max_velocity: 0.0,
319 };
320
321 let neighbour = Agent {
322 position: position,
323 velocity: Vec2::ZERO,
324 radius: 1.0,
325 avoidance_responsibility: 1.0,
326 max_velocity: 0.0,
327 };
328
329 let actual_line = agent.get_line_for_neighbour(
330 &neighbour, 1.0, 1.0,
331 );
332 assert_line_eq!(
334 actual_line,
335 Line {
336 point: position.normalize() * (position.length() - radius),
337 direction: position.perp().normalize(),
338 }
339 );
340 }
341
342 #[test]
343 fn velocity_projects_to_shadow() {
344 let mut agent = Agent {
345 position: Vec2::ZERO,
346 velocity: Vec2::new(-1.0, 3.0),
347 radius: 1.0,
348 max_velocity: 0.0,
349 avoidance_responsibility: 1.0,
350 };
351
352 let neighbour = Agent {
353 position: Vec2::new(2.0, 2.0),
354 velocity: Vec2::ZERO,
355 radius: 1.0,
356 max_velocity: 0.0,
357 avoidance_responsibility: 1.0,
358 };
359
360 let inside_shadow_line = agent.get_line_for_neighbour(
361 &neighbour, 1.0, 1.0,
362 );
363 assert_line_eq!(
364 inside_shadow_line,
365 Line { point: Vec2::new(0.0, 3.0), direction: Vec2::new(0.0, 1.0) }
366 );
367
368 agent.velocity = Vec2::new(10.0, -1.0);
369
370 let outside_shadow_line = agent.get_line_for_neighbour(
371 &neighbour, 1.0, 1.0,
372 );
373 assert_line_eq!(
374 outside_shadow_line,
375 Line { point: Vec2::new(10.0, -0.5), direction: Vec2::new(-1.0, 0.0) }
376 );
377 }
378
379 #[test]
380 fn collision_uses_time_step() {
381 let agent = Agent {
382 position: Vec2::ZERO,
383 velocity: Vec2::new(0.0, 0.0),
384 radius: 2.0,
385 max_velocity: 0.0,
386 avoidance_responsibility: 1.0,
387 };
388
389 let neighbour = Agent {
390 position: Vec2::new(2.0, 2.0),
391 velocity: Vec2::ZERO,
392 radius: 2.0,
393 max_velocity: 0.0,
394 avoidance_responsibility: 1.0,
395 };
396
397 let collision_line = agent.get_line_for_neighbour(
398 &neighbour, 1.0, 0.5,
399 );
400 assert_line_eq!(
401 collision_line,
402 Line {
403 point: (Vec2::ONE.normalize() * -8.0 + Vec2::new(4.0, 4.0)) * 0.5,
404 direction: Vec2::new(-1.0, 1.0).normalize(),
405 }
406 );
407 }
408
409 #[test]
410 fn no_collision_uses_time_horizon() {
411 let agent = Agent {
412 position: Vec2::ZERO,
413 velocity: Vec2::new(0.0, 0.0),
414 radius: 1.0,
415 max_velocity: 0.0,
416 avoidance_responsibility: 1.0,
417 };
418
419 let neighbour = Agent {
420 position: Vec2::new(2.0, 2.0),
421 velocity: Vec2::ZERO,
422 radius: 1.0,
423 max_velocity: 0.0,
424 avoidance_responsibility: 1.0,
425 };
426
427 let collision_line = agent.get_line_for_neighbour(
428 &neighbour, 2.0, 0.5,
429 );
430 assert_line_eq!(
431 collision_line,
432 Line {
433 point: -Vec2::ONE.normalize() + Vec2::new(1.0, 1.0),
434 direction: Vec2::new(-1.0, 1.0).normalize(),
435 }
436 );
437 }
438
439 #[test]
440 fn uses_avoidance_responsibility() {
441 let agent = Agent {
442 position: Vec2::ZERO,
443 velocity: Vec2::new(1.5, 0.0),
444 radius: 1.0,
445 avoidance_responsibility: 1.0,
446 max_velocity: 0.0,
447 };
448
449 let neighbour = Agent {
450 position: Vec2::new(4.0, 0.0),
451 velocity: Vec2::ZERO,
452 radius: 1.0,
453 avoidance_responsibility: 3.0,
454 max_velocity: 0.0,
455 };
456
457 let actual_line = agent.get_line_for_neighbour(
458 &neighbour, 2.0, 0.5,
459 );
460 assert_line_eq!(
461 actual_line,
462 Line { point: Vec2::new(1.375, 0.0), direction: Vec2::new(0.0, 1.0) }
463 );
464 }
465
466 #[test]
467 fn uses_avoidance_responsibility_only_when_inside_vo() {
468 let agent = Agent {
469 position: Vec2::ZERO,
470 velocity: Vec2::new(0.5, 0.0),
471 radius: 1.0,
472 avoidance_responsibility: 1.0,
473 max_velocity: 0.0,
474 };
475
476 let neighbour = Agent {
477 position: Vec2::new(4.0, 0.0),
478 velocity: Vec2::ZERO,
479 radius: 1.0,
480 avoidance_responsibility: 3.0,
481 max_velocity: 0.0,
482 };
483
484 let actual_line = agent.get_line_for_neighbour(
485 &neighbour, 2.0, 0.5,
486 );
487 assert_line_eq!(
488 actual_line,
489 Line { point: Vec2::new(1.0, 0.0), direction: Vec2::new(0.0, 1.0) }
490 );
491 }
492 }
493}