use crate::semantic::{
ConnectivityGraph, Marker, MarkerType, Masks, Region, SemanticConfig, SemanticLayers,
};
use crate::{Grid, Rng, Tile};
use std::collections::HashMap;
pub struct SemanticExtractor {
config: SemanticConfig,
}
impl SemanticExtractor {
pub fn new(config: SemanticConfig) -> Self {
Self { config }
}
pub fn for_caves() -> Self {
Self::new(SemanticConfig::cave_system())
}
pub fn for_rooms() -> Self {
Self::new(SemanticConfig::room_system())
}
pub fn for_mazes() -> Self {
Self::new(SemanticConfig::maze_system())
}
pub fn extract(&self, grid: &Grid<Tile>, rng: &mut Rng) -> SemanticLayers {
let mut regions = self.extract_regions(grid);
self.classify_regions(&mut regions);
let markers = self.generate_markers(®ions, rng);
let masks = Masks::from_tiles(grid);
let connectivity = self.build_connectivity(grid, ®ions);
SemanticLayers {
regions,
markers,
masks,
connectivity,
}
}
fn extract_regions(&self, grid: &Grid<Tile>) -> Vec<Region> {
let (labels, count) = crate::effects::label_regions(grid);
let mut regions = Vec::new();
let width = grid.width();
for region_id in 1..=count {
let mut region = Region::new(region_id, "Unknown");
for (x, y, _) in grid.iter() {
let index = y * width + x;
if labels[index] == region_id {
region.add_cell(x as u32, y as u32);
}
}
if !region.cells.is_empty() {
regions.push(region);
}
}
regions
}
fn classify_regions(&self, regions: &mut [Region]) {
for region in regions {
let size = region.cells.len();
region.kind = self
.config
.size_thresholds
.iter()
.find(|(threshold, _)| size >= *threshold)
.map(|(_, name)| name.clone())
.unwrap_or_else(|| "Unknown".to_string());
}
}
fn generate_markers(&self, regions: &[Region], rng: &mut Rng) -> Vec<Marker> {
let mut markers = Vec::new();
for region in regions {
let marker_count = (self.config.max_markers_per_region as f32
* (region.cells.len() as f32 / self.config.marker_scaling_factor).min(1.0))
as usize;
for _ in 0..marker_count {
if let Some((marker_type, weight)) = rng.pick(&self.config.marker_types) {
if rng.random() < (*weight as f64) {
if let Some(position) = self.find_marker_position(region, &markers, rng) {
markers.push(
Marker::new(
position.0,
position.1,
MarkerType::Custom(marker_type.clone()),
)
.with_region(region.id)
.with_weight(*weight),
);
}
}
}
}
}
markers
}
fn find_marker_position(
&self,
region: &Region,
existing_markers: &[Marker],
rng: &mut Rng,
) -> Option<(u32, u32)> {
use crate::semantic::PlacementStrategy;
let candidates: Vec<(u32, u32)> = match self.config.marker_placement.strategy {
PlacementStrategy::Random => region.cells.clone(),
PlacementStrategy::Center => {
if let Some(center) = self.find_region_center(region) {
vec![center]
} else {
region.cells.clone()
}
}
PlacementStrategy::Edges => self.find_edge_positions(region),
PlacementStrategy::Corners => self.find_corner_positions(region),
};
let valid_candidates: Vec<_> = candidates
.into_iter()
.filter(|&pos| self.is_valid_marker_position(pos, existing_markers))
.collect();
rng.pick(&valid_candidates).copied()
}
fn is_valid_marker_position(&self, pos: (u32, u32), existing_markers: &[Marker]) -> bool {
let min_dist = self.config.marker_placement.min_marker_distance as f32;
for marker in existing_markers {
let dx = pos.0 as f32 - marker.x as f32;
let dy = pos.1 as f32 - marker.y as f32;
let distance = (dx * dx + dy * dy).sqrt();
if distance < min_dist {
return false;
}
}
true
}
fn find_region_center(&self, region: &Region) -> Option<(u32, u32)> {
if region.cells.is_empty() {
return None;
}
let sum_x: u32 = region.cells.iter().map(|(x, _)| x).sum();
let sum_y: u32 = region.cells.iter().map(|(_, y)| y).sum();
let count = region.cells.len() as u32;
Some((sum_x / count, sum_y / count))
}
fn find_edge_positions(&self, region: &Region) -> Vec<(u32, u32)> {
region.cells.clone() }
fn find_corner_positions(&self, region: &Region) -> Vec<(u32, u32)> {
if region.cells.len() < 4 {
return region.cells.clone();
}
let min_x = region.cells.iter().map(|(x, _)| x).min().unwrap();
let max_x = region.cells.iter().map(|(x, _)| x).max().unwrap();
let min_y = region.cells.iter().map(|(_, y)| y).min().unwrap();
let max_y = region.cells.iter().map(|(_, y)| y).max().unwrap();
vec![
(*min_x, *min_y),
(*max_x, *min_y),
(*min_x, *max_y),
(*max_x, *max_y),
]
.into_iter()
.filter(|pos| region.cells.contains(pos))
.collect()
}
fn build_connectivity(&self, grid: &Grid<Tile>, regions: &[Region]) -> ConnectivityGraph {
let mut graph = ConnectivityGraph::new();
for region in regions {
graph.add_region(region.id);
}
let region_map = self.create_region_map(grid, regions);
for region in regions {
for &(x, y) in ®ion.cells {
let neighbors = match self.config.connectivity_type {
crate::semantic::ConnectivityType::FourConnected => {
vec![(0, 1), (1, 0), (0, -1), (-1, 0)]
}
crate::semantic::ConnectivityType::EightConnected => {
vec![
(0, 1),
(1, 0),
(0, -1),
(-1, 0),
(1, 1),
(1, -1),
(-1, 1),
(-1, -1),
]
}
};
for (dx, dy) in neighbors {
let nx = x as i32 + dx;
let ny = y as i32 + dy;
if let Some(neighbor_region) = region_map.get(&(nx, ny)) {
if *neighbor_region != region.id {
graph.add_edge(region.id, *neighbor_region);
}
}
}
}
}
graph
}
fn create_region_map(
&self,
_grid: &Grid<Tile>,
regions: &[Region],
) -> HashMap<(i32, i32), u32> {
let mut map = HashMap::new();
for region in regions {
for &(x, y) in ®ion.cells {
map.insert((x as i32, y as i32), region.id);
}
}
map
}
}
impl Default for SemanticExtractor {
fn default() -> Self {
Self::new(SemanticConfig::default())
}
}
pub fn extract_semantics(grid: &Grid<Tile>, config: SemanticConfig, seed: u64) -> SemanticLayers {
let mut rng = Rng::new(seed);
let extractor = SemanticExtractor::new(config);
extractor.extract(grid, &mut rng)
}
pub fn extract_semantics_default(grid: &Grid<Tile>, seed: u64) -> SemanticLayers {
extract_semantics(grid, SemanticConfig::default(), seed)
}