use super::*;
#[derive(Debug, Clone)]
pub struct TriMesh {
pub vertices: Vec<Vec3>,
pub indices: Vec<[usize; 3]>,
}
#[derive(Debug, Clone)]
pub struct ConvexDecomposition {
pub parts: Vec<Vec<usize>>,
}
pub struct AcdConfig {
pub max_concavity: f32,
pub max_parts: usize,
}
impl Default for AcdConfig {
fn default() -> Self {
Self {
max_concavity: 0.05,
max_parts: 32,
}
}
}
pub fn convex_decompose(
mesh: &TriMesh,
config: &AcdConfig,
) -> Result<ConvexDecomposition, crate::HisabError> {
if mesh.indices.is_empty() {
return Err(crate::HisabError::InvalidInput(
"mesh has no triangles".into(),
));
}
let all_verts: Vec<usize> = (0..mesh.vertices.len()).collect();
let mut parts: Vec<Vec<usize>> = vec![all_verts];
for _ in 0..config.max_parts {
if parts.len() >= config.max_parts {
break;
}
let mut worst_idx = 0;
let mut worst_concavity = 0.0f32;
for (i, part) in parts.iter().enumerate() {
let c = compute_concavity(mesh, part);
if c > worst_concavity {
worst_concavity = c;
worst_idx = i;
}
}
if worst_concavity <= config.max_concavity {
break; }
let part = parts.remove(worst_idx);
let (left, right) = split_part_pca(mesh, &part);
if !left.is_empty() {
parts.push(left);
}
if !right.is_empty() {
parts.push(right);
}
}
Ok(ConvexDecomposition { parts })
}
fn compute_concavity(mesh: &TriMesh, part: &[usize]) -> f32 {
if part.len() < 4 {
return 0.0;
}
let mut centroid = Vec3::ZERO;
for &idx in part {
centroid += mesh.vertices[idx];
}
centroid /= part.len() as f32;
let avg_dist: f32 = part
.iter()
.map(|&idx| (mesh.vertices[idx] - centroid).length())
.sum::<f32>()
/ part.len() as f32;
let variance: f32 = part
.iter()
.map(|&idx| {
let d = (mesh.vertices[idx] - centroid).length() - avg_dist;
d * d
})
.sum::<f32>()
/ part.len() as f32;
variance.sqrt() / avg_dist.max(crate::EPSILON_F32)
}
fn split_part_pca(mesh: &TriMesh, part: &[usize]) -> (Vec<usize>, Vec<usize>) {
if part.len() < 2 {
return (part.to_vec(), Vec::new());
}
let mut centroid = Vec3::ZERO;
for &idx in part {
centroid += mesh.vertices[idx];
}
centroid /= part.len() as f32;
let mut min = Vec3::splat(f32::INFINITY);
let mut max = Vec3::splat(f32::NEG_INFINITY);
for &idx in part {
let v = mesh.vertices[idx];
min = min.min(v);
max = max.max(v);
}
let extent = max - min;
let split_axis = if extent.x >= extent.y && extent.x >= extent.z {
0
} else if extent.y >= extent.z {
1
} else {
2
};
let split_val = centroid.to_array()[split_axis];
let mut left = Vec::new();
let mut right = Vec::new();
for &idx in part {
if mesh.vertices[idx].to_array()[split_axis] <= split_val {
left.push(idx);
} else {
right.push(idx);
}
}
if left.is_empty() || right.is_empty() {
let mid = part.len() / 2;
left = part[..mid].to_vec();
right = part[mid..].to_vec();
}
(left, right)
}