use crate::error::{SpatialError, SpatialResult};
use scirs2_core::ndarray::{Array2, ArrayView2};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ConvexHullAlgorithm {
#[default]
Quickhull,
GrahamScan,
JarvisMarch,
}
#[derive(Debug, Clone)]
pub struct ConvexHull {
pub(crate) points: Array2<f64>,
pub(crate) vertex_indices: Vec<usize>,
pub(crate) simplices: Vec<Vec<usize>>,
pub(crate) equations: Option<Array2<f64>>,
}
impl ConvexHull {
pub fn new(points: &ArrayView2<'_, f64>) -> SpatialResult<Self> {
Self::new_with_algorithm(points, ConvexHullAlgorithm::default())
}
pub fn new_with_algorithm(
points: &ArrayView2<'_, f64>,
algorithm: ConvexHullAlgorithm,
) -> SpatialResult<Self> {
let npoints = points.nrows();
let ndim = points.ncols();
let min_points = match ndim {
1 => 1, 2 => 3, _ => ndim + 1,
};
if npoints < min_points {
return Err(SpatialError::ValueError(format!(
"Need at least {} points to construct a {}D convex hull",
min_points, ndim
)));
}
match algorithm {
ConvexHullAlgorithm::GrahamScan | ConvexHullAlgorithm::JarvisMarch => {
if ndim != 2 {
return Err(SpatialError::ValueError(format!(
"{algorithm:?} algorithm only supports 2D points, got {ndim}D"
)));
}
}
ConvexHullAlgorithm::Quickhull => {
}
}
match algorithm {
ConvexHullAlgorithm::GrahamScan => {
crate::convex_hull::algorithms::graham_scan::compute_graham_scan(points)
}
ConvexHullAlgorithm::JarvisMarch => {
crate::convex_hull::algorithms::jarvis_march::compute_jarvis_march(points)
}
ConvexHullAlgorithm::Quickhull => {
crate::convex_hull::algorithms::quickhull::compute_quickhull(points)
}
}
}
pub fn vertices(&self) -> Vec<Vec<f64>> {
self.vertex_indices
.iter()
.map(|&idx| self.points.row(idx).to_vec())
.collect()
}
pub fn vertices_array(&self) -> Array2<f64> {
let ndim = self.points.ncols();
let n_vertices = self.vertex_indices.len();
let mut vertices = Array2::zeros((n_vertices, ndim));
for (i, &idx) in self.vertex_indices.iter().enumerate() {
if idx < self.points.nrows() {
for j in 0..ndim {
vertices[[i, j]] = self.points[[idx, j]];
}
} else {
for j in 0..ndim {
vertices[[i, j]] = 0.0;
}
}
}
vertices
}
pub fn vertex_indices(&self) -> &[usize] {
&self.vertex_indices
}
pub fn simplices(&self) -> &[Vec<usize>] {
&self.simplices
}
pub fn ndim(&self) -> usize {
self.points.ncols()
}
pub fn contains<T: AsRef<[f64]>>(&self, point: T) -> SpatialResult<bool> {
crate::convex_hull::properties::containment::check_point_containment(self, point)
}
pub fn volume(&self) -> SpatialResult<f64> {
crate::convex_hull::properties::volume::compute_volume(self)
}
pub fn area(&self) -> SpatialResult<f64> {
crate::convex_hull::properties::surface_area::compute_surface_area(self)
}
pub fn compute_equations_from_simplices(
points: &Array2<f64>,
simplices: &[Vec<usize>],
ndim: usize,
) -> Option<Array2<f64>> {
let n_facets = simplices.len();
if n_facets == 0 {
return None;
}
let npoints = points.nrows();
let mut centroid = vec![0.0; ndim];
for i in 0..npoints {
for j in 0..ndim {
centroid[j] += points[[i, j]];
}
}
for c in &mut centroid {
*c /= npoints as f64;
}
let mut equations = Array2::zeros((n_facets, ndim + 1));
for (i, simplex) in simplices.iter().enumerate() {
if simplex.len() < ndim {
continue;
}
if ndim == 2 {
let p1 = [points[[simplex[0], 0]], points[[simplex[0], 1]]];
let p2 = [points[[simplex[1], 0]], points[[simplex[1], 1]]];
let dx = p2[0] - p1[0];
let dy = p2[1] - p1[1];
let len = (dx * dx + dy * dy).sqrt();
if len < 1e-10 {
continue;
}
let mut nx = -dy / len;
let mut ny = dx / len;
let mut offset = -(nx * p1[0] + ny * p1[1]);
let centroid_val = nx * centroid[0] + ny * centroid[1] + offset;
if centroid_val > 0.0 {
nx = -nx;
ny = -ny;
offset = -offset;
}
equations[[i, 0]] = nx;
equations[[i, 1]] = ny;
equations[[i, 2]] = offset;
} else if ndim == 3 {
if simplex.len() < 3 {
continue;
}
let p1 = [
points[[simplex[0], 0]],
points[[simplex[0], 1]],
points[[simplex[0], 2]],
];
let p2 = [
points[[simplex[1], 0]],
points[[simplex[1], 1]],
points[[simplex[1], 2]],
];
let p3 = [
points[[simplex[2], 0]],
points[[simplex[2], 1]],
points[[simplex[2], 2]],
];
let v1 = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
let v2 = [p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]];
let mut nx = v1[1] * v2[2] - v1[2] * v2[1];
let mut ny = v1[2] * v2[0] - v1[0] * v2[2];
let mut nz = v1[0] * v2[1] - v1[1] * v2[0];
let len = (nx * nx + ny * ny + nz * nz).sqrt();
if len < 1e-10 {
continue;
}
nx /= len;
ny /= len;
nz /= len;
let mut offset = -(nx * p1[0] + ny * p1[1] + nz * p1[2]);
let centroid_val = nx * centroid[0] + ny * centroid[1] + nz * centroid[2] + offset;
if centroid_val > 0.0 {
nx = -nx;
ny = -ny;
nz = -nz;
offset = -offset;
}
equations[[i, 0]] = nx;
equations[[i, 1]] = ny;
equations[[i, 2]] = nz;
equations[[i, 3]] = offset;
}
}
Some(equations)
}
pub fn equations(&self) -> Option<&Array2<f64>> {
self.equations.as_ref()
}
}