terrain_forge/algorithms/
prefab.rs1use 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}