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 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 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 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 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 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}