1use super::topology::{HalfEdge, INVALID_INDEX};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ConnectivityError {
31 LabelBufferTooSmall { required: usize },
33 QueueBufferTooSmall { required: usize },
35 VisitedBufferTooSmall { required: usize },
37 HalfEdgeOutOfRange { index: u32 },
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct ConnectivitySummary {
44 pub component_count: u32,
46 pub boundary_loop_count: u32,
48 pub euler_characteristic: i32,
50 pub genus: Option<u32>,
52 pub vertex_count: u32,
54 pub edge_count: u32,
56 pub face_count: u32,
58}
59
60pub fn label_components(
75 face_count: u32,
76 half_edges: &[HalfEdge],
77 labels: &mut [u32],
78 queue: &mut [u32],
79) -> Result<u32, ConnectivityError> {
80 let fc = face_count as usize;
81 if labels.len() < fc {
82 return Err(ConnectivityError::LabelBufferTooSmall { required: fc });
83 }
84 if queue.len() < fc {
85 return Err(ConnectivityError::QueueBufferTooSmall { required: fc });
86 }
87
88 for l in &mut labels[..fc] {
89 *l = INVALID_INDEX;
90 }
91
92 let mut component = 0u32;
93 let mut seed = 0u32;
94
95 while (seed as usize) < fc {
96 let mut head = 0usize;
98 let mut tail = 0usize;
99 queue[tail] = seed;
100 tail += 1;
101 labels[seed as usize] = component;
102
103 while head < tail {
104 let face = queue[head];
105 head += 1;
106
107 for he in half_edges.iter() {
109 if he.face != face {
110 continue;
111 }
112 if he.twin == INVALID_INDEX {
113 continue;
114 }
115 let twin_idx = he.twin as usize;
116 if twin_idx >= half_edges.len() {
117 return Err(ConnectivityError::HalfEdgeOutOfRange { index: he.twin });
118 }
119 let neighbour = half_edges[twin_idx].face;
120 if neighbour == INVALID_INDEX || (neighbour as usize) >= fc {
121 continue;
122 }
123 if labels[neighbour as usize] == INVALID_INDEX {
124 labels[neighbour as usize] = component;
125 queue[tail] = neighbour;
126 tail += 1;
127 }
128 }
129 }
130
131 component += 1;
132
133 while (seed as usize) < fc && labels[seed as usize] != INVALID_INDEX {
135 seed += 1;
136 }
137 }
138
139 Ok(component)
140}
141
142pub fn count_boundary_loops(
158 half_edges: &[HalfEdge],
159 visited: &mut [bool],
160) -> Result<u32, ConnectivityError> {
161 if visited.len() < half_edges.len() {
162 return Err(ConnectivityError::VisitedBufferTooSmall {
163 required: half_edges.len(),
164 });
165 }
166
167 for v in &mut visited[..half_edges.len()] {
168 *v = false;
169 }
170
171 let mut loop_count = 0u32;
172 let he_len = half_edges.len();
173
174 for start in 0..he_len {
175 if visited[start] || half_edges[start].twin != INVALID_INDEX {
176 continue;
177 }
178
179 loop_count += 1;
181 let mut cur = start as u32;
182 loop {
183 if (cur as usize) >= he_len {
184 return Err(ConnectivityError::HalfEdgeOutOfRange { index: cur });
185 }
186 if visited[cur as usize] {
187 break;
188 }
189 visited[cur as usize] = true;
190
191 let next_in_face = half_edges[cur as usize].next;
195 if (next_in_face as usize) >= he_len {
196 return Err(ConnectivityError::HalfEdgeOutOfRange {
197 index: next_in_face,
198 });
199 }
200
201 let mut candidate = next_in_face;
202 let mut rotations = 0;
205 while half_edges[candidate as usize].twin != INVALID_INDEX {
206 let twin = half_edges[candidate as usize].twin;
207 if (twin as usize) >= he_len {
208 return Err(ConnectivityError::HalfEdgeOutOfRange { index: twin });
209 }
210 let next_after_twin = half_edges[twin as usize].next;
211 if (next_after_twin as usize) >= he_len {
212 return Err(ConnectivityError::HalfEdgeOutOfRange {
213 index: next_after_twin,
214 });
215 }
216 candidate = next_after_twin;
217 rotations += 1;
218 if rotations > he_len {
219 return Err(ConnectivityError::HalfEdgeOutOfRange { index: candidate });
221 }
222 }
223
224 cur = candidate;
225 if cur as usize == start {
226 break;
227 }
228 }
229 }
230
231 Ok(loop_count)
232}
233
234#[inline]
246pub fn euler_characteristic(vertex_count: u32, face_count: u32, half_edges: &[HalfEdge]) -> i32 {
247 let boundary = half_edges
248 .iter()
249 .filter(|he| he.twin == INVALID_INDEX)
250 .count() as u32;
251 let edge_count = (half_edges.len() as u32 + boundary) / 2;
252 (vertex_count as i32) - (edge_count as i32) + (face_count as i32)
253}
254
255#[inline]
263pub fn genus_from_euler(euler: i32, boundary_loops: u32) -> Option<u32> {
264 let numerator = 2 - euler - boundary_loops as i32;
265 if numerator < 0 || numerator % 2 != 0 {
266 return None;
267 }
268 Some((numerator / 2) as u32)
269}
270
271pub fn compute_connectivity(
284 vertex_count: u32,
285 face_count: u32,
286 half_edges: &[HalfEdge],
287 labels: &mut [u32],
288 queue: &mut [u32],
289 visited: &mut [bool],
290) -> Result<ConnectivitySummary, ConnectivityError> {
291 let component_count = label_components(face_count, half_edges, labels, queue)?;
292 let boundary_loop_count = count_boundary_loops(half_edges, visited)?;
293
294 let boundary = half_edges
295 .iter()
296 .filter(|he| he.twin == INVALID_INDEX)
297 .count() as u32;
298 let edge_count = (half_edges.len() as u32 + boundary) / 2;
299 let euler = euler_characteristic(vertex_count, face_count, half_edges);
300 let genus = genus_from_euler(euler, boundary_loop_count);
301
302 Ok(ConnectivitySummary {
303 component_count,
304 boundary_loop_count,
305 euler_characteristic: euler,
306 genus,
307 vertex_count,
308 edge_count,
309 face_count,
310 })
311}
312
313#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::specialized_libs::computational_geometry::topology::{
321 build_triangle_half_edges, required_edge_slots, EdgeSlot,
322 };
323
324 fn build_he(vertex_count: u32, triangles: &[[u32; 3]]) -> (Vec<HalfEdge>, u32) {
325 let n = triangles.len() * 3;
326 let mut edges = vec![HalfEdge::default(); n];
327 let mut slots = vec![EdgeSlot::default(); required_edge_slots(triangles.len())];
328 let summary =
329 build_triangle_half_edges(vertex_count, triangles, &mut edges, &mut slots).unwrap();
330 (edges, summary.boundary_half_edges)
331 }
332
333 #[test]
336 fn single_triangle_one_component() {
337 let (edges, _) = build_he(3, &[[0, 1, 2]]);
338 let mut labels = [0u32; 1];
339 let mut queue = [0u32; 1];
340 let count = label_components(1, &edges, &mut labels, &mut queue).unwrap();
341 assert_eq!(count, 1);
342 assert_eq!(labels, [0]);
343 }
344
345 #[test]
346 fn two_disjoint_triangles_two_components() {
347 let (edges, _) = build_he(6, &[[0, 1, 2], [3, 4, 5]]);
348 let mut labels = [0u32; 2];
349 let mut queue = [0u32; 2];
350 let count = label_components(2, &edges, &mut labels, &mut queue).unwrap();
351 assert_eq!(count, 2);
352 assert_eq!(labels[0], 0);
353 assert_eq!(labels[1], 1);
354 }
355
356 #[test]
357 fn two_shared_edge_triangles_one_component() {
358 let (edges, _) = build_he(4, &[[0, 1, 2], [2, 1, 3]]);
359 let mut labels = [0u32; 2];
360 let mut queue = [0u32; 2];
361 let count = label_components(2, &edges, &mut labels, &mut queue).unwrap();
362 assert_eq!(count, 1);
363 assert_eq!(labels, [0, 0]);
364 }
365
366 #[test]
367 fn tetrahedron_one_component() {
368 let (edges, _) = build_he(4, &[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]]);
369 let mut labels = [0u32; 4];
370 let mut queue = [0u32; 4];
371 let count = label_components(4, &edges, &mut labels, &mut queue).unwrap();
372 assert_eq!(count, 1);
373 assert!(labels.iter().all(|&l| l == 0));
374 }
375
376 #[test]
377 fn components_deterministic() {
378 let (edges, _) = build_he(6, &[[0, 1, 2], [3, 4, 5]]);
379 let mut l1 = [0u32; 2];
380 let mut q1 = [0u32; 2];
381 let mut l2 = [0u32; 2];
382 let mut q2 = [0u32; 2];
383 label_components(2, &edges, &mut l1, &mut q1).unwrap();
384 label_components(2, &edges, &mut l2, &mut q2).unwrap();
385 assert_eq!(l1, l2);
386 }
387
388 #[test]
391 fn single_triangle_one_boundary_loop() {
392 let (edges, _) = build_he(3, &[[0, 1, 2]]);
393 let mut visited = [false; 3];
394 let loops = count_boundary_loops(&edges, &mut visited).unwrap();
395 assert_eq!(loops, 1);
396 }
397
398 #[test]
399 fn two_shared_edge_triangles_one_boundary_loop() {
400 let (edges, _) = build_he(4, &[[0, 1, 2], [2, 1, 3]]);
402 let mut visited = [false; 6];
403 let loops = count_boundary_loops(&edges, &mut visited).unwrap();
404 assert_eq!(loops, 1);
405 }
406
407 #[test]
408 fn tetrahedron_zero_boundary_loops() {
409 let (edges, _) = build_he(4, &[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]]);
410 let mut visited = [false; 12];
411 let loops = count_boundary_loops(&edges, &mut visited).unwrap();
412 assert_eq!(loops, 0);
413 }
414
415 #[test]
416 fn two_disjoint_triangles_two_boundary_loops() {
417 let (edges, _) = build_he(6, &[[0, 1, 2], [3, 4, 5]]);
418 let mut visited = [false; 6];
419 let loops = count_boundary_loops(&edges, &mut visited).unwrap();
420 assert_eq!(loops, 2);
421 }
422
423 #[test]
426 fn single_triangle_euler_1() {
427 let (edges, _) = build_he(3, &[[0, 1, 2]]);
429 let chi = euler_characteristic(3, 1, &edges);
430 assert_eq!(chi, 1);
431 }
432
433 #[test]
434 fn tetrahedron_euler_2() {
435 let (edges, _) = build_he(4, &[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]]);
437 let chi = euler_characteristic(4, 4, &edges);
438 assert_eq!(chi, 2);
439 }
440
441 #[test]
442 fn two_shared_edge_triangles_euler_1() {
443 let (edges, _) = build_he(4, &[[0, 1, 2], [2, 1, 3]]);
445 let chi = euler_characteristic(4, 2, &edges);
446 assert_eq!(chi, 1);
447 }
448
449 #[test]
452 fn tetrahedron_genus_0() {
453 let g = genus_from_euler(2, 0);
455 assert_eq!(g, Some(0));
456 }
457
458 #[test]
459 fn disk_genus_0() {
460 let g = genus_from_euler(1, 1);
462 assert_eq!(g, Some(0));
463 }
464
465 #[test]
466 fn torus_genus_1() {
467 let g = genus_from_euler(0, 0);
469 assert_eq!(g, Some(1));
470 }
471
472 #[test]
473 fn invalid_topology_genus_none() {
474 let g = genus_from_euler(3, 0);
476 assert_eq!(g, None);
477 }
478
479 #[test]
482 fn summary_tetrahedron() {
483 let (edges, _) = build_he(4, &[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]]);
484 let mut labels = [0u32; 4];
485 let mut queue = [0u32; 4];
486 let mut visited = [false; 12];
487 let summary =
488 compute_connectivity(4, 4, &edges, &mut labels, &mut queue, &mut visited).unwrap();
489 assert_eq!(summary.component_count, 1);
490 assert_eq!(summary.boundary_loop_count, 0);
491 assert_eq!(summary.euler_characteristic, 2);
492 assert_eq!(summary.genus, Some(0));
493 assert_eq!(summary.vertex_count, 4);
494 assert_eq!(summary.edge_count, 6);
495 assert_eq!(summary.face_count, 4);
496 }
497
498 #[test]
499 fn summary_single_triangle() {
500 let (edges, _) = build_he(3, &[[0, 1, 2]]);
501 let mut labels = [0u32; 1];
502 let mut queue = [0u32; 1];
503 let mut visited = [false; 3];
504 let summary =
505 compute_connectivity(3, 1, &edges, &mut labels, &mut queue, &mut visited).unwrap();
506 assert_eq!(summary.component_count, 1);
507 assert_eq!(summary.boundary_loop_count, 1);
508 assert_eq!(summary.euler_characteristic, 1);
509 assert_eq!(summary.genus, Some(0));
510 assert_eq!(summary.edge_count, 3);
511 }
512
513 #[test]
514 fn summary_two_disjoint_triangles() {
515 let (edges, _) = build_he(6, &[[0, 1, 2], [3, 4, 5]]);
516 let mut labels = [0u32; 2];
517 let mut queue = [0u32; 2];
518 let mut visited = [false; 6];
519 let summary =
520 compute_connectivity(6, 2, &edges, &mut labels, &mut queue, &mut visited).unwrap();
521 assert_eq!(summary.component_count, 2);
522 assert_eq!(summary.boundary_loop_count, 2);
523 assert_eq!(summary.euler_characteristic, 2);
525 assert_eq!(summary.genus, None);
527 }
528
529 #[test]
530 fn summary_deterministic_across_runs() {
531 let (edges, _) = build_he(4, &[[0, 1, 2], [2, 1, 3]]);
532 let mut l1 = [0u32; 2];
533 let mut q1 = [0u32; 2];
534 let mut v1 = [false; 6];
535 let mut l2 = [0u32; 2];
536 let mut q2 = [0u32; 2];
537 let mut v2 = [false; 6];
538 let s1 = compute_connectivity(4, 2, &edges, &mut l1, &mut q1, &mut v1).unwrap();
539 let s2 = compute_connectivity(4, 2, &edges, &mut l2, &mut q2, &mut v2).unwrap();
540 assert_eq!(s1, s2);
541 assert_eq!(l1, l2);
542 }
543
544 #[test]
547 fn label_components_rejects_small_label_buffer() {
548 let (edges, _) = build_he(3, &[[0, 1, 2]]);
549 let mut labels = [0u32; 0];
550 let mut queue = [0u32; 1];
551 let err = label_components(1, &edges, &mut labels, &mut queue).unwrap_err();
552 assert_eq!(err, ConnectivityError::LabelBufferTooSmall { required: 1 });
553 }
554
555 #[test]
556 fn count_boundary_loops_rejects_small_visited() {
557 let (edges, _) = build_he(3, &[[0, 1, 2]]);
558 let mut visited = [false; 2];
559 let err = count_boundary_loops(&edges, &mut visited).unwrap_err();
560 assert_eq!(
561 err,
562 ConnectivityError::VisitedBufferTooSmall { required: 3 }
563 );
564 }
565}