line_of_sight 0.2.1

A crate for finding the line of sight on a 2D grid.
Documentation
mod tests;

pub const EMPTY: u8 = 0;
pub const BLOCK: u8 = 1;
pub const VISIBLE: u8 = 2;
pub const PLAYER: u8 = 3;

#[derive(Debug)]
pub struct ShadowMap {
    map: Vec<Vec<u8>>,
}

impl ShadowMap {
    pub fn new_with_empty_cells(width: usize, height: usize) -> Self {
        let mut outer_vec = Vec::with_capacity(height);
        let mut inner_vec = Vec::with_capacity(width);

        for _ in 0..width {
            inner_vec.push(EMPTY);
        }

        for _ in 0..height {
            outer_vec.push(inner_vec.clone());
        }

        #[cfg(test)]
        assert_eq!(outer_vec.len(), height);

        #[cfg(test)]
        assert_eq!(inner_vec.len(), width);

        ShadowMap { map: outer_vec }
    }

    pub fn new_with_obstacles(width: usize, height: usize) -> Self {
        let mut outer_vec = Vec::with_capacity(height);
        let mut inner_vec = Vec::with_capacity(width);

        for _ in 0..width {
            inner_vec.push(BLOCK);
        }

        for _ in 0..height {
            outer_vec.push(inner_vec.clone());
        }

        #[cfg(test)]
        assert_eq!(outer_vec.len(), height);

        #[cfg(test)]
        assert_eq!(inner_vec.len(), width);

        ShadowMap { map: outer_vec }
    }

    pub fn get(&self, x: isize, y: isize) -> u8 {
        if x < 0 || y < 0 || x as usize >= self.map[0].len() || y as usize >= self.map.len() {
            return EMPTY;
        }

        return self.map[y as usize][x as usize];
    }

    pub fn set(&mut self, x: isize, y: isize, value: u8) {
        if x >= 0 && y >= 0 && (x as usize) < self.map[0].len() && (y as usize) < self.map.len() {
            self.map[y as usize][x as usize] = value;
        }
    }

    pub fn show(&self) {
        println!("");

        for row in &self.map {
            println!("{:?}", row);
        }
    }

    pub fn scan_arc(
        &mut self,
        center: (f32, f32),
        distance: f32,
        mut min: f32,
        max: f32,
        rotate: &dyn Fn(f32, f32) -> (f32, f32),
        sight_radius: f32,
    ) {
        if distance > sight_radius || min >= max {
            return;
        }

        let mut i = (distance * min).ceil();

        while i <= distance * max {
            let x = center.0 + rotate(distance, i).0;
            let y = center.1 + rotate(distance, i).1;

            if self.get(x as isize, y as isize) == BLOCK {
                self.scan_arc(
                    center,
                    distance + 1.0,
                    min,
                    (i - 0.5) / distance,
                    rotate,
                    sight_radius,
                );
                min = (i + 0.5) / distance;
            } else {
                self.set(x as isize, y as isize, VISIBLE);
            }

            i += 1.0;
        }

        self.scan_arc(center, distance + 1.0, min, max, rotate, sight_radius);
    }

    pub fn full_scan(&mut self, center: (f32, f32), sight_radius: f32) {
        self.scan_arc(center, 0.0, -1.0, 1.0, &|x, y| (x, y), sight_radius);
        self.scan_arc(center, 0.0, -1.0, 1.0, &|x, y| (y, -x), sight_radius);
        self.scan_arc(center, 0.0, -1.0, 1.0, &|x, y| (-x, -y), sight_radius);
        self.scan_arc(center, 0.0, -1.0, 1.0, &|x, y| (-y, x), sight_radius);
    }
}