use crate::convex_hull::core::ConvexHull;
use crate::error::{SpatialError, SpatialResult};
pub fn check_point_containment<T: AsRef<[f64]>>(
hull: &ConvexHull,
point: T,
) -> SpatialResult<bool> {
let point_slice = point.as_ref();
if point_slice.len() != hull.points.ncols() {
return Err(SpatialError::DimensionError(format!(
"Point dimension ({}) does not match hull dimension ({})",
point_slice.len(),
hull.points.ncols()
)));
}
if let Some(equations) = &hull.equations {
return check_containment_with_equations(hull, point_slice, equations);
}
check_containment_convex_combination(hull, point_slice)
}
fn check_containment_with_equations(
_hull: &ConvexHull,
point: &[f64],
equations: &scirs2_core::ndarray::Array2<f64>,
) -> SpatialResult<bool> {
for i in 0..equations.nrows() {
let mut result = equations[[i, equations.ncols() - 1]];
for j in 0..point.len() {
result += equations[[i, j]] * point[j];
}
if result > 1e-8 {
return Ok(false);
}
}
Ok(true)
}
fn check_containment_convex_combination(hull: &ConvexHull, point: &[f64]) -> SpatialResult<bool> {
let ndim = hull.ndim();
match ndim {
1 => check_containment_1d(hull, point),
2 => check_containment_2d(hull, point),
3 => check_containment_3d(hull, point),
_ => check_containment_nd(hull, point),
}
}
fn check_containment_1d(hull: &ConvexHull, point: &[f64]) -> SpatialResult<bool> {
if hull.vertex_indices.is_empty() {
return Ok(false);
}
let vertices = hull.vertices();
if vertices.is_empty() {
return Ok(false);
}
let min_val = vertices.iter().map(|v| v[0]).fold(f64::INFINITY, f64::min);
let max_val = vertices
.iter()
.map(|v| v[0])
.fold(f64::NEG_INFINITY, f64::max);
Ok(point[0] >= min_val - 1e-10 && point[0] <= max_val + 1e-10)
}
fn check_containment_2d(hull: &ConvexHull, point: &[f64]) -> SpatialResult<bool> {
if hull.vertex_indices.is_empty() {
return Ok(false);
}
if hull.vertex_indices.len() == 1 {
let idx = hull.vertex_indices[0];
let tolerance = 1e-10;
let dx = (hull.points[[idx, 0]] - point[0]).abs();
let dy = (hull.points[[idx, 1]] - point[1]).abs();
return Ok(dx < tolerance && dy < tolerance);
}
if hull.vertex_indices.len() == 2 {
let idx1 = hull.vertex_indices[0];
let idx2 = hull.vertex_indices[1];
let p1 = [hull.points[[idx1, 0]], hull.points[[idx1, 1]]];
let p2 = [hull.points[[idx2, 0]], hull.points[[idx2, 1]]];
let cross = (p2[0] - p1[0]) * (point[1] - p1[1]) - (p2[1] - p1[1]) * (point[0] - p1[0]);
if cross.abs() > 1e-10 {
return Ok(false); }
let dot = (point[0] - p1[0]) * (p2[0] - p1[0]) + (point[1] - p1[1]) * (p2[1] - p1[1]);
let len_squared = (p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]);
return Ok(dot >= -1e-10 && dot <= len_squared + 1e-10);
}
if hull.vertex_indices.len() < 3 {
return Ok(false);
}
let mut winding_number = 0;
let vertices = &hull.vertex_indices;
let n = vertices.len();
for i in 0..n {
let j = (i + 1) % n;
let v1 = [hull.points[[vertices[i], 0]], hull.points[[vertices[i], 1]]];
let v2 = [hull.points[[vertices[j], 0]], hull.points[[vertices[j], 1]]];
if v1[1] <= point[1] {
if v2[1] > point[1] {
let cross =
(v2[0] - v1[0]) * (point[1] - v1[1]) - (v2[1] - v1[1]) * (point[0] - v1[0]);
if cross > 0.0 {
winding_number += 1;
}
}
} else if v2[1] <= point[1] {
let cross = (v2[0] - v1[0]) * (point[1] - v1[1]) - (v2[1] - v1[1]) * (point[0] - v1[0]);
if cross < 0.0 {
winding_number -= 1;
}
}
}
Ok(winding_number != 0)
}
fn check_containment_3d(hull: &ConvexHull, point: &[f64]) -> SpatialResult<bool> {
if hull.vertex_indices.is_empty() {
return Ok(false);
}
if hull.vertex_indices.len() == 1 {
let idx = hull.vertex_indices[0];
let tolerance = 1e-10;
let dx = (hull.points[[idx, 0]] - point[0]).abs();
let dy = (hull.points[[idx, 1]] - point[1]).abs();
let dz = (hull.points[[idx, 2]] - point[2]).abs();
return Ok(dx < tolerance && dy < tolerance && dz < tolerance);
}
if hull.vertex_indices.len() == 2 {
let idx1 = hull.vertex_indices[0];
let idx2 = hull.vertex_indices[1];
let p1 = [
hull.points[[idx1, 0]],
hull.points[[idx1, 1]],
hull.points[[idx1, 2]],
];
let p2 = [
hull.points[[idx2, 0]],
hull.points[[idx2, 1]],
hull.points[[idx2, 2]],
];
let v = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
let w = [point[0] - p1[0], point[1] - p1[1], point[2] - p1[2]];
let cross = [
v[1] * w[2] - v[2] * w[1],
v[2] * w[0] - v[0] * w[2],
v[0] * w[1] - v[1] * w[0],
];
let cross_mag = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
if cross_mag > 1e-10 {
return Ok(false); }
let dot = v[0] * w[0] + v[1] * w[1] + v[2] * w[2];
let len_squared = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
return Ok(dot >= -1e-10 && dot <= len_squared + 1e-10);
}
if hull.vertex_indices.len() == 3 {
return Ok(false);
}
if hull.vertex_indices.len() < 4 {
return Ok(false);
}
for simplex in &hull.simplices {
if simplex.len() != 3 {
continue; }
let v0 = [
hull.points[[simplex[0], 0]],
hull.points[[simplex[0], 1]],
hull.points[[simplex[0], 2]],
];
let v1 = [
hull.points[[simplex[1], 0]],
hull.points[[simplex[1], 1]],
hull.points[[simplex[1], 2]],
];
let v2 = [
hull.points[[simplex[2], 0]],
hull.points[[simplex[2], 1]],
hull.points[[simplex[2], 2]],
];
let edge1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
let edge2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
let mut normal = [
edge1[1] * edge2[2] - edge1[2] * edge2[1],
edge1[2] * edge2[0] - edge1[0] * edge2[2],
edge1[0] * edge2[1] - edge1[1] * edge2[0],
];
let mut centroid = [0.0, 0.0, 0.0];
for &idx in &hull.vertex_indices {
centroid[0] += hull.points[[idx, 0]];
centroid[1] += hull.points[[idx, 1]];
centroid[2] += hull.points[[idx, 2]];
}
let n_vertices = hull.vertex_indices.len() as f64;
centroid[0] /= n_vertices;
centroid[1] /= n_vertices;
centroid[2] /= n_vertices;
let face_center = [
(v0[0] + v1[0] + v2[0]) / 3.0,
(v0[1] + v1[1] + v2[1]) / 3.0,
(v0[2] + v1[2] + v2[2]) / 3.0,
];
let to_centroid = [
centroid[0] - face_center[0],
centroid[1] - face_center[1],
centroid[2] - face_center[2],
];
let centroid_dot =
normal[0] * to_centroid[0] + normal[1] * to_centroid[1] + normal[2] * to_centroid[2];
if centroid_dot > 0.0 {
normal[0] = -normal[0];
normal[1] = -normal[1];
normal[2] = -normal[2];
}
let to_point = [point[0] - v0[0], point[1] - v0[1], point[2] - v0[2]];
let dot = normal[0] * to_point[0] + normal[1] * to_point[1] + normal[2] * to_point[2];
if dot > 1e-8 {
return Ok(false);
}
}
Ok(true)
}
fn check_containment_nd(_hull: &ConvexHull, _point: &[f64]) -> SpatialResult<bool> {
Ok(false)
}
pub fn check_multiple_containment(
hull: &ConvexHull,
points: &scirs2_core::ndarray::ArrayView2<'_, f64>,
) -> SpatialResult<Vec<bool>> {
let npoints = points.nrows();
let mut results = Vec::with_capacity(npoints);
if let Some(equations) = &hull.equations {
for i in 0..npoints {
let point = points.row(i);
let is_inside = check_containment_with_equations(
hull,
point.as_slice().expect("Operation failed"),
equations,
)?;
results.push(is_inside);
}
} else {
for i in 0..npoints {
let point = points.row(i);
let is_inside = check_containment_convex_combination(
hull,
point.as_slice().expect("Operation failed"),
)?;
results.push(is_inside);
}
}
Ok(results)
}
pub fn distance_to_hull<T: AsRef<[f64]>>(hull: &ConvexHull, point: T) -> SpatialResult<f64> {
let point_slice = point.as_ref();
if point_slice.len() != hull.points.ncols() {
return Err(SpatialError::DimensionError(format!(
"Point dimension ({}) does not match hull dimension ({})",
point_slice.len(),
hull.points.ncols()
)));
}
if let Some(equations) = &hull.equations {
return compute_distance_with_equations(point_slice, equations);
}
compute_distance_to_vertices(hull, point_slice)
}
fn compute_distance_with_equations(
point: &[f64],
equations: &scirs2_core::ndarray::Array2<f64>,
) -> SpatialResult<f64> {
let mut min_distance = f64::INFINITY;
let mut is_inside = true;
for i in 0..equations.nrows() {
let mut distance = equations[[i, equations.ncols() - 1]];
let mut normal_length_sq = 0.0;
for j in 0..point.len() {
distance += equations[[i, j]] * point[j];
normal_length_sq += equations[[i, j]] * equations[[i, j]];
}
let normal_length = normal_length_sq.sqrt();
if normal_length > 1e-12 {
distance /= normal_length;
}
if distance > 1e-10 {
is_inside = false;
}
min_distance = min_distance.min(distance.abs());
}
if is_inside {
Ok(-min_distance)
} else {
Ok(min_distance)
}
}
fn compute_distance_to_vertices(hull: &ConvexHull, point: &[f64]) -> SpatialResult<f64> {
let mut min_distance = f64::INFINITY;
for &vertex_idx in &hull.vertex_indices {
let mut dist_sq = 0.0;
for d in 0..point.len() {
let diff = point[d] - hull.points[[vertex_idx, d]];
dist_sq += diff * diff;
}
min_distance = min_distance.min(dist_sq.sqrt());
}
let is_inside = check_point_containment(hull, point)?;
if is_inside {
Ok(-min_distance)
} else {
Ok(min_distance)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::convex_hull::ConvexHull;
use scirs2_core::ndarray::arr2;
#[test]
fn test_2d_containment() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
assert!(check_point_containment(&hull, [0.1, 0.1]).expect("Operation failed"));
assert!(!check_point_containment(&hull, [2.0, 2.0]).expect("Operation failed"));
assert!(check_point_containment(&hull, [0.5, 0.0]).expect("Operation failed"));
}
#[test]
fn test_1d_containment() {
let points = arr2(&[[0.0], [3.0], [1.0], [2.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
assert!(check_point_containment(&hull, [1.5]).expect("Operation failed"));
assert!(!check_point_containment(&hull, [5.0]).expect("Operation failed"));
assert!(check_point_containment(&hull, [0.0]).expect("Operation failed"));
assert!(check_point_containment(&hull, [3.0]).expect("Operation failed"));
}
#[test]
fn test_3d_containment() {
let points = arr2(&[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
assert!(check_point_containment(&hull, [0.1, 0.1, 0.1]).expect("Operation failed"));
assert!(!check_point_containment(&hull, [2.0, 2.0, 2.0]).expect("Operation failed"));
}
#[test]
fn test_multiple_containment() {
let hull_points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&hull_points.view()).expect("Operation failed");
let test_points = arr2(&[[0.1, 0.1], [2.0, 2.0], [0.2, 0.2]]);
let results =
check_multiple_containment(&hull, &test_points.view()).expect("Operation failed");
assert_eq!(results.len(), 3);
assert!(results[0]); assert!(!results[1]); assert!(results[2]); }
#[test]
fn test_distance_to_hull() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let dist = distance_to_hull(&hull, [0.1, 0.1]).expect("Operation failed");
assert!(dist < 0.0);
let dist = distance_to_hull(&hull, [2.0, 2.0]).expect("Operation failed");
assert!(dist > 0.0);
}
#[test]
fn test_dimension_mismatch_error() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let result = check_point_containment(&hull, [0.1, 0.1, 0.1]);
assert!(result.is_err());
let result = check_point_containment(&hull, [0.1]);
assert!(result.is_err());
}
#[test]
fn test_degenerate_cases() {
let points = arr2(&[[1.0, 2.0]]);
let hull_result = ConvexHull::new(&points.view());
assert!(hull_result.is_err());
let points = arr2(&[[0.0, 0.0], [1.0, 1.0]]);
let hull_result = ConvexHull::new(&points.view());
assert!(hull_result.is_err());
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let result = check_point_containment(&hull, [0.1, 0.1]).expect("Operation failed");
assert!(result);
let result = check_point_containment(&hull, [2.0, 2.0]).expect("Operation failed");
assert!(!result);
}
}