use crate::convex_hull::core::ConvexHull;
use crate::error::{SpatialError, SpatialResult};
use scirs2_core::ndarray::ArrayView2;
pub fn handle_degenerate_case(points: &ArrayView2<'_, f64>) -> Option<SpatialResult<ConvexHull>> {
let npoints = points.nrows();
let ndim = points.ncols();
if npoints == 1 {
return Some(handle_single_point(points));
}
if npoints == 2 {
return Some(handle_two_points(points));
}
if is_all_collinear(points) {
return Some(handle_collinear_points(points));
}
if has_all_identical_points(points) {
return Some(handle_identical_points(points));
}
if npoints < ndim + 1 {
return Some(Err(SpatialError::ValueError(format!(
"Need at least {} points to construct a {}D convex hull, got {}",
ndim + 1,
ndim,
npoints
))));
}
None }
fn handle_single_point(points: &ArrayView2<'_, f64>) -> SpatialResult<ConvexHull> {
let npoints = points.nrows();
if npoints != 1 {
return Err(SpatialError::ValueError(
"handle_single_point called with wrong number of points".to_string(),
));
}
Ok(ConvexHull {
points: points.to_owned(),
vertex_indices: vec![0],
simplices: vec![], equations: None,
})
}
fn handle_two_points(points: &ArrayView2<'_, f64>) -> SpatialResult<ConvexHull> {
let npoints = points.nrows();
if npoints != 2 {
return Err(SpatialError::ValueError(
"handle_two_points called with wrong number of points".to_string(),
));
}
Ok(ConvexHull {
points: points.to_owned(),
vertex_indices: vec![0, 1],
simplices: vec![vec![0, 1]], equations: None,
})
}
fn handle_collinear_points(points: &ArrayView2<'_, f64>) -> SpatialResult<ConvexHull> {
let npoints = points.nrows();
if npoints < 2 {
return Err(SpatialError::ValueError(
"Need at least 2 points for collinear case".to_string(),
));
}
let (min_idx, max_idx) = find_extreme_points_on_line(points)?;
let vertex_indices = if min_idx != max_idx {
vec![min_idx, max_idx]
} else {
vec![min_idx] };
let simplices = if vertex_indices.len() == 2 {
vec![vec![vertex_indices[0], vertex_indices[1]]]
} else {
vec![]
};
Ok(ConvexHull {
points: points.to_owned(),
vertex_indices,
simplices,
equations: None,
})
}
fn handle_identical_points(points: &ArrayView2<'_, f64>) -> SpatialResult<ConvexHull> {
Ok(ConvexHull {
points: points.to_owned(),
vertex_indices: vec![0],
simplices: vec![],
equations: None,
})
}
pub fn is_all_collinear(points: &ArrayView2<'_, f64>) -> bool {
let npoints = points.nrows();
let ndim = points.ncols();
if npoints <= 2 {
return true;
}
if ndim == 1 {
return true; }
if ndim == 2 {
return is_all_collinear_2d(points);
}
is_all_collinear_nd(points)
}
fn is_all_collinear_2d(points: &ArrayView2<'_, f64>) -> bool {
let npoints = points.nrows();
if npoints <= 2 {
return true;
}
let p1 = [points[[0, 0]], points[[0, 1]]];
let p2 = [points[[1, 0]], points[[1, 1]]];
for i in 2..npoints {
let p3 = [points[[i, 0]], points[[i, 1]]];
let cross = (p2[0] - p1[0]) * (p3[1] - p1[1]) - (p2[1] - p1[1]) * (p3[0] - p1[0]);
if cross.abs() > 1e-10 {
return false;
}
}
true
}
fn is_all_collinear_nd(points: &ArrayView2<'_, f64>) -> bool {
let npoints = points.nrows();
let ndim = points.ncols();
if npoints <= 2 {
return true;
}
let mut direction_found = false;
let mut direction = vec![0.0; ndim];
for i in 1..npoints {
let mut is_different = false;
for d in 0..ndim {
direction[d] = points[[i, d]] - points[[0, d]];
if direction[d].abs() > 1e-10 {
is_different = true;
}
}
if is_different {
direction_found = true;
break;
}
}
if !direction_found {
return true; }
let length = (direction.iter().map(|x| x * x).sum::<f64>()).sqrt();
if length < 1e-10 {
return true;
}
for d in 0..ndim {
direction[d] /= length;
}
for i in 2..npoints {
let mut point_to_first = vec![0.0; ndim];
for d in 0..ndim {
point_to_first[d] = points[[i, d]] - points[[0, d]];
}
let projection: f64 = point_to_first
.iter()
.zip(direction.iter())
.map(|(a, b)| a * b)
.sum();
let mut residual = 0.0;
for d in 0..ndim {
let projected_component = projection * direction[d];
let residual_component = point_to_first[d] - projected_component;
residual += residual_component * residual_component;
}
if residual.sqrt() > 1e-10 {
return false;
}
}
true
}
pub fn has_all_identical_points(points: &ArrayView2<'_, f64>) -> bool {
let npoints = points.nrows();
let ndim = points.ncols();
if npoints <= 1 {
return true;
}
let first_point = points.row(0);
for i in 1..npoints {
for d in 0..ndim {
if (points[[i, d]] - first_point[d]).abs() > 1e-10 {
return false;
}
}
}
true
}
fn find_extreme_points_on_line(points: &ArrayView2<'_, f64>) -> SpatialResult<(usize, usize)> {
let npoints = points.nrows();
let ndim = points.ncols();
if npoints == 0 {
return Err(SpatialError::ValueError("Empty point set".to_string()));
}
if npoints == 1 {
return Ok((0, 0));
}
let mut max_spread = 0.0;
let mut spread_dim = 0;
for d in 0..ndim {
let mut min_val = points[[0, d]];
let mut max_val = points[[0, d]];
for i in 1..npoints {
let val = points[[i, d]];
min_val = min_val.min(val);
max_val = max_val.max(val);
}
let spread = max_val - min_val;
if spread > max_spread {
max_spread = spread;
spread_dim = d;
}
}
let mut min_idx = 0;
let mut max_idx = 0;
let mut min_val = points[[0, spread_dim]];
let mut max_val = points[[0, spread_dim]];
for i in 1..npoints {
let val = points[[i, spread_dim]];
if val < min_val {
min_val = val;
min_idx = i;
}
if val > max_val {
max_val = val;
max_idx = i;
}
}
Ok((min_idx, max_idx))
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::arr2;
#[test]
fn test_single_point() {
let points = arr2(&[[1.0, 2.0]]);
let hull = handle_single_point(&points.view()).expect("Operation failed");
assert_eq!(hull.vertex_indices().len(), 1);
assert_eq!(hull.simplices().len(), 0);
assert_eq!(hull.vertex_indices()[0], 0);
}
#[test]
fn test_two_points() {
let points = arr2(&[[0.0, 0.0], [1.0, 1.0]]);
let hull = handle_two_points(&points.view()).expect("Operation failed");
assert_eq!(hull.vertex_indices().len(), 2);
assert_eq!(hull.simplices().len(), 1);
assert_eq!(hull.simplices()[0], vec![0, 1]);
}
#[test]
fn test_is_all_collinear_2d() {
let collinear = arr2(&[[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]);
assert!(is_all_collinear(&collinear.view()));
let not_collinear = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
assert!(!is_all_collinear(¬_collinear.view()));
let two_points = arr2(&[[0.0, 0.0], [1.0, 1.0]]);
assert!(is_all_collinear(&two_points.view()));
}
#[test]
fn test_has_all_identical_points() {
let identical = arr2(&[[1.0, 2.0], [1.0, 2.0], [1.0, 2.0]]);
assert!(has_all_identical_points(&identical.view()));
let different = arr2(&[[1.0, 2.0], [1.0, 2.0], [1.0, 2.1]]);
assert!(!has_all_identical_points(&different.view()));
let single = arr2(&[[1.0, 2.0]]);
assert!(has_all_identical_points(&single.view()));
}
#[test]
fn test_find_extreme_points_on_line() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [1.5, 0.0]]);
let (min_idx, max_idx) =
find_extreme_points_on_line(&points.view()).expect("Operation failed");
assert_eq!(min_idx, 0);
assert_eq!(max_idx, 2);
}
#[test]
fn test_handle_degenerate_case() {
let single = arr2(&[[1.0, 2.0]]);
let result = handle_degenerate_case(&single.view());
assert!(result.is_some());
assert!(result.expect("Operation failed").is_ok());
let two = arr2(&[[0.0, 0.0], [1.0, 1.0]]);
let result = handle_degenerate_case(&two.view());
assert!(result.is_some());
assert!(result.expect("Operation failed").is_ok());
let collinear = arr2(&[[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]);
let result = handle_degenerate_case(&collinear.view());
assert!(result.is_some());
assert!(result.expect("Operation failed").is_ok());
let normal = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let result = handle_degenerate_case(&normal.view());
assert!(result.is_none());
}
#[test]
fn test_is_all_collinear_3d() {
let collinear_3d = arr2(&[[0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [2.0, 2.0, 2.0]]);
assert!(is_all_collinear(&collinear_3d.view()));
let not_collinear_3d = arr2(&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]);
assert!(!is_all_collinear(¬_collinear_3d.view()));
}
}