terrain_forge/algorithms/
prefab.rs

1use 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    /// Rotate prefab 90 degrees clockwise
96    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    /// Mirror prefab horizontally
119    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    /// Mirror prefab vertically
141    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        // Add some basic prefabs
291        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, // 0, 1, 2, 3 for 0°, 90°, 180°, 270°
319    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        // Apply mirroring first
328        if self.mirror_h {
329            result = result.mirrored_horizontal();
330        }
331        if self.mirror_v {
332            result = result.mirrored_vertical();
333        }
334
335        // Apply rotation
336        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            // Apply random transformations
393            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}