1use crate::semantic::{
8 ConnectivityGraph, Marker, MarkerType, Masks, Region, SemanticConfig, SemanticLayers,
9};
10use crate::{Grid, Rng, Tile};
11use std::collections::HashMap;
12
13pub struct SemanticExtractor {
15 config: SemanticConfig,
16}
17
18impl SemanticExtractor {
19 pub fn new(config: SemanticConfig) -> Self {
21 Self { config }
22 }
23
24 pub fn for_caves() -> Self {
26 Self::new(SemanticConfig::cave_system())
27 }
28
29 pub fn for_rooms() -> Self {
31 Self::new(SemanticConfig::room_system())
32 }
33
34 pub fn for_mazes() -> Self {
36 Self::new(SemanticConfig::maze_system())
37 }
38
39 pub fn extract(&self, grid: &Grid<Tile>, rng: &mut Rng) -> SemanticLayers {
41 let mut regions = self.extract_regions(grid);
43
44 self.classify_regions(&mut regions);
46
47 let markers = self.generate_markers(®ions, rng);
49
50 let masks = Masks::from_tiles(grid);
52
53 let connectivity = self.build_connectivity(grid, ®ions);
55
56 SemanticLayers {
57 regions,
58 markers,
59 masks,
60 connectivity,
61 }
62 }
63
64 fn extract_regions(&self, grid: &Grid<Tile>) -> Vec<Region> {
66 let (labels, count) = crate::effects::label_regions(grid);
67 let mut regions = Vec::new();
68 let width = grid.width();
69
70 for region_id in 1..=count {
71 let mut region = Region::new(region_id, "Unknown");
72
73 for (x, y, _) in grid.iter() {
75 let index = y * width + x;
76 if labels[index] == region_id {
77 region.add_cell(x as u32, y as u32);
78 }
79 }
80
81 if !region.cells.is_empty() {
82 regions.push(region);
83 }
84 }
85
86 regions
87 }
88
89 fn classify_regions(&self, regions: &mut [Region]) {
91 for region in regions {
92 let size = region.cells.len();
93
94 region.kind = self
96 .config
97 .size_thresholds
98 .iter()
99 .find(|(threshold, _)| size >= *threshold)
100 .map(|(_, name)| name.clone())
101 .unwrap_or_else(|| "Unknown".to_string());
102 }
103 }
104
105 fn generate_markers(&self, regions: &[Region], rng: &mut Rng) -> Vec<Marker> {
107 let mut markers = Vec::new();
108
109 for region in regions {
110 let marker_count = (self.config.max_markers_per_region as f32
111 * (region.cells.len() as f32 / self.config.marker_scaling_factor).min(1.0))
112 as usize;
113
114 for _ in 0..marker_count {
115 if let Some((marker_type, weight)) = rng.pick(&self.config.marker_types) {
116 if rng.random() < (*weight as f64) {
117 if let Some(position) = self.find_marker_position(region, &markers, rng) {
118 markers.push(
119 Marker::new(
120 position.0,
121 position.1,
122 MarkerType::Custom(marker_type.clone()),
123 )
124 .with_region(region.id)
125 .with_weight(*weight),
126 );
127 }
128 }
129 }
130 }
131 }
132
133 markers
134 }
135
136 fn find_marker_position(
138 &self,
139 region: &Region,
140 existing_markers: &[Marker],
141 rng: &mut Rng,
142 ) -> Option<(u32, u32)> {
143 use crate::semantic::PlacementStrategy;
144
145 let candidates: Vec<(u32, u32)> = match self.config.marker_placement.strategy {
146 PlacementStrategy::Random => region.cells.clone(),
147 PlacementStrategy::Center => {
148 if let Some(center) = self.find_region_center(region) {
149 vec![center]
150 } else {
151 region.cells.clone()
152 }
153 }
154 PlacementStrategy::Edges => self.find_edge_positions(region),
155 PlacementStrategy::Corners => self.find_corner_positions(region),
156 };
157
158 let valid_candidates: Vec<_> = candidates
160 .into_iter()
161 .filter(|&pos| self.is_valid_marker_position(pos, existing_markers))
162 .collect();
163
164 rng.pick(&valid_candidates).copied()
165 }
166
167 fn is_valid_marker_position(&self, pos: (u32, u32), existing_markers: &[Marker]) -> bool {
169 let min_dist = self.config.marker_placement.min_marker_distance as f32;
170
171 for marker in existing_markers {
172 let dx = pos.0 as f32 - marker.x as f32;
173 let dy = pos.1 as f32 - marker.y as f32;
174 let distance = (dx * dx + dy * dy).sqrt();
175
176 if distance < min_dist {
177 return false;
178 }
179 }
180
181 true
182 }
183
184 fn find_region_center(&self, region: &Region) -> Option<(u32, u32)> {
186 if region.cells.is_empty() {
187 return None;
188 }
189
190 let sum_x: u32 = region.cells.iter().map(|(x, _)| x).sum();
191 let sum_y: u32 = region.cells.iter().map(|(_, y)| y).sum();
192 let count = region.cells.len() as u32;
193
194 Some((sum_x / count, sum_y / count))
195 }
196
197 fn find_edge_positions(&self, region: &Region) -> Vec<(u32, u32)> {
199 region.cells.clone() }
203
204 fn find_corner_positions(&self, region: &Region) -> Vec<(u32, u32)> {
206 if region.cells.len() < 4 {
207 return region.cells.clone();
208 }
209
210 let min_x = region.cells.iter().map(|(x, _)| x).min().unwrap();
212 let max_x = region.cells.iter().map(|(x, _)| x).max().unwrap();
213 let min_y = region.cells.iter().map(|(_, y)| y).min().unwrap();
214 let max_y = region.cells.iter().map(|(_, y)| y).max().unwrap();
215
216 vec![
217 (*min_x, *min_y),
218 (*max_x, *min_y),
219 (*min_x, *max_y),
220 (*max_x, *max_y),
221 ]
222 .into_iter()
223 .filter(|pos| region.cells.contains(pos))
224 .collect()
225 }
226
227 fn build_connectivity(&self, grid: &Grid<Tile>, regions: &[Region]) -> ConnectivityGraph {
229 let mut graph = ConnectivityGraph::new();
230
231 for region in regions {
233 graph.add_region(region.id);
234 }
235
236 let region_map = self.create_region_map(grid, regions);
238
239 for region in regions {
240 for &(x, y) in ®ion.cells {
241 let neighbors = match self.config.connectivity_type {
243 crate::semantic::ConnectivityType::FourConnected => {
244 vec![(0, 1), (1, 0), (0, -1), (-1, 0)]
245 }
246 crate::semantic::ConnectivityType::EightConnected => {
247 vec![
248 (0, 1),
249 (1, 0),
250 (0, -1),
251 (-1, 0),
252 (1, 1),
253 (1, -1),
254 (-1, 1),
255 (-1, -1),
256 ]
257 }
258 };
259
260 for (dx, dy) in neighbors {
261 let nx = x as i32 + dx;
262 let ny = y as i32 + dy;
263
264 if let Some(neighbor_region) = region_map.get(&(nx, ny)) {
265 if *neighbor_region != region.id {
266 graph.add_edge(region.id, *neighbor_region);
267 }
268 }
269 }
270 }
271 }
272
273 graph
274 }
275
276 fn create_region_map(
278 &self,
279 _grid: &Grid<Tile>,
280 regions: &[Region],
281 ) -> HashMap<(i32, i32), u32> {
282 let mut map = HashMap::new();
283
284 for region in regions {
285 for &(x, y) in ®ion.cells {
286 map.insert((x as i32, y as i32), region.id);
287 }
288 }
289
290 map
291 }
292}
293
294impl Default for SemanticExtractor {
295 fn default() -> Self {
296 Self::new(SemanticConfig::default())
297 }
298}
299
300pub fn extract_semantics(grid: &Grid<Tile>, config: SemanticConfig, seed: u64) -> SemanticLayers {
302 let mut rng = Rng::new(seed);
303 let extractor = SemanticExtractor::new(config);
304 extractor.extract(grid, &mut rng)
305}
306
307pub fn extract_semantics_default(grid: &Grid<Tile>, seed: u64) -> SemanticLayers {
309 extract_semantics(grid, SemanticConfig::default(), seed)
310}