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};
7use std::collections::HashMap;
8
9/// Configuration for semantic layer generation
10#[derive(Debug, Clone)]
11pub struct SemanticConfig {
12    /// Size thresholds for region classification
13    pub size_thresholds: Vec<(usize, String)>,
14    /// Marker types to generate with their weights
15    pub marker_types: Vec<(String, f32)>,
16    /// Maximum number of markers per region type
17    pub max_markers_per_region: usize,
18    /// Region size scaling factor for marker density (default: 100.0)
19    pub marker_scaling_factor: f32,
20    /// Connectivity analysis type
21    pub connectivity_type: ConnectivityType,
22    /// Advanced region analysis options
23    pub region_analysis: RegionAnalysisConfig,
24    /// Marker placement strategy
25    pub marker_placement: MarkerPlacementConfig,
26}
27
28/// Type of connectivity analysis to perform
29#[derive(Debug, Clone)]
30pub enum ConnectivityType {
31    /// 4-connected (orthogonal neighbors only)
32    FourConnected,
33    /// 8-connected (includes diagonal neighbors)
34    EightConnected,
35}
36
37/// Configuration for advanced region analysis
38#[derive(Debug, Clone)]
39pub struct RegionAnalysisConfig {
40    /// Enable shape analysis (aspect ratio, compactness)
41    pub analyze_shape: bool,
42    /// Enable connectivity pattern analysis
43    pub analyze_connectivity_patterns: bool,
44    /// Minimum region size for detailed analysis
45    pub min_analysis_size: usize,
46}
47
48/// Configuration for marker placement strategies
49#[derive(Debug, Clone)]
50pub struct MarkerPlacementConfig {
51    /// Placement strategy for markers
52    pub strategy: PlacementStrategy,
53    /// Minimum distance between markers of same type
54    pub min_marker_distance: usize,
55    /// Avoid placing markers near walls
56    pub avoid_walls: bool,
57}
58
59/// Marker placement strategies
60#[derive(Debug, Clone)]
61pub enum PlacementStrategy {
62    /// Random placement within region
63    Random,
64    /// Place at region center
65    Center,
66    /// Place near region edges
67    Edges,
68    /// Place in corners/extremes
69    Corners,
70}
71
72impl SemanticConfig {
73    /// Configuration optimized for cave systems (Cellular Automata)
74    pub fn cave_system() -> Self {
75        Self {
76            size_thresholds: vec![
77                (80, "Chamber".to_string()),
78                (25, "Tunnel".to_string()),
79                (5, "Alcove".to_string()),
80                (0, "Crevice".to_string()),
81            ],
82            marker_types: vec![
83                ("PlayerStart".to_string(), 1.0),
84                ("Exit".to_string(), 0.8),
85                ("Treasure".to_string(), 0.4),
86                ("Enemy".to_string(), 0.6),
87                ("Crystal".to_string(), 0.2),
88            ],
89            max_markers_per_region: 2,
90            marker_scaling_factor: 80.0, // Caves tend to be larger
91            connectivity_type: ConnectivityType::EightConnected, // Natural cave connections
92            region_analysis: RegionAnalysisConfig {
93                analyze_shape: true, // Cave shape matters
94                analyze_connectivity_patterns: true,
95                min_analysis_size: 15,
96            },
97            marker_placement: MarkerPlacementConfig {
98                strategy: PlacementStrategy::Random,
99                min_marker_distance: 5,
100                avoid_walls: true,
101            },
102        }
103    }
104
105    /// Configuration optimized for structured rooms
106    pub fn room_system() -> Self {
107        Self {
108            size_thresholds: vec![
109                (150, "Hall".to_string()),
110                (50, "Room".to_string()),
111                (15, "Chamber".to_string()),
112                (0, "Closet".to_string()),
113            ],
114            marker_types: vec![
115                ("PlayerStart".to_string(), 1.0),
116                ("Exit".to_string(), 1.0),
117                ("Treasure".to_string(), 0.3),
118                ("Enemy".to_string(), 0.4),
119                ("Furniture".to_string(), 0.7),
120            ],
121            max_markers_per_region: 4,
122            marker_scaling_factor: 60.0, // Rooms are more compact
123            connectivity_type: ConnectivityType::FourConnected, // Structured connections
124            region_analysis: RegionAnalysisConfig {
125                analyze_shape: true, // Room rectangularity matters
126                analyze_connectivity_patterns: false,
127                min_analysis_size: 8,
128            },
129            marker_placement: MarkerPlacementConfig {
130                strategy: PlacementStrategy::Center, // Furniture in room centers
131                min_marker_distance: 4,
132                avoid_walls: true,
133            },
134        }
135    }
136
137    /// Configuration optimized for maze systems
138    pub fn maze_system() -> Self {
139        Self {
140            size_thresholds: vec![
141                (50, "Junction".to_string()),
142                (10, "Corridor".to_string()),
143                (0, "DeadEnd".to_string()),
144            ],
145            marker_types: vec![
146                ("PlayerStart".to_string(), 1.0),
147                ("Exit".to_string(), 1.0),
148                ("Treasure".to_string(), 0.1),
149                ("Trap".to_string(), 0.3),
150            ],
151            max_markers_per_region: 1,
152            marker_scaling_factor: 30.0, // Mazes have smaller regions
153            connectivity_type: ConnectivityType::FourConnected, // Maze structure
154            region_analysis: RegionAnalysisConfig {
155                analyze_shape: false,
156                analyze_connectivity_patterns: true, // Junction analysis important
157                min_analysis_size: 5,
158            },
159            marker_placement: MarkerPlacementConfig {
160                strategy: PlacementStrategy::Corners, // Traps in corners
161                min_marker_distance: 8,
162                avoid_walls: false, // Maze walls are part of structure
163            },
164        }
165    }
166}
167
168impl Default for SemanticConfig {
169    fn default() -> Self {
170        Self {
171            size_thresholds: vec![
172                (100, "Large".to_string()),
173                (25, "Medium".to_string()),
174                (5, "Small".to_string()),
175                (0, "Tiny".to_string()),
176            ],
177            marker_types: vec![
178                ("PlayerStart".to_string(), 1.0),
179                ("Exit".to_string(), 1.0),
180                ("Treasure".to_string(), 0.3),
181                ("Enemy".to_string(), 0.5),
182            ],
183            max_markers_per_region: 3,
184            marker_scaling_factor: 100.0,
185            connectivity_type: ConnectivityType::FourConnected,
186            region_analysis: RegionAnalysisConfig {
187                analyze_shape: false,
188                analyze_connectivity_patterns: false,
189                min_analysis_size: 10,
190            },
191            marker_placement: MarkerPlacementConfig {
192                strategy: PlacementStrategy::Random,
193                min_marker_distance: 3,
194                avoid_walls: true,
195            },
196        }
197    }
198}
199
200/// A distinct region within the generated map
201#[derive(Debug, Clone)]
202pub struct Region {
203    pub id: u32,
204    pub kind: String,
205    pub cells: Vec<(u32, u32)>,
206    pub tags: Vec<String>,
207}
208
209/// A spawn marker for entity placement
210#[derive(Debug, Clone)]
211pub struct Marker {
212    pub x: u32,
213    pub y: u32,
214    pub tag: String,
215    pub weight: f32,
216    pub region_id: Option<u32>,
217    pub metadata: HashMap<String, String>,
218}
219
220/// Spatial masks for gameplay logic
221#[derive(Debug, Clone)]
222pub struct Masks {
223    pub walkable: Vec<Vec<bool>>,
224    pub no_spawn: Vec<Vec<bool>>,
225    pub width: usize,
226    pub height: usize,
227}
228
229/// Region connectivity information
230#[derive(Debug, Clone)]
231pub struct ConnectivityGraph {
232    pub regions: Vec<u32>,
233    pub edges: Vec<(u32, u32)>,
234}
235
236/// Complete semantic information for a generated map
237#[derive(Debug, Clone)]
238pub struct SemanticLayers {
239    pub regions: Vec<Region>,
240    pub markers: Vec<Marker>,
241    pub masks: Masks,
242    pub connectivity: ConnectivityGraph,
243}
244
245impl Region {
246    pub fn new(id: u32, kind: impl Into<String>) -> Self {
247        Self {
248            id,
249            kind: kind.into(),
250            cells: Vec::new(),
251            tags: Vec::new(),
252        }
253    }
254
255    pub fn add_cell(&mut self, x: u32, y: u32) {
256        self.cells.push((x, y));
257    }
258
259    pub fn add_tag(&mut self, tag: impl Into<String>) {
260        self.tags.push(tag.into());
261    }
262
263    pub fn area(&self) -> usize {
264        self.cells.len()
265    }
266}
267
268impl Marker {
269    pub fn new(x: u32, y: u32, tag: impl Into<String>) -> Self {
270        Self {
271            x,
272            y,
273            tag: tag.into(),
274            weight: 1.0,
275            region_id: None,
276            metadata: HashMap::new(),
277        }
278    }
279
280    pub fn with_weight(mut self, weight: f32) -> Self {
281        self.weight = weight;
282        self
283    }
284
285    pub fn with_region(mut self, region_id: u32) -> Self {
286        self.region_id = Some(region_id);
287        self
288    }
289
290    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
291        self.metadata.insert(key.into(), value.into());
292        self
293    }
294}
295
296impl Masks {
297    pub fn new(width: usize, height: usize) -> Self {
298        Self {
299            walkable: vec![vec![false; width]; height],
300            no_spawn: vec![vec![false; width]; height],
301            width,
302            height,
303        }
304    }
305
306    pub fn from_tiles(tiles: &Grid<Tile>) -> Self {
307        let mut masks = Self::new(tiles.width(), tiles.height());
308
309        for y in 0..tiles.height() {
310            for x in 0..tiles.width() {
311                let walkable = tiles.get(x as i32, y as i32).is_some_and(|t| t.is_floor());
312                masks.walkable[y][x] = walkable;
313            }
314        }
315
316        masks
317    }
318}
319
320impl ConnectivityGraph {
321    pub fn new() -> Self {
322        Self {
323            regions: Vec::new(),
324            edges: Vec::new(),
325        }
326    }
327
328    pub fn add_region(&mut self, id: u32) {
329        if !self.regions.contains(&id) {
330            self.regions.push(id);
331        }
332    }
333
334    pub fn add_edge(&mut self, from: u32, to: u32) {
335        self.add_region(from);
336        self.add_region(to);
337
338        if !self.edges.contains(&(from, to)) && !self.edges.contains(&(to, from)) {
339            self.edges.push((from, to));
340        }
341    }
342}
343
344impl Default for ConnectivityGraph {
345    fn default() -> Self {
346        Self::new()
347    }
348}