rust/landgen/src/wavefront_collapse/wavefront_collapse.rs
branchtransitional_engine
changeset 15943 c5684cc62de8
parent 15942 6e22f4390b7e
child 15945 8f093b1b18bc
equal deleted inserted replaced
15942:6e22f4390b7e 15943:c5684cc62de8
       
     1 use integral_geometry::Size;
       
     2 use vec2d::Vec2D;
       
     3 use std::collections::HashMap;
       
     4 
       
     5 #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
       
     6 pub enum Tile {
       
     7     Empty,
       
     8     Outside,
       
     9     Numbered(u32),
       
    10 }
       
    11 
       
    12 impl Tile {
       
    13     fn is(&self, i: u32) -> bool {
       
    14         *self == Tile::Numbered(i)
       
    15     }
       
    16 
       
    17     fn is_empty(&self) -> bool {
       
    18         match self {
       
    19             Tile::Empty => true,
       
    20             Tile::Outside => true,
       
    21             _ => false,
       
    22         }
       
    23     }
       
    24 
       
    25     fn is_empty_or(&self, i: u32) -> bool {
       
    26         match self {
       
    27             Tile::Numbered(n) => *n == i,
       
    28             Tile::Empty => true,
       
    29             _ => false,
       
    30         }
       
    31     }
       
    32 
       
    33     fn is_void_or(&self, i: u32) -> bool {
       
    34         match self {
       
    35             Tile::Numbered(n) => *n == i,
       
    36             _ => true,
       
    37         }
       
    38     }
       
    39 }
       
    40 
       
    41 impl Default for Tile {
       
    42     fn default() -> Self {
       
    43         Tile::Outside
       
    44     }
       
    45 }
       
    46 
       
    47 struct CollapseRule {
       
    48     to_tile: Tile,
       
    49     condition: fn([Tile; 4]) -> bool,
       
    50 }
       
    51 
       
    52 #[derive(Default)]
       
    53 pub struct WavefrontCollapse {
       
    54     rules: HashMap<Tile, Vec<CollapseRule>>,
       
    55 }
       
    56 
       
    57 impl WavefrontCollapse {
       
    58     pub fn generate_map<I: Iterator<Item = u32>, F: FnOnce(&mut Vec2D<Tile>)>(
       
    59         &mut self,
       
    60         map_size: &Size,
       
    61         seed_fn: F,
       
    62         random_numbers: &mut I,
       
    63         ) -> Vec2D<Tile> {
       
    64         let mut land = Vec2D::new(&map_size, Tile::Empty);
       
    65 
       
    66         seed_fn(&mut land);
       
    67 
       
    68         while self.collapse_step(&mut land, random_numbers) {}
       
    69 
       
    70         land
       
    71     }
       
    72 
       
    73     fn add_rule(&mut self, from_tile: Tile, to_tile: Tile, condition: fn([Tile; 4]) -> bool) {
       
    74         let rule = CollapseRule { to_tile, condition };
       
    75         self.rules
       
    76             .entry(from_tile)
       
    77             .or_insert_with(Vec::new)
       
    78             .push(rule);
       
    79     }
       
    80 
       
    81     fn collapse_step<I: Iterator<Item = u32>>(
       
    82         &self,
       
    83         land: &mut Vec2D<Tile>,
       
    84         random_numbers: &mut I,
       
    85     ) -> bool {
       
    86         let mut collapse_occurred = false;
       
    87         for x in 0..land.width() {
       
    88             for y in 0..land.height() {
       
    89                 let current_tile = land.get(y, x).expect("valid iteration range");
       
    90 
       
    91                 if let Some(rules) = self.rules.get(&current_tile) {
       
    92                     for rule in rules
       
    93                         .iter()
       
    94                         .cycle()
       
    95                         .skip(
       
    96                             random_numbers.next().unwrap_or_default() as usize % (rules.len() + 1),
       
    97                         )
       
    98                         .take(rules.len())
       
    99                     {
       
   100                         let neighbors = self.get_neighbors(&land, x, y);
       
   101                         let have_neighbors = neighbors.iter().any(|t| !t.is_empty());
       
   102                         if have_neighbors && (rule.condition)(neighbors) {
       
   103                             *land.get_mut(y, x).expect("valid iteration range") = rule.to_tile;
       
   104                             collapse_occurred = true;
       
   105                             break;
       
   106                         }
       
   107                     }
       
   108                 }
       
   109             }
       
   110         }
       
   111 
       
   112         collapse_occurred
       
   113     }
       
   114 
       
   115     fn get_neighbors(&self, land: &Vec2D<Tile>, x: usize, y: usize) -> [Tile; 4] {
       
   116         [
       
   117             land.get(y, x + 1).map(|p| *p).unwrap_or_default(),
       
   118             land.get(y + 1, x).map(|p| *p).unwrap_or_default(),
       
   119             land.get(y, x.wrapping_sub(1)).map(|p| *p).unwrap_or_default(),
       
   120             land.get(y.wrapping_sub(1), x).map(|p| *p).unwrap_or_default(),
       
   121         ]
       
   122     }
       
   123 }
       
   124 
       
   125 #[cfg(test)]
       
   126 mod tests {
       
   127     use super::{Tile, WavefrontCollapse};
       
   128     use integral_geometry::Size;
       
   129     use vec2d::Vec2D;
       
   130 
       
   131     #[test]
       
   132     fn test_wavefront_collapse() {
       
   133         let size = Size::new(4, 4);
       
   134         let mut rnd = [0u32; 64].into_iter();
       
   135         let mut wfc = WavefrontCollapse::default();
       
   136 
       
   137         let empty_land = Vec2D::new(&size, Tile::Empty);
       
   138         let no_rules_land = wfc.generate_map(&size, |_| {}, &mut rnd);
       
   139 
       
   140         assert_eq!(empty_land.as_slice(), no_rules_land.as_slice());
       
   141 
       
   142         wfc.add_rule(Tile::Empty, Tile::Numbered(0), |neighbors| {
       
   143             neighbors.iter().filter(|&n| *n == Tile::Empty).count() >= 2
       
   144         });
       
   145         let ruled_map = wfc.generate_map(&size, |_| {}, &mut rnd);
       
   146 
       
   147         assert_eq!(ruled_map.as_slice(), empty_land.as_slice());
       
   148     }
       
   149 }