use gizmo_math::Vec3;
use std::collections::{HashMap, HashSet};
const EPSILON: f32 = 1e-4;
#[derive(Clone, Debug)]
pub struct HullFace {
pub v: [usize; 3],
pub normal: Vec3,
pub outside_set: Vec<usize>,
}
impl HullFace {
pub fn new(a: usize, b: usize, c: usize, points: &[Vec3]) -> Self {
let pa = points[a];
let pb = points[b];
let pc = points[c];
let mut normal = (pb - pa).cross(pc - pa);
let len = normal.length();
if len > 1e-8 {
normal /= len;
} else {
normal = Vec3::Y;
}
Self {
v: [a, b, c],
normal,
outside_set: Vec::new(),
}
}
pub fn distance(&self, p: Vec3, points: &[Vec3]) -> f32 {
(p - points[self.v[0]]).dot(self.normal)
}
}
pub struct ConvexHull {
pub vertices: Vec<Vec3>,
pub faces: Vec<[u32; 3]>,
}
pub fn compute_convex_hull(points: &[Vec3]) -> ConvexHull {
if points.len() < 4 {
return ConvexHull {
vertices: points.to_vec(),
faces: vec![],
};
}
let pts = points;
let mut min_x = 0;
let mut max_x = 0;
let mut min_y = 0;
let mut max_y = 0;
let mut min_z = 0;
let mut max_z = 0;
for i in 1..pts.len() {
let p = pts[i];
if p.x < pts[min_x].x {
min_x = i;
}
if p.x > pts[max_x].x {
max_x = i;
}
if p.y < pts[min_y].y {
min_y = i;
}
if p.y > pts[max_y].y {
max_y = i;
}
if p.z < pts[min_z].z {
min_z = i;
}
if p.z > pts[max_z].z {
max_z = i;
}
}
let extremes = [min_x, max_x, min_y, max_y, min_z, max_z];
let mut max_dist_sq = -1.0;
let mut p0 = 0;
let mut p1 = 0;
for &e1 in &extremes {
for &e2 in &extremes {
let dist_sq = (pts[e1] - pts[e2]).length_squared();
if dist_sq > max_dist_sq {
max_dist_sq = dist_sq;
p0 = e1;
p1 = e2;
}
}
}
if max_dist_sq < 1e-8 {
return ConvexHull {
vertices: vec![pts[0]],
faces: vec![],
};
}
let mut max_dist_line = -1.0;
let mut p2 = 0;
let line_dir = (pts[p1] - pts[p0]).normalize();
for i in 0..pts.len() {
let p = pts[i];
let v = p - pts[p0];
let proj = v.dot(line_dir);
let dist_sq = (v - line_dir * proj).length_squared();
if dist_sq > max_dist_line {
max_dist_line = dist_sq;
p2 = i;
}
}
if max_dist_line < 1e-8 {
return ConvexHull {
vertices: vec![pts[p0], pts[p1]],
faces: vec![],
};
}
let plane_normal = (pts[p1] - pts[p0]).cross(pts[p2] - pts[p0]).normalize();
let mut max_dist_plane = -1.0;
let mut p3 = 0;
for i in 0..pts.len() {
let dist = (pts[i] - pts[p0]).dot(plane_normal).abs();
if dist > max_dist_plane {
max_dist_plane = dist;
p3 = i;
}
}
if max_dist_plane < 1e-8 {
let mut dedup = pts.to_vec();
dedup.dedup_by(|a, b| {
(a.x - b.x).abs() < 1e-4 && (a.y - b.y).abs() < 1e-4 && (a.z - b.z).abs() < 1e-4
});
return ConvexHull {
vertices: dedup,
faces: vec![],
};
}
let mut faces = Vec::new();
let mut f0 = HullFace::new(p0, p1, p2, pts);
if f0.distance(pts[p3], pts) > 0.0 {
f0 = HullFace::new(p0, p2, p1, pts);
}
let mut f1 = HullFace::new(p0, p3, p1, pts);
if f1.distance(pts[p2], pts) > 0.0 {
f1 = HullFace::new(p0, p1, p3, pts);
}
let mut f2 = HullFace::new(p1, p3, p2, pts);
if f2.distance(pts[p0], pts) > 0.0 {
f2 = HullFace::new(p1, p2, p3, pts);
}
let mut f3 = HullFace::new(p2, p3, p0, pts);
if f3.distance(pts[p1], pts) > 0.0 {
f3 = HullFace::new(p2, p0, p3, pts);
}
faces.push(f0);
faces.push(f1);
faces.push(f2);
faces.push(f3);
for i in 0..pts.len() {
if i == p0 || i == p1 || i == p2 || i == p3 {
continue;
}
let p = pts[i];
let mut best_face = usize::MAX;
let mut max_d = EPSILON;
for (f_idx, f) in faces.iter().enumerate() {
let d = f.distance(p, pts);
if d > max_d {
max_d = d;
best_face = f_idx;
}
}
if best_face != usize::MAX {
faces[best_face].outside_set.push(i);
}
}
loop {
let mut active_face_idx = usize::MAX;
for (i, f) in faces.iter().enumerate() {
if !f.outside_set.is_empty() {
active_face_idx = i;
break;
}
}
if active_face_idx == usize::MAX {
break; }
let mut best_pt = 0;
let mut max_d = -1.0;
let active_face = &faces[active_face_idx];
for &idx in &active_face.outside_set {
let d = active_face.distance(pts[idx], pts);
if d > max_d {
max_d = d;
best_pt = idx;
}
}
let p = pts[best_pt];
let mut visible_faces = Vec::new();
for (i, f) in faces.iter().enumerate() {
if f.distance(p, pts) > EPSILON {
visible_faces.push(i);
}
}
let mut edge_counts = HashMap::new();
for &f_idx in &visible_faces {
let f = &faces[f_idx];
let edges = [(f.v[0], f.v[1]), (f.v[1], f.v[2]), (f.v[2], f.v[0])];
for &(u, v) in &edges {
*edge_counts.entry((u, v)).or_insert(0) += 1;
}
}
let mut horizon_edges = Vec::new();
for (&(u, v), &count) in &edge_counts {
if count == 1 && !edge_counts.contains_key(&(v, u)) {
horizon_edges.push((u, v));
}
}
let mut orphaned_points = Vec::new();
for &f_idx in &visible_faces {
for &idx in &faces[f_idx].outside_set {
if idx != best_pt {
orphaned_points.push(idx);
}
}
}
let mut new_faces = Vec::new();
for (i, f) in faces.into_iter().enumerate() {
if !visible_faces.contains(&i) {
new_faces.push(f);
}
}
faces = new_faces;
let mut new_face_indices = Vec::new();
for &(u, v) in &horizon_edges {
let new_face = HullFace::new(u, v, best_pt, pts);
faces.push(new_face);
new_face_indices.push(faces.len() - 1);
}
for idx in orphaned_points {
let pt = pts[idx];
let mut best_face = usize::MAX;
let mut best_d = EPSILON;
for &f_idx in &new_face_indices {
let d = faces[f_idx].distance(pt, pts);
if d > best_d {
best_d = d;
best_face = f_idx;
}
}
if best_face != usize::MAX {
faces[best_face].outside_set.push(idx);
}
}
}
let mut vertex_set = HashSet::new();
for f in &faces {
vertex_set.insert(f.v[0]);
vertex_set.insert(f.v[1]);
vertex_set.insert(f.v[2]);
}
let mut out_vertices = Vec::new();
let mut old_to_new = HashMap::new();
for old_idx in vertex_set {
out_vertices.push(pts[old_idx]);
old_to_new.insert(old_idx, (out_vertices.len() - 1) as u32);
}
let mut out_faces = Vec::new();
for f in &faces {
out_faces.push([
old_to_new[&f.v[0]],
old_to_new[&f.v[1]],
old_to_new[&f.v[2]],
]);
}
ConvexHull {
vertices: out_vertices,
faces: out_faces,
}
}