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