terrain_forge/algorithms/
prefab.rs1use crate::{Algorithm, Grid, Rng, Tile};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::Path;
5
6#[derive(Debug, Clone)]
7pub struct PrefabConfig {
8 pub max_prefabs: usize,
9 pub min_spacing: usize,
10 pub allow_rotation: bool,
11 pub allow_mirroring: bool,
12 pub weighted_selection: bool,
13}
14
15impl Default for PrefabConfig {
16 fn default() -> Self {
17 Self {
18 max_prefabs: 3,
19 min_spacing: 5,
20 allow_rotation: true,
21 allow_mirroring: false,
22 weighted_selection: true,
23 }
24 }
25}
26
27#[derive(Clone, Debug, Serialize, Deserialize)]
28pub struct PrefabData {
29 pub name: String,
30 pub width: usize,
31 pub height: usize,
32 pub pattern: Vec<String>,
33 pub weight: f32,
34 pub tags: Vec<String>,
35}
36
37#[derive(Clone)]
38pub struct Prefab {
39 pub name: String,
40 pub width: usize,
41 pub height: usize,
42 pub data: Vec<bool>,
43 pub weight: f32,
44 pub tags: Vec<String>,
45}
46
47impl Prefab {
48 pub fn new(pattern: &[&str]) -> Self {
49 let height = pattern.len();
50 let width = pattern.first().map(|s| s.len()).unwrap_or(0);
51 let data = pattern
52 .iter()
53 .flat_map(|row| row.chars().map(|c| c == '.'))
54 .collect();
55 Self {
56 name: "unnamed".to_string(),
57 width,
58 height,
59 data,
60 weight: 1.0,
61 tags: Vec::new(),
62 }
63 }
64
65 pub fn from_data(data: PrefabData) -> Self {
66 let width = data.width;
67 let height = data.height;
68 let tiles = data
69 .pattern
70 .iter()
71 .flat_map(|row| row.chars().map(|c| c == '.'))
72 .collect();
73
74 Self {
75 name: data.name,
76 width,
77 height,
78 data: tiles,
79 weight: data.weight,
80 tags: data.tags,
81 }
82 }
83
84 pub fn rect(w: usize, h: usize) -> Self {
85 Self {
86 name: format!("rect_{}x{}", w, h),
87 width: w,
88 height: h,
89 data: vec![true; w * h],
90 weight: 1.0,
91 tags: vec!["rectangle".to_string()],
92 }
93 }
94
95 pub fn rotated(&self) -> Self {
97 let mut rotated_data = vec![false; self.width * self.height];
98 for y in 0..self.height {
99 for x in 0..self.width {
100 let old_idx = y * self.width + x;
101 let new_x = self.height - 1 - y;
102 let new_y = x;
103 let new_idx = new_y * self.height + new_x;
104 rotated_data[new_idx] = self.data[old_idx];
105 }
106 }
107
108 Self {
109 name: format!("{}_rot90", self.name),
110 width: self.height,
111 height: self.width,
112 data: rotated_data,
113 weight: self.weight,
114 tags: self.tags.clone(),
115 }
116 }
117
118 pub fn mirrored_horizontal(&self) -> Self {
120 let mut mirrored_data = vec![false; self.width * self.height];
121 for y in 0..self.height {
122 for x in 0..self.width {
123 let old_idx = y * self.width + x;
124 let new_x = self.width - 1 - x;
125 let new_idx = y * self.width + new_x;
126 mirrored_data[new_idx] = self.data[old_idx];
127 }
128 }
129
130 Self {
131 name: format!("{}_mirror_h", self.name),
132 width: self.width,
133 height: self.height,
134 data: mirrored_data,
135 weight: self.weight,
136 tags: self.tags.clone(),
137 }
138 }
139
140 pub fn mirrored_vertical(&self) -> Self {
142 let mut mirrored_data = vec![false; self.width * self.height];
143 for y in 0..self.height {
144 for x in 0..self.width {
145 let old_idx = y * self.width + x;
146 let new_y = self.height - 1 - y;
147 let new_idx = new_y * self.width + x;
148 mirrored_data[new_idx] = self.data[old_idx];
149 }
150 }
151
152 Self {
153 name: format!("{}_mirror_v", self.name),
154 width: self.width,
155 height: self.height,
156 data: mirrored_data,
157 weight: self.weight,
158 tags: self.tags.clone(),
159 }
160 }
161
162 pub fn get(&self, x: usize, y: usize) -> bool {
163 if x < self.width && y < self.height {
164 self.data[y * self.width + x]
165 } else {
166 false
167 }
168 }
169
170 pub fn has_tag(&self, tag: &str) -> bool {
171 self.tags.contains(&tag.to_string())
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct PrefabLibraryData {
177 pub prefabs: Vec<PrefabData>,
178}
179
180#[derive(Clone)]
181pub struct PrefabLibrary {
182 prefabs: Vec<Prefab>,
183 by_tag: HashMap<String, Vec<usize>>,
184}
185
186impl PrefabLibrary {
187 pub fn new() -> Self {
188 Self {
189 prefabs: Vec::new(),
190 by_tag: HashMap::new(),
191 }
192 }
193
194 pub fn add_prefab(&mut self, prefab: Prefab) {
195 let index = self.prefabs.len();
196
197 for tag in &prefab.tags {
198 self.by_tag.entry(tag.clone()).or_default().push(index);
199 }
200
201 self.prefabs.push(prefab);
202 }
203
204 pub fn load_from_json<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
205 let content = std::fs::read_to_string(path)?;
206 let data: PrefabLibraryData = serde_json::from_str(&content)?;
207
208 let mut library = Self::new();
209 for prefab_data in data.prefabs {
210 library.add_prefab(Prefab::from_data(prefab_data));
211 }
212
213 Ok(library)
214 }
215
216 pub fn save_to_json<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
217 let data = PrefabLibraryData {
218 prefabs: self
219 .prefabs
220 .iter()
221 .map(|p| PrefabData {
222 name: p.name.clone(),
223 width: p.width,
224 height: p.height,
225 pattern: self.prefab_to_pattern(p),
226 weight: p.weight,
227 tags: p.tags.clone(),
228 })
229 .collect(),
230 };
231
232 let content = serde_json::to_string_pretty(&data)?;
233 std::fs::write(path, content)?;
234 Ok(())
235 }
236
237 fn prefab_to_pattern(&self, prefab: &Prefab) -> Vec<String> {
238 let mut pattern = Vec::new();
239 for y in 0..prefab.height {
240 let mut row = String::new();
241 for x in 0..prefab.width {
242 row.push(if prefab.get(x, y) { '.' } else { '#' });
243 }
244 pattern.push(row);
245 }
246 pattern
247 }
248
249 pub fn get_prefabs(&self) -> &[Prefab] {
250 &self.prefabs
251 }
252
253 pub fn get_by_tag(&self, tag: &str) -> Vec<&Prefab> {
254 self.by_tag
255 .get(tag)
256 .map(|indices| indices.iter().map(|&i| &self.prefabs[i]).collect())
257 .unwrap_or_default()
258 }
259
260 pub fn select_weighted(&self, rng: &mut Rng, tag: Option<&str>) -> Option<&Prefab> {
261 let candidates = if let Some(tag) = tag {
262 self.get_by_tag(tag)
263 } else {
264 self.prefabs.iter().collect()
265 };
266
267 if candidates.is_empty() {
268 return None;
269 }
270
271 let total_weight: f32 = candidates.iter().map(|p| p.weight).sum();
272 if total_weight <= 0.0 {
273 return rng.pick(&candidates).copied();
274 }
275
276 let mut target = rng.random() as f32 * total_weight;
277 for prefab in &candidates {
278 target -= prefab.weight;
279 if target <= 0.0 {
280 return Some(prefab);
281 }
282 }
283
284 candidates.last().copied()
285 }
286
287 pub fn create_default() -> Self {
288 let mut library = Self::new();
289
290 let mut room = Prefab::rect(5, 5);
292 room.name = "small_room".to_string();
293 room.tags = vec!["room".to_string(), "small".to_string()];
294 library.add_prefab(room);
295
296 let mut corridor = Prefab::new(&[".....", "#####"]);
297 corridor.name = "corridor".to_string();
298 corridor.tags = vec!["corridor".to_string()];
299 library.add_prefab(corridor);
300
301 let mut cross = Prefab::new(&["#.#", "...", "#.#"]);
302 cross.name = "cross".to_string();
303 cross.tags = vec!["junction".to_string()];
304 library.add_prefab(cross);
305
306 library
307 }
308}
309
310impl Default for PrefabLibrary {
311 fn default() -> Self {
312 Self::create_default()
313 }
314}
315
316#[derive(Debug, Clone, Default)]
317pub struct PrefabTransform {
318 pub rotation: u8, pub mirror_h: bool,
320 pub mirror_v: bool,
321}
322
323impl PrefabTransform {
324 pub fn apply(&self, prefab: &Prefab) -> Prefab {
325 let mut result = prefab.clone();
326
327 if self.mirror_h {
329 result = result.mirrored_horizontal();
330 }
331 if self.mirror_v {
332 result = result.mirrored_vertical();
333 }
334
335 for _ in 0..self.rotation {
337 result = result.rotated();
338 }
339
340 result
341 }
342
343 pub fn random(rng: &mut Rng, allow_rotation: bool, allow_mirroring: bool) -> Self {
344 Self {
345 rotation: if allow_rotation {
346 rng.range(0, 4) as u8
347 } else {
348 0
349 },
350 mirror_h: allow_mirroring && rng.chance(0.5),
351 mirror_v: allow_mirroring && rng.chance(0.5),
352 }
353 }
354}
355pub struct PrefabPlacer {
356 config: PrefabConfig,
357 library: PrefabLibrary,
358}
359
360impl PrefabPlacer {
361 pub fn new(config: PrefabConfig, library: PrefabLibrary) -> Self {
362 Self { config, library }
363 }
364
365 pub fn with_library(library: PrefabLibrary) -> Self {
366 Self::new(PrefabConfig::default(), library)
367 }
368}
369
370impl Default for PrefabPlacer {
371 fn default() -> Self {
372 Self::with_library(PrefabLibrary::default())
373 }
374}
375
376impl Algorithm<Tile> for PrefabPlacer {
377 fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
378 let mut rng = Rng::new(seed);
379 let mut placed: Vec<(usize, usize, usize, usize)> = Vec::new();
380
381 for _ in 0..self.config.max_prefabs * 10 {
382 if placed.len() >= self.config.max_prefabs {
383 break;
384 }
385
386 let base_prefab = if let Some(prefab) = self.library.select_weighted(&mut rng, None) {
387 prefab
388 } else {
389 continue;
390 };
391
392 let transform = PrefabTransform::random(
394 &mut rng,
395 self.config.allow_rotation,
396 self.config.allow_mirroring,
397 );
398 let prefab = transform.apply(base_prefab);
399
400 if prefab.width + 2 >= grid.width() || prefab.height + 2 >= grid.height() {
401 continue;
402 }
403
404 let x = rng.range_usize(1, grid.width() - prefab.width - 1);
405 let y = rng.range_usize(1, grid.height() - prefab.height - 1);
406
407 let overlaps = placed.iter().any(|&(px, py, pw, ph)| {
408 let s = self.config.min_spacing;
409 !(x + prefab.width + s < px
410 || px + pw + s < x
411 || y + prefab.height + s < py
412 || py + ph + s < y)
413 });
414
415 if overlaps {
416 continue;
417 }
418
419 for py in 0..prefab.height {
420 for px in 0..prefab.width {
421 if prefab.get(px, py) {
422 grid.set((x + px) as i32, (y + py) as i32, Tile::Floor);
423 }
424 }
425 }
426 placed.push((x, y, prefab.width, prefab.height));
427 }
428 }
429
430 fn name(&self) -> &'static str {
431 "PrefabPlacer"
432 }
433}