rust/landgen/src/wavefront_collapse/wavefront_collapse.rs
author unC0Rr
Fri, 03 Feb 2023 14:44:33 +0100
branchtransitional_engine
changeset 15916 e82de0410da5
parent 15915 8f093b1b18bc
child 15917 60b5639cc3a5
permissions -rw-r--r--
Rework how rules are defined, add transformations for tiles

use integral_geometry::Size;
use std::collections::{HashMap, HashSet};
use vec2d::Vec2D;

#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub enum Tile {
    Empty,
    Outside,
    Numbered(u32),
}

impl Tile {
    fn is(&self, i: u32) -> bool {
        *self == Tile::Numbered(i)
    }

    fn is_empty(&self) -> bool {
        match self {
            Tile::Empty => true,
            Tile::Outside => true,
            _ => false,
        }
    }

    fn is_empty_or(&self, i: u32) -> bool {
        match self {
            Tile::Numbered(n) => *n == i,
            Tile::Empty => true,
            _ => false,
        }
    }

    fn is_void_or(&self, i: u32) -> bool {
        match self {
            Tile::Numbered(n) => *n == i,
            _ => true,
        }
    }
}

impl Default for Tile {
    fn default() -> Self {
        Tile::Outside
    }
}

pub struct CollapseRule {
    tile: Tile,
    right: HashSet<Tile>,
    bottom: HashSet<Tile>,
    left: HashSet<Tile>,
    top: HashSet<Tile>,
}

pub struct WavefrontCollapse {
    rules: Vec<CollapseRule>,
    grid: Vec2D<Tile>,
}

impl Default for WavefrontCollapse {
    fn default() -> Self {
        Self {
            rules: Vec::new(),
            grid: Vec2D::new(&Size::new(1, 1), Tile::Empty),
        }
    }
}

impl WavefrontCollapse {
    pub fn generate_map<I: Iterator<Item = u32>, F: FnOnce(&mut Vec2D<Tile>)>(
        &mut self,
        map_size: &Size,
        seed_fn: F,
        random_numbers: &mut I,
    ) {
        self.grid = Vec2D::new(&map_size, Tile::Empty);

        seed_fn(&mut self.grid);

        while self.collapse_step(random_numbers) {}
    }

    fn add_rule(&mut self, rule: CollapseRule) {
        self.rules.push(rule);
    }

    fn get_tile(&self, y: usize, x: usize) -> Tile {
        self.grid.get(y, x).map(|p| *p).unwrap_or_default()
    }

    fn collapse_step<I: Iterator<Item = u32>>(&mut self, random_numbers: &mut I) -> bool {
        let mut tiles_to_collapse = (usize::max_value(), Vec::new());

        // Iterate through the tiles in the land
        for x in 0..self.grid.width() {
            for y in 0..self.grid.height() {
                let current_tile = self.get_tile(y, x);

                if let Tile::Empty = current_tile {
                    // calc entropy
                    let right_tile = self.get_tile(y, x + 1);
                    let bottom_tile = self.get_tile(y + 1, x);
                    let left_tile = self.get_tile(y, x.wrapping_sub(1));
                    let top_tile = self.get_tile(y.wrapping_sub(1), x);

                    let possibilities: Vec<Tile> = self
                        .rules
                        .iter()
                        .filter_map(|rule| {
                            if rule.right.contains(&right_tile)
                                && rule.bottom.contains(&bottom_tile)
                                && rule.left.contains(&left_tile)
                                && rule.top.contains(&top_tile)
                            {
                                Some(rule.tile)
                            } else {
                                None
                            }
                        })
                        .collect();

                    let entropy = possibilities.len();
                    if entropy > 0 && entropy <= tiles_to_collapse.0 {
                        let entry = (
                            y,
                            x,
                            possibilities
                                [random_numbers.next().unwrap_or_default() as usize % entropy],
                        );

                        if entropy < tiles_to_collapse.0 {
                            tiles_to_collapse = (entropy, vec![entry])
                        } else {
                            tiles_to_collapse.1.push(entry)
                        }
                    } else {
                        todo!("no collapse possible")
                    }
                }
            }
        }

        let tiles_to_collapse = tiles_to_collapse.1;
        let possibilities_number = tiles_to_collapse.len();

        if possibilities_number > 0 {
            let (y, x, tile) = tiles_to_collapse
                [random_numbers.next().unwrap_or_default() as usize % possibilities_number];

            *self
                .grid
                .get_mut(y, x)
                .expect("correct iteration over grid") = tile;

            true
        } else {
            false
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Tile, WavefrontCollapse};
    use integral_geometry::Size;
    use vec2d::Vec2D;

    #[test]
    fn test_wavefront_collapse() {
        let size = Size::new(4, 4);
        let mut rnd = [0u32; 64].into_iter();
        let mut wfc = WavefrontCollapse::default();

        let empty_land = Vec2D::new(&size, Tile::Empty);

        assert_eq!(empty_land.as_slice(), wfc.grid.as_slice());
    }
}