terrain_forge/
semantic.rs

1//! Semantic layers for procedural generation
2//! 
3//! Provides region metadata, spawn markers, and connectivity information
4//! alongside tile generation for game integration.
5
6use crate::{Grid, Tile, Rng};
7use std::collections::HashMap;
8
9/// A distinct region within the generated map
10#[derive(Debug, Clone)]
11pub struct Region {
12    pub id: u32,
13    pub kind: String,
14    pub cells: Vec<(u32, u32)>,
15    pub tags: Vec<String>,
16}
17
18/// A spawn marker for entity placement
19#[derive(Debug, Clone)]
20pub struct Marker {
21    pub x: u32,
22    pub y: u32,
23    pub tag: String,
24    pub weight: f32,
25    pub region_id: Option<u32>,
26    pub metadata: HashMap<String, String>,
27}
28
29/// Spatial masks for gameplay logic
30#[derive(Debug, Clone)]
31pub struct Masks {
32    pub walkable: Vec<Vec<bool>>,
33    pub no_spawn: Vec<Vec<bool>>,
34    pub width: usize,
35    pub height: usize,
36}
37
38/// Region connectivity information
39#[derive(Debug, Clone)]
40pub struct ConnectivityGraph {
41    pub regions: Vec<u32>,
42    pub edges: Vec<(u32, u32)>,
43}
44
45/// Complete semantic information for a generated map
46#[derive(Debug, Clone)]
47pub struct SemanticLayers {
48    pub regions: Vec<Region>,
49    pub markers: Vec<Marker>,
50    pub masks: Masks,
51    pub connectivity: ConnectivityGraph,
52}
53
54/// Extended generation result with semantic information
55#[derive(Debug)]
56pub struct GenerationResult {
57    pub tiles: Grid<Tile>,
58    pub semantic: Option<SemanticLayers>,
59}
60
61/// Trait for algorithms that can generate semantic information
62pub trait SemanticGenerator<T: crate::grid::Cell> {
63    /// Generate semantic layers for the given grid
64    fn generate_semantic(&self, grid: &Grid<T>, rng: &mut Rng) -> SemanticLayers;
65}
66
67impl GenerationResult {
68    pub fn new(tiles: Grid<Tile>) -> Self {
69        Self { tiles, semantic: None }
70    }
71    
72    pub fn with_semantic(tiles: Grid<Tile>, semantic: SemanticLayers) -> Self {
73        Self { tiles, semantic: Some(semantic) }
74    }
75}
76
77impl Region {
78    pub fn new(id: u32, kind: impl Into<String>) -> Self {
79        Self {
80            id,
81            kind: kind.into(),
82            cells: Vec::new(),
83            tags: Vec::new(),
84        }
85    }
86    
87    pub fn add_cell(&mut self, x: u32, y: u32) {
88        self.cells.push((x, y));
89    }
90    
91    pub fn add_tag(&mut self, tag: impl Into<String>) {
92        self.tags.push(tag.into());
93    }
94    
95    pub fn area(&self) -> usize {
96        self.cells.len()
97    }
98}
99
100impl Marker {
101    pub fn new(x: u32, y: u32, tag: impl Into<String>) -> Self {
102        Self {
103            x, y,
104            tag: tag.into(),
105            weight: 1.0,
106            region_id: None,
107            metadata: HashMap::new(),
108        }
109    }
110    
111    pub fn with_weight(mut self, weight: f32) -> Self {
112        self.weight = weight;
113        self
114    }
115    
116    pub fn with_region(mut self, region_id: u32) -> Self {
117        self.region_id = Some(region_id);
118        self
119    }
120    
121    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
122        self.metadata.insert(key.into(), value.into());
123        self
124    }
125}
126
127impl Masks {
128    pub fn new(width: usize, height: usize) -> Self {
129        Self {
130            walkable: vec![vec![false; width]; height],
131            no_spawn: vec![vec![false; width]; height],
132            width,
133            height,
134        }
135    }
136    
137    pub fn from_tiles(tiles: &Grid<Tile>) -> Self {
138        let mut masks = Self::new(tiles.width(), tiles.height());
139        
140        for y in 0..tiles.height() {
141            for x in 0..tiles.width() {
142                let walkable = tiles.get(x as i32, y as i32).map_or(false, |t| t.is_floor());
143                masks.walkable[y][x] = walkable;
144            }
145        }
146        
147        masks
148    }
149}
150
151impl ConnectivityGraph {
152    pub fn new() -> Self {
153        Self {
154            regions: Vec::new(),
155            edges: Vec::new(),
156        }
157    }
158    
159    pub fn add_region(&mut self, id: u32) {
160        if !self.regions.contains(&id) {
161            self.regions.push(id);
162        }
163    }
164    
165    pub fn add_edge(&mut self, from: u32, to: u32) {
166        self.add_region(from);
167        self.add_region(to);
168        
169        if !self.edges.contains(&(from, to)) && !self.edges.contains(&(to, from)) {
170            self.edges.push((from, to));
171        }
172    }
173}
174
175impl Default for ConnectivityGraph {
176    fn default() -> Self {
177        Self::new()
178    }
179}
180
181/// Utility functions for marker placement
182pub mod placement {
183    use super::*;
184    use crate::effects;
185    
186    /// Place markers in regions using simple distribution
187    pub fn distribute_markers(
188        regions: &[Region], 
189        tag: &str, 
190        total: usize,
191        rng: &mut Rng
192    ) -> Vec<Marker> {
193        if regions.is_empty() || total == 0 {
194            return Vec::new();
195        }
196        
197        let mut markers = Vec::new();
198        let total_area: usize = regions.iter().map(|r| r.area()).sum();
199        
200        for region in regions {
201            let region_markers = if total_area > 0 {
202                (total * region.area()) / total_area
203            } else {
204                total / regions.len()
205            };
206            
207            for _ in 0..region_markers {
208                if let Some(&(x, y)) = region.cells.get(rng.range_usize(0, region.cells.len())) {
209                    markers.push(
210                        Marker::new(x, y, tag)
211                            .with_region(region.id)
212                    );
213                }
214            }
215        }
216        
217        markers
218    }
219    
220    /// Extract regions from a grid using flood fill
221    pub fn extract_regions(grid: &Grid<Tile>) -> Vec<Region> {
222        let (labels, count) = effects::label_regions(grid);
223        let mut regions = Vec::new();
224        
225        for region_id in 1..=count {
226            let mut region = Region::new(region_id, "unknown");
227            
228            for y in 0..grid.height() {
229                for x in 0..grid.width() {
230                    let idx = y * grid.width() + x;
231                    if labels.get(idx).copied().unwrap_or(0) == region_id {
232                        region.add_cell(x as u32, y as u32);
233                    }
234                }
235            }
236            
237            if !region.cells.is_empty() {
238                regions.push(region);
239            }
240        }
241        
242        regions
243    }
244    
245    /// Build connectivity graph from regions
246    pub fn build_connectivity(grid: &Grid<Tile>, regions: &[Region]) -> ConnectivityGraph {
247        let mut graph = ConnectivityGraph::new();
248        
249        // Add all regions to graph
250        for region in regions {
251            graph.add_region(region.id);
252        }
253        
254        // Check adjacency between regions
255        for i in 0..regions.len() {
256            for j in (i + 1)..regions.len() {
257                if are_regions_adjacent(grid, &regions[i], &regions[j]) {
258                    graph.add_edge(regions[i].id, regions[j].id);
259                }
260            }
261        }
262        
263        graph
264    }
265    
266    /// Check if two regions are adjacent (share a border)
267    fn are_regions_adjacent(_grid: &Grid<Tile>, region1: &Region, region2: &Region) -> bool {
268        for &(x1, y1) in &region1.cells {
269            for &(x2, y2) in &region2.cells {
270                let dx = (x1 as i32 - x2 as i32).abs();
271                let dy = (y1 as i32 - y2 as i32).abs();
272                
273                // Adjacent if Manhattan distance is 1 (orthogonally adjacent)
274                if (dx == 1 && dy == 0) || (dx == 0 && dy == 1) {
275                    return true;
276                }
277            }
278        }
279        false
280    }
281}