use std::cmp::Ordering;
use std::collections::BinaryHeap;
use crate::mesh::MeshBuffers;
#[allow(dead_code)]
pub type EdgeGraph = Vec<Vec<(usize, f32)>>;
#[allow(dead_code)]
pub type VoronoiLabels = Vec<usize>;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GeodesicConfig {
pub early_stop: bool,
pub level_set_eps: f32,
pub heat_iterations: usize,
pub heat_lambda: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GeodesicResult {
pub distances: Vec<f32>,
pub predecessors: Vec<usize>,
pub source: usize,
}
#[allow(dead_code)]
pub fn default_geodesic_config() -> GeodesicConfig {
GeodesicConfig {
early_stop: false,
level_set_eps: 0.05,
heat_iterations: 10,
heat_lambda: 0.5,
}
}
#[derive(Clone, Copy, PartialEq)]
struct HeapNode {
dist: f32,
vertex: usize,
}
impl Eq for HeapNode {}
impl Ord for HeapNode {
fn cmp(&self, other: &Self) -> Ordering {
other
.dist
.partial_cmp(&self.dist)
.unwrap_or(Ordering::Equal)
}
}
impl PartialOrd for HeapNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[inline]
fn dist3(a: [f32; 3], b: [f32; 3]) -> f32 {
let dx = a[0] - b[0];
let dy = a[1] - b[1];
let dz = a[2] - b[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
#[allow(dead_code)]
pub fn build_edge_graph(mesh: &MeshBuffers) -> EdgeGraph {
let n = mesh.vertex_count();
let mut adj: EdgeGraph = vec![Vec::new(); n];
let idx = &mesh.indices;
let face_count = idx.len() / 3;
for f in 0..face_count {
let a = idx[f * 3] as usize;
let b = idx[f * 3 + 1] as usize;
let c = idx[f * 3 + 2] as usize;
for (p, q) in [(a, b), (b, a), (b, c), (c, b), (c, a), (a, c)] {
if !adj[p].iter().any(|&(nb, _)| nb == q) {
let w = dist3(mesh.positions[p], mesh.positions[q]);
adj[p].push((q, w));
}
}
}
adj
}
#[allow(dead_code)]
pub fn geodesic_distances(
mesh: &MeshBuffers,
source: usize,
_cfg: &GeodesicConfig,
) -> GeodesicResult {
let n = mesh.vertex_count();
let adj = build_edge_graph(mesh);
let mut dist = vec![f32::INFINITY; n];
let mut pred = vec![usize::MAX; n];
dist[source] = 0.0;
let mut heap = BinaryHeap::new();
heap.push(HeapNode {
dist: 0.0,
vertex: source,
});
while let Some(HeapNode { dist: d, vertex: u }) = heap.pop() {
if d > dist[u] {
continue;
}
for &(nb, w) in &adj[u] {
let nd = d + w;
if nd < dist[nb] {
dist[nb] = nd;
pred[nb] = u;
heap.push(HeapNode {
dist: nd,
vertex: nb,
});
}
}
}
GeodesicResult {
distances: dist,
predecessors: pred,
source,
}
}
#[allow(dead_code)]
pub fn geodesic_distances_multi(
mesh: &MeshBuffers,
sources: &[usize],
cfg: &GeodesicConfig,
) -> GeodesicResult {
let n = mesh.vertex_count();
let adj = build_edge_graph(mesh);
let mut dist = vec![f32::INFINITY; n];
let mut pred = vec![usize::MAX; n];
let mut heap = BinaryHeap::new();
for &s in sources {
if s < n {
dist[s] = 0.0;
pred[s] = s;
heap.push(HeapNode {
dist: 0.0,
vertex: s,
});
}
}
while let Some(HeapNode { dist: d, vertex: u }) = heap.pop() {
if d > dist[u] {
continue;
}
for &(nb, w) in &adj[u] {
let nd = d + w;
if nd < dist[nb] {
dist[nb] = nd;
pred[nb] = u;
heap.push(HeapNode {
dist: nd,
vertex: nb,
});
}
}
}
let _ = cfg; let source = sources.first().copied().unwrap_or(0);
GeodesicResult {
distances: dist,
predecessors: pred,
source,
}
}
#[allow(dead_code)]
pub fn farthest_point(result: &GeodesicResult) -> (usize, f32) {
result
.distances
.iter()
.enumerate()
.filter(|(_, &d)| d.is_finite())
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
.map(|(i, &d)| (i, d))
.unwrap_or((0, 0.0))
}
#[allow(dead_code)]
pub fn geodesic_path(result: &GeodesicResult, target: usize) -> Vec<usize> {
if !result.distances[target].is_finite() {
return Vec::new();
}
let source = result.source;
if target == source {
return vec![source];
}
let mut path = Vec::new();
let mut cur = target;
let max_steps = result.distances.len() + 1;
for _ in 0..max_steps {
path.push(cur);
if cur == source {
break;
}
let p = result.predecessors[cur];
if p == usize::MAX || p == cur {
if cur != source {
return Vec::new();
}
break;
}
cur = p;
}
path.reverse();
path
}
#[allow(dead_code)]
pub fn geodesic_diameter(mesh: &MeshBuffers, cfg: &GeodesicConfig) -> (usize, usize, f32) {
let n = mesh.vertex_count();
if n < 2 {
return (0, 0, 0.0);
}
let r1 = geodesic_distances(mesh, 0, cfg);
let (a, _) = farthest_point(&r1);
let r2 = geodesic_distances(mesh, a, cfg);
let (b, d) = farthest_point(&r2);
(a, b, d)
}
#[allow(dead_code)]
pub fn level_set_isolines(result: &GeodesicResult, target_dist: f32, eps: f32) -> Vec<usize> {
result
.distances
.iter()
.enumerate()
.filter(|(_, &d)| d.is_finite() && (d - target_dist).abs() <= eps)
.map(|(i, _)| i)
.collect()
}
#[allow(dead_code)]
pub fn normalize_geodesic(result: &GeodesicResult) -> Vec<f32> {
let max = result
.distances
.iter()
.copied()
.filter(|d| d.is_finite())
.fold(0.0f32, f32::max);
if max == 0.0 {
return vec![0.0; result.distances.len()];
}
result
.distances
.iter()
.map(|&d| if d.is_finite() { d / max } else { 1.0 })
.collect()
}
#[allow(dead_code)]
pub fn geodesic_voronoi(
mesh: &MeshBuffers,
sources: &[usize],
cfg: &GeodesicConfig,
) -> VoronoiLabels {
let n = mesh.vertex_count();
if sources.is_empty() {
return vec![0; n];
}
let adj = build_edge_graph(mesh);
let mut dist = vec![f32::INFINITY; n];
let mut label = vec![0usize; n];
let mut heap = BinaryHeap::new();
for (si, &sv) in sources.iter().enumerate() {
if sv < n {
dist[sv] = 0.0;
label[sv] = si;
heap.push(HeapNode {
dist: 0.0,
vertex: sv,
});
}
}
let _ = cfg;
while let Some(HeapNode { dist: d, vertex: u }) = heap.pop() {
if d > dist[u] {
continue;
}
for &(nb, w) in &adj[u] {
let nd = d + w;
if nd < dist[nb] {
dist[nb] = nd;
label[nb] = label[u];
heap.push(HeapNode {
dist: nd,
vertex: nb,
});
}
}
}
label
}
#[allow(dead_code)]
pub fn geodesic_heat(
mesh: &MeshBuffers,
result: &GeodesicResult,
cfg: &GeodesicConfig,
) -> Vec<f32> {
let adj = build_edge_graph(mesh);
let n = mesh.vertex_count();
let lambda = cfg.heat_lambda.clamp(0.0, 1.0);
let max_finite = result
.distances
.iter()
.copied()
.filter(|d| d.is_finite())
.fold(0.0f32, f32::max);
let sentinel = max_finite * 2.0;
let mut signal: Vec<f32> = result
.distances
.iter()
.map(|&d| if d.is_finite() { d } else { sentinel })
.collect();
for _ in 0..cfg.heat_iterations {
let prev = signal.clone();
for v in 0..n {
if adj[v].is_empty() {
continue;
}
let sum: f32 = adj[v].iter().map(|&(nb, _)| prev[nb]).sum();
let avg = sum / adj[v].len() as f32;
signal[v] = prev[v] * (1.0 - lambda) + avg * lambda;
}
}
signal
}
#[allow(dead_code)]
pub fn geodesic_vertex_count(result: &GeodesicResult) -> usize {
result.distances.iter().filter(|d| d.is_finite()).count()
}
#[cfg(test)]
mod tests {
use super::*;
use oxihuman_morph::engine::MeshBuffers as MB;
fn make_mesh(positions: Vec<[f32; 3]>, indices: Vec<u32>) -> MeshBuffers {
let n = positions.len();
MeshBuffers::from_morph(MB {
positions,
normals: vec![[0.0, 0.0, 1.0]; n],
uvs: vec![[0.0, 0.0]; n],
indices,
has_suit: false,
})
}
fn single_tri() -> MeshBuffers {
make_mesh(
vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
vec![0, 1, 2],
)
}
fn two_tris() -> MeshBuffers {
make_mesh(
vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0],
],
vec![0, 1, 2, 1, 3, 2],
)
}
fn disconnected() -> MeshBuffers {
make_mesh(
vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[10.0, 0.0, 0.0],
[11.0, 0.0, 0.0],
[10.0, 1.0, 0.0],
],
vec![0, 1, 2, 3, 4, 5],
)
}
fn cfg() -> GeodesicConfig {
default_geodesic_config()
}
#[test]
fn test_default_config() {
let c = default_geodesic_config();
assert!(!c.early_stop);
assert!(c.level_set_eps > 0.0);
}
#[test]
fn test_build_edge_graph_single_tri() {
let mesh = single_tri();
let adj = build_edge_graph(&mesh);
assert_eq!(adj.len(), 3);
assert_eq!(adj[0].len(), 2);
}
#[test]
fn test_geodesic_distances_source_zero() {
let mesh = single_tri();
let r = geodesic_distances(&mesh, 0, &cfg());
assert_eq!(r.distances[0], 0.0);
assert_eq!(r.source, 0);
}
#[test]
fn test_geodesic_distances_correct_lengths() {
let mesh = single_tri();
let r = geodesic_distances(&mesh, 0, &cfg());
assert!((r.distances[1] - 1.0).abs() < 1e-5);
assert!((r.distances[2] - 1.0).abs() < 1e-5);
}
#[test]
fn test_geodesic_distances_multi_all_sources_zero() {
let mesh = two_tris();
let r = geodesic_distances_multi(&mesh, &[0, 3], &cfg());
assert_eq!(r.distances[0], 0.0);
assert_eq!(r.distances[3], 0.0);
}
#[test]
fn test_farthest_point_nonzero_distance() {
let mesh = two_tris();
let r = geodesic_distances(&mesh, 0, &cfg());
let (v, d) = farthest_point(&r);
assert!(d > 0.0);
assert_ne!(v, 0);
}
#[test]
fn test_geodesic_path_adjacent() {
let mesh = single_tri();
let r = geodesic_distances(&mesh, 0, &cfg());
let path = geodesic_path(&r, 1);
assert_eq!(path.first(), Some(&0));
assert_eq!(path.last(), Some(&1));
}
#[test]
fn test_geodesic_path_self() {
let mesh = single_tri();
let r = geodesic_distances(&mesh, 0, &cfg());
let path = geodesic_path(&r, 0);
assert_eq!(path, vec![0]);
}
#[test]
fn test_geodesic_path_unreachable_empty() {
let mesh = disconnected();
let r = geodesic_distances(&mesh, 0, &cfg());
let path = geodesic_path(&r, 3);
assert!(path.is_empty());
}
#[test]
fn test_geodesic_diameter_positive() {
let mesh = two_tris();
let (a, b, d) = geodesic_diameter(&mesh, &cfg());
assert!(d > 0.0);
assert_ne!(a, b);
}
#[test]
fn test_level_set_isolines() {
let mesh = single_tri();
let r = geodesic_distances(&mesh, 0, &cfg());
let iso = level_set_isolines(&r, 1.0, 0.1);
assert!(iso.contains(&1));
assert!(iso.contains(&2));
}
#[test]
fn test_normalize_geodesic_range() {
let mesh = two_tris();
let r = geodesic_distances(&mesh, 0, &cfg());
let n = normalize_geodesic(&r);
for &v in &n {
assert!((0.0..=1.0 + 1e-6).contains(&v));
}
assert!((n[0]).abs() < 1e-6); }
#[test]
fn test_geodesic_voronoi_two_sources() {
let mesh = two_tris();
let labels = geodesic_voronoi(&mesh, &[0, 3], &cfg());
assert_eq!(labels.len(), 4);
assert_eq!(labels[0], 0); assert_eq!(labels[3], 1); }
#[test]
fn test_geodesic_heat_length_preserved() {
let mesh = two_tris();
let r = geodesic_distances(&mesh, 0, &cfg());
let heat = geodesic_heat(&mesh, &r, &cfg());
assert_eq!(heat.len(), mesh.vertex_count());
}
#[test]
fn test_geodesic_vertex_count_connected() {
let mesh = single_tri();
let r = geodesic_distances(&mesh, 0, &cfg());
assert_eq!(geodesic_vertex_count(&r), 3);
}
#[test]
fn test_geodesic_vertex_count_disconnected() {
let mesh = disconnected();
let r = geodesic_distances(&mesh, 0, &cfg());
assert_eq!(geodesic_vertex_count(&r), 3);
}
#[test]
fn test_multi_source_middle_vertices_finite() {
let mesh = two_tris();
let r = geodesic_distances_multi(&mesh, &[0, 3], &cfg());
assert!(r.distances[1].is_finite());
assert!(r.distances[2].is_finite());
}
}