rust/landgen/src/maze.rs
author unC0Rr
Mon, 16 Sep 2024 16:57:11 +0200
branchtransitional_engine
changeset 16034 09beeec033ba
parent 16033 1860852892c0
child 16035 0caa3dfb3ba2
permissions -rw-r--r--
Sanitize maze mapgen code a bit

use crate::outline_template_based::outline::OutlinePoints;
use crate::{LandGenerationParameters, LandGenerator};
use integral_geometry::{Point, Polygon, Rect, Size};
use land2d::Land2D;

pub struct MazeTemplate {
    pub width: usize,
    pub height: usize,
    pub cell_size: usize,
    pub inverted: bool,
    pub distortion_limiting_factor: u32,
    pub braidness: u32,
}

struct Maze {
    inverted: bool,
    braidness: u32,
    off_y: i32,
    num_cells: Size,
    num_edges: Size,
    seen_cells: Size,
    cell_size: usize,
    seen_list: Vec<Vec<Option<usize>>>,
    walls: Vec<Vec<Vec<bool>>>,
    edge_list: Vec<Vec<Vec<bool>>>,
    last_cell: Vec<Point>,
    came_from: Vec<Vec<Point>>,
    came_from_pos: Vec<i32>,
}

fn in_line(p1: &Point, p2: &Point, p3: &Point) -> bool {
    p1.x == p2.x && p2.x == p3.x || p1.y == p2.y && p2.y == p3.y
}

#[derive(Clone, Copy)]
struct Direction(Point);

impl Direction {
    #[inline]
    pub fn new(direction: usize) -> Self {
        match direction % 4 {
            0 => Self(Point::new(0, -1)),
            1 => Self(Point::new(1, 0)),
            2 => Self(Point::new(0, 1)),
            3 => Self(Point::new(-1, 0)),
            _ => panic!("Impossible"),
        }
    }

    #[inline]
    pub fn rotate_right(self) -> Self {
        Self(self.0.rotate90())
    }

    #[inline]
    pub fn rotate_left(self) -> Self {
        Self(self.0.rotate270())
    }

    #[inline]
    pub fn to_edge(self) -> Self {
        Self(Point::new(
            if self.0.x < 0 { 0 } else { self.0.x },
            if self.0.y < 0 { 0 } else { self.0.y },
        ))
    }

    #[inline]
    pub fn orientation(&self) -> usize {
        if self.0.x == 0 {
            0
        } else {
            1
        }
    }
}

impl Maze {
    pub fn new<I: Iterator<Item = u32>>(
        size: &Size,
        cell_size: usize,
        num_steps: usize,
        inverted: bool,
        braidness: u32,
        random_numbers: &mut I,
    ) -> Self {
        let num_cells = Size::new(
            prev_odd(size.width / cell_size),
            prev_odd(size.height / cell_size),
        );

        let num_edges = Size::new(num_cells.width - 1, num_cells.height - 1);
        let seen_cells = Size::new(num_cells.width / 2, num_cells.height / 2);

        let mut last_cell = vec![Point::diag(0); num_steps];
        let came_from_pos = vec![0; num_steps];
        let came_from = vec![vec![Point::diag(0); num_steps]; num_cells.area()];

        let seen_list = vec![vec![None as Option<usize>; seen_cells.width]; seen_cells.height];
        let walls = vec![vec![vec![true; seen_cells.width]; seen_cells.height]; 2];
        let edge_list = vec![vec![vec![false; num_cells.width]; num_cells.height]; 2];

        for current_step in 0..num_steps {
            let x = random_numbers.next().unwrap_or_default() as usize % (seen_cells.width - 1)
                / num_steps;
            last_cell[current_step] = Point::new(
                (x + current_step * seen_cells.width / num_steps) as i32,
                random_numbers.next().unwrap_or_default() as i32 % seen_cells.height as i32,
            );
        }

        Self {
            inverted,
            braidness,
            off_y: ((size.height - num_cells.height * cell_size) / 2) as i32,
            num_cells,
            num_edges,
            seen_cells,
            cell_size,
            seen_list,
            walls,
            edge_list,
            last_cell,
            came_from,
            came_from_pos,
        }
    }

