#![doc = include_str!("../README.md")]
mod linear_programming;
mod simulator;
use std::borrow::Cow;
use crate::linear_programming::{solve_linear_program, Plane};
pub use glam::Vec3;
pub use simulator::{AgentParameters, Simulator, SimulatorMargin};
#[derive(Clone, PartialEq, Debug)]
pub struct Agent {
pub position: Vec3,
pub velocity: Vec3,
pub radius: f32,
pub avoidance_responsibility: f32,
}
#[derive(Clone, PartialEq, Debug)]
pub struct AvoidanceOptions {
pub time_horizon: f32,
}
impl Agent {
pub fn compute_avoiding_velocity(
&self,
neighbours: &[Cow<'_, Agent>],
preferred_velocity: Vec3,
max_speed: f32,
time_step: f32,
avoidance_options: &AvoidanceOptions,
) -> Vec3 {
assert!(time_step > 0.0, "time_step must be positive, was {}", time_step);
let planes = neighbours
.iter()
.map(|neighbour| {
self.get_plane_for_neighbour(
neighbour,
avoidance_options.time_horizon,
time_step,
)
})
.collect::<Vec<Plane>>();
solve_linear_program(&planes, max_speed, preferred_velocity)
}
fn get_plane_for_neighbour(
&self,
neighbour: &Agent,
time_horizon: f32,
time_step: f32,
) -> Plane {
let relative_neighbour_position = neighbour.position - self.position;
let relative_agent_velocity = self.velocity - neighbour.velocity;
let distance_squared = relative_neighbour_position.length_squared();
let sum_radius = self.radius + neighbour.radius;
let sum_radius_squared = sum_radius * sum_radius;
let vo_normal;
let relative_velocity_projected_to_vo;
let inside_vo;
if distance_squared > sum_radius_squared {
let cutoff_sphere_center = relative_neighbour_position / time_horizon;
let cutoff_sphere_center_to_relative_velocity =
relative_agent_velocity - cutoff_sphere_center;
let cutoff_sphere_center_to_relative_velocity_length_squared =
cutoff_sphere_center_to_relative_velocity.length_squared();
let dot = cutoff_sphere_center_to_relative_velocity
.dot(relative_neighbour_position);
if dot < 0.0
&& dot * dot
> sum_radius_squared
* cutoff_sphere_center_to_relative_velocity_length_squared
{
let cutoff_sphere_radius = sum_radius / time_horizon;
vo_normal =
cutoff_sphere_center_to_relative_velocity.normalize_or_zero();
relative_velocity_projected_to_vo =
vo_normal * cutoff_sphere_radius + cutoff_sphere_center;
inside_vo = cutoff_sphere_center_to_relative_velocity_length_squared
< cutoff_sphere_radius * cutoff_sphere_radius;
} else {
let tangent_ring_triangle_leg_squared =
distance_squared - sum_radius_squared;
let squared_distance_between_rays = relative_neighbour_position
.cross(relative_agent_velocity)
.length_squared();
let a = relative_neighbour_position.length_squared();
let b = relative_neighbour_position.dot(relative_agent_velocity);
let c = relative_agent_velocity.length_squared()
- squared_distance_between_rays / tangent_ring_triangle_leg_squared;
let t = (-b - (b * b - a * c).sqrt()) / a;
vo_normal = (relative_agent_velocity + t * relative_neighbour_position)
.normalize_or_zero();
let distance_to_plane = Plane { normal: vo_normal, point: Vec3::ZERO }
.signed_distance_to_plane(relative_agent_velocity);
inside_vo = distance_to_plane < 0.0;
relative_velocity_projected_to_vo =
relative_agent_velocity - distance_to_plane * vo_normal;
}
} else {
let cutoff_sphere_center = relative_neighbour_position / time_step;
let cutoff_sphere_radius = sum_radius / time_step;
vo_normal = {
let velocity_from_circle_center =
relative_agent_velocity - cutoff_sphere_center;
let recip = velocity_from_circle_center.length_recip();
if recip.is_finite() && recip > 0.0 {
velocity_from_circle_center * recip
} else {
let z: f32 = rand::random();
let longitude: f32 = rand::random();
let z_normalize = (1.0 - z * z).sqrt();
Vec3::new(
longitude.cos() * z_normalize,
longitude.sin() * z_normalize,
z,
)
}
};
relative_velocity_projected_to_vo =
vo_normal * cutoff_sphere_radius + cutoff_sphere_center;
inside_vo = true;
}
let u = relative_velocity_projected_to_vo - relative_agent_velocity;
let responsibility = if inside_vo {
self.avoidance_responsibility
/ (self.avoidance_responsibility + neighbour.avoidance_responsibility)
} else {
1.0
};
Plane { point: self.velocity + u * responsibility, normal: vo_normal }
}
}
#[cfg(test)]
#[path = "lib_test.rs"]
mod test;