terrain_forge/algorithms/
prefab.rs

1use crate::{Algorithm, Grid, Rng, Tile};
2
3#[derive(Debug, Clone)]
4pub struct PrefabConfig {
5    pub max_prefabs: usize,
6    pub min_spacing: usize,
7    pub allow_rotation: bool,
8}
9
10impl Default for PrefabConfig {
11    fn default() -> Self { Self { max_prefabs: 3, min_spacing: 5, allow_rotation: true } }
12}
13
14#[derive(Clone)]
15pub struct Prefab {
16    pub width: usize,
17    pub height: usize,
18    pub data: Vec<bool>,
19}
20
21impl Prefab {
22    pub fn new(pattern: &[&str]) -> Self {
23        let height = pattern.len();
24        let width = pattern.first().map(|s| s.len()).unwrap_or(0);
25        let data = pattern.iter().flat_map(|row| row.chars().map(|c| c == '.')).collect();
26        Self { width, height, data }
27    }
28
29    pub fn rect(w: usize, h: usize) -> Self {
30        Self { width: w, height: h, data: vec![true; w * h] }
31    }
32
33    /// Rotate prefab 90 degrees clockwise
34    pub fn rotate_90(&self) -> Self {
35        let mut data = vec![false; self.width * self.height];
36        for y in 0..self.height {
37            for x in 0..self.width {
38                let old_idx = y * self.width + x;
39                let new_x = self.height - 1 - y;
40                let new_y = x;
41                let new_idx = new_y * self.height + new_x;
42                data[new_idx] = self.data[old_idx];
43            }
44        }
45        Self { width: self.height, height: self.width, data }
46    }
47
48    /// Rotate prefab 180 degrees
49    pub fn rotate_180(&self) -> Self {
50        let mut data = vec![false; self.width * self.height];
51        for y in 0..self.height {
52            for x in 0..self.width {
53                let old_idx = y * self.width + x;
54                let new_x = self.width - 1 - x;
55                let new_y = self.height - 1 - y;
56                let new_idx = new_y * self.width + new_x;
57                data[new_idx] = self.data[old_idx];
58            }
59        }
60        Self { width: self.width, height: self.height, data }
61    }
62
63    /// Rotate prefab 270 degrees clockwise (90 degrees counter-clockwise)
64    pub fn rotate_270(&self) -> Self {
65        let mut data = vec![false; self.width * self.height];
66        for y in 0..self.height {
67            for x in 0..self.width {
68                let old_idx = y * self.width + x;
69                let new_x = y;
70                let new_y = self.width - 1 - x;
71                let new_idx = new_y * self.height + new_x;
72                data[new_idx] = self.data[old_idx];
73            }
74        }
75        Self { width: self.height, height: self.width, data }
76    }
77}
78
79pub struct PrefabPlacer {
80    config: PrefabConfig,
81    prefabs: Vec<Prefab>,
82}
83
84impl PrefabPlacer {
85    pub fn new(config: PrefabConfig, prefabs: Vec<Prefab>) -> Self { Self { config, prefabs } }
86    pub fn with_prefabs(prefabs: Vec<Prefab>) -> Self { Self::new(PrefabConfig::default(), prefabs) }
87}
88
89impl Default for PrefabPlacer {
90    fn default() -> Self { Self::with_prefabs(vec![Prefab::rect(5, 5)]) }
91}
92
93impl Algorithm<Tile> for PrefabPlacer {
94    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
95        if self.prefabs.is_empty() { return; }
96        let mut rng = Rng::new(seed);
97        let mut placed: Vec<(usize, usize, usize, usize)> = Vec::new();
98
99        for _ in 0..self.config.max_prefabs * 10 {
100            if placed.len() >= self.config.max_prefabs { break; }
101
102            let base_prefab = &self.prefabs[rng.range_usize(0, self.prefabs.len())];
103            
104            // Choose rotation
105            let prefab = if self.config.allow_rotation {
106                match rng.range(0, 4) {
107                    0 => base_prefab.clone(),
108                    1 => base_prefab.rotate_90(),
109                    2 => base_prefab.rotate_180(),
110                    3 => base_prefab.rotate_270(),
111                    _ => unreachable!(),
112                }
113            } else {
114                base_prefab.clone()
115            };
116            
117            if prefab.width + 2 >= grid.width() || prefab.height + 2 >= grid.height() { continue; }
118
119            let x = rng.range_usize(1, grid.width() - prefab.width - 1);
120            let y = rng.range_usize(1, grid.height() - prefab.height - 1);
121
122            let overlaps = placed.iter().any(|&(px, py, pw, ph)| {
123                let s = self.config.min_spacing;
124                !(x + prefab.width + s < px || px + pw + s < x
125                    || y + prefab.height + s < py || py + ph + s < y)
126            });
127
128            if overlaps { continue; }
129
130            for py in 0..prefab.height {
131                for px in 0..prefab.width {
132                    if prefab.data[py * prefab.width + px] {
133                        grid.set((x + px) as i32, (y + py) as i32, Tile::Floor);
134                    }
135                }
136            }
137            placed.push((x, y, prefab.width, prefab.height));
138        }
139    }
140
141    fn name(&self) -> &'static str { "PrefabPlacer" }
142}