#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DualGraphConfig {
pub weighted_arcs: bool,
}
#[allow(dead_code)]
pub fn default_dual_graph_config() -> DualGraphConfig {
DualGraphConfig { weighted_arcs: true }
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DualNode {
pub face: usize,
pub centroid: [f32; 3],
pub arcs: Vec<usize>,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
pub struct DualArc {
pub node_a: usize,
pub node_b: usize,
pub weight: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DualGraph {
pub nodes: Vec<DualNode>,
pub arcs: Vec<DualArc>,
pub config: DualGraphConfig,
}
fn tri_centroid(verts: &[[f32; 3]], indices: &[u32], tri: usize) -> [f32; 3] {
let b = tri * 3;
let v0 = verts[indices[b] as usize];
let v1 = verts[indices[b+1] as usize];
let v2 = verts[indices[b+2] as usize];
[
(v0[0]+v1[0]+v2[0])/3.0,
(v0[1]+v1[1]+v2[1])/3.0,
(v0[2]+v1[2]+v2[2])/3.0,
]
}
fn dist3(a: [f32; 3], b: [f32; 3]) -> f32 {
let dx = b[0]-a[0]; let dy = b[1]-a[1]; let dz = b[2]-a[2];
(dx*dx + dy*dy + dz*dz).sqrt()
}
#[allow(dead_code)]
pub fn build_dual_graph(
verts: &[[f32; 3]],
indices: &[u32],
config: DualGraphConfig,
) -> DualGraph {
let face_count = indices.len() / 3;
let mut nodes: Vec<DualNode> = (0..face_count)
.map(|f| DualNode {
face: f,
centroid: tri_centroid(verts, indices, f),
arcs: vec![],
})
.collect();
use std::collections::HashMap;
let mut edge_map: HashMap<(usize, usize), usize> = HashMap::new();
let mut arcs: Vec<DualArc> = Vec::new();
for f in 0..face_count {
let b = f * 3;
for k in 0..3 {
let a = indices[b + k] as usize;
let c = indices[b + (k+1) % 3] as usize;
let key = if a < c { (a, c) } else { (c, a) };
if let Some(&other) = edge_map.get(&key) {
let w = if config.weighted_arcs {
dist3(nodes[other].centroid, nodes[f].centroid)
} else {
1.0
};
let arc_idx = arcs.len();
let (na, nb) = if other < f { (other, f) } else { (f, other) };
arcs.push(DualArc { node_a: na, node_b: nb, weight: w });
nodes[na].arcs.push(arc_idx);
nodes[nb].arcs.push(arc_idx);
} else {
edge_map.insert(key, f);
}
}
}
DualGraph { nodes, arcs, config }
}
#[allow(dead_code)]
pub fn dual_node_count(g: &DualGraph) -> usize {
g.nodes.len()
}
#[allow(dead_code)]
pub fn dual_arc_count(g: &DualGraph) -> usize {
g.arcs.len()
}
#[allow(dead_code)]
pub fn dual_neighbors(g: &DualGraph, n: usize) -> Vec<usize> {
g.nodes[n].arcs.iter().map(|&ai| {
let arc = &g.arcs[ai];
if arc.node_a == n { arc.node_b } else { arc.node_a }
}).collect()
}
#[allow(dead_code)]
pub fn dual_node_centroid(g: &DualGraph, n: usize) -> [f32; 3] {
g.nodes[n].centroid
}
#[allow(dead_code)]
pub fn dual_graph_to_json(g: &DualGraph) -> String {
format!(
"{{\"node_count\":{},\"arc_count\":{}}}",
g.nodes.len(),
g.arcs.len()
)
}
#[allow(dead_code)]
pub fn dual_graph_is_connected(g: &DualGraph) -> bool {
if g.nodes.is_empty() { return true; }
let n = g.nodes.len();
let mut visited = vec![false; n];
let mut queue = std::collections::VecDeque::new();
queue.push_back(0);
visited[0] = true;
while let Some(cur) = queue.pop_front() {
for nb in dual_neighbors(g, cur) {
if !visited[nb] {
visited[nb] = true;
queue.push_back(nb);
}
}
}
visited.iter().all(|&v| v)
}
#[allow(dead_code)]
pub fn dual_path_between(g: &DualGraph, start: usize, goal: usize) -> Option<Vec<usize>> {
if start >= g.nodes.len() || goal >= g.nodes.len() { return None; }
if start == goal { return Some(vec![start]); }
let n = g.nodes.len();
let mut prev = vec![usize::MAX; n];
let mut visited = vec![false; n];
let mut queue = std::collections::VecDeque::new();
queue.push_back(start);
visited[start] = true;
while let Some(cur) = queue.pop_front() {
for nb in dual_neighbors(g, cur) {
if !visited[nb] {
visited[nb] = true;
prev[nb] = cur;
if nb == goal {
let mut path = vec![goal];
let mut c = goal;
while c != start {
c = prev[c];
path.push(c);
}
path.reverse();
return Some(path);
}
queue.push_back(nb);
}
}
}
None
}
#[allow(dead_code)]
pub fn dual_graph_clear(g: &mut DualGraph) {
g.arcs.clear();
for node in &mut g.nodes {
node.arcs.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn two_tri_mesh() -> (Vec<[f32; 3]>, Vec<u32>) {
let verts = vec![
[0.0f32, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let indices = vec![0u32, 1, 2, 0, 2, 3];
(verts, indices)
}
fn single_tri_mesh() -> (Vec<[f32; 3]>, Vec<u32>) {
let verts = vec![[0.0f32,0.0,0.0],[1.0,0.0,0.0],[0.0,1.0,0.0]];
let indices = vec![0u32,1,2];
(verts, indices)
}
#[test]
fn test_empty_mesh() {
let cfg = default_dual_graph_config();
let g = build_dual_graph(&[], &[], cfg);
assert_eq!(dual_node_count(&g), 0);
assert_eq!(dual_arc_count(&g), 0);
assert!(dual_graph_is_connected(&g));
}
#[test]
fn test_single_tri_no_arcs() {
let (verts, indices) = single_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
assert_eq!(dual_node_count(&g), 1);
assert_eq!(dual_arc_count(&g), 0);
assert!(dual_graph_is_connected(&g));
}
#[test]
fn test_two_tris_one_arc() {
let (verts, indices) = two_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
assert_eq!(dual_node_count(&g), 2);
assert_eq!(dual_arc_count(&g), 1);
}
#[test]
fn test_neighbors_two_tris() {
let (verts, indices) = two_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
let n0 = dual_neighbors(&g, 0);
let n1 = dual_neighbors(&g, 1);
assert_eq!(n0, vec![1]);
assert_eq!(n1, vec![0]);
}
#[test]
fn test_centroid_single_tri() {
let (verts, indices) = single_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
let c = dual_node_centroid(&g, 0);
assert!((c[0] - 1.0/3.0).abs() < 1e-5);
assert!((c[1] - 1.0/3.0).abs() < 1e-5);
}
#[test]
fn test_json_output() {
let (verts, indices) = two_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
let json = dual_graph_to_json(&g);
assert!(json.contains("node_count"));
assert!(json.contains("arc_count"));
}
#[test]
fn test_connected_two_tris() {
let (verts, indices) = two_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
assert!(dual_graph_is_connected(&g));
}
#[test]
fn test_path_between() {
let (verts, indices) = two_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
let path = dual_path_between(&g, 0, 1);
assert!(path.is_some());
let p = path.expect("should succeed");
assert_eq!(p.first(), Some(&0));
assert_eq!(p.last(), Some(&1));
}
#[test]
fn test_path_to_self() {
let (verts, indices) = two_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
let path = dual_path_between(&g, 0, 0);
assert_eq!(path, Some(vec![0]));
}
#[test]
fn test_clear_removes_arcs() {
let (verts, indices) = two_tri_mesh();
let cfg = default_dual_graph_config();
let mut g = build_dual_graph(&verts, &indices, cfg);
dual_graph_clear(&mut g);
assert_eq!(dual_arc_count(&g), 0);
assert!(dual_neighbors(&g, 0).is_empty());
}
#[test]
fn test_arc_weight_positive() {
let (verts, indices) = two_tri_mesh();
let cfg = default_dual_graph_config();
let g = build_dual_graph(&verts, &indices, cfg);
assert!(g.arcs[0].weight > 0.0);
}
#[test]
fn test_unweighted_arc() {
let (verts, indices) = two_tri_mesh();
let cfg = DualGraphConfig { weighted_arcs: false };
let g = build_dual_graph(&verts, &indices, cfg);
assert_eq!(g.arcs[0].weight, 1.0);
}
}