use crate::convex_hull::core::ConvexHull;
use crate::convex_hull::geometry::{
compute_high_dim_surface_area, compute_polygon_perimeter, compute_polyhedron_surface_area,
};
use crate::error::SpatialResult;
pub fn compute_surface_area(hull: &ConvexHull) -> SpatialResult<f64> {
match hull.ndim() {
1 => Ok(0.0), 2 => compute_2d_surface_area(hull),
3 => compute_3d_surface_area(hull),
_ => compute_nd_surface_area(hull),
}
}
fn compute_2d_surface_area(hull: &ConvexHull) -> SpatialResult<f64> {
if hull.vertex_indices.len() < 3 {
return Ok(0.0);
}
compute_polygon_perimeter(&hull.points.view(), &hull.vertex_indices)
}
fn compute_3d_surface_area(hull: &ConvexHull) -> SpatialResult<f64> {
if hull.vertex_indices.len() < 4 {
return Ok(0.0);
}
compute_polyhedron_surface_area(&hull.points.view(), &hull.simplices)
}
fn compute_nd_surface_area(hull: &ConvexHull) -> SpatialResult<f64> {
if let Some(equations) = &hull.equations {
compute_high_dim_surface_area(&hull.points.view(), &hull.vertex_indices, &equations.view())
} else {
use crate::error::SpatialError;
Err(SpatialError::NotImplementedError(
"Surface area computation for dimensions > 3 requires facet equations".to_string(),
))
}
}
pub fn compute_surface_area_bounds(hull: &ConvexHull) -> SpatialResult<(f64, f64, Option<f64>)> {
if hull.ndim() <= 3 {
let exact = compute_surface_area(hull)?;
return Ok((exact, exact, Some(exact)));
}
let exact = compute_surface_area(hull).ok();
if let Some(area) = exact {
Ok((area, area, Some(area)))
} else {
let bounds = compute_geometric_surface_area_bounds(hull)?;
Ok((bounds.0, bounds.1, None))
}
}
fn compute_geometric_surface_area_bounds(hull: &ConvexHull) -> SpatialResult<(f64, f64)> {
use crate::convex_hull::geometry::compute_bounding_box;
let ndim = hull.ndim();
let (min_coords, max_coords) = compute_bounding_box(&hull.points.view(), &hull.vertex_indices);
let mut bbox_surface_area = 0.0;
if ndim == 2 {
let width = max_coords[0] - min_coords[0];
let height = max_coords[1] - min_coords[1];
bbox_surface_area = 2.0 * (width + height);
} else if ndim == 3 {
let width = max_coords[0] - min_coords[0];
let height = max_coords[1] - min_coords[1];
let depth = max_coords[2] - min_coords[2];
bbox_surface_area = 2.0 * (width * height + width * depth + height * depth);
} else {
let mut surface_area = 0.0;
for i in 0..ndim {
let mut face_area = 1.0;
for j in 0..ndim {
if i != j {
face_area *= max_coords[j] - min_coords[j];
}
}
surface_area += 2.0 * face_area; }
bbox_surface_area = surface_area;
}
let characteristic_size = (0..ndim)
.map(|d| max_coords[d] - min_coords[d])
.fold(0.0, |acc, x| acc + x * x)
.sqrt();
let lower_bound = characteristic_size; let upper_bound = bbox_surface_area;
Ok((lower_bound.min(upper_bound), upper_bound))
}
pub fn compute_surface_to_volume_ratio(hull: &ConvexHull) -> SpatialResult<f64> {
let surface_area = compute_surface_area(hull)?;
let volume = crate::convex_hull::properties::volume::compute_volume(hull)?;
if volume.abs() < 1e-12 {
Ok(f64::INFINITY)
} else {
Ok(surface_area / volume)
}
}
pub fn compute_compactness(hull: &ConvexHull) -> SpatialResult<f64> {
let ndim = hull.ndim() as f64;
let surface_area = compute_surface_area(hull)?;
let volume = crate::convex_hull::properties::volume::compute_volume(hull)?;
if volume.abs() < 1e-12 || surface_area.abs() < 1e-12 {
return Ok(0.0); }
let sphere_surface_area = if ndim == 1.0 {
0.0 } else if ndim == 2.0 {
2.0 * std::f64::consts::PI * (volume / std::f64::consts::PI).sqrt()
} else if ndim == 3.0 {
let radius = (3.0 * volume / (4.0 * std::f64::consts::PI)).powf(1.0 / 3.0);
4.0 * std::f64::consts::PI * radius * radius
} else {
let gamma = |x: f64| (2.0 * std::f64::consts::PI).powf(x / 2.0) / tgamma_approx(x / 2.0);
let radius = (volume * gamma(ndim + 1.0) / gamma(ndim)).powf(1.0 / ndim);
ndim * gamma(ndim / 2.0) * radius.powf(ndim - 1.0)
};
if sphere_surface_area <= 0.0 {
Ok(0.0)
} else {
Ok((sphere_surface_area / surface_area).min(1.0))
}
}
fn tgamma_approx(x: f64) -> f64 {
if x > 10.0 {
(2.0 * std::f64::consts::PI / x).sqrt() * (x / std::f64::consts::E).powf(x)
} else {
match x as i32 {
1 => 1.0,
2 => 1.0,
3 => 2.0,
4 => 6.0,
5 => 24.0,
_ => x * tgamma_approx(x - 1.0), }
}
}
pub fn is_surface_area_computation_reliable(hull: &ConvexHull) -> bool {
let ndim = hull.ndim();
let nvertices = hull.vertex_indices.len();
let nsimplices = hull.simplices.len();
if ndim <= 3 {
return true;
}
if hull.equations.is_some() && nvertices > ndim {
return true;
}
if nsimplices < ndim {
return false;
}
if ndim > 10 && (nvertices < 2 * ndim || nsimplices < ndim) {
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::convex_hull::ConvexHull;
use scirs2_core::ndarray::arr2;
#[test]
fn test_compute_2d_surface_area() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let perimeter = compute_surface_area(&hull).expect("Operation failed");
assert!((perimeter - 4.0).abs() < 1e-10);
let points = arr2(&[[0.0, 0.0], [3.0, 0.0], [0.0, 4.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let perimeter = compute_surface_area(&hull).expect("Operation failed");
assert!((perimeter - 12.0).abs() < 1e-10);
}
#[test]
fn test_compute_1d_surface_area() {
let points = arr2(&[[0.0], [3.0], [1.0], [2.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let surface_area = compute_surface_area(&hull).expect("Operation failed");
assert_eq!(surface_area, 0.0);
}
#[test]
fn test_compute_surface_area_bounds() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let (lower, upper, exact) = compute_surface_area_bounds(&hull).expect("Operation failed");
assert!(exact.is_some());
assert!((exact.expect("Operation failed") - 4.0).abs() < 1e-10);
assert_eq!(lower, upper); }
#[test]
fn test_surface_to_volume_ratio() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let ratio = compute_surface_to_volume_ratio(&hull).expect("Operation failed");
assert!((ratio - 4.0).abs() < 1e-10);
}
#[test]
fn test_compactness() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let compactness = compute_compactness(&hull).expect("Operation failed");
assert!(compactness > 0.7 && compactness <= 1.0);
let points = arr2(&[[0.0, 0.0], [2.0, 0.0], [0.0, 2.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let triangle_compactness = compute_compactness(&hull).expect("Operation failed");
assert!(triangle_compactness > 0.0 && triangle_compactness <= 1.0);
}
#[test]
fn test_is_surface_area_computation_reliable() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
assert!(is_surface_area_computation_reliable(&hull));
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!(is_surface_area_computation_reliable(&hull));
}
#[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 surface_area = compute_surface_area(&hull).expect("Operation failed");
let expected_perimeter = 2.0 + (2.0_f64).sqrt();
assert!((surface_area - expected_perimeter).abs() < 1e-10);
}
}