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