#![allow(dead_code)]
use std::cmp::Ordering;
use std::collections::BinaryHeap;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GeoVoronoiCell {
pub seed: usize,
pub vertices: Vec<usize>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GeoVoronoiResult {
pub labels: Vec<usize>,
pub distances: Vec<f32>,
pub cells: Vec<GeoVoronoiCell>,
}
#[derive(Debug)]
struct State {
cost: u32,
vertex: usize,
}
impl Eq for State {}
impl PartialEq for State {
fn eq(&self, other: &Self) -> bool {
self.cost == other.cost
}
}
impl Ord for State {
fn cmp(&self, other: &Self) -> Ordering {
other.cost.cmp(&self.cost)
}
}
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[allow(dead_code)]
pub fn build_adjacency(vertex_count: usize, indices: &[u32]) -> Vec<Vec<usize>> {
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); vertex_count];
let tri_count = indices.len() / 3;
for t in 0..tri_count {
let vs = [
indices[t * 3] as usize,
indices[t * 3 + 1] as usize,
indices[t * 3 + 2] as usize,
];
for k in 0..3 {
let a = vs[k];
let b = vs[(k + 1) % 3];
if !adj[a].contains(&b) {
adj[a].push(b);
}
if !adj[b].contains(&a) {
adj[b].push(a);
}
}
}
adj
}
#[allow(dead_code)]
fn edge_weight(positions: &[[f32; 3]], a: usize, b: usize) -> f32 {
let pa = positions[a];
let pb = positions[b];
let dx = pa[0] - pb[0];
let dy = pa[1] - pb[1];
let dz = pa[2] - pb[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
#[allow(dead_code)]
pub fn compute_geodesic_voronoi(
positions: &[[f32; 3]],
indices: &[u32],
seeds: &[usize],
) -> GeoVoronoiResult {
let n = positions.len();
if n == 0 || seeds.is_empty() {
return GeoVoronoiResult {
labels: vec![],
distances: vec![],
cells: vec![],
};
}
let adj = build_adjacency(n, indices);
let mut dist = vec![f32::MAX; n];
let mut labels = vec![usize::MAX; n];
let mut heap = BinaryHeap::new();
for (si, &seed) in seeds.iter().enumerate() {
if seed < n {
dist[seed] = 0.0;
labels[seed] = si;
heap.push(State {
cost: 0,
vertex: seed,
});
}
}
while let Some(State { cost, vertex }) = heap.pop() {
let d = cost as f32 * 1e-6;
if d > dist[vertex] + 1e-6 {
continue;
}
for &nb in &adj[vertex] {
let new_d = dist[vertex] + edge_weight(positions, vertex, nb);
if new_d < dist[nb] {
dist[nb] = new_d;
labels[nb] = labels[vertex];
heap.push(State {
cost: (new_d * 1e6) as u32,
vertex: nb,
});
}
}
}
let mut cells: Vec<GeoVoronoiCell> = seeds
.iter()
.map(|&s| GeoVoronoiCell {
seed: s,
vertices: Vec::new(),
})
.collect();
for (vi, &label) in labels.iter().enumerate() {
if label < cells.len() {
cells[label].vertices.push(vi);
}
}
GeoVoronoiResult {
labels,
distances: dist,
cells,
}
}
#[allow(dead_code)]
pub fn voronoi_cell_count(result: &GeoVoronoiResult) -> usize {
result.cells.len()
}
#[allow(dead_code)]
pub fn vertex_label(result: &GeoVoronoiResult, vertex: usize) -> Option<usize> {
result
.labels
.get(vertex)
.copied()
.filter(|&l| l != usize::MAX)
}
#[allow(dead_code)]
pub fn largest_cell_size(result: &GeoVoronoiResult) -> usize {
result
.cells
.iter()
.map(|c| c.vertices.len())
.max()
.unwrap_or(0)
}
#[allow(dead_code)]
pub fn voronoi_result_to_json(result: &GeoVoronoiResult) -> String {
format!("{{\"cell_count\":{}}}", result.cells.len())
}
#[cfg(test)]
mod tests {
use super::*;
fn line_mesh() -> (Vec<[f32; 3]>, Vec<u32>) {
let pos = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[2.0, 0.0, 0.0],
[0.5, 1.0, 0.0],
[1.5, 1.0, 0.0],
];
let idx = vec![0, 1, 3, 1, 2, 4, 1, 4, 3];
(pos, idx)
}
#[test]
fn test_build_adjacency() {
let (pos, idx) = line_mesh();
let adj = build_adjacency(pos.len(), &idx);
assert!(!adj[0].is_empty());
}
#[test]
fn test_single_seed() {
let (pos, idx) = line_mesh();
let result = compute_geodesic_voronoi(&pos, &idx, &[0]);
assert_eq!(voronoi_cell_count(&result), 1);
}
#[test]
fn test_two_seeds() {
let (pos, idx) = line_mesh();
let result = compute_geodesic_voronoi(&pos, &idx, &[0, 2]);
assert_eq!(voronoi_cell_count(&result), 2);
}
#[test]
fn test_vertex_label() {
let (pos, idx) = line_mesh();
let result = compute_geodesic_voronoi(&pos, &idx, &[0]);
assert_eq!(vertex_label(&result, 0), Some(0));
}
#[test]
fn test_largest_cell() {
let (pos, idx) = line_mesh();
let result = compute_geodesic_voronoi(&pos, &idx, &[0]);
assert_eq!(largest_cell_size(&result), pos.len());
}
#[test]
fn test_empty_mesh() {
let result = compute_geodesic_voronoi(&[], &[], &[]);
assert_eq!(voronoi_cell_count(&result), 0);
}
#[test]
fn test_empty_seeds() {
let pos = vec![[0.0; 3]];
let result = compute_geodesic_voronoi(&pos, &[], &[]);
assert_eq!(voronoi_cell_count(&result), 0);
}
#[test]
fn test_voronoi_result_to_json() {
let (pos, idx) = line_mesh();
let result = compute_geodesic_voronoi(&pos, &idx, &[0, 2]);
let json = voronoi_result_to_json(&result);
assert!(json.contains("\"cell_count\":2"));
}
#[test]
fn test_seed_distance_zero() {
let (pos, idx) = line_mesh();
let result = compute_geodesic_voronoi(&pos, &idx, &[0]);
assert!((result.distances[0]).abs() < 1e-6);
}
#[test]
fn test_all_vertices_labeled() {
let (pos, idx) = line_mesh();
let result = compute_geodesic_voronoi(&pos, &idx, &[0]);
for vi in 0..pos.len() {
assert!(vertex_label(&result, vi).is_some());
}
}
}