terrain_forge/
semantic_visualization.rs1use crate::semantic::{ConnectivityGraph, Masks, SemanticLayers};
7use crate::{Grid, Tile};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
12pub struct VisualizationConfig {
13 pub region_chars: HashMap<String, char>,
15 pub default_region_char: char,
17 pub wall_char: char,
19 pub floor_char: char,
21 pub show_region_ids: bool,
23 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
52pub fn visualize_regions(
54 grid: &Grid<Tile>,
55 semantic: &SemanticLayers,
56 config: &VisualizationConfig,
57) -> String {
58 let mut output = String::new();
59
60 let mut region_map = HashMap::new();
62 for region in &semantic.regions {
63 for &(x, y) in ®ion.cells {
64 region_map.insert((x as usize, y as usize), region);
65 }
66 }
67
68 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(®ion.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
93pub 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
134pub 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 let mut adjacencies: HashMap<u32, Vec<u32>> = HashMap::new();
144 for ®ion_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 (®ion_id, neighbors) in &adjacencies {
155 output.push_str(&format!(
156 "Region {}: connected to {:?}\n",
157 region_id, neighbors
158 ));
159 }
160
161 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
170pub 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 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 let mut region_counts = HashMap::new();
188 for region in &semantic.regions {
189 *region_counts.entry(®ion.kind).or_insert(0) += 1;
190 }
191 output.push_str("Region Types:\n");
192 for (kind, count) in ®ion_counts {
193 output.push_str(&format!(" {}: {}\n", kind, count));
194 }
195 output.push('\n');
196
197 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 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 output.push('\n');
225 output.push_str(&visualize_connectivity_graph(&semantic.connectivity));
226
227 output.push('\n');
229 output.push_str(&visualize_masks(grid, &semantic.masks));
230
231 output
232}
233
234pub 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}