use std::{
cmp::Ordering,
collections::{BinaryHeap, HashMap, HashSet, VecDeque}
};
use super::vector2::{ORTHO_DIRECTIONS, Vector2i};
pub fn find_path(
start: Vector2i,
end: Vector2i,
tiles: &HashSet<Vector2i>,
blockers: &HashSet<Vector2i>
) -> Option<VecDeque<Vector2i>> {
let mut queue = BinaryHeap::new();
queue.push(Node { v: start, cost: 0});
let mut visited = HashMap::new();
visited.insert(start, 0);
let mut came_from = HashMap::new();
while let Some(Node { v, cost }) = queue.pop() {
if v == end { break; }
for dir in ORTHO_DIRECTIONS {
let n = v + dir;
let new_cost = cost + 1;
if !tiles.contains(&n) { continue }
if blockers.contains(&n) && n != end { continue }
match visited.get(&n) {
Some(c) if *c <= new_cost => (),
_ => {
visited.insert(n, new_cost);
queue.push(Node { v: n, cost: new_cost });
came_from.insert(n, v);
}
}
}
}
let mut path = VecDeque::new();
let mut cur = end;
while let Some(v) = came_from.get(&cur) {
path.push_front(cur);
cur = *v;
if cur == start { return Some(path) }
}
None
}
#[derive(Copy, Clone, Eq, PartialEq)]
struct Node {
pub v: Vector2i,
pub cost: u32
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
other.cost.cmp(&self.cost)
.then_with(|| self.v.cmp(&other.v))
}
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub fn get_line(a: Vector2i, b: Vector2i) -> Vec<Vector2i> {
let mut tiles = Vec::new();
let n = line_dist(a, b);
if n == 0 { return tiles };
for i in 0..=n {
let t = i as f32 / n as f32;
let f = a.as_f32().lerp(&b.as_f32(), t);
tiles.push(Vector2i::new(
f.x.round() as i32,
f.y.round() as i32,
));
}
tiles
}
fn line_dist(a: Vector2i, b: Vector2i) -> i32 {
let dx = b.x - a.x;
let dy = b.y - a.y;
dx.abs().max(dy.abs())
}
pub fn visible_tiles(
origin: Vector2i,
tiles: &HashSet<Vector2i>,
blockers: &HashSet<Vector2i>,
range: u32,
) -> HashSet<Vector2i> {
let mut visible = Vec::new();
for octant in 0..8 {
visible.extend(
visible_octant(
origin,
tiles,
blockers,
range as i32,
1,
0.0,
1.0,
octant
)
);
}
HashSet::from_iter(visible)
}
fn transform_octant(v: Vector2i, octant: u32) -> Vector2i {
match octant {
0 => Vector2i::new(v.y, -v.x),
1 => Vector2i::new(v.x, -v.y),
2 => Vector2i::new(v.x, v.y),
3 => Vector2i::new(v.y, v.x),
4 => Vector2i::new(-v.y, v.x),
5 => Vector2i::new(-v.x, v.y),
6 => Vector2i::new(-v.x, -v.y),
7 => Vector2i::new(-v.y, -v.x),
_ => Vector2i::ZERO
}
}
fn visible_octant(
origin: Vector2i,
tiles: &HashSet<Vector2i>,
blockers: &HashSet<Vector2i>,
range: i32,
start: i32,
mut left_slope: f32,
right_slope: f32,
octant: u32
) -> Vec<Vector2i> {
let mut visible = vec![origin];
if left_slope >= right_slope { return visible };
for y in start..range {
let sx = (y as f32 * left_slope).round() as i32;
let ex = (y as f32 * right_slope).round() as i32;
for x in sx..=ex {
let v = origin + transform_octant(Vector2i::new(x, y), octant);
if origin.manhattan(v) > range { continue; }
if !tiles.contains(&v) { continue; }
visible.push(v);
if !blockers.contains(&v) { continue; }
let new_right_slope = (x as f32 - 0.5) / (y as f32 + 0.5);
visible.extend(
visible_octant(
origin,
tiles,
blockers,
range,
y + 1,
left_slope,
new_right_slope,
octant
)
);
left_slope = (x as f32 + 0.5) / (y as f32 - 0.5);
}
}
visible
}