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