Skip to main content

terrain_forge/
semantic_visualization.rs

1//! Visualization utilities for semantic layers
2//!
3//! Provides functions to visualize regions, masks, connectivity graphs,
4//! and other semantic information for debugging and analysis.
5
6use crate::semantic::{ConnectivityGraph, Masks, SemanticLayers};
7use crate::{Grid, Tile};
8use std::collections::HashMap;
9
10/// Visualization configuration
11#[derive(Debug, Clone)]
12pub struct VisualizationConfig {
13    /// Characters to use for different region types
14    pub region_chars: HashMap<String, char>,
15    /// Default character for unknown regions
16    pub default_region_char: char,
17    /// Character for walls
18    pub wall_char: char,
19    /// Character for floors without regions
20    pub floor_char: char,
21    /// Show region IDs instead of types
22    pub show_region_ids: bool,
23    /// Show connectivity edges
24    pub show_connectivity: bool,
25}
26
27impl Default for VisualizationConfig {
28    fn default() -> Self {
29        let mut region_chars = HashMap::new();
30        region_chars.insert("Chamber".to_string(), 'C');
31        region_chars.insert("Tunnel".to_string(), 't');
32        region_chars.insert("Alcove".to_string(), 'a');
33        region_chars.insert("Crevice".to_string(), 'c');
34        region_chars.insert("Hall".to_string(), 'H');
35        region_chars.insert("Room".to_string(), 'R');
36        region_chars.insert("Closet".to_string(), 'c');
37        region_chars.insert("Junction".to_string(), 'J');
38        region_chars.insert("Corridor".to_string(), '-');
39        region_chars.insert("DeadEnd".to_string(), 'D');
40
41        Self {
42            region_chars,
43            default_region_char: '?',
44            wall_char: '#',
45            floor_char: '.',
46            show_region_ids: false,
47            show_connectivity: false,
48        }
49    }
50}
51
52/// Visualize regions overlaid on the grid
53pub fn visualize_regions(
54    grid: &Grid<Tile>,
55    semantic: &SemanticLayers,
56    config: &VisualizationConfig,
57) -> String {
58    let mut output = String::new();
59
60    // Create region lookup map
61    let mut region_map = HashMap::new();
62    for region in &semantic.regions {
63        for &(x, y) in &region.cells {
64            region_map.insert((x as usize, y as usize), region);
65        }
66    }
67
68    // Generate visualization
69    for y in 0..grid.height() {
70        for x in 0..grid.width() {
71            let char = if grid[(x, y)].is_wall() {
72                config.wall_char
73            } else if let Some(region) = region_map.get(&(x, y)) {
74                if config.show_region_ids {
75                    std::char::from_digit(region.id % 10, 10).unwrap_or('*')
76                } else {
77                    *config
78                        .region_chars
79                        .get(&region.kind)
80                        .unwrap_or(&config.default_region_char)
81                }
82            } else {
83                config.floor_char
84            };
85            output.push(char);
86        }
87        output.push('\n');
88    }
89
90    output
91}
92
93/// Visualize spatial masks
94pub fn visualize_masks(grid: &Grid<Tile>, masks: &Masks) -> String {
95    let mut output = String::new();
96
97    output.push_str("=== WALKABLE MASK ===\n");
98    for y in 0..grid.height() {
99        for x in 0..grid.width() {
100            let char = if y < masks.walkable.len() && x < masks.walkable[y].len() {
101                if masks.walkable[y][x] {
102                    '.'
103                } else {
104                    '#'
105                }
106            } else {
107                '?'
108            };
109            output.push(char);
110        }
111        output.push('\n');
112    }
113
114    output.push_str("\n=== NO-SPAWN MASK ===\n");
115    for y in 0..grid.height() {
116        for x in 0..grid.width() {
117            let char = if y < masks.no_spawn.len() && x < masks.no_spawn[y].len() {
118                if masks.no_spawn[y][x] {
119                    'X'
120                } else {
121                    '.'
122                }
123            } else {
124                '?'
125            };
126            output.push(char);
127        }
128        output.push('\n');
129    }
130
131    output
132}
133
134/// Visualize connectivity graph as text
135pub fn visualize_connectivity_graph(connectivity: &ConnectivityGraph) -> String {
136    let mut output = String::new();
137
138    output.push_str("=== CONNECTIVITY GRAPH ===\n");
139    output.push_str(&format!("Regions: {:?}\n", connectivity.regions));
140    output.push_str(&format!("Edges: {:?}\n", connectivity.edges));
141
142    // Create adjacency representation
143    let mut adjacencies: HashMap<u32, Vec<u32>> = HashMap::new();
144    for &region_id in &connectivity.regions {
145        adjacencies.insert(region_id, Vec::new());
146    }
147
148    for &(from, to) in &connectivity.edges {
149        adjacencies.entry(from).or_default().push(to);
150        adjacencies.entry(to).or_default().push(from);
151    }
152
153    output.push_str("\n=== ADJACENCY LIST ===\n");
154    for (&region_id, neighbors) in &adjacencies {
155        output.push_str(&format!(
156            "Region {}: connected to {:?}\n",
157            region_id, neighbors
158        ));
159    }
160
161    // Simple graph visualization
162    output.push_str("\n=== GRAPH STRUCTURE ===\n");
163    for &(from, to) in &connectivity.edges {
164        output.push_str(&format!("{} -- {}\n", from, to));
165    }
166
167    output
168}
169
170/// Create comprehensive semantic visualization
171pub fn visualize_semantic_layers(grid: &Grid<Tile>, semantic: &SemanticLayers) -> String {
172    let mut output = String::new();
173    let config = VisualizationConfig::default();
174
175    output.push_str("=== SEMANTIC LAYERS VISUALIZATION ===\n\n");
176
177    // Basic statistics
178    output.push_str(&format!("Regions: {}\n", semantic.regions.len()));
179    output.push_str(&format!("Markers: {}\n", semantic.markers.len()));
180    output.push_str(&format!(
181        "Connectivity: {} regions, {} edges\n\n",
182        semantic.connectivity.regions.len(),
183        semantic.connectivity.edges.len()
184    ));
185
186    // Region breakdown
187    let mut region_counts = HashMap::new();
188    for region in &semantic.regions {
189        *region_counts.entry(&region.kind).or_insert(0) += 1;
190    }
191    output.push_str("Region Types:\n");
192    for (kind, count) in &region_counts {
193        output.push_str(&format!("  {}: {}\n", kind, count));
194    }
195    output.push('\n');
196
197    // Marker breakdown
198    let mut marker_counts = HashMap::new();
199    for marker in &semantic.markers {
200        *marker_counts.entry(marker.tag()).or_insert(0) += 1;
201    }
202    output.push_str("Marker Types:\n");
203    for (tag, count) in &marker_counts {
204        output.push_str(&format!("  {}: {}\n", tag, count));
205    }
206    output.push('\n');
207
208    // Region visualization
209    output.push_str("=== REGION MAP ===\n");
210    output.push_str("Legend: ");
211    for (kind, &char) in &config.region_chars {
212        if region_counts.contains_key(kind) {
213            output.push_str(&format!("{}={} ", char, kind));
214        }
215    }
216    output.push_str(&format!(
217        "{}=Wall {}=Floor\n",
218        config.wall_char, config.floor_char
219    ));
220    output.push('\n');
221    output.push_str(&visualize_regions(grid, semantic, &config));
222
223    // Connectivity graph
224    output.push('\n');
225    output.push_str(&visualize_connectivity_graph(&semantic.connectivity));
226
227    // Masks
228    output.push('\n');
229    output.push_str(&visualize_masks(grid, &semantic.masks));
230
231    output
232}
233
234/// Create region ID visualization (useful for debugging)
235pub fn visualize_region_ids(grid: &Grid<Tile>, semantic: &SemanticLayers) -> String {
236    let config = VisualizationConfig {
237        show_region_ids: true,
238        ..Default::default()
239    };
240    visualize_regions(grid, semantic, &config)
241}