1mod export;
25mod geometry;
26mod quickhull;
27mod types;
28
29pub mod testdata;
31
32pub use export::{export_html, export_obj};
34pub use types::{ConvexHull3D, Face, Vertex};
35
36#[derive(Debug, thiserror::Error)]
38pub enum ConvexHullError {
39 #[error("Not enough vertices to form a hull (minimum 4 required)")]
40 InsufficientVertices,
41
42 #[error("Vertices are coplanar or collinear")]
43 DegenerateConfiguration,
44
45 #[error("Maximum iterations exceeded")]
46 MaxIterationsExceeded,
47
48 #[error("IO error: {0}")]
49 IoError(#[from] std::io::Error),
50
51 #[error("Invalid face: {0}")]
52 InvalidFace(String),
53}
54
55pub type Result<T> = std::result::Result<T, ConvexHullError>;
56
57pub(crate) const EPSILON: f64 = 1e-10;
63
64pub(crate) fn compute_relative_epsilon(vertices: &[Vertex]) -> f64 {
66 if vertices.is_empty() {
67 return EPSILON;
68 }
69 let max_coord = vertices.iter().fold(0.0f64, |max, v| {
70 max.max(v.x.abs()).max(v.y.abs()).max(v.z.abs())
71 });
72 (max_coord * EPSILON).max(EPSILON)
76}
77
78pub(crate) fn deduplicate_vertices(vertices: &[Vertex], epsilon: f64) -> Vec<Vertex> {
80 let mut unique_points: Vec<Vertex> = Vec::with_capacity(vertices.len());
81 let eps_sq = epsilon * epsilon;
82
83 for point in vertices {
84 let mut is_duplicate = false;
85 for existing in &unique_points {
86 let dx = point.x - existing.x;
87 let dy = point.y - existing.y;
88 let dz = point.z - existing.z;
89 if dx * dx + dy * dy + dz * dz < eps_sq {
90 is_duplicate = true;
91 break;
92 }
93 }
94 if !is_duplicate {
95 unique_points.push(*point);
96 }
97 }
98
99 unique_points
100}