use crate::error::{SpatialError, SpatialResult};
use scirs2_core::ndarray::Array2;
use std::collections::HashSet;
pub(crate) fn bowyer_watson_nd_small(
points: &Array2<f64>,
ndim: usize,
) -> SpatialResult<Vec<Vec<usize>>> {
let npoints = points.nrows();
if npoints < ndim + 1 {
return Err(SpatialError::ValueError(format!(
"Need at least {} points for {}D triangulation, got {}",
ndim + 1,
ndim,
npoints
)));
}
if npoints == ndim + 1 {
let simplex: Vec<usize> = (0..npoints).collect();
return Ok(vec![simplex]);
}
let all_points: Vec<Vec<f64>> = (0..npoints)
.map(|i| (0..ndim).map(|j| points[[i, j]]).collect())
.collect();
let initial_simplex: Vec<usize> = (0..=ndim).collect();
let mut simplices: Vec<Vec<usize>> = vec![initial_simplex];
for point_idx in (ndim + 1)..npoints {
let point = &all_points[point_idx];
let mut bad_simplices: Vec<usize> = Vec::new();
for (simplex_idx, simplex) in simplices.iter().enumerate() {
if point_in_circumhypersphere_nd(point, simplex, &all_points, ndim) {
bad_simplices.push(simplex_idx);
}
}
if bad_simplices.is_empty() {
continue;
}
let mut boundary_facets: Vec<Vec<usize>> = Vec::new();
for &simplex_idx in &bad_simplices {
let simplex = &simplices[simplex_idx];
for excluded_vertex in 0..=ndim {
let mut face: Vec<usize> = Vec::with_capacity(ndim);
for (i, &v) in simplex.iter().enumerate() {
if i != excluded_vertex {
face.push(v);
}
}
let mut is_shared = false;
for &other_idx in &bad_simplices {
if other_idx == simplex_idx {
continue;
}
if simplex_has_face_nd(&simplices[other_idx], &face) {
is_shared = true;
break;
}
}
if !is_shared {
boundary_facets.push(face);
}
}
}
bad_simplices.sort_by(|a, b| b.cmp(a));
for simplex_idx in bad_simplices {
simplices.remove(simplex_idx);
}
for facet in boundary_facets {
let mut new_simplex = vec![point_idx];
new_simplex.extend(facet);
simplices.push(new_simplex);
}
}
Ok(simplices)
}
pub(crate) fn bowyer_watson_nd(
points: &Array2<f64>,
ndim: usize,
) -> SpatialResult<Vec<Vec<usize>>> {
let npoints = points.nrows();
if npoints <= ndim + 3 {
return bowyer_watson_nd_small(points, ndim);
}
let mut min_coords = vec![f64::INFINITY; ndim];
let mut max_coords = vec![f64::NEG_INFINITY; ndim];
for i in 0..npoints {
for j in 0..ndim {
min_coords[j] = min_coords[j].min(points[[i, j]]);
max_coords[j] = max_coords[j].max(points[[i, j]]);
}
}
let mut delta_max: f64 = 0.0;
let mut mid = vec![0.0; ndim];
for j in 0..ndim {
let delta = max_coords[j] - min_coords[j];
delta_max = delta_max.max(delta);
mid[j] = (min_coords[j] + max_coords[j]) / 2.0;
}
let margin = 100.0;
let scale = margin * (delta_max + 1.0);
let mut super_vertices: Vec<Vec<f64>> = Vec::with_capacity(ndim + 1);
let mut v0 = mid.clone();
v0[ndim - 1] -= scale * ((ndim + 1) as f64);
super_vertices.push(v0);
for i in 0..ndim {
let mut v = mid.clone();
v[i] += scale * ((ndim + 1) as f64);
for j in 0..ndim {
if j != i {
v[j] += scale * 0.1;
}
}
super_vertices.push(v);
}
let mut all_points: Vec<Vec<f64>> = Vec::with_capacity(npoints + ndim + 1);
for i in 0..npoints {
let mut point = Vec::with_capacity(ndim);
for j in 0..ndim {
point.push(points[[i, j]]);
}
all_points.push(point);
}
for sv in super_vertices {
all_points.push(sv);
}
let super_simplex: Vec<usize> = (npoints..npoints + ndim + 1).collect();
let mut simplices: Vec<Vec<usize>> = vec![super_simplex];
for point_idx in 0..npoints {
let point = &all_points[point_idx];
let mut bad_simplices: Vec<usize> = Vec::new();
for (simplex_idx, simplex) in simplices.iter().enumerate() {
if point_in_circumhypersphere_nd(point, simplex, &all_points, ndim) {
bad_simplices.push(simplex_idx);
}
}
if bad_simplices.is_empty() {
continue;
}
let mut boundary_facets: Vec<Vec<usize>> = Vec::new();
for &simplex_idx in &bad_simplices {
let simplex = &simplices[simplex_idx];
for excluded_vertex in 0..=ndim {
let mut face: Vec<usize> = Vec::with_capacity(ndim);
for (i, &v) in simplex.iter().enumerate() {
if i != excluded_vertex {
face.push(v);
}
}
let mut is_shared = false;
for &other_idx in &bad_simplices {
if other_idx == simplex_idx {
continue;
}
if simplex_has_face_nd(&simplices[other_idx], &face) {
is_shared = true;
break;
}
}
if !is_shared {
boundary_facets.push(face);
}
}
}
for facet in boundary_facets {
let mut new_simplex = vec![point_idx];
new_simplex.extend(facet);
simplices.push(new_simplex);
}
}
let super_vertex_indices: HashSet<usize> = (npoints..npoints + ndim + 1).collect();
simplices.retain(|simplex| !simplex.iter().any(|&v| super_vertex_indices.contains(&v)));
Ok(simplices)
}
fn simplex_has_face_nd(simplex: &[usize], face: &[usize]) -> bool {
let mut sorted_face: Vec<usize> = face.to_vec();
sorted_face.sort_unstable();
for excluded in 0..simplex.len() {
let mut candidate_face: Vec<usize> = simplex
.iter()
.enumerate()
.filter(|(i, _)| *i != excluded)
.map(|(_, &v)| v)
.collect();
candidate_face.sort_unstable();
if candidate_face == sorted_face {
return true;
}
}
false
}
fn point_in_circumhypersphere_nd(
point: &[f64],
simplex: &[usize],
all_points: &[Vec<f64>],
ndim: usize,
) -> bool {
if simplex.len() != ndim + 1 {
return false;
}
let n = ndim + 1;
let mut matrix = vec![vec![0.0; n + 1]; n + 1];
for (i, &vertex_idx) in simplex.iter().enumerate() {
let vertex = &all_points[vertex_idx];
let mut norm_sq: f64 = 0.0;
for j in 0..ndim {
let diff = vertex[j] - point[j];
matrix[i][j] = diff;
norm_sq += diff * diff;
}
matrix[i][ndim] = norm_sq;
matrix[i][n] = 1.0;
}
for j in 0..ndim {
matrix[n][j] = 0.0;
}
matrix[n][ndim] = 0.0;
matrix[n][n] = 1.0;
let det = determinant(&matrix);
det > 1e-10
}
fn determinant(matrix: &[Vec<f64>]) -> f64 {
let n = matrix.len();
if n == 0 {
return 0.0;
}
let mut a = matrix.to_vec();
let mut det = 1.0;
let mut sign = 1.0;
for i in 0..n {
let mut max_row = i;
let mut max_val = a[i][i].abs();
for k in i + 1..n {
let val = a[k][i].abs();
if val > max_val {
max_val = val;
max_row = k;
}
}
if max_row != i {
a.swap(i, max_row);
sign = -sign;
}
if a[i][i].abs() < 1e-15 {
return 0.0;
}
det *= a[i][i];
for k in i + 1..n {
let factor = a[k][i] / a[i][i];
for j in i + 1..n {
a[k][j] -= factor * a[i][j];
}
}
}
det * sign
}