terrain_forge/algorithms/
wfc.rs

1use crate::{Algorithm, Grid, Rng, Tile};
2
3#[derive(Debug, Clone)]
4pub struct WfcConfig {
5    pub floor_weight: f64,
6}
7
8impl Default for WfcConfig {
9    fn default() -> Self {
10        Self { floor_weight: 0.4 }
11    }
12}
13
14pub struct Wfc {
15    config: WfcConfig,
16}
17
18impl Wfc {
19    pub fn new(config: WfcConfig) -> Self {
20        Self { config }
21    }
22}
23
24impl Default for Wfc {
25    fn default() -> Self {
26        Self::new(WfcConfig::default())
27    }
28}
29
30impl Algorithm<Tile> for Wfc {
31    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
32        let mut rng = Rng::new(seed);
33        let (w, h) = (grid.width(), grid.height());
34
35        // Simplified WFC: propagate constraints from random initial state
36        let mut possibilities: Vec<Vec<[bool; 2]>> = vec![vec![[true, true]; w]; h];
37
38        // Border must be walls
39        for cell in possibilities[0].iter_mut() {
40            *cell = [true, false];
41        }
42        for cell in possibilities[h - 1].iter_mut() {
43            *cell = [true, false];
44        }
45        for row in possibilities.iter_mut() {
46            row[0] = [true, false];
47            row[w - 1] = [true, false];
48        }
49
50        // Collapse cells
51        loop {
52            // Find cell with lowest entropy > 1
53            let mut min_entropy = 3;
54            let mut candidates = Vec::new();
55
56            for (y, row) in possibilities.iter().enumerate() {
57                for (x, cell) in row.iter().enumerate() {
58                    let entropy = cell.iter().filter(|&&b| b).count();
59                    if entropy > 1 {
60                        if entropy < min_entropy {
61                            min_entropy = entropy;
62                            candidates.clear();
63                        }
64                        if entropy == min_entropy {
65                            candidates.push((x, y));
66                        }
67                    }
68                }
69            }
70
71            if candidates.is_empty() {
72                break;
73            }
74
75            let &(cx, cy) = rng.pick(&candidates).unwrap();
76            let choose_floor = rng.chance(self.config.floor_weight) && possibilities[cy][cx][1];
77            possibilities[cy][cx] = if choose_floor {
78                [false, true]
79            } else {
80                [true, false]
81            };
82
83            propagate(&mut possibilities);
84        }
85
86        // Apply to grid
87        for (y, row) in possibilities.iter().enumerate() {
88            for (x, cell) in row.iter().enumerate() {
89                if cell[1] {
90                    grid.set(x as i32, y as i32, Tile::Floor);
91                }
92            }
93        }
94    }
95
96    fn name(&self) -> &'static str {
97        "WFC"
98    }
99}
100
101fn propagate(poss: &mut [Vec<[bool; 2]>]) {
102    let mut changed = true;
103    while changed {
104        changed = false;
105        for row in poss.iter_mut() {
106            for cell in row.iter_mut() {
107                if cell.iter().filter(|&&b| b).count() != 1 {
108                    continue;
109                }
110                // Simplified propagation - no actual constraints applied
111            }
112        }
113    }
114}