use glam::Vec2;
use crate::{common::determinant, Agent, Line};
pub enum Obstacle {
  Closed { vertices: Vec<Vec2> },
  Open { vertices: Vec<Vec2> },
}
pub fn get_lines_for_agent_to_obstacle(
  agent: &Agent,
  obstacle: &Obstacle,
  time_horizon: f32,
) -> Vec<Line> {
  match obstacle {
    Obstacle::Closed { vertices } => get_lines_for_agent_to_obstacle_const::<
      true,
    >(agent, vertices, time_horizon),
    Obstacle::Open { vertices } => {
      get_lines_for_agent_to_obstacle_const::<false>(
        agent,
        vertices,
        time_horizon,
      )
    }
  }
}
fn get_lines_for_agent_to_obstacle_const<const CLOSED: bool>(
  agent: &Agent,
  vertices: &[Vec2],
  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]),
        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])
        },
        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>,
  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,
    radius: 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)
        >= radius / time_horizon - EDGE_COVER_EPSILON
        && determinant(right_vertex / time_horizon - line.point, line.direction)
          >= radius / time_horizon - EDGE_COVER_EPSILON
      {
        return true;
      }
    }
    false
  }
  if is_edge_covered(
    relative_left_vertex,
    relative_right_vertex,
    time_horizon,
    agent.radius,
    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 = agent.radius * agent.radius;
  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() * agent.radius)
      / dist_left_squared;
    right_shadow_direction = (relative_left_vertex * tangent_triangle_leg
      - relative_left_vertex.perp() * agent.radius)
      / 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() * agent.radius)
      / dist_right_squared;
    right_shadow_direction = (relative_right_vertex * tangent_triangle_leg
      - relative_right_vertex.perp() * agent.radius)
      / 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() * agent.radius)
        / 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() * agent.radius)
        / 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
        + agent.radius / 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
        + agent.radius / 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 + agent.radius / 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
          + agent.radius / time_horizon * left_shadow_direction.perp(),
      })
    }
  } else {
    if is_right_shadow_covered {
      None
    } else {
      Some(Line {
        direction: -right_shadow_direction,
        point: right_cutoff
          - agent.radius / 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,
      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,
      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]),
      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]),
      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,
      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]),
      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]),
      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]),
      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]),
      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]),
      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]),
      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]),
      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]),
      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,
      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,
      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,
      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,
        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,
        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,
        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,
        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,
        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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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 },
      ]
    );
  }
}