Skip to main content

delaunay_connections/
delaunay_connections.rs

1//! Delaunay Triangulation Demo
2//!
3//! Demonstrates natural room connections using Delaunay triangulation and MST
4
5use terrain_forge::{
6    algorithms::Bsp,
7    analysis::{DelaunayTriangulation, Graph, GraphAnalysis, Point},
8    Algorithm, Grid, Tile,
9};
10
11fn main() {
12    println!("=== Delaunay Triangulation Demo ===\n");
13
14    // Step 1: Generate base dungeon with rooms
15    println!("1. Generating Base Dungeon:");
16    let mut grid = Grid::new(40, 30);
17    let bsp = Bsp::default();
18    bsp.generate(&mut grid, 42424);
19
20    let room_count = count_rooms(&grid);
21    println!(
22        "   Generated {}x{} dungeon with ~{} rooms",
23        grid.width(),
24        grid.height(),
25        room_count
26    );
27
28    // Step 2: Find room centers
29    println!("\n2. Identifying Room Centers:");
30    let room_centers = find_room_centers(&grid);
31    println!("   Found {} room centers:", room_centers.len());
32    for (i, center) in room_centers.iter().enumerate() {
33        println!("     Room {}: ({:.1}, {:.1})", i + 1, center.x, center.y);
34    }
35
36    // Step 3: Create Delaunay triangulation
37    println!("\n3. Creating Delaunay Triangulation:");
38    let triangulation = DelaunayTriangulation::new(room_centers.clone());
39
40    println!("   Triangulation results:");
41    println!("     Vertices: {}", triangulation.points.len());
42    println!("     Triangles: {}", triangulation.triangles.len());
43    println!("     Edges: {}", triangulation.edges.len());
44
45    // Step 4: Generate minimum spanning tree
46    println!("\n4. Generating Minimum Spanning Tree:");
47    let mst_edges = triangulation.minimum_spanning_tree();
48
49    println!("   MST results:");
50    println!("     Edges: {}", mst_edges.len());
51    println!(
52        "     Expected for {} rooms: {}",
53        room_centers.len(),
54        room_centers.len().saturating_sub(1)
55    );
56
57    let total_length: f32 = mst_edges
58        .iter()
59        .map(|edge| edge.length(&triangulation.points))
60        .sum();
61    println!("     Total length: {:.1}", total_length);
62
63    // Step 5: Graph analysis
64    println!("\n5. Graph Analysis:");
65    let graph = Graph::new(triangulation.points.clone(), mst_edges.clone());
66    let analysis = GraphAnalysis::analyze(&graph);
67
68    println!("   Connectivity analysis:");
69    println!("     Connected: {}", analysis.is_connected);
70    println!("     Components: {}", analysis.component_count);
71    println!("     Diameter: {:.1}", analysis.diameter);
72    println!("     Avg clustering: {:.3}", analysis.average_clustering);
73
74    // Step 6: Compare with full triangulation
75    println!("\n6. Comparison: MST vs Full Triangulation:");
76    let full_graph = Graph::new(triangulation.points.clone(), triangulation.edges.clone());
77    let full_analysis = GraphAnalysis::analyze(&full_graph);
78
79    println!("   Full triangulation:");
80    println!("     Edges: {}", full_analysis.edge_count);
81    println!("     Diameter: {:.1}", full_analysis.diameter);
82    println!(
83        "     Avg clustering: {:.3}",
84        full_analysis.average_clustering
85    );
86
87    println!("   MST (optimized):");
88    println!("     Edges: {}", analysis.edge_count);
89    println!("     Diameter: {:.1}", analysis.diameter);
90    println!("     Avg clustering: {:.3}", analysis.average_clustering);
91
92    // Step 7: Pathfinding example
93    println!("\n7. Pathfinding Example:");
94    if room_centers.len() >= 2 {
95        let start = 0;
96        let end = room_centers.len() - 1;
97
98        if let Some(path) = graph.shortest_path(start, end) {
99            println!("   Path from room {} to room {}:", start + 1, end + 1);
100            print!("     Route: ");
101            for (i, &room) in path.iter().enumerate() {
102                if i > 0 {
103                    print!(" -> ");
104                }
105                print!("Room {}", room + 1);
106            }
107            println!();
108
109            let mut path_length = 0.0;
110            for i in 0..(path.len() - 1) {
111                path_length += room_centers[path[i]].distance_to(&room_centers[path[i + 1]]);
112            }
113            println!("     Total distance: {:.1}", path_length);
114        } else {
115            println!("   No path found between rooms");
116        }
117    }
118
119    // Step 8: Performance analysis
120    println!("\n8. Performance Analysis:");
121    let start = std::time::Instant::now();
122    let _ = DelaunayTriangulation::new(room_centers.clone());
123    println!("   Triangulation time: {:?}", start.elapsed());
124
125    let start = std::time::Instant::now();
126    let _ = triangulation.minimum_spanning_tree();
127    println!("   MST generation time: {:?}", start.elapsed());
128
129    println!("\n✅ Delaunay triangulation demo complete!");
130    println!("   - Natural room connections via triangulation");
131    println!("   - Optimal corridor networks with MST");
132    println!("   - Graph analysis for connectivity insights");
133}
134
135fn count_rooms(grid: &Grid<Tile>) -> usize {
136    // Simple room counting by finding floor clusters
137    let mut room_count = 0;
138    let mut visited = vec![vec![false; grid.width()]; grid.height()];
139
140    for y in 0..grid.height() {
141        for x in 0..grid.width() {
142            if !visited[y][x] {
143                if let Some(tile) = grid.get(x as i32, y as i32) {
144                    if tile.is_floor() {
145                        flood_fill(grid, &mut visited, x, y);
146                        room_count += 1;
147                    }
148                }
149            }
150        }
151    }
152
153    room_count
154}
155
156fn flood_fill(grid: &Grid<Tile>, visited: &mut [Vec<bool>], start_x: usize, start_y: usize) {
157    let mut stack = vec![(start_x, start_y)];
158
159    while let Some((x, y)) = stack.pop() {
160        if visited[y][x] {
161            continue;
162        }
163        visited[y][x] = true;
164
165        for (dx, dy) in [(-1, 0), (1, 0), (0, -1), (0, 1)] {
166            let nx = x as i32 + dx;
167            let ny = y as i32 + dy;
168
169            if nx >= 0 && ny >= 0 && (nx as usize) < grid.width() && (ny as usize) < grid.height() {
170                let nx = nx as usize;
171                let ny = ny as usize;
172
173                if !visited[ny][nx] {
174                    if let Some(tile) = grid.get(nx as i32, ny as i32) {
175                        if tile.is_floor() {
176                            stack.push((nx, ny));
177                        }
178                    }
179                }
180            }
181        }
182    }
183}
184
185fn find_room_centers(grid: &Grid<Tile>) -> Vec<Point> {
186    let mut centers = Vec::new();
187    let mut visited = vec![vec![false; grid.width()]; grid.height()];
188
189    for y in 0..grid.height() {
190        for x in 0..grid.width() {
191            if !visited[y][x] {
192                if let Some(tile) = grid.get(x as i32, y as i32) {
193                    if tile.is_floor() {
194                        let center = find_room_center(grid, &mut visited, x, y);
195                        centers.push(center);
196                    }
197                }
198            }
199        }
200    }
201
202    centers
203}
204
205fn find_room_center(
206    grid: &Grid<Tile>,
207    visited: &mut [Vec<bool>],
208    start_x: usize,
209    start_y: usize,
210) -> Point {
211    let mut room_cells = Vec::new();
212    let mut stack = vec![(start_x, start_y)];
213
214    while let Some((x, y)) = stack.pop() {
215        if visited[y][x] {
216            continue;
217        }
218        visited[y][x] = true;
219        room_cells.push((x, y));
220
221        for (dx, dy) in [(-1, 0), (1, 0), (0, -1), (0, 1)] {
222            let nx = x as i32 + dx;
223            let ny = y as i32 + dy;
224
225            if nx >= 0 && ny >= 0 && (nx as usize) < grid.width() && (ny as usize) < grid.height() {
226                let nx = nx as usize;
227                let ny = ny as usize;
228
229                if !visited[ny][nx] {
230                    if let Some(tile) = grid.get(nx as i32, ny as i32) {
231                        if tile.is_floor() {
232                            stack.push((nx, ny));
233                        }
234                    }
235                }
236            }
237        }
238    }
239
240    // Calculate centroid
241    let sum_x: usize = room_cells.iter().map(|(x, _)| x).sum();
242    let sum_y: usize = room_cells.iter().map(|(_, y)| y).sum();
243    let count = room_cells.len();
244
245    Point::new(sum_x as f32 / count as f32, sum_y as f32 / count as f32)
246}