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}
8
9impl Default for PrefabConfig {
10    fn default() -> Self { Self { max_prefabs: 3, min_spacing: 5 } }
11}
12
13#[derive(Clone)]
14pub struct Prefab {
15    pub width: usize,
16    pub height: usize,
17    pub data: Vec<bool>,
18}
19
20impl Prefab {
21    pub fn new(pattern: &[&str]) -> Self {
22        let height = pattern.len();
23        let width = pattern.first().map(|s| s.len()).unwrap_or(0);
24        let data = pattern.iter().flat_map(|row| row.chars().map(|c| c == '.')).collect();
25        Self { width, height, data }
26    }
27
28    pub fn rect(w: usize, h: usize) -> Self {
29        Self { width: w, height: h, data: vec![true; w * h] }
30    }
31}
32
33pub struct PrefabPlacer {
34    config: PrefabConfig,
35    prefabs: Vec<Prefab>,
36}
37
38impl PrefabPlacer {
39    pub fn new(config: PrefabConfig, prefabs: Vec<Prefab>) -> Self { Self { config, prefabs } }
40    pub fn with_prefabs(prefabs: Vec<Prefab>) -> Self { Self::new(PrefabConfig::default(), prefabs) }
41}
42
43impl Default for PrefabPlacer {
44    fn default() -> Self { Self::with_prefabs(vec![Prefab::rect(5, 5)]) }
45}
46
47impl Algorithm<Tile> for PrefabPlacer {
48    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
49        if self.prefabs.is_empty() { return; }
50        let mut rng = Rng::new(seed);
51        let mut placed: Vec<(usize, usize, usize, usize)> = Vec::new();
52
53        for _ in 0..self.config.max_prefabs * 10 {
54            if placed.len() >= self.config.max_prefabs { break; }
55
56            let prefab = &self.prefabs[rng.range_usize(0, self.prefabs.len())];
57            if prefab.width + 2 >= grid.width() || prefab.height + 2 >= grid.height() { continue; }
58
59            let x = rng.range_usize(1, grid.width() - prefab.width - 1);
60            let y = rng.range_usize(1, grid.height() - prefab.height - 1);
61
62            let overlaps = placed.iter().any(|&(px, py, pw, ph)| {
63                let s = self.config.min_spacing;
64                !(x + prefab.width + s < px || px + pw + s < x
65                    || y + prefab.height + s < py || py + ph + s < y)
66            });
67
68            if overlaps { continue; }
69
70            for py in 0..prefab.height {
71                for px in 0..prefab.width {
72                    if prefab.data[py * prefab.width + px] {
73                        grid.set((x + px) as i32, (y + py) as i32, Tile::Floor);
74                    }
75                }
76            }
77            placed.push((x, y, prefab.width, prefab.height));
78        }
79    }
80
81    fn name(&self) -> &'static str { "PrefabPlacer" }
82}