rust/landgen/src/wavefront_collapse.rs
branchtransitional_engine
changeset 15913 c5684cc62de8
parent 15912 6e22f4390b7e
child 15914 c571d4b8879c
equal deleted inserted replaced
15912:6e22f4390b7e 15913:c5684cc62de8
     1 use integral_geometry::Size;
       
     2 use land2d::Land2D;
       
     3 use std::collections::HashMap;
       
     4 
       
     5 #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
       
     6 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 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 Land2D<Tile>)>(
       
    59         &mut self,
       
    60         map_size: &Size,
       
    61         seed_fn: F,
       
    62         random_numbers: &mut I,
       
    63     ) -> Land2D<Tile> {
       
    64         let mut land = Land2D::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 Land2D<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.map(y as i32, x as i32, |p| *p);
       
    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.map(y as i32, x as i32, |p| *p = 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: &Land2D<Tile>, x: usize, y: usize) -> [Tile; 4] {
       
   116         [
       
   117             land.get(y as i32, x as i32 + 1),
       
   118             land.get(y as i32 + 1, x as i32),
       
   119             land.get(y as i32, x as i32 - 1),
       
   120             land.get(y as i32 - 1, x as i32),
       
   121         ]
       
   122     }
       
   123 }
       
   124 
       
   125 #[cfg(test)]
       
   126 mod tests {
       
   127     use super::{CollapseRule, Tile, WavefrontCollapse};
       
   128     use integral_geometry::Size;
       
   129     use land2d::Land2D;
       
   130     use std::collections::HashMap;
       
   131 
       
   132     #[test]
       
   133     fn test_wavefront_collapse() {
       
   134         let size = Size::new(4, 4);
       
   135         let mut rnd = [0u32; 64].into_iter();
       
   136         let mut wfc = WavefrontCollapse::default();
       
   137 
       
   138         let empty_land = Land2D::new(&size, Tile::Empty);
       
   139         let no_rules_land = wfc.generate_map(&size, |l| {}, &mut rnd);
       
   140 
       
   141         assert_eq!(empty_land.raw_pixels(), no_rules_land.raw_pixels());
       
   142 
       
   143         wfc.add_rule(Tile::Empty, Tile::Numbered(0), |neighbors| {
       
   144             neighbors.iter().filter(|&n| *n == Tile::Empty).count() >= 2
       
   145         });
       
   146         let ruled_map = wfc.generate_map(&size, |l| {}, &mut rnd);
       
   147 
       
   148         assert_eq!(ruled_map.raw_pixels(), empty_land.raw_pixels());
       
   149     }
       
   150 }