    fn see_cell<I: Iterator<Item = u32>>(
        &mut self,
        current_step: usize,
        start_dir: Direction,
        random_numbers: &mut I,
    ) -> bool {
        let mut dir = start_dir;
        loop {
            let p = self.last_cell[current_step];
            self.seen_list[p.y as usize][p.x as usize] = Some(current_step);

            let next_dir_clockwise = random_numbers.next().unwrap_or_default() % 2 == 0;

            for _ in 0..5 {
                let sp = p + dir.0;
                let when_seen = if sp.x < 0
                    || sp.x >= self.seen_cells.width as i32
                    || sp.y < 0
                    || sp.y >= self.seen_cells.height as i32
                {
                    Some(current_step)
                } else {
                    self.seen_list[sp.y as usize][sp.x as usize]
                };

                match when_seen {
                    Some(a) if a == current_step => {
                        // try another direction
                        if !self.inverted
                            && random_numbers.next().unwrap_or_default() % self.braidness == 0
                        {
                            if dir.0.x == -1 && p.x > 0 {
                                self.walls[dir.orientation()][p.y as usize][p.x as usize - 1] =
                                    false;
                            }
                            if dir.0.x == 1 && p.x < self.seen_cells.width as i32 - 1 {
                                self.walls[dir.orientation()][p.y as usize][p.x as usize] = false;
                            }
                            if dir.0.y == -1 && p.y > 0 {
                                self.walls[dir.orientation()][p.y as usize - 1][p.x as usize] =
                                    false;
                            }
                            if dir.0.y == 1 && p.y < self.seen_cells.height as i32 - 1 {
                                self.walls[dir.orientation()][p.y as usize][p.x as usize] = false;
                            }
                        }

                        if next_dir_clockwise {
                            dir = dir.rotate_right();
                        } else {
                            dir = dir.rotate_left();
                        }
                    }
                    None => {
                        // cell was not seen yet, go there
                        let o_dir = dir.rotate_right().rotate_right();
                        let op = p - o_dir.to_edge().0;
                        self.walls[o_dir.orientation()][op.y as usize][op.x as usize] = false;
                        self.last_cell[current_step] = sp;
                        self.came_from_pos[current_step] += 1;
                        self.came_from[self.came_from_pos[current_step] as usize][current_step] = p;
                        return false;
                    }
                    _ => {
                        return true;
                    }
                }
            }

            self.last_cell[current_step] =
                self.came_from[self.came_from_pos[current_step] as usize][current_step];
            self.came_from_pos[current_step] -= 1;

            if self.came_from_pos[current_step] < 0 {
                return true;
            }
        }
    }

    fn add_vertex(&mut self, p: Point, polygon: &mut Vec<Point>) {
        let [x, y] = [p.x, p.y].map(|i| {
            if self.inverted || i & 1 == 0 {
                self.cell_size
            } else {
                self.cell_size * 2 / 3
            }
        });
        let new_point = Point::new(
            (p.x - 1) * self.cell_size as i32 + x as i32,
            (p.y - 1) * self.cell_size as i32 + y as i32 + self.off_y,
        );

        let nv = polygon.len();
        if nv > 2 {
            if in_line(&polygon[nv - 2], &polygon[nv - 1], &new_point) {
                polygon.pop();
            }
        }

        polygon.push(new_point);
    }

    fn add_edge(&mut self, p: Point, mut dir: Direction, polygon: &mut Vec<Point>) {
        let mut next_p = Some(p);

        while let Some(p) = next_p {
            next_p = None;

            for _ in 0..4 {
                let cdir = dir.to_edge();

                let np = p + cdir.0;

                if np.x >= 0
                    && np.y >= 0
                    && np.x < self.num_cells.width as i32
                    && np.y < self.num_cells.height as i32
                    && self.edge_list[dir.orientation()][np.y as usize][np.x as usize]
                {
                    self.edge_list[dir.orientation()][np.y as usize][np.x as usize] = false;
                    self.add_vertex(p + dir.0 + Point::new(1, 1), polygon);
                    next_p = Some(p + dir.0);
                    break;
                }

                dir = dir.rotate_right();
            }
        }
    }

