terrain_forge/analysis/
graph.rs1use crate::analysis::delaunay::{Edge, Point};
4use std::collections::{HashMap, HashSet, VecDeque};
5
6#[derive(Debug, Clone)]
7pub struct Graph {
8 pub vertices: Vec<Point>,
9 pub edges: Vec<Edge>,
10 adjacency: HashMap<usize, Vec<usize>>,
11}
12
13impl Graph {
14 pub fn new(vertices: Vec<Point>, edges: Vec<Edge>) -> Self {
15 let mut adjacency = HashMap::new();
16
17 for (i, _) in vertices.iter().enumerate() {
18 adjacency.insert(i, Vec::new());
19 }
20
21 for edge in &edges {
22 adjacency.entry(edge.a).or_default().push(edge.b);
23 adjacency.entry(edge.b).or_default().push(edge.a);
24 }
25
26 Self {
27 vertices,
28 edges,
29 adjacency,
30 }
31 }
32
33 pub fn from_delaunay(triangulation: &crate::analysis::delaunay::DelaunayTriangulation) -> Self {
34 Self::new(triangulation.points.clone(), triangulation.edges.clone())
35 }
36
37 pub fn vertex_count(&self) -> usize {
38 self.vertices.len()
39 }
40
41 pub fn edge_count(&self) -> usize {
42 self.edges.len()
43 }
44
45 pub fn neighbors(&self, vertex: usize) -> &[usize] {
46 self.adjacency
47 .get(&vertex)
48 .map(|v| v.as_slice())
49 .unwrap_or(&[])
50 }
51
52 pub fn is_connected(&self) -> bool {
53 if self.vertices.is_empty() {
54 return true;
55 }
56
57 let mut visited = HashSet::new();
58 let mut queue = VecDeque::new();
59 queue.push_back(0);
60 visited.insert(0);
61
62 while let Some(vertex) = queue.pop_front() {
63 for &neighbor in self.neighbors(vertex) {
64 if visited.insert(neighbor) {
65 queue.push_back(neighbor);
66 }
67 }
68 }
69
70 visited.len() == self.vertices.len()
71 }
72
73 pub fn connected_components(&self) -> Vec<Vec<usize>> {
74 let mut visited = HashSet::new();
75 let mut components = Vec::new();
76
77 for i in 0..self.vertices.len() {
78 if !visited.contains(&i) {
79 let mut component = Vec::new();
80 let mut queue = VecDeque::new();
81 queue.push_back(i);
82 visited.insert(i);
83
84 while let Some(vertex) = queue.pop_front() {
85 component.push(vertex);
86 for &neighbor in self.neighbors(vertex) {
87 if visited.insert(neighbor) {
88 queue.push_back(neighbor);
89 }
90 }
91 }
92
93 components.push(component);
94 }
95 }
96
97 components
98 }
99
100 pub fn shortest_path(&self, start: usize, end: usize) -> Option<Vec<usize>> {
101 if start >= self.vertices.len() || end >= self.vertices.len() {
102 return None;
103 }
104
105 let mut distances = vec![f32::INFINITY; self.vertices.len()];
106 let mut previous = vec![None; self.vertices.len()];
107 let mut visited = HashSet::new();
108
109 #[derive(PartialEq)]
110 struct State {
111 cost: u32,
112 vertex: usize,
113 }
114
115 impl Eq for State {}
116
117 impl Ord for State {
118 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
119 other.cost.cmp(&self.cost)
120 }
121 }
122
123 impl PartialOrd for State {
124 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
125 Some(self.cmp(other))
126 }
127 }
128
129 let mut queue = std::collections::BinaryHeap::new();
130
131 distances[start] = 0.0;
132 queue.push(State {
133 cost: 0,
134 vertex: start,
135 });
136
137 while let Some(State { cost: _, vertex }) = queue.pop() {
138 if visited.contains(&vertex) {
139 continue;
140 }
141 visited.insert(vertex);
142
143 if vertex == end {
144 break;
145 }
146
147 for &neighbor in self.neighbors(vertex) {
148 if !visited.contains(&neighbor) {
149 let edge_weight = self.vertices[vertex].distance_to(&self.vertices[neighbor]);
150 let new_dist = distances[vertex] + edge_weight;
151
152 if new_dist < distances[neighbor] {
153 distances[neighbor] = new_dist;
154 previous[neighbor] = Some(vertex);
155 queue.push(State {
156 cost: (new_dist * 1000.0) as u32, vertex: neighbor,
158 });
159 }
160 }
161 }
162 }
163
164 if distances[end] == f32::INFINITY {
165 return None;
166 }
167
168 let mut path = Vec::new();
169 let mut current = Some(end);
170 while let Some(vertex) = current {
171 path.push(vertex);
172 current = previous[vertex];
173 }
174 path.reverse();
175
176 Some(path)
177 }
178
179 pub fn minimum_spanning_tree(&self) -> Graph {
180 if self.vertices.is_empty() {
181 return Graph::new(Vec::new(), Vec::new());
182 }
183
184 let mut mst_edges = Vec::new();
185 let mut edges = self.edges.clone();
186 edges.sort_by(|a, b| {
187 let len_a = self.vertices[a.a].distance_to(&self.vertices[a.b]);
188 let len_b = self.vertices[b.a].distance_to(&self.vertices[b.b]);
189 len_a.partial_cmp(&len_b).unwrap()
190 });
191
192 let mut parent: Vec<usize> = (0..self.vertices.len()).collect();
193
194 fn find(parent: &mut [usize], x: usize) -> usize {
195 if parent[x] != x {
196 parent[x] = find(parent, parent[x]);
197 }
198 parent[x]
199 }
200
201 fn union(parent: &mut [usize], x: usize, y: usize) {
202 let px = find(parent, x);
203 let py = find(parent, y);
204 if px != py {
205 parent[px] = py;
206 }
207 }
208
209 for edge in edges {
210 let root_a = find(&mut parent, edge.a);
211 let root_b = find(&mut parent, edge.b);
212
213 if root_a != root_b {
214 mst_edges.push(edge);
215 union(&mut parent, edge.a, edge.b);
216
217 if mst_edges.len() == self.vertices.len() - 1 {
218 break;
219 }
220 }
221 }
222
223 Graph::new(self.vertices.clone(), mst_edges)
224 }
225
226 pub fn clustering_coefficient(&self, vertex: usize) -> f32 {
227 let neighbors = self.neighbors(vertex);
228 if neighbors.len() < 2 {
229 return 0.0;
230 }
231
232 let mut edges_between_neighbors = 0;
233 for i in 0..neighbors.len() {
234 for j in (i + 1)..neighbors.len() {
235 let a = neighbors[i];
236 let b = neighbors[j];
237 if self.neighbors(a).contains(&b) {
238 edges_between_neighbors += 1;
239 }
240 }
241 }
242
243 let possible_edges = neighbors.len() * (neighbors.len() - 1) / 2;
244 edges_between_neighbors as f32 / possible_edges as f32
245 }
246
247 pub fn average_clustering_coefficient(&self) -> f32 {
248 if self.vertices.is_empty() {
249 return 0.0;
250 }
251
252 let sum: f32 = (0..self.vertices.len())
253 .map(|i| self.clustering_coefficient(i))
254 .sum();
255
256 sum / self.vertices.len() as f32
257 }
258
259 pub fn diameter(&self) -> f32 {
260 let mut max_distance: f32 = 0.0;
261
262 for i in 0..self.vertices.len() {
263 for j in (i + 1)..self.vertices.len() {
264 if let Some(path) = self.shortest_path(i, j) {
265 let mut distance = 0.0;
266 for k in 0..(path.len() - 1) {
267 distance += self.vertices[path[k]].distance_to(&self.vertices[path[k + 1]]);
268 }
269 max_distance = max_distance.max(distance);
270 }
271 }
272 }
273
274 max_distance
275 }
276}
277
278#[derive(Debug, Clone)]
279pub struct GraphAnalysis {
280 pub vertex_count: usize,
281 pub edge_count: usize,
282 pub is_connected: bool,
283 pub component_count: usize,
284 pub diameter: f32,
285 pub average_clustering: f32,
286}
287
288impl GraphAnalysis {
289 pub fn analyze(graph: &Graph) -> Self {
290 let components = graph.connected_components();
291
292 Self {
293 vertex_count: graph.vertex_count(),
294 edge_count: graph.edge_count(),
295 is_connected: graph.is_connected(),
296 component_count: components.len(),
297 diameter: graph.diameter(),
298 average_clustering: graph.average_clustering_coefficient(),
299 }
300 }
301}
302
303pub fn analyze_room_connectivity(room_centers: &[Point], connections: &[Edge]) -> GraphAnalysis {
305 let graph = Graph::new(room_centers.to_vec(), connections.to_vec());
306 GraphAnalysis::analyze(&graph)
307}