spatial_workflow/
spatial_workflow.rs

1//! Spatial Analysis Workflow Demo
2//!
3//! Demonstrates combining all Phase 3 features in a complete workflow
4
5use terrain_forge::{
6    algorithms,
7    spatial::{
8        dijkstra_map, distance_field, flow_field_from_dijkstra, morphological_transform,
9        DistanceMetric, MorphologyOp, PathfindingConstraints, StructuringElement,
10    },
11    Grid, Tile,
12};
13
14fn main() {
15    println!("=== Complete Spatial Analysis Workflow ===\n");
16
17    // Step 1: Generate base dungeon
18    println!("1. Generating Base Dungeon:");
19    let mut grid = Grid::new(40, 30);
20    let algo = algorithms::get("bsp").unwrap();
21    algo.generate(&mut grid, 42424);
22
23    let original_floors = grid.count(|t| t.is_floor());
24    println!(
25        "   Generated {}x{} dungeon with {} floor tiles",
26        grid.width(),
27        grid.height(),
28        original_floors
29    );
30
31    // Step 2: Clean up with morphological operations
32    println!("\n2. Cleaning Up Small Features:");
33    let cleanup_element = StructuringElement::rectangle(3, 3);
34    let cleaned = morphological_transform(&grid, MorphologyOp::Opening, &cleanup_element);
35
36    let cleaned_floors = cleaned.count(|t| t.is_floor());
37    println!(
38        "   Removed {} small features ({} floors remaining)",
39        original_floors - cleaned_floors,
40        cleaned_floors
41    );
42
43    // Step 3: Analyze distances from walls
44    println!("\n3. Analyzing Distance from Walls:");
45    let distance_map = distance_field(&cleaned, DistanceMetric::Euclidean);
46
47    let mut max_distance = 0.0;
48    let mut center_points = Vec::new();
49
50    for y in 0..distance_map.height() {
51        for x in 0..distance_map.width() {
52            let dist = distance_map.get(x, y);
53            if dist != f32::INFINITY {
54                if dist > max_distance {
55                    max_distance = dist;
56                    center_points.clear();
57                    center_points.push((x, y));
58                } else if (dist - max_distance).abs() < 0.1 {
59                    center_points.push((x, y));
60                }
61            }
62        }
63    }
64
65    println!("   Maximum distance from walls: {:.1}", max_distance);
66    println!("   Found {} center points", center_points.len());
67
68    // Step 4: Create strategic pathfinding network
69    println!("\n4. Creating Strategic Pathfinding Network:");
70
71    // Use the most central points as strategic locations
72    let strategic_points: Vec<_> = center_points.into_iter().take(3).collect();
73    println!("   Strategic points: {:?}", strategic_points);
74
75    let constraints = PathfindingConstraints::default();
76    let strategic_dijkstra = dijkstra_map(&cleaned, &strategic_points, &constraints);
77
78    // Step 5: Generate AI movement flow field
79    println!("\n5. Generating AI Movement Flow Field:");
80    let flow_field = flow_field_from_dijkstra(&strategic_dijkstra);
81
82    // Analyze flow field coverage
83    let mut flow_coverage = 0;
84    for y in 0..flow_field.height() {
85        for x in 0..flow_field.width() {
86            let (dx, dy) = flow_field.get_direction(x, y);
87            if dx != 0 || dy != 0 {
88                flow_coverage += 1;
89            }
90        }
91    }
92
93    println!("   Flow field covers {} cells", flow_coverage);
94
95    // Step 6: Identify chokepoints using morphological analysis
96    println!("\n6. Identifying Chokepoints:");
97    let thin_element = StructuringElement::rectangle(2, 2);
98    let thinned = morphological_transform(&cleaned, MorphologyOp::Erosion, &thin_element);
99
100    let mut chokepoints = Vec::new();
101    for y in 1..cleaned.height() - 1 {
102        for x in 1..cleaned.width() - 1 {
103            if let (Some(original), Some(thinned_cell)) = (
104                cleaned.get(x as i32, y as i32),
105                thinned.get(x as i32, y as i32),
106            ) {
107                if original.is_floor() && !thinned_cell.is_floor() {
108                    // This was a floor that got eroded - potential chokepoint
109                    let neighbors = count_floor_neighbors(&cleaned, x, y);
110                    if (2..=4).contains(&neighbors) {
111                        chokepoints.push((x, y));
112                    }
113                }
114            }
115        }
116    }
117
118    println!("   Found {} potential chokepoints", chokepoints.len());
119
120    // Step 7: Performance summary
121    println!("\n7. Performance Summary:");
122
123    let start = std::time::Instant::now();
124    let _ = morphological_transform(&grid, MorphologyOp::Opening, &cleanup_element);
125    println!("   Morphological cleanup: {:?}", start.elapsed());
126
127    let start = std::time::Instant::now();
128    let _ = distance_field(&cleaned, DistanceMetric::Euclidean);
129    println!("   Distance field calculation: {:?}", start.elapsed());
130
131    let start = std::time::Instant::now();
132    let _ = dijkstra_map(&cleaned, &strategic_points, &constraints);
133    println!("   Dijkstra map generation: {:?}", start.elapsed());
134
135    let start = std::time::Instant::now();
136    let _ = flow_field_from_dijkstra(&strategic_dijkstra);
137    println!("   Flow field generation: {:?}", start.elapsed());
138
139    // Step 8: Spatial analysis results
140    println!("\n8. Spatial Analysis Results:");
141    println!("   Original dungeon: {} floors", original_floors);
142    println!("   After cleanup: {} floors", cleaned_floors);
143    println!("   Strategic locations: {}", strategic_points.len());
144    println!("   Chokepoints identified: {}", chokepoints.len());
145    println!(
146        "   Flow field coverage: {:.1}%",
147        100.0 * flow_coverage as f32 / cleaned_floors as f32
148    );
149
150    println!("\n✅ Spatial analysis workflow complete!");
151    println!("   The dungeon is now ready for:");
152    println!("   - AI pathfinding using flow fields");
153    println!("   - Strategic placement at center points");
154    println!("   - Tactical analysis of chokepoints");
155    println!("   - Distance-based gameplay mechanics");
156}
157
158fn count_floor_neighbors(grid: &Grid<Tile>, x: usize, y: usize) -> usize {
159    let mut count = 0;
160    for dy in -1..=1 {
161        for dx in -1..=1 {
162            if dx == 0 && dy == 0 {
163                continue;
164            }
165            if let Some(tile) = grid.get(x as i32 + dx, y as i32 + dy) {
166                if tile.is_floor() {
167                    count += 1;
168                }
169            }
170        }
171    }
172    count
173}