use glam::Vec2;
use crate::{common::determinant, Agent, Line};
#[derive(Clone, PartialEq, Debug)]
pub enum Obstacle {
Closed { vertices: Vec<Vec2> },
Open { vertices: Vec<Vec2> },
}
pub fn get_lines_for_agent_to_obstacle(
agent: &Agent,
obstacle: &Obstacle,
obstacle_margin: f32,
time_horizon: f32,
) -> Vec<Line> {
match obstacle {
Obstacle::Closed { vertices } => {
get_lines_for_agent_to_obstacle_const::<true>(
agent,
vertices,
obstacle_margin,
time_horizon,
)
}
Obstacle::Open { vertices } => {
get_lines_for_agent_to_obstacle_const::<false>(
agent,
vertices,
obstacle_margin,
time_horizon,
)
}
}
}
fn get_lines_for_agent_to_obstacle_const<const CLOSED: bool>(
agent: &Agent,
vertices: &[Vec2],
obstacle_margin: f32,
time_horizon: f32,
) -> Vec<Line> {
let convexity = {
let mut convexity = Vec::with_capacity(vertices.len());
for i in 0..vertices.len() {
if CLOSED {
let left = vertices[if i == 0 { vertices.len() - 1 } else { i - 1 }];
let center = vertices[i];
let right = vertices[if i == vertices.len() - 1 { 0 } else { i + 1 }];
convexity.push(determinant(right - center, left - center) >= 0.0);
} else {
if i == 0 || i == vertices.len() - 1 {
convexity.push(true);
} else {
let left = vertices[i - 1];
let center = vertices[i];
let right = vertices[i + 1];
convexity.push(determinant(right - center, left - center) >= 0.0);
}
}
}
convexity
};
let mut lines = Vec::new();
let edge_vertex_for_index = |index: usize| EdgeVertex {
point: vertices[index],
convex: convexity[index],
};
if CLOSED {
for left_index in 0..vertices.len() {
let right_index =
if left_index == vertices.len() - 1 { 0 } else { left_index + 1 };
let left_left_index =
if left_index == 0 { vertices.len() - 1 } else { left_index - 1 };
let right_right_index =
if right_index == vertices.len() - 1 { 0 } else { right_index + 1 };
let left_vertex = edge_vertex_for_index(left_index);
let right_vertex = edge_vertex_for_index(right_index);
if let Some(line) = get_line_for_agent_to_edge(
agent,
left_vertex,
right_vertex,
Some(vertices[left_left_index]),
Some(vertices[right_right_index]),
obstacle_margin,
time_horizon,
&lines,
) {
lines.push(line);
}
}
} else {
for left_index in 0..(vertices.len() - 1) {
let right_index = left_index + 1;
let left_vertex = edge_vertex_for_index(left_index);
let right_vertex = edge_vertex_for_index(right_index);
if let Some(line) = get_line_for_agent_to_edge(
agent,
left_vertex,
right_vertex,
if left_index == 0 { None } else { Some(vertices[left_index - 1]) },
if right_index == vertices.len() - 1 {
None
} else {
Some(vertices[right_index + 1])
},
obstacle_margin,
time_horizon,
&lines,
) {
lines.push(line);
}
}
}
lines
}
#[derive(Clone, Copy)]
struct EdgeVertex {
point: Vec2,
convex: bool,
}
fn get_line_for_agent_to_edge(
agent: &Agent,
mut left_vertex: EdgeVertex,
mut right_vertex: EdgeVertex,
mut left_left_vertex: Option<Vec2>,
mut right_right_vertex: Option<Vec2>,
edge_margin: f32,
time_horizon: f32,
existing_lines: &[Line],
) -> Option<Line> {
let relative_left_vertex = left_vertex.point - agent.position;
let relative_right_vertex = right_vertex.point - agent.position;
let edge_vector = right_vertex.point - left_vertex.point;
let agent_from_left_vertex = -relative_left_vertex;
if determinant(agent_from_left_vertex, edge_vector) < 0.0 {
return None;
}
fn is_edge_covered(
left_vertex: Vec2,
right_vertex: Vec2,
time_horizon: f32,
edge_margin: f32,
existing_lines: &[Line],
) -> bool {
const EDGE_COVER_EPSILON: f32 = 1e-5;
for line in existing_lines {
if determinant(left_vertex / time_horizon - line.point, line.direction)
>= edge_margin / time_horizon - EDGE_COVER_EPSILON
&& determinant(right_vertex / time_horizon - line.point, line.direction)
>= edge_margin / time_horizon - EDGE_COVER_EPSILON
{
return true;
}
}
false
}
if is_edge_covered(
relative_left_vertex,
relative_right_vertex,
time_horizon,
edge_margin,
existing_lines,
) {
return None;
}
let edge_unit_vector = edge_vector.normalize();
let dist_left_squared = relative_left_vertex.length_squared();
let dist_right_squared = relative_right_vertex.length_squared();
let squared_radius = edge_margin * edge_margin;
let edge_t =
agent_from_left_vertex.dot(edge_vector) / edge_vector.length_squared();
let dist_to_edge_line_squared =
(edge_t * edge_vector).distance_squared(agent_from_left_vertex);
if edge_t < 0.0 && dist_left_squared <= squared_radius {
if !left_vertex.convex {
return Some(Line { point: Vec2::ZERO, direction: -edge_unit_vector });
}
return Some(Line {
point: Vec2::ZERO,
direction: relative_left_vertex.perp().normalize(),
});
} else if edge_t > 1.0 && dist_right_squared <= squared_radius {
if !right_vertex.convex {
return Some(Line { point: Vec2::ZERO, direction: -edge_unit_vector });
}
if let Some(right_right_vertex) = right_right_vertex {
if determinant(
relative_right_vertex,
right_right_vertex - right_vertex.point,
) < 0.0
{
return None;
}
}
return Some(Line {
point: Vec2::ZERO,
direction: relative_right_vertex.perp().normalize(),
});
} else if 0.0 <= edge_t
&& edge_t <= 1.0
&& dist_to_edge_line_squared <= squared_radius
{
return Some(Line { point: Vec2::ZERO, direction: -edge_unit_vector });
}
let mut left_shadow_direction;
let mut right_shadow_direction;
if edge_t < 0.0 && dist_to_edge_line_squared <= squared_radius {
if !left_vertex.convex {
return None;
}
right_right_vertex = Some(right_vertex.point);
right_vertex = left_vertex;
let tangent_triangle_leg = (dist_left_squared - squared_radius).sqrt();
left_shadow_direction = (relative_left_vertex * tangent_triangle_leg
+ relative_left_vertex.perp() * edge_margin)
/ dist_left_squared;
right_shadow_direction = (relative_left_vertex * tangent_triangle_leg
- relative_left_vertex.perp() * edge_margin)
/ dist_left_squared;
} else if edge_t > 1.0 && dist_to_edge_line_squared <= squared_radius {
if !right_vertex.convex {
return None;
}
left_left_vertex = Some(left_vertex.point);
left_vertex = right_vertex;
let tangent_triangle_leg = (dist_right_squared - squared_radius).sqrt();
left_shadow_direction = (relative_right_vertex * tangent_triangle_leg
+ relative_right_vertex.perp() * edge_margin)
/ dist_right_squared;
right_shadow_direction = (relative_right_vertex * tangent_triangle_leg
- relative_right_vertex.perp() * edge_margin)
/ dist_right_squared;
} else {
if left_vertex.convex {
let left_tangent_triangle_leg =
(dist_left_squared - squared_radius).sqrt();
left_shadow_direction = (relative_left_vertex
* left_tangent_triangle_leg
+ relative_left_vertex.perp() * edge_margin)
/ dist_left_squared;
} else {
left_shadow_direction = -edge_unit_vector;
}
if right_vertex.convex {
let right_tangent_triangle_leg =
(dist_right_squared - squared_radius).sqrt();
right_shadow_direction = (relative_right_vertex
* right_tangent_triangle_leg
- relative_right_vertex.perp() * edge_margin)
/ dist_right_squared;
} else {
right_shadow_direction = edge_unit_vector;
}
}
let mut is_left_shadow_covered = false;
let mut is_right_shadow_covered = false;
let left_edge_direction =
left_left_vertex.and_then(|v| Some(v - left_vertex.point));
if left_vertex.convex
&& left_edge_direction.is_some()
&& determinant(left_shadow_direction, left_edge_direction.unwrap()) >= 0.0
{
left_shadow_direction = left_edge_direction.unwrap().normalize();
is_left_shadow_covered = true;
}
let right_edge_direction =
right_right_vertex.and_then(|v| Some(v - right_vertex.point));
if right_vertex.convex
&& right_edge_direction.is_some()
&& determinant(right_shadow_direction, right_edge_direction.unwrap()) <= 0.0
{
right_shadow_direction = right_edge_direction.unwrap().normalize();
is_right_shadow_covered = true;
}
let left_cutoff = (left_vertex.point - agent.position) / time_horizon;
let right_cutoff = (right_vertex.point - agent.position) / time_horizon;
let cutoff_vector = right_cutoff - left_cutoff;
let is_degenerate_edge = left_vertex.point == right_vertex.point;
let velocity = agent.velocity;
let t_cutoff_edge = if is_degenerate_edge {
0.5
} else {
(velocity - left_cutoff).dot(cutoff_vector) / cutoff_vector.length_squared()
};
let t_left_shadow = (velocity - left_cutoff).dot(left_shadow_direction);
let t_right_shadow = (velocity - right_cutoff).dot(right_shadow_direction);
if t_cutoff_edge < 0.0 && t_left_shadow < 0.0
|| is_degenerate_edge && t_left_shadow < 0.0 && t_right_shadow < 0.0
{
let velocity_from_left_cutoff = (velocity - left_cutoff).normalize();
return Some(Line {
direction: -velocity_from_left_cutoff.perp(),
point: left_cutoff
+ edge_margin / time_horizon * velocity_from_left_cutoff,
});
}
if t_cutoff_edge > 1.0 && t_right_shadow < 0.0 {
let velocity_from_right_cutoff = (velocity - right_cutoff).normalize();
return Some(Line {
direction: -velocity_from_right_cutoff.perp(),
point: right_cutoff
+ edge_margin / time_horizon * velocity_from_right_cutoff,
});
}
let cutoff_edge_distance_squared =
if t_cutoff_edge < 0.0 || t_cutoff_edge > 1.0 || is_degenerate_edge {
f32::INFINITY
} else {
(velocity - (left_cutoff + t_cutoff_edge * cutoff_vector))
.length_squared()
};
let left_shadow_distance_squared = if t_left_shadow < 0.0 {
f32::INFINITY
} else {
(velocity - (left_cutoff + t_left_shadow * left_shadow_direction))
.length_squared()
};
let right_shadow_distance_squared = if t_right_shadow < 0.0 {
f32::INFINITY
} else {
(velocity - (right_cutoff + t_right_shadow * right_shadow_direction))
.length_squared()
};
if cutoff_edge_distance_squared <= left_shadow_distance_squared
&& cutoff_edge_distance_squared <= right_shadow_distance_squared
{
let line_direction = -cutoff_vector.normalize();
Some(Line {
direction: line_direction,
point: left_cutoff + edge_margin / time_horizon * line_direction.perp(),
})
} else if left_shadow_distance_squared <= right_shadow_distance_squared {
if is_left_shadow_covered {
None
} else {
Some(Line {
direction: left_shadow_direction,
point: left_cutoff
+ edge_margin / time_horizon * left_shadow_direction.perp(),
})
}
} else {
if is_right_shadow_covered {
None
} else {
Some(Line {
direction: -right_shadow_direction,
point: right_cutoff
- edge_margin / time_horizon * right_shadow_direction.perp(),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_line_eq {
($a: expr, $b: expr) => {{
let a = $a;
let b = $b;
assert!(
a.point.distance_squared(b.point) < 1e-5,
"\n left: {:?}\n right: {:?}",
a,
b
);
assert!(
a.direction.distance_squared(b.direction) < 1e-5,
"\n left: {:?}\n right: {:?}",
a,
b
);
}};
}
#[test]
fn covered_edge_is_skipped() {
let agent = Agent {
position: Vec2::new(0.0, -1.0),
velocity: Vec2::new(0.0, 0.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: Vec2::new(-1.0, 0.0), convex: true },
EdgeVertex { point: Vec2::new(1.0, 0.0), convex: true },
None,
None,
agent.radius,
0.5,
&[Line { point: Vec2::new(-2.5, 1.5), direction: Vec2::new(-1.0, 1.0) }],
);
assert!(line.is_some(), "Line should have been Some since the existing line did not fully cover the edge.");
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: Vec2::new(-1.0, 0.0), convex: true },
EdgeVertex { point: Vec2::new(1.0, 0.0), convex: true },
None,
None,
agent.radius,
1.0,
&[Line { point: Vec2::new(-3.0, 1.0), direction: Vec2::new(-1.0, 1.0) }],
);
assert!(line.is_none(), "Line should have been None due to an existing line covering the edge. Line was {:?}", line);
}
#[test]
fn agent_collides_with_edge() {
let agent = Agent {
position: Vec2::new(0.0, -0.1),
velocity: Vec2::new(0.0, -1.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let vertices =
vec![Vec2::new(-1.0, 0.0), Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)];
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
Some(vertices[2]),
Some(vertices[2]),
agent.radius,
1.0,
&[],
);
assert_line_eq!(
line.expect("Line should be Some."),
Line { direction: Vec2::new(-1.0, 0.0), point: Vec2::ZERO }
);
}
#[test]
fn agent_collides_with_left_vertex() {
let agent = Agent {
position: Vec2::new(-1.1, -0.1),
velocity: Vec2::new(0.0, -1.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let vertices =
vec![Vec2::new(-1.0, 0.0), Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)];
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
Some(vertices[2]),
Some(vertices[2]),
agent.radius,
1.0,
&[],
);
assert_line_eq!(
line.expect("Line should be Some, but was None."),
Line { direction: Vec2::new(-1.0, 1.0).normalize(), point: Vec2::ZERO }
);
}
#[test]
fn agent_collides_with_right_vertex_with_line() {
let agent = Agent {
position: Vec2::new(1.1, -0.1),
velocity: Vec2::new(0.0, -1.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let vertices = vec![Vec2::new(-1.0, 0.0), Vec2::new(1.0, 0.0)];
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
None,
None,
agent.radius,
1.0,
&[],
);
assert_line_eq!(
line.expect("Line should be Some, but was None."),
Line { direction: Vec2::new(-1.0, -1.0).normalize(), point: Vec2::ZERO }
);
}
#[test]
fn agent_collides_with_right_vertex_handled_by_next_edge() {
let agent = Agent {
position: Vec2::new(1.1, -0.1),
velocity: Vec2::new(0.0, -1.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let vertices =
vec![Vec2::new(-1.0, 0.0), Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)];
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
Some(vertices[2]),
Some(vertices[2]),
agent.radius,
1.0,
&[],
);
assert!(line.is_none(), "The right vertex should be handled by the next edge. The generated line was {:?}", line.unwrap());
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[1], convex: true },
EdgeVertex { point: vertices[2], convex: true },
Some(vertices[0]),
Some(vertices[0]),
agent.radius,
1.0,
&[],
);
assert!(line.is_some(), "The right vertex should be handled by this edge.");
}
#[test]
fn agent_velocity_projects_to_cutoff_line() {
let agent = Agent {
position: Vec2::new(0.0, -2.0),
velocity: Vec2::new(0.5, -1.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let vertices =
vec![Vec2::new(-1.0, 0.0), Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)];
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
Some(vertices[2]),
Some(vertices[2]),
agent.radius,
0.5,
&[],
);
assert_line_eq!(
line.expect("Line should be Some."),
Line { direction: Vec2::new(-1.0, 0.0), point: Vec2::new(-2.0, 2.0) }
);
}
#[test]
fn agent_velocity_projects_to_shadows() {
let mut agent = Agent {
position: Vec2::new(0.0, -2.0),
velocity: Vec2::new(3.0, 3.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let vertices =
vec![Vec2::new(-1.0, 0.0), Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)];
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
Some(vertices[2]),
Some(vertices[2]),
agent.radius,
0.5,
&[],
);
assert_line_eq!(
line.expect("Line should be Some."),
Line { direction: Vec2::new(-0.8, -0.6), point: Vec2::new(3.2, 2.4) }
);
agent.velocity = Vec2::new(-3.0, 3.0);
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
Some(vertices[2]),
Some(vertices[2]),
agent.radius,
0.5,
&[],
);
assert_line_eq!(
line.expect("Line should be Some."),
Line { direction: Vec2::new(-0.8, 0.6), point: Vec2::new(-3.2, 2.4) }
);
}
#[test]
fn agent_velocity_projects_to_covered_shadows_creates_no_lines() {
let mut agent = Agent {
position: Vec2::new(0.0, -2.0),
velocity: Vec2::new(-10.0, 0.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let vertices = vec![
Vec2::new(-2.0, 0.5),
Vec2::new(-1.0, 0.0),
Vec2::new(1.0, 0.0),
Vec2::new(2.0, 0.5),
];
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[1], convex: true },
EdgeVertex { point: vertices[2], convex: true },
Some(vertices[0]),
Some(vertices[3]),
agent.radius,
0.5,
&[],
);
assert!(line.is_none(), "Left shadow was covered, so the left edge should have taken care of creating the line.");
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
None,
Some(vertices[2]),
agent.radius,
0.5,
&[],
);
assert!(line.is_some(), "Left shadow was covered for the right edge, so this edge should create a line.");
agent.velocity = Vec2::new(10.0, 0.0);
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[1], convex: true },
EdgeVertex { point: vertices[2], convex: true },
Some(vertices[0]),
Some(vertices[3]),
agent.radius,
0.5,
&[],
);
assert!(line.is_none(), "Right shadow was covered, so the right edge should have taken care of creating the line.");
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[2], convex: true },
EdgeVertex { point: vertices[3], convex: true },
Some(vertices[1]),
None,
agent.radius,
0.5,
&[],
);
assert!(line.is_some(), "Right shadow was covered for the left edge, so this edge should create a line.");
}
#[test]
fn backwards_edges_are_ignored() {
let agent = Agent {
position: Vec2::new(0.0, 0.0),
velocity: Vec2::new(0.0, 0.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let vertices = vec![Vec2::new(-1.0, -1.0), Vec2::new(-1.0, 1.0)];
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[0], convex: true },
EdgeVertex { point: vertices[1], convex: true },
None,
None,
agent.radius,
1.0,
&[],
);
assert!(line.is_some(), "Forward edges should not be ignored.");
let line = get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: vertices[1], convex: true },
EdgeVertex { point: vertices[0], convex: true },
None,
None,
agent.radius,
1.0,
&[],
);
assert!(
line.is_none(),
"Backward edges should be ignored. {:?}",
line.unwrap()
);
}
#[test]
fn velocity_projects_to_cutoff_endpoints() {
let mut agent = Agent {
position: Vec2::ZERO,
velocity: Vec2::new(3.0, 0.0),
radius: 0.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
assert_line_eq!(
get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: Vec2::new(-1.0, 2.0), convex: true },
EdgeVertex { point: Vec2::new(1.0, 2.0), convex: true },
None,
None,
agent.radius,
1.0,
&[],
)
.unwrap(),
Line {
direction: Vec2::new(-1.0, -1.0).normalize(),
point: Vec2::new(1.0, 2.0)
}
);
agent.velocity = Vec2::new(-3.0, 0.0);
assert_line_eq!(
get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: Vec2::new(-1.0, 2.0), convex: true },
EdgeVertex { point: Vec2::new(1.0, 2.0), convex: true },
None,
None,
agent.radius,
1.0,
&[],
)
.unwrap(),
Line {
direction: Vec2::new(-1.0, 1.0).normalize(),
point: Vec2::new(-1.0, 2.0)
}
);
}
#[test]
fn velocity_projects_to_degenerate_edge() {
let agent = Agent {
position: Vec2::ZERO,
velocity: Vec2::ZERO,
radius: 0.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
assert_line_eq!(
get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: Vec2::new(0.0, 2.0), convex: true },
EdgeVertex { point: Vec2::new(0.0, 2.0), convex: true },
None,
None,
agent.radius,
1.0,
&[],
)
.unwrap(),
Line { direction: Vec2::new(-1.0, 0.0), point: Vec2::new(0.0, 2.0) }
);
}
#[test]
fn shadow_of_endpoint_covers_edge() {
let mut agent = Agent {
position: Vec2::ZERO,
velocity: Vec2::new(-0.5, 3.0),
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
assert_line_eq!(
get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: Vec2::new(-1.0, 4.0), convex: true },
EdgeVertex { point: Vec2::new(0.0, 2.0f32.sqrt()), convex: true },
None,
None,
agent.radius,
1.0,
&[],
)
.unwrap(),
Line {
direction: Vec2::new(-1.0, 1.0).normalize(),
point: Vec2::new(-1.0, 1.0).normalize()
}
);
agent.velocity = Vec2::new(0.5, 3.0);
assert_line_eq!(
get_line_for_agent_to_edge(
&agent,
EdgeVertex { point: Vec2::new(0.0, 2.0f32.sqrt()), convex: true },
EdgeVertex { point: Vec2::new(1.0, 4.0), convex: true },
None,
None,
agent.radius,
1.0,
&[],
)
.unwrap(),
Line {
direction: Vec2::new(-1.0, -1.0).normalize(),
point: Vec2::new(1.0, 1.0).normalize()
}
);
}
macro_rules! assert_lines_eq_unordered {
($left: expr, $right: expr) => {{
let left = $left;
let right = $right;
let mut left_not_found = Vec::new();
let mut right_not_found = right.iter().cloned().collect::<Vec<Line>>();
for left_line in left.iter() {
let mut found_index = None;
for (right_index, right_line) in right_not_found.iter().enumerate() {
if left_line.point.distance_squared(right_line.point) < 1e-5
&& left_line.direction.distance_squared(right_line.direction) < 1e-5
{
found_index = Some(right_index);
break;
}
}
if let Some(found_index) = found_index {
right_not_found.remove(found_index);
} else {
left_not_found.push(left_line);
}
}
if !left_not_found.is_empty() || !right_not_found.is_empty() {
panic!("Left lines did not match right lines.\n\nleft={:?}\nright={:?}\n\nunmatched left={:?}\nunmatched right={:?}", left, right, left_not_found, right_not_found);
}
}};
}
#[test]
fn lines_generated_for_closed_convex_obstacle() {
let mut agent = Agent {
position: Vec2::ZERO,
velocity: Vec2::new(0.5, 3.0),
radius: 0.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let obstacle = Obstacle::Closed {
vertices: vec![
Vec2::new(4.0, 4.0),
Vec2::new(-4.0, 4.0),
Vec2::new(0.0, 2.0),
],
};
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line {
direction: Vec2::new(-2.0, -1.0).normalize(),
point: Vec2::new(0.0, 2.0),
}]
);
agent.velocity = Vec2::new(-0.5, 3.0);
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line {
direction: Vec2::new(-2.0, 1.0).normalize(),
point: Vec2::new(-4.0, 4.0),
}]
);
agent.velocity = Vec2::new(2.0, 7.0);
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line {
direction: Vec2::new(-1.0, -1.0).normalize(),
point: Vec2::new(4.0, 4.0),
}]
);
}
#[test]
fn lines_generated_for_open_convex_obstacle() {
let mut agent = Agent {
position: Vec2::ZERO,
velocity: Vec2::new(0.5, 3.0),
radius: 0.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let obstacle = Obstacle::Open {
vertices: vec![
Vec2::new(-4.0, 4.0),
Vec2::new(0.0, 2.0),
Vec2::new(4.0, 4.0),
],
};
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line {
direction: Vec2::new(-2.0, -1.0).normalize(),
point: Vec2::new(0.0, 2.0),
}]
);
agent.velocity = Vec2::new(-0.5, 3.0);
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line {
direction: Vec2::new(-2.0, 1.0).normalize(),
point: Vec2::new(-4.0, 4.0),
}]
);
agent.velocity = Vec2::new(2.0, 7.0);
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line {
direction: Vec2::new(-1.0, -1.0).normalize(),
point: Vec2::new(4.0, 4.0),
}]
);
}
#[test]
fn velocity_projects_to_concave_corner() {
let agent = Agent {
position: Vec2::ZERO,
velocity: Vec2::new(0.0, 3.0),
radius: 0.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let obstacle = Obstacle::Open {
vertices: vec![
Vec2::new(-1.0, 1.0),
Vec2::new(0.0, 2.0),
Vec2::new(1.0, 1.0),
],
};
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[
Line {
direction: Vec2::new(-1.0, -1.0).normalize(),
point: Vec2::new(0.0, 2.0),
},
Line {
direction: Vec2::new(-1.0, 1.0).normalize(),
point: Vec2::new(0.0, 2.0),
},
]
);
}
#[test]
fn no_line_for_projecting_to_concave_endpoint_covered_by_shadow() {
let agent = Agent {
position: Vec2::ZERO,
velocity: Vec2::ZERO,
radius: 0.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let obstacle = Obstacle::Closed {
vertices: vec![
Vec2::new(1.0, 1.0),
Vec2::new(0.0, 2.0),
Vec2::new(0.0, 4.0),
Vec2::new(-1.0, 1.0),
],
};
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line { direction: Vec2::new(-1.0, 0.0), point: Vec2::new(-1.0, 1.0) }]
);
let obstacle = Obstacle::Closed {
vertices: vec![
Vec2::new(1.0, 1.0),
Vec2::new(0.0, 4.0),
Vec2::new(0.0, 2.0),
Vec2::new(-1.0, 1.0),
],
};
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line { direction: Vec2::new(-1.0, 0.0), point: Vec2::new(-1.0, 1.0) }]
);
}
#[test]
fn collision_with_non_back_face_culled_edge_ignored() {
let mut agent = Agent {
position: Vec2::new(0.0, -0.5),
velocity: Vec2::ZERO,
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let obstacle = Obstacle::Open {
vertices: vec![
Vec2::new(-1.0, 1.0),
Vec2::new(0.0, 0.0),
Vec2::new(3.0, 0.0),
Vec2::new(2.0, 1.0),
],
};
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line { direction: Vec2::new(-1.0, 0.0), point: Vec2::new(0.0, 0.0) }]
);
agent.position = Vec2::new(3.1, -0.5);
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[Line {
direction: Vec2::new(-0.1, 0.5).normalize().perp(),
point: Vec2::new(0.0, 0.0)
}]
);
}
#[test]
fn collision_with_convex_vertex() {
let mut agent = Agent {
position: Vec2::new(0.1, -0.1),
velocity: Vec2::ZERO,
radius: 1.0,
max_velocity: 0.0,
avoidance_responsibility: 1.0,
};
let obstacle = Obstacle::Open {
vertices: vec![
Vec2::new(-2.0, -1.0),
Vec2::new(-1.0, 0.0),
Vec2::new(0.0, 0.0),
Vec2::new(1.0, -1.0),
],
};
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[
Line { direction: Vec2::new(-1.0, 1.0).normalize(), point: Vec2::ZERO },
Line { direction: Vec2::new(-1.0, 0.0), point: Vec2::ZERO },
]
);
agent.position = Vec2::new(-1.1, -0.1);
assert_lines_eq_unordered!(
get_lines_for_agent_to_obstacle(
&agent,
&obstacle,
agent.radius,
1.0,
),
[
Line {
direction: Vec2::new(-1.0, -1.0).normalize(),
point: Vec2::ZERO
},
Line { direction: Vec2::new(-1.0, 0.0), point: Vec2::ZERO },
]
);
}
}