    pub fn to_islands(mut self) -> (Vec<Polygon>, Vec<Point>) {
        let mut islands: Vec<Polygon> = vec![];
        let mut polygon: Vec<Point> = vec![];
        let mut maze = vec![vec![false; self.num_cells.width]; self.num_cells.height];

        for x in 0..self.seen_cells.width {
            for y in 0..self.seen_cells.height {
                if self.seen_list[y][x].is_some() {
                    maze[y * 2 + 1][x * 2 + 1] = true;
                }
            }

            for y in 0..self.seen_cells.height - 1 {
                if !self.walls[0][y][x] {
                    maze[y * 2 + 2][x * 2 + 1] = true;
                }
            }
        }

        for x in 0..self.seen_cells.width - 1 {
            for y in 0..self.seen_cells.height {
                if !self.walls[1][y][x] {
                    maze[y * 2 + 1][x * 2 + 2] = true;
                }
            }
        }

        for x in 0..self.num_edges.width {
            for y in 0..self.num_cells.height {
                self.edge_list[0][y][x] = maze[y][x] != maze[y][x + 1];
            }
        }

        for x in 0..self.num_cells.width {
            for y in 0..self.num_edges.height {
                self.edge_list[1][y][x] = maze[y][x] != maze[y + 1][x];
            }
        }

        let mut fill_points = vec![];

        for y in 0..self.num_cells.height {
            for x in 0..self.num_cells.width {
                if maze[y][x] {
                    let half_cell = self.cell_size / 2;
                    let fill_point = Point::new(
                        (x * self.cell_size + half_cell) as i32,
                        (y * self.cell_size + half_cell) as i32 + self.off_y,
                    );
                    islands.push(Polygon::new(&[fill_point]));
                    fill_points.push(fill_point);
                }
            }
        }

        for x in 0..self.num_edges.width {
            for y in 0..self.num_cells.height {
                if self.edge_list[0][y][x] {
                    self.edge_list[0][y][x] = false;
                    self.add_vertex(Point::new(x as i32 + 1, y as i32 + 1), &mut polygon);
                    self.add_vertex(Point::new(x as i32 + 1, y as i32), &mut polygon);
                    self.add_edge(
                        Point::new(x as i32, y as i32 - 1),
                        Direction::new(0),
                        &mut polygon,
                    );

                    if polygon.len() > 4 {
                        if in_line(polygon.last().unwrap(), &polygon[0], &polygon[1]) {
                            polygon.pop();
                        }

                        /*
                                                for p in &polygon {
                                                    println!("{} {}", p.x, p.y);
                                                }
                                                println!("\ne\n");
                        */

                        islands.push(Polygon::new(&polygon));
                    }
                    polygon.clear();
                }
            }
        }

        (islands, fill_points)
    }
}

pub struct MazeLandGenerator {
    maze_template: MazeTemplate,
}

fn prev_odd(num: usize) -> usize {
    if num & 1 == 0 {
        num - 1
    } else {
        num
    }
}

impl MazeLandGenerator {
    pub fn new(maze_template: MazeTemplate) -> Self {
        Self { maze_template }
    }

    fn generate_outline<I: Iterator<Item = u32>>(
        &self,
        size: &Size,
        play_box: Rect,
        intersections_box: Rect,
        random_numbers: &mut I,
    ) -> OutlinePoints {
        let num_steps = if self.maze_template.inverted { 3 } else { 1 };
        let mut step_done = vec![false; num_steps];
        let mut done = false;

        let mut maze = Maze::new(
            &size,
            self.maze_template.cell_size,
            num_steps,
            self.maze_template.inverted,
            self.maze_template.braidness,
            random_numbers,
        );

        while !done {
            done = true;

            for current_step in 0..num_steps {
                if !step_done[current_step] {
                    let dir = Direction::new(random_numbers.next().unwrap_or_default() as usize);
                    step_done[current_step] = maze.see_cell(current_step, dir, random_numbers);
                    done = false;
                }
            }
        }

        let (islands, fill_points) = maze.to_islands();

        OutlinePoints {
            islands,
            fill_points,
            size: *size,
            play_box,
            intersections_box,
        }
    }
}

impl LandGenerator for MazeLandGenerator {
    fn generate_land<T: Copy + PartialEq + Default, I: Iterator<Item = u32>>(
        &self,
        parameters: &LandGenerationParameters<T>,
        random_numbers: &mut I,
    ) -> Land2D<T> {
        let do_invert = self.maze_template.inverted;
        let (basic, zero) = if do_invert {
            (parameters.zero, parameters.basic)
        } else {
            (parameters.basic, parameters.zero)
        };

        let land_size = Size::new(self.maze_template.width, self.maze_template.height);
        let mut land = Land2D::new(&land_size, basic);

        let mut points = self.generate_outline(
            &land.size().size(),
            land.play_box(), //??? Rect::at_origin(land_size).with_margin(land_size.to_square().width as i32 * -2),
            land.play_box(),
            random_numbers,
        );

        if !parameters.skip_distort {
            points.distort(parameters.distance_divisor, random_numbers);
        }

        if !parameters.skip_bezier {
            points.bezierize(5);
        }

        points.draw(&mut land, zero);

        for p in &points.fill_points {
            land.fill(*p, zero, zero)
        }

        land
    }
}