use super::{
global_route_planner::GlobalRoutePlanner,
local_planner::{LocalPlanner, LocalPlannerConfig},
types::RoadOption,
};
use crate::{
client::{ActorBase, ActorList, Map, TrafficLight, Vehicle, Waypoint, World},
error::{MapError, Result},
geom::{Location, Vector3D},
};
#[derive(Debug, Clone)]
pub struct ObstacleDetectionResult {
pub obstacle_was_found: bool,
pub obstacle_id: Option<u32>,
pub distance: f32,
}
#[derive(Debug, Clone)]
pub struct TrafficLightDetectionResult {
pub traffic_light_was_found: bool,
pub traffic_light: Option<TrafficLight>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LaneChangeDirection {
Left,
Right,
}
#[derive(Debug, Clone)]
pub struct AgentCoreConfig {
pub ignore_traffic_lights: bool,
pub ignore_stop_signs: bool,
pub ignore_vehicles: bool,
pub base_tlight_threshold: f32,
pub base_vehicle_threshold: f32,
pub speed_ratio: f32,
pub max_brake: f32,
pub offset: f32,
pub use_bbs_detection: bool,
pub sampling_resolution: f32,
}
impl Default for AgentCoreConfig {
fn default() -> Self {
Self {
ignore_traffic_lights: false,
ignore_stop_signs: false,
ignore_vehicles: false,
base_tlight_threshold: 5.0,
base_vehicle_threshold: 5.0,
speed_ratio: 1.0,
max_brake: 0.5,
offset: 0.0,
use_bbs_detection: false,
sampling_resolution: 2.0,
}
}
}
pub struct AgentCore {
pub vehicle: Vehicle,
pub world: World,
pub map: Map,
pub local_planner: LocalPlanner,
pub global_planner: GlobalRoutePlanner,
pub ignore_traffic_lights: bool,
pub ignore_stop_signs: bool,
pub ignore_vehicles: bool,
pub base_tlight_threshold: f32,
pub base_vehicle_threshold: f32,
pub speed_ratio: f32,
pub max_brake: f32,
lights_list: Option<ActorList>,
last_traffic_light: Option<TrafficLight>,
}
impl AgentCore {
pub fn new(
vehicle: Vehicle,
map: Option<Map>,
grp_inst: Option<GlobalRoutePlanner>,
config: AgentCoreConfig,
local_config: LocalPlannerConfig,
) -> Result<Self> {
let world = vehicle.world()?;
let map = if let Some(m) = map { m } else { world.map()? };
let global_planner = if let Some(grp) = grp_inst {
grp
} else {
GlobalRoutePlanner::new(map.clone(), config.sampling_resolution)?
};
let local_planner = LocalPlanner::new(vehicle.clone(), local_config);
Ok(Self {
vehicle,
world,
map,
local_planner,
global_planner,
ignore_traffic_lights: config.ignore_traffic_lights,
ignore_stop_signs: config.ignore_stop_signs,
ignore_vehicles: config.ignore_vehicles,
base_tlight_threshold: config.base_tlight_threshold,
base_vehicle_threshold: config.base_vehicle_threshold,
speed_ratio: config.speed_ratio,
max_brake: config.max_brake,
lights_list: None,
last_traffic_light: None,
})
}
pub fn ignore_traffic_lights(&mut self, active: bool) {
self.ignore_traffic_lights = active;
}
pub fn ignore_stop_signs(&mut self, active: bool) {
self.ignore_stop_signs = active;
}
pub fn ignore_vehicles(&mut self, active: bool) {
self.ignore_vehicles = active;
}
pub fn affected_by_traffic_light(
&mut self,
max_distance: f32,
) -> Result<TrafficLightDetectionResult> {
if self.ignore_traffic_lights {
return Ok(TrafficLightDetectionResult {
traffic_light_was_found: false,
traffic_light: None,
});
}
if self.lights_list.is_none() {
self.lights_list = Some(self.world.actors()?.filter("*traffic_light*")?);
}
let vehicle_location = self.vehicle.transform()?.location;
if let Some(ref lights) = self.lights_list {
for actor in lights.iter() {
if let Ok(traffic_light) = TrafficLight::try_from(actor.clone()) {
use crate::rpc::TrafficLightState;
if traffic_light.state()? != TrafficLightState::Red {
continue;
}
let light_location = traffic_light.transform()?.location;
let distance = ((vehicle_location.x - light_location.x).powi(2)
+ (vehicle_location.y - light_location.y).powi(2))
.sqrt();
if distance < max_distance {
self.last_traffic_light = Some(traffic_light.clone());
return Ok(TrafficLightDetectionResult {
traffic_light_was_found: true,
traffic_light: Some(traffic_light),
});
}
}
}
}
Ok(TrafficLightDetectionResult {
traffic_light_was_found: false,
traffic_light: None,
})
}
pub fn affected_by_traffic_light_with_trigger_volumes(
&mut self,
max_distance: f32,
) -> Result<TrafficLightDetectionResult> {
if self.ignore_traffic_lights {
return Ok(TrafficLightDetectionResult {
traffic_light_was_found: false,
traffic_light: None,
});
}
if self.lights_list.is_none() {
self.lights_list = Some(self.world.actors()?.filter("*traffic_light*")?);
}
let vehicle_location = self.vehicle.transform()?.location;
let vehicle_waypoint =
self.map
.waypoint_at(&vehicle_location)?
.ok_or_else(|| MapError::InvalidWaypoint {
location: format!("{:?}", vehicle_location),
reason: "Failed to get waypoint for vehicle location".to_string(),
})?;
if let Some(ref lights) = self.lights_list {
for actor in lights.iter() {
if let Ok(traffic_light) = TrafficLight::try_from(actor.clone()) {
use crate::rpc::TrafficLightState;
let state = traffic_light.state()?;
if state != TrafficLightState::Red && state != TrafficLightState::Yellow {
continue;
}
let affected_waypoints = traffic_light.affected_lane_waypoints()?;
for trigger_wp in affected_waypoints.iter() {
let trigger_loc = trigger_wp.transform().location;
let vehicle_loc = vehicle_location;
let distance = ((trigger_loc.x - vehicle_loc.x).powi(2)
+ (trigger_loc.y - vehicle_loc.y).powi(2))
.sqrt();
let same_road = trigger_wp.road_id() == vehicle_waypoint.road_id();
let same_lane = trigger_wp.lane_id() == vehicle_waypoint.lane_id();
if distance < max_distance && same_road && same_lane {
self.last_traffic_light = Some(traffic_light.clone());
return Ok(TrafficLightDetectionResult {
traffic_light_was_found: true,
traffic_light: Some(traffic_light),
});
}
}
}
}
}
Ok(TrafficLightDetectionResult {
traffic_light_was_found: false,
traffic_light: None,
})
}
pub fn vehicle_obstacle_detected(&self, max_distance: f32) -> Result<ObstacleDetectionResult> {
if self.ignore_vehicles {
return Ok(ObstacleDetectionResult {
obstacle_was_found: false,
obstacle_id: None,
distance: -1.0,
});
}
let vehicle_list = self.world.actors()?.filter("*vehicle*")?;
let ego_transform = self.vehicle.transform()?;
let ego_location = ego_transform.location;
let ego_waypoint = match self.map.waypoint_at(&ego_location)? {
Some(wp) => wp,
None => {
return self.simple_vehicle_obstacle_detected(max_distance, &vehicle_list);
}
};
let next_waypoint = self
.local_planner
.get_incoming_waypoint_and_direction(3)
.map(|(wp, _)| wp);
let ego_extent = 2.5; let ego_forward = ego_transform.rotation.forward_vector();
let ego_front_location = Location {
x: ego_location.x + ego_extent * ego_forward.x,
y: ego_location.y + ego_extent * ego_forward.y,
z: ego_location.z,
};
for actor in vehicle_list.iter() {
if actor.id() == self.vehicle.id() {
continue;
}
let target_transform = actor.transform()?;
let target_location = target_transform.location;
let dx = target_location.x - ego_location.x;
let dy = target_location.y - ego_location.y;
let distance = (dx * dx + dy * dy).sqrt();
if distance > max_distance {
continue;
}
let target_waypoint = match self.map.waypoint_at(&target_location)? {
Some(wp) => wp,
None => continue,
};
let mut in_same_lane = target_waypoint.road_id() == ego_waypoint.road_id()
&& target_waypoint.lane_id() == ego_waypoint.lane_id();
if !in_same_lane && let Some(ref next_wp) = next_waypoint {
in_same_lane = target_waypoint.road_id() == next_wp.road_id()
&& target_waypoint.lane_id() == next_wp.lane_id();
}
if !in_same_lane {
continue;
}
let target_extent = 2.5; let target_forward = target_transform.rotation.forward_vector();
let target_rear_location = Location {
x: target_location.x - target_extent * target_forward.x,
y: target_location.y - target_extent * target_forward.y,
z: target_location.z,
};
let rear_dx = target_rear_location.x - ego_front_location.x;
let rear_dy = target_rear_location.y - ego_front_location.y;
let rear_distance = (rear_dx * rear_dx + rear_dy * rear_dy).sqrt();
if rear_distance > max_distance {
continue;
}
let rear_dot = rear_dx * ego_forward.x + rear_dy * ego_forward.y;
let rear_angle = (rear_dot / rear_distance).acos().to_degrees();
if rear_angle <= 90.0 && rear_dot > 0.0 {
return Ok(ObstacleDetectionResult {
obstacle_was_found: true,
obstacle_id: Some(actor.id()),
distance,
});
}
}
Ok(ObstacleDetectionResult {
obstacle_was_found: false,
obstacle_id: None,
distance: -1.0,
})
}
fn simple_vehicle_obstacle_detected(
&self,
max_distance: f32,
vehicle_list: &crate::client::ActorList,
) -> Result<ObstacleDetectionResult> {
let vehicle_transform = self.vehicle.transform()?;
let vehicle_location = vehicle_transform.location;
let vehicle_forward = vehicle_transform.rotation.forward_vector();
for actor in vehicle_list.iter() {
if actor.id() == self.vehicle.id() {
continue;
}
let other_location = actor.transform()?.location;
let dx = other_location.x - vehicle_location.x;
let dy = other_location.y - vehicle_location.y;
let distance = (dx * dx + dy * dy).sqrt();
if distance > max_distance {
continue;
}
let dot = dx * vehicle_forward.x + dy * vehicle_forward.y;
if dot > 0.0 {
let angle = (dot / distance).acos().to_degrees();
if angle <= 60.0 {
return Ok(ObstacleDetectionResult {
obstacle_was_found: true,
obstacle_id: Some(actor.id()),
distance,
});
}
}
}
Ok(ObstacleDetectionResult {
obstacle_was_found: false,
obstacle_id: None,
distance: -1.0,
})
}
pub fn vehicle_obstacle_detected_with_bounding_boxes(
&self,
max_distance: f32,
route_waypoints: &[(Waypoint, RoadOption)],
) -> Result<ObstacleDetectionResult> {
use geo::{Coord, LineString, Polygon as GeoPolygon};
if self.ignore_vehicles {
return Ok(ObstacleDetectionResult {
obstacle_was_found: false,
obstacle_id: None,
distance: -1.0,
});
}
if route_waypoints.is_empty() {
return Ok(ObstacleDetectionResult {
obstacle_was_found: false,
obstacle_id: None,
distance: -1.0,
});
}
let mut route_coords: Vec<Coord<f64>> = Vec::new();
const ROUTE_WIDTH: f32 = 2.0;
for (waypoint, _) in route_waypoints.iter().take(20) {
let wp_transform = waypoint.transform();
let wp_loc = wp_transform.location;
let forward = wp_transform.rotation.forward_vector();
let right = crate::geom::Vector3D {
x: -forward.y,
y: forward.x,
z: 0.0,
};
let left_point = Coord {
x: (wp_loc.x - right.x * ROUTE_WIDTH) as f64,
y: (wp_loc.y - right.y * ROUTE_WIDTH) as f64,
};
route_coords.push(left_point);
}
for (waypoint, _) in route_waypoints.iter().take(20).rev() {
let wp_transform = waypoint.transform();
let wp_loc = wp_transform.location;
let forward = wp_transform.rotation.forward_vector();
let right = crate::geom::Vector3D {
x: -forward.y,
y: forward.x,
z: 0.0,
};
let right_point = Coord {
x: (wp_loc.x + right.x * ROUTE_WIDTH) as f64,
y: (wp_loc.y + right.y * ROUTE_WIDTH) as f64,
};
route_coords.push(right_point);
}
if !route_coords.is_empty() {
let first = route_coords[0];
route_coords.push(first);
}
let route_polygon = GeoPolygon::new(LineString::from(route_coords), vec![]);
let vehicle_list = self.world.actors()?.filter("*vehicle*")?;
let vehicle_location = self.vehicle.transform()?.location;
for actor in vehicle_list.iter() {
if actor.id() == self.vehicle.id() {
continue;
}
let other_transform = actor.transform()?;
let other_location = other_transform.location;
let dx = other_location.x - vehicle_location.x;
let dy = other_location.y - vehicle_location.y;
let distance = (dx * dx + dy * dy).sqrt();
if distance > max_distance {
continue;
}
if let Ok(other_vehicle) = crate::client::Vehicle::try_from(actor.clone()) {
let bbox = other_vehicle.bounding_box()?;
let bbox_transform = other_transform;
let extent = &bbox.extent;
let corners = vec![
crate::geom::Location::new(extent.x, extent.y, 0.0),
crate::geom::Location::new(-extent.x, extent.y, 0.0),
crate::geom::Location::new(-extent.x, -extent.y, 0.0),
crate::geom::Location::new(extent.x, -extent.y, 0.0),
];
let mut world_coords: Vec<Coord<f64>> = Vec::new();
for corner in corners {
let rotated = bbox_transform.rotation.rotate_vector(&Vector3D {
x: corner.x,
y: corner.y,
z: corner.z,
});
let world_corner = Location {
x: rotated.x + bbox_transform.location.x,
y: rotated.y + bbox_transform.location.y,
z: rotated.z + bbox_transform.location.z,
};
world_coords.push(Coord {
x: world_corner.x as f64,
y: world_corner.y as f64,
});
}
world_coords.push(world_coords[0]);
let bbox_polygon = GeoPolygon::new(LineString::from(world_coords), vec![]);
use geo::algorithm::intersects::Intersects;
if route_polygon.intersects(&bbox_polygon) {
return Ok(ObstacleDetectionResult {
obstacle_was_found: true,
obstacle_id: Some(actor.id()),
distance,
});
}
}
}
Ok(ObstacleDetectionResult {
obstacle_was_found: false,
obstacle_id: None,
distance: -1.0,
})
}
pub fn generate_lane_change_path(
waypoint: &Waypoint,
direction: LaneChangeDirection,
distance_same_lane: f32,
distance_other_lane: f32,
lane_change_distance: f32,
check: bool,
step_distance: f32,
) -> Vec<(Waypoint, RoadOption)> {
let mut path = Vec::new();
let mut current_wp = waypoint.clone();
let same_lane_steps = (distance_same_lane / step_distance).ceil() as usize;
for _ in 0..same_lane_steps {
path.push((current_wp.clone(), RoadOption::LaneFollow));
let next_wps = match current_wp.next(step_distance as f64) {
Ok(wps) => wps,
Err(_) => return Vec::new(),
};
if next_wps.is_empty() {
return Vec::new(); }
current_wp = next_wps.get(0).unwrap().clone();
}
let target_lane_wp = match direction {
LaneChangeDirection::Left => current_wp.left().ok().flatten(),
LaneChangeDirection::Right => current_wp.right().ok().flatten(),
};
let target_lane_wp = match target_lane_wp {
Some(wp) => wp,
None => return Vec::new(), };
if check {
let target_next = match target_lane_wp.next(1.0) {
Ok(wps) => wps,
Err(_) => return Vec::new(),
};
if target_next.is_empty() {
return Vec::new(); }
}
let lane_change_steps = (lane_change_distance / step_distance).ceil() as usize;
let road_option = match direction {
LaneChangeDirection::Left => RoadOption::ChangeLaneLeft,
LaneChangeDirection::Right => RoadOption::ChangeLaneRight,
};
for i in 0..lane_change_steps {
path.push((current_wp.clone(), road_option));
let next_wps = match current_wp.next(step_distance as f64) {
Ok(wps) => wps,
Err(_) => return path,
};
if next_wps.is_empty() {
return path; }
current_wp = next_wps.get(0).unwrap().clone();
if i == lane_change_steps / 2 {
if let Ok(target_next) = target_lane_wp.next((step_distance * (i as f32)) as f64)
&& !target_next.is_empty()
{
current_wp = target_next.get(0).unwrap().clone();
}
}
}
let other_lane_steps = (distance_other_lane / step_distance).ceil() as usize;
for _ in 0..other_lane_steps {
path.push((current_wp.clone(), RoadOption::LaneFollow));
let next_wps = match current_wp.next(step_distance as f64) {
Ok(wps) => wps,
Err(_) => break,
};
if next_wps.is_empty() {
break; }
current_wp = next_wps.get(0).unwrap().clone();
}
path
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_core_config_default() {
let config = AgentCoreConfig::default();
assert!(!config.ignore_traffic_lights);
assert_eq!(config.base_tlight_threshold, 5.0);
assert_eq!(config.sampling_resolution, 2.0);
}
#[test]
fn test_obstacle_detection_result() {
let result = ObstacleDetectionResult {
obstacle_was_found: true,
obstacle_id: Some(42),
distance: 10.5,
};
assert!(result.obstacle_was_found);
assert_eq!(result.obstacle_id, Some(42));
}
#[test]
fn test_traffic_light_detection_result() {
let result = TrafficLightDetectionResult {
traffic_light_was_found: false,
traffic_light: None,
};
assert!(!result.traffic_light_was_found);
assert!(result.traffic_light.is_none());
}
}